From 23189e26936d3e5fc1e023e0ea2b543585401491 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold Date: Mon, 5 Jul 2021 17:36:41 +0200 Subject: [PATCH 001/145] Repair --- libs/runtimes/win-x64/libmultihash.dll | Bin 1321472 -> 1333248 bytes src/Miningcore.Tests/Crypto/HashingTests.cs | 44 +- src/Miningcore/AutofacModule.cs | 19 +- src/Miningcore/Blockchain/Ergo/ErgoJob.cs | 24 + .../Blockchain/Ergo/ErgoJobManager.cs | 196 + src/Miningcore/Blockchain/Ergo/ErgoPool.cs | 426 + .../Blockchain/Ergo/RPC/ErgoClient.cs | 10157 ++++++++++++++++ src/Miningcore/Blockchain/Ergo/RPC/ergo.nswag | 100 + src/Miningcore/Blockchain/Share.cs | 2 +- src/Miningcore/Configuration/ClusterConfig.cs | 10 +- .../Configuration/ClusterConfigExtensions.cs | 14 +- .../Crypto/Hashing/Algorithms/Blake2b.cs | 22 + src/Miningcore/Native/LibMultihash.cs | 5 +- src/Native/libmultihash/Makefile | 5 +- .../libmultihash/blake2/ref/blake2-impl.h | 160 + src/Native/libmultihash/blake2/ref/blake2.h | 195 + .../libmultihash/blake2/ref/blake2b-ref.c | 379 + .../libmultihash/blake2/ref/blake2bp-ref.c | 359 + .../libmultihash/blake2/ref/blake2s-ref.c | 367 + .../libmultihash/blake2/ref/blake2sp-ref.c | 359 + .../libmultihash/blake2/ref/blake2xb-ref.c | 241 + .../libmultihash/blake2/ref/blake2xs-ref.c | 239 + src/Native/libmultihash/blake2/ref/genkat-c.c | 139 + .../libmultihash/blake2/ref/genkat-json.c | 154 + src/Native/libmultihash/blake2/ref/makefile | 40 + .../libmultihash/blake2/sse/blake2-config.h | 72 + .../libmultihash/blake2/sse/blake2-impl.h | 160 + src/Native/libmultihash/blake2/sse/blake2.h | 195 + src/Native/libmultihash/blake2/sse/blake2b | Bin 0 -> 46032 bytes .../blake2/sse/blake2b-load-sse2.h | 68 + .../blake2/sse/blake2b-load-sse41.h | 402 + .../libmultihash/blake2/sse/blake2b-round.h | 157 + src/Native/libmultihash/blake2/sse/blake2b.c | 373 + src/Native/libmultihash/blake2/sse/blake2bp | Bin 0 -> 50488 bytes src/Native/libmultihash/blake2/sse/blake2bp.c | 361 + src/Native/libmultihash/blake2/sse/blake2s | Bin 0 -> 29616 bytes .../blake2/sse/blake2s-load-sse2.h | 60 + .../blake2/sse/blake2s-load-sse41.h | 236 + .../blake2/sse/blake2s-load-xop.h | 191 + .../libmultihash/blake2/sse/blake2s-round.h | 88 + src/Native/libmultihash/blake2/sse/blake2s.c | 363 + src/Native/libmultihash/blake2/sse/blake2sp | Bin 0 -> 34072 bytes src/Native/libmultihash/blake2/sse/blake2sp.c | 358 + src/Native/libmultihash/blake2/sse/blake2xb | Bin 0 -> 95488 bytes src/Native/libmultihash/blake2/sse/blake2xb.c | 241 + src/Native/libmultihash/blake2/sse/blake2xs | Bin 0 -> 91360 bytes src/Native/libmultihash/blake2/sse/blake2xs.c | 239 + src/Native/libmultihash/blake2/sse/genkat-c.c | 139 + .../libmultihash/blake2/sse/genkat-json.c | 154 + src/Native/libmultihash/blake2s.c | 15 - src/Native/libmultihash/blake2s.h | 16 - src/Native/libmultihash/exports.cpp | 16 +- src/Native/libmultihash/libmultihash.vcxproj | 12 +- .../libmultihash/libmultihash.vcxproj.filters | 36 +- src/Native/libmultihash/x25x.c | 10 +- 55 files changed, 17539 insertions(+), 79 deletions(-) create mode 100644 src/Miningcore/Blockchain/Ergo/ErgoJob.cs create mode 100644 src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs create mode 100644 src/Miningcore/Blockchain/Ergo/ErgoPool.cs create mode 100644 src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs create mode 100644 src/Miningcore/Blockchain/Ergo/RPC/ergo.nswag create mode 100644 src/Miningcore/Crypto/Hashing/Algorithms/Blake2b.cs create mode 100644 src/Native/libmultihash/blake2/ref/blake2-impl.h create mode 100644 src/Native/libmultihash/blake2/ref/blake2.h create mode 100644 src/Native/libmultihash/blake2/ref/blake2b-ref.c create mode 100644 src/Native/libmultihash/blake2/ref/blake2bp-ref.c create mode 100644 src/Native/libmultihash/blake2/ref/blake2s-ref.c create mode 100644 src/Native/libmultihash/blake2/ref/blake2sp-ref.c create mode 100644 src/Native/libmultihash/blake2/ref/blake2xb-ref.c create mode 100644 src/Native/libmultihash/blake2/ref/blake2xs-ref.c create mode 100644 src/Native/libmultihash/blake2/ref/genkat-c.c create mode 100644 src/Native/libmultihash/blake2/ref/genkat-json.c create mode 100644 src/Native/libmultihash/blake2/ref/makefile create mode 100644 src/Native/libmultihash/blake2/sse/blake2-config.h create mode 100644 src/Native/libmultihash/blake2/sse/blake2-impl.h create mode 100644 src/Native/libmultihash/blake2/sse/blake2.h create mode 100644 src/Native/libmultihash/blake2/sse/blake2b create mode 100644 src/Native/libmultihash/blake2/sse/blake2b-load-sse2.h create mode 100644 src/Native/libmultihash/blake2/sse/blake2b-load-sse41.h create mode 100644 src/Native/libmultihash/blake2/sse/blake2b-round.h create mode 100644 src/Native/libmultihash/blake2/sse/blake2b.c create mode 100644 src/Native/libmultihash/blake2/sse/blake2bp create mode 100644 src/Native/libmultihash/blake2/sse/blake2bp.c create mode 100644 src/Native/libmultihash/blake2/sse/blake2s create mode 100644 src/Native/libmultihash/blake2/sse/blake2s-load-sse2.h create mode 100644 src/Native/libmultihash/blake2/sse/blake2s-load-sse41.h create mode 100644 src/Native/libmultihash/blake2/sse/blake2s-load-xop.h create mode 100644 src/Native/libmultihash/blake2/sse/blake2s-round.h create mode 100644 src/Native/libmultihash/blake2/sse/blake2s.c create mode 100644 src/Native/libmultihash/blake2/sse/blake2sp create mode 100644 src/Native/libmultihash/blake2/sse/blake2sp.c create mode 100644 src/Native/libmultihash/blake2/sse/blake2xb create mode 100644 src/Native/libmultihash/blake2/sse/blake2xb.c create mode 100644 src/Native/libmultihash/blake2/sse/blake2xs create mode 100644 src/Native/libmultihash/blake2/sse/blake2xs.c create mode 100644 src/Native/libmultihash/blake2/sse/genkat-c.c create mode 100644 src/Native/libmultihash/blake2/sse/genkat-json.c delete mode 100644 src/Native/libmultihash/blake2s.c delete mode 100644 src/Native/libmultihash/blake2s.h diff --git a/libs/runtimes/win-x64/libmultihash.dll b/libs/runtimes/win-x64/libmultihash.dll index 2693e9fb87029eabc9af62811a17bb8df2a2b4da..f170e747e79caa61b18418dd7eb80b169a23f910 100644 GIT binary patch delta 319316 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

{by+Vpu&sTX)oFs7)z%JL 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

!e54&>EW23{8gT zh|thHxScIu=~3Cip7@)_FWTl&C6sGuSWJZ6*Khcc8bJLaXsSIAeW_(6ms)Nx&s$ zYfgPbpJl$!bZPS!Z|K9{nKz7q4Q=o3@v6HJ<%cf~o6iozt0H|~b(;AsHO4fQFKgvh zhfz;+uT$fwvKmQ>s2@Uz3eV#x8+9XhIRb%8h$K&xRrlT zc>c3DdetJ7w$EI;T=(_n<;I91vQF0bRU-Ahscn^)drMJlSjn%Uy`i8K-B+y>U8>on zu-57`^IKNvc}`uSKh|Eh)9K$kHN+Th@ZZYkBozSuG z^`}aB+Du<&w33nW)k@tPQ6+kWbS^P2>k`~jUQUhDQ7OGFZ1`KEUG8xmnb@xU2k!i$ z|Mq7WEIw{U)VsGH=V6N8 zKNxe~S?%?|{#;ul#xCvufjH0>!BpW1z5cf!JMs7ZEe}Y9FIX=B=dQG}8veDVMSu0-5?41 zA1ERE@)use@LenGtbYV7a-KXlL<-H^{_uP_&H_69m=ud`4AuS0F zPqXmBCQf2S`Hiig4C_O^6J9j5&QQluA^EYqcr~k=Q>CDyi<~N-h|5BJI`1p>^G?&m z0i=5|jl8Vxq`OeIt5fCnB#->qWTsQ62$iBL-t3xBelE=I&hqoAltrROCQvDh1*R}# zxm=%5UJ)up^T9PyoGxiqX4hBdS72PX-KC zk2!-pKO3qI>WT)S;pirmgC0Nu^bA^nO3-`g6Z94O7M((k>bbv#;!p?F0}Vh|A^FZ7 z!B;(Q#&G4eF%bv^)|dz0HZE}_n=Mxx9USAo&=OswVnp^ZZIbFHNg5BX2Jpo#S}zNvD6 zk20HVG%jpKR`|z3u3gk&{mO$mcB64oBPY)^^RvM5PWdsL|J`+UvRwW6w688+&ptOn zGi#{bg68RsbZ3=EYF^Ifa+o{(#%u<`RqOIUGMYxkxa|&g`^;B9G1^B|&tA9b6Juno zGyiErFJWEE7ly;(IAZqv(&z}rXiU>sp|MNjk1vhx6zRO*!kGP5g=rcqGFKKYhHw;izNdQwkm_m!0z zrZMR&tN3D_+V+*vmkJRFL-WWuX!It_n%{zh#ufNO2dz1fHHg%(nqZ0r)>wr%)fl%j zrQO%Y(ZB=(#Rqd5mW{-h!oEDoA`cv-!}kUe#)MByCyJd8-;XbaPv61qK74oxy@b|3 ze5X>0OAU)p<5Tg4@T0r=>W{C2OYR}y6Q4hUr&}h?AY@n)@C_tk^1^GL;AsWC@K#ie z&w|Zn(ja^yJbX%srpPtH@e2_@V8~VVAcVP?Sl5@1nu@a@b=v zn+l%}uUo?y!e_w~XbHX!&f-M46fY;l-yrD~UZkPA6DnUxe#%I!4PK({hsE1<Bkb*ri$<1XaYVFUXEtrdEbbdfuu`$ zS&H)Pq}$1lg;yiV_rrJ48uEExiTVpkxASwmY603^VgwYwxvP5bVX#n$-`!O^_sNTz z*r(y?Y8H&=_jgtN7uxe1ysFQa+VeZSYUF*O6mIphoZF<~_YFYJP(7gOM)NOrA!xZU>r z-mhwNSm&q1gm2ko)J+Uv?)#CoCm|0h%tIM(u?b+;I`(^f3Os^l;p^alpI9?|I_!9o zhLlnUK8yO}3*jPU9nRq;e=>&1PlvxYcBs>M#U-<~%^WIF@HX29^NYa`6t8B2&!HYA zR(UKw6tiqtvCfTKIn-|Qg$Am}3tJ=KJFE@7p|ze*7JPx2WtnFvcG<9Ep5ur`PNh5y zs6-N(QWxT z!3%#!Yw)4qU%tx{2>eSQhpHuC_-}L?FLV$P>{&;n!-9h)Ncf|R+5Txvz^G4hC}hu3Ck?}xpI z(+DX8+Y@SdEI=X!(`y9XB@legF`)nWN_ufWk^msO5vy*SxYKp!RXQKn#A44!YyMQY6`vv zUd|PwO3M3T0g|JkTs5k+eHFZDESrh)DR4s;1O9!E|K*qyIcy)9>D`P_0&eT25DW(2 z3Kz~`8iBaNPER=0P<#r!@ktuU!oq`YB^bBUOa?V&gac77K0MY|;&4~ZV*BSaVbv_Q z{ZnjXDFpADt!J7GpD3ie$S;KdPKr=fct!j~0fD&3B|j`6R|0bo72|W^3nv<>N+}Od z5Zx<26xGX#CDFcn(Q)#H5hoidMKo?K{E1MlSiEZHRIeCL6-XvggehV7I|%_E ziw_U`m5|_4e%NUq`9cZvEyjn3`%2jF6K$PpH~GSQ_#LNXG9Q@5MYdD;8tCRKUg8EC zl*|>oXs*yDO9GBY*0zKRT>48VUoHW5;}T#tUM>aB=d$4xyj(s^;xgep$%kW*%v3Hf z&bGZ=MjXWz!D8~|%HZ8x5iA!EAL`{)`!{g>m+OiTb1ku+335%bJ(mDoAJS?VjPMPQ z;+5#$i9BbWMj2rYmk|AUVKrwG!CB+C4ARK6m57IuY~tLNvLjr@`a0$U&@4q?@Czj z!#6oq4*9~RXbL`1iur-?-2xJ!uwBCn=e_(kx{G{a2CBt}!f}nWLw5<`U3 zFgTk;C?MFd0)k_g18}GIj8)(nx*^k_k1KrmG{>D5(I;2m#K+heCgiv%-QUJoqV8K^fr(s2VR! z_$*Q#llj0BLWED@L!rWk6*4^er%07VNN+w2JW3eHU`arU86Jxdj~$j6;yq{v`NETE zzLdF=`<28C7vn>5!-f?lNt4~NGCOI&bDL6l1TnB%{g?F9d(e@yo_1OXdX zaBydnV0rR|6HyUf*q^}PQoQgoB>ABLV8aRw9!&snJ^8}?NFIEOG?1}vNblA65 zW0i$3g&z{_D_`--17V@R5(1ofX=7DJAz@ooEtTQIryHwd_#*gafv#Kw2hMBEmjTMa zi(YK3lJO~UcPE!h!`HymU0iAuK0It#!iO)q!KDI)QUT+=(WRE)W8vvhE>$iS!s3J_ zURcKmbKXvz@F%%0<@tn72FJ{HsYHAhoIuQQ8a@;+Y*=x`=asrtHu=I>G#L*A>g3xj zn0-{OcBy9xDxSrJ@F3yv0AdL&ej8O%M(Fy0fMvYUK=t@U_*%Kkx=>XFhwO1F=QhR% zocxRr6678pu@HbSC z7e*K{%2~;Ff=hgQrll~0=w=`Jp~z;Qp(XF-IHG;Y7cNHG_)_=)k+3P~0<*$q^5IfVk)NF_h#hX-nh2XRYq_fxm? z7Mq-19Jw%^a(H(MUYLTG;QjD^REE!krAWTlmBGWduZ8E|t-Ci7X4+nOx9umudA1iW zw|&Xo?tt>#qqniJKa%Yv^xIzelBCIB06UJ?J{gvy^)yacYv&8S6LgtaIL!7L@OC8g z&xWb@%J|^;FJ>%~3Bp2TeS3xDCc4#e7LW}eLQ-Zj^xek-$rnD3>hUw+FGzjPi6tCL ze7lqv-a;I@hx{y9i-zKb%>)17Ap#Pu;0;JV2_@|50c0%xPCX#|oP37u1P zWe;>d##=C`APgY6wx18bMN6BN z+f%myo}44`_>3F)+p~nyk$(!dd`|o7x%6nW=k)@7@Gc}>ngbs}vVbXYj_nI!F_H!e z%WW?lGLL8GS(rQ%-}nWdTgAKJ3*xyDF&}<_q;5I9h^H5d_Hg`9#oUf$!Yp_KNh9Q` z@IR0=!fCoy0urAB(@_-*@WYQ^Wx(RM!a5{ngwBO_UD(3*tzZw^r^3Mt+2&F~V)(8t z(p~3=b*P4!wpzrPK+?Fua2%5I`S1j)ql{Y2qXg)bGy;Buq|7nc`*mG64PJ*NKNA{D z^rrL$Ft;ID<7~JRNud(B!}eA1H`||rZQsx{O@ZT))SUtk+4;3_`%+%pLxU>ewqg$5 z`|$A76*L-O1q)ZQ{ZpzLXqf9tI11u3Vf!~3fcRwiyX{ZIjjQ=IBfkQExQ?-aFNb^H zqqp$YaLalYjNb|)H}Hwa!d!6JhuUYr=^NcDhx{2E+5cNruuG5-&PCGI5`|cd#BYVG zc>-m^UPG;c&TXs#zF<4uSgFhS;9w*@A>8pPpQ@Cpf@^syr5IlZhws#%HGVi?ms@4* zSHYbq z8SmLk!}hUv;DxUut1dhmuoK5%w`x5h1wM{sZDznD$ohu#FGfBZ%8dN*5#-0`!=-2x zz7+n3vhb&1%P)1ecZD;NwP1Jv#ed25pBl{h`x(7o@ZkZ!Kfn=-%E#flgW8wD*%3JVg3=FFLWPe`_Ev(o}+XN5?=(Hf5XQv zz7?En`+2bYG2Ih`;r-t-&}e8LeB(Rqec!v)yQqZxGI-N*){^;*g_BOODVI_n7Syrr z-(?Kd1>EYzlX}8@81aj)?1X{ew9kiiNV-m#eM);_)bHB6U@4OEA#D3^?UUdOr?sCC zyPeTK1#bQeFZX}9VlpB;s+?8}S39*|1E1q3unAXD^WZJ~-1Qp#Zg`EGr)>DV@WVU2 z9#u`<9JuH_kIJIV5@D>zYP2w_DV}^6{Ir?J`XXBe1F!gWVgY=pxt{4{*rbK791Hg$ zSy(lk)Y4xGGVK>9yOMcER65qQIqk)@f~?< z3Vt4Job0i7Zx_58$xVn1xG*{3u|h72Nc_8#N6n{#voo6jmEqUGT}Zyc?1qiHXzzqq zBkOn$>ro};Rac%K>aObwe?wB{6s$|3(UcM1*+VxdCxH1J)iGf=9K_R8;?v<5$hqH8 z`{81O6X5m}wjS=aeKm~b5iiMa1+Pbb%4fo7 zQ69b!rf0J`*E6W$$UF4$CI>FQgY7?*LX{+ZcTy0a46jBqQ$NhIz3^2eYfuc2*@Hg>t{6B^1e6OC+72ag~vG9X`uzypz9A1{E z4=jH8^=ukl!@&fueTe~uuYyBfVKC!|!qE%pJ^YN<7|2K(t%^MAF*KR{5*TQ^NKZ(D zx1lLaD1ecRIq2YB@T=E7*4ax9?6yRgPk~cypAUD~z6!>?p;yKO7oi!)a)@o*B7ndZS-B!_Kb`Es5iWWmDN z6}qnQaU^ACz@JvI{bi>0B-*XitxkedOSI32r;sc_*y&C7V=l3Dg(p!l3#f;^SLrfo z@LNmXaxBg z@E!3qXbt=YSv>(O-_x5w_>cE>ejZH!r_L8XjAXP=feUS41a~5t&+dP+|3|LZ2^W0e zLk>8HI6lMc%k>&(!Va70A@Y;q_>UOv_z7@f1$FU7aLE=vIcc;o@e`hGBwsiLNh2o1 zE4S&D844dq`G*1w1k8r*?C*yeEU;T8-H1fvxH%@^kkTj?c-WS-z@tKz9!ab-IUkxALOM~!J;GNZsY-(LDq*8Z2vV4B0m}SJfx2esqiBt`GE>dpTlf7 zIX**Qtv)^rA466o8!SRHQ(+B~H4xr3KH**QrDywa`xP`FM! zSJBE~U>VP}9;b3Kyws};Wx}TCc~$;Vt^>knn|iItrb5_-=UubO?+UNrxz~97P`Hq1 zT{G}S@K-cn>bCZ(Gf1|#iu0-s@p}H{a4gTjmL28%U(Bbdk_uJu{si8cNq6PJ9v676 zgHI}4n8;(54ALU_6O!#$4^!G|FI-^zQrPc89yDd|=nt=G&pf4}@UsqHYj3K8Uv&z2 ztzGC?C$C!E#jB=#!=Qt0yLzqdodnk)Sp%Wjjm<-u`EU$R<@)hsp|1yJ@WMPK<3?C$ zd*M~7JdI1447d>$;j06fKao@z+uN&7BI{Z&{H2dxgH!OCzPb^Gumnl@GMJX8yHuEm zk=MH#Va2BO+d06C&DLq{5Md;gjtxpbltteA1=ioV=%(9%c+1D zUU7w2<=}_HO-L421=EMHrsNB=uVnDzg+C5u9N<%~@~Q`sG)2@-dnYr}7|jC1vtqzw7kM z^oMUFS@0V81Cj$;9lY~;dhc6~|2dd8nYvIZ>@ZR<*bkpX@~Kz~N8PB)2rH1EMhH8N z(ml}?HW{sbEWC6)y-JzE@O31OSOQl~U~`aP3J(YFXP}X&h5ku;VZyWr*sr->D=b9v zB}BMmvMyf*s~@K4s4L8SM3)hcdQ>-}65c<>tBz474_^M5&L0f-+dd#>b3TKGLR;bL z$Guk2$Qtly7+;X6ofyRU&g3wgsh<^3=V&B7PKFHs?W zKfLTYy|8(6$$y^lL4L_R_ADfupdJo>kwHp+;fw752T90;8u*N<_sc?f{e0H=dp=U( zk(W76u*S#W$px$d`StMKS832v_73>jLdFF%t%4D+>D|l;zhCTCHROjsQ7%uK2bR*% z05cNvDw={Xg8x{i*B}ozE2dl7I})KPVPTwfxZnaL*9nC0AZvufA8cO--K)H+fO&f0 z{ip<=2fMwc$4Uwe+*r!K$b?bw>9@5nga26VRlB7E^siyS;DxzJ4!y$lwikNd)%jKM z(SI_AD8B|qt=C6V7feFZP~joOmy-bRCG`@!p(ps@N#s0E_rk0X^*|Fwmg@z$;Il~1 z?F-=YwHnT} zefnkw6jJPpS@2He!po0Ul%k4aK47+Z)g4H@{4Bx-+m}Q6xdO>=wUsg`yF^Y(y=ov5 z?+;+))$4Vo#B@M;1G{*kyog=AP~OKbUMR0+7cZ2zvx^tXOWMT?XW3r(mhI)m?DAfA zDI=8Evj-$0Mqbk{UMO#C7cZ2Twu=|ayW7PJ<<0JrFO(O&ix&>Hz3@KU7sFqWbg8^`JP}E^%NxYYY%lK# zud}_p(ECYLQOxmQ-soLsB{=_wM{O@}=5AG`XOsws*gj8)lDLQ{Z!wP8smsWF&gA`N z`?(h^l=qZLBZTsfGVwxr-I#b`Q9u&htCbgh$&11AxMw4j7kh~p%DcGA@j??xWud%N zOY(*CJ}t=?$~(U#UnuYUl6;}Ot4q8vb`PHA2E@o~m?R;T*EUHZp}gNnyineUBwo16 z_Ck3%jMNp%Ym20e(6Lv0p}b;9@`dv19q~e5vlFlq^1dB;6OBv|${S_G3*}`n;)UC7 zFFay<;c43ojeWYjP+oW10|y@7e=9KCg!6dFT2$s)B|0Jnj;7L;U#XqppVgmuejBH=r(jc zx&rk;=c8!!3*~n$;LaiX0KJ74q36*P=pl4BQfggZoG;d4Hc9YZ;^fzw9zcInwRA+NCT7QWzHSk*P0SnH`T99h%(?A+-5u%cHnsD$aX9?u zR~Py^I&#-VCHX#cG|q3LN?Cw8rLAwdqj24m9eiCJjv{k)N8h^S@+Q_BPqr?tT2{TR zsJOVew7851AXPr6g`jkC(emQurOV6ATQBsDzSzHV)XJ=tIV*ElPF|V6a@NYimGf5? zt=#%n&0ELbqI4mLWH76!uxNf!QIT5YS`@pe)gqpLGSBPedoUu_XU^>8>&&{C?{xBA F{9lp=tk3`e delta 308529 zcmaHU2Y8gl^Z)I^K_DDSAc4?>91waSlpul$1iU~3NCzoFQL!MXfGFi?LE>?MzUWH_ z1rZemr6l?V=|w=nj$pxp9zFD+82+Ex*|*#!zrW8T_wLN>%6((>Fah{oCFP zr~7i8854Iu(y+eq|I$d0=Lp(MBkOs-k)QW?M&mQS>OG#9&7?k-PPe3HK(lO<;};k z9qPXV$2|#=&Wbf1E8Pyqo-z_z_<+_~G!(0~Cq>&f*`HN*)GY0AOxaV}(VNiIV;!dj zK9gb{qf#V2|FbaG@ohPWm;&FgE@mW**-dU#{rX*XnwIXNriZlxcX5%Q2 z&jhVOsh^*Ece&efHlbyt!#nFjqBr3F@oZ&BFufDn0XN~iv-TyPbr3$_7MRi5>q+u? z3H1xoGxw`|0sSg)zrmoB{SGm0IqJ7PNqeSrkEm0X9hv@RarY!`i%-- z=77G*X|LbaZ>RR8Ye3}T$`F_7=^b1%X!8+QRH7KV`BbxcE-_FWo!qL_YyXwkRwUQc z$Nf+$w(+hTckg;w#W;Kb-xP?bn4ClgjwWEVYhe2rBB9kohZG5P_W=-;UIq@rN3g|# z(vHkPU=!5q4Fsl_0%&^$7s-Dc#E_Q?3Gd-i+cu?6Q(ZDJj2pu^FXP5CZV=<1 zW!z|L#RMib#Ak4Rwu`1%pr%N(J~dKK@mtbdqWkB2wcS%IdrwF`2Gj^nDy_1VpBSe` z@C)PA2u?Fjjo|ltwVKmDb{1u8$EVea-LbF?O|HS)rY@Vjt(EfCeIgzZjBs>>T*(vo zz(6WXV9q4ja>X&pT*g&p++xO6W88Yi)nHscwPFH?@DYq}SQZQFdUdT9@YQjy{#5(W zcQx+l{|NZHTI_|SoX`K#woYG=Jolk;rj8D#dlFKy%ep4-B2!LE{yNzHnQ~+v5v0Q0 zVVkf49$2frFyrIuUH&d>m=DO4zXs8168~?J)@9~%j}4T|KX)Oxd2_cq&>gevBY|0k zczj$wb`3&BnGB5w^jU^R06Ku7DS%F9Xd_2|)72X+HC(GPtEO-3G`XZ-wv$Wx z1AWAF4SI7YoJNNevYAzgK@?yt;PyQeL;ym55H#O14GrK&Mv|ys7)k^9gP~qPBl3uS z)*wLL@-e4sq2%j|lFdMbyp5 z7#G3abC@(IUN&7XGcu8pEHEd9S}_4HK7xPsw~Qtb=na-ZZ9qAi*@b-=U84I2eZ&~! z;X%3zBrrhZ!5n#z?(-7ZIETjNJ(v>&dvg9@^*fW(5NY)!(Eam1ZDdZ>s^2n>MhKh( z&I>}1?b8)9X)Huy^O&Z#G^YERE;=zRA#?e#fr#G~4gIkm)k?d+WP z&QEhR&&$h7O|2iP?RmLfsV?;+H{W@=(B&*l-+VJrtF-uAi+!z?b6whI?`yZ6&6h_y z()|a$x!5gU|2JO$p-g{aux(7Fjh#3XJQ_HlmuUC%-=&`7=uFTSi zB!T+|I6*Sw2(;6E0ew^V!YjarSC692tk|94>R1?#O*k>EAk zGZ);wfeIbG{(hc>;Hs!dt>BF+l|X$ymU<(50ouLprWUU!h$UKky@9jMLt5P>ab+q1 ze~JU&?a zWpTB#Zb%8nUeSsc*L6-9OufoQF+t&At<#d>;ZtMrzxZwgKgZrIdrkF3W27dZZ&~ z*C$xByHVcVP)f>(;lm!X^g=4RT5QT+KBlECZj{gg`55`ugkWx5@#$_IKm%{vD?(0p z^kbyhQdl|CJWBxc7%(@bNqwavi`YC)^DnD?e~$sQq(5Rkq6Y4gf}&!a!82tg!!HTR z4E=EL-sGdfZ+^n$XYET>t@F*XTE`VWaZ1~Kn@#iB5{Jz4n3*$S=GF0EK$NR0}n=^gBoF=M?~=b(sE=&Z(@~l%VI$9 z-PAU$YTb29pZ}vn5AJY>EEygA2cx39z&x-mqGBG1H}?fNHM*qLU)?EwH(6sp44)S- z>7l)8{zJiEFKd%m4=Hnk<}G()oK~>9T-ClLR<7C2Jz{+MPZ35P!IdYp=r`;5E|zyJ z^!j@Y^5%R#-W9yHAkvY17q&SFFfYUY01OZ>ASn#_iXkb0Br;?tLlOarXUGQ(!6bS_ zFk~4+psFLh;c2&*I-7e|`{>P9&Li=6KQWR8$n8>^{x}tE^3$Gs_rAK*Pj)|f@;l&JIkFA1;|~7L^1@*%DT*u+W|>) z8IWHY@;5_%1>_h*zGKKSKz1@@FGF_9k$w6%q~^o>JG8lLYE=8=99Cr0U*)BHirNO} z@?f<8(AhZ`we4$~ii_IWHLb)Yt>)UM_%~zi1HL-~YPIb=!!aSl&+wnfY_cbKGK9ji zQU5=*m@ukg(wh8f2|2r_MSA@i@xfm6;H)EGEGHYEeoJ=HBQe+>(1qxb(UUrqO1*hd zjIZGSG)Y?Xk)ZaiR^#m)=br`ITW@#jH6Bjw=*>N)$CvX4;uZueInKx3+~Y*=&3FcA z5q(6ZA7>|X>E_+LnSSXAl7cZ5W&&~l^kEm26ikG3Yq9Pr@r|}@T?_HGc4XZ?@s+ml zovEG2{v{dj2IIKNcn1G@^kFjo^16-jl{=F0$=(Lzh;oryuk~Zaac$@N$@sU!yDjkV zQ}4EEdfh9PQhZPP|A96FuKO-PirkZ*UeJ!d+qTv{ur+Mr;P+>+oV?AwYMU=Owzh{RFMucBHoN@t=N%6JyqVe0Z?62oO5yA98;zS>?Z}IAe0wqs-#ga{C1Pp?zZxW+ zm}Hen@~9$lpEgKpFv%2?q_ZNq$0RAdAO#I35}y=ZN73AG(tO7>ZA~_0X*8IjCdtQ4 zQrRT=kx1r0=WtZV6n8)CaNNcJiD);+=Re~djxX>TiFRxJ-w`yi&pIkP?gcO#fF<~J zXuCJwrybu|TKjL~!_KKgH;>%ZQaIoHck`l;h6!iA-?eKWCpD@22kAP!!Z@!#-80Vn zh_`g$!Ghd=6`sbzoFQ%Ib}hu-Dhw|AN$a`!lL7m&WHa_HM&?G2J8f9n&=JEQ8nzd6 zsr(aZEh{^C^>3+>zUicTw5$Jz9SDNz2EkWM(8?gVeMcL%rM7d)X>ID3_RjggY57|` z(Oyyw1+F8(+lU$qWhV35)-DwXpCUQ#fAA4Zo25OtHLhY?2Hho4?OEFRtxYT5IVq`J zkQw}QruO01?#}dE+CN(Z&i(_m+->(&{NYDQkVJiU&d|Qu_NdsSwa=@Ke+T8&bRN2) zP0SnN{JX98L*8Rzx7I1YP0iiq9a*^zvvMDg!2i+YDG*Y#DWvY)r7g&>S#}mSbueL; z_Hq7Tu}d5GNfqaZGd0gAJ!GS*n7MiGC!duP+qDOFv=#r;rtPSUe^>9Q>%@lNyJHy| zBR=a|V=C4?xaLP02H6AMOdE%Y=6d;iZRckr#iv@`oh_W{-)VhzK2YIPEQd@Kg3YFA zZ|~gXZ1ArMb72#x_qE&i!9+p0Oudh@;wTdo!F*e69Vww{iJeUr~P%{OX7nv+thrW8fwaBL8+c zXi+L_z4z4KEh>V*BQr-!v1>YbUfG-_E&zEfzFqSJdoRXr6&_4ZXAeI_=HvR(L*!;$8Wm zj|lt)$Xsux`G3en=9)zbEM=}=2Ad;+_^zE;t9iexD<0FH{BD?Y|8ec}?^3IFsRbWw z%Qq-956)k+IpO>Jg>!0~%_D#KRyYTKvANxkdrOJs+P^=gJ6E*O(tdt0^3yE{3-z&m zw{Fo^{9M1`2r5iY=Qjs~$8x*o1us2JK{Fz1o0!`*4_eNn9UC`d^Vy$sg|qrWt=*6H zHtWA`DJ9;}GXEG==g&=Y(La#`*!((8=7dbs93P2bhj+DOe>~${m8jkS=TInf`JWxc zBJISVAIE5+gk#^)mYsR0<3BYyviLKigMW3War8%@F!Ef>91v<+=^;6d$o~ zurcCyfu#|nwwZ9bg)?Q1w(fF@v*H@<^yMc-F4#UM=4u}obi=>53li#JosI@Srqv2= zkpHfe|E`w*F72+RTxsr{SVJ3q#V20g9K2FWhz8n)tMT~Pb#08Z&%fIEYj2AjEKYUp z=#83M@9UcQZ1bPju}HJDu{ZlyJh4PB(yPm{NW0RsuW!C6X39kxbt|dq%n}x9x>}^= zGiV;mJ-y-V5cQ&8yO`+AT&A77Ro8dD`26?uHs*if8qdEl>;J!E8Un-&1a_pn@YUw`(Qu zjIP;pG~cyk?b)Ko#Y@_cMTf=a&1>&s^(N^pgy>%}et}%RuU^ISIoj#33Gt$Mf!0s= zI7L$J7fM(_W0(CjQSP#N3-$M$qOR|3aTPeb87gQBEE#P|m;e8wU68}UHe2(?|FLMl z2aC&D+SOdNeL$0-;Hva)`>j411Kl|ed3R~G4XFm#q`!Ay@Q`1LFVZ?C`2 zrTc--9q&QC+3~p~Jm=h<(2z~#?@qTaM;A*$1-EN?SIdenPOxZOUKEOF^cIn#9zOa;iWYI@mywPih{y=^ES=Tl zAP$3qx0dR9q(~8C^y87Dy=bAAuOOQF-gZD#aVe2?D6jw(KjR#SNvorsf~#SnV7&%zBN%%U^kG>fH;cowhWQ_iB18b(6! zA)@qmj3+?oEN;3ni)ng=ikO8QtF@MsQb5g?HHErcv-ti=V-_{_S1XE-$~_4@rAkNj z-@1va&Wca!qoPDobm1FF)BRY(?=p}QoqI!H8zov+d!oGKd=0Pv`{0Km=+$rNm!d>- z(L}EmE%M8aRxIx@%gLklThXHHJ-v~qAk9i6mPdnErLm-Y5;6j@b$SFUv<@b{uGf7) z)NAz+rC3OR>VePs1|;qFB74te=-E9G+bPq(SN1!#NZ%YInuJ4JW)1ep3=0zz?cndiFm0Y35*5=*Qm1XB?T6rB&*?u^72SOkL8}hh-akjmFGE>nAnS+VdO~5y zEqKUhaGH)&wibZYVaPWOsRKw9Lv}L+KGish+sKf+fLvyp6%4ry$gkd<%5)0pXV9-w z?9Au%{CH8X?2HMR%A{xY-{Zvqk*&9{ChFj0a5Yie85huBtR_6-9(`psQLXI$7eU!? zoc>ugu|tf|r&JehD%7>Y%uy9*s*7b}xc+nv(MBZbZ`BZ!onv3o>(msFieY+AP0>!& z(Rb7ot;JCNYE99g^3d`Q+*;z|e{ZnSWYo*ScWUcRYKgYMKU@p=hb;UjFA=_7ZG9Qx zAJV@L!)HkR!CHD`ZBZ?@GFfSlgkS_eBnIzRiqunTi~3IAC4EF~(a^bVzCO3Mxb9p$ zL7!GfG;=PQsDDsLRBJLED#4LSJoFSFbdRIu2@Hj;%$nj!rA!>p9JlKq)vweMu~iaD z)Cq=_I!YezP%x^2UL!%&5QFu038JlY>;-*n0%We6r!P(r3!Gh_)f?3ni=BO+*Z0;H z%^LR`PcqZptG)iNtIZP}_i}93YR}8zz&NuKgNMfG)$b8CntZPEiF8kIv)a%b*v9c0 zC}*?9(AO_@3xd$s8b@*v;3%Hw%!(x6M8>#ZQk}uv1S$*uzngddo(lrSswad7~PM2ZiXVZ)+?ba7IVw-EAyh zc8W~>px!mEFj1X<-H^ZrYM zBFlCw@5p%K*c?63OvLM3l0^e&*~q+~lCj=c17}+?-#L3k-fL||C8skuMqigAS|p~D ziiUbpy$2tVm6+E*-s9z4f#9!n0I2@cV@kPW^_cdeUc*7y?%qJ@+TM<3aNIw!T3Y^P z@)Lo$UIXP|Q-)x$?e6LiwHFB$>oV+Kf|a|Qm)l-siN0`bxUNE2%h~l}LRORgh;f;v z_GXpZ7hL`ncsIG_#gRaubaGaygXe2!HaSR5!XFGy0Y0P24eB8A`^beQ1Rs4$Z`47o zsd&7f9Kk0L8qB|=pX`7Uyv4Bp5$u&ac@g)Co}%vrHG*SdBRCMOi3n>S!M>0Y>S0FU6zX#L%e;-iY4`^XVA=?`g*|ItTx!U$p+){J0xZs)zxN%R(d;b5f>ijQC( zBIEyW1WyBB!U+05me)R2;K(aQ@17?5IxmmXSEY&J&IP0NyJ-l{xAU5I7H%Qh>i2d5 z`tHN}f-XR>dRV{Q1?XFOHPb}}AzJH=x{7|zsFAv_E6n@XY<*)_(bKsgTff^?G!{4X z#@)n+qLuz*H-z|B`aj)ZU1LU2-q19%6+${LVZINx9ib0;K-?p)>ysZ4n?*~#g%{K< z_1<3LamEkVCwfJj$d;gvA?g#u^le@Y;i~?JSFEk|c6U-CEvvteJb}INp#k{#@w~Me z;&~y`_4poQg!9qe`iniVS>G6`ukRs#cfR7)=Vf9u&&|+3%M{gXW_r;xx9bV`jT6Ct zJ9CstrE`lRP4(-UBH6ihCv*4}x*LGCW<}It$%D)DIO`cETFi1{9nnNr% zo7t!_JYrcsQOV@h2xc6S?-e%`?B~js&*1;tV?^QQ7Y)bIHiJ) zj8iJOpK(eBX^fL9P|LFN<LuF7FdGMh?bWmvs^-mj!R+ ztfzdR_r{8w`m23K<)p<-Km!9ozD2Nr+0humZWmkUD>wCT`--(yt_%5I4k^|f2qXX* z{LSgo=l6q;^XMP-6HR@0^Nig%Jwh7l@R4daK8gpq8*}9AL;kP8B3trz4FL*kN3TZ~ z0s_Ik<#62(0s-^l7l?Lv2^j5~k_|?=J5EzyyiX~C?iTJS;{4KdO?ViJ+R>pEHTVK#$)NQ(5GBD&zQHkz6=BngNiP5g#N9aHG7u9{=kB}mz z#6;KJhvhnuU|8!bvL)Yzg1C8%D`eb!#zn|io?HtV7tb$1y~emi#w}u83gg~joR?a7 z5z~jy;O3X*E1;=riZp9SuAJg+QeC3k$(@40kj~@?T>nY62Gn@&DkZZ_!7|l&N^y5J z9v9F1d>lKu_PWk`Lxwz$?aBEdbp=f1_U+h;6(N zNX)_u3`Y6=*HV>(WXpAeTa&nTPPPK0ner&(7BjB)caqCo#yMmwC!cZmFm65LN;B>R zwPFH?%HT8j>?D1~AXGOZpzjzYRyd!VqYoV{>cqC(CSON<1$t3NvS6gXV6ceqmLpXq z{Sjd)Xkd94Ff@pXEMh2pzH13XBLH2=(0D|I#9+&p^^1d1JOA;HT&Fu^Vu@}SljN)T zQHDDsLhXp=H*QNa0fN*HdRrF)oJ>PeB#CJpOj8Jmd}}z-RU{1YibvlTCV4}qmvZD|mAp4rh)-!ON1iHUq z%6uj{#w3Rr_Z8z#l&vF8CkSY;`84UMmD*_F!Bf-p%|k^!9Esnt943CN z=5&_VV_y(Woy%X>JHH_IRq2K+v-5qtfsP&VK(DLebn?08dd*3qTdBs)%j@;)md_H+ z^v#n*pSYKsArJCT@eJZ43M6c89Od(>zbM`pF$bFiyjNND#0^UH7(6J^4>#7EP7&Rl)f?-hCW$QPkw*I7DWXs8 z3ys1>*T0k3XsUQZRPH|C@bRn3rlMU-R_bq0!`x@D)DKS+-P=!UNPW8^eZS@{wKucW zzeqkE>+hf7MgqDzSiLrEj<7j5Cwzn}#?@H+)ns`i>+4xQvB|lhfqvH~MmZZbc<0gS zLbNLTzcP-rPrMY80xL{JV^;yIgn{X+ zim4>A344*@sZrYw+Jx0fDf$M&GW@%&!S_;nYAh{T8cn$|#MP5*gBkwa zp>vC5I9?}{()0wsi1+=uiS)HrGEI{MR7{TmfhiDE@Z5n`X7XwO!BCd#7>pSlFlTTY zBhQ#|z2mq$WkIaCO$A{FG@b6>&Mc+kBci%UWo85G-F+_FQdv*QxR9r5Q(0SvV|{jK z>^>X86ZyvvJBc{7a*n3s4mY_8>%G$33h?k7k6^AGP?Df)GrJ1tc_1XbZz2Bc7LLB@d ztb}KqAvK{kzyV+_1?Lkj&LG1#z-oqL9?DAQfphxtvVS42%!CKvc%v#w> z23p?af{bu;Z^>}5-pA|qFIfA^5|plN(*5()uBAPx27UGe`=C#um6?2q?p8u}?Oui( zVHIujR3BWQkYvi+rJ^#Vew8hj11@9tIoOq9i?=qA2$*@;zWSBn^y0+7c%ifaAp5tv;(n+tnXrEFIS z>s#*;>!3H>=6_{4c@#F1tXD-f-!?$W6H3k6z10dJEbCYxI~R9?FEHG0?uGfC>RdjP znOw*tPzKji%Gn|+t@yfE8e#&F2nn_(C~Exj69w0FkM zRSRxC$1vPm^JP}g8!e$kZeKbY>Gw#y#4 zDE9UN_s#Wk>ysB(`uh_Qa!vu)NjzE3{ty=5itmT=zdSg6@>wF>eK7)C85b_@_s~I_ z$RdN;VnE2rCXtp^GQzQ78ewEC7|&u>TfsoN>|GjctP8M49AKkRtKJw0OEopoDk%<> zW_ZY|@5;abmmY6}IwY=~e22D4IyrDoBV=&!@xVzPpMtJ3Dmj3j}QDO+S;#Bd0- zr4sp9uM9sYscGfZ1fC>*oCbwhdmpj^XIOsi%Wuz=F5i~v z-B|~5M%Kb%PYvXE!k9+(En3o-Qz95&-P%Cg8G)Ix8R+9)7u5!6fEr$$J(A-35|M`_ zL!$wKzb@undNVvEO8g4mK9A&WB*8Qn#XAxRYsCM(O?=@>Lv|oXhvQ~+;1qc3b{z}R z&l8OfP#rw{PV~0Li%n>%c;S;dHbsi%03uI|$8MO*?j$Z(3WfJLil1Vl^UxcF&7CJ-TF#_vca@#8 z7g8SjSr)`G*Zl}uNKHdC4)}*23^yH#{+o_mpe{jkmwTv$GI`S^6mJp2``Q}CJ8TlC z7;bC_z4u}m=Vk(ebt<#wOvAPXHQ+7%h3o_q|$rDvnaq3;CmzUh+pP)~yL+abo^ zFobb6W>BmO5DbUnmB(wcL{uN}!&^LP`i7?&6DW66#-9hgHRyMVnl@FKA9|JH;LYJq zU$#UvOd3K^+?w{z^l$gbDiu4F_rd)@k&VNjN6SisVuXHmiD;IjGN+qwk|=V87w}|6 z20$Q$5-gQ69eY#nwN%t=^B#y)prIA_LL!yEM$Qz7%QgA=m>>3*{?1aoBL*E`SSo@( z?`p6xGDKTgOd`1HvF<@jsvyiH$}`+l0qq|sO6FCldwS#ydrU?ARbYUxyTzO4M>Nq< z9b{Fv4_F9Ow!MvLOVIa9hO;WjNainx2b&-{*YsHRE{#zc_Mi~P-O)no#TkhaPympt z<8CTxRoW8FU%RLIA2Ls~IrwrQq1eS%E@!FA_=1&ob^VT(j74G9$6yOSK|>HO{e zP9PscoTF`{2EH1U=_RE{F9Tus`99Q+k}DFeu*Dh1aOi6n3TmzmNFpfZ-bYH-XQcit zFHjPQO?kFB5KnN^4$7kynu*oo)bHvFiGVC4fq^snd4-;~67_;w*X^L8!I%#D1Z8cY zTLXmL7XjB|`FI&y+oyXlX`iwFJ{G4m_pD@VW~F9QI+)?mjFFTg_~$pd&}y4Z z0&HQbg%n7;#HcnY_fW=G$rR?pft;(ffrswCR9T2%M-q0UB@W%F3~hA?d^`(i*c z$FfqaScYTSjpPxTSbnzt(I%OZQ4mmCIk%Kd!AOnFkk&Tf`U)6p_5k>ol#DAmjWnBO2w0Z_F~SDYPDiG z)Pt!Qp`jMupnOYImV1uvVwSWonf+W_A_va>-Dl9^iC+oUlgJ>iO$p_Mg1BIM&Eea{ve{)a0Wj9b%a4T+ML9#&!Q1_=ZW;)u>qMi-e{< z4`-%I4ZYDai%=uXU^k^O99l4aB>7@l9-~w}@o3Yg>>Z+r9mix_%99ds-O}aYm7KF^ znc>zZ=PQOo4*N^}p|zq~)_OpVDnhv=mbM^-=?t{sJc?)f6>&bU z@ZrUgoF@iRm&PvglXqDvcTQeNPFgB2^c;)dv-2H()eG*y4kwlnV+RRa??zMaS3$OE7 zxo-ajEt#f-oF<|^4Y>CL#m>RZQjdhYAj~TGWMHJxqZ7I+-BvisMjTX7VDw0SLtT&S>B`1 z{xLv#pvI2a0SMU|zW9zOBL%{yTg!-$I$$BgNTsm1L4(SVQH3wr2*$!^M6?}zu17&@ z?T3MA@qREe2lD;#kv-lF_x;U4S)%WcWrk|i%b-?8@r?v2)IsejVJ69d0hqwtU#8*yHMF@`hT@zM+NSY zKe+sutz1vzmyR-fz>NjmR~BG(_`H^_$PjY^2p01tp3KeYpXV6|{>%oK;Af>6{n-sd z_r($~&Q3XnQW53Oe7gl)LJRpOFQaXdzGpZoOW87Ir+r7ie^XN168a59iI+SgLKAvb zjxJznDs`G+NDJFJPngo!Q&RO_n6Gz!=yiPZPE@16UrM{r3}7MI;XO8CjRNjiG-W`d z9cavrD(Qgxc7RU)l>N2%5N`P!K&*;dMo!+Wp8{bS*aoypDySDR+;H_=PdCfi_DXm? z{gjkbtZMKDTz!DC7t{fFs$>cUgl+vqhGTI;>uFHZ!r+SIR_w|5`t$hzorm!o$)YR( z8U6>s&5h&x4o#UWh6S-542LW;SO(nd8Ay91d5gMRzWMg?0a^a-#$`heNN9kj?=M7| zK@sg~AXJS#Qa&Q0?Y`u*&RdfU(Qi+tKh|Sd9x_%b5T)K;?~GgJ$9&h=s(MGH6I;k| z+Wm6$`tnVp+Mve?nmgOGp4^Dx@zXu1ybQ5jnY-F2?a)F(U2Ng&FdX{fV5z%5!u{=y zS7--PNzKd2!=^+F(fR}-Q7Pg>%?cXYix3>5W$FB#1|^ zc4anmR}r|6t=K+#1wWotTCwahirZ66G?raCnRbt1GQC#&?>4jWTq*s3Elv3kC$AP z8#N*Z;6oX1?u%nvurFFuUv{i)>S{C8WS&Z2714rUEq45KbB=^#0-TMKODf3Ed-BHD zl3ErctgoY>EM9~M+PSt6!t~(lB*WsD1M?XUK}Hp_Ra8$K1*o;!w1L#agTCOCjEL|9h}0;QZuZM=RAT%OPi9t= zyo-|`Sy@8+D|7Ve+e97X^mZcip>7Y38&;jb%hj#SQGv0WHQ3W^n8jh)VEkd z6mUHi!gHAHTfE5b%5d;xXU3`Hw?`g2AQM#zaB8pD0Hx*aX?oJ+>+(@j(M)pAL1f{s zYk0IKb7rRA{u5E78aYq6dn&Tg&nU9V?~nNeR|`5i*?j$6GhZqNK>$)b7z`85?W8&j+bhK)IU4kaPHA)vU68Q)ipmA)qQ`>pwV$w z6LN0)1rXL;ccN8NR`U+SF&xXe<2r$A{T5@ZH8WHha7`mZdg(6HPlrs4q)BG@ee!6L zZE`3}RGCgs$r%A6ELUjDaGN8ba5DMDBZs6rvQ@Mdq)5CdWQgPWsYSmuo#y8=ZuCy0 zDH()ixrZ2T84*c)BX8aTNlar?{8kZR?jp|#nG6|c^0LI|=}nUYje2w(^YwYd&k6=H z0vfgA7qlSVK2B$DTz#V@^J}35(i_#5!?xur6+;9%m@n(Vbl8;w?x0W390FAsNZ&3R z`8EP7lW}=IYD|abtHQy25LosxotfB9pvb**Rx!#HKt+iZ&E<6uMnJblXh31z7Ql=d zzN^E3X*GoOIU3Wm3TzC~*fteG$dzwtJQ*t?XIee^n?VNd=s6+_N_8ig~bLGrR%nb|?$jO`k5e?pYW!SEZTw-{`!D0#kMnlzyZ zq`5C18J)r=^pX_R)U26#0zM^bScs@zXd#12lc!du{RG_Y7;fJnJRW53H}dAq>B^#_ zi7a=6%s`E5nlIrQUogc~7rplVJDLc0;mO?*23xi|awp=%TY!?SQX)s*!@dl2fD5gi z$4efCJXhO9hAMcm+Bttnx4lOT&|8{XvlJvG`16 zGEx{;zS)|QFIx5S5a8HERJN2Mi78vUA5T&z0XV`2gKCbY~*jbiw$xgEG{K8~rNY&PyF@{_B-aoY$F=m%{gNIWHF z!3oP+Fm5QCJ{po09lA>~R6(TTNqx%zRtG9u#q41!QXevwH6xeAQ5mi>Dk|1-PT~L! z2=-C^OeY-}oONm8`uzntxzjqo2mCFXykR(zl^ez?FCJ~(v@iR+_d!@Lqi1;+MaxtJG z$%Z8c+;_*zHG{d|z>|@!z;bLk0RCFfc)jaEe2G`Fc^9N=m6dvRKw!RL6JRz*y$oqa zIx4-sTzAyVt($Ic%X%G(4u<<}k1{Qr9v7Qa6RU4C=Q=amv5T?UqXVk`a1Argy%5XU64tO%{ z`QjnC!Yagp4}j<%ovL|d&3B%&E8`Mcrk}Q@BHuF{O1Hd7LH_pUcOj8oAQ&(e0JM2fvR4TBsnocs=;EQh`kE!YR;d31Y+DXsv7F8hq+ z2fq3hCc;YFocsg^<}cpb@qHr#7_VeBm+xfQ{CNh5$)dP)=|CdncWG43eC06QRScjd z`tHJUc0HU!t0dq3HN!De!*_#_=yBaRnb_1P`q`-CxIGcs4 zJv~t}hg%6Nn};*pa_V0mfuXjM67VM;$YDtzAkAEDgsLH&%Nd-*Jo=TfG)=k|wQ?#J zj|$-0_cu2v2k|-4;l^~A3?R~GeQG9ZbF2r2WkySxZ?TvAW-=V=Gdz-Xqw*jD0*{_t zsqVK1D}jeBf%w+0tw2ukg$QhH2(<5X?C(mZK-&y?STw_@CJ#`z_h+Bs8PXNC6#;BD z^C1ve6a4g<_EK}j634fQPx1-O&`9dgqRlL?pfREu&*&?@5b+N`4npH?584=3oxF!M z?V!@xte5WvG1tpUcrro+AoK@rR)dsw9^MKcL_1Ly=y7GA(W`zbJY9bqgK?X;&-mv1 zcu%&)crUQb_!B&tyAk`ncSyNB{Y(7jQz?tSpfXi#KFDhTDw| z*L1XC*g97)4!$^qw@wt%<;I$oty-=qpM1ma>eEt|+A@Elg*-izen*VH!8f|t=Gns( z6q#V!G%ksO6&9n+OWA75TPCmA9;3M(0gFg#@C3-fPnnE0Q3 zbJdp0B@sj8$kmo)#Iqn3&F6ClH+eM*m4pOOe`LNckM3CWAFmES$X6FLk!l_`bX zn|w@gbG6n=ZkP_Or!9`nW4LXr>TexKRr@gDl=;J^;mj{d??a2hidWvCV~B_rXwfd< zFibbbmQhq@9QiifpxLVEf1Z?`@=tC)xiwRL*BMNxRM-s%Zrne73oKMYbh3p)bT|u=~8E)U_1^FLL z{P~i671oxMTpEora;n8+XX|O_`koM`g&snyq)clQ!=Zc5wDc+8VDNLOZ+KRP1$!oh zXSVDco=~x;CU1;sbOZ@_??t}Y3XdUQ0X>mF@PE&|6P{y5g3QM+r5cB>wquD)kJ~48 z8Z9f&vcJ=W*VSaLY+Oc|)%3?FC;S`w&9`M>VkBQky5^xt`@?q7`jQkwrSv#Fnf^WC z9?1x-ye-?xWMKY#!}4Z@XItUPRsv|rz}UA}LQ5uFK3fro6DH-Au0Fo?`~Wu0vyai3 z=c#iH>HQgT90;}OR_29u$d_;yJ!*Z_<3@862!-sz5JAo_`P$O2bTT@I6A7!m@s5pm~bpE6Un{WM^ z;r1O;nE%4h759fnig$^f5h-}|v`45&Siq8$XPSYQ37VpPL*cy0`96_rB zS0o%P^pPi^kv9P@F)+;K-fD$kLaU^}@G!$M94q16W!_a6d|K=9R9@lsL>N|%_qM&F zQJux9{ZK)&EgmthKTN}*yK3XG2Ce+>FZTXWR_|2Xs2t!hC`+u-KLUb02k()FYb3|w zVZ_gHW0UBse?-yq2=%q9bBx~*m%&_>wmP6yQp~8waA?b3oo_t#<{hrivE~jUF}=jq zkuu#;C$GNHBIJ7@LksnN1ff(XqLw`%)ME=;9pF0*FNTx}?^yP-IIMRr0 zbR@9WF6oO_34ugEeNx0drCt`#AKzh7SmfE*0V2yrcTyMfL}9_< z9fq61LB30nf1&p!r2L?^RY+5%(B+>f;Wy0RETcun7N()m8QJB^^X}uF?3cv23+K|uehlFn8zOe36pQ&$NiSan zH>;6(($T>yQ9GRtxfb?B3+<#`pAU5tA9WCa=m)H!2boBxM2RaKXBayV;-C zhXVBlJYjdp?__5NYW>UzPB@H{-a`z`zge!^mzl};QEj~6dU>3_sAv0*_Hq!DA#f@- zA1zMBc+=@C|3Zc|3Q!}&hOBrG?o6Yk%`1H{9hD?YxKX0lM~l-2BgCSW-!tjCqxcMQ zm;d%7vt2G7w^gNgo9$T)Pi`5;cObKj_aJpLE{1JI^+twc_R6VF{Tp-10o1BOY==)z zhcF#3JHu0lZMSX=H+BTg8*M!AhzGffR=QgwUnrlQH6T&*^&pAMp~(1q4<6d!(liONdbmn5+SW3zH>yGD8P05E%h#cp77KOkf}>6>o`@ z>o)&cYik^&eJU44Rw>;qW9RTU>E9uIF8dk7zlUTbb6AWD9+M=y;>CM5!`Xen0`#Nj zutnMeTH;|=tq`VBvNIfDSV?lLH_ZvY&xqU7ugjf=udKzl@1~6E80eIH4@pIQd4w=e zd*4N?n0tqhd6nT%kx`6*D&JM_U{JdDU5EhB5Q!O1veA;qRpF;vaI5K(l5g1QN5a6r zmE>RiD62ZdA=fHLD4c*&g_B$JYSvRB@nSC;f&Eg1^q`Q(fwbni2QBFjE6RXr9&hQ- zUZ8geaft`4+#l+9UITH{&;{vF_Mh=-w&*b$P#T@JLk39J6h1QXgdJkMl-`jMw$cPY zq`)xJD~_|e&5tRK4bqwniOd8$#P(KtQBOGo6+8;i;^4uNUBB<5sQ%;uK&`BaT%4Nz z2i#dX!BuFLlr_D~a16%^9=ojLtmoz>RA=`yyr;hFqNwr!@lzg|5p7tG+6oONKkV7C z(mjgd>?~+2)VhQ`>P!~xvqhfMGLKrs6_NDQ%82)YfP4gJncIk*UZf1m?^iP1*b;i+ z6253XQSn}{coUD1GlKrinSvtjjOdE4StsC z4`J>lmKmK)$%ZF{1{yI??gsqwAEHH7KoBjk6I#=Hb&!>&S)bc3KrZ(nBc;7?22Vz` z00b*fK?N({t3cH3`VrCLg%s7j2b z5LM&k_K;T;c<*o&ev?c}`KbVJ=2t~eD@@Gxq@cUyv2J%ajY|dz+vfR&QEVHBsD_~* zm$MZ7Sus3)x!q#DQsOz7OWw0RCzwF3&@_ty6h00uMV7>JY>8H10~r_Z zFs3m^2>=ll87h4dCnNYls0=9wIMLDn-L~6&v-*63}<~)^AtW^90HNN3uX)HUW9|=DV&X&eg!#dBFKQ>|Y zPhh<9zFs9XrN5gkew64;X63cf_eN z0rz5qcD5&oDJXKO3|n-1j;K&Vk}|DaoE)3d)`K8Wdl>0AXUfU}v}F{2ZSe9nyz^bf zXwv$rD>cRB-v>gOm}aOronpN5uV0`gIfPl-R)*7}s2yZe=HkH?R&4?sb}}O=t0T*{ zboa1ZykhJVlH=FUH-ewC^y7-Eb zYwr}#ptO&djl$6WYJ<=bI_9YJO2u-ZF)}|n6N6mp8;nZ2sNg5j3k}V2D5Zh zn-OVv^~OxP0`77aIc*8kp5zpgtSS7~mGerum{=<1N!0B30&8kbrCn<7>lsD;kV{es z^O;r{2BvypxUh~PzoOOG>IoHgMN2Xdi+s%txana@yT+?G(H2&@Peb?M1vA0> z?d}WdG%>@a{el*7q+2po3$foqkd>NR#C&;Gl`>WkJD+hZVtNmy#pzNQ=P9%)WlG9< z(4&l!+BeQGBXEY01vpc53F~ayh7w)R zve?vT>egiBqWGPPG`wPIZ*2*i?{%sY?$mWMf%P{QR4wp z1XhUdqY=?tqXPwInaIU8Knv+M{bnAmVrC#N77)tGDVw(3V z3?%JPCLnVRiD*s;AC(U5TP;i+cd;!I))}X$tOR9sSlnIEp1c`{8h%ToWu-quLZyJD z1oQ|)DbCnvw=$IYcT&qq0*roOLnsPCPzA{WrM@dLA8j+5fV;ebzz>95yYPCdoFB!}6a%WVTl2RIz5&Ie(2Fyqc?l=gS>zMMj?e$;F7kde7Cgvu z*v8yl(2~bTyFzE)gyE3G$&)VbpfGS!U*igf#;*4Ex%*kA(*E5)p(RUN8kKG1?qfK% zgsB`H0FYoDqu%NkJ*GB$lI6tBxMr}u12DS{i{b6yo+#O=GeDb|;S9&nRVDf59Z}ua z8qgB!m>MBW-DsgADVBEL=p^AVQX}h>R8*DV?Cxs4xox9(@JB&tB<0pY*S5w0Gr(qHpe#mUfV-q5{nsK< zy>C5&;!YJOef;XWI+|y7yO^~y_gnWV)?pdVuM9VKm7aMQs{a^3izj7`%G{R#K{{Y_ z_j6yKsm%_bH+UN6jydJWj2ZqvIX7Eu?M{T4K$4{!!Ww_iqA5#L%Bjsx6l*2s~ z9WVy@i$Rz}rhkJx%_+*>z@R8WXJU^wq04R)|OVKqQf0)nI-Z)0mM)M&iQg3># z$5LPKu->!fS6D0tNfraue|1RzXj}h)+liJ8y%^j2 zGfx49avL)8x%`I;p1(#sW$J;Hl`Xf^;Tt^vE{yJ2u?zJm2p<}ZL<+@ZcpDQG-8rLOJy!%C%(zU zI#k?Wv^v8ao@iKKJMjnrC8lCy~b$CBrq(OsKjv6A#DPiu@xrE9;8C$c5TR0jD!kzS;{)V zKvH4vu@-of0cNW;b*S=IrG-~OWu>CBMBo(QSs+aH9WoYM_3Fn+7~NM9&Z^~F8?^cr zYQ~z_m~zfo->p`5PU>ESUIl`FPDz|9T$+7>799d3ev`mKAovs@QV5?1Fzm z`Tqf^R8@@DD%B_8dR@XfgC`-pOQ5lt{Hq*+xbYT$ikPf2<7{9cBQaIZJ!Hz?nSo?i z*)k)cje>C~!wkgL1p$BiwpJB9(iJ@2;VW8<&p{k~vRc?@y976Cg+Q&-N#u)<^I{wN zoW=UAwbw$Xn)>7_BT#Fdfix|E)oFB|W8!^=D`I`x%9KYyLxG-cs=wL8i0C$hORzFwuHdXT;1L3&gqUOt%9&zfcVgTJRbX85Uc z?3Ct7F~;>ekDeTFdsMN{vLsakJt8?Gw36GlN$SCHvjozA!|)%jEBP94)7TUqVKJ5O z;m8sPYgo{XOrO4_QtJ#J<*mQzoxTAC)KHP_FSQP<2sOPeY4K+*jnh?{B256}&jrp} z+sSCj#9jtp`s*=<_rqyqA~0Z1druumN&tREcZ2cXR|o1pY9s9X*dP>OMjU7DL$q8wi|4Z1IhNlTycVqpJD{i z+FT#V{yups9dZbOR8J;9INTY1?PO0@#dPXx9PZduG@5zJp(B3@xCatjd0S3Dc9S8| z#;)u@xMcc+I_6D+em%-r^}!e*jFO!^&m?OL+-zdE)*xCk0SZgLk24%oRu|$!qp`N@ z0Hs`nMi-J92SS+oWM{a%Fs6?)+%!y?mE?~&b@l_9KS(99b~;Ky^LS6G@f+`b-NUlf zCWw_hac~rt!QE+06QkGm)WKa+3`$MjqjCFyw5)O)TItE`azZYeRw#t*C3l&!Z_$D8`e<|Y{ixH=ADs1HF0ww(m?1i&!7b|52DG(m`;gYc4hdM>EtxgQq3 z5{RO<$uN>)E#{p@GGZ@OmVOaUaFJ?b7o`ULEhBt)!(wIXpXQLwhcqJH8D-a^nSV)?s0kc7o=TXCok>kK0 z;HJN*YfZ^^3VJz_eA3xyqdNxw6F@Y0QjoO{>>h#M0W=Su)&VbHqfIFX?k*&l&rTKt ziaE$vG)%BH5Bx0^*90Yp)|Izr>62-fc+Z#q@}R2YW$+_DX<$Uo13x01)L@*lQ>Y_>kacSY3^l zd_OTP^37#9cBmQVah~NjD(s=cUi7$1ry$v=9NMPu!m@JhN@PvS?i)3r$ zR&68$eovS(9{pCF)8i9>7B_TToprjtwPpD*Q!Us*g^*ht!<|bRGkz@$U^@FC2cg0O z+q5+4*MPNqoQ9nDO#zsKglW$K_Y(kNO5}8Cu)C|^u&Xm*U2`9xg=|NU*q|gGB*i^S z6%Y`Dhi9A6e;(U}0`5y_sz4w`@qkVM8tzU4_qg`}OwoY{C1IiwjB_eI{{I}wP!P~KY4fpWlzzJ?gq%RY-5N~}X(f1$s!S{QWh(9h_G2MR14bLb z8f)B@dh9xY{R@x$cQ?+WV8E~W$l=S^|Blh_qMv0T*XCdJigF8E&;ygAApb7$NnRx1 z5u~}(_U7br0bzQ>I|0N4cTEK-c@h6+Lcl#1Em-_$d>Ebs91d6>qbD%v@5LVu$%Y9@ zMBeZ5&I%Qxn^%>68{$KH3cbjKO1CUGQ3uzq@2r(Tt_edtSp~E*yza%l2!ESC;M!NE zNPnolv+l^%KpWl$ufNb%OOr1PD)&B}@p4x9T|dGTH%=HNLl!g06eC}n2qr`oJZZ>i6p$Ve=_C?oTrAhJ>@RP5)zo9d-?;I8) zb%dWI_NK|#9n=V(u#X@U2LjYz`f5WwrkBnFAYJ}BFYMB@4V|^i#TktC=tO7L2X0gj znap}#3v~Yp2wpY*Q%t<_2LOdM8Cp&I`lxb|{!F6NQ|=XJ2BTb-=&VuhF@v|h6U}&K zqaBz9ITTihel^kA*ykW(@(8+k14zK)$L73se z52rTS^%(nv{z6OMq=$w4FBy*2xrukbPqzUTtYuhG|`y)`5cos127aR#BiurQKFU183C5_)HMfM_GJvX5}5BoY~ z#sGwdsc(;d@*I9SRIoXsp79q+psu6=e_oVyc57+QUFU{naJ3D(FFpdl54sORDsWxi|K4X<8m2_%?K?jkxvJ*|TFP1)j z8ahV9d0S(z*2M*Q%;u6Kp4kLcSbd$0UxpGrr}UVrtHQy*hMWE`jlDXt(xs34Wtw;u zdWoLIjGMF}1O6Y+6udex@vl&lfL|1t4fd1qcNDGz!_H1@f?Uo#Lh-&UHCwPf$SBV& zu$vUOn!)%4i(AqcA@05kr=00P>}cZE7+p(nUxR$Sy~RY0lyT_M^AmBm=Lb-do(D&5 zf&ov)ztBo%4QXMN%&?|l;W^fTh4}<4rN~CeJq{7>;W50)!Mt57a2w^1R{Ydsiu=Q+ zUX4L54eo7Ch%l?A*HpFK_9s1 z%BbD~{_l?^fx#XjpuC$Ev6=9o_wS$-L~Ty*pHnz`AMPK^Mj6MC$nX*H2afbZJy-Cp zXl;eSOjJ*{GNLe4Ng7eO5G7F@!H#i2*LZoTl~KDJy!_k3tD4ASZY?qH9q*R*=A!=|`#1K)Rw%M!cui+ARZlj1{(cJ^48l&*3)V+Y#A)y6))}G>VIGCYu zjH4<>G3RQvc$LP^-ZOI*-O$r7CG`bqR2^;EK0>guBF;G8Q6V5`7Jg0}uX^IA(-iqq zun@AOyPk%h^=K0EvyuB`O@f`C7>}KUn6(a7FFP_w>JiYrrka)g+-iERmNV zrh1(mYG0*kmS$H)Yqlmsat#%ZFP+pu{hw7yY#|YZ92C{u4b{a=un`PhBPDik@5JUz^M$jI# zDgw4{Mm*1m5IhzmuW~BNtV)OKF0BlA_WTRmdQ}oD0S*kR!^%R!1C#UsO5#REVh~tZ zOeS}~p**fZ6FBHdvvwSfzjS)kXXnS@oOi783@#LeLLZDRpni$bzVqI213buCRs9v5 zeX`7=Htf0*qxI#=kI>p|sFdU{l3?G&Qf}n|Pg{?>rPCni)f6g$t&W4HO5UNQSwP zzzR*2gdnJDmtdW0lnKB|L1DECgsW;TDRM%kMr;&#WBBqO!%PF{s`Hvt&3wVF*7g+w_KW3f;xok(I%C!7zyPSD!-J9WP`l7hTM zNf=YqO|h6fIpsh-zD5eiRGD>E>^!f*_=Eo*!(z3<=R{fmMTL<&5gwdk#`kW~Unq#T zP}e8fW)*&zUB|h8=sSF)C7`u{4O;C2Ep=G@Ip=v*F1(H?Pja~7GQtD<(}!43a!E*Q zg-9%vq*)!1_T>^;8vJvg)? z*Z=Lm*u|?>B{U+v@ho~^gtFt`{6p0u!fR;L>=tNOMVp}> z8V2NaO^NrRE02!(#&!MbLpAU&u41MORPLcOD}JCVk6lNFKcoIWLrHwnH1)Sy;h2H7 zW_qR73^7kwoeZhg^_yT`pP0?ovlf&xpTCh&^=tL>$|uGvH8VSdEj9H$xXOuL^+pG+ z44p|~(%4*(8`rq;IAx)%kQ)|;I_8xQtvz%kWUyf}{v(PqgR)Z*lH06saA;J=$nG@% zgklC$n456;f*CYQ{SJo&UgftGILZz~gB5NSi8E&$y)mO3RL~ALH#FfkF$SAAP=8L3YFPVU{?97}y}3@ssC!Ic0CgGmKz3 z;|O3T{Nb-U->WC@z32|i*@9GG;R!;MS^&Zb*nQt}D57<0#xq-Rj|t2KHid(@Eoxm!RaXOd^@}j@HrSa6T+GG>5b!xOkHF1&0O?E;-Q&T4n*NLupOU z&43_F1wAV4AAw}W~m`1C*sQp{`P#Ye9zG0)I}`>+=xmgluX!?men|R zP-@s|#&)tM{8qiZS|vB3M!gAtL@%#KrzhDG$2zzk!#SG;D4c|hz-5ZU!I^Qe-t2|M zQ#V49)z*x&-Vj5vpe75&Fc+;t3Z)fOh9)_3Kqv>+jf-$gs{e!+|HQ9}UUXq?W=p2< zFA{sBWFukAGPDROKB1U1!j`)gjx|-JkmctgAK=^k-T0(Xa)* z<}5H?fhzIj5#!TuNi~_d>ONu130wZRpKK-Yy;?kCGv1N?)VI+BY#H6Sob5SPX=$1H z3I|(Z*s>+nZRpDFLeYs?Cke&5CT`JFmHxWkUbznW5lZb9BWD)&B26>pYGt54{L+1} ze{*ObnHj(U#h%u5{DG~g4HP~}rROlIp5sS*lNX2f} z$|d_8=NQLqEK<9pp>pPT9r{QxAx7Di5K?oE0@pAi5}WM)-4(O#Cyd6N&^Mq)=D=K;>^L^C9+I~YH*a*>E?D%^V|awJj)peXw_!Hyr^jQ?}Xz?ViqS@hr?xMHp1 z>)0+_7MMY1#R}wo6kuI~YVkwD?GIK1F`&d7kfEvvgFAkd2x};7kP&-O+_o0X_Ep$#>n%G=bw2yLdL23Am}Qd9g!ny(Oi)(07a(#dvgx+pwUB76H{ zYpx=p*s_s$7NdMGdQFkB-c&^A@A<`Dj9;UK6f#CbM;qDXWPBrlp-iJX^}@hrui#f# zwORI0@lo!X4+A+%Dw2jvL`Y3hQt9TU$q)m_n~M0Sa+cLy9;N5@?Im7yFZUO}SU)e@ z!#SdyeqOZ_yZ0C-{Eq#+N;Nb}X|fhjIf?{dEPSWx#XbJ@{k+;G?g^eb@9tQo5?5#u z7)PR5$)M=0D9{T;9(XIu|Fs`ZBAsR*MV^sIuOeBbJ-ekZVr_7!Lyum~&@6zNjQnJi z^=QRISz<)>buWb11qUvD(I15BC9Fo;2&kRd;(kRvP*k)zcxHC0RfSJ-X$J?-0sPqh4wyC%@*0iK zLv^pUyNdTY0|ps~TR~|9%MlHG;(K<+YS;rPo!piGN^2sn{2N0Cdw&=6-v8FW^fGV9 zkiEOipqQkDgoSSngMq18;X}zrbN-_ zfFt!b4f;!>=vgo5FA7K%${`ZxX)BfL6| zhDNCjW>?Jyq@mN!((!*C;Z?~MdB*LWofYT17@!zhC1YK3Fb zI0RFd^;~~YTI;WVQc;%cQTt05U5{`IkDN>#H;4&81_m#eD2 z_?0pXO}_RIjKW@GDb(uDz&^ccCb&kXNW+eO$b;|#8tW8)yHQ#WminQ|)88U(gv#~sk?j%McBnPOY|x$sh#IV1{0m+f!hzig+G5-N+{@t}%i>DL zzxgj%#j2=p(pzCS*RzI!b;fNQ&ufjF_m+lhM1HvcQN}1_m9AUSo;%Z=X(o-h60gc{ zh1fWzxeDn^!NCzbTVBQ&p}5{7-g!Ggu)y5jX2XFxgffMZmM1?$;TW*BA`5=4F%W_s z+bC{nnHkZaK^*!YGzr)6i+q6mr)4}JRyYRVP8M3Fa^fE@lh)2rnJW!Rtl_!&Cb(g> zk?!6Ravd$XG?h_T;o!#PnNA<;RUdccD{?c}EZ8vB)SN>cQ1}$BLWb7!88S3O4U`ih zC*#j*CH(~&NFBKQ#$uE87DGzc=}Gc2gOrL9s9u&z+TKD7g@X^xeEa{5^(vPuZjf{B zXw>g7x*nU6-+d`PH#omQ$@E+dPNe5=XeAA`^qRpdgrbPUfFrcC?;dBqGI>86V$8M$ zw5*ad8fjucv<(;XNJ~{b65`fvpYs(isvyY8-81gXFa7;@dfDYF8!}r0GX8@%fTcrQ zU6y`C$*?4zPlTmSTFJ?vD-p7l`e4G~XN5h+L#Er0u{HQQDg^BTDJ}{lXt$DbBG|IK zTCgK*O`DhMnM^>svNb4e}o}KH2{GBEk(pZj8%JVd`c9E+?##P42*79=)pT zet#kvpFlA7r*T$`>KpsT(gXj)E1)Y{qSDO}ia!?&X9d(+qRIGaD4B^gi!ZQCPI%p2_}qVL0y@t%gGK|{2t#ic3`9YbOL_#kt)Mu{2I5^|YT)MB+)@>-<=kNA zg4|NDU;g)Jz+G_U>Cum7JGrGF04=?$55ho=q2Lw*E%%MN;;>2aE+6AtEXl0@;4e6G z&IqsXgN9r)*1jgQ=YHnjccoXo7dg`W(p7j2-82?&81bn>6Q@2RU*~BhdEmV0>>juh zK3oaZgxMx1hra()cVfRo>15fa4-_8JH$`TP0WZOX->YB5hKSTjo@a)2eXhCCz*LWN`QH9znF?<%kM#Z>`4>Gznt2+}{ckR{A|nvL0^a3D1n zv+vbj)yrQc6t94BY(wN`cUITs{b&*{h3OW(ULzUp_!zC^eAEx-WAoK8Fl`JSt}^@0 z5seg$f~%lZj4?P(5vjNAjLh$!2yg-f0U96dKU zAJk`~rx-9&Kvo2Ga-B3bx*l^P$tZltWZaj3_R)dp`-%VYHD1M@BAL3Ks}*NO=u{Ru zkd`#nTTJ0W*Beg6UeXsIv+KG*DX)quAvup^7Buyt)_|SRFE2_B z89x#EOZ0{++>G@|Lv(Pl2^=t+lcm5IYtl(+nl;#5F=y-}+o_B;eo;GjFRl|y!T5P1Z=Cg$>>}lP)ANhY>>(wp6S;h(7BOm%_P4;r-j2Mcae#n8^ z&$V0z>k5`bw$cGg&0pE^MXBN@_w#568M5c89z#(F(FR)g0-`a?9rzcRI60XM?^*+` zWcFxnmh#t6_9{F3+GhhEnuGc%8FN4lgMoL%;tZ_}V-~;m6tDJ0+dg0q?a4t$GTuQ& zI7zRfge{REg=}5HM-?6j+e1^lvl3$nM3_Uv%0rLgT|#P3!==8n9K?!T zVl%{(QCUAcsJ7u@LQA&M$)mJa! z*UgS)%{Bxn^cZ4f3>mC+xWXZmj1N(Cj_>7&Ar8HgR-j7!{nvX{29`5;H8MHLncM^K zg^VQQ-=ic)Wz!=rbi9on|<4-pvt|S6Y<8L_3}CWAQrS zi2uV4NLN-O5*u;I1nIz^*yzgI0hDAY(`4Q@^ZPcvFRqSTJN@a&N-Re07Z^3RJ7SA;gQ_(g5127&P{9@ljHA7s#A0>iu>O6 zo84?KszGrhY8{a_;e_2RS;|^{fx`(WF#5o=4i^-swvrt#DzTi;D7l#i* z)Mc&306=+L36!2S7hM5jtd-1ai+^GKz%6_@Hr8BJ)n=&|hQD!_3C&Cdhq1RGH_(7$ z|9TFy4Jf`tDJ`IAO37s0uLp9PQ=D+;B^okbp-az<78jR@us`({xZ?>_Ti+?)-D(&w z(W2s3d;91#w+jLQOakA`e_3kc|G|I`Sf)P${??bj zM#qTv6*dOD8j4t#Ted$YXp8&N!kQGkgsR9hyx$252UZQJgkb8+-}=5RY&*l($x!6N z;H*MDGd}5%6QNU5@sUG`r6U~vRwV3$mlSy~)=J|D_UaZpCOj(Zr&|0vce;og-;S{G%XgH4 z!NP#5<;XdJm1q#E$miIciZ;!8e?Z|F9ZlEz3vc%-C&ml%H6|usl9`AAlb$7 zFN5wxS*W6wlrT)Yt{m=z`o;CP&0u)>HOa#^?=PBtf! z@i$Q+GsoA=`IbqO=LwNlOZGWx<6%I>^)_NKf0|dLNET+ zoPkYvys!=kM~bE#hCpL#e4#+wrf%pk_aC&NEIS9K9%gMQ`^NZKIya(LBB&XnhslRv zmfh+2Agv`$OkE-*YER;xg9bP#HT;ckLzo715?Z;akwsD=1M-mu0WlJIIck<4Rw9;0D`>ek>iQ$|6nkB&#Z%qNy6B zYym9hudB7s+SP!_&{rv&1XNc=o)McgR5%#3;TZaty5#q1ctZh%#eyltQ`i!_0O2-`?xbx0 zk8nsjGTZDcbVz)iz-=JA0wq*i6C!PO9$=skp84a>E0oG?wgVMvK|87urF^xLGlD$) zKXnCfM&R7T(o=W9|LIPIDwPa!YEdG$p~`_&zydn{Jxap-$wHNlTFFVG4Od~f>Mod* z`K&?Dw+M0@vlL-8jHKPHR7}sW6hxVuB$p^06Y{Pi{~E}zI1A7)Md}1XXQG0Kl6cFN z?lIft_s1J#Ljr^S8EgD2trYc7*yHFF=LjiB+ZzWM#;=f^E8%hz#>~A(9ZU~ zLJBq#N?TV^2Oqqt{*gNnyx1hvJAiVa(SABfpy9Gr*iP5?3Kyd`$Y<^6j2{J2wv`PL zm zj(tnLf>yI1oKM>iJdx`9pCj+%7d{kp|F0`KqKH+nAW{4VMV$+4WIhChHaVHK7XM&O z^!lwJCuUB>WiMIj-*$IocjPv-hn$$%xCW)PV1`1Zdzcq%C40-MQtA5kZk)xff*Ko_ zM$T&$6YN%e53OJ?kr7MN#9u2sj9o6d$E#CnDWIxAo><{ezX!=}Vc!jnCZY*PJ=DfS z1=TK$z$`VVyRiCu0_eytysOlji<|555(!_FObtGZ!zDT5p<4eg^t%&T$6x1QS$6iL zBq`-IcD5;;Vk%=q#kebVQSt{>H5QJOfZ=Clnh@wG+gyw`wDq`MoVN~reJ^&fYBnMHL%5C|%4jgw7ZvlyJ*B65+8HtQ7*j-i@mnW7bA+%kABb^DzUexL8(J_`pk zXDW6m>8ECSmD-QfqR#7VT~9cgRXF2S!wXME8)mu&ApV7p^@IRUDRl4+SJ}&$4x+uXB6}oU_ z_C_3H#g!NbOj@D!U>P{dmgVsKm;DY&uX@MzFGmgybJyIyr@j!>=@EVt+}&>JDA<@uXoY`R$R@|ep*Pzt)F7Kuf0pR^ zF@Dt~oX)4f$Tl#5g?jWQbVSo&Fk9#=G3sCU|jV{DZgfu?!Rw#*yfO=7pvG$>|);K{%g$KO`8MfOh|DnGwqsc`0bpuXpWUac!u0_-T}IXI1uE}_|+crD%YekLU(pO zFMa1kG}q1x`2FYoOCN$7dIr^E#r)|HK@H8+B9_!s4|zHAJ^^qHw>YR0$^IvJG||XD zstR{`FuEo{Im6;YJysX%!8gzyTnZ5kR==If9e|=9?3+QBer{B#36Y@Xa@zGQ!$pfP zu|MpU81*#jrT^NAr6eMQZ#r?8)?2G0^{&>p)Q2h^2$JDZsP|av|My|9&ghy^Dn-uV z8#>3I3uOV!N(<8Io;TUp7o}9#I1fxxIba~($3tdBhtEY2yAb8@l^@EwSq?K4yIFNN zp%lC}+Su*YH0E(nMoGvEY%H2=_e##HAyMi&?~J2O{@8D+GbqZXn9$2=zA) zcFnF<$Mmy^4RLQ~XG^SCVP}Vp#i+KEg;Pk1Rj12g<93DH_`-kX5!|15iC|tP-($4M zc{yx2M$IC~Ygy!JnhA-i$>=;>2&k`rpJ9I_g@n@YD4{AVoEzz2Ju@o$)i^ZokPNqpKf{-Swaj(jH?B!(##j-r{%3jkr1 zmCWjle?Y_}7uLy9o`UTQiW(&Xl#Ew@W}fd==wZ544DA_};61v|A-=s!$ON7LZ}Be( zZ+PpjJ#7k18y!b?|F`gxl%M|(8 z(CYxOjrStCquPR3EHkX5NqD0xB>|^rw;XGVo8D9t+!^5t3aS0@G}%JJVp$%SFvevq zE9HGfATyG9u(&^JzE>jgI$I;xgfFRvd7C1thCuM=ph+l-cPjdfEW=d_cLq;7LHoe8 zM);ZKE{5XdK7|AF67^4Zt6hx<#j+?N*P5}<@z-L38&MM}LLgde`j2{_lb|=gf-Tq|aUnx|3lt7~CWSo*`1)OM@O2J)N}Xgza9{*+z9PstU)5aU64OxD z|9Q--G1e2j5LphE4gv?ZuL_#M|IJfj$2s4@3^IpSB$akay@nFF;`noNWc>X*9``CI zX0iq&Qs2Jk5DBcWP!dL?3_(fu48)5G$EJ(I?a;u!+4nCX%75T9%~e#SDzhCzP`*J1 zu4Kd`zS&<-x)QY)C3KpMnHAEj=+Ycedw9x9IquV{%*MguKLaJ|ZAJ^s>V)g#z!QFxh1|5J z^Thq=*>X3!Mw}9W0oiL$aD=zXx2^Ro)dnP;?&e5QAt!SmL<1WkU}*5E1OTj&fP+3o z5OCkzJ%2k?q3m^{2tz{|#UbV{D{jS=E=5Q=*{zaRXvkLLQ3+G1Rf_`T*{s#Abn$jD zXslItFn(mA)KbPcT~fmrB*6~9`)#sJ2vj~nLuV_a2t^%gn`U|$5FR2)W-Y-#8%_WM zw$_Dv>Gv%3{U^K{O*lGKj-AD@<-@ww4~=J#;_=1I2B(@Qd97? zwy#-LUI~{F!plTHLxLO;!|CqFT~4>7B$(V~L;&ueM->Q$7Ik;(tyQ1@hS2p9LgOru1I%7tI zo}%cD>Oc6H;Ui7;Z&x_@uxV*9m->0-!sGajoC{sfmf(o;-`&2}5Lzz?^aE%S8k+*a zY$o4PKvya}P(aieXtfsN!Ot0nUyP2Du+gP6I?+xA_U7S;*6cIzaAy)Mb-!j zJ@fgvqNp$Y30p!asQ@fRJ85QAm!S!HOsGYDLU9KH3nxe&y&0A&<};AUqSTh3RxU(m z^4(x-h#^~qsmxpp6k#JV&6PL@0yntDYYPd?Qd-{V_`7eCpB*Am<7?BLaKXX+HHkzn^<*o8CJ=Q^+ zas}Wp`>hk78|2)(SY5~juVV%zG}<`@wTMa+VIGPh&jPV&j4ApeAN9>VbQI>(G|7FigsPF5o2ns5jWJ# zLfC|gaE@1_1OuAk>MT}xWF4TDu?Yt{yw#$G(z%rlabmbR-72nZ^^gs*VGc2#+|?ip zY`?AhufJkl!L1NZR?`sn4pG$Nm>u2hOumVueDoabDm>2HrPV?ncdeRfOy? zOZH^p&v{x&Ij6DHi9GNeu9GS&Wb|0HDsMT_rELH4urOxK(@!X&s~M{7V}(=2DB=-I zae90Kpxi5qSOj+at_ac_Q4+aG3!jE7JkTf~FZC+-Z$lttpl^s+;#KW!r3m5@iXg)a zIq;AW3_9EXdF;5oOCS(;wum_&bbCTIya-J&$!07bRxHl6Icw%7OTr0uXwfzquMXG$ zu3WhD7VUK}G#6`l+8-5qR35_A(Um$z@bzy6YuIwG=0HqFmS_z zoCzC@IHqbDDnhw^0wwV$#QQ)jkd$g`D-&8x^{cpFdcmu5(WPu-Q9FI^D9_5|L-d=W zB$AUBw^dR&m8{~-e$lJZboU(gS6>q00MmO4N876dfUol~zRq8UMpx<4KO-{2q`&8XT&7?aHU91X0#Mtt)wiV2BRGC~-& z1WSmp;LuICjk(+jVD1N0$Ix1Y+X11xC9|%>KTyz}cyxtk4mFrY47oiNe4#c^O&wEv zk^l8FFWb41w>Ii9IY#;_JRtFly^;6G{hU}>Xk9HjiS1tUYL8xv8k?euxTs4}VdA7U zBJ=r$H!#a*W#wMhGmDm{5GE@ee42d9nwQ{V_aqc2SGZO!IgWTyL}qHDRY;+nV#?4A ze5~1F4MB7c(@~ac#2hV0i;&=AMVw&~?o&AC!kQoB1x=>f zfp=5BL{TXvInt5@UzQ?#s)QS>oEst9)Dq2zRVrvr5UWr%ZAqNfXDn6dst7Nz38! zK1?74Lt_)$ef4Th-zP_~Uv9CRy_9gs@-2QhG=VgVb%to{oSzTIxWcO&qRVmmD3Fav zR_oY(t~BmLNhXQ&=vb|<6pl%<*>vGsVmT1(lXprhCHOKMA_W~}>})`ikp9exP`gvG z?s6v4AvsPfRT*hSV!s2x#NEd3-ee_tE^(eDI?0RBI;$dzCks!G&k}fGx|+P^RqymE zfOaY29mI8P6OB?{Aqd8SffJ7uhb#J#S^lio%sYR){R{gpP=Y53Q!WJx4BoYU@@hI^ z%6WitoMA@Hn z)gf046>Zechz}FL6IGNbwb0M41Xlyu8v832F_)-ud5z=QI)c5P#U6NM&Ppz!m5~60 zS{~d`f#9-{KOa2hBa?>TF%y&%U+m_g*+ z(9w(6BgwX}P#JRaK1!0kPP0?5D%>*S-}VMRV|f>Wkh#tVuQNNwl8lZZ?{AR@air7r zvn>TDAo2fx1O8KSLk7BBy?9*`MgV@f%N4I3C`o{j_Da^f3dc-n)WMn9ohtVFi2Lzt zES5_1V`yo`O%_FPXIDXl()EH7vJ)&Xhnay|Nm1h@iA2lf^Eee=0681=@Hq8Uv1~JV z&G**T4pc#%95pu4MM8pf!|m}qU2gZIgxNBww9qhbQ#duSA&pk47JGkuLgrQjY>2#> zrRUMyDkSm~4Q9#|MrcmEQ=%E&9f(9TayErrZcEiY<@LA2VCGyPz^f)U;%LGmCIU*_ zgc-YGSts_J_yL*yrG8%-If)Fjhk&w?h~T!3bJ zf(aVl?1*NyQ6QsKxK+RB;KXcgfKqFAyt5RIX|4T|Z+m4&6jLG+D5De9O%Y@8?KEN0 zc0vpcB-or|0V9t&~xK14$A=I?Nu`K9?v( zLBNx<@^Y2}jfaKnq$(0naA3smBM0yI*fljGt*nAo%Q5|CS=JcD*|lgHq@Y$#MV;P3*e^~J zbU;b%*oMMWXW&?}e_$=XF_c3v9G}TRTT0L(d4*5`3QZ1ZYdn*C;8s4(J&&L9FF2db zH!$T0v!0(Sf`ntw^6+&hx?&&YEj|Nj>WsMRRX(K$iSY=RfksOBJc*Ag#vIA4B>ver z1`u?CA)DE#*@CV3>rO153`YZ}Hs?xa4Mc|E8DM@&&)mYtp}+m<>v^bEi+s@l#TuMOP#giMiV8zgedi~cm4A!><$7E^ zUDu+R_pVp5#AyMB8t-~JIR?Ydn*{@{h+^$nKyR zbVreysR_Dsv;XD0UV{?30gB=qyy_+Xyh$(uMY9cF&RNnQxF6F!S!2La1P0$hBZsr) zH~Et`;81kZK8s{9RPcN}(?!oUs7acr^;QM5p3-iDOF^gu*c}Z~V*~@DxqE|Gb6kZe z`Z;(UeK~$3IxJZdCGtDVCM8B6PKjs;=#Y31&2)}^+{Xl361astOyQBvkvWQxo(S*!o~5 z%nUciBhI+w-ryJ8^gd3gUIYw04^8EcH%W3r1;>Y* zOYLuE*e4PJC>J1Ab>;ETQFZ9M=tMoGDVy$4cg;$Eh}Uo`L&k+9t1x+Oi^I&%#7EbXCKHzyMIjWI89ZflGHhM{BPZBiL^JDQ3J(NSl z(EMlyr^4bnUz-8A0s4ON@@hAlzG+gaKB}$KrM6XlqCyR~wnQ!;$6}Q+M?4{NgVV<~ z>E-%Lqr4%QM7?wBquXN+S!QUCX`_1G#BMWu_F{7L~aX2}s7TMisD|w9?xj;r89mY>h~RLl`30BN<`;t3)W2 zvJ}vankiKdgw<`*WsZ zs=~p?T1EcqW<*Sv2=Y7`AYGEd92gzkEP}n3#f}U>1`nqOBs6!#NPd@bDUs-2cik5k z=*WLP(XiBqtm=s|vumsk-b90trI%2Wja7GQFfWfN9E(^vn)fjhepe8P!(pOLEb3rl z3{zbr_**L;E|{mNjv^CN$r9076H0ohp+V#GH=|pDOV7xC&%kSxJk3d%ZDKCT=W?#(&bruY}A3mM$VmK-D#g<8k&Wraf= z&E(y&#jDXh0jO0sG94;oX6QyoF!x7^UN>hAgB{G8|Lqp4T6wczTPl0r)2r0~W;UoS zp23E=J%{U|Xq-Qr>e+V{vGU08kN6k7zL$Q~cat_teQ-%5d_W30yzX!$#BV{~Xib3} z-2uJDXvPkjwE{lew9*XL>IDGe+Bj(?*_P0y2sA@Ut?=ADn_7ZxlY|ypF)xCYVDZ$e z)mE+xHZvL~@Pw0Ws){0qnx8tSj~QB?Vnhx5iyI!Vj3k|+7Hm5z=DtGvK0$RhL5?Z>q5wU6Sq25YvtNlFxNF@TEUY_7j{yd0L z;}4=83{hwj9{(I$u2&GsL-9}{#-c@b%g=YPGQOUVP%_~KphwYxe^x6w@U&ooIKj9|o&_H(yZ^FJ%?)6;UNOLM zM#-_%9hYuMgm7=gow1eCO5qqav$OEm7jWi0KsnJFR&CFC;YMAi_}3`OM5md6^$NE* zslbX-TkjkAv@~|q?qh6-x#vm@>1{ku7cJ%g)(F9?6>)|c?QaPhwY6<4Y))N+oU7O( z=fIo!F-GUaIG3XXD4~J|M@RXFqh}PfX4mF*e)!f zkhYU8n-w8DRgnjoB&(0dC>*o%t}es(zr;nRjRCb*OzoUe#pM;LxD3g7mOP9zb7IA? z&HTq$vTAYvgc6J#6;Qy;?$p&iUMvvP=WL3t;d55P*5#iU$o3LJXqWvwluot-euu(C ztNHR*xB%;7Ky?e;FZ$N2Fup|uR~?j0Zr@B-;4~rYRx@MEJGFS=3VsQbDJaS^5;(iY z(5mS1T&ie*HjnWy*=9dOo6LOzEqK*SGHWjWh1=hpV;XZPbo<8o_1ZMm@Ur-;a%dO9 zo2HzAb|5NcMdKNYg4z?=UFW_GKH-N3UBkdQQI8Msq!>a@3DW4gY)h?JymgL%dGOXl z8%U0x5=&+ux;%Yj;#)u{s>!Uc@DJ$qJ`xO1TYxbLPN=iev{kj^-}qmzoO8^o5Va5i zBR116_ZR&){P6iGRKiqnOgIxNhFkvY)vDSEHP$VN?01*D+%L7=%RR>+{B<-t3YTD6 z9+zUq#`i~BucnO-_NeV}u%9Lr@rZg=x@Q*2DBg(%p<=zk5=nE}h6Tut&DpXYmkzZw z$jMR20v|OiscZy!%py)Hh!Bvk#6ioyK@s+|J+|0w1AO*z>nX_@llF#eES`=%_Os z)Mwb#YNPTxjlo5P4Rybrv1#;d_cqyL2O>+@=?~XWrH{6w% zFHn-mi__tZ$r^?0(x;eA-id?D4-<;><2XPQ>hM6&Z;W8R0wp={khY30R=73j{*Ikq z&ZPQ8986mkDec2yC2q9-g5wq8DOwX)o3*un7&Aq`$-P_d#Ujc0m#D$u(|dNNO*v!$ zQ9hxYQqYwvt9*wm9D@tj@nDa_C3Yve07VWkl&fsxO2Q!PYCwZGxA|5IV~>GsDznU_ zRQ!At(K=OXN`n>nrQAy7!1F@Mc!LD0*|U>J4rj3T1e}P?*4JUiB7WIe(%J^N!1X?ATea%q+wOwA` zg|cRYO@X#Rf&nmM+YqJyGct&4C0U?8)5THeTkPR{b(yv1OoJ4Kn(l2>gqm(SO0?4H zhmz(iJdj*^j7XKq!7Cm{?Y*c)Z_PnWVoU6JkiI=Z#{)IrdS%YekI-tbI5RXyZH0&C zrsQ{6-A9KQyrS>sn$31DFe9~Jp@Ct9eZzNW0mujxUL_j2rDm;;&eZR`8fV{WkW-I` zomM9!q%bU`0Q46P@wa^ERgg^)6k8}_ZpqYC%>RA|HGR6l%oRq=s-`*qM%n!|Sh{W0 z^fr`aWa)`InxjbJ7=IH5T=TtGW!OAIG1g2|SmYEZ6S)`)lOq%^w-nM3&GpiXfWmj* zLu_&Yw3yYn8>A-&x!pd1l5{&wE&W&F)(D8yq|QhVc|#hj3vZ_HWi|yb8Vs1@vHo2@ zcx4m!Mrhq;X$66TGfu-T!AOJ#YHIi>s_#CtB}YZIxgn_r!ya*xU_l#{7QzYZKNl0^ z@Ygwg4$(aW$^C8!YRIh#S`m5+jehc~mE3x%)h+%dKOu?q2Ff9_=TVaB4wh3ebS9~eT1l_!)jz>^#V6xI z%-47QCX~V+vJhNpclY7@}7&T%I0Zqx%94edr+CF0;^DC_Ye zSA*8$J399o{==wU6`MgX`yq@II3HMPoDPjwvOY#*!K*ui@9^1H{+OS=%92TA%rLL( zn0Z0WP>2Rj%y2u3982DZON$lemo7(*#twt=FNht|cQ!hhX6TvhN=SZbf7dZz_P{li z(F9XjG{r<3D-uIVtVl)}y{Es_(1g&lS5-FYh*!xUvD+(|Sb=I7Kcjw~Y{uiL2q!&> z5>1?JumUx}FBpJ>1%xEob4)C(&(IWNT`24^v>&M%@V6O8x!Q z_jonTsfhT#e(VJ^PDX2X+yhIpfly?eG`6sq9afg0LCDHHMUr7j?g)?<=^(wl2eCz8 zL5}^C(3wk*Et*8o*Rbf(%m`Pw-aM`3WY9Dzou8d3>g`p$^m9Iw4CYvso6s+G4UJm zK=$<|E3NZ*3yzq{Ugpife9fw?$9W9Ko@!@3reE>3Iz!dPPUy7dj-?v5gl^(R zG63U3svtQ?u>T)B9__U<(D|H5vpogUe{5WK#)V3{EOD+;iE~dZwfXC!fJG47i;_6u zX)ey^3J=!m;NQIJJ)Z^81}Y{kj7^p~fGW+q@h@Dex1fZW!MkbLQ{C7!b{{GnLqBLAP?f-F?_9~TlU_cngPzaT)NT9E)o@v_KxWh2_)K>PUN z6L8m|-lGNRazNRwlgA|w2?g1Dru#!-gH=h0)y2a%XmxsEB4(4Br~yLq;FvFG)^p;L zPJqf_+Jmx91Pybxb?`3;OSlIEVZ!{vd$C*Q#n8g9vCpgKd_(Z7KIGn7koQsgKjV;P zS{aQ)X6(bgdp8jY15f{zeO{%@2C^a6J(p%UkDcXl?2D(Z?)nOML{ZGLMj?3hvFrNP zcc@$(@9o#z?^Trh$0C~*TYI|#^Bzj@+%nY2D+&+Hls|1h4984Dv5%-J7wa!NTjOIR z$Ol^FX-TP03a7lNLjQfgSHmma+b?~<%a)VXJfB2;Pl=)ZbF{?;VfV4XrBQIe5$D@i zOTApVhte{XE!raoQ%@+Wx6Z2IFU@E_-+NAQyF^@iEYZ@&Y`8cOtoHbCXu7@T!lFOHzG z5z-Ii8E8KW8}qtG)d@Ht>)-!}S9j8tB$mv&96x-4A|QhG=j!{eY$n&#?nR<0d^4`F z6mbR_NP8H!Je$j)V)pD|+~Ylu`LzyvH5%;b8R2FZOtul766MC!`HU%lbi|I;hm{`($8kLW{Bs?unfH~Y9bumQD! zQNhO6n!E`yo*m3x*5jCe{hwYfp5U7Ir*~HOk!T5egZaQoPNycbI-!O-IhoZO|DfUZ zc1DS)>}j)NN4%OB{?k1`0WAch{0jkrZr_2@$)kgHT9XkS)Jh~-gZQ2L%G^E}5&wxo z!G_W!Erm3H^n8VLWJNb}-$m@=D0RYc^Txr|Z{K~TYN*OWa{v-9b7M3qWsiE5%O31T zRxF_(x*hX79Q7L6kl+>60;Qwb@yum+6N=8Cf{_n8?>K4WBRB^uj(l-Xo#3Xk!Z|p* z&|OvT`;U6n%bjWPr!Y7gJ^3~N@+zFWyQ|yR?I_U)ghig9O2Nk6MOs75Z3E1c|3Vz_ zFl(RyiUhMg=0v+`+}i7ugoy*j3Zh*ZaX=r12bR^}^%vrRs)Bq;-X=k=K?XJB(TL-G zxQj4hDEy2Py(Kg=bLNcD2UQzP!OfycYlAmo-5vRlX_=g#aKqYDQ*9rK$W^Kvhz z_`=uzpc5vUvP(fo=Dx|I#A0V;)CZn)GHW)Q>vx(41A$`|t^=Ky zj(IhPE1j%fmX0zEc9_YsM{gSm9*ju$JfxZ=8nB19%V-rj;Z!;#{_J?nZ*bhJ(N9() zct}NXi{lOsS9Roc)AB6CKl8~YL=jIYoQOBc+O)C2OyGU&xL2jO#cQPB^x5LQ0M8p` zZDuie+YewhGxdZb7vYKHcw^u+6jc?O6@B>ULlC(pb(SAkY&`9v$IJwh`#SkqK3d=+(4Usz=)MF#a z2U_H5CmuT~JhV)|19|@ng1k@O1dCb6--ywl?c8qfMoG-7t4IMrcj5VbAZ^*}p1%#VJ)g5S_;;}L=eA-VnMaCN{4nM`em zQ)4v}>j2~=SjA%`-7vERwIMU}P%_R_O_viaan6g(_gWKRpg>_os%I3QLjLTP(Z3AE zN4)L!^aX&T!8v@rN%yCd+2;U8_ad3q1plD(=Zk5P>~fd}N@kZn=a^q63!P`DLE)#f z$-EAtP6U^~#kvW#3@^}to(Mb6TGe#fFKDf(WL?IhlktGaZuWhshoy}7|6d30)5AvE z(@2xgMo27!X|q2g1=kk)iZ%GS5Rkxj=?iQ;0z}CV6G8RQKIR|EidA<$xp%A$$KB?E z_Mv3dK2?o7^vU>VTIprs3baZsZScP+QBRET2oG0_A54I96IKINe zlYWQdvC4xs5e&>46sUKr<7`yeb7)}3AM5i`#S%p3HtHx)IJ%96Lk;<2@mQ6_AcLH< z%`wqPIXx(75us2=F{MW~^f8n1(@+T-MM8to0A@uLAso+X*IrRe?cc7JA( zNs%`n)}uCLXeCO>3})mH7b-lkQ}>sMRULahfjCGdk$@g|;&L=jjv26vBKSKi9ySyA zA)+|zSR#%)?Z-<(=O1pNl<1e)|5Ri;_2b2qz*&3qKP2Q&3HVkF_6X<;+Mug1V z(gAS{)!ycAr@ui-lCo)Le1pQ#X;tt*f*i@J*LUGvkigG1gZ(JlTJ^x*im#IhjM&r& z!HFo*oeiffV8wWMs1*s8WEu*oy;D29h+p)D3H!!C;CzoYmqp|AQMA#bBtZGO;_sh= zzrnXEcr=%nX>bl&mx|RXx1kwP;XMY8B~~>%=3iMVmRr)`c>v`(rDAx?LLJ#DXf8G{ zaOHuC19LRB^XnnYrHc9eUT6n1XmxvgQiqHbRS}=SB%?tI+$QgzzlL zd0L5UuW&0H{(*R`TC8+a7}7Iix#o*^>dC4FC8z&w;)>meC`sU#CU&nYoD(YZMD>Dv z5?vz1mQL9uu=LV`l4}$m==yic#A=l5B$($K z@1D5*{beu}IT4g4Ey}=a3seeD2{sh|I2EBw->qz{Rw9KOn;KCOH!B9A|j3Z=dNT4^WEiZ z&^lamGOpd}NSfihC_=I`N@ktV({t8|Kyf_`px7hMX>iOxy?m_ZINhGbiwAphDBh5? z4rlCai#+;{`m*b5@5kGWA_pG|-KJqy1Ef2@%keMrRvTS@dz|Ru`p5j)Ta0~KmErH(+)hiarEGDDSrT|a{rz&RFE})0|HiSfkJcXidzN|gpp}Ld zuo%ApM5CC@+J%2G2x*@})oqdN^=BRPhgFE>I-9(YqJ3x!3Q;o7eP)C>Nh{e~&V(wZ zbroXgBm^tK1Z2L&bI#D$7K$LbuIXZM7oE%Uk4W zT>Vp*+|p+-Dz-&M1Ob}~#h$VT0g?-rAmBN`!wx^H&j!v=#NoVME1e+V)rtrL`Weg` z1n4WhIvGZdv``Y_&?Vz_6pMxlj(#{p;pp}{9g|v>pdP->({58tZJH#K$tywwik)VT zUSo4+tJ3v^!W}&!@=(NugPE>ce(40A=_x`FFBU1W?)HXv!%F`MwH}t5oA7~YYNGIf zljD_QRma5yGY;s80SylaMg9M8>bUHDf|9UvGXMWIt>m(xQPx4bBL_1wlQj@mjZ8JI zVL~0{a;#F3hzcj)6;VbM(@Ei&5o}b_1^YcG)*z7usFh9?Y@L*A1%ea-Yu3&97wp)|zR6M5)|#D3 zW)IY+6l=t=eV(e_3Q>VYooI(z8wsQPK{^QjK9E;MZJ@Am<0%{K=8Q@Ai{})?2EW!s zeZi59r#8IK2@$>oG>i}^s>!T%fN>Aw9AH<2!#h=AX&|ILg2M#>)3JaE@$Lb*cS47| zYaa6-ts2X9qK08;h&eREdJ!L^Yn>4_wALD~hcIf`*WRl%m3hLZST zjUA>%5MFB$o-B6grI&u+CU#h1Vh3lbpQ==h68|$-x}D*;?o>Fs zZF094RgYCptRxg0tQtEga>oNT%H#pG3e7};VzR!Iqav;dXs8w93f`Z3ev@2^8_A(` z6uLL`#x|@bb0TJ`W`tfjl*AiLQ-*(66L?@KTGWVDFT0sQc-@j8C29#B5=#LNIe1K; zB%}009q$y1aGFKygbtDiQM(AI_)x)p_8#26hMXpg)X9(dQq6>L@Nz;e6oZ4A&d8Ne zIA$kQ><9&@oOyh+$N0C*w_0{p z_r`v6Ixq`RJQoHSI(L_j{qk{ub?%Hbj3A|xq?mdV9t0h2QNpb0yjFE6)zk{AU6t}u zwcwN=tP<)L&P#L?;S1D;s5hc?^5BA7g%VkqcNOHI5}KyE^cepQekuJpvojM2u#sS2 zR=BCku>|c%aO6jK5Jm~nrQrrb5u%3P@@NoLoQon_r|P`E6DJ6ClZF~v>~IlID@oj9 zmkRBOc_$%e@MzL0EA~g_V}7IBv6?39l=VSn@`#8c8e{;JYSeNszHMcKjm2o^L>UjG zs3U846bb|pgN&Q;FR)pXSi=O^?c>W#v(e$$L-6*u+}p7tU19eUjOJ(>nv~io{hz@{ zmR6DrdjC4#jp`s5^+^u7$3WrphIAlQ{R>|cCIZZ?#>;kFls$L#8RbB^p=A(zJ`|aw+N|*SeqG7^aFP6_5EZ*NxR0dZEPx{fh1Rnh_(=+~TDNtM{~7 ze+=5h5-1rEfHk%R4rd?p8`g_eFY!fo(A9eEsutN*6sv0{y-5;KVchBBt-$bXy;z+@ ze~W=#G%5LTgpEdQYVoqGx|a@wRAb@BgYB@V)Kt^k4*ym`x{%s@+XGwWNe((cB%ToA zRrl+CG=kuWokv&b2}a_Q=e1s3J@i66Mb2~DN#Hw`7CHdu$aY|&>A;%z9dCs{l0DtF93uK^-%P1$r@Qw~qy zBr8$jvfTGp+XDK-4ebS}$9o47Z0JOo%T`qh(YWzwZZhk7G_;%8$=q=$sTD&^sdsAV zOR`9p?HqIAfIf+2yer$76geoh5=ygWvjZwi_Q2KVWakJr1|4VOUvMoSH{E4RT_dnf zsb4yNIRiQvJxAd2eOaQ{Bn~!RzCbbLV|`OZVj62OiJkQZYGH9~;%q63Fcs*AJ?1*I zVXS%Vin7Q2RKr;JY)Vw0GnVAl#7O{n*YI1;L{-^Fu?n&`gYy0|$NXlEV)f0q$KOVI zbfZ`eZ`YG=+}S8ry4aS%|!&fhEex^#-TW5k(Toz#O5Q~_b zr%)N9e~3+qB`xW4o5EE(Of1w&ig3s~1n!ksEF(%2a4NEOP&AN7N_mV#R046nz(aKHnPYN(#5JfsjkJz9a{q z5XMkDS;5~+DTjTPLoSVo75W(UA-N*9Zj}IvwB6pNQo*#L_1`&5lG+4&gUipHqU=pK z(X2j&*E@~0W8MO*fdf-q_FXAsU zEx2WM4}x|c>^Kv-eDQXa&V6VQO8M=A@2Jd{vu?&IIB-Nawt$<~mQQNWxhg358WB+0 zR#mjpeQ2intmNtuHxv3(-nhWl$e|K4RT2T9jnwFLq&7ZQ|06u38u>%C!`U>u&#cqj> z35~yP87nXG3hY8Hp&3@qaV3LdJ}0@V{*%x|7(g(F7)o*ss&5vS*SRD{B^6(t(+bfx~5!ojc6 zve&l8QN6i@!epz&&s7hP+7tsBdLfe{6h>JJ6j(qC``0F&6hrK8X4{4Ff=L!dARptJWHW2w|2n7$DS~KW3IR}Slw-AWlmvM00jIhD}5}JfP ze^RlKK{`s&sk+mW(0Np0d-Y;=TN=CSSrotg^ zIw^kjw%Eo0CWh9?TkdCn@DUXh!)*sghp$9MxGbMT37HdTrks^?pH>DQ5aar7sr9M$ zU*I?LYRITqf+M&fkDBgeXKXu1sJ282I{_I!LluRCGkfZVdkYw3iaci;I|ixtH<-7m ze)U4lliwPx=plt7`7x?PMvG9Awd>~j{VDCJuW3V)Z64GXly!)A{n;;eD_nNBu8%f1 zqE5$KU$BF9N@k4$kQb7}e8`)1VzYDt(3VHEI-!NwRw#Dan-E#RFk;)1RWQh_XM?4BL;C>YSggY)xrMz z<7nsXvdl(N$JpA#+W^tsmdW@BhAP{g7PN!6U=3n_bnm0~k?oWQcmO>zGFufTV@S-L zIr4P;6sdGJe4BNM)f)5>z_4{j$vd9t>!>g@2s3B)G?>2PI>iEcSn93sG!rE>3#8S5 zt^-ap4&~W%DOS{0p};f>$pLw;pu_4b;-H%`2RuvR7#B5Im7C7V5+ZSN<^(N z5lo*M?g0_9JrzA?xn>4a4FcL~uyqb^*!|gJp)Du7l=g{ZN;^M7n^TxDWOt%P$n6%k z=j^0$n^Jg$S@w-%3S|70;O@_ApQl6%4N&UheH zFf*J`P&@+DafwoFM8vvh6gd=JK0+`HCE3GB%S8P4&%Ytzx$9?)0+;-0u?>=f1kWt=ze2^o3Vo&3JYEe*_L{t zoByT%b>~>g#5us>TnSR2C?Ptu%b;T1etR^aBx)(G$*fcG&jt|WYlip{Fesw=(I)j& z@4|oai^(j)rq7nYAxPmjTGygTGBpzsI`;I~)2voYE~;Ctm`#Gs)LIgyLIzPepcWhz z7{~g8Be|tk47}V z{YB=3ES+7J$mRXzU(gk*&P3-gpq+$4x)hCs0TRiN%#c>N5RO4&!NIMIdS4xG>p04P zm{7H6$}U1lwsq1J$Ju}}avI@W zg$K6g%=2TF#~uCyeKsc!l1+X_NzO93DaX$QxCdv}_Q7WGhDEDuQmB!G2x<2EDTRj~ z^z8Fv)dyY&s7;PIYsm=e0Mv$q05Pt@sb~C)+>a<{6KpybXb}&k*t+0w;lKaZ#z6!7I?LiievS6A?Di(4 znt?`QQfEj$h_VHOHl!M_wbToJF@aL^r2k=ltm@@wqY46K4bWNtkGXGwv$0zLH~Zat z8?l?2G7N?p3?|pfK1x!}W^cC4ge1C%E}~NDN*DGBFHQKWlm*uYCB zkD=C*^Dr$6O#eL06Ov<5wU)9n@&|iy_1R$+?qg&*-k-h*&A^&oTRnSNP3&XD zU))**AkRh!f$ZDrwLV7k%PvE;y_#~Q(OsCBWdm(cvKLi){P4UKsgGM*=A}l~^?MGH zNuwWHL>&avRz3|ENk8?fnxJsP7Ne!$&(>E zV$cEsl9|}cq8WX0r|y2;4^25V2wAN-=FLIUi=HZa3!y&Mk54*DJq97mBXzQ`(J128 z#K|;m;&C15sbTleJPzH%R+RKOD7Yua(q#ffH}>qS6K1SQe!FDl{Ievg%^<`Ga~(XWg7)ZDkFWeX>k9gM~>8;&drEfEyL#s8-2@`ftX959?tkWUb@!K zc_&INWzw{x*TGT`0m}reJ}tySX%1>-0FCiWZVvCqnTxg7DzhEx*iTNQ+>a73wcd=MoTPPJ+22C2uu^mh9C z%Z#kSe$?90C%&Pe9V|V8a&468_(Jbw#0R1e8~}m`BLLHTyz=`Q_4`%)n6S2a7e$gQ zP-H>FE14HjYd`VAftl}cr%xwz9c^(9;GrMYi~Wpt*5mHI0i|fBT?7o%`G~bpI)|XL z4VMbZl8TuWZ^Nb66)4G_f}zz(eEV(xezcx=q_We((U?fB`Qua622vs<3~_|RGGv$l$#jNWE~~a&j(aob>V8PWR?eKT z&PFf!qw8IeT}xkP_dK%Y7= zJ`FSsPIo&)$cL*Zt@p;ewYPpMN?+)F(Z_5bj+M@(x?6HQdz{WMHJy{tV-u^#YNlXF zeOy_&S77(|(;s9^R#-wUX^B^oQQyPd%34sxuO%O< z+jiSM64Mug-kGl-S`J0kl9(zABXcHHys8f(V0r5*~jkKlHf&e?klZoFc#;7Ba? zeos!Srz+lnjA&=t42W%zt5DJjfmW?GMnr*^1PG6^yE|Gs;Hl^ESv@2MyxfriK14*V zJj$Wa8|}{8g5V$&`K+hfV&fvIk53;51{qmHQc!8dnVM^jL*K=RK~@Bl6kSGr+X11F zgZE}`#7|xeA?nk|Kq!*$WY}iDXy`p)&ob=UhPr0eVgQ`hUEAncR9t_JXeJ6BJ$*7W2{* zjn&Ps-;!>spZ8chWmN*DK1p-&K5;7dNR0hW;+_Y(rm! z5~fq@0JTwks+%nUd)jzvh|%n-5mbxEx3S8>8x?U>gnd{?PDb{tySXr-n+{AZ1}Q!+ z+qGD(`Z7=%x9!xcFe@GTn#eEKr$LhGN!-*_w*tXtehs11Ef{hwhl4JbdNN#@rHigI z+C=W7T3qtWBddyfZblKtk4@$DYw**GZ@44Ndej-0?)L)0kcG+&!SGjLIIr`*C!%4r zOFD4K>}b|2h<5OD6YHg?NB5}KR~yaie7Z*uK=r>GcZ-)v5qFEnpr~JD+2t|R;wCHj zFPGHrxvPzi1N&P|p?h#i&LMk=de+IaXaSD=kevra%*k)E$hE?RE`8$}I}9r88f@Ku zvD+3Z2m!;Q9|Ggpwbt4c89y9 zvUKr{|C|IMIYEV5d5w`?CoI(Q=*^ec7>(+*mLddm>KdbImX@-mK1c?k*OKiwyVQl( z8qKdO+m)b3(zDpLR^ohtTS{a*AkOvvuJzK|2Y@D0!Y-E8$4$G`d+4K2Ah8d20q2UR zd14;~2d(%O3*AT4<6o(qp+mQ^?q{ z`UA6S`E_`A-bK^_pYibHigf+CEmz?sH+-o&hF7r1FDS32ha8u}?=Y+3;D zd|`FC7r)}m9!TxloK*Bmw~F-%PDSZu`>Q?xcPRt-+VZ>96zNGSMu|baO6}v;ZE|>i3C|$VG z`=2sB6$uMEyj*Ry<*A2QJScE&o87Fg0LPnmP~sWNUgmIpeXAZ8H`*DqPkhb|i0-r+ zL*HcqgE{@Jpw8sh3V_0HD)Z0jCi;KP0t9o0=sDnejCcxF>kDUbdEpTla?|+KH5^EZ zl86ahOCn$390)~H2NT+7S8E^434=H_rW>S&v$cAm1h;Fx*^$`c;+!&wfNpUtB*IbO zDj*c#d^2CWdVzUGPM)0|ya+IaO|r`tP8??Qs11zO5bP}kjPTyb(IT-+>$TjY5eHvs%v3U+RezB8<~A&$A$S;d%|}HWDD+oQy$K zh$q%!4stxP9ew4}hCUv=Nf6{W1Q9?oXo`gv-efdvS4t4Lb6#9>NMsYn{vpuQ6`%{Q~gI6^$TTo z@Vz0pG|pKLyNLSc+JIUK0*Kgv{I~$#7=aZ?0U-8`4##$*9{`M-Y=W0{;_|jpoh@(* z$r_$3a?@sc}f45(Rq8RCD~G7)aYPGmccsudI_s|K~7ir9PN=}AZ72+N8dkj z3>ulTc(gt4GJ%WG7_MWP7TWw8?aA@&_C+kwy#1(ay&fc(CVX*!nU=xDE0-b7-fm}pCRvp@(y@h~pWSi*tltAvG>2wh{hZ*WNv z)1sLQ6t+k8+$eb3NGU?_A5gJ(9QqX%+r**2qZp}eLCIEem5cVAX&LzQR-$Pg6z@Ta z?w;7;(J|~b7EofJ1M;dsU|g&0VLu_2lszPyP08c}w;76b-0-L=u=vL2wWllR#jJ zgFw{x0!o^XS%T&e%VZmvQ6r#@w1IQkfFsT+;8KB-%}}7Uq$=<3;OK~}$iT4xSliky z7ACZid|r$1@Ma43D{}JL%B*dvG8+GA8UGewaIhysVF9KljQ*#EzTQ!Q1FnD_G_MEzLrU=YtNB|D%#=z%{Ll zIcTv>8&G7+Ob?qgq4*B#GnMVhI&saFtkc>kKq7~B_TcXaK^C4`G8#U3 zr4%t!R#Nf2IP_;KHi$!0ZpTdd>!!Hi!E+2HzPlXH6&<78U;_`B(+X$3DiOoAH99gfsz;)>&!AKy%n-!HnvcIQLZZ(PL zps-?ovp0?&7g#`vwGPO18$zS7H<9mRK${9O)m)6ih76G=1ULxZ7@(@|GBP8%fY2>i z{c3Y`+yFHssHL_XfX`EBH2v5|q$bWKC?C>dAbNZsFMWp%r}!i0Ml9;~?>gWlB)qfN z8iNpc%0iF?jYMd-e%J!y<7YzMA(mk_kd~Y1+5zlg19j#pK)UA;O>ZSo5>FZE(r`Gh zNv~%E4kiGORInU4C|CkzQlATevL?p~^ct$gnlQh*K(fhjmMUw?amT*J(cEaod6d z_^ALXEgWM@Z>3c$f!GH8-3Cw)wgJ&~>hXIqUa}_H*}4^|h!SExzH3p^OHCaPMP7UX z4bi8&4W}p)AHUT;(}wG4nArZrTBe^#_Adz>U*xtK_+sq|HDEcuVz2X-mC}Ilb^L}@ zti=0oHc$s=2XKl2$%6W`46Iywv<=8h01UJNV{UOkXv>CnUNIEf5bXdo>taa4;;udb z)|3jEIy#Ge!*HJ&$| z5GO8~oO=C!T;#e!6Caa6+;M%=pFq5oMcldff}xPl!3MmH3~8#YvA9LBYmIPDhGub2 z>Z*vxXk!0eVA$M0#rH;T$lzloi5j|Y_2=}mnE#6wC>PI<5xz{HUAIL;72UOd~xKQe?^`bgB&S-Ld8q>ZP z36j0|)1ud$|Gdh{`5u(?+KV>+5=Fm8faEiz7tS8k=m%h*iXgNEVl7*lJejt(&-Wm9%1Q2w=?C?6#>tssZ#><}RF zPdft4Kd;D2m!3>IiDn7M{)>Q(Q;O8ftw&uF4sjj>)VA0nrd*}B0FZm#$zaQ{t`{(6 zMIy8f4;uI)7;42O)S(BlxM;|2*m&Vbb+`>XfmT?2B*@@MIq`dARucDF84bP+p#z84 zO?PJHIo_K89$+LDT3v|nD0||-OeZ~no~M^alsA*!In(1)K;HyvEMLu{r5ZypL!;qM zW$L|$jBI{y0E!ptUP!z(dudvxk{&kFId}v4)Jmd?lYG&YMU`??WJDBACLXE!y=VeI z3lvS?40JlErgRopC#;m`T~w1`>F|hr6tkIFS^D#U$RX7e_=RH~efEDAGBjj$_Ov&4 zrF!|{gfFNRqJfqURB;JPJnbRmkYq@QvTklA5E=q0Nt9c6Jiegv^9Q6{c^F>qDrryn z3;^43Dp2B+`?5sBOQqhP1|A-dt5T0rEw+`1Sd{fM91!uX;e{wJ*VX<?rM%>w)oP`|tXe1$|SUlC~p&WhRIh1`UF)8P~0O@U&dURMW z^0|6~k(K%))nX`NJrvdaa!l(*2^8zWXW1qbjTVtz?~y}Vw}2hj z0oMXxJ7g(Jyj#n&D=vh*%wWsmpAs+`9wn%8JJ7qoxHa@^(#cEj6)lgZruyPo!mFpQ zV47B>?}ZZ2xz)zeQR?HF;K_+NSgD0d+xx{awAFq0u5%#UQPK+FHH#x|Vg4YU0Lnhe zw!^V34IW&a-WNlt@6JC^Qt=GAW#|tX-a}*_~!foOYD4tIk+PCgc~yRRqpakI{vf&}v0 zcc_oWY?s#40SS}aY9QFOml67D?u7HaVq#d_9CW zsYdhEF%CM>4mZ55R!qh1qTX==Urn_cY9a)n3ncRvL*BPl#xz*=*KaXHq7l5T2^J?- ztYg_KfwTX>nuTRSS>N_ zYo!~D4RRZ?SItIufm>{_Tamx%%s|+{wHx*r>y?&t{VAi^Xx{akHxmZQNX~0!s|9XR zXLz*fYj3K%ij9_9uT3WcNrvdS1~&Rl^=7e=Q>T~JgW6RL%V=#8RHusVTVE&NP|c@f zEvRpO?RWzOaqElRm_D^mf0qOgT@OXk1+Di0PJBP5b?Iw9AT*=kZU%nEFMZLxu|_U^ zJv$wb{9H?D(ey$1Lzlc97rdNrpgfO>k@vM{Vmx~w{(zYC4U~A`wAJ*KZq|YTZxUNd zfnr8Z5xCY77fXp4YH68(oo{uCX*6ofGG$)h0|?RFy3)0XFcV%x`G{%cTzWsM69V`M zs)z^h!P1K22HgU3tN<<&h+a`pDs#{@+C)55O_{pm0?4%ZmKY8Dn>EY>1rV6 zmGhUVA7&W2*0!5hn)R_RU0O5Cf!CRwxICxiXuBg`X)QQVxkf5UY@>L>Lu96GF_@DL zCiGyQCnQ=USVD|oIHd_v91j#Lz)H3`U*MX=P=PzdF)cwH7u9IbH*VuBG)-t9A$j&s zVBq9yHUl{MM@h@`Xf=6aA!Gw*2@QuGEwg~t79Gz@JtYUjlSafdPVQte-`nL}l;}l$ zcLCXwX4H41G{x#6ei21Pv^haU-3bkgmHrS63z|P*P^Tf&LXN7=oQcr#+hQIkCW($z z+%82xFGDd>VnO3-M80S3{XA{q0vPqpKuL?JtcDcsw1Eq2057+JLlS{~K(~uPNg5S^ z+G5DG0rP2ywBBG&3>z@qYN0yJ!f>hbb(ml{fzE9Lw;~q$i4@83G=aV~2+U0&(9S^suX934yV5wPKSzCr1%VCYiyMi+ z?_Z6pWvPC*iNiWS5~!84vG|r*bvV?s#K(Yn79g0@)si-aG0LcP6ncPAU~{wJBy!P7 zBs`4=9yDQ?pNpmnfOlb zRunnWL~brhaY$vE03~)i>Ysf@P5i5UTcAkL$TBI&S|W-1MhXDo5-FQym6yjWF{yH; z?tgfq(g_U-^JbRd0x>^3Jkn_DVV3a#jYp%hXTxp3TgXMYZ9a;T52Z+^$v5L18hX{j z)S!6W({h?_H-+;(j1p6j_-)C8t0w2@kW34df!s{b7ZG2`YB$+>Ovq{q+*|>Y+$fmZ zB1*9V6i98ruP-wj3Hv3xNyiisY#XQ~wBnm`YUjzLPmcB5;wr>J7LFR0cYZyB%~cX~Mz?;S&L4qUyBt%J2gE^ZrGBpf<`Km0x3a#Rh2 zT`xeAXjy{b-uU~8w$vd(7A8RE+D$0vvaHqICvbE<3J}MeZgp_f=FyjcR?v%)3MfX{ z!e}RO33?3IK~cA_Tdh4piQV|XOCTPxZn<-79T9cwO@t8l;WCu;-gc@m;gHvhHc*Fc z2k=oFs6(<1jQDO5sI^zdv2T!7Co$GG&F(f}sU=6Wpf#*J$F_rXLgQB}TRT+}H0;42 zS-@kPA3@~99*pce>Ty>IOr{#WC8ZYGi`3;o!lryz ztnlKwM#Hpq1i=NzJgTPKKKw25$lp*63lRIl#u0Xwgi}4@M+7dCVmL(-Tgo@oXrG_Z zzDKq8jx~Q9fFf4kQC}kz=}3cwDkL02Iq|nRQ0Ju%;4T~3zedYd0;B{w+ZNtpUE2zR znvXU%WVVGbkya%5>{FNoMiB(}r79@K>fK5(O98@`M^7MGW0K=F9UQehTMAs{$-$Ar zh~3_((SEqVt@~2nykNVmbTA}=z-ocBLZiNADCuL$DnWC|ZLSTRFA>@Se8>Vy_{~5Z zk=aOs-)5qQFZ%%tyORCz#};8gnvxMx+_eF@Hh^NT1(0DR3x8TpC+9o2Z|S5YRXgan zfUtyB;2F%+Yoth2G-mf&!=mv=tx?DMC{Rq*Nf}6T3x25Dz|?gjG6i z(V=>rNG%yp8=61{6nn^c%=rM`8OZy{Q)C{dTL){3u+W3`Dx|^O13}|YcmXN! zy!vvUkx_?wK{5Mz^*f5UGAW!#K1ckP>45A!|NKrvqMn`jh24au6vY@hpZtJsJlxMM+nyy9}Mu!1la+Cm@%v5}@v+X&?Jw)`v#57-DgVrdOn z6o%Lcqg=Hjry+5^jZq;?kR16rP<*khkqw=1OIe_@Ur(VFhzFm6nLr2D0gQvjrvPDd z4bqPA*SHwOR@iiT9F#-R(gAtFf*@4JtNB@ATsfNzV8VvkJya3jFdKoAUghhxCP@Ww z*@Tb!LN)@04J}ujKr0)*)K(1O93i4Uj}52bQltHMPsbTZ)-t8WD#9VM!iFAoLK(Qo z0!q>u4}nqN;{qY0rN~MIAw05toWiDQ|Y z4S+T8Mtx_Y#OGkuHi?cu#r24G*sRxU#;Q{9?G1kcw(QbD{Iu)1IYOwI?MW+kkJYb7 zzX~k7+j&AhzU^&o_f3|1#|9I+D;>Z2{x`T=8iwxnYTav4gWp%1|yT7U~AQ<;`r z1rzaW1>KQAbT~K0btoEwgTb)W)0`W(GfwtcV2G}GliH}|3-w#g8WT-R7_5%omOa#R zbcZDnHcp?UDGrOKM127ADM94!5pTzleXDC;!aL#?0&=DEE2_2rrG&$j?&6&gwe|dsN zLs#eHs~fmXv$u!N;WYv$gwo#zU|QCka3=L805rUgw3Lhb7NE#hNzUZZ)eNaePx{W; z{J+Cju4Ag&dLVCY|9T+#fXdgO_aV>Pn`%q3HB&}+MOup;?J07><<{=g8x3e6LgVt~#P0wR%)dS1dW zWL|uS3pM1Xq))q($#c}xFJoUqJH{|3D>t?hX@&sd{8;qskXbu{5N%SOP5UeXkm2KM zM9SwKFU7IEHGp4c^VJ9vGq}bCAN75MDmqM$dUoJf{N`Cj!g&W?8L!c_B5>ICdBko$-J}mT3|yjK zst&(`yfj^E5mr-QHJY|$iE)Y~pOKAr`SNkjgXMY*J~Z;U>i??IeE1?%gJX*tP3s9~ z{-(g$fT25vQ=Z1x;*dvKA9=bFcyh3--~C66f-Z%Rt1al|>b4FlxU!I)aANOsun_+~ zCQkqk`mtFSeM)$(QEZ*13KroVKyLzS$sIDGOj~y7wFv(za2_Q_L2&y3-qFjg(hKDP z(u-QX$Y_(=nX03nc2+Oy_#z{-j@KfpvKAXn+h~!~3u0;dq)-J3te=@6_T@9xjf;&I zS1DA-8K&99`9`n@1t{DW02sd%<6w0VkN`mP;E+fDO!d`bypSu)K>_lhEG{8GIrW&` zJ>)@f&=Rdsc5G>NJMg#@T6aR|YS3#&iwj1#%M7Khkqm;OQx{=xEU^Ic@;TFU8;g5ss z)j(#haf^yHVN-u4s_uIocTlDXIqXNwe;xY~BP{Bw>~$l9!wm*=k&pp{TEyxgSQ(It zYTUiD;$;$wGXL>95-aYg=cgGQ-SsU1`nCzbH<*`0QC}sXb}%lVp@uFoTBeCK<$II} zi_sbC@g+v1i}lQL8`@A}L|4zEo}pBNAC1BkQp)1=?*^M67T{x~JOM}^f8GogTViBh z%cSvGELzdjq@zpXH1ZFVWQHs*zPNPEB-;f@GYMtdHuE2tYWPy4@pZa~XMjiI0)pnd zm|N1_x(I%OUA~8gd?cP0Vq!$Ny=~o26Mqw&wh%336ZFtOUG*={qoPiPXdS@W93+)N zUEr3Ob*duQ!dz7AuZnl0+P+~l8~n+1I}LH{LD1CpEOau!jeKpHkhBTFWvKf}MN_o?0HuUFW zVqEUdk&g7f0CY&aZvoV%yc#8a_f0Q09jg4h07;dygrjM6`kO|3ooS>G1Atyhd$iZ2X9!mm$LW8T8P;S)f{uG=gzz4S*>L_57WbF6Z1*8s+Iu3N8g zv;g}d6iL=&v;o9wXkgzSQJnpJJksj}1mmiYK-Ox&xD*(X_z>{O z1Imy$GUH^fl|a%8wHU{0+O3RN+hfVbSX|td=$a;74w~W%=;qr;Pn`14v4X;9D3MrD zLqm=LVU1L+wvpaM4e>ro^G?X@+Mc<9^zhG-+!=2pN0H2#21!W5@;zKj^teJZTiib{ zA-gFAmg$~HQ`4BkN%bRnRMy!LUVw-aVu%DKhYy`0^%z3@4iJ_-2Y;RZPhR%$;*eKU z1?Iyztw-8Cg}j%#p{&TW9z`qq=mez%RoG6k*1@l!sKXEEDhc6dh5%{I6q|H3=2a8z zq~H?ok*0Sp86qw@49NBkYe#b2i+g02ApfpRv&H5bm!Ps4pxdIytFkJn9B-9`1DAX!>@bLFcClM)__#8oe<|-Cl}l zG#A*8XjF`%#cjgf&@mcNBN`2Mv;g~I6v-dv>Qy-Vq0Dt10ID91&O=&915X@+8<^yI zXe=StcH;UJkRDMNylXUxR6KGz**li=F9X3IjUx~}rU5(}yvhYdV!En2)A0Z_R2o>S zxQrU8ZSTgvK<>*$4Osr8*pmacJ-QcGw5-;ug`bHD;RhuW0>uV|ABsLwM~YXMpL5Ju^@w+okbYp(g6PpKEKzp1&ky`Tc>Vw_p+;x9Ljtlc)Vfi?A(<99ATa zPjnxj05O^m#dWfl8%@smx`@1!kB`gtxa#*@gR-3-yn_VmohONJc^tOo$$X z3Yc_K`0nboy6x{ zXL!+on&HLK0z%6|kxZN~AS1ju{L&3j^*pB~{PG~I#p#66^qmu&p+)t3-^hq8LtV-{ zNDy(RB<`c-G7kVpWQn3^`=TSyxmN>fM3!qDEii%t6seb3f|Cwr&o~#b>Y;@e&^1Po z3`BA&a8f<8;B~g1z2l{igSAxPZKFZtBb4J>K!QVk+iGw`n)yJqN9Tw%Q#C-l$vEAC z7L`*CLm9*y3ZTT1<~$6$H>xF?TRB4))L;y&8H>^g2t~5Z{9KHo!FZLt(%zCg^e{xj ztJ{4U@O{n4POjZcZ}BY0mLk8)n!Xin2b*_+LA}W5BYwgcMh`gJKLaTBOEyBjZik+U zfKceMt}Wk^*p5Bda&0{JM16fVK^jPL|E}nHt^URiWY7^n)KKuy{AUt6P7|V@ zv+zqVFHu1~(KdlO`3y^3haXby%Z#imbr4W#uh)N?5+L{-~Bc}+=)@03qTmpF%&^E&|8a#)QK{r z**Y`)=Yzxw`;~PThh5N}?GD@qjvX7J+knT;7mqrQ@w~%CuwxPrT8{kiDFKqmC^6#T zb7n2|RJqYMaur~f;oAo9h&x?@U>Urespxo@r2$$bsVK^P;ET#X{HqApb2yB>u0JpI&*DT|$iyk!CqtN|pP{*BN837ovT zg--fOEqk91D5$-_Q5%l7@U=x5jII-=I07|975JpULVP~d5PTjS$9!gj&+q7`P7@K% zKSPoB{4LHL%P1`I8Az8dWR*l5CLBi01p+Ojtk&Rg3RTHo19LzbR|HSQ6Ddhz;?+^# zRj4I14dVs&TrHyRfocpPA%y{2C80vx?JHRGD3CQ zXp|@2qz-K~_H=yjE-x~wNPeG52a@hv8a5k*qP>tEZ;0(Ny*N_rl^lv*hH?X{x?{$Z zn~Z>~?qjOwCgU2{W3$x!O~y;DdXE7KK9n*yrKB1uX|+;P`=+L(g;QgnT%Qb$eH`0( zy(-vdXh7xnv<4@H#@DUGtgobQ?A551Goj+G48l)hcs4n7T^!+HMqN zUD_oT8*gjE#Z%@(A}Oh!2fp&v^24S-Pf7KS|4O~F-N?B19@K@4$7iRc6?RQY3x#7X z&QC%_jSi=f7%ki#YcVQ`+O`8;;9W`zg3;dDAkdZ?MxS83;_*Y!Ha8rN@|;HGQaz2n zQmv|tZ_{tQ(M^a&Opp;`MKe^h9mZnAWq8%cJB&=@t#q&Yafk7|3&A7$xsmBIj;TeT z8!eNbQ;%a`hwFO8-b zw-y@H=)uu!)JvH=Z6_e=G>?CVsM9V|OPzN6Qmy&Ycs_E=?;h@s1hx)jvLUh%?Ryv2 z@^6hbN%KDd?W?DRr#^G38bP$A7B(uOtg;u?ggWllNq4H3>bNg5j=NGzzN_QD$z$AF zyOv7&v0menn^W9fYr8J~Rn2eUej#Z^Nsor^qejwo>cS@O){&jvYt`Cl|F^eYW@7oD zH+mD`&)kt%-uYl+`JuNH%ZnaOEI)Jn)G52WjcRujcMDgy?#i9%&T+kcP_@l;zus(4 z7M#-DM&^(mD+yD9y*ljQ7#nh}`Zd$tx#3^(FiTEajmK#R=M`WYkM_%sW#^qxmo#-Z zNUjYa^`ZtO`Rb=eN9PoUC;nIj=Y~{={;Vc8bwA$py${F-3iH?j^5YILA2Pjji|W+O zz2?kYvM4GSj-h@FuU1J}?(ep21C+;_TKmZ?Qq?xu~t{n=G3P)hA72=bqToV?gi zE7hJXcVpKZKdX~KL(FcH?QZ9K@<$cUb{DzaE7kgJccyFY0Ts)3|1D{-GMl?wW_R45 zA|V}nX&{9_cROUlRfCl!4M9g=tEG$!;`*4cb>=hX-=l> zCMxFPJ~!fG&5rQ8DP{QncdhJNNl7V#%TTO9iT=ar%lJDZ>*;1AeAKvaP? zW1XfDTc1%ecJ`z6JGz@%fWtXgfZ?g*!F# zhrOD?P;ZE7$5LBNUoKT!TDS{bS?8&&mhOypA3o>;@xtEJ(MN-%qYScziE9p`e{`e& z+uKCl(9(Tj+MXP-yuH*i{0n7$(4L1Xv}& zp#tPtH`g~zU^Mw6_$8ym*otL5xPv^n!qnL4cX(_c=0IYln+D9g_ta}fAF#D1M;*^` zH+3z`QH@&R@85G&SN!d!fCyS-PWBl7%9T@y2YU=r!PKnG%uzF2xgT~-d`J0OyR%$* zAE}P5-7O+3F0T#up2sie>SJD$-N$?=w~zTvT-$q>4?gLPMEQdT4i3Ws=EE6=(;!Sw zo$oCZqX}%o+1d!U;)iVnA+rp?O;krZwQL}Qp{_?|4I_Bz3JyDnTjZ)E2eFjK023CJ zk4E<~r9nuRVPk}&)qgt^i>v<*n>On`R+vCHC25fA*T&t*wP1j{wT-(?C5e6WF;CKX|Z|_Q2$kbWqYma6MkxN@6ki2BDe)QW3(_ zZlLLM-&rJ%t2A&v07K<>U-Fgfbv=c$zMEq$CKw>?+4+l)BN{qH;z@F#L?s!P5L=o? zGlG2IjhuRZe~xc7n0S2K+hm|Ka-6+!?=!C_%shMRs=wvB+htXLV>x?&8hWbd_Zb+v z&Z1pW^<%C(!*z6q@}A>vU4JQHl#!9xipP&bnv7LN{yFaK=JNo7?kP_m75n*>Q^&{5;{eNTWGV zs(o#d2-Lbvo!QRavT!F>PF7UL-@WY;vkDm7-sE*Q%3ia|E&xAVd~Z(n;P9j$vnO5D zsfn7<&fU1)oZFyh%y)S)mtQSt=gzq--;<2BK{oowqI(#?0dq@%Syo^kj5WHPR&?pz zsEt{f-}$H5lFnF#O)0y#ZrH4db@nIM%3qcQj7-6Z&FZWgw0Ac*wz!j3xAyLfB2&6j z>aZ!cjM_!h|9&e|iZ!63S>@k~MQbqH!xc;FLKPcg7o3Z~H&##*UhKe!fi z$+b4ddV$2o*zQrPV<&f>>*6of@J`@tWFNJm6F3X^QTsZ9v-?%;Ja=nXo10aqJohDe zkHxBU_9JSXoMpyr&ayYCr5)Y9x=jn$U@aY0r(x}pGl;bzps_J__~sL*)Wx0L&69Qt zZ&P~w>D& z@bb_y{@K~xWaOhe;$aTE$neVV9-%M?XZ;aBWI78Yz{_+Nqq=Be8AUWq zyp%`+^J~;nd_&Dbs!8>v5?=B6eBe$n(@o4E_LSl?gnbxW zmz3{(7M#Kp0A)hIEE)u$$pGyQq^Aix5 z=BKB?G(RDM2|tFfccf(O1z4~f-j`hJU^f`#^WEIZt`9#^uXJ+31ty)LjSfh`qSOki&c>?oCxy@Ob6$t`TIxqwhkfU~k_wurDlu)^xj9_~7> z^G~Yn-Q8Jz8$6&*EZ5frKiRLXAmM4mHSEpWS{e$DWi8=x)P&;oo~ESOsB69h$Ty+~kM>yHRbwCf|Nv-p#}Fcw_TzW?Gp;R|+KlT6iZ1nP}?zeh%TIVT<<2f z<0*U8RXwm6ob#J{um?ivnDf+gJ>1>TUiFa`O7n@YXw%2PRzLM{k8ivRZpG1gC(55UrF6d5em+z=y>wf%_(5LAzoWgHUjXm>a-}LSa9@F+Y+EwtGI!4;*M}=gcJy=q>~aNLm#n|sJ=Wt|SEYImaW8Utt4a08@+{SfP($lDPqw<3tPd@Fw%eClpbej;hYHI`1{OOXDPrHk*HL}uE zRMBtl8LoqGmz;SNYI1!xp``0^cSha%i(mHE+JA28CD2ZwU0K16WwEDUF1g9$sqIQB ze_S@3@}Yr?<~&~VU~SLcwUdR$jmh^6a9udM zWOcr0ev<2z^VEnQo=cN%ELqyab6rx>4J9c-PrH!ohDoY>vFCN;td?H&Q?ch2%Zt5)-Vax$;Gk(Gwc zW8q2V+5WA8()k!fY-KZVEmi+gch|@}J(Ged@1XTSqx|b+5D3;SOL?cKtM2CVuTukc zH%~k~^7}fs7B@tl2Z%l0z^v`W^xBwHULU!l zPC@@Z!mS6~6esSPHg30H`n^;<7#Ka%2o=}12A(a9q5&$-vQZ&#oT$y{OT&3K zexr|fUsF6d6DxhawBI;;!tlnC%@93y@3D1{J*{ZH4`N47o;p<=eE;R}sdeE=r&90U zmy$X#HE0&ToRZq4z`T7?N@}aJ*7a)L97vh48TPg6=9GJ?-1xEK=9CFlA)^B29Vk!O z6ErYq4gDcxZb?ZS^jpwBFgt*koI@BXX(MU}%2%ZZjP+(@OTfRGXqA<(uLmk?g2q0k z7&7)2n1xF~FkpTd@UJSbNG=YIHOh-a&rqRuacDL=DHjeVpjTqxo~mT(J{ety&Cpxu z8r|lgTN13*WkQb;bZPJyk^@FLdMqgpjigR>j}fChXdWyMO%3`Fqd)XBp*&E&sWy5L z>bam!s?gL6%<;=WYV||cFeN+z;)Z!W&@*jklk^@mU z?phRXHwQgqR0fPnUOJfLcY-dcfOPkuKNOK6Z7vSYWLlLnuwy~v7~1X%7#joTZ${zl zfd6379Py2GKM9RyUvy8S2Ye&W4Ev42*xa8TuyV zKLjbTjrq~U5-SMpWQ`abjlxKrQZV9&vLHEP3EEEuxm?id8FhCw4~NX-{`(>!|FQB- z^#ag-5Jw{*v@&Sy!JkEB!y7~X3UGvel37M&slB3^XTTA*2mD7cEEgHO|1kK0p==5o zA0ZM1fbYjWy3o|}iU{2D88(*OfPXC;OT?rBuOk};UCZ8vjtu&b2hBky>~RU00lA+= z(Ax|g;|9!=uv!1npnnUrM&1;4H#7%Lg2)5L)_{K z{=+ai2%&*FXf|G7FFej9o**MgBolu57TfST%^D;X^nV^G-vCnI1dG8VOJ-Zh{D-#S zP&Bgv><4B#GHyU0!GMB(m?;b>l3+mrV-@RsJ8VL%hI9t;KtVcenJ_?TFlgTXGF$Rm zTSV|9s1_O*mxPLz!*JLJ$Hp}d0}oj?2xUidwe3uTi!KSU@vzEZyq4{lh2O9ZZiJw7 zKnP;k$$f{O5pT~G>&Zl77^*Y}O*Pg+Fd_4Z*kaI6&6uq{68rWbT9EO45;h~DUE)1t z*C9W_;mw5Ut~RzkTI2_lY&VjD?3 zpje`hwx@`)NIYhzHVg6_E2h9GB4p<}stlTH*@f5yDG|~{q})_nho|jqs@pJVw9BPv z$ELAMOk*|}B4xEG8Zk0>!LmY$Sr&z^C~Q zeM`WZ1j$4==peRy4bxf|DBlQ2KEbwfj3gW~e@hH180vFq&zR?lJ5nfIk zU6zxcIgC({fiPA>u22`55~+*g4NREQB-UVPQrw&{%RA%C5^IjLXB5~GqE&S(QZVEa zXLR8>#S=b8QOTBF8G&hI7SNL%{0&nxO_XD`{ZtPs@GH!fFdWzq=}tqh2x5yejr&BlSYiB3<#fi<7l#-EgFKMXcvMtNvN^JLsoG5*pe7p zOpJ>#E|VF_(l+O5O5kJ)d^v6@yY14*0PdgKZPCWCLVT_BkX8f zK}G|5m2q342BaX8DrS8aYNeh+Bzi?)DfVQ2X`X|Kc40)UIhs?)EF23(7+7sk z=t0Xrv>9;an2xOE7p{v{hD=FQIi0aI9Re>jiNGj@Aw?<@gPHy7Z7GrELE{k2HaB4Y z${A{CCbWyZ7pYFrUm-~y>;$5u09af|O51h%a03}E3Yt~%kTWUduj24UQHPSL4IFVo z(Rybi9Fr)_a(J$eI2+i4z#hjP$&0qb0|F+VvAX>mD<7_pe0UYY4s2_=4mkcp+nGf3ld%~xLW;sV4w^%!#_bTqXobf4w1UD4%s%5QgZ>@t*<%Cct0f5@ zL{*EMTKt6`i!iBIZ!0det7Hw<_nOKz|1>OK*yfyp6TN+370PLWmV+y<| zf?SM4iWq+onRhNCNusr|b;Ml=Iil^gSYYWz38o_0MLd+?UMFPIAvUc*m-YQk4u-!U@Syc%U$Z$T54&Jx$31f&Z_=#%c~5 z@I^?@iW=mwR_+;}VkB(rijNr$tf)aDLp#+>Xl}c)iagwm9ONYJ5ZG8G2~jQvO^Z|n z6ViWt)D%DLAOsS~p%g10GoL-rOy!`0Rx;6$@{u`jMw(*hurl-UY!k5pM#%W?|2beF z5rY_m#_d=J{ZYsmi*=kGF)TgC=NOqgWsO)uu~pF)14cyifUPb;#oheeh{4%YgIGam>ZK^+s?8Zs)1{{5H?)&3d$$`2?sJ|1;h&=wuR!t{Q=t9flQcS!>BtA^xnc8L&G*c zE)Jc|A!BPM=s$`r+~Q!UwqC(dh9X;x zG-nl$H*_9At_M0tK!-_WfkbhrE-VaZfIsj=H9LFUg$*+3WNYFY+LGo=UPI}c*U;IA z-fj>X4V_#`7xwy?u>#(%E}T-9GP#6zuP4%xB^`F+F6_fOT~o61Yu%iZH5rw)YJE_I zf5OqedEs^k!v?;D^;!7B1NYxjV6Kf_kB3~sNpErE=)k;5J+t$1rEbzg+4++O=G2O9 z{Q)-@Q+tjHPnxupuC$jG7{7+iNraw~Y7$voWX(%SoivMo_Qg#XbH3E#rgz`r;dTe^ zdt`q~f%#$V=t=5n5%*+*L%M92NBi^nbn$L2(`{i#!?F)g$iFGQLa+QR>C<$t%CJ>AnnpYZQb`QOj@-|c2kU{>+( z9sKX-{O?Y)XT52mFZlPD{O?!zH!>}>%fu;5YH0Jc&~C2Y!~cHG|9)fktUoREE&u+G z|NWl-{lV;+F)bA1-+TGref;l!vuEbC(2xB4C;s;U|NAqpG^ZBsPD%5oq+ON@cR5JM zl_xiOGH^yBUnPX5`tzd)a{aXAc+}%-;2Ke&>MZbF;ad5f8n(c5qpNYLTC>1&u4`;x z_1gl^`L5^otGt&y=eX|Kk2isOI=RmJRn2(GGdk(pl4CD!`MtdmeICu2cP1 zdCHPLR;jB!tz74KQ0K4qT;v+^vATaXns@kEy|CKT)wQ}x?Og3Cb@iF6o>=3#)OFE^ zC41I*ZclP;T3ynAt!GS@tKOoLRo{8=^wFxlYO&Yb!Pu5@yyOe7cU{sMbs8SWg-ZnY z9V#i?+p^u52%f%tXNx&w_82n_&s`r1m>=QOWEtiRbIc^ENO@;Hm8PoUXM3BqBQ)Ir zO~d{}eas3+161H3@FQuE-@w}}LhnJ}mtn4$o!Au)I`bMF$D`=S0(dgT?vX0+dI-E) zE}4l?A3&+p_2xX2IFLT36UnfDM-OwxUy0kGSw9Cw05{+T(_7My;~nd6W_?MTH~l;w zVjpu8dc%R-mH-VJ1Pc7vc48J#qeuNwJ|5rY;#5`M$lJ6dw@qkNE|l;#J*!bU`qgcc z+IU+<^neSB3-eK}1ze7MzcgG&Gw{2K-cBlQi6NrYr+hc|#pjqrg)kV3p@;p%`EnXB zeu7VdOchA<(xigAtBUL86cnqhy51(n-Sv;Fi|TsM=|p1FBaaUp+IA|B)F0RO#8#wj zbZ(mmYujlaqU{RN_WTasHj!%P5lw&c>_1fUM|c$b50xyVh9OO7#{Wpk=YzZ@H>h6q z>gdhrsF^_hze~ZRxGzW;y8HJj_+6j3Npe*h1Z)=0SCx&uIe(UxPo(~@YWZ1^{}0eI zPNV+>Hy)Vk?Hp{A0s+Ha;Ea7ZLSy6X?I&zQe?lUe<>WJT5oT}i`O*-Q%$&4() zj7ivjZMD;mt=qw~bFfFYqI!RJ@V47g^k&DFwiJ~d;X7c?i|T;(5hHLw+tJgU@kF%% zZa3cnhw9^3@1}X{b)f+#L~uQBM+Dd7wjy{G6BkGD>deDa?FgtQ;RVH^3>7;I5&V^U z$CdYNZ>MU(_|9ew3Vl=+CdN0UAWF+h9Brck?&5y%sVKZ+- zNA&)y7_~#W)5WMQ<)Seuw^;|<5p)}eZgm^2TWSK`I%ekr`d$PQi0aYXYUj``Gn+6y za|%KjJQTKO((QYRy48c!dL0s&^kI?;HuauW=L)=;J=!$wjN|ILbZ^H$L&JZm^S`3u zFO&a+G+a3C->P6d1j!n1oZ)R)=b_q=FHQ(X{i*ZwbKXDH@_#Q26F=?^vi~uDTv+yR z*77LU^3ulMhQ@d9<7$25KQGI!(5c`7r!6-pWOkGusj81 z|ASOJe>h2caOjom$YE}zxM@LHeFV{)3B$=vyY~ju7sB>ZP z&S^idk*r-JPWuzrNEfukdZ9*}W3pJ|&(naCZbxgR^|UL-Rh<7G!w!_}eDM+qi6n47 z!bj{*{1dt~XA~1@XcsG`$0fJqZ7zmb33R1O&Ga_twD@s58-83eXdJ~=>P@F&$)NX2 z_Far@xOtAZnL|Rli6ep5P0ID27`3@Q$J^>Qd$inR!otXEgHc!uBedg%kyP>KRxxoJ zC(9$G?D?t0?3p?|nd8lMO+BvKwDPw5vo<+iJ@$v*`rk{U689_nf;@n)v%|LvIGoDSoJclf_&hx#?&GRHlnZG;$y(aERs;~3B8GbvL(mRXTj<(W3y+gP|XE1tqaEE%lqxWo;e=bg5 zYqWNxRo4DuyjOM4xz2sbp`}prziV4EFsVF=zBtvtq+w_8n%b_Umg>t3yjQut@29$U z^M+i{_fwH>-dV1p%T(Hh-X~nG2bCdl&+R?GY) z;D39@n3*biiMOFIdmfTm|F0o)-i+N!y>W@RxeK88Z}nzm%^_?#h?qlY{vEgq1~uTI zWey?N&G)t>@HQSSBLKsbxe*>!@E_!lQ%shoc&2GHULqdD?G}7fQa;iK z0y97$4Q~_JAvHLU#_g0c5KqQn9s~2w_wY7zWzSP9dw4Tk#CQ*Hr-sxfY|<;YQ*k+% zU3E~{r zmi=!>F_tq!-Yl)D-|%UPD0=J`@@A7buMK%KGPR3aqz3o$?swl>b?g+L5BFyJG@G!f ziCRk?>9qwRb44*V3;2J7I`HOT`gsI;a@%@>{;18;-rh!CWdNinZicJX^i@vW55`-^ z(KhI>@>ijCJ*Sf9Q0t_yw{df74I?6n<0bO2B_{GN_`qyH6jb-HcR(}J53U;9IxUE{ zs=J+LjL9=F+(lvUIV@y63@7$jvM20~CbhWuQ>TVPCLU5;!TP~XyPzm&)}`JCBScQW zvVMr6Bm6#U^CK+1g7|LbQSf73$5;Wa(peelJT++UiW>lK-OXd#sL|@g6>4!i(Nf*M z-kfUcgs9l#64iOLMx4=C)cJB>Z;QHYABRPipY(;d{kBz=6nI;oMv>}dfj7C;X|(8e zIGaO>P5-45)$l^Fp*5DDs4+YwjqAEKv_>+-Q%HCWu!`RFznkQTyYbOhJGQ7#3%woL z;Sv<8HQD$w@43}9Ipi{LB+2z@rP|TY+r%+gii1i0<8hFzl|oyq{{KZBWdG8UEzDF!&GPWV{*B?8D(1>&O6*A4xGo>_(SzbhE?-cVwCcYiISmvu=uvCMTa{xMBJ zk3oC~y(?Xkln>}=MZ)K@uy;C#=Q;mA2`g&#mEN!2wJRisW&r;7OmYbG_ke%JRCJEX zt_eGy|76IVGgYGJam2q|r~2j*YT6KQX#W?nwH++y_^J9??#yf+T7@E?uaZ`ub}DgXNfG=-RP}!MV6~hH+j2uCYt=H z_1kPG^2uR1Sp^uQ{hx5iT+Buh^8bRM2}4s)-Q;aWeU;thZR7|kj72lL*yIdC{q-gh zGN$iF&Dq%SWG0yhEC{o~S~!ZfI7S~b!h2oAU*E<0qGfqOT}PfqF7WXPQPkcM-WF_A zLH|CP;F2Ep)>rLr_I~1etyCqA^qyBhqk^8`XHXPnYt{#w*j;duygXzs&^kK---GnY zN67b56MT>K3?(+ovNQfK`*+;+o|N+QC_?k=!0eI;4R*}rHOG=EEoNXp(s9PoDfU`AfMy(0ioWWSmH38xN%TWaoTufHyj z&gqC1?1*n`rT&ienPmF=Xo(5gx9d0$Z&A1P4lk0T|$xHgQ> zAQka!_ZykAR_4U-k@nZ>Q}-9TbkEbJkpB2~Z$EOFW_O5lUT}vuQ#&V(wD1jb&I2;i z+wSmw=z4gm>U^g+=x|O<`_;^toU<#OlXGBkPD&dV-YF9srP~hYjHlb$ISJJ@Ms!HI zHP3;zZ^Ej$1=LTNL&tarW}bNc*eN#G>dr|i$;L4_F5%qC2{R+dhCi4Y|7(9GvX~zi z=cwd+yy0foEIO2&pX)m*2n(9 zTUR_O-b$(KW6;Um`y8q3leW7u8o8^Lx)So?`x4!i@e`KtLLIAKU>DwB-Ca}Zg|P9A zhsFbUwWdp2MPWb+PP*BZxlsLethcL=7z&xkg7#E-Ha+WLih~CIznd$_KTnR8QP4!0 zb-IbN%Hg7e#z}yBXq>lEOX>g$(344szPnY7_bVjKiY4Q`*EF?#8mU{pJrINejHA;7 z-rR)M(hU!Ix4Fi?q|Sfv|FHKa;87LZzBuWSPVa^$i4X{55@;YYXrMuYh$N6m2RaZC z6$cb2L_x&iD2~mLD52Ac5-jwhqN0c6h{_>w1WlL+k--UP^yo1lGAe@zyx&@@s(W`5 z&pq$|?*F^reedvnq4!!McEJUw|AZ-E;`Shj?bP~A3xyC)3x?{fsd!`v=|dyh+2z= z84OfqnqS~MNh3{rXGu!~CP>9-jkTRJ5}oSGHuINIWS{vr^xE4eP5e_@qvxt?3v{TU zsJ1`oY?egL!gQk)td2bB9O#!yKjaVhzxBBj__P?45VMOBcVk4G7ur|-L$Wq635QS) zD@)Icu?5Ui`QSs&8{-|bCRKhv&Dk){(JN8yoeq~xNmQO0&c2D`7MwU!v%{&1W;hFZ ze9kvaEak`&T8)JykVO|`LRo?D7Z?zZPt%DbGA+dZv*ZVeIuD&@GvU#N;R}Z$M#!*( zZs*xea>9^I25}AVfbmzl6S1OIK|!%iiC!zh3WunLyF6sPA;na2iL*_Pwh_eK>x#5( zW{6c8n1=^9<)vbdZ<(o&#;KKyN}S!|9A7@Ec9uE|X$7h6!xCPGRXCfc;%>18n7(mG zT;Mx~l>=(%w>~T*`9qI6TU>A$cUI0R(8FR9NqAsHrB{QZs^K~=TysR#XSDm~VP~uJ zV_QP3c4k)U!wh8yzOjwoS}z!ukaen*vqk%e6I4O3YIJj2gcGE+XgTo_XKtKha-4d7 zrn4Vu`O{39A4AJl=_E0^=MbOGnP>eSGlS# z6HD$cbGG0GHFyLj6r>04lFF<8mJadQ)d_usOX=|DY>@roW!GZ&?v4wsaDPYAJr(T9{oRQ6wT&13*wU2;HF!8e=QL1?w^f` z|LM8ToHTN_afL|_ZK59IcXnmz7U4;0nbIQ`_)bepF%dK^--l5lMFxd7)0(>?<4ZjG zprNZYXRM9?u`~S_HI*ufJGQ2BDB4usf85z6&e7?pn)8ISx9VQuv|@GsMRaCm>T5lw zfl^51Ow@YDli7^M|61#zX%2n7HqvoITr>0f4tR>j631eqp=nY7YGk-}p0j(LWA+hJ zw{KGtlP;jz{+}p`fd5|;BCb_V@4prz{zZip6#A2zG~fAG$BBcgu=nh#rBgX!t=`iQjXU~JKs_AWccl~PdZ!ow$~#2tvv{eBXir3KFkF915fJpF5l<% z_FyK)V+L1WkT;HyjT=``9lxJtNOunz$HSXE)y~f6v zYoEg|XD6E=5TOM?E+f442V@x| zy!97o8Dn@8Cp&L^3zpNtSIb~{uLmBiscxu0&Q4MZ&pTTO%O8wd1429qkO>$es6k^z z6DBbR;P3b{MuZTtVuc`+)MtPVu|i}esb`;us~vej3jwYW;(LHGEIF#!Sc+}xvdrc@ z(4D-FS3^Yzu4$30)Bc`Z0HLhZsrJ`!C>4;nnwROAyy^>e~Pf*9fUtcH{wecJd)#yBCYK zL;&b!6QG)(BR-Lwq;6h>>Nwj70bL`YNia=^|;(k^H1! zzBiXqFfB0r2#!eIz|Y`?=k@$dRp+ZXM2!)AB^}Do=>O3W_1ufjwn_UxZrp%w*~je( zw{?H>W3}}~tY5wHu{!XgGvJu^vFiVl^C3s($7gSi|FS}%_ot;=rvm3%CwRRUXpt)UD7$iT?>^j=no%2uF6@uMN!;bT5P9qu6oUUX5 zobqO@b&z4bg)`PU$oRP8;s#U)u?}k9-IJ1Uoyr)k?s*KA6Nx>WJ?IYjo<8;mM0;Es$innR51$M5(kbakL>tLN{VH(cq zi`A}|ou!W9cd2V$ao*E71N3k#9dYaZJJoluIK5YAuzETUyErDX?C2t>FOx{dQUXI! zs%|8!FL+W%OMUqbuc9ySaHo#j5FM&qWFRWp=tiG^ z&Dk<|=yojxc)$|h0c0VBf#@B}fLPY;Dw4SrDw8ciLl&_FENXly6AAPivSSqg z-PuCze%CQAUa5UA^j0I}?dc&r7e8pL^xPelcQZH)lb+3|D4Caxqk@eOCI zD?1q>kQ>HmVT3@fvJiDy5=L|umS`O;Hr1@yPHUmQeFLi^8C6d#aW;3LNu(`twk^Ep zHnMibMjA__p;l3_)?INGHBzcQ#xrs1xvNaS&d(4NzXvm_)I&?0or&z9mpH$;mD_hX zwZo)Z&P~F`Hty&^45Xl<4A$%wEzMmj5z^UgsJ&YqcoU6#{;e^My9^*JZmdGiX=hzu ztlAL1JeE@VW&>LT_lF>=I{R&B+u%5GHFGBjaT!3igc!KK#!Xtrx;C+kb!}2pECGw!ETJPT zHEzm+#=Y)s3{SUC&_WuhBx)KGPas)GBbijW4%W;SmX2_ZH3mucPuqF2Ok~iM^zdE%ycF+U z{l_iZDyR$aE(d7iBsRb`L_cL=TrCx1*F4)_{ZlsG-oRyyW0Ndd{^|n}yRgh0i}C7~ z$PL|4W;G9oa}j(X7tRLnrwZWdrax4{(`jtAp1AqM8Oj2?EyOk>58p@)eugx7Ud_+U ze+@svpg^zZXVT19aY9d~H!yB7yn_a3l}XE-lj0l)a#XMqvwrI{wXPC_^_y=}1wZlK{pDK^=)W zfC!-*SBwxOXkAwjH?ZlC%B&>>X7sO<5Z~XRg@Aj&?jHlR=`C?L*6-_Qna!oZ64fHM ziTEN?cx}TLsdU@NP-O5sn9&y-A)qLP$TLE~)X=Z~8ND#Evz2Zmiv@ufu^=R@2m<|# zAn;S$plG#Ueen)v^lx9UEkRtvsfz$IDXCrY06RMoz|O9|ek&>fbh8OicSb*DC90#9 z5dvHxL?a`FF)Wd@vlJmRSWsvfX%n%26CD{sH=7WV9xN&Ol5CpGH_gTWMa8xm{jBSh zca^ip<@L?zi;~grSIZJyWI|7_#%QpDq2(Fc42@3cWs<*`CiEM6s@GTHZMyGP*(UTa zu2P?^a^^XvuTpK^b)M_EeU&PD*LkI**DCe$yUtm)CiDfE@D&|omRwZ@jSTywWd+Un z8K%c`TYhFM75wp3w;&D^qii!V0_o#ij4PnPP0vA@rxY~h1gHtxZTMOE*e1CJe>}%6 zu)rtB#)k$`)nsOnJBy!1oz^xsUV0A7Y`UO3Cx|{r`I)^Qm2F?nC`bXHTpJ%3XB9rJ z_*wYa=CK8~X>7r-Ha4f}IY=?O&az2COYrd`V346gWg_yX*#&lm| zHastv^NJZy@w2Gs&YZVZla0=9j_Gf!3pP5RayZ{syEZ!EGca0V=zR1O~KXcQK$jKF9!bEO?loi6O3-9i59# z0+pAH?(U0=Pn;PND*v47FaSZu`h&i59-NZDTHjFx|+(L_u((T?MDh;%ULt3<%qo_*vA$mAX<}xEQSc zNKAA?mrNB0E!WMwU>dTo(cRiCeioi{`B``^;AheBMSd1muT%Z6;VD5`5$+-`KS`z( zBO3Z;oL>}toS%jMfAF)=U&PN$Ki_*E1CSr4eT|=m*<*g(eF_tPK|B}_qGbQ47!ay2 z@H11*_x84xewCj!iwhY53)o^EKQCr>PlAOXEIzce0Eg!}4|B-(_KH#}c%>TynqPkg zSlWE=MGOd!wfwwTS=*fLb&EAN$Kq|2M8V7atQlRvfEKxc0Zn@t140|KH&d6iby8L| z8Van5C?IC}3ul78l9r*x^kYa1ozH;gQpAAfaw!ACWj#N$m3S|uXe?^GmYMRs=hFv+ zF-|DJ>2t`S^%%r}W*10KO5x@Iq6|i3#`xH0&Za&YrRICDCKAOo+~8zzNBmh&$jQ{X zG7@6U=zla_1-CmhlfQhqF-GFpyc(B}uf(2yxpK*NXO83F{TX6-qz3*6^k*%G{x|ez zK(2$YQ$&GR-Nka#Om$C zy8mzWVQR-`&fbnm%`07>J8y{#o}Z89EILeuei9??t^n;TXxx?l|F!dq!Mr;31@=7` z4$?xvuOP%@0A-)UzQQPL_OjAu#ELHSqQj0X?8!nHk2>_8(LY`4^W$@`OR#>1E!*v~ zR7Wd|{&0eu#40N|ZnMv^fzcgE2!T@AH-&v@b=k$MZAK7YdS{Fggx2U{SLD_L1JPQ@ z;ViQN7g>c=nO|Y)?{=_+6qxu#YUyg0f>v%flb>iQ%+4qzAZZ|%VXwD4A_j_;g05l^ z1Ht@82#kJYWjJoJdgm+D(I$-%3U z7tlmgXF~O$qDX8uK6YS=tg0fy*{Te} z>BW3C`y13)JPA=&kYTgR!2#5~3$}BWMg2tNh~-L^7;7Z*-!v`>rtC}HY^2I!jYK}d z=OnxCh9Kj>6@uUsY4BjFo{L40<+TJM+3M&o4u;7ANfhznT9V+32<<$vlB_eE`I|_R zjTf>c*)~c33KUtit|a6Eu{I%>v)e@6I93w8lw6-En9XeuB*=l2@!w(rB}+ufv5Ar@ zqAaN^3e~q=6m6$iS?cc8*1NtBNnmz4;gI;9Gb32qUpp%~L^$DvXhXVqkKUEc0?-LxVLhlrz+N8bYuySw$E>wl7a3R_hD3LC_q$ByL zjr*}ILB^y|hH(7#1zdT4o5i*O1z9&T(Ix7@ch1z-)EhD_cqumtEG7x@^^+24d+K}>rEXE_+QA5tb!VWF-en7_Ia%6ehg{eM}f#1!Hg8+Jw zne%bF=BXVHk*g#KMW2m6i=SaG6q9EW`6w+309P$6mi^$|9T$8*N4p&8 zz~xo}L=56u1T_fEfn9>Ii_suVUQn-JQtO}^HmAP-;Jhr>=6693WDM$!Z88=d8l4I4 zzd|{3vmPWHK2*ik4Y9qE5rCrw-Ubrxv6ilv^!I(r0n{;p{aur>N1r+&3I`VYg z0beE3*+(mdK$#)ZMm2elvssaDBO;+)8Zln1rSxE}_E-(7h%jo$9@N?D-fR~8P~;7u z3}dP!9njbZ>`YDV+1aAb$h#?=Mi21U8`i1WnXrEAUW^+%iX>_5$I{NuVoCnL4A{g( zcFFAF+*Uah71$m@M3ZPJm%zlWI-F46i)~~dnA{7L8A?WN1P5Tn5lLW#EOIdi18A`w4Y**vE3%$0gqY1zDNCU5sxQM*sec-HPW#>=A;W^r66H?L2` zY8*PC@)Q;C`x$3(O6!P9%N5RtJ6a&DwP+BVY)Cyb#?lmO=F%&DVYjSt6M1T;E) z8ZxQd_v5A5jOD6)zcZ~Rg(FS?OSEQe$Ye0MSgqQRgD4WM)&M2vu|i|2G#LcpOO}KB z`arn%E7^K@!kR@wA|JqWAwPyC99?z$Wefzz?|SN~(SA|$`-$;-@(ZyDbtvkJ1J2~; z_JIgE$_QY&ntH(5;z~_fj>OmpAremM&4AhikTvitvdbnJ9l(IH%WVcAGLnFy4j;hg zF$|S)kPfPDR09q=Te86@;3TDl$Ku^ueNbS1#u9b=LELtOlc(W>xEw!Yapg+~oe##v zk2u{}bvo=k5w}WZ9C1DvzwP(NYT*&*5XW~5)V?FmnejPYPpEr-ah`Ob$7K z;ZoaPcIm^7>BW*Y_$#9Kj`5rNZK?X|SG=t@rBrn~>YVM!DpkvlIzQ9<09&D!-sCDB z$#=H!5e&URUV0_pfr1>UtE6S6Hy9933h-6~!hK*O8hGi=tQSWfS z&pX`MpIU9pKf>fkO@s0k24q(#z21N##zX^(7=JP##6a1H45(StQ5Cbf4k;0!NX1}8 z)XPnNVSSweh4lmj3hTQJD6Aheps;?}fSPq~l=TO>G?0%P=#pzqUSWHS0fp_I1{Agr z7*N=j8c^8IHsC^a?Fna=RBe>i%}5@qx4saoFx1{po^GoB4pUk<-)}(STw*}s95$eE ze!_rI7z$!Le3Z91h=BK8M?~bv{#)tooX>3SmELDSVL8Kq!m`|e!g8Jgh2=8_1j|6^ z>B#{7xEO>s_w>eiaNZOkQPpd(Ude+nc*VVxFR~3^I^BRGLCAn2L4^TDf~O5A61-$U zu4UZt4c2`lUjTynOtf0wN@g~~^(~WAxaJ#B=)Pk>5u(U|BE+QzgmK}c z>k1RNg{qHF;hUo5MK(TzOkSb%HvP)$4I>~hwQ`)MP- zv={|=EB?^^m8;G;eP%2E-(7I&zq?=xCFqgG_5N$R;M~^#?t)G4|9@2%T+s6W*)BMc z_J3&?e4Z=u|Di4zw?Jlae~F%c#%i3WFQuMXU1=pYOVKg#|DgM&>GY3QVw@xHm&!ej z68koE{C01pH#u>8N5`%!Dv$O~yrHAxj^345U77e&N5?ICI0Fd^L^N`t7gwk>=NJ1)lj`NGqGL#k;I1T%bAID9FvczNo9$x z9d{m4p|Zpt%`=WP!VLcKx^YQAZO81rdR)>M3EhsUon?vP_*G}()pfHHPdeT^U3pC? zvEp&Zy*t&fZCwK$^X{nZ+s@^Sb1b<pae_xr~!PU*{m{VOjX|U_lI7jC{tHFh?!Hx?DSI#MP-4GX7qkbLY z%61HXQFR{b%5~gWPUr^bv2>BgPonKip+~xE*+OJXNSGm%g zwTso>yOAh7cLaPD zUBFYN-sAe-@%Eq9$$MNIBCNM-)>V1Fk{n|F`4m?fvmSS^>jGx|;=Qh{E{@^va&Q-zCQtBdzl-oMDzJ<+k~n#z}7arJ8GxbSne`wiElj@Ld@w=Z!$;_!Z^ep}+2 z;JE1-!{*J6~Dqnij)iK^N;^WG#OI-^cjyHBz-d5>4nc{GCRueb5`Z+posQk+& zS3+j+8Lvf`x%qLK+Y9_SbaYa3!eYUW$NfaBI#7n8Q*dk1YJQwC6E8U+VJ$-;x>6o# z>m_Zmq#f@_g+j|E?Kg&IY?QR$C2bc&@d>9ps_dU!XFF!xUU}tDt|oDgK|VEMpQ~Nd zS|YgA+xj30=Cl@kt?AMJpnC*xcT&#s$QgS!ZHif3{ceM#pPLaQi%KLr0WUq_l=R&|hR$YeEec!-*fwBrO z-OCDlBnQI%H)XSdgdq&Hm)_UKom}NC3K;my9>B{BxB_`RNIfblVbM9rdptlrA_CGa zYqTd$wt9qZg?r@Z!w>ypK1>_L6eEiwb{O z5UxckK=!1qiwG%Tqz+gZPFI}|x|%nM)&vnzLl3$#nu}3LeA)~kqfjX|?VzhgZ{dX6 ze3BX?uBiPI-2>$yLbwhU>c1%EtE2+C=+=X-PU%#XuD+z4_+=Hr0~c6UaHg{kxw?0v zL{R;ns3NZ}WAd&sd2gh=l!&}8^4MB*;j8rR4yL>OkSjADbk&!KTm#!PMXDHI`D-L- zL1=JpUg)-~ihR51u1Qif_pqy@V{mtM(_z;Tn}0t_vhfYFrWv#Q00VC10^~Ys_hDCu z8~)Pm7#h|}E`gc~mCPcx0lMOo%Z5s(i;<3_RsfKPAXVxgy9RU*Dt0ce0~y)jpszJB z4J3fmEK=vDBd%^ki44b&iQWtt)3kV|C@zR)!%xv9rjrE7;71!L*9Mj8q!3&jk8gpF z`ud2gRnw(i!4r4!E|GdALaNa(uAVk`CGKDfWhh3HkBK&+3XUE~;cC@!hcl8h;y?sl z7K=R-FL=m)hzs6pi)I`&;hoVCLXi?O?#k=M(Wm5LF%|rq<8zVkOOVw2o(N^NqOLml zi>veHLZXV|z-0Iv;sp7$rbKR9MN9%Gz)zvYS zTo|bfU3JvewU5?nO@8PZ>LclkAp2~}q0NHFyV^$5W|`x=M|GPm@V&~4;g=;N1JE;x z6@SciPFJ=O9DzvFH>;=sakUj#Z3kJHU+p7Qs;)`OSzG+IqvGvMWkG* zGp#h4Ani)Zq<7=+-K^i$h^wt;ZE-R3JYpYu;v*J*5eKLO-y$lh@=mx?<5zS%rjDL)4b#$=FmZgDC#wj~d{YRNL zn@q1D(-kBhMQtGGiF!hfSmO%`fl#@Z_7lXqOdO%0g0qsrtR&SrDu`zmy%3eE(XfZ{x#pLBV1 zi2$~l`I}0(U0o3i{2GEddGZr_cybRo8(mJ0DguoH-K#Lqi2`U^WJ&t$v(d)6~FWOCG-A74|$15(s>caXAc0wxi;R zZC>aIZg`h36ZlGaKvdvME%^(Hm9(s!N1O1GG7{~o)2_=njKLU(Gf1R>cf1j(mOk5z zKHr^ob?^N&a+4{vKC`(ne>U3aVw4~SOws%^uCBrUl(+hT%UIGMf`WU}AVLSEP;nv( zos&`YQ$r?c89w{2>A*KcU$Ml#^jJ$|pz4IHs83oUs*ssW%CeK#4$#vW;nW#dr}M~) z@*R=Kn7eO}_HT%FE9m2{>N0i>-yV>Lk;}<|^DehCbwsC6kVsw7C*3v^yJ#8u#sR8L z%KXLZ6pXTCWdpuV(GNwk0|7%V5-k&57W)x8Lq-LlJH>Mh6I44^oW}>619#E*!W#-v z*N#Hf!g?0OB`Dy-{y;GLnHBlwIDPc#@u*ObiwzD%zXU!@ls`mv1#PCrUpe3C_bv63 z#i22L7b9J&O$fb!iHJ>^G`^jwKX9Ix3JO9n@;OX$$M52-j>a_P^I*h80#p(~fKAE; zfq}yOjaDO#Mg@F$Z5bELvr!=JHoQwcvhA@mp>iIS1$_99-5bmspTtOm+`xd;Mx?Wa z)WZ&|BS%~~nGRazCJ-TNaW=KtVV&KMj6|8pZj?Y&VQiBpC0=VodBmkT)Nnp>BD8i) z>@ZP7lh=hBhM}?~(6Se~q_V^dtd&OGSoo>dw~G&{ul5Q>BHt&H9$IRf!NDsVYv#TW$M4MmaGg zoQ#~d#(p2raB*s>M49Tvv{Fz&RF=3mx}kM;uq)+-6Pw14=9I?8w?K1h&$_XrYLk}j zgirt0iemsb0@9fW1JVFsBla{zJ$5Lxem?@=aq6*{FAt9qAxoYjvry$H0$_a-6Rh*N zkwbv-VByESI~i(00(LR{ietf!bATsP7)9<(usZiX3x#YphzIyD*kD@%8hd5SSQ|tm z%hO0PHXwVD4e~>u5M%93we@FZIsRWuWo_}FzG_Eip!fDU(vizS{U?)*F-*Xl;k3r| zrJ92+)}@g%;5t@ai(DO{3%q{cN@E`*iZMsaL@T@Z^(ben(@{)@KXq&LF-nl9o1zaT zT3w6dby1QJ68zGFB>2y|LJI#`nhd6HCE|JE)MYJ>sS}BotSpcsS)>SB0oNv#${>K6 z1b)2@$Vef&i0md}i!J%_609Y3QDZ>pTFO?C&?Ss~_)v-YkT2TXDItfjXA{uoPJZSjNRkxSGV(jD1kKv>nIty;L zl$k2`pLZ4u(LP`?UN9mSh`nrjUv;SbWhq|m4|nea($r)^wW_t93>J!;G>`%1AHfR2ZrKA)9*V$6)(ipHp^+hzopVZ13+{@1AW&;e+@H=(KnJ{btP z@{`niZmY#rRM@waMaCyqsTfR&V98~qI*=FZCWuMKR9wFoVTpta!~3{2eob-o6f(%~ z*~n_&f`p<>Y)tUwBPu9yIzKT^^HX0)S*!0*LBv3rx z*2rqdBRv`sXQLH_PUQJs<*^P+jBi!F$^#ryi=L@ao5oh>?!+4H{v6T^QgS-;-Wr;B#QF=KFLV6h@g;P(QMe&n60ocpdU^(I=W2DLM7B#7FSc^i$ zjI`fK=um}p1ilJ(Y0O(F@;Bzt4}c?oYmXI-KkKYx>S>SFsc28iu``49xE1}05oagR z+JS-P*n!rz5?Hbap{Py3baRNUkBs|l$}yGP#L6nVOOupZE@zwS#tj;mBGpE6u#lud zf+1NEA=w2aOPj#u9mqunBuTaX4w%(nY&g(}A{X+$4m_0E z$+e{eDD?>BcNmVN3YK~gLKLiXpEZM-m8>9N0++jzkEwo1R&SmI-kD@=2~J9aG@+$z zK8cJRK*OF(kbr?O6Ah;h2855j>u_ZTec{w|1uqeT?SNc}4OpJmtp`%LDHAQ-;M-L~ zka~$dSeoB~qj{cvO_&q6jBx7LfC7<0dz909!IMFNl)$z}x7Q+CP2B-f&mj}NxMMWp z?np+nVNVGs?nkIGTHVnk$ZAP2LxS;_E120KvM`P7G(#9LFPaeWVR}~1qf;bgAwkQg zP1c;VZh};e@f0e5lj=YM{6?B0)6hSZz^bUK6l+3ong`hy=$aq{>6*Bi@LFaczC%CJ zI~fcw2DVTpTv53%1QMfj2(s14Y{u$XyZc6r)RHolUFDsOYG+xcyTbukGU4xy?X|HV z5YG-s9-Dza5j+eOWrTX?8AGA(7a{c~GU0tK)JBCgPfpibRS*SC4UA^t-l@}p!XRGH zEuvc8B6u+SfR^};X5rKkjgP6Tn_4aUY2V34MlF)AgmzpMy`rZXoxesoblLB0+34lQIK~s3CF%A8l>w!Dd#=U~@ua!2zoUS_9K{f%FMCRRYzW z@%kTuiB80#)VB#9rs3IY)Xd8yA+$|qW}8u;#_j(DNkNi_K2S-MFqG1!pT?y~yL<37 zP*1xflQi9gcZ5Cw3K2U1zsAymX9zN5*@;&{C`eFW?6*74*+dtgY)&&VHHZLdbn_rl zhiqFUJ!a|dM#pO8$nSv#6xtsOdp<)d)p(SXu9Yez)b3*R=n)O@d89Irpv2a>fT>H1 zXs-p|WQhA%0$SeQDH zj@tYXQrQOD`&=|t(cMMZ^A$k!($p~2=gqAQ>KZCfHMbmbV_!=oOIPsF*N<61^|JrLIYX_x55gIIw7fc43GI-9oy#kk&rs zViE{6a1dUV4$XJSneTbUqTImY)SE;QO|YCD?)Ty{hYg7O`|4G~)8o!-Z2%#gl>Rb`L)Y9ztmE$IJ@-!~5tPh!kSP z8src=t-w3sENyw`E1sdp@$`7X=bwm=1eBb2b*g<1k$FU2kT0mVP>p4I5 z7c21;Z47!*Zx)YPt+rPBWhF>A@e*NA0%bV$R-o&~{>y-c(Iasg6`F%_KNbvf_%#iG zXe(^r1G&`h1S_>Ltsdghp#NK(4*C-Fe*zdw`vRF$?2J36zG{mJz{|*C;yWpd_^yP8 z9uk@OJ`Gq}9^@`Xs80e4HmVig1Kx%ZcqG!G)`zr1K=Vgx+q-O0z9-TV<%2RS2zxpJ z)?pqo9E&L^jE;PiH!F`1A-EzlH7qv58xsyUAl0_>J}A6`4$*uA6wa>j5(si^2Z*`G zOsPLlH9(xBqWxrG1wmb^Fwl5i)o2#1^Tq$%ppv%wuk zF;WxsTqN{X3pvajt1;c7IVtT=Ltr6`rb!aFL}pq`!1v)~c`}^p1ZohNo;h=rmqd%v zsrgpQnu@&C4wl~`k-7?emR5qkXFD*o_l^JsBq{Q9y48UdLH>D63@1)N40GZ1 zfcCvQpM!chVKN}p7Jbta2ElIbRo-OZt3(T0@ByJb-+@{tzkmt9$}5>Tj$lmR5omlP zcxE?k`{sZnwvWi%fUs^WB3Apzti;v!pWe}Ke;#^|DF z8t8WhmOEn*)I6F2rA@5asFyRXwoPSl@A>)^=~x9uADvR4XIicJ3=%5&O(ej(jG3PY z2-6mejTi7dOFN&jb3S!QF|K_p3I*uQ1RSZhVIb`==B<6(2psg}5Sk4vRw?k|hyYG0 zU}Glg-A$3naMNzt04P6pEwbPW#!^ue@v$E;{rq<}oH5T9fH76j7#l#??jegT`CXlE zZ)FC<$V2{aRKTF*KB1zknB0uae*-{oQ^r*(i~xcM{NndiQbIRN?uSx%Ab%z($Ng6 z7zMOGS95{msFG258W<{Bn0+*f)^m@8_{#YW6Y#~S1OvD?Rh3}C)*QKSLjnVm)-M=kJuEABlT`2uB&yg7&gA@Ngxh@4yd%p{~oopUT8 zKe=5kcM19>0YYssz9L6SeRt6X#fDlYQCq+!(dVv23QM@?5PR|%alzTGT8-@Qf(H}r zH}E1Hk%BBi(GC)@S>bQoa@;P5N%2y{ETYwmf)jpjanWxci$;gi*6C&qbl=ktEmhlcnqK| zb(1jB8e9&DPkdHUQ;G7(6Fhua*F7J+qizbb-mgX6wj>9QD?=3=nDl8xNo>H0`-8R` z+5ao?!Wm}nDx$nT5j;$b+o~|({%#~>dc->hxt=s5rUBf%IucNNl+DwUi#YH$$5p+zI4%ix|pUOf8f z;b#&!G<)gV_AuqK>-N7(30pn+6VKAq;ZS5q*YumU-0df@-#dU^s` zY|i=!mFTh5TLmTZF$8Hywh8!tHjZ21Tc01g%ZoW!Rls+QdQ?&uh8RXdh4_Fvnkcl~ zt|p&jc}AQIJdSCa`9lWL$5akK=u2zWU_7u=gNux=2M^jRko#Ws!8um?MY@k$j%4b0 z+5WzzrjDKiIy#4#3kY+F@lb^;Xg(y?^H<-wnD$+YbUIH%hXQIF3O>{a$&pHkwCYU4 z)|o9pH5XIM_ohj;#MVhTVb1}g_NtlteSY9*5VJ}>yfKr7b;yLtr0zZ|sR*heGQF(E ziaMEH8(M_ZozN^~t~E)%2PgBA@+jd<`@#zzuGuA@v?`CbUcb-yXRbXy!KytvKzw$n_$ zF{-^AVR3*O0-6pAA!1{Y1de!tE5g6Z&L0zLutQGitw`aof2Mk=NhU#NLJ$l(b=P4V zCa&O2*hY&C+y4v<-rWCNr10c3}OxXGqpov-$ zFJ28KNsD|uu{+VzYBN+(LHHyceXuP?O=Wu0S4F#&TLf#G#g#%3J&LSFjrCcrF6#-T znL&tFsp(V<&3iFfy-u$Y#|2_$68nE*N$K4U(_yO}J`C(OO2TS7)hEtm`ij?)0&iXd z7PfwRKClDz^cgKl&z|*UGN0cnfvxXAu6z_8kHVO5veY~?&`n_^(zz*|L%aT-e1Mp1 zYM+d{VmHCV#H+;_)MLG{A>*LZ3)t0S28(<5GF5SU4FBndH(E>B$#`SqwSX`?pn5YN zX5MAe(ml^P7I0L7;W09mep$Z*5|k$RAaWtH+Z=lWFg#bNH3zy_@Zh)>Yt}ZuK0lg3 zhh=da+aeu3j9;fAhn7@3v~w>8Au?O|eGhx|oak)9cr*nT-$yb;)=F(dW<8CQ*Fbcn zs3d%(ZTs$l#RyVIDK|ZOfnI4 z*fW4g#zemEz#tiJr7NbM*@CqPolER=!4_=x)eud4u=yOA`eqBO1rOWSnl1b#njo`< z`9Roa3uQI1zlf-7WD1v{fH(Ojz3(y(w5AhhV$hQr>XN=eOMWK4S==MOHARQfe zHwLyfgpQ0k(J#$JILz}==sWPEdasX_mAURmAxQ$7@BrrxV2dLQ?ct>#l`F@(<{TY% zFGOyTT;s>PHMG2F#zi1q9wFWGM>Q`8+XaXJ!Cr4)!21R$;zHc?Ye`_Ti8IE`8kPY? z(@i>!Y?#ojAseDUg)|7UnFJ{a;cc?7qjQCaC{3mcO%N^;ZUgMASOW3Y;J(&5yzerl zuk}&!*dM5>B;Leyy@QdU8!<%d3n)P0hDV`MC(jf-M9{Ntwcva!WB9@EQP?b+QPb8f zXBXikEoUEAhm z6@)&7nKuJ4ixKn+7ix~O($>Q7{j6(>M!wQYYZ?dE$39a+@4nISs% zQ(kwx5NAWTA9U7k%^);&pRZNOSJxNx82nH0CCXfk$jZLM}mifrnT(3(DGjQ zEYNbotsd{5j>OpYwaH8c5wTW9jut#Ls5KTGuv^_WAabJ0la72^1Rah&T#~|ZJf5Ns zyb%?pE2(;bm65sSn|k7|d08RsdG#CR$h8J|wMB10ZU_^Jy9nVFx^iI8!f(`UET|0W zQ4c{GGSDETDN-ruX?hKXUhEo8zV`yq+w-+7(Cqj+8frzi)OUfRP)k)74Yi9oV@#-> z2NcXwC)8qRHsM~RK@}Zpzlz4$v6K*5l4+DM0+!@6z{h%G3KPamtJdi4#23Vn8)|f2QEeP#wI8t>NfbqCjEtCRi?h|(0bhhv zTWp<2nX!@_P6**C>X$KrwO_t9sW=|#^#<0xpWDs*B_MvI+a6d65%yFg3Fb9{^+Y}b z>l`XvPhg!QM0M-A1yFr~b)?`WoUwT)6j&K!GJ&o)jJl8z8%B?QM&e>16jO0w&)0&F z38OSASpS)-DZnZ#hbJ6@MmEh|iy8(-2EsG&R4b-ROUIHnmXGkwJYGb?-r(ezW!0f5 zr-LjFy=gXu_R$;I0G94)0_P76q`;(uZyU-V3BFAL)faq^?qa;ny*FHBr3cpoU2nL3 z4GB^Ieil%D;r0>1>sTkdeAo!#SJVT(FC=nbhfIGEeDlHy1xStcc^?z48+}^~9xB+P zuX<^)m03vD0V93=sl;9Cw5UE%v+(SVz(zfrA}*FV-v&}&K!EU?D@K0iQQc+ z0P+FFgjJtND1H&SXkQMZ+(5V%QAK;8Jq4;K?>V{%$6$*2W~+adqHjU zHXmOrK@@=FJ0;o;vAU#F#)8msyi-j|`d&4;*5HvwCN~YSx{jkHT8^OCs;K~eO}Pw*<>w&ja~i-k-<@-XSqMbpmYlS0s^fEj(Hs)Vr7jL`b)LcOPLRs zUrKq<1+K$lyNt*YxYuH$u3vwY7Hbx4O0*<$84Fd0QG4o8Y$~LD1Y?Jeqj9nu!e!5V zh;~FB!B0?gfgBj7wFG`?dJ9(58UoEXQWW|=htxE$hj|z9oft&F!1sA*saZ*R6V&J= z6ZX7zU}1+eALv{99BCd1cbwZC$)JJl@mJ-x7vItnvUYaBx>TyfS5i)ea9pZ{3V{Li zF5yfs2CQ@VkPQ&!Tz@(fC~nZ1U?&>8(Gk8kcB(T0yu(I0fr3Uq(`)>E2n%Gl;_a-v zccMk=27+jjOrnM4niLA?lti`{`k1Tny8={uxC=#8t9Xs1Oj?GPiTR^kkpdYg?kD>` zL8K~FPZe3I=87J=m}n}>hf4UxDeltrXpnD900MO`H^d;$=$%X$^CU>&mf2-HC{|F3 zWUNAmNHruNF!B5O(Xj$B@}|D81~DWrbZ*HEQUn?FLW!SFH*J))50F|}i{79b+jQ zkpfQN(h{IOQtS)&UbCJ25vOVVJ!SfdK=cIHA&}##B2g{+mWvW|g9fgckc&M!4!6$j zNV(zloC&|2iJEJ;?S&u?45^{+9d2dn%cd$m;2KPW5 zz)ermtb{T*Lk!~d*c+s{K?0LI6!Or47{W;{R)HIR?1C?NfMrUk3bX_zBqD|-gF+=M z#iK|ex{8Hcb>0Xoa{y)0Y^h{)P!MGcN5QFgbOv--Yr8~HayKUO9Ek9XcrmhLU6co4 zHX`6m*UfODLnExLKEw`%Xzdettu%OXtBfsZ8UYtB9%-d#Q(;3bRfp zyfQ4G9cgum^ilBHm{~!6t->j{S&gKNX7(?2dX(2 zTUi`yxCckDBMCv1r(#q`sJ1i|*o^o7I)Xa&UX)C=n2!tih?EKDXto<+W?Ds=gJ0bA z!Z#e@_iv;i)h;3;p~b;@(XYVwtNP|*t1Sm}oW9g=5WdPC2$2VlFF|TO%Icsmby=x1 zt>jFcXC>#k3VHlwSBS_R41PHecvFW(_mk0k{&-JPU-|@D=d-NU1tHp&KDas3P}PP} zR=YMt4JGI{D_T^8b`x>xRvM{_yTt0>g)D|{@g|g{V1wq*;^EimKm!SuZ~P^AXS*3P zqBAz#X#&9LXs&iq8hdfktyJ`lO;VIyC!D$vpl)r%l?1VIU1y5+ywqynV>I&C7lE=! zW_tw6Fv^u)YIQw_I7t)H;Ra2FhSj#fp>HZ3Vs7GT0bg1YNDou=V zH5gmWw>?xMV=A7qVncvOQ_C;2+VC&|yd&4t9n^J#JY4O3dmlPlD)Gc#6B8_Pha_W_KRzhRw#dUJd&`P5T&78koPZg>6@O;M+ zKIr8K_3da&E(ba_+M3jxSi#55`SG48Lnq*9BoXyBZsTBW#`kLBE>Hrf!xdizfe}tF89Bhpb>#;rk0nd~l|Az|!`- z#=75eTbIf=uCdz0ofVhuXzD=Fqv7UyZdcN^q>GuIxC@8sKzHc}I<# zXzg^2`dj7sw_1b5#93=zV|e8o zm8 z{zL1|6vy8eseV6LnU3P~)zv>(!?TM!pG6;Z+Fab?Ebe$<69&^6y}CH*EQ)$Xp{9sA zANDYSpbMRPtdlvQWo^>|1nciI4P&$}O7in&KqY;U8A#{OeE_rM43uDqRc^ zLs%PI{(u?L1X{DDhMQH?6`dpvFEaY7+5fPbpUZs1sd1n{>}^!|5%gv3MxW*aAyxPb z1~yYc;H{h(5ncdjD-3VoZu z+Y8VLCV44-Q)FRm`dNv_2lnogDoSsfGY#cP4Ar@?TRmcK@wL;|lmbM&>LK^Z3+r z`>a-7bpzYmGe$)n0VHdLcCDtR5C+?*2M$@*Md5@2u`*@bXowe?TC!P+w2MZ;Q`zdx{ZU8$ zI=dcA)z_Ae?zcJzaf6+`qA<0Js37eBlZx_vfd#H1C?%mYjdjLyTeMq*MWWpzQR6NV z8jw{wpkEa8u{&3@X+0;;1-%Ko65&BRiaV?K4p?nEMEhm>n&T$+$Q3W@f523uhdX`1 zdNPYsV13Rdow;sBjDe%--0vO4{YG2QfwkZH^JYGXCV8Uf!|D8s(4wTY?W zIzpJXSW5`*WQ)|rzgX?+(UDr2jknry*y<3ez!)7(4`m}S%_!%H)wmwY)N1UMZ#?ox zlugsHQ(kh!%IZX>qAMJVZGz~?Wv6`l4=LAU66)FOtL)l_cpPK3|1Cq*N3^yLI{j+p zd9_DHy{A4YL%&+Rn@~=yP>R2{P#^qiwHin^!N)PSk%wUfC9R!&Yk0VE)xYFls>@Mp zhU2!m>W!n;RL9d#svgIzY{!HrE3ZFhS#gdzv(=>ImHbCu0 z5lLw^TuWM^a~nxX3A92Ybgxb-hNR|`z}cN7?P^m@WE~DL8W>2{Nxxx>JRNAVDjCg) zgl{moI5#yh4q)MgV_p%Nbb`n=0Chx)h)gRfTI5Y8w>B^_q@DAUBa-&K%k?s4ubXof~<8A^tn9fqdd{w{JYTIN5A0Lb zGpDdk#J+(HVg)vJ2oSYPat%)5z)SYgDF?~Qnkbo>>JQ&s1yL<+S!1u3SY={K4J;`e zxu5pqsX%i*cx~Q8d8jdI6~-WsHr3^)t+rGZQ%+m$$n^8+7baSc-_{}x?wO#Lg14$% zQEAkn(|EK0xeOkcnCAUBK+vt`Hl)P@%PPW=lfpvi!_jFih&U)o1qVfz1=VCQao`2j zWFkECmS8p~l4U>>7k@7JmaGv7-JoWju|@?yNC#J(=`9`>DwjwUPWT&e*r+M;m2*r2 z6ADNmxQ=S*xJtwUAj5iA05u{qsNy(xi*v~Q?25#|&QgS^#Zt|SgVM*_#xUFi5T_B0 zhM+?pm#fWAmWe7&t}l&()Q{yyyg3$U`+_mq#Rw zm4`gbtXxuB8zgu}77wNg#o zeS)W3yWw0XLT!6rfQRbc3I6!?Ay?8Ho*(+T8Y)8Vl)00%Zy5IDz~^`q=p_#CRe7GePD2ibsfb+bsyXn-eO==Z8Ey zfyoc0{=`2CUJ&zFUn7QRyS_sSNpE1wYDz==_Wv_cz-J0Xl;;HMCZZ^T0wPLSpot>N z3_zmggXgQQ$xw0}FrbK|UgL>!_mPsnnNSW0lr5A83REnVa|D_!l+PhZF1!^bGl771 z3DF9qvoax%2l!AioF%mpo`$nhlZkcj<9$J56kbp*hV!Hz7N}RK7|xSgBhaW&F`Os$ zYk}?x6{m?d9|HmW=DuoWiaRT~gu(7FO13sa@L7R!gy1}ZMhn4^KvO^vWTp`$H$jJr z@m=x3i9k*U(&$pbuP=tCK+gW-fe%4}&{$U}bvS^eHwHrD-i3ICRvioQi?)72XbQ0X z2MBakXbSY`-&>#uLQ}LJ-GBf-vv1{#XSw&e9AA8-=C*dPa&-7ejcnuY-G1I!wCF_G z5&Ii$7Xv^ikaYG^K9C7i@6@rCe`(_`j*I)d`mL?o+xXr2@eTZ4cl$#L`8!pYcJ2bl zgNN1a?cD7hm!GBP(QogbY6bnK98zDkbMJKYKcwDFceitR`l}u3?sMZOZnxB+d*aq~ z_~a+6!A~Qx6tNW#0c>xFKCKyea~#I*7iZy`znW#~$JE(V;xb!5j_+R-#-H}j*dO<& zeaPhx56udM*46wrK2F^@B`%Qk3f?F*rP5KVYD!$U_=D|^sRL8unm40m%`gB}_6&cXJCjEmr1j{D$eQspN>9yne;eqKjIdN*|Meg>E9*AW8 zT%Ebd-Mr}$oUifEIG(|!14#ux*1V~*2D`^Pinpp+gJH1K>Wjhda|ez)Wv9D^iH^ia z#tP3AfX|kir`{*ww$(h2UtHN(GmC%UubIyH4{GjvUj_W`H{-rgr~U5UaWAS~g>IkY z_ATn>LiaU}PMtr`w>`+U#ojM>Pz%9e5Kg_1G5W&-8F zCB4}zvXEXgd6T+jsQYY3!6r3tsC#1W) zirCFIdlahK?fpYd(MA;xxUY;msP+WhBjcV{7Zka>=OrGu7Z(#8`I^LI6I`%?9=Fx> z!Y`S-8~?sv<7NETnl>BM%SG-h8%;Q71l^>PhoQc|gMfDaCu@P`ofsvHOm=`6_RO zyOpClM~xZb?(gWbMlBfO?$ow9=NjeB_J`X%O%)!XnGc-fc+G$u_00%(CfLV~bQd)4 za>U3zb2Zm&v*(~mtl-zIR`-u|k8w0u%_fVhQY|lbckkQfa9zope-@Ganre~!GW?R{ z!x`RMGq_p>FLuAte&8X)d0~~EP{egssNzxX%noM`ntWqo^TE~G#qK_$Dn_}xU9cpQ zu}STW5)?W^S8;y?0Dn@!Df-%DPPize<}3C4DEC#4sqd*PE^!ZYWWT3gzQn!3anrl% z&P(0d!L9qF0zwt$y5i%w2BAxFw5*B-YGaS>^oN>s*k1izLd~ zDl$sIJHBSCe#)s?!%y34-r?V^HE-jW4E74cTWVfV?Jsj*>>jR@2d`4W%iQUXPOH?y z%iOI8SirxZd&#Aj<&Da_B=6F^%a)HFh$ig5Mgo2qmv{Ngo}LdJf`7?;^c22z{<6|+ zAZGNRu~YqanR`&$vsZI&iRS+BRVafXx2Z?vC8OQD9ZiDUtOkEu_jH?Sfskjyw#q?Q zVsIJXq(Pk8ag}>;v+vu1=<-=p$T%?5NPqp$PhJW&Jm6Mpn0`_!T9+Y9}Z8&SUM>soJL7y zPfx}ViiFQo)2??nEneOmdCHz{K-iRU4*ev1-UbrH{DK^fpPD(Vf5WX8;UyM`2+5kZ z*2y#A;slrc@St1}ZFFAZ+`m4M(d{^wHyp%f>M) zVQnAcA11~qxSpX>R{}u^8zn7`A%aXMBB`rtNK9DrOq-~e9(UP`P(#8{%5}u+q z-QaF_V{bxcc`lSR;zxo?@xD@Av+XBYG~51?rr8dVG|e_o(lpyaeG=!kedtYf>PGjj zrk^f^>6d>nV!>T!UW+dT<4+cKS|RM+0*&g?QvGA~AEV9OaQrNbnkun|8sW$jO$@#9Rv!pc`~a`%dJOk7y`-FUYn z?(*fmh|SD70Je2M&)sy(JHEJ6{rs?f}6Ow|v3+ zfxY={Qle-yu#@<)^f;4ENSVxF*u^wGcf6fA_k$UoREG)f7EK&T(q>EqzGX>9@RzsP z=9D`ki01$!OVb3IE7I+)=A2oA9F4O|;Qa$5OLqy9jzdGX-IBA0Av-CeIOB5aoDQpu`W3IBu9BS7oVQM{SU{kq)dA{nZdQ5UR9qyt$ zWltv)HYLohDA^MaBmxq*A~X!7)$Fe1fK-K0s7cn`dipmCNS7kB4oFvaRdXk~GrRe@ zyx}-SI1Un`Ie-kqOdW)xEd5+ucywjca^1{iHBI5>$$pF*2;#h9rw- z8QgB2mz7emfCUcB zQwMH$5B0o-MBR`o=c&PWARaA}{L3Yryu?uHMuJkpyYN%0iN7Oh)Dj`nSCW<{?dv^B^Gn*dl9nTBYb0$v zL2xSk)QlZ|QVsu;yJIkm(y}}~qyWVdY9SI*x=A?~3Q)_CX46@65?dhsj7(@RIkjY7 z$)dS;k~A$D^AjQ9xvZMeV|}%$`}#Jvw@MNx>*+s$<@m^XO83?FKvC*GPUDwcT@jzNj%P3J%3}U)Wvxc zR!Ul$lz3OtvLvla(sCqi^)l7%9z?H>gw67tBUx+g923r#62u7t?-qt$$w>?my^iBY zN3YhBn|M<-$RMHlvM1)aTR1$B{T_EyhijQSXO6qIiksqo!Q;AdX(4R;zKt}!lLw7X`|bsS#yrAG{Y2#@~oJj#`H zb^&gB!eb#1HTzh-W2?8pzlTu)I8-o>L0Si8hk1V6bQbu}1Qh1J(8Sx@W{4 zRX^VA9@^&oyG@wgt;c;J>O-XSEKFC!?sK;c&I2MpJh&K@KA)r6s)G`#%87kS>0&|F z5HdNTvK>X(RUCIy!pj-yc?(G7+(-}_Dt>AneT5_JSc$MS4ytK$m`#u%{Bj|J8;|NA z_qo%%9b*bc0OpnDIV@zg0L(Hup=~?W<9@{06dTX1gf&zT!WwkfF;qHJg53s5n=i3) zlcX({v@MdhT++5m+D1v+CTY7QZHJ`Q5R?)=t~THAZfEr(WR|CYhB|aVoIXcNXin!# z3C-yONzNnZUcRH5UH6XwU?UiM9MK+6>j%9T)h!{`u z@33sPbUK#R9)dqqtcve)$Pq~pf+Py<9honyr?T94rklRgqHryPuALd6DzEmU0I5vS_tYuwNCly@Gb#$|7c%+Nj~B zLRwqd}wxq?`UBOSUTi(t_eRTZ=XVMLo{ z+<#Yu5m6j+OyW|E(SnvGWVTzzn$I*fw%`7od*C4zftwssTpstb9f42vc0xb(#`P;ir4>aJKU6~Rr~!9A&z z+gEUtJ>X6k+%GqAkL?xQ>_vuYmAzYKZYnLQ@1lxDkd_It2qqO_PIU!VUjuvcs&|2% zh6Q$7xYj(6FR-i73b1CQ1-5f9K2`s$gr=eDzf@pVg)oU!fJ+zXTRC2L6fN1V#Uum` z#=K63&=MTR7u+Pk*cxC}okpwbSPs&fi(_pF?eaPs;3?*-St15Qac|c+Y z@)Z)x5Rlrcm3*n~2F@^lV8w+jSG8#jT-_v*q#c<0d4mtEC?uwx4OWZX<)0em#f~GHVKMI;A7TO{~dm@av zEolBY@`s`zwr@6)*?2;Rs$B$)^pT;Wd4g!JtL-dUbhSx>rmN-i3eFSlx(k}Fwnt@m zR2Hqe-%W$t%kHdCPh)>%n;KUA5(HS}&%?SmncY5(h0if{t~egVco)7&7*?iDW7%`C z?7k-&*!ccNeV;>)WP-2_L7@%yyrA9nwW;p!=a^pik=jplD|1aIpHZ^{Nfpm#6SLQ? z5&~v$q&J&g$RTewn`?$%1ARw9f@CQqZOavKcN@8~g15?%#)01ahI! zt7oqFIg;xIQY;PXEx}1DfVA-y&3Hp_>Q?+HSaeNAf~H%+^_66`mrpaqHMA6W%4R1hZ#Vx%BWS;120A(8!IITT~d zP6Z}nS^dUa9O)ueCHmDHf|E49n#f)d+@u`bdLnyfIVG}H^97A`gBBoG;@stw)gH2Y z$_vlY^6QJc3~L}Q9c7l^f5D-zmJ%5FiipKBF?*<|1Us}4?Y; z-{0`CJYHhoiHdf81zzp&T7`X{$i;X{jCTb^%n^03}3?43LNRM z2_%ID$nq1M$$;uWSUJHnToC;QaT14AQ#qEo1nJ!vBk$iwRKUp~mgq2!=SZ(&p+g-l zIBi1cSiwRXp)f_zbcG)ZnyxTa(6WdV`z;j3U&;n|GiEyPW8XTKJAv;AL)GnogcB%$ z2q$a?4(%w0z`z(07uIp4gCYu$1srKyB{(MumeqnKOVHK|+EPJVFK8R(O5CQsz-ntA z@xWAbtb4(yIA$@eDyGlT&qBcpKQk>X3y=BSG}eAt;+7ZjqYgC>kg(hz3%aoU5OA4?Nvj#`&E=*xwN9jS+s{*h!@9CO#_!VE{N*^7C`f1Pm*d4X zHzFET>06?GfP}Lta0pduxJ7V^n5AwLEEG3zAMFq{ zn`pjE(2{*w+zL~@3Y7_jC+D&8E0C)O30_^J5Uy4dO-LieSpvi?4WiC&0#8G*c8`^z2_g zf&OpGJY(4ngx7+vd0kkrU&p7sqPKXc@Uc>Z#w%$RObtYp4_5pOF;W3QV4I=9&aFBf zk*_Aeugq-kDpPlJ2i%_ErB(rI$FVGUm#LZ!OV+@}}OF3kW z7LYhnHFo8YYop+7A!u~63ZG_-qjoNS+#0G5<&cQ0p~l&QMkfx?AdMKu_IzP-HJ>wz zPet)aQJo8jkC9`i2$@bG%weuv4ymqDZ2W3d>-r4{8>%+vL@#TLS6CxPu~Vx}v!yxv zSkE=4;Dr8ZCPSZ(Oa}WzlObuc$zYmdGPIjyGF1B5WC)#VGIW_{GQ2k3WQeqz4CT{J zhVO9ongO1fCc`{jr@eoyyN_DAv1@C+ls`Nk+l-%cDk9c7jdK#c`?o9vKg7(~%l>$i z%h>5Md$Go}P_ll^=C3uS*^e$X84`R?^ALUVPnwjn~8f%L07ie(_Bauq*y8dM^TY070Vn2x&<}sP_tR)O#SPYZUQ9 z)$$yv2kZkPm>MO)nJifR1dAR%%L&>fPV-g$|7Nq+W33H<72bfY>oAVvNUvg{ zLmeqNZCs9Vv|!OSjukXrV~U^&jp~PjmPPIKfV~d^JA4BL?9BuC^w$ISR)V+_M8GB| zYy=MNM8H0v2kbQ*sR!%>dca;GIQ4+NO0ehwd$pkH0eh{Wy%Mm$DB=OT`T+N$P1s4w z6tELq_&z{H?2DUCi(f$?WDm9t{y)mGcek1%-!~IFR1Ffmq^(4_v65&)8o^=_ zETJ4SRuwc|RW(7=Rn-tQU6u6@_WM>-P@9PHe5myJ+?pWXLlK{8s9FGr9*X#UK#$K& z#oFBo*HHtn(Q2InvuOnJ@ZkZVKDJ%~ZSI4m;PY2kTt~alIl~liqE1 z_WL$drPz(2k!(auI6;rkYdF%S2hDYYMGu-A1WgZ`o9rxpJMIjHu<(~)B{h&XNrd14 z(Zav13RM=Iq!-qxf<;$o7BpR9prGjrD+yXMwZkR}Me#FV1N&{e=|b~H#WbHpX0zWVUCgsh>FtHy9X* zH9&o90EOIo_D~?<$yY;dM1MYJdZ=wJAU)K+%^`274HGmy)P6jW+Id55e-3#=tzFRc zP&-)Af>_=z)9mIm`|{bO$JycHn?<~B>y5Vo2*4PL;(O)9dFdlm z#Ca(#z@-oWfkv?9Pp0sMn@HKqi}+%${&0`A_=+C>6A#`|5=-?)D)8_hNZeI>1+oBz zRy3Z+&_c1zC6U^gy?k=@0<-tw|2?+wD|-0P+j})$Au*w92O)GRAg@9^{J+bJzM_Z! zcS|+m;r|1IT%;Hct8mCMNvyg-f|kW0wYs1!6||aywo%Y(3EEyk3l_8jL8~ihR|vv0 zAvSprJ^a59wV~=KRN#39Xk@5pp3RXiU9D5F=xSYprmLMNXu8_@f~Kooc;Eee4}bXI z4YRQ9d8yKk?RMk5^l?`{$+Hk=N+16J+Lb+WOt{pC7V@D2o&r5BBMu80$c6yK1 z`_>e1xiGlY-|2_2e*D~S}}zmn;Hd;bc3cJn;KHsuLp2!+U{C8K4IzDv;*qg zf#@B7bkCn!MFXoJo3`gw@zjb=G3n@1vu5*SQ(AWj$B#|h)Z`1#?W;m~420iD{T1Q- z*tCQ3Ch)rWyS&PeO%q;lY%m&jp=#-|X;%)G9GiA~QI3r}WEy6lNcUBMnl5fHo^J*i zXNp@-E^G~MIMjD z>r`qYK|HOakRWmk8*p#}g(1pm#ai8gBY89e@(Be61di|d<}ZgJJyHayBY?yZ@PGqX zf@aq`4(5R?mtv^PMY^>M8~FVBmbh{JD;kjzB|43{9O*TL>kL)D5uAGkCs!R{ynlh4 z%wtu6s&S~)0^0nw=i_7iV&dJhCF$q|gRInqJl1jr!%(y4K# z;MAi^reL|k8yIH^n#LhtHR}>9cMK`+L-Jy%`loOvMXnMZ#@}?em(`*Eq`O^6{aLW+ z8h;fuUE?D`6B^Y&1nmj6!-GEZSyg=tHPYeL5*O$;4!v)rC|YR;CxeXZWNq_gv3L36vfl{a%Bt~#Ai#9U~f#2m@Rn9 zA`EH>9L*LWIzyW#9{tqiSVtBhnnN6EvYa#9Z2r&jb-a)hQfu1o5ocxJ=OeMUFgO&?4FTbJ!iOjN)^QKg|tQzaR)53v(2T zrmKK}w~Iv6)lsb9c~k3#QG~@*=R_}S$5&WwfY{*kCJX!Syy<4(&Q1Q5pimg1521Xq zfgQMD>MR}omYKdcMM`tNWpUq|-VFQ^pDMs29tprP=J%MUyrD=30~C`Z|-rX9M<{8%>4wNU$d5vBQ_J8J% z2sCl`y9(pw$-Jk0%28NN`pcoc@BPreFyYp{tgl zg$i1*RS+Wuv92K61hHNhcJijFapLSGz9i;Qfj3La>Nh$$(nYFZzR<7E5}c$FyG?&Y ze0?ycC9&8dQ>E}pf`PPxA*hsLoDfF_KpcMv6RM6(VwpvzLlrl}kFL z4(p^IywO~JHnv6`t98n27|AdP9o%E%34*c7 zE0UUV$PvtAoiT@RFkRsy@+}}J(6Nl-cJ?~`BT}xbtj=vbmCm}#UcYS`({mHuP66s} zepfl@&<9(auf#pJRD_&kjk!ZNif~jQpaOz$iwc%JAoW>fJl>f7bK6w!&09@*GYuSZ z+3O<39eRT!T~C07P4p}SeZ4F={RPX_rtIB2rg>82dUpQ~_LwWf_#QKy-g7{B1mxXg z?nKro_L!do3-@C76VLK6Hux^y=9#pPt-ovP)Z#`c-s;Q+;SMTv>?*R&djcvXsCRB@ z^B+Rpb?%vheJa*q{(LE?aA{u5y52YSkTw^vwf7MRuMJ>--Z!7>@)0_6a!8NfClshY;7D8DL&yeor;u&VTzjqs(UW2;xB+>bk z&3QHAeC*yBSVpI(5UTClNL@1?*~J5#-qE9gc!0AZG{kuW+(BS5G|3{e>@07vlT-F_ ztYf&qZlqWeV7v=gAo|1*gdCP5)tf+K-ed{nQWV;^(LCIdC6pV$E-fLGhr5>Cpv6Ri zl2#mYi#tE{uEdvP>6rJK`hmdV8UpzgYDelI@^Ql$!3f`Tx=l zabznBvM@2*zAjfhLYY#7vj4?9`3S31Ipj(w8Zu54=nNX!c*bHJ12X2`72Z@G$03I% z1dSK8CxSLX(ELTaG(igzdCx>Zc8Cup5Rdv%XsOHj8T~KSVNEu^=nez@h(-{f`-VW< zaeS{`0OpzYBK%)ZysL{pxd(5R2qK9lKR*7PxDE3wedw+?VQKYAX*B?^u~0Ke9t12}52u1U z6V{SGKeIR-dkS=v4AvU-CzN#{IV`{z*em3u0QUG7Q_@IcGzVsgBCS!)=C<%qK;L( zBoqq^6EvP-VqO5LwYvDs&TkfSj(7OQEl)<`e!24(U+G?K=PR922qZ(iJ;K{80;yZ@ zn$V(K{wK8)X)r9PB51nhPdLraEdlY(W_;S~rgMcjV;>CaRm^s=hnXqZn6;Eg~FJh1veQA z?khUC9i6=+*vV+H7YX(s1v}Xec1>s3y8*UGg78Eq0AoPz4pwiD4@`i-c$0cDY?R5< zfF~8-rz&STu4RH4Kx%s6k5XDA~Fic#ehM3-NwHtqxO2*3v?`i z@9LpSw>+4TgXTgqtqv&A%OKE@fEIE{t%mwG*;* zk}ga0Jm6c2jqA8FVu0$+9Kt7Ggu|@=&I3NCMYnzA4M%P z1C2RDVA5!4CxgILUNB9?GcpAuF_&9Cv@$@v7CE#Yt|quz;cAO34p$~vsi>eU)VsDqNGLGax=ca=61=7D}1h=LyjDR!d$CRpnE&nuD?tn_D9^xC;0Lw zvhuj*&#m^I${82Ia2T+KsQkgqC)gU+gk|< z304fC$Uw|6t#v~W)N?sB>A0I*BnJ#Q;3QKBnS{I09x7ypxeV1n2z3e3jsSwfT)`-g zJHnCfXN_!E@3^BCY8U+1IF36aQEN7*TBYNTwy4ETNbx=Hpw|@ZMpjsR+!2RbVT+Yv z#~n7*mOg9df83FXTK4EJKOc8=Lk+18B=@wV3ZGJclTp={MZch!9}c}G`3o%876DJ+ zW3<)5&ya~uo8sje{H(Ye5)P$&_3NP?r|`nTO?F#u!ZtJBmM3RZS4IJAs{gLHLPb~Srx#|)diCbU81&w z;d>|#5Z&h56HXd?=VC=#i`{laZni_CT{k}JFN(dQC=kUJ6b_F$(}CgL8%$%2B?J;0 zACGeuIUl9|$6Pzzz>=Pts@l^bzwA;mDF@tqq40g4;R#Nw7D2;ETrif3IM>@g+BMgWp#ZNs> z$7%Qi7!sWANP!a~&O7;84Ic_Skf}gA9p8g5+cAmP$$#07sl0xJ*VB2uh}ScD{VuO( z@j8t~wqq{p+E0t&Pppg=@s(wm{y>TIC7NcyM{zu#<9zUtW<_{M6$l!}lA)!+#C8V_ zdHRH`7YIzkEH27;_u>)fV#P*NvH*i4zybYl* zOg_?|zq3aFnyga%T$b>!seP?o3&_6SgYZR-9!uu$INhTkF~#?O;`<8kIoYOvO?{;+ zzq4{LO>19UFdy@rzPj0!#K5ARpToX=X__cin9o{!On*pwep?(S|0@m4Vxc~Ah;;53 zmgFOUD1A4Z9rlr98xQ!!->{Ens3X@LcR1VF1#n);`;{5d(SQx9>TzR>UszK~o)y>z zTZR&bjOgV*yN^n8wR)1LfZc2$za`Zk!D=^@yGZyQ&v6X_&l}EmG?bI1qT}pkLwSW1 zbDXURmFG!2j=7tM$v>8p&K_dsa4`NhkaZ15!*++*N`eOtWH-anaOXkRtOXhd9Asx& z$i-6q18iSQdAbz;g}Y6JyvygccUDS<{FnB{lojRw^}VI;O7^0aTwUt8(jC-VuHhq% zSixGqE*D83EN4~UkoQX8Eo0~2kn^ScpSzbv%7cBSjHT}9QS$FTl3@w^HCk>h?OV+1 zza=}QcNViBy2_F4?ptzzIcTwjnDN^m!_1L}*3TC)-*)mu^MXYj`HYa!X^U8PJ2};C z7Ra9n+4PTvOo@?4N=p{9NilLgY0yHpB1Rq{1uSGQW8^y04+~hGSb2Q4DGMMe*8K7U z{*60QqNv9Lwk1|hm4BE|ilF)KeAcMFJY1SG-@T~4+~2pN{E39Hc9s7wpZOEFCtg13 z6A&?%OB;8Kqg&5)UwvEtN|L&+WAi$}x#Qy5*-miwp|z}bXTUe(*uc(kcKBMhy)&Gh zvxap^l$T0>uXbyRFjBgc$>gqb8)<1K>)2IZEY-;*&vY~wXYl?Dr<~4UExW-fLj+Pd zrDF!$+YL@Bn#nnZQ}SlAsP6JO>Ajh3Yj?SU)MzF-r7`n#hxf0{V6VS}xw>Wsd8QH@ z`;I)ITFeaIq5pPIwHfT*JMu(%RXRzBdp4w#dt%sz9&(t}Hr;)`hn(+Qap=@iAB~vm zUe{Yb>JzYV3KyySXz>*Hti2( zb6j4QaTH)}Gfs@OK48;TRn~G}aG*lO32i=`J3tN^6Ang$^J>}$8BcvC zj>NCk(Y>D<>bRa(4_}gAoy1&yMz}Pb{HS#aKEdJCW-va2(yPC$SdTLbJde$GWAT@$ zXB+=OK_Ha?pQAWt%>R-F4wM_bcJI)ip0r7}A!<jL>~b&K6!T)jtqYaN1FMk@YJol zS<69koP9l!E!tQ-&2Xs+sS<$41URqQ&^Zf`Q#ta-5@f0!#?1y2-`VN0oZ&4kOu+cc zSC8czLODFC7Dz}9uw_S&q#k4&N1=FFD?Z``WuqrzUEO^=mI`)a&Nc?^W-kZH(e_)r z$ehMpnR*(4&2b{NoX7HX4A^M=^(lmBDknUa4xA5Gv_!>YY0JTa{T2KSx!hwmPyKMh z(HFC0;zaI4Mi0Jvz`?Zg`*4zD_$~~6>;%&^d?~RVorbD5aGKdB5fJD_+~@n zVd{g^SY3>qlPm*g3@GzJIc^*Z4Cy^?9EciCxZ}nVdW{b74YpCaegQ+txjW0LT4M->?Dh`@n2Y+fts+`rPsVRki`z*@U_q3w(QEt5Y#uu;?CES zUSg-j??-%OGq^XL8-qFKv5XiEcRRm%LbkYi;+F=TXEE9yOP|p!dWhUQsu8F6t2Z2t zV+M+e+N-qr^HJ3NOKKkBK|_);$mTrbu@sMD+aN&NKZ@NQBCjyVZin;Cj(l(;>~3fC z-eW8{OdE7o() zV_g0vYcUjSeg7}n_@Q#u#&H;Mt;ate&zvh+*xF=7H`?ZJkka1JK7d%QCDI`63;fHd z|JpY8fQHB4^pgKw7^Cf8_C=`TvU z&5I9f$H<>f;{%$l7~G2ZYTStP3DogcquB0Ya{XG{1Sze8E7^y>I{RT|@ZfbW;`x0f z`)io|KrN#FWyktsh0Pzsia(HR$}7jyOh~L3ti`i{;fRP6X0s{7XMculqaC%_zw?4qBQpwuj~j;rqz!7eaTscQ4F=!BxR5Jx zQCHG}XyOLfD{5Pmre>&bgBp`)W6XB)l^A<-56q5+2R zHJj7d=8x%v1#|0A_U33gq}pW=Krbwz3v$X(_R(m>{=bH^FGkCiE6n3ryK&G6c5t*@ z*Z$RTiMBanH=ixzTk|oeFp128l4cXE)ke@<^QB*d$v7I0m;-}s_^gnaY~LKk)OJ~# zq>y%+eRUgB2Ji|GzJ7yvaz=Z3USc>oPv=rWUj}kPmB)ISt_h}6A2^>r@)$?Y^f#o{ zhCed4fDvCNy@MVLxr|?JWt+!fK16I`568%Lt-sB{Bz@vDnOJ>w)`o($)mGMMtlUz{ z-OL7zmBT9D`v4ux7)%qP>F*z~#bf18(zfdC)>!!?sdaVMXB^Vh--a>!IJv&GaTr@a zPVQ*kyBXTE5%ZEMW^5*iM{eBO+cvWbDe(Br&8$_5+*k@5#s;Uz?b{aP^au<8etMw~ z!{ygVW9WB$w-IRLhfYH!?u$Be^)^3kf|c7*bt0ED63{h>Bz1&X_2 zHYb8m4CeZffx3#f2eOqP$~8ltZosm*kx$S1Kcmh^Ib0j|oF**7^l7ZuKYl3Js1yOw zFBSjvw*Cft5)I7lAf`-^tv$XOKpiZg)egPFc2MHKs^GD#24O!o_k0-p7juCkr@?q0 zz~}kf_@@RTt|9TVd@>S3qK@GA9$dzj}obk-!;p4t$1Hkc4_Cj*9D(^?ds6Si!1_$0PsBGOCKB=-G8xx3`)%^FUU*Gb8} z*_la5_3QU$6(-BQrPICGdz0lYQte*MJVlO?_PoowPmu?*qf_P3O8sbGi%^#Al!ZT^ z_hO!@a&?wJRc=_x|G%jlIQ0$bU=0>I4YHHgu=&$uYX!U**M8327C8mXJxzY64EWk~ z`Ry{`d(#oXvTCx`)8)=mR898VbWG8o+p{WmIjTZb9BiQ>TDbk&AoihMt}Lyt!Lsaf zq_n6zJ7UL{?~CdzJ008MDFy6Nm|Byqogv$#M=RK`Gvw&z!ic~el(xo&&h{@Z;QQcu z_-A*(k5-v7Zkaoo4`v3YmvFKF%q+1Sjad3j*-@F%-5h~0LXS5mI^Ug$!83lqteNr$ zl3yg7nIZS9*q{pyN0OON2kKs9KWE6+$_vN*cdNleMXR49*{n=?Qb@U^(pJ({%$ylV zi!hP;=bcV;#^QImEeAelbsTaP-B)B!j6)7C1CDqVT;Pyn%22O6MNAxu}RGUp?XUrgR1Jha^s!b3$O4o2xdpB^-tJ;5rEMhL%nIHqy+6z220N&=r zASYuz9F73WzMCyq3##aC=td4KH2Lt=+wz~&K3mkzhCJ%_M zICjkEaQ>}t&PDXdJdtM7>=2{Xf`}55m{1=8@7>BHhBMm&Z2yzl_yw}#jlbSS$5U$| zM*WPM_FD+w=y-o^=R1$(BGAsn=K4z6<51J^?+W zF}PT8F*ev8ZjWM17s+u_N)-EXk=(vf%{M{CoT+4UBJnKHUQDNHlxM^4{ueQCMMtzl ztkYt-p8KQ4@&KR2AY`SssDf0Z)@vI0`3+IB8+EGH`XIA->Pd|tF%`AjIJ9u8en4V^ z;*`63Yx3Ro)XG@w&Nx^wljDQdCu6glRz4!PI0vhg$MPT&-Baf$vuO+|>$qgLlHr7| zM>6|{$&IQt1UCf5Krqecn5j^Jlh5Ii>?M=yHhkEdg!x6}K5Xo**=wzNfjL|;IopYj-u8ZpUL(0 zm5;!opUI8NfJ;A+?zE9BwkImm_* zosZfK41)I;-)Hq#%Aw{SWsw8!vwkb(kIg5vGMwMvWA}-3L0RPTd(65@PLbpv*t}Kp z>;58?1H0G!9X?>I5N zt@h?EmcCZLD0MAjz1LyRzk8WYUMJUm)WUxShO2oXXV$+6ROW7Ij*E9 zTAfN5ahGL(1jdBm!)(!d1cs+Jc5l7x4B0yIPfu!C!jNA6Hs4k8DHU_OtmudWgNwV@==+Y(wIK!^;l8r*>{L&<{6Z zwbZ~yIXGDp&l57Hp~HCo51~-Hh*aQ=luUN z|8w4*w_E5Lw?3Y8Ob{1Ck8@ZwM+DR6JGN1w^ zVl6lAMv`_$n|<0n@f*3akCcsHX!};)D&?JIQ3vGuA%DIm;q~|TDR%e60VaNuFH-z+ zsT1ENmw<%qJr{%LUVB8|;v-Ev%(@>%?ApASJvu6XTCI65P3*!1XMA|# zkU>g9#!1hgcpT!fN8|kHq##!VtfDHs2@O=|k*VzC5dK97whyU2$6; z6PDJ{T9l%Mp}~ak&BLZ7*XFX}h{$q39_Ipm*s^0dNa>Qy3XjQ_GT!|4nB2b>SfB!K_M{wYe_4qqVRAy$ zk7mcGST-JxN^*YtPwr(EsTF<&4G*@F@OUh~^*!g+M8jK+BAbBbrSI0orWeWTqO>K7oqP78p*L74Ne0%!N3G=6PRosbq&D4{<%}F+uiOnYc+@dGfWSuESdpNM zfNX8766$Hq_ftlk9sNsJ#B2O)UaXh%E)d!l!FdJsG$WqN;(31=Dk@8A*B7~)>c4vS)u9_?qxEK0C?42PI_ z=u-ou`Q<(K*rJSi@Wt*olFu9F`%iD0Yi~_hVpVEXAHSP^E_07RIzwCj7(5Zf{pGO?-pyKDl{Qkd z-7Lkbyiv72_##fwF8KK_n1`nlHaz&=#SU7PSZSz%`P5Rn`!4v>hn-obRH>5aw2VE6 zZ?kcpigljE$ITNXEcbD&KBJcMiL_@Y3#_e#gr)DKZWipXDxNoo;ucE->U446H18P@ z&gYCX3R(Z!$^l98u$sY`Gny~!5Ud<1U(Zj$ca49?f|sku*_k@ZFzJ0i_Ih3AyuFc+ zVrYRY5?4E19dRY$>WQl_uJ>?_z?Fh)BChGU9JuDRKZmrR|{OxxDs%^ zi)%2hF}NnxPpC^I735U#n22_Yg}z{b->jLS9e^=xCY`HhHDJ2R9qk9 znu#kL7eD!GoXu7aQmV4sAMNlV9y~lCxbl=7b{o$-rP5B>1apk8*QcIiW! zDb|Ru*_tS&rR4iHyBvjE`{o|^KT%3eAF09vW{FnrNp6X!0ta<~v5k_P`II-D)OfCe5q`;52Z&;zC)WM9Vyc2d#;)4#>s zK3wFN;2V!Qu1@}z-Rq>(kuI%e{+*S#Dy^91gXiUi4=W8y0B{rmay zhSHZbw7N9vh}4yNE>mw7JJMNcSn=_i(hjUV@BXK=@}W=Fk2A4OejM;Hz-jYK%(nR_ zIB^pX%Vg%RHh*72<#| z;)v}+0ng9qeGzvC-T3NG+}IBlyxEhc0@@Eqv*HDx96Vghh&0ecGD{MS0j&WFt-orh z1HjTr;GziENkzY$9sG&E;xS``LN>dL(ox!x&dzpGj@GP`j^@q+=LL_Y7OGftAs+=! z;AXHLU6mT0KXcP+|YW-+gbY8$YZN)6AwSK^-hBfyhJNAw; z-#;2)wz2s|Hn4~Cj`=#K1LZx_@CuCO()TR4hw_H>-6{68htjITj#D(nj5(*=ulH0^ z5lv6Bt?w$;+f_VS-hfk4CndGwF##@a$efyVP4$FW`Bv!1ojNb0?Uwm$F~7 ze##E_QkF^frEFktB|_@7lr8G5G;LpFDTehVhPCnGIRy#M^0o_SY!QFhoOr(4BGy@e z!G6lXaU`RtU1LE%Ym0bB4-~Vf_=9?#G2dimtTc+T3CT*&3f9RKEsPZzD@;~8Nk^8j znthb^(yU`_P#>j_6taYU(?{voARcPb*Jg0)P9Oe=Ky+zOKDselbm?$Di~bL8_X&&H zsQ)OD_Q`K}JSC42)V%M?<8=tnBlBS&JaERdX{WjqC@egIpV90*g(JMqZ(>NYtxJLT zzPiO-=Tzf?(wY$2sm4;XjQTB57_=aPa|m30j`QQVX6j^t9Kn%~+Msbp{r3s*p21^& z#c#vpaj9=@W!?HJZ`3}01Y``}eC*A=$MP=AZ(BK&wuPeaIB9trOX#OW zL=I2$F+4PA2XJ7SBeno|#Nx7K=iz$;XeY=wX(z>=PHD&YNP!;XXZh@_eo6y-d$7?a z4$*SlhlrNmr%Fz1GMvkId$qw*cDFYWd>*w8$T(ZS>2=j<+mOQ$l#cD;ujqy@GepSb zQRsRseBe@3NF!z@H2ypOHOhfqYJb9c>i<0+AX|A>uHv52AF)|V`^>#@pmNDa8t{ob zeX!ENM_Mw5ZF~>Cf2XPY=l7JMeodoR!#mlZ8tBf-_Bn_GN{s8)ax70nvS$Pk(Pa!+ z&!&H%{37+sWn+dbP3#|U^f4rb6q++T!R?7r#=d~fnJxmHY6Ad?At$kxC89>}UUW;0 zQUf^4Pe^!dQI^IW_^uoWh~J!(4fkxXRG~hdJO$5)Sx6j1@sk z3{lM}{xJ88et2v9%eWuI$*nERZ>bp%>}0=?)DbFy_g zrzNXH1rKnXuYhRd1?^wpZU8=o>nz$eEk^gD_m3ah=#ff;Mwe!X|X+C%f^!{lmungyd`kWhPo_@=AJQw$hA>=SRhYt2p`*LDHVdH|s zPs_oibGR;{)yXROXZ%D^t;0lF|E;|&ca%~q;uF{lMr%hQx)}BP)kU48Ud43}?_dH= z9?6SRliVzDw9-~uxsG)mt;9!MSqHl_urWa|dKph5X-A2<`0$%yIk8T>ym=kHGwudg zoVml*xF{?8njIglG>~?D&3+rLw2mEK(xez)sYRbfD19+F(P?c1a-!U?Ubo`k@H~-I zU!iBOWkbg(we1>iD7eB>#LED@5`Ep0iu$kw^Yp@D<_Q;D4KqJ$`X20nH~%W$gI7;V zKJ;fP@P_B{sHf=?seOt~- zeY*{W%^Y_GwDM)z*Wm5uYlM)Wso)M;P%6aDkp*T)E$AL* zGmkw|{0qk2TssEiW|={ZGQD*gk(=bag3<0x{IYcw3b6`yL``(aGKy$eiyd`KWPJfy z^}HXPfz8!83w6YdnmMx#t?E&h_g1l=Qsjm2iwI<`Gc3G1D93e*ezuH`=uP%D6x|GZCb z{+;BTM{gHzQZ+Q;m9yT;*WL;_n3FPj<>{tVkkGUt00}^tP#n|&WT)OmGPk=0< z^jXZR|BcesK4ljn&OYf=h)uUq_3KYIA-^uqjeh|>Azo;Cf0sLv3k zJ7bwISp%>D>|@?}I1e@RylOl;T22bIh|>Q85K;O$@nj+5(_OG0KuMHFi2jY(oF{pd ze#o1MC_NGExDkp?H)rCcnHKkS4*Vh3YW)7Ve(MD?_QPbQrv32} z@{6@C2qHw@20{;!$qi)Vj z!_>emH32(>ti?LMCz~LS!?VR=1uaMAkq z5ZPq0H$+wf8l9sM8M>HV`WPW{$s(pqRjSuGvk0NF6_(uZs1gNF_>lsq<%dNqb}A0U zmM&t$rYgZbdx1_4La-c#stA@ds3sdfLs1eeo012-!7`jEvN{=s@NR9?bnnJ3V%MfB zjqC=XicdZxzZgVVJho8R6>q(c>i>kz^kuhWNCDa6fy z_=*1_e%_nUE>2gPO8J|ZpIsR!omj_)+LaJ{Cf0~y=9Fu{VxMpU`-JD#nPQ)Sa|-qH zWxlbXjihpTm^l>ts(|`m^K84%Eg(TiuHPQ~sAHFwOpMkQprdVHj!v#gn>5-=<>3t5 zI0rQIZ2CwvL78iP`Wwu%j?m`D9C~FK8sF!_@87{j<$E-;j08+uKHBMl2V2zu>j2Kv z=L+Z$o@_lLVC+Ma50`8WVL|na?EQ4b_11Bu{pjMAUm?W0nE;!4`Zb$*{~ztJip>YY zoDqx1zmOljgY$--Lj5u1sTpfn^BH)4Gjk2=H$yohwOs8Eo~iWmDSr(at#;4N<~x*1 zEGI)5EhVpFFEj9f#IlOTW-2Yq-$MqeX~tup16+e)_LqfwN#yy%p@OS;~jf)D>*^EDYou zXpsLD1DSsd1DSv8xwSQ6p53Q`oKuME<9;U*dzHhO9p^AF>W@H5fe^i^{tItq9wix% z`<%|d)5qEab+im1A$Ks0`FHwVMFN6cp_#EAXy(}#3?o5p2zqDMWej8f(>~UfB;@)1 zJ5c>S1%AZn1IAi}d>UE+z1HfeAHDr_lhxs^1o0+)^2(0hmG9X4*~-eO2}oPeuPeX8 z0BbKwXl^|%9ldBE%o%TD^uY5=98{djzYy>+|8cU~av4j{QbH<*k${JN&gEZCuHSSS z+m)ppm9JCE@N`_Ec6%55eXdf;y)awZ>LXb`WwB1>E$QM?Hp7V_r0=fNHtygo>^rCO zRk;fYFWNO0>ryJS&*v(ZavKS;g%B2YYOYed+=ql1FF5>MNI}o&0UHjg)tWG zQX;CK$CDBLvD28eCl#irY|BtaZfvci=|A5aO z7B4UZR|gPK5ES*gAt@@Zp`u+CjntIP6t}`9y(uP{dU456)F{h?X+~y7YDVkDCBvPv zlGKXI3d`!6Ex2UZ#``^Uo`DO0fA9MapL3u2KF@QWv#)bznC#9hP)Avmo|D`qPpU(F zZ0~KvgWLS0#xwTXuuuAOzh~6jqLzVM8&=fUc(mHwvt=j7)BMWXu;9Kt{Ta1++l3Q^ zulMuCM!%!G-=+=NdBt;pv-rBiu>AM2%Qvx~h4G@(L1Clv13mVW*tT@!H%ZG!Cmz-j zevRGZeJSV%v4woLJgxhYv^9PMVP!wMa#3B@V1ZT})*cba$8L{y;~wlCuF+a;;J-hk z_Kmulcdh=o$%y=Q|5f>fiV@`5dwA+5b$|;iVq^WVCW7#wVz zJNei=ck)wjF|vpQ?F;X$4SNAdoI4q`KBELrHSyse%ybWCB7en{0*?neNY)fga3S|R zDY?4wyu)}gki&ut25~~?E1W-QikOPy$v8`L5{FW5q#$aSV{j711mc;4Jc2S##008$ zJLH)a%qNUhYMA&GDYko&pyB2bmDb5Zw=BC`TE5?0i1();eb}5AyY`=~<+)r9Nf04* zL8c6W{BkL7`r&3^{t+YFjm>+<+0(59cJqfN`Sc zPMFw--cnd?0j6gX%!#u(&CJ6&2{0xO=N#(6%br!cj_e}B&c8(Z$d#60;9oi7xyE0Z z|2XnL61ii;Lw7vK&Ht=6>=;sVxjCNbZp6AEA0L%61#k9{&*_JH$4{L2(E^Mx#EG9; zbW!{83YdtWSP*zti!cy?hW|q=j?Rx0*2D3Tu?H{QqW0(~RuIOip>J>_PYt!42pf3! zhIyjk+3;u7iPZcPIIoZV5y_j}9o>sKO2k`kRn;(5QMM1~lSR0B!V(^hpmW@+Hg8z~ zA4ts<^eZ?wVDv6j^sAG&bE_KPEK9`U3Ve>2J~KGqxfNA4wg*44Rqd}|*koqmX>qqc ztYQ-ne-0069!la1pTpzq{@wX2&#CRYj5HL#fW~0l?ZX!iMSQyA-V=pam&JiFWJO^9 z=e1!!cjx~+rzZ7?H@t}AX(sO0hv~v+q)7cf>|Y4|_Ob}@1t1hwZP<$LvKaWI+tm1O z&xx7ydcO@I@cabG`VkSHJIQN16wXGeu}W$@_6+}Io7yq#5zH+2iOhiK8Mue%`!{mi z^J)uM7eJ$B#!9}Jn|hAjz<$|!O~_1dky0Eb#4ky4J0V835muW?u`a}`rFgOs<6(#p zkC5UtA$~xLbA&j^CFRqlT*S@ekn$&m97nf>_oY(2Oo%&3@me9Ky7#aYFB0NFDLyL1 zR7GBs;u0ag{D+aUV?rDX<1Sbi0_4c+LjIwYzboWnGVoWVc$W~rD#eF{mi=kEvsaB87REqvsS`O z7wIM8DPB>ewrpRMh|(%EdNaJGyaXRzz?;d-#iKbtzm*_;@n0MKPL1OF3u?P5FOC97 zyab5HC@}jA=KA~aIU`}V68DJ77h?LqDpH-j44$2GqqvW9r=$hsVkKO%lYh`>OcPPA`7~g@d0n)nB>Zr)vuIqe&U(M zIM6h=J+CfCCzJ4#+xCju&7wRzoTu(mXU9CZ6scY_q-xDw;(7k*dqv`gR65omx3s3i z`A56d=atxd-E()V0hXXoMYc6ZKAs=KSL{*yD0xHpp*?E2;vB+1+oN{3j%kCwP3tj) zH+@x&S3-vHUazXL%CD*Xo>y_w=V&TlDfsWU=C8bpf^(+w&t65O(o^wj1N0J7dBk4m z`KIzgd)2|#&*Ox(eS`V>y=r{?#=(f`$`5KihwxMydHK_R@d;C-d+>bq1G;YQp}}|+ z0{-t9%xmF885qagzJ|5q)j@phYidIOBZDw)G~R2Pi1%_296UcYtM1tw^4F4bNRrx z)z_70&+_YUtEEcS8NT-&oG;pYhF86#-Vr+E48Gtx?Zo~>)OPf?9=|XCtK|<3z=_pS zZgr-jw0-pDefv~JX_Lec98d=Y`1C}{ukX<^%dH$#r&vNB=_ZQXvGRS3qqlZ&7N7sF z+BcjegN{UsF_0e0^atn&~6p*ZOVDbf0=xJoG-B#IGGfCUu#~Bi~b#Q$Fa^ zgjp7jb>u%KUR6Ws|BQ`f&ppQbmww;i-jSb&_Y}pn*nwb;^BLMC+~qs0YqZ`;{K@y! zyTWD(#q~WF2o1da7mK2c9K9&)ceuFm%BFnP8y{S_SvYN_AYIjE($kM&Fvr=Qn% zxgByetK>O6=9D@<=6k6=Y~{&VLoF>IA0_=xO!n6MwVBN~o>G5TCbZ$JK2#r4#?Rt^ zeyAoZZD;b^K2j6I$6&(=9wXBF)+{?dY~=|Bz0|ie-HSd_arS9 zaAqwvjt73Cj@K_w7wzWsi1ohH9r?aTYQyF;p8W~Vt$O14H=n4_w451@3^Cq^Zl0EZ zbztcS7z}aL5wFv}_AYF32(~he5QWvABBHp-&g)36nrpl<@f(xvLH_e-Z-g*tooDj531A)$^}37s~6O-73G7=?sXS&axY?R z2D*eVhUFi|3aGa&yZOM{GE~*z3GT8V)a^?1nL!xovfJJ`oRS^u`E?79AjX~W8ymzQ z@u;6DPx-skE~&5hC_gFgKYvjpEj?AVr3Go`(F6bT z9ZKmdlt@m5rgFpA1xS6yJn(ZbYfJNn-DYQx^Vz!&_E zu6+nR4$MC@GJoAk95uZ7yV@ZFifQ>n+-aRo3<`NCt$M)FkV6N1mz~pj+iLahmd!T* zhp*@$_Gj=tre*jJpkG14wOTAutKffAHI79=)BlQkXW+>$*J_97KP~u^uc!x=E8pud8>)JPv)-%Vy#ThUE_5zDMVWIQsrEf6HYnzBKg}e-S!9KmSEO ztzCuBwJ~$Px}~Q);i>3Le&`Psugcxzp*JwDc)IYOH`KF=&zC&tPvG0W;9dSy7l$oO zL=nyw?*->ya~%H9KW%-Nsh#=3Kh=xMl1_Ykjhdmf?!^D8Q9E?{vttt+1@-j|N}gEL zJ7m=nql@&rU~p9o+;>+G&riE)uMPX=bDn-vU8ZdKj91*m*wd;buf3^0sy_9Z(I^9I z!zw@JtN&6*E2$m$mw%~86_=eq|F;^gq}EyBT&g`(u z+Q9vCS{OYkj+ZIgL(1h?-rYw#qrCSaZ{n+URxY*TJ$$v{%5$ywa$oJ7ax#XG_tSDB zMn+z%#U22@JS;NWvA!Q3HLtslYW9<#HoWOMOq8>~3wbAK5Z-x;6P0_Azn0n@bt^CR zX5W>6`7CU@)@PVcC63??jw-8L@j|N>tvt|zyRBM@@^%!T8KBKmQlt2n z0opNTStKtC#8+1qoZtrnwa)5&CydN&RU0<@IKLLC4OQ}w^S(h^du4wFpB|)DDE4E# zr>gy-c4#U_$SaO%U79!Ly)@0EtZ(6t3)aqAl-CmZpCMWgWl17WuxYK72NL<6f{se$ zP8%Z8PvfndYO|DrW_&|a?ZM!<@cMoxteNO%2JsQikj0PVfmJhYNGs<-BUZcc0`%a^3Ty5v}x-8yO|bbQNnH98mi&LN?-HNq1qDVEuHTQ)%q%nbpBJQc9&w; zd7m)Ui;8CasW9z#<*R+}mEqbvU*+d>?(0!nDu#x)-F;eWHjC2iEuI#wjZt2GlfMwH zT_W&$M3zon<`-x!sB?;ylEgHnLB+ppw#o<0Jzw8amK1@RJ zO)>nQ@M{jH#XOOTv!|X%9~VPRIVK!e6qpceIeg(C?}tu|kGOl>_LVGylW5bYdi4&A=;d z=RV=X+iR<$gUMp_zY)@6^e^HD7Nft4;FsHL*P3rD7Bfb__iFu7s+%R)U+d5RuxnG4 zk9YEn4%#Lqe5d#dO)F*T%e-kv?TB)CC;zmgwzbKr>G&XZ{t13pTfArRL?^9DjQ>uU zd#nr=L;uA`?(05N(bgA`@rwU~LV0;7t+|3{#}_(jiOSiRxZYX2Ls|P0pV(Pz(Q5|& zJ6`?=j_zUo@PC_)E#wazkDY-b;{@;;tu5FX4#N6FFD?JDq1qJx(|K=aEkcQT$z9%A z^S3A)w!163pvEiDzUNL&)ZW5(I<~q0x=q`vC_@jp3zM`5Ez0Nnc$1!5fpUDadvi~1 zphZzi`T1Vjm&)q#yfj%$QXHKBmaHAH1^q69lJ7OthW*O~ZK|SO=E?oFeae;!ehu{Y z*q>1;OGT>^4*6!_>MOOi!maVM3cTL$3w-JgxvBtlH}V3N0{2HZ-u^?_rrWh-Wo#_( zbGvp*Y5py5lA?96OyIYrXzd~=1Y`8b3GaUq#utP*3LQRW1M^p%bf$1;igry2*uZBD z)D}1E|D!OE^+4b1B|TAtx_rm44Afc$so<|HOIhRR^CNFDNNb_|c!9?b)>`_kM`^m; zT2z!{+82)ezaD!(XLjG{=YQ~L3#MV1!y0Jg1)e_$z2N5?_~(OA!2eYVk1dqAb-e#z zt*cT}#pe!2KlA8CzJ0JZr0IkCjNP~c(>uJUfQ`ZX^Lfo+ZD;VZ?_qM5G&%oUz9$tu zNy$3?U#d1W_~3Vtb#zP<+r}l|xaSVhe0-EKyZHi#Hcc5<%qtz*kBa{;UN%f~Drvv+ zZo@TfM*NDmE6^-|`Gr3vK~u>I%x z(<8Kn%9x!zC{4SnJkgKWrlIn_x}D!PQhP?3G{jv#Qd{Dy)b!^=M{9R0S996ISP+c=to_!C1q=9zFxb@ObeAe>6ku?iCMrh8EN9fnG*DQcnE; zjK|}NyvaRUMey5Q>w~IlUz(5u90)(_V6t!O4`IFv&>)XPo5tNR9)XG|9PwX$->3 z0%yW>$@*5ZoM5F(&PS4y1I|#%Ibd+|bJ6<1>@Ar)B{NSlJ4@!1l4-2%`)aM8#LHV4 z?Ryot|IE}%eUzo^+^MV8D3^z_Dg1+JoPAc`L@FZ{@9cqwaj3 z`;9DZsGqXxefM7vYTw~&s=4lO=4s)I;#umx@sRctUbb88{x(~)So*cZfeg<^ybWPa zWKoti#+KSCPa>WLVj6NMSpO0$TC)0q^;c)!@?q^kWy~Y){D-wk7G-5Cet8i(=PoUH zT#hzM`4=B9%F)^>A7${(IokJ%vjboDh?c8-vvAX5Oo29!<99E{{IAH)mo3(QQws0q zb05|ADxR@CW{H-d6fEGQmS|&@;bZxZCFrFrWBIR3w8hHXZTZZlm|653!%LTHUn_S! z#Q8GVQ%1XgS%x`QGlq>c&!Vm(N!bGsQEM9>oQszJZaTj$SL+l}kZw%+#K(Yw#kvV= zRP1*CmCmzsVYXAc`)IBhsPwt~)N<|KfOo<%z^~Wvy@swUwDe|Qhl>4@)rF`bwI6{0 zu8lvr0+Yf&NAi%B7(G85$?Yq(K6nXM=IP5&%pZBtc$|UHVvgjCS85YtB1Rg~>zzN+ zleYfxToeR$Q=+92hx4et17v@qmm zZ{By4*1hS+$zpn4Aw#PT`?fcCZ9-RnK7%Ved@i57Roky*=DJ%vC#LE}xqQkt z?R(eIr5YOvHx}+*xT$cccgzX52yPkNV{q%>Ho`p%_X1oooU%+~P2pnUy21^B8xA)P zZYtbdxW#a*;huuq2Dc0D9k?TK!(Ccmc9%Ei!AuT_P7_Bi?pOoZ`-e{ce(1b3ZLL#@V_i2Xht#RFc@W+*WpxAO(pG(w>RAdl*c~hY@%?{|r{REmr!ok&mI{@4w>% z_e(o5m@9XU=IdY4`i1GE8TQ=pCjBoN`1dII=ZP!(UtY$)c|~g#{=`k=)Q@;`q{|rN zzH65eH@Ut`YwcPDjKex@YbC$O$oiDh!%|cR9ofs7WwuYx|*L&erAP zO}=)w)_PEZ46;kH3y={RInSU{3oHABx7ekz3v5^Y&<8fihqJ#1MaA&Pv3YTZ>e%50 zI7B5Luptj{TI#Sk)6_wj!6EA_&=qH@qOD+vn=a3TgUqQixZ+dVI79{T$-eSq-(A@E zAq;4$!L@ZsH9WuHYIl9TI8$r*4n1=Ga>l2}zI%V4pX~uHY!t`v~L{aa6kPH6w&euXG9glaJl2weIL|XiYqN{Hu(r<6qsQ z-H$zQ(6)00{r(<51LHKEI5-SPBDLZ9pI+@h&aFe-yghc zuNEFK4m{L_CvM;olGgTihlDc_zUM>Zpx8Y02I!J9juxN_L_(b19yd(fh`P`oH%4oQ zk?uG6wAV1?)ryBlT2$&$1ddbVsp8Saew0{BZCE;vtzg~t{&3OH)zpSOa}>LgE03j! zFZ|PsqOTe8+Em}CKawz-uPMbE&eqHQX{ol}62GFk@l^%m+m=Xw{@N7e z(xr2Uv0hle)+z4G5A)UgwZX1m3>MOl#2t>fl-dB>FC{g=$(Q0AV8^A<1~}uAFR%y| z!6b@c;t?caBiQ)!S8G#nPzoR9&i^@oNGf+6&_eYxL;{C9#CN~f;*kft^lcfRcR-8M zs*Kn91FN+ro+%rlKL4!KM(q5dRt0z0-81DIo_I)W zT`|Xc1UaO>-6CJAO_Rv`LMUaj8}#n60EjLj6vI3-j%RIQtg#@jU2QOybs^} zt+#IZ+O*y$xz4&yOsD6UUS$1;gVNlggS_B9Ocn?_CDn!iay=*0`GcAi$~Na z^pwVZoSru&&*@PmFJ5`)(4vF8R{M2H0`wTmzhO>Dc#I`MpQA z)_Q4O;Oo~GhIhAOluLT)Ms1q6(cGs zLU~CUpMOm2Z#l+Gj$y0n4FB?&)=GDKg=ysUAh%mRu05+LyWZu+C$+A&&1L_JwPKL? zeBCKe^D_R^N$q~c=UskhIiy$dzaZh+cWr* zQ`-2L!vQF<_1e_i#Y)$249KP54*ZKo0cP|_e&v)FIpf(;5Sm|L{|Lj2=!+3X!_TF^ z1wq~C(&rW6A0FpNX9}O#NqSS1q5L$z9X)?b&cOWZN!)|8(Tpl3K(p}zf;9hd?5H3n*!&5WcpvPy$zq2Vqf^TcH=;w z6xa6S;-FLgwc(V+&l1fgT2wCi$4LJS(VvMbC#1Y1(LqE<6TP2k4$*azy4X%)>?ise z(d$I5C#B(*M3af85uHRdhv){PJBS`4dXDJNM6E`GT=;r3rOZxr0MSuIGl|Y2x|rw& zqNPO3iB=N5Nc1XE_P$I=Akhdx5kBikj6OsiL?;o=Cc27f0nu$lcM*Mu=n0~gL_I|R zBO3aFj7Th~2%q&N#&Dt&h|VFpglGZLBBFbUmJzKW`W?|L(gqj4NXshl&B>^hNPdn>=UiZ5?C@>H{^x=`L46tYp&}ORuMH~i=VP7l3#GI zjG&XKXX>r_Q>RRioib<6ylGRiXU?0;SjMzl>*daC$ak5B?TrL={LF?POuqGhM5KI{ z)UTK>BOX6X=9o!K$X(JezPe;W>n@WikQc7_qow z2|p+yOD1fS*u^r4(Sa14gt6K*t~|m`2^SG=LAZo)JHlmz?Sv}{qna2O<|9UxoM#|3 zSnFZQ=tv5&gd+(j6ZR#XNjRLalW;WQJi-_9aBZ$jJ_z?-2op5`?iG;-z*XXbu#8^xUnS>uD>?GWha30|$go_B{ zxubEF5|;fUW0i!l@;5FIVT`Xv_{@4#rXZ9QA_(Uawi8}TIGOM=!VbdXM6I|o2rrkA zISJ1soJ-hA;j=5MD(%i*O#{oa62<&ujNLaoMShENIlXmpv%E&Y~>YBi8qfgddQ!fG8uq zYQmX>(~0Ib!kKdo`z}_s`PKnct&#lPwUWlJm$d8&NvjGZWlxcOqogTM6P_&-VA~~e z;{@2Y2)<}D#tkafvr;jWsO?$zgdenJimNJ1jv6WdV;B^}-He=RGn{g?qZ~#<|3sFV z*;U38HU|fLBY84mb1-m7>|$omkwFS3&LV8KtDHs#RuMKEenBI7Q6pSJ*op;pt~w4O5ua$iSRN*x3kYH^MoM@RCM2w-H|D zB8Hh|d5!S8Mji?p;intnLc(S4*4Q6=Fh!Zn0r3CEt35ikdf6vF15EQ_$|Kaa3@XULcX z6cWQMf~|zjIaF~25129$HY0eRu-OrL2%AMvL)e@aTR)W%HXzo`b++YM4hS(XdO}0ZZ7$YEs**XC2h;Bt0g0qXlZ~nUPGy6fwHKr5z@YC zC-Dx+k0pMfDJUJqcl8K%yIM&7c%pWqorxwAO(L31v>(w_qUl64h-MMZCYnoh9nk`! zuBYjykmy#TMMQTHEhf5$XbI7`h?Wv9BU(=M9MSVcP5T!mcClYbaGfY?DRaa|G=iv| zXcEz6qA5hviDnTs`;#2PxkU4b78n$rVj(e#h!ztqAzDVXf@l@d8ltvn8R1x>$wX6# zI*6td%^;d7sOXflh~XrfLo}CY9?=4#g+z;qmJzKWY6eh6xSA-7p`0aZ2OS`?DVZ4Q zM5hy-Lo|nI0nt*T6*2Cp-odART(Q!yIT2YhPBtQQRD6r%=9p7TxD~a+GQ#FYLOJ0$ zl2;HmhxJOr<}%z>MGQI7z=LbTwEM)W37hkb8p7fu3&s^E6JQR4zJy~*Y$a@t!YW}J zbzLl!80O|rOTy+T8c*09MLQEVhw&uB=CZsWVRQ2-m9V*tPa`bm6?l5j*jQqiGo=i| z=0s*9VRNFBNw{w67RPrD2(FDJb|p~{(V94ZbusMhJ5H_dK)r8FnpsR)$=1P;@DI;Xg zkF12vlC%*vt5O7Ev#P}sHfxrhuvxPb2~$D3STZrps+2<5%r^&NbJCwq*sM7jgw2xA zBy84exf?J2-2!W{^E2%A&pYQkbpF2ZLu#OO>OSh}nO zT?ktVcO`5i+>>ww;a-Ge2`3Y_6Q&6*OC&tNoMR^w<970pLO6x6gYZDY>4XOp&LE6- zZCsgzhZ4>rdNB=M^H|3g>WTdAHp8OzJzP2UlQLU6jzOD-=7%PyJVR+Asj*2 zO4v>~fN%v3X+eaONv;xh5Y`B15Dq4sC9#Wz5F>{aY=rX&Hziz7DQHI6>}hqvg`_V& zQZBAy!eN9<35OFdH!$)yf*6&g5J}iWIEru$;TD9gqh*e@Bpg9Fny{U44B=$Ltq3~= z7UdgDj0{qUBb-IJHQ^k>Z3yQPjwf75xGmvg!tDr`5^hhp9N5@n>p+Z3Qs_w7L%0*+ z8p53kTgOm8K{%FhSHg*eyAe(yoJ2T%Ot9Qz>p_f6Qs_z8Nw^o`T*AqO3kdfnTtv7J z;S$1q36~KbK)7OzOQzsT3EK$wC2S`=fN(P5!Gs-zXA3OaeBgrjUU{$>%QFDc{@w$P+6kFYP{Lc&c57ZVO3TuL~Q za5-U>a3$fWP$T`w0b=we1(qRGY@u1Cjc@?rSi*sX6A7z?QwT>9PAA-#a28>Ui)NWQ z#0VgqM>vphA>k;(#f1A3E+cG-6$Q#z1>t~L1F|Z@frP6GyZRD?-9sskmmX|{0|>_w z4kVmNxG&)p)4yH%PdEJ&&NTfKc1rAGmd?^cuIYhrf$4#85n)TB)Gsmh3747rgey$_ zB&lCz>JzRuFv>rW7;FONK(h2;BOFLL)|B^?@4b|2yE2LK1Tmb1PZQ21{3+oA!eFgxWC%L&% z(S&d^$;}m-xo=@5xr5{{Qur)`7$1>B7U4aFa|pjrIFImCgbNApCtOVUIN?&lZxJph z{27gDtdbZNq~IYuf^ZGtGQ!q5GRMvmX0$2~ARIyRPYBxye@!@<@aIPUgXti~2c(ce z_-(>jgbx$WA$*Q-9^oB?3kknUxR~&_gi8s#HWH(p7*&KT2_GQrA^b7n8p0O{TOX7; z@)h9-!ru_K6aIp5vcxWSf*9sbNgKir@?fsAt<=uVoskTZ&mnzt=fvD;$s&0c$*ojG zfrN7mIr2Y^7y;j3mOov8FWlitMD2O&)T{ z08NBgGRa>fxr6Wo!m*^U5zZj_orKN3D?8z=xwNL7MT`f@hc$$)^JE#1A?%<4x)F{b`6GnQ{l&I~?IeGIu&a_H5KoL` zQn;V6gYX8zHVUv4;S7>LNH~k|O2S2?-=A;}$yZB^a~q`4mlX0yVHM$Ik|z=_B>8iM ziwQ3xTuOK$;c~)v6L#DwbFi6#(N-!+VG}8M2#+OPLzojzCj&ZRt5X_WPdJMrT({#& z@?|8?ApfBPi?(Vf1#<&3lN7?pLo&(76Lt_TB%DEb9pNm(&lAoeTx`n8emJlQpXHH4 z1}Qj6p*i6~lHX(EbeZCQgo{c37~xXFFB2{&{4n84!duPpKbHcGBn1yC+-n*jd27No zB%e;$`jCv!A;J-aj}o>ME+L#uxYQi~9mF_A3K@h?63(LtL=ny+`9#8HG`;_xa1P0j z5H2A6F5zOrJB|DYvy2#qogv|)06E+i=4nFNKBd@;aR(I2ys#*e%0{@F@K83|$a33K83BPsgcH56NKl3s z77@rHoXUoA-8*B5kZF+7V}4^7ZwRB^unpY74_J~M2o1dXCPDX z=qT4r;TR^0B*hDhN}1(_MXSv7!XiP%URdN%g%=jBrn(Nh#xPMyB9_+&D6%Zs3yV6C z>4n9}n(KwdRG^sf7&gvGP&r{y#v%vJ4p6iLk)U&?f0xk3}UGmC(#` zGl2ypH*tx9RfcX?X6d=*?l*XFc(jr;;qm?N1jB{6_rGJ?`8~J29bCk7-wBR=JoDM6 z?#ySKrGKVm-0ObBssHBhDx}vkoO$wX3-e{iI{Fy#iJAxf3J9m0{8dt~iVpmhHKHYi z>-k<}#_^Tq^1Rh%qaAfFd+q>M^1=qRvdoFC&^uw0yes$EHy@_k!~1NA&KalSx!e zdElWuXrPKpQPk+=pz6ueR5C@WUf;d_5q+x9_{ZNooe6)&>k>}T59jIZ!zsF8F;j** z#KZ!jpHpZ3rZ$vm;P3Il#wBFw^)i0%QhlyZg)UFws-0psF|&6e0sw4CIbZ}KRY-X`#OomEcO8D5TkGA>e>_U&B_Tz6%9-3xQj zH!pmOv{u}wzzG}p4%F{@^Pt3EksNrlXBsALkL!NnURLsOUp`l7&Z!WGMGI|3Si$9O zP*@;?DI$)5c!3o@j5zYlT-~=twU^llk?~nNOTJ%c1^0`PqlG_YuW@;s7PiQA213t* zPrU6Ul3xw019Pbeg}*}FBcjrV*RSwrA?$5h*pjB(S3so#UhzJ$mq&S@{X>EA6;@;Ui?1{+8Y_9h$pVo{VY@Y zq!oJeK=>3|cug8PxoNfT>*JZC^KWzYFg>w880L`4=G9B}=w3DOWP>NXGi}DBjtbeW zDhBjLEf7UDIK|7K$cH>QU%1>WI&;iG-h0VS!?x)tmzj{uh#<028Nc1SqIl-;T`Tn# zfmc!gW}|9NH$oO|q?I4cIZ@rr%L#d->KA+M)`gRa8cNl#7y;)1qW2QjPgw4o^}uWlJtc z11f+8j2cFHO%@sD0Rvqq`Kl+0$aC}Zu4BbsQ4n$mg)8p8^{Qp=Cay%4n!|s4Ob_pn z;}wk4E4tp^GKG#2-CkCdQ{DqQ!=NUNH@HNN5GfRww|CSMvo`68Ucp&$k4kPNyOSNs z&JcYIj5of@!FPzlN46Et(DC7oI9d50)cNx+J`Ry4OEPw3vNyoG=7 z%Yl^Ms%_*nsu$H+x{Woe@Q7hsqn5O?d)418lu~uWIt;~Vm#6}_ijk-m-j@?Gx@BM6 zDCQz7Z&eN=d%dp+VLuD!N9z0YPN?DDD$K0Gg6UnRsKG|<-?T>eZJ*=SoSgq7G1dp9 z2V+!eZ}ln^(LisR(W0@*PRJ*geEXE+#kwr3Yr6-c+eO1DC<3=Uq>GgWChJGxv@K*IOv%VKnX3JX9KE*3CMaXva3x*Bv zpY)kVQHTWI>G2wMyxWh^7h$HrHEuuNW#L^fP!?tpx3BbSbD~)^xRA+pO{HPy?rpBV zb8jzl0I6ko_q~B30qo$_6583~>z_)2%w7ta&q{j{WRvi`o&4%I~S7Fn&ZuGq=QBym;~SsVEWT ze>S>z+$7A;o)$lBpb@aO0dIm4u+5t{R_aAG;7L6!@Qhw1A$!gO5pcX0jt9oVPlgA1 z{DcWE!+0W?8Q4il$dRea+m9(OI#`ME?`Cji9zv=_3T8;1B}cG?1)nNhH=$Dso>7-% zCG0ME72tVQ%p}3fJFc_V{=%=zsI5H(lT{!q@z@|i+Gk=ZSZyI3Ki^nuricg_UgjIA z%fwh%o($g91qNn`p=`RqPBP*Q72o`1lSiixGzv90lw|@Z%wI5nT72SdOibJ}bNQ@| z`mA=zjv!Wza*Iq1Vy%+qTUuGNEVk<4Dzzf8%<62C>7S0>J_lEx(GwKVZqMiw5{mGo zrWWoOK@x-~WsrCH#K&jNYzQ;Unt?biJqck%R7TuO3$sh zP1qxT4vL=-#m{;1^9z1nRcy0@SWj4bYm?sDRfMYeHe4!<7^(2aehTx;MitFsEDX-n z@y3yXzBt3@&jP#oJ4LMaz>X2&w{hd^P*5VQX^<}q%JO6WZzzdErvf7T7x<}q*dM71 zM1p5J2@YGtw{ShKlVJ7(Gjkw*l+mnlfEi#L0Zv)Of8u(FKpQ&K&T!Kg z@u)&QW^z52DfGsXT>@E4OLwMK2UP`BG%53Q`eynhD(Rwps$qBqDxv)*Zkpzx?F6Re zpeOCwa;wHBh@x8Vw+-3I3-xFt>PgTsV$wf{A1%~-#~Y;#=2WAUakB-^KR5^eJD1?8 zS8lhGB|LVsp8Wte$C&>rY&kwf|YO4Hn$t$fV0R3z8IG7|f*khN4I{T1flO_r~1G07qq^XK8g$ek3EIczaM{j475 zFWOuI;|W{z)^Q@I3ZSq;yxE?{5HMqFcCNF zGMz1h!CZu!0%vau#_e#;f)lr)I~*^5mf>gIOXBWBad!m2!Z}2nDuLyUY@iT4=ewHo9&(0U{^Kp3*pYptZhhZ+s z)hKrT!I%x#EVvrF3C&-?&j9?4n<4JjiMu!Pv+PU!?6ho$Is0~g<$3+~SdrMb;L|9G zcX9IpoNu5Q4IJH^pEI6bq{kcW{yGH7mRy}h!F7U@?LO0J_nD2`eQPM7Ao8~J*NXJs zeKJ30Y$selu;av3;-W9UMUHWFItKKlVAfEMDqN?}Tfd;U>yvwivAu9xP5;;7|04Wn z!~fV|)=-XetJCKzgn!#P#sWe6~mg1+g`04jzB7W1v?L_=Mr_8}`QW?6T!@hm0FEDn&*ZHrHVBY5|s<#-S-P`pp z##lcc#_bQ{XFFVIa0#?1l=HYv_(lBuBYxB!_-QGAI^(B)thc|Yrx*>z0|Q1wxw3*U zdQp$=V>IHGG}Oi6CK=8eoC5D1#dtSR889FB8$_JctxBoyRsV0S$x0>`{2#xlhxZl! zv)~2)0h-w#!cCsxzsU5zTp4i2%l{C?%Rs9gdR))kA5l_2VTT?5#MsvmqlB*G(aAYH zA{iU*CF2k8&|CLRzl8r509Q?Z8)D%X{>}xXhK8_u8UGl5yH#DrR^2byhFm4XZHPt9 z4uro@R?p|HUxL5t->_)^9krPJHN^0DJqZ3(R?p`T!=J0<3VNZdh%5PPh(%lx-Z9}f zlr@lvZWuQGV6*Bv^5>8Gup8p~u)X<_co)kyU**kr>RM}2Wp_MAeZcFuIRYn!_Rn}03rDdHipZYQ~(-~x8&%>QJ{Tf{nDoS<`Ybe7` zo0o26gq1~L^C2RsDT{a`w7OZ9&6u{PKQ4wM{49Z64;O6Ahp1mP2Fl#bti9%-7R(fX zf9opfAJTH6E0O~fD?B&sd7iz;tH*+@4v~^94jQ2wywOCAn zFozXWpdeO;wOnMUCM+tumnABxrzOhL%@QP6GrtuhBZZFmx_D$uf2JjAOdB0gE!I1N zZ%4%9=KHew$W*_JDzo*qurY82h=(!F0lxuHOl2^fjVIH^5%@VsE#! zVAF1IXb?=|@m;UzF=EN|!7KWNC}*Az`vGpr8fmm1^8vf`sX}l2F1@v&CwA!xQ5E>V z;FsYhu9f=rmMKrhZ_Y@CJMkPyp#roC)U7`(LR{hMlfGv zP8-BZL~ZGSf1irl65>@`M1fs|EK_7STx6^tYnIf8HN&7Km#;yrO7M-!@F0syn!}BA1f z8QlV2M%OM{ncvtZ2JcJo%hnm0)wdBt^cM|1M|KWCN`|wb>VT^HWtuU|-|(u;)xEFk ztw)JmC_+~fiGR4;Jn41T+_I~#JqcL~x|c}2V;-0kXw1INY95JyqO0S-dU+4;s2MSP z_3&{*Cs8EIsxa$C2dlths|Q|;FI!JF%L`k4EzDQw;>R}$Q;251ECesf|6}UbVZL^+ ztOa}b>K$FS=DutpT>1uS(;HiCKFs!=Slq#GM}OAwVjydM4owmp3{IpaAFe?7i|}U= z*8`Y#OvQ>K*x(P0@?~4erqJ`oUVi!b*YsggM&^ksV$^~JuUf$OzorioP36DW&=iY4 z@MVim`Lc>9WMu0xpI@T4>sfWimz_9^6={K#d*jHqfh-&&d3aJ73qRAW+Ex`@CXxo* zAD18_iv0c9=S}>Wll*$)$PPg$)K)0eaQ1(9XHf8nyQO6iYl&*#(h~K5bSyT1W*Zy8 zY#5BNTCR!{BWrdOjI92wWVRpsVz%EjR!spD!@!Zz_+O?;HWu=f?3<06Br>W^AbRZx z7J8;(bc8%p$m5%^X49k44kJ-}!isIhZj*E@>LeD-xcW}I!iAL|PD^pnUeqt?V3 z_+jvh1rK(F+#7rQkzsU17#$EsJ2Q;nH}sBy>?J>D+3CkxKE(&Uq4yae;+1PQPfWVg zqx{*6aOSpyH;!!K&(tIgIA_c>^Zjq=-2yX_7mvpHv+}3-jW_g|z>FY{-sUH&ZTX(_MA zeC=C$H&G^s-qNFDL_U>ZG7^WDZOa+MtXX4O$&3Chbca9t@M(VIExqFaasM2gN7xEb zSb!(Zz}sI!nxB#O8|a`My=+W+TQ;ceZzJ8dYJZjpS3>^kG5_&xy^n}j>v#0wg3f(M z@7O0Di?2s5R<>l5PIW9A<=+zJA3^0$<3yZ4>R%XSp{&`}ASe8vc}Gu(N$+B16B4Yf z3jU2=?fPv7p4HXLe%Zuh-1?}fvXxf$6I=$DX{pD2wOj8qxatWjn_XaKB|?tgu&L;g zFl83JN8rlHUt{d$kAJmMkK%r%dS8*UL8U0+lG_8=mvCl{tjByssXo={qRK?ch#W5- z$*M*LurJ00uycj{zfwIWs$^RLD~H3bn$dFVG4HugpA=Q}SpYi;XQrwi^S%4@)`O~l z2w)3;4ER5(5pfxL$o&6_(U54|JUOndj?bpM*i5goSi+u5#UZt==wi3>Jx>nEQLl2;o zOQHkW$8d#PD8K43-+KV%kscGsvfwI6UXS^22T*epV^QL8wyn~CJ?7mGikgicvOnBd zlGkJY@IlzOwS#@QLQ`Ic`8x;oo}ym-c2Kr}j_=Cxaum_|L^l!LPxKq2$|0%W4pjDh z;pmb31hMF(Cw!tUt9%;v*>bwP3a@7rprxk3Md}#KzcYI5Wry@|5tmJ{HLB!7Ao~FB z*fz>DFKh|HNHN{YLNLh*=@vxmBW&J&9mp=h{X>50G53E@?_<lWX zGN0!Y-qTwT7x(kvoX^WJ8{>L?^a#fMrWp%7zrRdxJzCtaP6%QdMKb)xxL#j}HTk9L05KP8 zjm`K4K_Y1^BqfM#hWiamq0K4$%QAhaOUM++GKU1Q({TPf#aT$kx(y9t{ozb3_ad#v zoK5&a)Y&4)H^D`dpEZ^wUc(%brpjL+c5 zkLc}pkZ>=g8AEtyee+y zPntm&06zfg1{T+L+!uRcW9#6IS{+zrt?)0CJL@85qQsVN31ZvfMi*-a6*y{Z5L*N+ zuER0%W$IvB;07UU_ zdM=1vfcqT=1r_+dZ9y!CBKtM&zlSsBCXRW94?V80u$vL>u_TDiS}LpH4$!r5f{H3B zC@(p#M|3c?doByYKS7n!mqFLV2`aP&4d4TJ>3XD47p!l<8k;NcXMsxbXjA+=SYgYh zxD#k(`#=_HY5RYkH;LL}gS|-1*;-m|yM>$|IiZghRVnW+) zQz7XAQny!K5BGI~$~t#d*`H8e1bV9gUx3aoxShMW@{vAaf*DX)FO{`|tBc4jYVci< z-|MAgj7o#*d(a8}RW=n)wC1V(`8yx!33VkobAZaS;SlbPkK|ra%*XnsS)wMV{Ers1 z=3$k60#_F&<0)GG$R)z*wo+v!aLsm8%WIT@QL9yU8(gQ|Jm$1Ms;&n0UaPWUaCNqC zk=}ifpZ@>QvE;)(-2a=MM_}V|X@_4vt*;l=d~t<5|FEkJG#EgzE^}ohD$XQeT&F1e_mzlpi>vIKt-G^sH-f|FQ}{?+$6Fg_n^jM z6u-L{Hlaq>Z9?UupRqJoSo1e*>9?v;Yu{Db({O0x!DnP&nQ%rQ6>iq7QSYnlZa5bL zy8C_p)EPMk{rHSL#PKs|T`l?gW0fVJR@v^|Jp8O4Q^!A8sj^?;j_>9}&+2_ziwvqk z21RxWU@gWLC@qpUC@m~&71?tc^UUpM<(&1zS$#mAov;fky8{k(!q0ix-|JD?4{+yU z|C~OdPQUP1l^um^wwu3kPH$JozvrsTHp3yFKcCZkb`t){W9`_UYLD%ycxE$f^A(l)+PeHdHAiDt;cnZ*W4_S4 z^$}rXgMr1_1DXB$V|YPlHJ(j8jxDM@W=&edDi$GqIU38^C&!(7%-4P)nV+BR5^vPyzFxAkYbZA;d>8P+l z+nH8qYNMh;Vs$HhP-&#?pd=`w4-_rY2C+&5*--Bdhh9`Z?nHGe(O>uKbpWFioY1fSDfeA}-Q`q`=x?gQ`!-X{Zq#xf zN;?_QvLPUd->1}PUu(vl;Nwt?sCov#fR|Te>(nYFa7E~R8p?~dM}p!o5eK0 zyEQsrROgJtt1FkJCXj?mE^D*REY^NW<*YyRWwwDjV?2D{6K^8B<4tWxyout2`{GUL z>v)s*Ez@7}Er#ZQM$JB@?5=oI`2{iJojaJ%GR>$(%Q9U{JOC>xU;9~n*yJ(MtQjnn z_X#suvA#A55{saa=AnRMnuibKLnwldr}C$xbJ19IC7O!nq2*{DdK+y=?dUt?eZ^-6 zp<`YN`phYq5ojzbMz^3j=svU%Ek|q6dbAD2P$%lUj;==N2y_BE6%9w{qasv{LhGvM z@9o}}n8rh5zH|DV720TzmKt8kptU3+$0*wUS4Aw1jAkW|cKAG_w)<75?)4p=L8$kOL&z-}=lsG$ z_4vKM6XRRed3vUAaDslw=Kt=p64Z~DWVBR*{Sk3QRj^uE~ zRsTHGcMrdB^p{3@7#`Fw`9rrsKlD{U@}2LPK6RHiuKUh+Nm@eSGItA&*@@|KadFR7 z|1l~3IB=Q89E*C3trkBdrT>8O70-9;f&cJN1Sq{d5-S zJ8WS64EkdI*>MCg2qflqbL-CFQY;&e3dajUCrLhPc&u( z(%$Gz;p48?JF}l$pH@sok(RB6uOO|z1^(!IGl}nR!~9!DA*B%ig0#nGvN88w!v?f4 z?CvHY{tE@j;1}CbF}`=V0Cg8Q6O|Ab^HDiI2xlTyA_5WZt=6XIc5=` z!+rRvc=3Bwfj2G2ocfMkQ69WytMxr8+de1N`-g3u?UWZyD_4+~h5b+mUi6@_X7uE1 z`#kDe#|i@UJLKqVSOd6 zjoC|GO#MXVIRQCHJ{^&a`r=Pyd`t zi@10;+JF~FqE>uQE;NUg*p~2@93K*sx4>;^FkbusW#N00pnXo#^t)eirz0+Yh)VF{ zC#VeHll)Ap>@{ZT*R&9EaRu6h7gwV;{I9a3l_kBciwfOECEy2WFkak&vhck#oRty% z)OWN5aj^*n@#0G;gl~p}f8f4`_ru@)$cFK5QgYZyQB(%=MwCWYvDku!;Klb)4n78t z_=Sz({qWJaILFt*Kk$vW;@NOuzc>?A8Q3?``ZVYxlet9k|70}#$bx3n-XhYJtC%5P ziK2M17;VOj*SYcDDa}fQZuZ2P4$6qHqC_%}#m#64Ufk-&1AK*i8PZ7_gmcmMux=oj zVFTh!4h!VL&w04H4BrVKKRC{G;hW*nzBuQNiJq}cMS4*Q!3Nhyp*KCwWRWJE1{Y

1_5+h2ZJr6zfH(cRsQ5jFU(iZl#R) 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 xI=Fhu>d@-Q>hjff&=LRu diff --git a/src/Miningcore.Tests/Crypto/HashingTests.cs b/src/Miningcore.Tests/Crypto/HashingTests.cs index b7333f7b6..6af46a1bd 100644 --- a/src/Miningcore.Tests/Crypto/HashingTests.cs +++ b/src/Miningcore.Tests/Crypto/HashingTests.cs @@ -43,6 +43,39 @@ public void Blake2s_Hash() Assert.Equal("c3ee938582d70ccd9a323b6097357449365d1fdfbbe2ecd7ee44e4bdbbb24392", result); } + [Fact] + public void Blake2s_Hash_Empty() + { + var hasher = new Blake2s(); + var hash = new byte[32]; + hasher.Digest(new byte[0], hash); + var result = hash.ToHexString(); + + Assert.Equal("69217a3079908094e11121d042354a7c1f55b6482ca1a51e1b250dfd1ed0eef9", result); + } + + [Fact] + public void Blake2b_Hash() + { + var hasher = new Blake2b(); + var hash = new byte[64]; + hasher.Digest(testValue2, hash); + var result = hash.ToHexString(); + + Assert.Equal("9cf604870022c048c8e05e701fd6718bfffdcf55d2c78264394cfced51964bc7cd9086133324d2c0ef637b8195ecee025889896b66f7418a83a910d853a00253", result); + } + + [Fact] + public void Blake2b_Hash_Empty() + { + var hasher = new Blake2b(); + var hash = new byte[64]; + hasher.Digest(new byte[0], hash); + var result = hash.ToHexString(); + + Assert.Equal("786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce", result); + } + [Fact] public void Groestl_Hash() { @@ -247,17 +280,6 @@ public void X22I_Hash() Assert.Equal("616c341e79417e6623dacff834c5c480d8d7d43ba6ae60fcee99f69343fd7c99", result); } - [Fact] - public void X25X_Hash() - { - var hasher = new X25X(); - var hash = new byte[32]; - hasher.Digest(testValue, hash); - var result = hash.ToHexString(); - - Assert.Equal("fe2a3d0e45eb5afbf007055c2605590db4167169dc03d1d5a070885771e51846", result); - } - [Fact] public void Skein_Hash() { diff --git a/src/Miningcore/AutofacModule.cs b/src/Miningcore/AutofacModule.cs index 2dcae2e98..30946c87a 100644 --- a/src/Miningcore/AutofacModule.cs +++ b/src/Miningcore/AutofacModule.cs @@ -20,6 +20,7 @@ using Newtonsoft.Json.Serialization; using Module = Autofac.Module; using Microsoft.AspNetCore.Mvc; +using Miningcore.Blockchain.Ergo; using Miningcore.Nicehash; using Miningcore.Pushover; @@ -135,27 +136,27 @@ protected override void Load(ContainerBuilder builder) ////////////////////// // Bitcoin and family - builder.RegisterType() - .AsSelf(); + builder.RegisterType(); ////////////////////// // Cryptonote - builder.RegisterType() - .AsSelf(); + builder.RegisterType(); ////////////////////// // Ethereum - builder.RegisterType() - .AsSelf(); - + builder.RegisterType(); ////////////////////// // ZCash - builder.RegisterType() - .AsSelf(); + builder.RegisterType(); + + ////////////////////// + // Ergo + + builder.RegisterType(); base.Load(builder); } diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs new file mode 100644 index 000000000..3ecb1f7e2 --- /dev/null +++ b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Miningcore.Blockchain.Cryptonote; + +namespace Miningcore.Blockchain.Ergo +{ + public class ErgoJob + { + public string PrevHash { get; set; } + + public void PrepareWorkerJob(CryptonoteWorkerJob workerJob, out string blob, out string target) + { + throw new NotImplementedException(); + } + + public object GetJobParams(bool isNew) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs new file mode 100644 index 000000000..94e7047b5 --- /dev/null +++ b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs @@ -0,0 +1,196 @@ +using System; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Autofac; +using Miningcore.Configuration; +using Miningcore.Extensions; +using Miningcore.Messaging; +using Miningcore.Stratum; +using Miningcore.Util; +using Contract = Miningcore.Contracts.Contract; +using static Miningcore.Util.ActionUtils; + +namespace Miningcore.Blockchain.Ergo +{ + public class ErgoJobManager : JobManagerBase + { + public ErgoJobManager( + IComponentContext ctx, + IMessageBus messageBus, + IHttpClientFactory httpClientFactory, + IExtraNonceProvider extraNonceProvider) : + base(ctx, messageBus) + { + Contract.RequiresNonNull(httpClientFactory, nameof(httpClientFactory)); + + this.extraNonceProvider = extraNonceProvider; + this.httpClientFactory = httpClientFactory; + } + + private ErgoCoinTemplate coin; + private ErgoClient daemon; + private readonly IHttpClientFactory httpClientFactory; + private readonly IExtraNonceProvider extraNonceProvider; + + protected async Task ShowDaemonSyncProgressAsync() + { + var info = await Guard(() => daemon.GetNodeInfoAsync(), + ex=> logger.Debug(ex)); + + if(info.FullHeight.HasValue && info.HeadersHeight.HasValue) + { + var totalBlocks = info.FullHeight.Value; + var percent = (double) info.HeadersHeight.Value / totalBlocks * 100; + + logger.Info(() => $"Daemon has downloaded {percent:0.00}% of blockchain from {info.PeersCount} peers"); + } + + else + logger.Info(() => $"Waiting for daemon to resume syncing ..."); + } + + protected async Task UpdateNetworkStatsAsync() + { + logger.LogInvoke(); + + //try + //{ + // var infoResponse = await daemon.ExecuteCmdAnyAsync(logger, CryptonoteCommands.GetInfo); + + // if(infoResponse.Error != null) + // logger.Warn(() => $"Error(s) refreshing network stats: {infoResponse.Error.Message} (Code {infoResponse.Error.Code})"); + + // if(infoResponse.Response != null) + // { + // var info = infoResponse.Response.ToObject(); + + // BlockchainStats.NetworkHashrate = info.Target > 0 ? (double) info.Difficulty / info.Target : 0; + // BlockchainStats.ConnectedPeers = info.OutgoingConnectionsCount + info.IncomingConnectionsCount; + // } + //} + + //catch(Exception e) + //{ + // logger.Error(e); + //} + } + + #region API-Surface + + public IObservable Jobs { get; private set; } + public BlockchainStats BlockchainStats { get; } = new(); + + public ErgoCoinTemplate Coin => coin; + + public object[] GetSubscriberData(StratumConnection worker) + { + throw new NotImplementedException(); + } + + public ValueTask SubmitShareAsync(StratumConnection worker, object submission, double stratumDifficultyBase, CancellationToken ct) + { + throw new NotImplementedException(); + } + + public async Task ValidateAddress(string address, CancellationToken ct) + { + if(string.IsNullOrEmpty(address)) + return false; + + var validity = await Guard(() => daemon.CheckAddressValidityAsync(address, ct), + ex=> logger.Debug(ex)); + + return validity?.IsValid == true; + } + + #endregion // API-Surface + + #region Overrides + + protected override Task PostStartInitAsync(CancellationToken ct) + { + return Task.CompletedTask; + } + + public override void Configure(PoolConfig poolConfig, ClusterConfig clusterConfig) + { + coin = poolConfig.Template.As(); + + base.Configure(poolConfig, clusterConfig); + } + + protected override void ConfigureDaemons() + { + var epConfig = poolConfig.Daemons.First(); + + var baseUrl = new UriBuilder(epConfig.Ssl || epConfig.Http2 ? Uri.UriSchemeHttps : Uri.UriSchemeHttp, + epConfig.Host, epConfig.Port, epConfig.HttpPath); + + daemon = new ErgoClient(baseUrl.ToString(), httpClientFactory.CreateClient()); + } + + protected override async Task AreDaemonsHealthyAsync() + { + var info = await Guard(() => daemon.GetNodeInfoAsync(), + ex=> logger.ThrowLogPoolStartupException($"Daemon reports: {ex.Message}")); + + if(info?.IsMining != true) + logger.ThrowLogPoolStartupException($"Mining is disabled in Ergo Daemon"); + + return true; + } + + protected override async Task AreDaemonsConnectedAsync() + { + var info = await Guard(() => daemon.GetNodeInfoAsync(), + ex=> logger.Debug(ex)); + + return info?.PeersCount > 0; + } + + protected override async Task EnsureDaemonsSynchedAsync(CancellationToken ct) + { + var syncPendingNotificationShown = false; + + while(true) + { + var info = await Guard(() => daemon.GetNodeInfoAsync(ct), + ex=> logger.Debug(ex)); + + var isSynched = info?.Difficulty.HasValue == true; + + if(isSynched) + { + logger.Info(() => "Daemon is synced with blockchain"); + break; + } + + if(!syncPendingNotificationShown) + { + logger.Info(() => "Daemon is still syncing with network. Manager will be started once synced"); + syncPendingNotificationShown = true; + } + + await ShowDaemonSyncProgressAsync(); + + // delay retry by 5s + await Task.Delay(5000, ct); + } + } + + protected Task<(bool IsNew, bool Force)> UpdateJob(bool forceUpdate, string via = null, string json = null) + { + return Task.FromResult((true, false)); + } + + protected object GetJobParamsForStratum(bool isNew) + { + var job = currentJob; + return job?.GetJobParams(isNew); + } + + #endregion // Overrides + } +} diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs new file mode 100644 index 000000000..3c1b0a835 --- /dev/null +++ b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs @@ -0,0 +1,426 @@ +using System; +using System.Reactive; +using System.Reactive.Linq; +using System.Reactive.Threading.Tasks; +using System.Threading; +using System.Threading.Tasks; +using System.Linq; +using Autofac; +using AutoMapper; +using Miningcore.Blockchain.Bitcoin; +using Miningcore.Configuration; +using Miningcore.JsonRpc; +using Miningcore.Messaging; +using Miningcore.Mining; +using Miningcore.Nicehash; +using Miningcore.Notifications.Messages; +using Miningcore.Persistence; +using Miningcore.Persistence.Repositories; +using Miningcore.Stratum; +using Miningcore.Time; +using Newtonsoft.Json; +using NLog; + +namespace Miningcore.Blockchain.Ergo +{ + [CoinFamily(CoinFamily.Ergo)] + public class ErgoPool : PoolBase + { + public ErgoPool(IComponentContext ctx, + JsonSerializerSettings serializerSettings, + IConnectionFactory cf, + IStatsRepository statsRepo, + IMapper mapper, + IMasterClock clock, + IMessageBus messageBus, + NicehashService nicehashService) : + base(ctx, serializerSettings, cf, statsRepo, mapper, clock, messageBus, nicehashService) + { + } + + protected object currentJobParams; + protected ErgoJobManager manager; + private ErgoCoinTemplate coin; + + protected virtual async Task OnSubscribeAsync(StratumConnection connection, Timestamped tsRequest) + { + var request = tsRequest.Value; + + if(request.Id == null) + throw new StratumException(StratumError.MinusOne, "missing request id"); + + var context = connection.ContextAs(); + var requestParams = request.ParamsAs(); + + var data = new object[] + { + new object[] + { + new object[] { BitcoinStratumMethods.SetDifficulty, connection.ConnectionId }, + new object[] { BitcoinStratumMethods.MiningNotify, connection.ConnectionId } + } + } + .Concat(manager.GetSubscriberData(connection)) + .ToArray(); + + await connection.RespondAsync(data, request.Id); + + // setup worker context + context.IsSubscribed = true; + context.UserAgent = requestParams?.Length > 0 ? requestParams[0].Trim() : null; + + // send intial update + await connection.NotifyAsync(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty }); + await connection.NotifyAsync(BitcoinStratumMethods.MiningNotify, currentJobParams); + } + + protected virtual async Task OnAuthorizeAsync(StratumConnection connection, Timestamped tsRequest, CancellationToken ct) + { + var request = tsRequest.Value; + + if(request.Id == null) + throw new StratumException(StratumError.MinusOne, "missing request id"); + + var context = connection.ContextAs(); + var requestParams = request.ParamsAs(); + var workerValue = requestParams?.Length > 0 ? requestParams[0] : null; + var password = requestParams?.Length > 1 ? requestParams[1] : null; + var passParts = password?.Split(PasswordControlVarsSeparator); + + // extract worker/miner + var split = workerValue?.Split('.'); + var minerName = split?.FirstOrDefault()?.Trim(); + var workerName = split?.Skip(1).FirstOrDefault()?.Trim() ?? string.Empty; + + // assumes that minerName is an address + context.IsAuthorized = await manager.ValidateAddress(minerName, ct); + context.Miner = minerName; + context.Worker = workerName; + + if(context.IsAuthorized) + { + // respond + await connection.RespondAsync(context.IsAuthorized, request.Id); + + // log association + logger.Info(() => $"[{connection.ConnectionId}] Authorized worker {workerValue}"); + + // extract control vars from password + var staticDiff = GetStaticDiffFromPassparts(passParts); + + // Nicehash support + staticDiff = await GetNicehashStaticMinDiff(connection, context.UserAgent, staticDiff, coin.Name, coin.GetAlgorithmName()); + + // Static diff + if(staticDiff.HasValue && + (context.VarDiff != null && staticDiff.Value >= context.VarDiff.Config.MinDiff || + context.VarDiff == null && staticDiff.Value > context.Difficulty)) + { + context.VarDiff = null; // disable vardiff + context.SetDifficulty(staticDiff.Value); + + logger.Info(() => $"[{connection.ConnectionId}] Setting static difficulty of {staticDiff.Value}"); + + await connection.NotifyAsync(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty }); + } + } + + else + { + await connection.RespondErrorAsync(StratumError.UnauthorizedWorker, "Authorization failed", request.Id, context.IsAuthorized); + + // issue short-time ban if unauthorized to prevent DDos on daemon (validateaddress RPC) + logger.Info(() => $"[{connection.ConnectionId}] Banning unauthorized worker {minerName} for {loginFailureBanTimeout.TotalSeconds} sec"); + + banManager.Ban(connection.RemoteEndpoint.Address, loginFailureBanTimeout); + + CloseConnection(connection); + } + } + + protected virtual async Task OnSubmitAsync(StratumConnection connection, Timestamped tsRequest, CancellationToken ct) + { + var request = tsRequest.Value; + var context = connection.ContextAs(); + + try + { + if(request.Id == null) + throw new StratumException(StratumError.MinusOne, "missing request id"); + + // check age of submission (aged submissions are usually caused by high server load) + var requestAge = clock.Now - tsRequest.Timestamp.UtcDateTime; + + if(requestAge > maxShareAge) + { + logger.Warn(() => $"[{connection.ConnectionId}] Dropping stale share submission request (server overloaded?)"); + return; + } + + // check worker state + context.LastActivity = clock.Now; + + // validate worker + if(!context.IsAuthorized) + throw new StratumException(StratumError.UnauthorizedWorker, "unauthorized worker"); + else if(!context.IsSubscribed) + throw new StratumException(StratumError.NotSubscribed, "not subscribed"); + + // submit + var requestParams = request.ParamsAs(); + var poolEndpoint = poolConfig.Ports[connection.PoolEndpoint.Port]; + + var share = await manager.SubmitShareAsync(connection, requestParams, poolEndpoint.Difficulty, ct); + + await connection.RespondAsync(true, request.Id); + + // publish + messageBus.SendMessage(new StratumShare(connection, share)); + + // telemetry + PublishTelemetry(TelemetryCategory.Share, clock.Now - tsRequest.Timestamp.UtcDateTime, true); + +// TODO: logger.Info(() => $"[{connection.ConnectionId}] Share accepted: D={Math.Round(share.Difficulty * coin.ShareMultiplier, 3)}"); + + // update pool stats + if(share.IsBlockCandidate) + poolStats.LastPoolBlockTime = clock.Now; + + // update client stats + context.Stats.ValidShares++; + await UpdateVarDiffAsync(connection); + } + + catch(StratumException ex) + { + // telemetry + PublishTelemetry(TelemetryCategory.Share, clock.Now - tsRequest.Timestamp.UtcDateTime, false); + + // update client stats + context.Stats.InvalidShares++; + logger.Info(() => $"[{connection.ConnectionId}] Share rejected: {ex.Message}"); + + // banning + ConsiderBan(connection, context, poolConfig.Banning); + + throw; + } + } + + private async Task OnSuggestDifficultyAsync(StratumConnection connection, Timestamped tsRequest) + { + var request = tsRequest.Value; + var context = connection.ContextAs(); + + // acknowledge + await connection.RespondAsync(true, request.Id); + + try + { + var requestedDiff = (double) Convert.ChangeType(request.Params, TypeCode.Double); + + // client may suggest higher-than-base difficulty, but not a lower one + var poolEndpoint = poolConfig.Ports[connection.PoolEndpoint.Port]; + + if(requestedDiff > poolEndpoint.Difficulty) + { + context.SetDifficulty(requestedDiff); + await connection.NotifyAsync(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty }); + + logger.Info(() => $"[{connection.ConnectionId}] Difficulty set to {requestedDiff} as requested by miner"); + } + } + + catch(Exception ex) + { + logger.Error(ex, () => $"Unable to convert suggested difficulty {request.Params}"); + } + } + + protected virtual async Task OnNewJobAsync(object jobParams) + { + currentJobParams = jobParams; + + logger.Info(() => "Broadcasting job"); + + var tasks = ForEachConnection(async client => + { + if(!client.IsAlive) + return; + + var context = client.ContextAs(); + + if(context.IsSubscribed && context.IsAuthorized) + { + // check alive + var lastActivityAgo = clock.Now - context.LastActivity; + + if(poolConfig.ClientConnectionTimeout > 0 && + lastActivityAgo.TotalSeconds > poolConfig.ClientConnectionTimeout) + { + logger.Info(() => $"[{client.ConnectionId}] Booting zombie-worker (idle-timeout exceeded)"); + CloseConnection(client); + return; + } + + // varDiff: if the client has a pending difficulty change, apply it now + if(context.ApplyPendingDifficulty()) + await client.NotifyAsync(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty }); + + // send job + await client.NotifyAsync(BitcoinStratumMethods.MiningNotify, currentJobParams); + } + }); + + try + { + await Task.WhenAll(tasks); + } + + catch(Exception ex) + { + logger.Debug(() => $"{nameof(OnNewJobAsync)}: {ex.Message}"); + } + } + + public override double HashrateFromShares(double shares, double interval) + { + var multiplier = BitcoinConstants.Pow2x32; + var result = shares * multiplier / interval; + + //result *= coin.HashrateMultiplier; + + return result; + } + + #region Overrides + + public override void Configure(PoolConfig poolConfig, ClusterConfig clusterConfig) + { + coin = poolConfig.Template.As(); + + base.Configure(poolConfig, clusterConfig); + } + + protected override async Task SetupJobManager(CancellationToken ct) + { + manager = ctx.Resolve( + new TypedParameter(typeof(IExtraNonceProvider), new BitcoinExtraNonceProvider(clusterConfig.InstanceId))); + + manager.Configure(poolConfig, clusterConfig); + + await manager.StartAsync(ct); + + if(poolConfig.EnableInternalStratum == true) + { + disposables.Add(manager.Jobs + .Select(job => Observable.FromAsync(async () => + { + try + { + await OnNewJobAsync(job); + } + + catch(Exception ex) + { + logger.Debug(() => $"{nameof(OnNewJobAsync)}: {ex.Message}"); + } + })) + .Concat() + .Subscribe(_ => { }, ex => + { + logger.Debug(ex, nameof(OnNewJobAsync)); + })); + + // we need work before opening the gates + await manager.Jobs.Take(1).ToTask(ct); + } + + else + { + // keep updating NetworkStats + disposables.Add(manager.Jobs.Subscribe()); + } + } + + protected override async Task InitStatsAsync() + { + await base.InitStatsAsync(); + + blockchainStats = manager.BlockchainStats; + } + + protected override WorkerContextBase CreateClientContext() + { + return new BitcoinWorkerContext(); + } + + protected override async Task OnRequestAsync(StratumConnection connection, + Timestamped tsRequest, CancellationToken ct) + { + var request = tsRequest.Value; + + try + { + switch(request.Method) + { + case BitcoinStratumMethods.Subscribe: + await OnSubscribeAsync(connection, tsRequest); + break; + + case BitcoinStratumMethods.Authorize: + await OnAuthorizeAsync(connection, tsRequest, ct); + break; + + case BitcoinStratumMethods.SubmitShare: + await OnSubmitAsync(connection, tsRequest, ct); + break; + + case BitcoinStratumMethods.SuggestDifficulty: + await OnSuggestDifficultyAsync(connection, tsRequest); + break; + + case BitcoinStratumMethods.ExtraNonceSubscribe: + await connection.RespondAsync(true, request.Id); + break; + + case BitcoinStratumMethods.GetTransactions: + // ignored + break; + + case BitcoinStratumMethods.MiningMultiVersion: + // ignored + break; + + default: + logger.Debug(() => $"[{connection.ConnectionId}] Unsupported RPC request: {JsonConvert.SerializeObject(request, serializerSettings)}"); + + await connection.RespondErrorAsync(StratumError.Other, $"Unsupported request {request.Method}", request.Id); + break; + } + } + + catch(StratumException ex) + { + await connection.RespondErrorAsync(ex.Code, ex.Message, request.Id, false); + } + } + + protected override async Task OnVarDiffUpdateAsync(StratumConnection connection, double newDiff) + { + var context = connection.ContextAs(); + context.EnqueueNewDifficulty(newDiff); + + // apply immediately and notify client + if(context.HasPendingDifficulty) + { + context.ApplyPendingDifficulty(); + + await connection.NotifyAsync(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty }); + await connection.NotifyAsync(BitcoinStratumMethods.MiningNotify, currentJobParams); + } + } + + #endregion // Overrides + } +} diff --git a/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs b/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs new file mode 100644 index 000000000..a6a986548 --- /dev/null +++ b/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs @@ -0,0 +1,10157 @@ +//---------------------- +// +// Generated using the NSwag toolchain v13.11.3.0 (NJsonSchema v10.4.4.0 (Newtonsoft.Json v12.0.0.0)) (http://NSwag.org) +// +//---------------------- + +using System.Collections.Generic; +using Newtonsoft.Json.Linq; + +#nullable enable + +#pragma warning disable 108 // Disable "CS0108 '{derivedDto}.ToJson()' hides inherited member '{dtoBase}.ToJson()'. Use the new keyword if hiding was intended." +#pragma warning disable 114 // Disable "CS0114 '{derivedDto}.RaisePropertyChanged(String)' hides inherited member 'dtoBase.RaisePropertyChanged(String)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword." +#pragma warning disable 472 // Disable "CS0472 The result of the expression is always 'false' since a value of type 'Int32' is never equal to 'null' of type 'Int32?' +#pragma warning disable 1573 // Disable "CS1573 Parameter '...' has no matching param tag in the XML comment for ... +#pragma warning disable 1591 // Disable "CS1591 Missing XML comment for publicly visible type or member ..." +#pragma warning disable 8073 // Disable "CS8073 The result of the expression is always 'false' since a value of type 'T' is never equal to 'null' of type 'T?'" + +namespace Miningcore.Blockchain.Ergo +{ + using System = global::System; + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.11.3.0 (NJsonSchema v10.4.4.0 (Newtonsoft.Json v12.0.0.0))")] + public partial class ErgoClient + { + private string _baseUrl = ""; + private System.Net.Http.HttpClient _httpClient; + private System.Lazy _settings; + + public ErgoClient(string baseUrl, System.Net.Http.HttpClient httpClient) + { + BaseUrl = baseUrl; + _httpClient = httpClient; + _settings = new System.Lazy(CreateSerializerSettings); + } + + private Newtonsoft.Json.JsonSerializerSettings CreateSerializerSettings() + { + var settings = new Newtonsoft.Json.JsonSerializerSettings(); + UpdateJsonSerializerSettings(settings); + return settings; + } + + public string BaseUrl + { + get { return _baseUrl; } + set { _baseUrl = value; } + } + + + public Newtonsoft.Json.JsonSerializerSettings JsonSerializerSettings { get { return _settings.Value; } } + + partial void UpdateJsonSerializerSettings(Newtonsoft.Json.JsonSerializerSettings settings); + + + partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, string url); + partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, System.Text.StringBuilder urlBuilder); + partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response); + /// Get the Array of header ids + /// The number of items in list to return + /// The number of items in list to skip + /// Array of header ids + /// A server side error occurred. + public System.Threading.Tasks.Task> GetHeaderIdsAsync(int? limit, int? offset) + { + return GetHeaderIdsAsync(limit, offset, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Get the Array of header ids + /// The number of items in list to return + /// The number of items in list to skip + /// Array of header ids + /// A server side error occurred. + public async System.Threading.Tasks.Task> GetHeaderIdsAsync(int? limit, int? offset, System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/blocks?"); + if (limit != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("limit") + "=").Append(System.Uri.EscapeDataString(ConvertToString(limit, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + } + if (offset != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("offset") + "=").Append(System.Uri.EscapeDataString(ConvertToString(offset, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + } + urlBuilder_.Length--; + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Send a mined block + /// Block is valid + /// A server side error occurred. + public System.Threading.Tasks.Task SendMinedBlockAsync(FullBlock body) + { + return SendMinedBlockAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Send a mined block + /// Block is valid + /// A server side error occurred. + public async System.Threading.Tasks.Task SendMinedBlockAsync(FullBlock body, System.Threading.CancellationToken cancellationToken) + { + if (body == null) + throw new System.ArgumentNullException("body"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/blocks"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + return; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Get the header ids at a given height + /// Height of a wanted block + /// Array of header ids + /// A server side error occurred. + public System.Threading.Tasks.Task> GetFullBlockAtAsync(int blockHeight) + { + return GetFullBlockAtAsync(blockHeight, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Get the header ids at a given height + /// Height of a wanted block + /// Array of header ids + /// A server side error occurred. + public async System.Threading.Tasks.Task> GetFullBlockAtAsync(int blockHeight, System.Threading.CancellationToken cancellationToken) + { + if (blockHeight == null) + throw new System.ArgumentNullException("blockHeight"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/blocks/at/{blockHeight}"); + urlBuilder_.Replace("{blockHeight}", System.Uri.EscapeDataString(ConvertToString(blockHeight, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + if (status_ == 404) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Blocks at this height doesn\'t exist", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Get headers in a specified range + /// Min header height + /// Max header height (best header height by default) + /// Array of headers + /// A server side error occurred. + public System.Threading.Tasks.Task> GetChainSliceAsync(int? fromHeight, int? toHeight) + { + return GetChainSliceAsync(fromHeight, toHeight, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Get headers in a specified range + /// Min header height + /// Max header height (best header height by default) + /// Array of headers + /// A server side error occurred. + public async System.Threading.Tasks.Task> GetChainSliceAsync(int? fromHeight, int? toHeight, System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/blocks/chainSlice?"); + if (fromHeight != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("fromHeight") + "=").Append(System.Uri.EscapeDataString(ConvertToString(fromHeight, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + } + if (toHeight != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("toHeight") + "=").Append(System.Uri.EscapeDataString(ConvertToString(toHeight, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + } + urlBuilder_.Length--; + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Get the full block info by a given signature + /// ID of a wanted block + /// Block object + /// A server side error occurred. + public System.Threading.Tasks.Task GetFullBlockByIdAsync(string headerId) + { + return GetFullBlockByIdAsync(headerId, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Get the full block info by a given signature + /// ID of a wanted block + /// Block object + /// A server side error occurred. + public async System.Threading.Tasks.Task GetFullBlockByIdAsync(string headerId, System.Threading.CancellationToken cancellationToken) + { + if (headerId == null) + throw new System.ArgumentNullException("headerId"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/blocks/{headerId}"); + urlBuilder_.Replace("{headerId}", System.Uri.EscapeDataString(ConvertToString(headerId, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + if (status_ == 404) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Block with this id doesn\'t exist", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Get the block header info by a given signature + /// ID of a wanted block header + /// Block header object + /// A server side error occurred. + public System.Threading.Tasks.Task GetBlockHeaderByIdAsync(string headerId) + { + return GetBlockHeaderByIdAsync(headerId, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Get the block header info by a given signature + /// ID of a wanted block header + /// Block header object + /// A server side error occurred. + public async System.Threading.Tasks.Task GetBlockHeaderByIdAsync(string headerId, System.Threading.CancellationToken cancellationToken) + { + if (headerId == null) + throw new System.ArgumentNullException("headerId"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/blocks/{headerId}/header"); + urlBuilder_.Replace("{headerId}", System.Uri.EscapeDataString(ConvertToString(headerId, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + if (status_ == 404) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Block with this id doesn\'t exist", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Get the block transactions info by a given signature + /// ID of a wanted block transactions + /// Block transaction object + /// A server side error occurred. + public System.Threading.Tasks.Task GetBlockTransactionsByIdAsync(string headerId) + { + return GetBlockTransactionsByIdAsync(headerId, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Get the block transactions info by a given signature + /// ID of a wanted block transactions + /// Block transaction object + /// A server side error occurred. + public async System.Threading.Tasks.Task GetBlockTransactionsByIdAsync(string headerId, System.Threading.CancellationToken cancellationToken) + { + if (headerId == null) + throw new System.ArgumentNullException("headerId"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/blocks/{headerId}/transactions"); + urlBuilder_.Replace("{headerId}", System.Uri.EscapeDataString(ConvertToString(headerId, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + if (status_ == 404) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Block with this id doesn\'t exist", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Get Merkle proof for transaction + /// ID of a wanted block transactions + /// ID of a wanted transaction + /// Merkle proof object + /// A server side error occurred. + public System.Threading.Tasks.Task GetProofForTxAsync(string headerId, string txId) + { + return GetProofForTxAsync(headerId, txId, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Get Merkle proof for transaction + /// ID of a wanted block transactions + /// ID of a wanted transaction + /// Merkle proof object + /// A server side error occurred. + public async System.Threading.Tasks.Task GetProofForTxAsync(string headerId, string txId, System.Threading.CancellationToken cancellationToken) + { + if (headerId == null) + throw new System.ArgumentNullException("headerId"); + + if (txId == null) + throw new System.ArgumentNullException("txId"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/blocks/{headerId}/proofFor/{txId}"); + urlBuilder_.Replace("{headerId}", System.Uri.EscapeDataString(ConvertToString(headerId, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Replace("{txId}", System.Uri.EscapeDataString(ConvertToString(txId, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Get the last headers objects + /// count of a wanted block headers + /// Array of block headers + /// A server side error occurred. + public System.Threading.Tasks.Task> GetLastHeadersAsync(double count) + { + return GetLastHeadersAsync(count, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Get the last headers objects + /// count of a wanted block headers + /// Array of block headers + /// A server side error occurred. + public async System.Threading.Tasks.Task> GetLastHeadersAsync(double count, System.Threading.CancellationToken cancellationToken) + { + if (count == null) + throw new System.ArgumentNullException("count"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/blocks/lastHeaders/{count}"); + urlBuilder_.Replace("{count}", System.Uri.EscapeDataString(ConvertToString(count, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Get the persistent modifier by its id + /// ID of a wanted modifier + /// Persistent modifier object + /// A server side error occurred. + public System.Threading.Tasks.Task GetModifierByIdAsync(string modifierId) + { + return GetModifierByIdAsync(modifierId, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Get the persistent modifier by its id + /// ID of a wanted modifier + /// Persistent modifier object + /// A server side error occurred. + public async System.Threading.Tasks.Task GetModifierByIdAsync(string modifierId, System.Threading.CancellationToken cancellationToken) + { + if (modifierId == null) + throw new System.ArgumentNullException("modifierId"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/blocks/modifier/{modifierId}"); + urlBuilder_.Replace("{modifierId}", System.Uri.EscapeDataString(ConvertToString(modifierId, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + return; + } + else + if (status_ == 404) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Modifier with this id doesn\'t exist", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Construct PoPow header according to given header id + /// ID of wanted header + /// PoPow header object + /// A server side error occurred. + public System.Threading.Tasks.Task GetPopowHeaderByIdAsync(string headerId) + { + return GetPopowHeaderByIdAsync(headerId, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Construct PoPow header according to given header id + /// ID of wanted header + /// PoPow header object + /// A server side error occurred. + public async System.Threading.Tasks.Task GetPopowHeaderByIdAsync(string headerId, System.Threading.CancellationToken cancellationToken) + { + if (headerId == null) + throw new System.ArgumentNullException("headerId"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/nipopow/popowHeaderById/{headerId}"); + urlBuilder_.Replace("{headerId}", System.Uri.EscapeDataString(ConvertToString(headerId, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + if (status_ == 404) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Header of extension of a corresponding block are not available", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Construct PoPow header for best header at given height + /// Height of a wanted header + /// PoPow header object + /// A server side error occurred. + public System.Threading.Tasks.Task GetPopowHeaderByHeightAsync(int height) + { + return GetPopowHeaderByHeightAsync(height, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Construct PoPow header for best header at given height + /// Height of a wanted header + /// PoPow header object + /// A server side error occurred. + public async System.Threading.Tasks.Task GetPopowHeaderByHeightAsync(int height, System.Threading.CancellationToken cancellationToken) + { + if (height == null) + throw new System.ArgumentNullException("height"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/nipopow/popowHeaderByHeight/{height}"); + urlBuilder_.Replace("{height}", System.Uri.EscapeDataString(ConvertToString(height, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + if (status_ == 404) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Header of extension of a corresponding block are not available", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Construct PoPoW proof for given min superchain length and suffix length + /// Minimal superchain length + /// Suffix length + /// Nipopow proof object + /// A server side error occurred. + public System.Threading.Tasks.Task GetPopowProofAsync(double minChainLength, double suffixLength) + { + return GetPopowProofAsync(minChainLength, suffixLength, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Construct PoPoW proof for given min superchain length and suffix length + /// Minimal superchain length + /// Suffix length + /// Nipopow proof object + /// A server side error occurred. + public async System.Threading.Tasks.Task GetPopowProofAsync(double minChainLength, double suffixLength, System.Threading.CancellationToken cancellationToken) + { + if (minChainLength == null) + throw new System.ArgumentNullException("minChainLength"); + + if (suffixLength == null) + throw new System.ArgumentNullException("suffixLength"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/nipopow/proof/{minChainLength}/{suffixLength}"); + urlBuilder_.Replace("{minChainLength}", System.Uri.EscapeDataString(ConvertToString(minChainLength, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Replace("{suffixLength}", System.Uri.EscapeDataString(ConvertToString(suffixLength, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Construct PoPoW proof for given min superchain length, suffix length and header ID + /// Minimal superchain length + /// Suffix length + /// ID of wanted header + /// Nipopow proof object + /// A server side error occurred. + public System.Threading.Tasks.Task GetPopowProofByHeaderIdAsync(double minChainLength, double suffixLength, string headerId) + { + return GetPopowProofByHeaderIdAsync(minChainLength, suffixLength, headerId, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Construct PoPoW proof for given min superchain length, suffix length and header ID + /// Minimal superchain length + /// Suffix length + /// ID of wanted header + /// Nipopow proof object + /// A server side error occurred. + public async System.Threading.Tasks.Task GetPopowProofByHeaderIdAsync(double minChainLength, double suffixLength, string headerId, System.Threading.CancellationToken cancellationToken) + { + if (minChainLength == null) + throw new System.ArgumentNullException("minChainLength"); + + if (suffixLength == null) + throw new System.ArgumentNullException("suffixLength"); + + if (headerId == null) + throw new System.ArgumentNullException("headerId"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/nipopow/proof/{minChainLength}/{suffixLength}/{headerId}"); + urlBuilder_.Replace("{minChainLength}", System.Uri.EscapeDataString(ConvertToString(minChainLength, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Replace("{suffixLength}", System.Uri.EscapeDataString(ConvertToString(suffixLength, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Replace("{headerId}", System.Uri.EscapeDataString(ConvertToString(headerId, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Get the information about the Node + /// Node info object + /// A server side error occurred. + public System.Threading.Tasks.Task GetNodeInfoAsync() + { + return GetNodeInfoAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Get the information about the Node + /// Node info object + /// A server side error occurred. + public async System.Threading.Tasks.Task GetNodeInfoAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/info"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Submit an Ergo transaction to unconfirmed pool to send it over the network + /// JSON with ID of the new transaction + /// A server side error occurred. + public System.Threading.Tasks.Task SendTransactionAsync(ErgoTransaction body) + { + return SendTransactionAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Submit an Ergo transaction to unconfirmed pool to send it over the network + /// JSON with ID of the new transaction + /// A server side error occurred. + public async System.Threading.Tasks.Task SendTransactionAsync(ErgoTransaction body, System.Threading.CancellationToken cancellationToken) + { + if (body == null) + throw new System.ArgumentNullException("body"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/transactions"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Checks an Ergo transaction without sending it over the network. Checks that transaction is valid and its inputs are in the UTXO set. Returns transaction identifier if the transaction is passing the checks. + /// JSON with ID of the new transaction + /// A server side error occurred. + public System.Threading.Tasks.Task CheckTransactionAsync(ErgoTransaction body) + { + return CheckTransactionAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Checks an Ergo transaction without sending it over the network. Checks that transaction is valid and its inputs are in the UTXO set. Returns transaction identifier if the transaction is passing the checks. + /// JSON with ID of the new transaction + /// A server side error occurred. + public async System.Threading.Tasks.Task CheckTransactionAsync(ErgoTransaction body, System.Threading.CancellationToken cancellationToken) + { + if (body == null) + throw new System.ArgumentNullException("body"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/transactions/check"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Get current pool of the unconfirmed transactions pool + /// The number of items in list to return + /// The number of items in list to skip + /// Array with Ergo transactions + /// A server side error occurred. + public System.Threading.Tasks.Task> GetUnconfirmedTransactionsAsync(int? limit, int? offset) + { + return GetUnconfirmedTransactionsAsync(limit, offset, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Get current pool of the unconfirmed transactions pool + /// The number of items in list to return + /// The number of items in list to skip + /// Array with Ergo transactions + /// A server side error occurred. + public async System.Threading.Tasks.Task> GetUnconfirmedTransactionsAsync(int? limit, int? offset, System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/transactions/unconfirmed?"); + if (limit != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("limit") + "=").Append(System.Uri.EscapeDataString(ConvertToString(limit, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + } + if (offset != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("offset") + "=").Append(System.Uri.EscapeDataString(ConvertToString(offset, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + } + urlBuilder_.Length--; + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Get histogram (waittime, (n_trans, sum(fee)) for transactions in mempool. It contains "bins"+1 bins, where i-th elements corresponds to transaction with wait time [i*maxtime/bins, (i+1)*maxtime/bins), and last bin corresponds to the transactions with wait time >= maxtime. + /// The number of bins in histogram + /// Maximal wait time in milliseconds + /// Array with fee histogram + /// A server side error occurred. + public System.Threading.Tasks.Task> GetFeeHistogramAsync(int? bins, long? maxtime) + { + return GetFeeHistogramAsync(bins, maxtime, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Get histogram (waittime, (n_trans, sum(fee)) for transactions in mempool. It contains "bins"+1 bins, where i-th elements corresponds to transaction with wait time [i*maxtime/bins, (i+1)*maxtime/bins), and last bin corresponds to the transactions with wait time >= maxtime. + /// The number of bins in histogram + /// Maximal wait time in milliseconds + /// Array with fee histogram + /// A server side error occurred. + public async System.Threading.Tasks.Task> GetFeeHistogramAsync(int? bins, long? maxtime, System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/transactions/poolHistogram?"); + if (bins != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("bins") + "=").Append(System.Uri.EscapeDataString(ConvertToString(bins, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + } + if (maxtime != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("maxtime") + "=").Append(System.Uri.EscapeDataString(ConvertToString(maxtime, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + } + urlBuilder_.Length--; + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Get recommended fee (in nanoErgs) for a transaction with specified size (in bytes) to be proceeded in specified time (in minutes) + /// Maximum transaction wait time in minutes + /// Transaction size + /// Recommended fee for the transaction (in nanoErgs) + /// A server side error occurred. + public System.Threading.Tasks.Task GetRecommendedFeeAsync(int waitTime, int txSize) + { + return GetRecommendedFeeAsync(waitTime, txSize, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Get recommended fee (in nanoErgs) for a transaction with specified size (in bytes) to be proceeded in specified time (in minutes) + /// Maximum transaction wait time in minutes + /// Transaction size + /// Recommended fee for the transaction (in nanoErgs) + /// A server side error occurred. + public async System.Threading.Tasks.Task GetRecommendedFeeAsync(int waitTime, int txSize, System.Threading.CancellationToken cancellationToken) + { + if (waitTime == null) + throw new System.ArgumentNullException("waitTime"); + + if (txSize == null) + throw new System.ArgumentNullException("txSize"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/transactions/getFee?"); + urlBuilder_.Append(System.Uri.EscapeDataString("waitTime") + "=").Append(System.Uri.EscapeDataString(ConvertToString(waitTime, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + urlBuilder_.Append(System.Uri.EscapeDataString("txSize") + "=").Append(System.Uri.EscapeDataString(ConvertToString(txSize, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + urlBuilder_.Length--; + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Get expected wait time for the transaction with specified fee and size + /// Transaction fee (in nanoErgs) + /// Transaction size + /// Expected wait time in milliseconds + /// A server side error occurred. + public System.Threading.Tasks.Task GetExpectedWaitTimeAsync(int fee, int txSize) + { + return GetExpectedWaitTimeAsync(fee, txSize, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Get expected wait time for the transaction with specified fee and size + /// Transaction fee (in nanoErgs) + /// Transaction size + /// Expected wait time in milliseconds + /// A server side error occurred. + public async System.Threading.Tasks.Task GetExpectedWaitTimeAsync(int fee, int txSize, System.Threading.CancellationToken cancellationToken) + { + if (fee == null) + throw new System.ArgumentNullException("fee"); + + if (txSize == null) + throw new System.ArgumentNullException("txSize"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/transactions/waitTime?"); + urlBuilder_.Append(System.Uri.EscapeDataString("fee") + "=").Append(System.Uri.EscapeDataString(ConvertToString(fee, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + urlBuilder_.Append(System.Uri.EscapeDataString("txSize") + "=").Append(System.Uri.EscapeDataString(ConvertToString(txSize, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + urlBuilder_.Length--; + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Get all known peers + /// Array of peer objects + /// A server side error occurred. + public System.Threading.Tasks.Task> GetAllPeersAsync() + { + return GetAllPeersAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Get all known peers + /// Array of peer objects + /// A server side error occurred. + public async System.Threading.Tasks.Task> GetAllPeersAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/peers/all"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Get current connected peers + /// Array of peer objects + /// A server side error occurred. + public System.Threading.Tasks.Task> GetConnectedPeersAsync() + { + return GetConnectedPeersAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Get current connected peers + /// Array of peer objects + /// A server side error occurred. + public async System.Threading.Tasks.Task> GetConnectedPeersAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/peers/connected"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Add address to peers list + /// Attempt to connect to the peer + /// A server side error occurred. + public System.Threading.Tasks.Task ConnectToPeerAsync(string body) + { + return ConnectToPeerAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Add address to peers list + /// Attempt to connect to the peer + /// A server side error occurred. + public async System.Threading.Tasks.Task ConnectToPeerAsync(string body, System.Threading.CancellationToken cancellationToken) + { + if (body == null) + throw new System.ArgumentNullException("body"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/peers/connect"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + return; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Get blacklisted peers + /// Array of the addresses + /// A server side error occurred. + public System.Threading.Tasks.Task> GetBlacklistedPeersAsync() + { + return GetBlacklistedPeersAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Get blacklisted peers + /// Array of the addresses + /// A server side error occurred. + public async System.Threading.Tasks.Task> GetBlacklistedPeersAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/peers/blacklisted"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Get last incomming message timestamp and current network time + /// Network status + /// A server side error occurred. + public System.Threading.Tasks.Task GetPeersStatusAsync() + { + return GetPeersStatusAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Get last incomming message timestamp and current network time + /// Network status + /// A server side error occurred. + public async System.Threading.Tasks.Task GetPeersStatusAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/peers/status"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Get random seed of 32 bytes + /// Base16-encoded 32 byte seed + /// A server side error occurred. + public System.Threading.Tasks.Task GetRandomSeedAsync() + { + return GetRandomSeedAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Get random seed of 32 bytes + /// Base16-encoded 32 byte seed + /// A server side error occurred. + public async System.Threading.Tasks.Task GetRandomSeedAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/utils/seed"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Check address validity + /// address to check + /// Address validity with validation error + /// A server side error occurred. + public System.Threading.Tasks.Task CheckAddressValidityAsync(string address) + { + return CheckAddressValidityAsync(address, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Check address validity + /// address to check + /// Address validity with validation error + /// A server side error occurred. + public async System.Threading.Tasks.Task CheckAddressValidityAsync(string address, System.Threading.CancellationToken cancellationToken) + { + if (address == null) + throw new System.ArgumentNullException("address"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/utils/address/{address}"); + urlBuilder_.Replace("{address}", System.Uri.EscapeDataString(ConvertToString(address, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Convert Pay-To-Public-Key Address to raw representation (hex-encoded serialized curve point) + /// address to extract public key from + /// hex-encoded public key (serialized secp256k1 element) + /// A server side error occurred. + public System.Threading.Tasks.Task AddressToRawAsync(string address) + { + return AddressToRawAsync(address, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Convert Pay-To-Public-Key Address to raw representation (hex-encoded serialized curve point) + /// address to extract public key from + /// hex-encoded public key (serialized secp256k1 element) + /// A server side error occurred. + public async System.Threading.Tasks.Task AddressToRawAsync(string address, System.Threading.CancellationToken cancellationToken) + { + if (address == null) + throw new System.ArgumentNullException("address"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/utils/addressToRaw/{address}"); + urlBuilder_.Replace("{address}", System.Uri.EscapeDataString(ConvertToString(address, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Generate Pay-To-Public-Key address from hex-encoded raw pubkey (secp256k1 serialized point) + /// public key to get address from + /// Pay-to-public-key (P2PK) address + /// A server side error occurred. + public System.Threading.Tasks.Task RawToAddressAsync(string pubkeyHex) + { + return RawToAddressAsync(pubkeyHex, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Generate Pay-To-Public-Key address from hex-encoded raw pubkey (secp256k1 serialized point) + /// public key to get address from + /// Pay-to-public-key (P2PK) address + /// A server side error occurred. + public async System.Threading.Tasks.Task RawToAddressAsync(string pubkeyHex, System.Threading.CancellationToken cancellationToken) + { + if (pubkeyHex == null) + throw new System.ArgumentNullException("pubkeyHex"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/utils/rawToAddress/{pubkeyHex}"); + urlBuilder_.Replace("{pubkeyHex}", System.Uri.EscapeDataString(ConvertToString(pubkeyHex, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Generate Ergo address from hex-encoded ErgoTree + /// ErgoTree to derive an address from + /// Ergo address + /// A server side error occurred. + public System.Threading.Tasks.Task ErgoTreeToAddressAsync(string ergoTreeHex) + { + return ErgoTreeToAddressAsync(ergoTreeHex, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Generate Ergo address from hex-encoded ErgoTree + /// ErgoTree to derive an address from + /// Ergo address + /// A server side error occurred. + public async System.Threading.Tasks.Task ErgoTreeToAddressAsync(string ergoTreeHex, System.Threading.CancellationToken cancellationToken) + { + if (ergoTreeHex == null) + throw new System.ArgumentNullException("ergoTreeHex"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/utils/ergoTreeToAddress/{ergoTreeHex}"); + urlBuilder_.Replace("{ergoTreeHex}", System.Uri.EscapeDataString(ConvertToString(ergoTreeHex, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Generate random seed of specified length in bytes + /// seed length in bytes + /// Base16-encoded N byte seed + /// A server side error occurred. + public System.Threading.Tasks.Task GetRandomSeedWithLengthAsync(string length) + { + return GetRandomSeedWithLengthAsync(length, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Generate random seed of specified length in bytes + /// seed length in bytes + /// Base16-encoded N byte seed + /// A server side error occurred. + public async System.Threading.Tasks.Task GetRandomSeedWithLengthAsync(string length, System.Threading.CancellationToken cancellationToken) + { + if (length == null) + throw new System.ArgumentNullException("length"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/utils/seed/{length}"); + urlBuilder_.Replace("{length}", System.Uri.EscapeDataString(ConvertToString(length, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Return Blake2b hash of specified message + /// Base16-encoded 32 byte hash + /// A server side error occurred. + public System.Threading.Tasks.Task HashBlake2bAsync(string body) + { + return HashBlake2bAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Return Blake2b hash of specified message + /// Base16-encoded 32 byte hash + /// A server side error occurred. + public async System.Threading.Tasks.Task HashBlake2bAsync(string body, System.Threading.CancellationToken cancellationToken) + { + if (body == null) + throw new System.ArgumentNullException("body"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/utils/hash/blake2b"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Initialize new wallet with randomly generated seed + /// New wallet with randomly generated seed created successfully + /// A server side error occurred. + public System.Threading.Tasks.Task WalletInitAsync(Body body) + { + return WalletInitAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Initialize new wallet with randomly generated seed + /// New wallet with randomly generated seed created successfully + /// A server side error occurred. + public async System.Threading.Tasks.Task WalletInitAsync(Body body, System.Threading.CancellationToken cancellationToken) + { + if (body == null) + throw new System.ArgumentNullException("body"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/wallet/init"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Create new wallet from existing mnemonic seed + /// Wallet restored successfully + /// A server side error occurred. + public System.Threading.Tasks.Task WalletRestoreAsync(Body2 body) + { + return WalletRestoreAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Create new wallet from existing mnemonic seed + /// Wallet restored successfully + /// A server side error occurred. + public async System.Threading.Tasks.Task WalletRestoreAsync(Body2 body, System.Threading.CancellationToken cancellationToken) + { + if (body == null) + throw new System.ArgumentNullException("body"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/wallet/restore"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + return; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Check whether mnemonic phrase is corresponding to the wallet seed + /// Whether passphrase match wallet + /// A server side error occurred. + public System.Threading.Tasks.Task CheckSeedAsync(Body3 body) + { + return CheckSeedAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Check whether mnemonic phrase is corresponding to the wallet seed + /// Whether passphrase match wallet + /// A server side error occurred. + public async System.Threading.Tasks.Task CheckSeedAsync(Body3 body, System.Threading.CancellationToken cancellationToken) + { + if (body == null) + throw new System.ArgumentNullException("body"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/wallet/check"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Unlock wallet + /// Wallet unlocked successfully + /// A server side error occurred. + public System.Threading.Tasks.Task WalletUnlockAsync(Body4 body) + { + return WalletUnlockAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Unlock wallet + /// Wallet unlocked successfully + /// A server side error occurred. + public async System.Threading.Tasks.Task WalletUnlockAsync(Body4 body, System.Threading.CancellationToken cancellationToken) + { + if (body == null) + throw new System.ArgumentNullException("body"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/wallet/unlock"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + return; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Lock wallet + /// Wallet locked successfully + /// A server side error occurred. + public System.Threading.Tasks.Task WalletLockAsync() + { + return WalletLockAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Lock wallet + /// Wallet locked successfully + /// A server side error occurred. + public async System.Threading.Tasks.Task WalletLockAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/wallet/lock"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + return; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Rescan wallet (all the available full blocks) + /// Wallet storage recreated + /// A server side error occurred. + public System.Threading.Tasks.Task WalletRescanAsync() + { + return WalletRescanAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Rescan wallet (all the available full blocks) + /// Wallet storage recreated + /// A server side error occurred. + public async System.Threading.Tasks.Task WalletRescanAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/wallet/rescan"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + return; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Get wallet status + /// Wallet status + /// A server side error occurred. + public System.Threading.Tasks.Task GetWalletStatusAsync() + { + return GetWalletStatusAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Get wallet status + /// Wallet status + /// A server side error occurred. + public async System.Threading.Tasks.Task GetWalletStatusAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/wallet/status"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Update address to be used to send change to + /// Change address updated successfully + /// A server side error occurred. + public System.Threading.Tasks.Task WalletUpdateChangeAddressAsync(Body5 body) + { + return WalletUpdateChangeAddressAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Update address to be used to send change to + /// Change address updated successfully + /// A server side error occurred. + public async System.Threading.Tasks.Task WalletUpdateChangeAddressAsync(Body5 body, System.Threading.CancellationToken cancellationToken) + { + if (body == null) + throw new System.ArgumentNullException("body"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/wallet/updateChangeAddress"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + return; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Derive new key according to a provided path + /// Resulted address + /// A server side error occurred. + public System.Threading.Tasks.Task WalletDeriveKeyAsync(Body6 body) + { + return WalletDeriveKeyAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Derive new key according to a provided path + /// Resulted address + /// A server side error occurred. + public async System.Threading.Tasks.Task WalletDeriveKeyAsync(Body6 body, System.Threading.CancellationToken cancellationToken) + { + if (body == null) + throw new System.ArgumentNullException("body"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/wallet/deriveKey"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Derive next key + /// Resulted secret path and address + /// A server side error occurred. + public System.Threading.Tasks.Task WalletDeriveNextKeyAsync() + { + return WalletDeriveNextKeyAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Derive next key + /// Resulted secret path and address + /// A server side error occurred. + public async System.Threading.Tasks.Task WalletDeriveNextKeyAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/wallet/deriveNextKey"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Get total amount of confirmed Ergo tokens and assets + /// Get total amount of confirmed Ergo tokens and assets + /// A server side error occurred. + public System.Threading.Tasks.Task WalletBalancesAsync() + { + return WalletBalancesAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Get total amount of confirmed Ergo tokens and assets + /// Get total amount of confirmed Ergo tokens and assets + /// A server side error occurred. + public async System.Threading.Tasks.Task WalletBalancesAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/wallet/balances"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Get a list of all wallet-related transactions + /// Minimal tx inclusion height + /// Maximal tx inclusion height + /// Minimal confirmations number + /// Maximal confirmations number + /// A list of all wallet-related transactions + /// A server side error occurred. + public System.Threading.Tasks.Task> WalletTransactionsAsync(int? minInclusionHeight, int? maxInclusionHeight, int? minConfirmations, int? maxConfirmations) + { + return WalletTransactionsAsync(minInclusionHeight, maxInclusionHeight, minConfirmations, maxConfirmations, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Get a list of all wallet-related transactions + /// Minimal tx inclusion height + /// Maximal tx inclusion height + /// Minimal confirmations number + /// Maximal confirmations number + /// A list of all wallet-related transactions + /// A server side error occurred. + public async System.Threading.Tasks.Task> WalletTransactionsAsync(int? minInclusionHeight, int? maxInclusionHeight, int? minConfirmations, int? maxConfirmations, System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/wallet/transactions?"); + if (minInclusionHeight != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("minInclusionHeight") + "=").Append(System.Uri.EscapeDataString(ConvertToString(minInclusionHeight, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + } + if (maxInclusionHeight != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("maxInclusionHeight") + "=").Append(System.Uri.EscapeDataString(ConvertToString(maxInclusionHeight, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + } + if (minConfirmations != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("minConfirmations") + "=").Append(System.Uri.EscapeDataString(ConvertToString(minConfirmations, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + } + if (maxConfirmations != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("maxConfirmations") + "=").Append(System.Uri.EscapeDataString(ConvertToString(maxConfirmations, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + } + urlBuilder_.Length--; + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Get wallet-related transaction by id + /// Transaction id + /// Wallet-related transaction + /// A server side error occurred. + public System.Threading.Tasks.Task> WalletGetTransactionAsync(string id) + { + return WalletGetTransactionAsync(id, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Get wallet-related transaction by id + /// Transaction id + /// Wallet-related transaction + /// A server side error occurred. + public async System.Threading.Tasks.Task> WalletGetTransactionAsync(string id, System.Threading.CancellationToken cancellationToken) + { + if (id == null) + throw new System.ArgumentNullException("id"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/wallet/transactionById?"); + urlBuilder_.Append(System.Uri.EscapeDataString("id") + "=").Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + urlBuilder_.Length--; + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + if (status_ == 404) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Transaction with specified id not found in wallet", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Get scan-related transactions by scan id + /// Scan id + /// Scan-related transactions + /// A server side error occurred. + public System.Threading.Tasks.Task> WalletTransactionsByScanIdAsync(string scanId) + { + return WalletTransactionsByScanIdAsync(scanId, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Get scan-related transactions by scan id + /// Scan id + /// Scan-related transactions + /// A server side error occurred. + public async System.Threading.Tasks.Task> WalletTransactionsByScanIdAsync(string scanId, System.Threading.CancellationToken cancellationToken) + { + if (scanId == null) + throw new System.ArgumentNullException("scanId"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/wallet/transactionsByScanId?"); + urlBuilder_.Append(System.Uri.EscapeDataString("scanId") + "=").Append(System.Uri.EscapeDataString(ConvertToString(scanId, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + urlBuilder_.Length--; + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + if (status_ == 404) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Transactions with related scan id not found in wallet", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Get a list of all wallet-related boxes, both spent and unspent. Set minConfirmations to -1 to get mempool boxes included. + /// Minimal number of confirmations + /// Minimal box inclusion height + /// A list of all wallet-related boxes + /// A server side error occurred. + public System.Threading.Tasks.Task> WalletBoxesAsync(int? minConfirmations, int? minInclusionHeight) + { + return WalletBoxesAsync(minConfirmations, minInclusionHeight, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Get a list of all wallet-related boxes, both spent and unspent. Set minConfirmations to -1 to get mempool boxes included. + /// Minimal number of confirmations + /// Minimal box inclusion height + /// A list of all wallet-related boxes + /// A server side error occurred. + public async System.Threading.Tasks.Task> WalletBoxesAsync(int? minConfirmations, int? minInclusionHeight, System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/wallet/boxes?"); + if (minConfirmations != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("minConfirmations") + "=").Append(System.Uri.EscapeDataString(ConvertToString(minConfirmations, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + } + if (minInclusionHeight != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("minInclusionHeight") + "=").Append(System.Uri.EscapeDataString(ConvertToString(minInclusionHeight, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + } + urlBuilder_.Length--; + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Get a list of collected boxes. + /// This API method recieves balance and assets, according to which, it's collecting result + /// A list of all collected boxes + /// A server side error occurred. + public System.Threading.Tasks.Task> WalletBoxesCollectAsync(BoxesRequestHolder body) + { + return WalletBoxesCollectAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Get a list of collected boxes. + /// This API method recieves balance and assets, according to which, it's collecting result + /// A list of all collected boxes + /// A server side error occurred. + public async System.Threading.Tasks.Task> WalletBoxesCollectAsync(BoxesRequestHolder body, System.Threading.CancellationToken cancellationToken) + { + if (body == null) + throw new System.ArgumentNullException("body"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/wallet/boxes/collect"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Get a list of unspent boxes. Set minConfirmations to -1 to have mempool boxes considered. + /// Minimal number of confirmations + /// Minimal box inclusion height + /// A list of unspent boxes + /// A server side error occurred. + public System.Threading.Tasks.Task> WalletUnspentBoxesAsync(int? minConfirmations, int? minInclusionHeight) + { + return WalletUnspentBoxesAsync(minConfirmations, minInclusionHeight, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Get a list of unspent boxes. Set minConfirmations to -1 to have mempool boxes considered. + /// Minimal number of confirmations + /// Minimal box inclusion height + /// A list of unspent boxes + /// A server side error occurred. + public async System.Threading.Tasks.Task> WalletUnspentBoxesAsync(int? minConfirmations, int? minInclusionHeight, System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/wallet/boxes/unspent?"); + if (minConfirmations != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("minConfirmations") + "=").Append(System.Uri.EscapeDataString(ConvertToString(minConfirmations, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + } + if (minInclusionHeight != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("minInclusionHeight") + "=").Append(System.Uri.EscapeDataString(ConvertToString(minInclusionHeight, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + } + urlBuilder_.Length--; + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Get summary amount of confirmed plus unconfirmed Ergo tokens and assets + /// Get summary amount of confirmed plus unconfirmed Ergo tokens and assets + /// A server side error occurred. + public System.Threading.Tasks.Task WalletBalancesUnconfirmedAsync() + { + return WalletBalancesUnconfirmedAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Get summary amount of confirmed plus unconfirmed Ergo tokens and assets + /// Get summary amount of confirmed plus unconfirmed Ergo tokens and assets + /// A server side error occurred. + public async System.Threading.Tasks.Task WalletBalancesUnconfirmedAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/wallet/balances/withUnconfirmed"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Get wallet addresses + /// String with encoded wallet addresses + /// A server side error occurred. + public System.Threading.Tasks.Task> WalletAddressesAsync() + { + return WalletAddressesAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Get wallet addresses + /// String with encoded wallet addresses + /// A server side error occurred. + public async System.Threading.Tasks.Task> WalletAddressesAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/wallet/addresses"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Generate arbitrary transaction from array of requests. + /// This API method receives a sequence of requests as an input. Each request will produce an output of the resulting transaction (with fee output created automatically). Currently supported types of requests are payment and asset issuance requests. An example for a transaction with requests of both kinds is provided below. Please note that for the payment request "assets" and "registers" fields are not needed. For asset issuance request, "registers" field is not needed. + /// You may specify boxes to spend by providing them in "inputsRaw". Please note you need to have strict equality between input and output total amounts of Ergs in this case. If you want wallet to pick up the boxes, leave "inputsRaw" empty. + /// Generated Ergo transaction + /// A server side error occurred. + public System.Threading.Tasks.Task WalletTransactionGenerateAsync(RequestsHolder body) + { + return WalletTransactionGenerateAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Generate arbitrary transaction from array of requests. + /// This API method receives a sequence of requests as an input. Each request will produce an output of the resulting transaction (with fee output created automatically). Currently supported types of requests are payment and asset issuance requests. An example for a transaction with requests of both kinds is provided below. Please note that for the payment request "assets" and "registers" fields are not needed. For asset issuance request, "registers" field is not needed. + /// You may specify boxes to spend by providing them in "inputsRaw". Please note you need to have strict equality between input and output total amounts of Ergs in this case. If you want wallet to pick up the boxes, leave "inputsRaw" empty. + /// Generated Ergo transaction + /// A server side error occurred. + public async System.Threading.Tasks.Task WalletTransactionGenerateAsync(RequestsHolder body, System.Threading.CancellationToken cancellationToken) + { + if (body == null) + throw new System.ArgumentNullException("body"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/wallet/transaction/generate"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + if (status_ == 400) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Bad transaction request", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Generate unsigned transaction from array of requests. + /// The same as /wallet/transaction/generate but generates unsigned transaction. + /// Generated unsigned Ergo transaction + /// A server side error occurred. + public System.Threading.Tasks.Task WalletUnsignedTransactionGenerateAsync(RequestsHolder body) + { + return WalletUnsignedTransactionGenerateAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Generate unsigned transaction from array of requests. + /// The same as /wallet/transaction/generate but generates unsigned transaction. + /// Generated unsigned Ergo transaction + /// A server side error occurred. + public async System.Threading.Tasks.Task WalletUnsignedTransactionGenerateAsync(RequestsHolder body, System.Threading.CancellationToken cancellationToken) + { + if (body == null) + throw new System.ArgumentNullException("body"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/wallet/transaction/generateUnsigned"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + if (status_ == 400) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Bad transaction request", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Sign arbitrary unsigned transaction with wallet secrets and also secrets provided. + /// With this API method an arbitrary unsigned transaction can be signed with secrets provided or stored in the wallet. Both DLOG and Diffie-Hellman tuple secrets are supported. + /// Please note that the unsigned transaction contains only identifiers of inputs and data inputs. If the node holds UTXO set, it is able to extract boxes needed. Otherwise, input (and data-input) boxes can be provided in "inputsRaw" and "dataInputsRaw" fields. + /// Generated Ergo transaction + /// A server side error occurred. + public System.Threading.Tasks.Task WalletTransactionSignAsync(TransactionSigningRequest body) + { + return WalletTransactionSignAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Sign arbitrary unsigned transaction with wallet secrets and also secrets provided. + /// With this API method an arbitrary unsigned transaction can be signed with secrets provided or stored in the wallet. Both DLOG and Diffie-Hellman tuple secrets are supported. + /// Please note that the unsigned transaction contains only identifiers of inputs and data inputs. If the node holds UTXO set, it is able to extract boxes needed. Otherwise, input (and data-input) boxes can be provided in "inputsRaw" and "dataInputsRaw" fields. + /// Generated Ergo transaction + /// A server side error occurred. + public async System.Threading.Tasks.Task WalletTransactionSignAsync(TransactionSigningRequest body, System.Threading.CancellationToken cancellationToken) + { + if (body == null) + throw new System.ArgumentNullException("body"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/wallet/transaction/sign"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + if (status_ == 400) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Bad transaction request", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Generate and send arbitrary transaction + /// See description of /wallet/transaction/generate + /// Identifier of an Ergo transaction generated + /// A server side error occurred. + public System.Threading.Tasks.Task WalletTransactionGenerateAndSendAsync(RequestsHolder body) + { + return WalletTransactionGenerateAndSendAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Generate and send arbitrary transaction + /// See description of /wallet/transaction/generate + /// Identifier of an Ergo transaction generated + /// A server side error occurred. + public async System.Threading.Tasks.Task WalletTransactionGenerateAndSendAsync(RequestsHolder body, System.Threading.CancellationToken cancellationToken) + { + if (body == null) + throw new System.ArgumentNullException("body"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/wallet/transaction/send"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + if (status_ == 400) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Bad transaction request", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Generate and send payment transaction (default fee of 0.001 Erg is used) + /// Identifier of an Ergo transaction generated + /// A server side error occurred. + public System.Threading.Tasks.Task WalletPaymentTransactionGenerateAndSendAsync(System.Collections.Generic.IEnumerable body) + { + return WalletPaymentTransactionGenerateAndSendAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Generate and send payment transaction (default fee of 0.001 Erg is used) + /// Identifier of an Ergo transaction generated + /// A server side error occurred. + public async System.Threading.Tasks.Task WalletPaymentTransactionGenerateAndSendAsync(System.Collections.Generic.IEnumerable body, System.Threading.CancellationToken cancellationToken) + { + if (body == null) + throw new System.ArgumentNullException("body"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/wallet/payment/send"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + if (status_ == 400) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Bad payment request", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Request block candidate + /// External candidate + /// A server side error occurred. + public System.Threading.Tasks.Task MiningRequestBlockCandidateAsync() + { + return MiningRequestBlockCandidateAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Request block candidate + /// External candidate + /// A server side error occurred. + public async System.Threading.Tasks.Task MiningRequestBlockCandidateAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/mining/candidate"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Request block candidate + /// External candidate + /// A server side error occurred. + public System.Threading.Tasks.Task MiningRequestBlockCandidateWithMandatoryTransactionsAsync(System.Collections.Generic.IEnumerable body) + { + return MiningRequestBlockCandidateWithMandatoryTransactionsAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Request block candidate + /// External candidate + /// A server side error occurred. + public async System.Threading.Tasks.Task MiningRequestBlockCandidateWithMandatoryTransactionsAsync(System.Collections.Generic.IEnumerable body, System.Threading.CancellationToken cancellationToken) + { + if (body == null) + throw new System.ArgumentNullException("body"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/mining/candidateWithTxs"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Read miner reward address + /// Miner Reward Script (in P2S format) + /// A server side error occurred. + public System.Threading.Tasks.Task MiningReadMinerRewardAddressAsync() + { + return MiningReadMinerRewardAddressAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Read miner reward address + /// Miner Reward Script (in P2S format) + /// A server side error occurred. + public async System.Threading.Tasks.Task MiningReadMinerRewardAddressAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/mining/rewardAddress"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Read public key associated with miner rewards + /// Public key for miner rewards (as hex-encoded secp256k1 point) + /// A server side error occurred. + public System.Threading.Tasks.Task MiningReadMinerRewardPubkeyAsync() + { + return MiningReadMinerRewardPubkeyAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Read public key associated with miner rewards + /// Public key for miner rewards (as hex-encoded secp256k1 point) + /// A server side error occurred. + public async System.Threading.Tasks.Task MiningReadMinerRewardPubkeyAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/mining/rewardPublicKey"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Submit solution for current candidate + /// Solution is valid + /// A server side error occurred. + public System.Threading.Tasks.Task MiningSubmitSolutionAsync(PowSolutions body) + { + return MiningSubmitSolutionAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Submit solution for current candidate + /// Solution is valid + /// A server side error occurred. + public async System.Threading.Tasks.Task MiningSubmitSolutionAsync(PowSolutions body, System.Threading.CancellationToken cancellationToken) + { + if (body == null) + throw new System.ArgumentNullException("body"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/mining/solution"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + return; + } + else + if (status_ == 400) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Solution is invalid", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Get box contents for a box by a unique identifier. + /// ID of a wanted box + /// Box object + /// A server side error occurred. + public System.Threading.Tasks.Task GetBoxByIdAsync(string boxId) + { + return GetBoxByIdAsync(boxId, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Get box contents for a box by a unique identifier. + /// ID of a wanted box + /// Box object + /// A server side error occurred. + public async System.Threading.Tasks.Task GetBoxByIdAsync(string boxId, System.Threading.CancellationToken cancellationToken) + { + if (boxId == null) + throw new System.ArgumentNullException("boxId"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/utxo/byId/{boxId}"); + urlBuilder_.Replace("{boxId}", System.Uri.EscapeDataString(ConvertToString(boxId, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + if (status_ == 404) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Box with this id doesn\'t exist", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Get serialized box from UTXO pool in Base16 encoding by an identifier. + /// ID of a wanted box + /// Json containing box identifier and hex-encoded box bytes + /// A server side error occurred. + public System.Threading.Tasks.Task GetBoxByIdBinaryAsync(string boxId) + { + return GetBoxByIdBinaryAsync(boxId, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Get serialized box from UTXO pool in Base16 encoding by an identifier. + /// ID of a wanted box + /// Json containing box identifier and hex-encoded box bytes + /// A server side error occurred. + public async System.Threading.Tasks.Task GetBoxByIdBinaryAsync(string boxId, System.Threading.CancellationToken cancellationToken) + { + if (boxId == null) + throw new System.ArgumentNullException("boxId"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/utxo/byIdBinary/{boxId}"); + urlBuilder_.Replace("{boxId}", System.Uri.EscapeDataString(ConvertToString(boxId, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + if (status_ == 404) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Box with this id doesn\'t exist", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Get box contents for a box by a unique identifier, from UTXO set and also the mempool. + /// ID of a box to obtain + /// Box object + /// A server side error occurred. + public System.Threading.Tasks.Task GetBoxWithPoolByIdAsync(string boxId) + { + return GetBoxWithPoolByIdAsync(boxId, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Get box contents for a box by a unique identifier, from UTXO set and also the mempool. + /// ID of a box to obtain + /// Box object + /// A server side error occurred. + public async System.Threading.Tasks.Task GetBoxWithPoolByIdAsync(string boxId, System.Threading.CancellationToken cancellationToken) + { + if (boxId == null) + throw new System.ArgumentNullException("boxId"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/utxo/withPool/byId/{boxId}"); + urlBuilder_.Replace("{boxId}", System.Uri.EscapeDataString(ConvertToString(boxId, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + if (status_ == 404) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Box with this id doesn\'t exist", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Get serialized box in Base16 encoding by an identifier, considering also the mempool. + /// ID of a wanted box + /// Json containing box identifier and hex-encoded box bytes + /// A server side error occurred. + public System.Threading.Tasks.Task GetBoxWithPoolByIdBinaryAsync(string boxId) + { + return GetBoxWithPoolByIdBinaryAsync(boxId, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Get serialized box in Base16 encoding by an identifier, considering also the mempool. + /// ID of a wanted box + /// Json containing box identifier and hex-encoded box bytes + /// A server side error occurred. + public async System.Threading.Tasks.Task GetBoxWithPoolByIdBinaryAsync(string boxId, System.Threading.CancellationToken cancellationToken) + { + if (boxId == null) + throw new System.ArgumentNullException("boxId"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/utxo/withPool/byIdBinary/{boxId}"); + urlBuilder_.Replace("{boxId}", System.Uri.EscapeDataString(ConvertToString(boxId, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + if (status_ == 404) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Box with this id doesn\'t exist", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Get genesis boxes (boxes existed before the very first block) + /// A list of all the genesis boxes + /// A server side error occurred. + public System.Threading.Tasks.Task> GenesisBoxesAsync() + { + return GenesisBoxesAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Get genesis boxes (boxes existed before the very first block) + /// A list of all the genesis boxes + /// A server side error occurred. + public async System.Threading.Tasks.Task> GenesisBoxesAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/utxo/genesis"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + if (status_ == 404) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Box with this id doesn\'t exist", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Create P2SAddress from Sigma source + /// Ergo address derived from source + /// A server side error occurred. + public System.Threading.Tasks.Task ScriptP2SAddressAsync(SourceHolder body) + { + return ScriptP2SAddressAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Create P2SAddress from Sigma source + /// Ergo address derived from source + /// A server side error occurred. + public async System.Threading.Tasks.Task ScriptP2SAddressAsync(SourceHolder body, System.Threading.CancellationToken cancellationToken) + { + if (body == null) + throw new System.ArgumentNullException("body"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/script/p2sAddress"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + if (status_ == 400) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Bad source", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Create P2SHAddress from Sigma source + /// Ergo address derived from source + /// A server side error occurred. + public System.Threading.Tasks.Task ScriptP2SHAddressAsync(SourceHolder body) + { + return ScriptP2SHAddressAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Create P2SHAddress from Sigma source + /// Ergo address derived from source + /// A server side error occurred. + public async System.Threading.Tasks.Task ScriptP2SHAddressAsync(SourceHolder body, System.Threading.CancellationToken cancellationToken) + { + if (body == null) + throw new System.ArgumentNullException("body"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/script/p2shAddress"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + if (status_ == 400) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Bad source", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Convert an address to hex-encoded serialized ErgoTree (script) + /// address to get a script from + /// Base16-encoded ErgoTree (script) + /// A server side error occurred. + public System.Threading.Tasks.Task AddressToTreeAsync(string address) + { + return AddressToTreeAsync(address, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Convert an address to hex-encoded serialized ErgoTree (script) + /// address to get a script from + /// Base16-encoded ErgoTree (script) + /// A server side error occurred. + public async System.Threading.Tasks.Task AddressToTreeAsync(string address, System.Threading.CancellationToken cancellationToken) + { + if (address == null) + throw new System.ArgumentNullException("address"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/script/addressToTree/{address}"); + urlBuilder_.Replace("{address}", System.Uri.EscapeDataString(ConvertToString(address, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Convert an address to hex-encoded Sigma byte array constant which contains script bytes + /// address to get a script from + /// Base16-encoded Sigma byte array constant which contains script bytes + /// A server side error occurred. + public System.Threading.Tasks.Task AddressToBytesAsync(string address) + { + return AddressToBytesAsync(address, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Convert an address to hex-encoded Sigma byte array constant which contains script bytes + /// address to get a script from + /// Base16-encoded Sigma byte array constant which contains script bytes + /// A server side error occurred. + public async System.Threading.Tasks.Task AddressToBytesAsync(string address, System.Threading.CancellationToken cancellationToken) + { + if (address == null) + throw new System.ArgumentNullException("address"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/script/addressToBytes/{address}"); + urlBuilder_.Replace("{address}", System.Uri.EscapeDataString(ConvertToString(address, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Execute script with context + /// Result of reduceToCrypto + /// A server side error occurred. + public System.Threading.Tasks.Task ExecuteWithContextAsync(ExecuteScript body) + { + return ExecuteWithContextAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Execute script with context + /// Result of reduceToCrypto + /// A server side error occurred. + public async System.Threading.Tasks.Task ExecuteWithContextAsync(ExecuteScript body, System.Threading.CancellationToken cancellationToken) + { + if (body == null) + throw new System.ArgumentNullException("body"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/script/executeWithContext"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + if (status_ == 400) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Compiler error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Register a scan + /// Identifier of a scan generated + /// A server side error occurred. + public System.Threading.Tasks.Task RegisterScanAsync(ScanRequest body) + { + return RegisterScanAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Register a scan + /// Identifier of a scan generated + /// A server side error occurred. + public async System.Threading.Tasks.Task RegisterScanAsync(ScanRequest body, System.Threading.CancellationToken cancellationToken) + { + if (body == null) + throw new System.ArgumentNullException("body"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/scan/register"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + if (status_ == 400) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Bad request", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Stop tracking and deregister scan + /// Identifier of a scan removed + /// A server side error occurred. + public System.Threading.Tasks.Task DeregisterScanAsync(ScanId body) + { + return DeregisterScanAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Stop tracking and deregister scan + /// Identifier of a scan removed + /// A server side error occurred. + public async System.Threading.Tasks.Task DeregisterScanAsync(ScanId body, System.Threading.CancellationToken cancellationToken) + { + if (body == null) + throw new System.ArgumentNullException("body"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/scan/deregister"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + if (status_ == 400) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("No scan found", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// List all the registered scans + /// List of scans registered + /// A server side error occurred. + public System.Threading.Tasks.Task> ListAllScansAsync() + { + return ListAllScansAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// List all the registered scans + /// List of scans registered + /// A server side error occurred. + public async System.Threading.Tasks.Task> ListAllScansAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/scan/listAll"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// List boxes which are not spent. + /// identifier of a scan + /// Minimal number of confirmations + /// Minimal box inclusion height + /// List of unspent boxes + /// A server side error occurred. + public System.Threading.Tasks.Task> ListUnspentScansAsync(int scanId, int? minConfirmations, int? minInclusionHeight) + { + return ListUnspentScansAsync(scanId, minConfirmations, minInclusionHeight, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// List boxes which are not spent. + /// identifier of a scan + /// Minimal number of confirmations + /// Minimal box inclusion height + /// List of unspent boxes + /// A server side error occurred. + public async System.Threading.Tasks.Task> ListUnspentScansAsync(int scanId, int? minConfirmations, int? minInclusionHeight, System.Threading.CancellationToken cancellationToken) + { + if (scanId == null) + throw new System.ArgumentNullException("scanId"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/scan/unspentBoxes/{scanId}?"); + urlBuilder_.Replace("{scanId}", System.Uri.EscapeDataString(ConvertToString(scanId, System.Globalization.CultureInfo.InvariantCulture))); + if (minConfirmations != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("minConfirmations") + "=").Append(System.Uri.EscapeDataString(ConvertToString(minConfirmations, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + } + if (minInclusionHeight != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("minInclusionHeight") + "=").Append(System.Uri.EscapeDataString(ConvertToString(minInclusionHeight, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + } + urlBuilder_.Length--; + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Stop scan-related box tracking + /// The box is not tracked anymore + /// A server side error occurred. + public System.Threading.Tasks.Task ScanStopTrackingAsync(ScanIdBoxId body) + { + return ScanStopTrackingAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Stop scan-related box tracking + /// The box is not tracked anymore + /// A server side error occurred. + public async System.Threading.Tasks.Task ScanStopTrackingAsync(ScanIdBoxId body, System.Threading.CancellationToken cancellationToken) + { + if (body == null) + throw new System.ArgumentNullException("body"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/scan/stopTracking"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Generate signature commitments for inputs of an unsigned transaction + /// Transaction-related hints + /// A server side error occurred. + public System.Threading.Tasks.Task GenerateCommitmentsAsync(GenerateCommitmentsRequest body) + { + return GenerateCommitmentsAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Generate signature commitments for inputs of an unsigned transaction + /// Transaction-related hints + /// A server side error occurred. + public async System.Threading.Tasks.Task GenerateCommitmentsAsync(GenerateCommitmentsRequest body, System.Threading.CancellationToken cancellationToken) + { + if (body == null) + throw new System.ArgumentNullException("body"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/wallet/generateCommitments"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + if (status_ == 400) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Extract hints from a transaction + /// Hints for the transaction + /// A server side error occurred. + public System.Threading.Tasks.Task ExtractHintsAsync(HintExtractionRequest body) + { + return ExtractHintsAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Extract hints from a transaction + /// Hints for the transaction + /// A server side error occurred. + public async System.Threading.Tasks.Task ExtractHintsAsync(HintExtractionRequest body, System.Threading.CancellationToken cancellationToken) + { + if (body == null) + throw new System.ArgumentNullException("body"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/wallet/extractHints"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + if (status_ == 400) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Adds a box to scans, writes box to database if it is not there. You can use scan number 10 to add a box to the wallet. + /// It the box is added successfully, then its id is returned + /// A server side error occurred. + public System.Threading.Tasks.Task AddBoxAsync(ScanIdsBox body) + { + return AddBoxAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Adds a box to scans, writes box to database if it is not there. You can use scan number 10 to add a box to the wallet. + /// It the box is added successfully, then its id is returned + /// A server side error occurred. + public async System.Threading.Tasks.Task AddBoxAsync(ScanIdsBox body, System.Threading.CancellationToken cancellationToken) + { + if (body == null) + throw new System.ArgumentNullException("body"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/scan/addBox"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Shuts down the node + /// The node will be shut down in 5 seconds + /// A server side error occurred. + public System.Threading.Tasks.Task NodeShutdownAsync() + { + return NodeShutdownAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Shuts down the node + /// The node will be shut down in 5 seconds + /// A server side error occurred. + public async System.Threading.Tasks.Task NodeShutdownAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/node/shutdown"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Content = new System.Net.Http.StringContent(string.Empty, System.Text.Encoding.UTF8, "application/json"); + request_.Method = new System.Net.Http.HttpMethod("POST"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + return; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + protected struct ObjectResponseResult + { + public ObjectResponseResult(T responseObject, string responseText) + { + this.Object = responseObject; + this.Text = responseText; + } + + public T Object { get; } + + public string Text { get; } + } + + public bool ReadResponseAsString { get; set; } + + protected virtual async System.Threading.Tasks.Task> ReadObjectResponseAsync(System.Net.Http.HttpResponseMessage response, System.Collections.Generic.IReadOnlyDictionary> headers, System.Threading.CancellationToken cancellationToken) + { + if (response == null || response.Content == null) + { + return new ObjectResponseResult(default(T)!, string.Empty); + } + + if (ReadResponseAsString) + { + var responseText = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + try + { + var typedBody = Newtonsoft.Json.JsonConvert.DeserializeObject(responseText, JsonSerializerSettings); + return new ObjectResponseResult(typedBody!, responseText); + } + catch (Newtonsoft.Json.JsonException exception) + { + var message = "Could not deserialize the response body string as " + typeof(T).FullName + "."; + throw new ApiException(message, (int)response.StatusCode, responseText, headers, exception); + } + } + else + { + try + { + using (var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) + using (var streamReader = new System.IO.StreamReader(responseStream)) + using (var jsonTextReader = new Newtonsoft.Json.JsonTextReader(streamReader)) + { + var serializer = Newtonsoft.Json.JsonSerializer.Create(JsonSerializerSettings); + var typedBody = serializer.Deserialize(jsonTextReader); + return new ObjectResponseResult(typedBody!, string.Empty); + } + } + catch (Newtonsoft.Json.JsonException exception) + { + var message = "Could not deserialize the response body stream as " + typeof(T).FullName + "."; + throw new ApiException(message, (int)response.StatusCode, string.Empty, headers, exception); + } + } + } + + private string ConvertToString(object? value, System.Globalization.CultureInfo cultureInfo) + { + if (value == null) + { + return ""; + } + + if (value is System.Enum) + { + var name = System.Enum.GetName(value.GetType(), value); + if (name != null) + { + var field = System.Reflection.IntrospectionExtensions.GetTypeInfo(value.GetType()).GetDeclaredField(name); + if (field != null) + { + var attribute = System.Reflection.CustomAttributeExtensions.GetCustomAttribute(field, typeof(System.Runtime.Serialization.EnumMemberAttribute)) + as System.Runtime.Serialization.EnumMemberAttribute; + if (attribute != null) + { + return attribute.Value != null ? attribute.Value : name; + } + } + + var converted = System.Convert.ToString(System.Convert.ChangeType(value, System.Enum.GetUnderlyingType(value.GetType()), cultureInfo)); + return converted == null ? string.Empty : converted; + } + } + else if (value is bool) + { + return System.Convert.ToString((bool)value, cultureInfo).ToLowerInvariant(); + } + else if (value is byte[]) + { + return System.Convert.ToBase64String((byte[]) value); + } + else if (value.GetType().IsArray) + { + var array = System.Linq.Enumerable.OfType((System.Array) value); + return string.Join(",", System.Linq.Enumerable.Select(array, o => ConvertToString(o, cultureInfo))); + } + + var result = System.Convert.ToString(value, cultureInfo); + return result == null ? "" : result; + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class ErgoTransactionInput + { + [Newtonsoft.Json.JsonProperty("boxId", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string BoxId { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("spendingProof", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public SpendingProof SpendingProof { get; set; } = new SpendingProof(); + + [Newtonsoft.Json.JsonProperty("extension", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.Generic.IDictionary Extension { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class ErgoTransactionDataInput + { + [Newtonsoft.Json.JsonProperty("boxId", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string BoxId { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("extension", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.Generic.IDictionary Extension { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class ErgoTransactionUnsignedInput + { + [Newtonsoft.Json.JsonProperty("boxId", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string BoxId { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("extension", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.Generic.IDictionary Extension { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + /// Spending proof for transaction input + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class SpendingProof + { + [Newtonsoft.Json.JsonProperty("proofBytes", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string ProofBytes { get; set; }= default!; + + /// Variables to be put into context + [Newtonsoft.Json.JsonProperty("extension", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.IDictionary Extension { get; set; } = new System.Collections.Generic.Dictionary(); + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class SerializedBox + { + [Newtonsoft.Json.JsonProperty("boxId", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string BoxId { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("bytes", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Bytes { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class ErgoTransactionOutput + { + [Newtonsoft.Json.JsonProperty("boxId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string BoxId { get; set; }= default!; + + /// Amount of Ergo token + [Newtonsoft.Json.JsonProperty("value", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Range(0D, double.MaxValue)] + public long Value { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("ergoTree", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string ErgoTree { get; set; }= default!; + + /// Height the output was created at + [Newtonsoft.Json.JsonProperty("creationHeight", Required = Newtonsoft.Json.Required.Always)] + public int CreationHeight { get; set; }= default!; + + /// Assets list in the transaction + [Newtonsoft.Json.JsonProperty("assets", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.Generic.ICollection Assets { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("additionalRegisters", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public Registers AdditionalRegisters { get; set; } = new Registers(); + + [Newtonsoft.Json.JsonProperty("transactionId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string TransactionId { get; set; }= default!; + + /// Index in the transaction outputs + [Newtonsoft.Json.JsonProperty("index", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public int Index { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class WalletBox + { + [Newtonsoft.Json.JsonProperty("box", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public ErgoTransactionOutput Box { get; set; } = new ErgoTransactionOutput(); + + /// Number of confirmations, if the box is included into the blockchain + [Newtonsoft.Json.JsonProperty("confirmationsNum", Required = Newtonsoft.Json.Required.AllowNull)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int? ConfirmationsNum { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("address", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Address { get; set; }= default!; + + /// Transaction which created the box + [Newtonsoft.Json.JsonProperty("creationTransaction", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string CreationTransaction { get; set; }= default!; + + /// Transaction which created the box + [Newtonsoft.Json.JsonProperty("spendingTransaction", Required = Newtonsoft.Json.Required.AllowNull)] + public string? SpendingTransaction { get; set; }= default!; + + /// The height the box was spent at + [Newtonsoft.Json.JsonProperty("spendingHeight", Required = Newtonsoft.Json.Required.AllowNull)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int? SpendingHeight { get; set; }= default!; + + /// The height the transaction containing the box was included in a block at + [Newtonsoft.Json.JsonProperty("inclusionHeight", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int InclusionHeight { get; set; }= default!; + + /// A flag signalling whether the box is created on main chain + [Newtonsoft.Json.JsonProperty("onchain", Required = Newtonsoft.Json.Required.Always)] + public bool Onchain { get; set; }= default!; + + /// A flag signalling whether the box was spent + [Newtonsoft.Json.JsonProperty("spent", Required = Newtonsoft.Json.Required.Always)] + public bool Spent { get; set; }= default!; + + /// An index of a box in the creating transaction + [Newtonsoft.Json.JsonProperty("creationOutIndex", Required = Newtonsoft.Json.Required.Always)] + public int CreationOutIndex { get; set; }= default!; + + /// Scan identifiers the box relates to + [Newtonsoft.Json.JsonProperty("scans", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.ICollection Scans { get; set; } = new System.Collections.ObjectModel.Collection(); + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + /// Unsigned Ergo transaction + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class UnsignedErgoTransaction + { + [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Id { get; set; }= default!; + + /// Unsigned inputs of the transaction + [Newtonsoft.Json.JsonProperty("inputs", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.ICollection Inputs { get; set; } = new System.Collections.ObjectModel.Collection(); + + /// Data inputs of the transaction + [Newtonsoft.Json.JsonProperty("dataInputs", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.ICollection DataInputs { get; set; } = new System.Collections.ObjectModel.Collection(); + + /// Outputs of the transaction + [Newtonsoft.Json.JsonProperty("outputs", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.ICollection Outputs { get; set; } = new System.Collections.ObjectModel.Collection(); + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + /// Ergo transaction + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class ErgoTransaction + { + [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Id { get; set; }= default!; + + /// Inputs of the transaction + [Newtonsoft.Json.JsonProperty("inputs", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.ICollection Inputs { get; set; } = new System.Collections.ObjectModel.Collection(); + + /// Data inputs of the transaction + [Newtonsoft.Json.JsonProperty("dataInputs", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.ICollection DataInputs { get; set; } = new System.Collections.ObjectModel.Collection(); + + /// Outputs of the transaction + [Newtonsoft.Json.JsonProperty("outputs", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.ICollection Outputs { get; set; } = new System.Collections.ObjectModel.Collection(); + + /// Size in bytes + [Newtonsoft.Json.JsonProperty("size", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public int Size { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + /// Transaction augmented with some useful information + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class WalletTransaction + { + [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Id { get; set; }= default!; + + /// Transaction inputs + [Newtonsoft.Json.JsonProperty("inputs", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.ICollection Inputs { get; set; } = new System.Collections.ObjectModel.Collection(); + + /// Transaction data inputs + [Newtonsoft.Json.JsonProperty("dataInputs", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.ICollection DataInputs { get; set; } = new System.Collections.ObjectModel.Collection(); + + /// Transaction outputs + [Newtonsoft.Json.JsonProperty("outputs", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.ICollection Outputs { get; set; } = new System.Collections.ObjectModel.Collection(); + + /// Height of a block the transaction was included in + [Newtonsoft.Json.JsonProperty("inclusionHeight", Required = Newtonsoft.Json.Required.Always)] + public int InclusionHeight { get; set; }= default!; + + /// Number of transaction confirmations + [Newtonsoft.Json.JsonProperty("numConfirmations", Required = Newtonsoft.Json.Required.Always)] + public int NumConfirmations { get; set; }= default!; + + /// Scan identifiers the transaction relates to + [Newtonsoft.Json.JsonProperty("scans", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.ICollection Scans { get; set; } = new System.Collections.ObjectModel.Collection(); + + /// Size in bytes + [Newtonsoft.Json.JsonProperty("size", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public int Size { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + /// Hex-encoded big-endian 256-bits secret exponent "w" along with generators "g", "h", and group elements "u", "v", such as g^w = u, h^w = v + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class DhtSecret + { + /// Hex-encoded big-endian 256-bits secret exponent + [Newtonsoft.Json.JsonProperty("secret", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Secret { get; set; }= default!; + + /// Hex-encoded "g" generator for the Diffie-Hellman tuple (secp256k1 curve point) + [Newtonsoft.Json.JsonProperty("g", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string G { get; set; }= default!; + + /// Hex-encoded "h" generator for the Diffie-Hellman tuple (secp256k1 curve point) + [Newtonsoft.Json.JsonProperty("h", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string H { get; set; }= default!; + + /// Hex-encoded "u" group element of the Diffie-Hellman tuple (secp256k1 curve point) + [Newtonsoft.Json.JsonProperty("u", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string U { get; set; }= default!; + + /// Hex-encoded "v" group element of the Diffie-Hellman tuple (secp256k1 curve point) + [Newtonsoft.Json.JsonProperty("v", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string V { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + /// A request to sign a transaction with secrets provided + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class TransactionSigningRequest + { + /// Unsigned transaction to sign + [Newtonsoft.Json.JsonProperty("tx", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public UnsignedErgoTransaction Tx { get; set; } = new UnsignedErgoTransaction(); + + /// Optional list of inputs to be used in serialized form + [Newtonsoft.Json.JsonProperty("inputsRaw", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.Generic.ICollection InputsRaw { get; set; }= default!; + + /// Optional list of inputs to be used in serialized form + [Newtonsoft.Json.JsonProperty("dataInputsRaw", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.Generic.ICollection DataInputsRaw { get; set; }= default!; + + /// Optional list of hints used for signing + [Newtonsoft.Json.JsonProperty("hints", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public TransactionHintsBag Hints { get; set; }= default!; + + /// Secrets used for signing + [Newtonsoft.Json.JsonProperty("secrets", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public Secrets Secrets { get; set; } = new Secrets(); + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + /// Holds encoded ErgoAddress + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class AddressHolder + { + [Newtonsoft.Json.JsonProperty("address", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Address { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + /// Holds request for wallet boxes + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class BoxesRequestHolder + { + /// Target assets + [Newtonsoft.Json.JsonProperty("targetAssets", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.ICollection> TargetAssets { get; set; } = new List>(); + + /// Target balance + [Newtonsoft.Json.JsonProperty("targetBalance", Required = Newtonsoft.Json.Required.Always)] + public long TargetBalance { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + /// Holds many transaction requests and transaction fee + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class RequestsHolder + { + /// Sequence of transaction requests + [Newtonsoft.Json.JsonProperty("requests", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.ICollection Requests { get; set; } = new System.Collections.ObjectModel.Collection(); + + /// Transaction fee + [Newtonsoft.Json.JsonProperty("fee", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long Fee { get; set; }= default!; + + /// List of inputs to be used in serialized form + [Newtonsoft.Json.JsonProperty("inputsRaw", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.Generic.ICollection InputsRaw { get; set; }= default!; + + /// List of data inputs to be used in serialized form + [Newtonsoft.Json.JsonProperty("dataInputsRaw", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.Generic.ICollection DataInputsRaw { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class SourceHolder + { + /// Sigma source to be compiled + [Newtonsoft.Json.JsonProperty("source", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Source { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class ErgoLikeTransaction + { + [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Id { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("inputs", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.ICollection Inputs { get; set; } = new System.Collections.ObjectModel.Collection(); + + [Newtonsoft.Json.JsonProperty("dataInputs", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.ICollection DataInputs { get; set; } = new System.Collections.ObjectModel.Collection(); + + [Newtonsoft.Json.JsonProperty("outputs", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.ICollection Outputs { get; set; } = new System.Collections.ObjectModel.Collection(); + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + /// Block header format used for sigma ErgoLikeContext + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class SigmaHeader + { + [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Id { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("timestamp", Required = Newtonsoft.Json.Required.Always)] + public int Timestamp { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("version", Required = Newtonsoft.Json.Required.Always)] + public int Version { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("adProofsRoot", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string AdProofsRoot { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("adProofsId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string AdProofsId { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("stateRoot", Required = Newtonsoft.Json.Required.Always)] + public AvlTreeData StateRoot { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("transactionsRoot", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string TransactionsRoot { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("transactionsId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string TransactionsId { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("nBits", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Range(0D, double.MaxValue)] + public long NBits { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("extensionHash", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string ExtensionHash { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("extensionRoot", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string ExtensionRoot { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("extensionId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string ExtensionId { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("height", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int Height { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("size", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int Size { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("parentId", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string ParentId { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("powSolutions", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public PowSolutions PowSolutions { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("votes", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Votes { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("minerPk", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string MinerPk { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("powOnetimePk", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string PowOnetimePk { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("powNonce", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string PowNonce { get; set; }= default!; + + /// sigma.BigInt + [Newtonsoft.Json.JsonProperty("powDistance", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public double PowDistance { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class PreHeader + { + [Newtonsoft.Json.JsonProperty("timestamp", Required = Newtonsoft.Json.Required.Always)] + public int Timestamp { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("version", Required = Newtonsoft.Json.Required.Always)] + public int Version { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("nBits", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Range(0D, double.MaxValue)] + public long NBits { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("height", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int Height { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("parentId", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string ParentId { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("votes", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Votes { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("minerPk", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string MinerPk { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class AvlTreeData + { + [Newtonsoft.Json.JsonProperty("digest", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Digest { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("treeFlags", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public int TreeFlags { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("keyLength", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public int KeyLength { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("valueLength", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public int? ValueLength { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class ErgoLikeContext + { + /// state root before current block application + [Newtonsoft.Json.JsonProperty("lastBlockUtxoRoot", Required = Newtonsoft.Json.Required.Always)] + public AvlTreeData LastBlockUtxoRoot { get; set; }= default!; + + /// fixed number of last block headers in descending order (first header is the newest one) + [Newtonsoft.Json.JsonProperty("headers", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.ICollection Headers { get; set; } = new System.Collections.ObjectModel.Collection(); + + /// fields of block header with the current `spendingTransaction`, that can be predicted by a miner before its formation + [Newtonsoft.Json.JsonProperty("preHeader", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public PreHeader PreHeader { get; set; } = new PreHeader(); + + /// boxes, that corresponds to id's of `spendingTransaction.dataInputs` + [Newtonsoft.Json.JsonProperty("dataBoxes", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.ICollection DataBoxes { get; set; } = new System.Collections.ObjectModel.Collection(); + + /// boxes, that corresponds to id's of `spendingTransaction.inputs` + [Newtonsoft.Json.JsonProperty("boxesToSpend", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.ICollection BoxesToSpend { get; set; } = new System.Collections.ObjectModel.Collection(); + + /// transaction that contains `self` box + [Newtonsoft.Json.JsonProperty("spendingTransaction", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public ErgoLikeTransaction SpendingTransaction { get; set; } = new ErgoLikeTransaction(); + + /// index of the box in `boxesToSpend` that contains the script we're evaluating + [Newtonsoft.Json.JsonProperty("selfIndex", Required = Newtonsoft.Json.Required.Always)] + public long SelfIndex { get; set; }= default!; + + /// prover-defined key-value pairs, that may be used inside a script + [Newtonsoft.Json.JsonProperty("extension", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public object Extension { get; set; } = new object(); + + /// validation parameters passed to Interpreter.verify to detect soft-fork conditions + [Newtonsoft.Json.JsonProperty("validationSettings", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string ValidationSettings { get; set; }= default!; + + /// hard limit on accumulated execution cost, if exceeded lead to CostLimitException to be thrown + [Newtonsoft.Json.JsonProperty("costLimit", Required = Newtonsoft.Json.Required.Always)] + public long CostLimit { get; set; }= default!; + + /// initial value of execution cost already accumulated before Interpreter.verify is called + [Newtonsoft.Json.JsonProperty("initCost", Required = Newtonsoft.Json.Required.Always)] + public long InitCost { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class ExecuteScript + { + /// Sigma script to be executed + [Newtonsoft.Json.JsonProperty("script", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Script { get; set; }= default!; + + /// Environment for compiler + [Newtonsoft.Json.JsonProperty("namedConstants", Required = Newtonsoft.Json.Required.AllowNull)] + public object? NamedConstants { get; set; }= default!; + + /// Interpreter context + [Newtonsoft.Json.JsonProperty("context", Required = Newtonsoft.Json.Required.AllowNull)] + public ErgoLikeContext? Context { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + /// Algebraic data type of sigma proposition expressions + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class SigmaBoolean + { + /// Sigma opCode + [Newtonsoft.Json.JsonProperty("op", Required = Newtonsoft.Json.Required.Always)] + public int Op { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("h", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string H { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("g", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string G { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("u", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string U { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("v", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string V { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("condition", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public bool Condition { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class SigmaBooleanAndPredicate : SigmaBoolean + { + [Newtonsoft.Json.JsonProperty("args", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.Generic.ICollection Args { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class SigmaBooleanOrPredicate : SigmaBoolean + { + [Newtonsoft.Json.JsonProperty("args", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.Generic.ICollection Args { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class SigmaBooleanThresholdPredicate : SigmaBoolean + { + [Newtonsoft.Json.JsonProperty("args", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.Generic.ICollection Args { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + /// Result of executeWithContext request (reduceToCrypto) + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class CryptoResult + { + /// value of SigmaProp type which represents a statement verifiable via sigma protocol + [Newtonsoft.Json.JsonProperty("value", Required = Newtonsoft.Json.Required.Always)] + public SigmaBoolean Value { get; set; }= default!; + + /// Estimated cost of contract execution + [Newtonsoft.Json.JsonProperty("cost", Required = Newtonsoft.Json.Required.Always)] + public long Cost { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class ScanningPredicate + { + [Newtonsoft.Json.JsonProperty("predicate", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Predicate { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class ContainsPredicate : ScanningPredicate + { + [Newtonsoft.Json.JsonProperty("register", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Register { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("bytes", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Bytes { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class EqualsPredicate : ScanningPredicate + { + [Newtonsoft.Json.JsonProperty("register", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Register { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("bytes", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Bytes { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class ContainsAssetPredicate : ScanningPredicate + { + [Newtonsoft.Json.JsonProperty("assetId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string AssetId { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class AndPredicate : ScanningPredicate + { + [Newtonsoft.Json.JsonProperty("args", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.Generic.ICollection Args { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class OrPredicate : ScanningPredicate + { + [Newtonsoft.Json.JsonProperty("args", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.Generic.ICollection Args { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class ScanRequest + { + [Newtonsoft.Json.JsonProperty("scanName", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string ScanName { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("trackingRule", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public ScanningPredicate TrackingRule { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class Scan + { + [Newtonsoft.Json.JsonProperty("scanName", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string ScanName { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("scanId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public int ScanId { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("trackingRule", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public ScanningPredicate TrackingRule { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class ScanId + { + [Newtonsoft.Json.JsonProperty("scanId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public int ScanId1 { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class ScanIdBoxId + { + [Newtonsoft.Json.JsonProperty("scanId", Required = Newtonsoft.Json.Required.Always)] + public int ScanId { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("boxId", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string BoxId { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + /// Ergo box with associated scans (their respective identifiers) + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class ScanIdsBox + { + [Newtonsoft.Json.JsonProperty("scanIds", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.ICollection ScanIds { get; set; } = new System.Collections.ObjectModel.Collection(); + + [Newtonsoft.Json.JsonProperty("box", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public ErgoTransactionOutput Box { get; set; } = new ErgoTransactionOutput(); + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + /// Randomness and commitment for the first step of the Schnorr protocol + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class DlogCommitment + { + /// Hex-encoded big-endian 256-bits secret exponent + [Newtonsoft.Json.JsonProperty("r", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string R { get; set; }= default!; + + /// Hex-encoded "g" generator for the Diffie-Hellman tuple (secp256k1 curve point) + [Newtonsoft.Json.JsonProperty("a", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string A { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + /// request to extract prover hints from a transaction + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class HintExtractionRequest + { + /// Transaction to extract prover hints from + [Newtonsoft.Json.JsonProperty("tx", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public ErgoTransaction Tx { get; set; } = new ErgoTransaction(); + + /// Real signers of the transaction + [Newtonsoft.Json.JsonProperty("real", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.ICollection Real { get; set; } = new System.Collections.ObjectModel.Collection(); + + /// Simulated signers of the transaction + [Newtonsoft.Json.JsonProperty("simulated", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.ICollection Simulated { get; set; } = new System.Collections.ObjectModel.Collection(); + + /// Optional list of inputs to be used in serialized form + [Newtonsoft.Json.JsonProperty("inputsRaw", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.Generic.ICollection InputsRaw { get; set; }= default!; + + /// Optional list of inputs to be used in serialized form + [Newtonsoft.Json.JsonProperty("dataInputsRaw", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.Generic.ICollection DataInputsRaw { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + /// basic trait for prover commitments + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class Commitment + { + [Newtonsoft.Json.JsonProperty("hint", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] + public CommitmentHint Hint { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("pubkey", Required = Newtonsoft.Json.Required.Always)] + public SigmaBoolean Pubkey { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("position", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Position { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("type", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] + public CommitmentType Type { get; set; }= default!; + + /// a group element of the commitment + [Newtonsoft.Json.JsonProperty("a", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string A { get; set; }= default!; + + /// b group element of the commitment (needed for DHT protocol only) + [Newtonsoft.Json.JsonProperty("b", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string B { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class SecretProven + { + [Newtonsoft.Json.JsonProperty("hint", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] + public SecretProvenHint Hint { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("challenge", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Challenge { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("pubkey", Required = Newtonsoft.Json.Required.Always)] + public SigmaBoolean Pubkey { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("proof", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Proof { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("position", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Position { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + /// hints for inputs, key is input index, values is a set of hints for the input + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class InputHints : System.Collections.Generic.Dictionary> + { + + } + + /// prover hints extracted from a transaction + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class TransactionHintsBag + { + /// Hints which contain secrets, do not share them! + [Newtonsoft.Json.JsonProperty("secretHints", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.Generic.ICollection SecretHints { get; set; }= default!; + + /// Hints which contain public data only, share them freely! + [Newtonsoft.Json.JsonProperty("publicHints", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.Generic.ICollection PublicHints { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + /// request to generate commitments to sign a transaction + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class GenerateCommitmentsRequest + { + /// Unsigned transaction to sign + [Newtonsoft.Json.JsonProperty("tx", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public UnsignedErgoTransaction Tx { get; set; } = new UnsignedErgoTransaction(); + + /// Optionally, external secrets used for signing + [Newtonsoft.Json.JsonProperty("secrets", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public Secrets2 Secrets { get; set; }= default!; + + /// Optional list of inputs to be used in serialized form + [Newtonsoft.Json.JsonProperty("inputsRaw", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.Generic.ICollection InputsRaw { get; set; }= default!; + + /// Optional list of inputs to be used in serialized form + [Newtonsoft.Json.JsonProperty("dataInputsRaw", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.Generic.ICollection DataInputsRaw { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + /// Request for generation of payment transaction to a given address + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class PaymentRequest + { + [Newtonsoft.Json.JsonProperty("address", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Address { get; set; }= default!; + + /// Payment amount + [Newtonsoft.Json.JsonProperty("value", Required = Newtonsoft.Json.Required.Always)] + public long Value { get; set; }= default!; + + /// Assets list in the transaction + [Newtonsoft.Json.JsonProperty("assets", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.Generic.ICollection Assets { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("registers", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public Registers Registers { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + /// Request for generation of asset issue transaction + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class AssetIssueRequest + { + /// Optional, first address in the wallet will be used if not defined + [Newtonsoft.Json.JsonProperty("address", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Address { get; set; }= default!; + + /// Optional, amount of ergs to be put into box with issued assets + [Newtonsoft.Json.JsonProperty("ergValue", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public int ErgValue { get; set; }= default!; + + /// Supply amount + [Newtonsoft.Json.JsonProperty("amount", Required = Newtonsoft.Json.Required.Always)] + public long Amount { get; set; }= default!; + + /// Assets name + [Newtonsoft.Json.JsonProperty("name", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Name { get; set; }= default!; + + /// Assets description + [Newtonsoft.Json.JsonProperty("description", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Description { get; set; }= default!; + + /// Number of decimal places + [Newtonsoft.Json.JsonProperty("decimals", Required = Newtonsoft.Json.Required.Always)] + public int Decimals { get; set; }= default!; + + /// Optional, possible values for registers R7...R9 + [Newtonsoft.Json.JsonProperty("registers", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public Registers Registers { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + /// Block with header and transactions + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class FullBlock + { + [Newtonsoft.Json.JsonProperty("header", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public BlockHeader Header { get; set; } = new BlockHeader(); + + [Newtonsoft.Json.JsonProperty("blockTransactions", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public BlockTransactions BlockTransactions { get; set; } = new BlockTransactions(); + + [Newtonsoft.Json.JsonProperty("adProofs", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public BlockADProofs AdProofs { get; set; } = new BlockADProofs(); + + [Newtonsoft.Json.JsonProperty("extension", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public Extension Extension { get; set; } = new Extension(); + + /// Size in bytes + [Newtonsoft.Json.JsonProperty("size", Required = Newtonsoft.Json.Required.Always)] + public int Size { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + /// An object containing all components of pow solution + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class PowSolutions + { + /// Base16-encoded public key + [Newtonsoft.Json.JsonProperty("pk", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Pk { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("w", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string W { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("n", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string N { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("d", Required = Newtonsoft.Json.Required.Always)] + public int D { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class BlockHeaderWithoutPow + { + [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Id { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("timestamp", Required = Newtonsoft.Json.Required.Always)] + public int Timestamp { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("version", Required = Newtonsoft.Json.Required.Always)] + public int Version { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("adProofsRoot", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string AdProofsRoot { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("stateRoot", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string StateRoot { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("transactionsRoot", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string TransactionsRoot { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("nBits", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Range(0D, double.MaxValue)] + public long NBits { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("extensionHash", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string ExtensionHash { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("height", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int Height { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("difficulty", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int Difficulty { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("parentId", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string ParentId { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("votes", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Votes { get; set; }= default!; + + /// Size in bytes + [Newtonsoft.Json.JsonProperty("size", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public int Size { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("extensionId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string ExtensionId { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("transactionsId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string TransactionsId { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("adProofsId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string AdProofsId { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class PopowHeader + { + [Newtonsoft.Json.JsonProperty("header", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public BlockHeader Header { get; set; } = new BlockHeader(); + + /// Array of header interlinks + [Newtonsoft.Json.JsonProperty("interlinks", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.ICollection Interlinks { get; set; } = new System.Collections.ObjectModel.Collection(); + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class NipopowProof + { + /// security parameter (min ÎĽ-level superchain length) + [Newtonsoft.Json.JsonProperty("m", Required = Newtonsoft.Json.Required.Always)] + public double M { get; set; }= default!; + + /// security parameter (min suffix length, >= 1) + [Newtonsoft.Json.JsonProperty("k", Required = Newtonsoft.Json.Required.Always)] + public double K { get; set; }= default!; + + /// proof prefix headers + [Newtonsoft.Json.JsonProperty("prefix", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.ICollection Prefix { get; set; } = new System.Collections.ObjectModel.Collection(); + + [Newtonsoft.Json.JsonProperty("suffixHead", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public PopowHeader SuffixHead { get; set; } = new PopowHeader(); + + /// tail of the proof suffix headers + [Newtonsoft.Json.JsonProperty("suffixTail", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.ICollection SuffixTail { get; set; } = new System.Collections.ObjectModel.Collection(); + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class BlockHeader + { + [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Id { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("timestamp", Required = Newtonsoft.Json.Required.Always)] + public int Timestamp { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("version", Required = Newtonsoft.Json.Required.Always)] + public int Version { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("adProofsRoot", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string AdProofsRoot { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("stateRoot", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string StateRoot { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("transactionsRoot", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string TransactionsRoot { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("nBits", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Range(0D, double.MaxValue)] + public long NBits { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("extensionHash", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string ExtensionHash { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("powSolutions", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public PowSolutions PowSolutions { get; set; } = new PowSolutions(); + + [Newtonsoft.Json.JsonProperty("height", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int Height { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("difficulty", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int Difficulty { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("parentId", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string ParentId { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("votes", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Votes { get; set; }= default!; + + /// Size in bytes + [Newtonsoft.Json.JsonProperty("size", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public int Size { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("extensionId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string ExtensionId { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("transactionsId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string TransactionsId { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("adProofsId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string AdProofsId { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class BlockTransactions + { + [Newtonsoft.Json.JsonProperty("headerId", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string HeaderId { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("transactions", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public Transactions Transactions { get; set; } = new Transactions(); + + /// Size in bytes + [Newtonsoft.Json.JsonProperty("size", Required = Newtonsoft.Json.Required.Always)] + public int Size { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class BlockADProofs + { + [Newtonsoft.Json.JsonProperty("headerId", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string HeaderId { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("proofBytes", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string ProofBytes { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("digest", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Digest { get; set; }= default!; + + /// Size in bytes + [Newtonsoft.Json.JsonProperty("size", Required = Newtonsoft.Json.Required.Always)] + public int Size { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class Extension + { + [Newtonsoft.Json.JsonProperty("headerId", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string HeaderId { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("digest", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Digest { get; set; }= default!; + + /// List of key-value records + [Newtonsoft.Json.JsonProperty("fields", Required = Newtonsoft.Json.Required.AllowNull)] + public System.Collections.Generic.ICollection? Fields { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class KeyValueItem : System.Collections.ObjectModel.Collection + { + + } + + /// Can be null if node is not mining or candidate block is not ready + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class CandidateBlock + { + [Newtonsoft.Json.JsonProperty("version", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public int Version { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("extensionHash", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string ExtensionHash { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("timestamp", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public int Timestamp { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("stateRoot", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string StateRoot { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("nBits", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + [System.ComponentModel.DataAnnotations.Range(0D, double.MaxValue)] + public long NBits { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("adProofBytes", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string AdProofBytes { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("parentId", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string ParentId { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("transactionsNumber", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public int TransactionsNumber { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("transactions", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public Transactions Transactions { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("votes", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Votes { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + /// Merkle proof for a leaf, which is an array of bytes (e.g. a transaction identifier) + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class MerkleProof + { + /// Base16-encoded Merkle tree leaf bytes + [Newtonsoft.Json.JsonProperty("leaf", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Leaf { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("levels", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.ICollection> Levels { get; set; } = new List>(); + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + /// Proof that a block corresponding to given header without PoW contains some transactions + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class ProofOfUpcomingTransactions + { + /// Base16-encoded serialized header without Proof-of-Work + [Newtonsoft.Json.JsonProperty("msgPreimage", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string MsgPreimage { get; set; }= default!; + + /// Merkle proofs of transactions included into blocks (not necessarily all the block transactions) + [Newtonsoft.Json.JsonProperty("txProofs", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.ICollection TxProofs { get; set; } = new System.Collections.ObjectModel.Collection(); + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + /// Block candidate related data for external miner to perform work + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class WorkMessage + { + /// Base16-encoded block header bytes without PoW solution + [Newtonsoft.Json.JsonProperty("msg", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Msg { get; set; }= default!; + + /// Work target value + [Newtonsoft.Json.JsonProperty("b", Required = Newtonsoft.Json.Required.Always)] + public int B { get; set; }= default!; + + /// Base16-encoded miner public key + [Newtonsoft.Json.JsonProperty("pk", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Pk { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("proof", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public ProofOfUpcomingTransactions Proof { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class Peer + { + [Newtonsoft.Json.JsonProperty("address", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Address { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("name", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string? Name { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("lastSeen", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public int LastSeen { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("connectionType", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] + public PeerConnectionType? ConnectionType { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class PeersStatus + { + [Newtonsoft.Json.JsonProperty("lastIncomingMessage", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public int LastIncomingMessage { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("currentNetworkTime", Required = Newtonsoft.Json.Required.Always)] + public int CurrentNetworkTime { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class NodeInfo + { + [Newtonsoft.Json.JsonProperty("name", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Name { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("appVersion", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string AppVersion { get; set; }= default!; + + /// Can be 'null' if state is empty (no full block is applied since node launch) + [Newtonsoft.Json.JsonProperty("fullHeight", Required = Newtonsoft.Json.Required.AllowNull)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int? FullHeight { get; set; }= default!; + + /// Can be 'null' if state is empty (no header applied since node launch) + [Newtonsoft.Json.JsonProperty("headersHeight", Required = Newtonsoft.Json.Required.AllowNull)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int? HeadersHeight { get; set; }= default!; + + /// Can be 'null' if no full block is applied since node launch + [Newtonsoft.Json.JsonProperty("bestFullHeaderId", Required = Newtonsoft.Json.Required.AllowNull)] + public string? BestFullHeaderId { get; set; }= default!; + + /// Can be 'null' if no full block is applied since node launch + [Newtonsoft.Json.JsonProperty("previousFullHeaderId", Required = Newtonsoft.Json.Required.AllowNull)] + public string? PreviousFullHeaderId { get; set; }= default!; + + /// Can be 'null' if no header applied since node launch + [Newtonsoft.Json.JsonProperty("bestHeaderId", Required = Newtonsoft.Json.Required.AllowNull)] + public string? BestHeaderId { get; set; }= default!; + + /// Can be 'null' if state is empty (no full block is applied since node launch) + [Newtonsoft.Json.JsonProperty("stateRoot", Required = Newtonsoft.Json.Required.AllowNull)] + public string? StateRoot { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("stateType", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] + public NodeInfoStateType StateType { get; set; }= default!; + + /// Can be 'null' if no full block is applied since node launch + [Newtonsoft.Json.JsonProperty("stateVersion", Required = Newtonsoft.Json.Required.AllowNull)] + public string? StateVersion { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("isMining", Required = Newtonsoft.Json.Required.Always)] + public bool IsMining { get; set; }= default!; + + /// Number of connected peers + [Newtonsoft.Json.JsonProperty("peersCount", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int PeersCount { get; set; }= default!; + + /// Current unconfirmed transactions count + [Newtonsoft.Json.JsonProperty("unconfirmedCount", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Range(0, 10000)] + public int UnconfirmedCount { get; set; }= default!; + + /// Difficulty on current bestFullHeaderId. Can be 'null' if no full block is applied since node launch + [Newtonsoft.Json.JsonProperty("difficulty", Required = Newtonsoft.Json.Required.AllowNull)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public long? Difficulty { get; set; }= default!; + + /// Current internal node time + [Newtonsoft.Json.JsonProperty("currentTime", Required = Newtonsoft.Json.Required.Always)] + public long CurrentTime { get; set; }= default!; + + /// Time when the node was started + [Newtonsoft.Json.JsonProperty("launchTime", Required = Newtonsoft.Json.Required.Always)] + public long LaunchTime { get; set; }= default!; + + /// Can be 'null' if no headers is applied since node launch + [Newtonsoft.Json.JsonProperty("headersScore", Required = Newtonsoft.Json.Required.AllowNull)] + public JToken HeadersScore { get; set; }= default!; + + /// Can be 'null' if no full block is applied since node launch + [Newtonsoft.Json.JsonProperty("fullBlocksScore", Required = Newtonsoft.Json.Required.AllowNull)] + public JToken FullBlocksScore { get; set; }= default!; + + /// Can be 'null' if genesis blocks is not produced yet + [Newtonsoft.Json.JsonProperty("genesisBlockId", Required = Newtonsoft.Json.Required.AllowNull)] + public string? GenesisBlockId { get; set; }= default!; + + /// current parameters + [Newtonsoft.Json.JsonProperty("parameters", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public Parameters Parameters { get; set; } = new Parameters(); + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class Parameters + { + /// Height when current parameters were considered(not actual height). Can be '0' if state is empty + [Newtonsoft.Json.JsonProperty("height", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int Height { get; set; }= default!; + + /// Storage fee coefficient (per byte per storage period ~4 years) + [Newtonsoft.Json.JsonProperty("storageFeeFactor", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int StorageFeeFactor { get; set; }= default!; + + /// Minimum value per byte of an output + [Newtonsoft.Json.JsonProperty("minValuePerByte", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int MinValuePerByte { get; set; }= default!; + + /// Maximum block size (in bytes) + [Newtonsoft.Json.JsonProperty("maxBlockSize", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int MaxBlockSize { get; set; }= default!; + + /// Maximum cumulative computational cost of input scripts in block transactions + [Newtonsoft.Json.JsonProperty("maxBlockCost", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int MaxBlockCost { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("blockVersion", Required = Newtonsoft.Json.Required.Always)] + public int BlockVersion { get; set; }= default!; + + /// Validation cost of a single token + [Newtonsoft.Json.JsonProperty("tokenAccessCost", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int TokenAccessCost { get; set; }= default!; + + /// Validation cost per one transaction input + [Newtonsoft.Json.JsonProperty("inputCost", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int InputCost { get; set; }= default!; + + /// Validation cost per one data input + [Newtonsoft.Json.JsonProperty("dataInputCost", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int DataInputCost { get; set; }= default!; + + /// Validation cost per one transaction output + [Newtonsoft.Json.JsonProperty("outputCost", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] + public int OutputCost { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + /// Ergo transaction objects + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class Transactions : System.Collections.ObjectModel.Collection + { + + } + + /// Fee histogram bin + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class FeeHistogramBin + { + [Newtonsoft.Json.JsonProperty("nTxns", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public int NTxns { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("totalFee", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long TotalFee { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + /// Fee histogram for transactions in mempool + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class FeeHistogram : System.Collections.ObjectModel.Collection + { + + } + + /// Token detail in the transaction + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class Asset + { + [Newtonsoft.Json.JsonProperty("tokenId", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string TokenId { get; set; }= default!; + + /// Amount of the token + [Newtonsoft.Json.JsonProperty("amount", Required = Newtonsoft.Json.Required.Always)] + public long Amount { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + /// Ergo box registers + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class Registers : System.Collections.Generic.Dictionary + { + + } + + /// Emission info for height + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class EmissionInfo + { + [Newtonsoft.Json.JsonProperty("minerReward", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long MinerReward { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("totalCoinsIssued", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long TotalCoinsIssued { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("totalRemainCoins", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long TotalRemainCoins { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + /// Amount of Ergo tokens and assets + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class BalancesSnapshot + { + [Newtonsoft.Json.JsonProperty("height", Required = Newtonsoft.Json.Required.Always)] + public int Height { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("balance", Required = Newtonsoft.Json.Required.Always)] + public long Balance { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("assets", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.Generic.ICollection Assets { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + /// Validity status of Ergo address + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class AddressValidity + { + [Newtonsoft.Json.JsonProperty("address", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Address { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("isValid", Required = Newtonsoft.Json.Required.Always)] + public bool IsValid { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("error", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Error { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class ApiError + { + /// Error code + [Newtonsoft.Json.JsonProperty("error", Required = Newtonsoft.Json.Required.Always)] + public int Error { get; set; }= default!; + + /// String error code + [Newtonsoft.Json.JsonProperty("reason", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Reason { get; set; }= default!; + + /// Detailed error description + [Newtonsoft.Json.JsonProperty("detail", Required = Newtonsoft.Json.Required.AllowNull)] + public string? Detail { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class Body + { + /// Password to encrypt wallet file with + [Newtonsoft.Json.JsonProperty("pass", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Pass { get; set; }= default!; + + /// Optional pass to password-protect mnemonic seed + [Newtonsoft.Json.JsonProperty("mnemonicPass", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string MnemonicPass { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class Body2 + { + /// Password to encrypt wallet file with + [Newtonsoft.Json.JsonProperty("pass", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Pass { get; set; }= default!; + + /// Mnemonic seed + [Newtonsoft.Json.JsonProperty("mnemonic", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Mnemonic { get; set; }= default!; + + /// Optional pass to password-protect mnemonic seed + [Newtonsoft.Json.JsonProperty("mnemonicPass", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string MnemonicPass { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class Body3 + { + /// Mnemonic seed (optional) + [Newtonsoft.Json.JsonProperty("mnemonic", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Mnemonic { get; set; }= default!; + + /// Optional pass to password-protect mnemonic seed + [Newtonsoft.Json.JsonProperty("mnemonicPass", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string MnemonicPass { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class Body4 + { + /// Password to decrypt wallet file with + [Newtonsoft.Json.JsonProperty("pass", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Pass { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class Body5 + { + /// Pay2PubKey address + [Newtonsoft.Json.JsonProperty("address", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Address { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class Body6 + { + /// Derivation path for a new secret to derive + [Newtonsoft.Json.JsonProperty("derivationPath", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string DerivationPath { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class Response + { + /// Mnemonic seed phrase + [Newtonsoft.Json.JsonProperty("mnemonic", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Mnemonic { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class Response2 + { + /// true if passphrase matches wallet, false otherwise + [Newtonsoft.Json.JsonProperty("matched", Required = Newtonsoft.Json.Required.Always)] + public bool Matched { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class Response3 + { + /// true if wallet is initialized, false otherwise + [Newtonsoft.Json.JsonProperty("isInitialized", Required = Newtonsoft.Json.Required.Always)] + public bool IsInitialized { get; set; }= default!; + + /// true if wallet is unlocked, false otherwise + [Newtonsoft.Json.JsonProperty("isUnlocked", Required = Newtonsoft.Json.Required.Always)] + public bool IsUnlocked { get; set; }= default!; + + /// address to send change to. Empty when wallet is not initialized or locked. By default change address correponds to root key address, could be set via /wallet/updateChangeAddress method. + [Newtonsoft.Json.JsonProperty("changeAddress", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string ChangeAddress { get; set; }= default!; + + /// last scanned height for the wallet (and external scans) + [Newtonsoft.Json.JsonProperty("walletHeight", Required = Newtonsoft.Json.Required.Always)] + public int WalletHeight { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class Response4 + { + [Newtonsoft.Json.JsonProperty("address", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Address { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class Response5 + { + /// Derivation path of the resulted secret + [Newtonsoft.Json.JsonProperty("derivationPath", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string DerivationPath { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("address", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Address { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class Response6 + { + [Newtonsoft.Json.JsonProperty("rewardAddress", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string RewardAddress { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class Response7 + { + [Newtonsoft.Json.JsonProperty("rewardAddress", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string RewardAddress { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class Response8 + { + /// serialized Ergo tree + [Newtonsoft.Json.JsonProperty("tree", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Tree { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class Response9 + { + /// Base16-encoded bytes + [Newtonsoft.Json.JsonProperty("tree", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Tree { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class Secrets + { + /// Sequence of secret exponents (DLOG secrets) + [Newtonsoft.Json.JsonProperty("dlog", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.Generic.ICollection Dlog { get; set; }= default!; + + /// Sequence of secret Diffie-Hellman tuple exponents (DHT secrets) + [Newtonsoft.Json.JsonProperty("dht", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.Generic.ICollection Dht { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class Requests + { + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public enum CommitmentHint + { + [System.Runtime.Serialization.EnumMember(Value = @"cmtWithSecret")] + CmtWithSecret = 0, + + [System.Runtime.Serialization.EnumMember(Value = @"cmtReal")] + CmtReal = 1, + + [System.Runtime.Serialization.EnumMember(Value = @"cmtSimulated")] + CmtSimulated = 2, + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public enum CommitmentType + { + [System.Runtime.Serialization.EnumMember(Value = @"dlog")] + Dlog = 0, + + [System.Runtime.Serialization.EnumMember(Value = @"dht")] + Dht = 1, + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public enum SecretProvenHint + { + [System.Runtime.Serialization.EnumMember(Value = @"proofReal")] + ProofReal = 0, + + [System.Runtime.Serialization.EnumMember(Value = @"proofSimulated")] + ProofSimulated = 1, + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public partial class Secrets2 + { + /// Sequence of secret exponents (DLOG secrets) + [Newtonsoft.Json.JsonProperty("dlog", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.Generic.ICollection Dlog { get; set; }= default!; + + /// Sequence of secret Diffie-Hellman tuple exponents (DHT secrets) + [Newtonsoft.Json.JsonProperty("dht", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.Generic.ICollection Dht { get; set; }= default!; + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public enum PeerConnectionType + { + [System.Runtime.Serialization.EnumMember(Value = @"Incoming")] + Incoming = 0, + + [System.Runtime.Serialization.EnumMember(Value = @"Outgoing")] + Outgoing = 1, + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] + public enum NodeInfoStateType + { + [System.Runtime.Serialization.EnumMember(Value = @"digest")] + Digest = 0, + + [System.Runtime.Serialization.EnumMember(Value = @"utxo")] + Utxo = 1, + + } + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.11.3.0 (NJsonSchema v10.4.4.0 (Newtonsoft.Json v12.0.0.0))")] + public partial class ApiException : System.Exception + { + public int StatusCode { get; private set; } + + public string? Response { get; private set; } + + public System.Collections.Generic.IReadOnlyDictionary> Headers { get; private set; } + + public ApiException(string message, int statusCode, string? response, System.Collections.Generic.IReadOnlyDictionary> headers, System.Exception? innerException) + : base(message + "\n\nStatus: " + statusCode + "\nResponse: \n" + ((response == null) ? "(null)" : response.Substring(0, response.Length >= 512 ? 512 : response.Length)), innerException) + { + StatusCode = statusCode; + Response = response; + Headers = headers; + } + + public override string ToString() + { + return string.Format("HTTP Response: \n\n{0}\n\n{1}", Response, base.ToString()); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.11.3.0 (NJsonSchema v10.4.4.0 (Newtonsoft.Json v12.0.0.0))")] + public partial class ApiException : ApiException + { + public TResult Result { get; private set; } + + public ApiException(string message, int statusCode, string? response, System.Collections.Generic.IReadOnlyDictionary> headers, TResult result, System.Exception? innerException) + : base(message, statusCode, response, headers, innerException) + { + Result = result; + } + } + +} + +#pragma warning restore 1591 +#pragma warning restore 1573 +#pragma warning restore 472 +#pragma warning restore 114 +#pragma warning restore 108 diff --git a/src/Miningcore/Blockchain/Ergo/RPC/ergo.nswag b/src/Miningcore/Blockchain/Ergo/RPC/ergo.nswag new file mode 100644 index 000000000..ca36861d9 --- /dev/null +++ b/src/Miningcore/Blockchain/Ergo/RPC/ergo.nswag @@ -0,0 +1,100 @@ +{ + "runtime": "Net50", + "defaultVariables": null, + "documentGenerator": { + "fromDocument": { + "url": "https://raw.githubusercontent.com/ergoplatform/ergo/master/src/main/resources/api/openapi.yaml", + "output": null, + "newLineBehavior": "Auto" + } + }, + "codeGenerators": { + "openApiToCSharpClient": { + "clientBaseClass": null, + "configurationClass": null, + "generateClientClasses": true, + "generateClientInterfaces": false, + "clientBaseInterface": null, + "injectHttpClient": true, + "disposeHttpClient": true, + "protectedMethods": [], + "generateExceptionClasses": true, + "exceptionClass": "ApiException", + "wrapDtoExceptions": true, + "useHttpClientCreationMethod": false, + "httpClientType": "System.Net.Http.HttpClient", + "useHttpRequestMessageCreationMethod": false, + "useBaseUrl": true, + "generateBaseUrlProperty": true, + "generateSyncMethods": false, + "generatePrepareRequestAndProcessResponseAsAsyncMethods": false, + "exposeJsonSerializerSettings": true, + "clientClassAccessModifier": "public", + "typeAccessModifier": "public", + "generateContractsOutput": false, + "contractsNamespace": null, + "contractsOutputFilePath": null, + "parameterDateTimeFormat": "s", + "parameterDateFormat": "yyyy-MM-dd", + "generateUpdateJsonSerializerSettingsMethod": true, + "useRequestAndResponseSerializationSettings": false, + "serializeTypeInformation": false, + "queryNullValue": "", + "className": "ErgoClient", + "operationGenerationMode": "MultipleClientsFromOperationId", + "additionalNamespaceUsages": [], + "additionalContractNamespaceUsages": [], + "generateOptionalParameters": false, + "generateJsonMethods": false, + "enforceFlagEnums": false, + "parameterArrayType": "System.Collections.Generic.IEnumerable", + "parameterDictionaryType": "System.Collections.Generic.IDictionary", + "responseArrayType": "System.Collections.Generic.ICollection", + "responseDictionaryType": "System.Collections.Generic.IDictionary", + "wrapResponses": false, + "wrapResponseMethods": [], + "generateResponseClasses": true, + "responseClass": "SwaggerResponse", + "namespace": "Miningcore.Blockchain.Ergo", + "requiredPropertiesMustBeDefined": true, + "dateType": "System.DateTimeOffset", + "jsonConverters": null, + "anyType": "object", + "dateTimeType": "System.DateTimeOffset", + "timeType": "System.TimeSpan", + "timeSpanType": "System.TimeSpan", + "arrayType": "System.Collections.Generic.ICollection", + "arrayInstanceType": "System.Collections.ObjectModel.Collection", + "dictionaryType": "System.Collections.Generic.IDictionary", + "dictionaryInstanceType": "System.Collections.Generic.Dictionary", + "arrayBaseType": "System.Collections.ObjectModel.Collection", + "dictionaryBaseType": "System.Collections.Generic.Dictionary", + "classStyle": "Poco", + "jsonLibrary": "NewtonsoftJson", + "generateDefaultValues": true, + "generateDataAnnotations": true, + "excludedTypeNames": [], + "excludedParameterNames": [], + "handleReferences": false, + "generateImmutableArrayProperties": false, + "generateImmutableDictionaryProperties": false, + "jsonSerializerSettingsTransformationMethod": null, + "inlineNamedArrays": false, + "inlineNamedDictionaries": false, + "inlineNamedTuples": true, + "inlineNamedAny": false, + "generateDtoTypes": true, + "generateOptionalPropertiesAsNullable": false, + "generateNullableReferenceTypes": true, + "templateDirectory": null, + "typeNameGeneratorType": null, + "propertyNameGeneratorType": null, + "enumNameGeneratorType": null, + "checksumCacheEnabled": false, + "serviceHost": null, + "serviceSchemes": null, + "output": "ErgoClient.cs", + "newLineBehavior": "Auto" + } + } +} \ No newline at end of file diff --git a/src/Miningcore/Blockchain/Share.cs b/src/Miningcore/Blockchain/Share.cs index 324937777..2154f6f58 100644 --- a/src/Miningcore/Blockchain/Share.cs +++ b/src/Miningcore/Blockchain/Share.cs @@ -4,7 +4,7 @@ namespace Miningcore.Blockchain { [ProtoContract] - public record Share + public class Share { /// /// The pool originating this share from diff --git a/src/Miningcore/Configuration/ClusterConfig.cs b/src/Miningcore/Configuration/ClusterConfig.cs index db990397c..7a5885d2c 100644 --- a/src/Miningcore/Configuration/ClusterConfig.cs +++ b/src/Miningcore/Configuration/ClusterConfig.cs @@ -7,7 +7,6 @@ using Newtonsoft.Json.Converters; using Newtonsoft.Json.Linq; -// ReSharper disable ClassNeverInstantiated.Global // ReSharper disable InconsistentNaming namespace Miningcore.Configuration @@ -27,6 +26,9 @@ public enum CoinFamily [EnumMember(Value = "ethereum")] Ethereum, + + [EnumMember(Value = "ergo")] + Ergo, } public abstract partial class CoinTemplate @@ -97,6 +99,7 @@ public abstract partial class CoinTemplate {CoinFamily.Equihash, typeof(EquihashCoinTemplate)}, {CoinFamily.Cryptonote, typeof(CryptonoteCoinTemplate)}, {CoinFamily.Ethereum, typeof(EthereumCoinTemplate)}, + {CoinFamily.Ergo, typeof(ErgoCoinTemplate)}, }; } @@ -338,6 +341,10 @@ public partial class EthereumCoinTemplate : CoinTemplate public EthereumSubfamily Subfamily { get; set; } } + public partial class ErgoCoinTemplate : CoinTemplate + { + } + #endregion // Coin Definitions public enum PayoutScheme @@ -595,6 +602,7 @@ public class ApiTlsConfig public string TlsPfxPassword { get; set; } } + public partial class ApiConfig { public bool Enabled { get; set; } diff --git a/src/Miningcore/Configuration/ClusterConfigExtensions.cs b/src/Miningcore/Configuration/ClusterConfigExtensions.cs index a36bf22c7..ab6b58e87 100644 --- a/src/Miningcore/Configuration/ClusterConfigExtensions.cs +++ b/src/Miningcore/Configuration/ClusterConfigExtensions.cs @@ -191,7 +191,19 @@ public partial class EthereumCoinTemplate public override string GetAlgorithmName() { - return "Ethash"; + return "Ethhash"; + } + + #endregion + } + + public partial class ErgoCoinTemplate + { + #region Overrides of CoinTemplate + + public override string GetAlgorithmName() + { + return "Autolykos"; } #endregion diff --git a/src/Miningcore/Crypto/Hashing/Algorithms/Blake2b.cs b/src/Miningcore/Crypto/Hashing/Algorithms/Blake2b.cs new file mode 100644 index 000000000..d1784c602 --- /dev/null +++ b/src/Miningcore/Crypto/Hashing/Algorithms/Blake2b.cs @@ -0,0 +1,22 @@ +using System; +using Miningcore.Contracts; +using Miningcore.Native; + +namespace Miningcore.Crypto.Hashing.Algorithms +{ + public unsafe class Blake2b : IHashAlgorithm + { + public void Digest(ReadOnlySpan data, Span result, params object[] extra) + { + Contract.Requires(result.Length >= 32, $"{nameof(result)} must be greater or equal 32 bytes"); + + fixed (byte* input = data) + { + fixed (byte* output = result) + { + LibMultihash.blake2b(input, output, (uint) data.Length, result.Length); + } + } + } + } +} diff --git a/src/Miningcore/Native/LibMultihash.cs b/src/Miningcore/Native/LibMultihash.cs index 6821f6d3a..9d0fa01d4 100644 --- a/src/Miningcore/Native/LibMultihash.cs +++ b/src/Miningcore/Native/LibMultihash.cs @@ -63,7 +63,10 @@ public static unsafe class LibMultihash public static extern void blake(byte* input, void* output, uint inputLength); [DllImport("libmultihash", EntryPoint = "blake2s_export", CallingConvention = CallingConvention.Cdecl)] - public static extern void blake2s(byte* input, void* output, uint inputLength); + public static extern void blake2s(byte* input, void* output, uint inputLength, int outputLength = -1); + + [DllImport("libmultihash", EntryPoint = "blake2b_export", CallingConvention = CallingConvention.Cdecl)] + public static extern void blake2b(byte* input, void* output, uint inputLength, int outputLength); [DllImport("libmultihash", EntryPoint = "dcrypt_export", CallingConvention = CallingConvention.Cdecl)] public static extern void dcrypt(byte* input, void* output, uint inputLength); diff --git a/src/Native/libmultihash/Makefile b/src/Native/libmultihash/Makefile index 534ce09bc..a96be2c26 100644 --- a/src/Native/libmultihash/Makefile +++ b/src/Native/libmultihash/Makefile @@ -4,14 +4,15 @@ LDFLAGS = -shared LDLIBS = -lsodium TARGET = libmultihash.so -OBJECTS = bcrypt.o blake.o blake2s.o c11.o dcrypt.o fresh.o lane.o \ +OBJECTS = bcrypt.o blake.o c11.o dcrypt.o fresh.o lane.o \ fugue.o groestl.o hefty1.o jh.o keccak.o neoscrypt.o exports.o nist5.o quark.o qubit.o s3.o scryptn.o \ sha3/aes_helper.o sha3/hamsi.o sha3/hamsi_helper.o sha3/sph_blake.o sha3/sph_bmw.o sha3/sph_cubehash.o \ sha3/sph_echo.o sha3/sph_fugue.o sha3/sph_groestl.o sha3/sph_hefty1.o sha3/sph_jh.o sha3/sph_keccak.o \ sha3/sph_luffa.o sha3/sph_shabal.o sha3/sph_shavite.o sha3/sph_simd.o sha3/sph_skein.o sha3/sph_whirlpool.o \ - sha3/sph_haval.o sha3/sph_sha2.o sha3/sph_sha2big.o sha3/sph_blake2s.o sha3/sm3.o sha3/panama.o \ + sha3/sph_haval.o sha3/sph_sha2.o sha3/sph_sha2big.o sha3/sm3.o sha3/panama.o \ sha3/extra.o sha3/gost_streebog.o sha3/sph_tiger.o sha3/SWIFFTX.o KeccakP-800-reference.o \ shavite3.o skein.o x11.o x13.o x15.o x17.o x16r.o x16s.o x21s.o x22i.o odocrypt.o x25x.o \ + blake2/sse/blake2s.o blake2/sse/blake2b.o \ Lyra2.o Lyra2RE.o Sponge.o geek.o \ verthash/tiny_sha3/sha3.o verthash/h2.o \ equi/util.o equi/support/cleanse.o equi/random.o \ diff --git a/src/Native/libmultihash/blake2/ref/blake2-impl.h b/src/Native/libmultihash/blake2/ref/blake2-impl.h new file mode 100644 index 000000000..c1df82e0c --- /dev/null +++ b/src/Native/libmultihash/blake2/ref/blake2-impl.h @@ -0,0 +1,160 @@ +/* + BLAKE2 reference source code package - reference C implementations + + Copyright 2012, Samuel Neves . You may use this under the + terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at + your option. The terms of these licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ +#ifndef BLAKE2_IMPL_H +#define BLAKE2_IMPL_H + +#include +#include + +#if !defined(__cplusplus) && (!defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L) + #if defined(_MSC_VER) + #define BLAKE2_INLINE __inline + #elif defined(__GNUC__) + #define BLAKE2_INLINE __inline__ + #else + #define BLAKE2_INLINE + #endif +#else + #define BLAKE2_INLINE inline +#endif + +static BLAKE2_INLINE uint32_t load32( const void *src ) +{ +#if defined(NATIVE_LITTLE_ENDIAN) + uint32_t w; + memcpy(&w, src, sizeof w); + return w; +#else + const uint8_t *p = ( const uint8_t * )src; + return (( uint32_t )( p[0] ) << 0) | + (( uint32_t )( p[1] ) << 8) | + (( uint32_t )( p[2] ) << 16) | + (( uint32_t )( p[3] ) << 24) ; +#endif +} + +static BLAKE2_INLINE uint64_t load64( const void *src ) +{ +#if defined(NATIVE_LITTLE_ENDIAN) + uint64_t w; + memcpy(&w, src, sizeof w); + return w; +#else + const uint8_t *p = ( const uint8_t * )src; + return (( uint64_t )( p[0] ) << 0) | + (( uint64_t )( p[1] ) << 8) | + (( uint64_t )( p[2] ) << 16) | + (( uint64_t )( p[3] ) << 24) | + (( uint64_t )( p[4] ) << 32) | + (( uint64_t )( p[5] ) << 40) | + (( uint64_t )( p[6] ) << 48) | + (( uint64_t )( p[7] ) << 56) ; +#endif +} + +static BLAKE2_INLINE uint16_t load16( const void *src ) +{ +#if defined(NATIVE_LITTLE_ENDIAN) + uint16_t w; + memcpy(&w, src, sizeof w); + return w; +#else + const uint8_t *p = ( const uint8_t * )src; + return ( uint16_t )((( uint32_t )( p[0] ) << 0) | + (( uint32_t )( p[1] ) << 8)); +#endif +} + +static BLAKE2_INLINE void store16( void *dst, uint16_t w ) +{ +#if defined(NATIVE_LITTLE_ENDIAN) + memcpy(dst, &w, sizeof w); +#else + uint8_t *p = ( uint8_t * )dst; + *p++ = ( uint8_t )w; w >>= 8; + *p++ = ( uint8_t )w; +#endif +} + +static BLAKE2_INLINE void store32( void *dst, uint32_t w ) +{ +#if defined(NATIVE_LITTLE_ENDIAN) + memcpy(dst, &w, sizeof w); +#else + uint8_t *p = ( uint8_t * )dst; + p[0] = (uint8_t)(w >> 0); + p[1] = (uint8_t)(w >> 8); + p[2] = (uint8_t)(w >> 16); + p[3] = (uint8_t)(w >> 24); +#endif +} + +static BLAKE2_INLINE void store64( void *dst, uint64_t w ) +{ +#if defined(NATIVE_LITTLE_ENDIAN) + memcpy(dst, &w, sizeof w); +#else + uint8_t *p = ( uint8_t * )dst; + p[0] = (uint8_t)(w >> 0); + p[1] = (uint8_t)(w >> 8); + p[2] = (uint8_t)(w >> 16); + p[3] = (uint8_t)(w >> 24); + p[4] = (uint8_t)(w >> 32); + p[5] = (uint8_t)(w >> 40); + p[6] = (uint8_t)(w >> 48); + p[7] = (uint8_t)(w >> 56); +#endif +} + +static BLAKE2_INLINE uint64_t load48( const void *src ) +{ + const uint8_t *p = ( const uint8_t * )src; + return (( uint64_t )( p[0] ) << 0) | + (( uint64_t )( p[1] ) << 8) | + (( uint64_t )( p[2] ) << 16) | + (( uint64_t )( p[3] ) << 24) | + (( uint64_t )( p[4] ) << 32) | + (( uint64_t )( p[5] ) << 40) ; +} + +static BLAKE2_INLINE void store48( void *dst, uint64_t w ) +{ + uint8_t *p = ( uint8_t * )dst; + p[0] = (uint8_t)(w >> 0); + p[1] = (uint8_t)(w >> 8); + p[2] = (uint8_t)(w >> 16); + p[3] = (uint8_t)(w >> 24); + p[4] = (uint8_t)(w >> 32); + p[5] = (uint8_t)(w >> 40); +} + +static BLAKE2_INLINE uint32_t rotr32( const uint32_t w, const unsigned c ) +{ + return ( w >> c ) | ( w << ( 32 - c ) ); +} + +static BLAKE2_INLINE uint64_t rotr64( const uint64_t w, const unsigned c ) +{ + return ( w >> c ) | ( w << ( 64 - c ) ); +} + +/* prevents compiler optimizing out memset() */ +static BLAKE2_INLINE void secure_zero_memory(void *v, size_t n) +{ + static void *(*const volatile memset_v)(void *, int, size_t) = &memset; + memset_v(v, 0, n); +} + +#endif diff --git a/src/Native/libmultihash/blake2/ref/blake2.h b/src/Native/libmultihash/blake2/ref/blake2.h new file mode 100644 index 000000000..ca390305e --- /dev/null +++ b/src/Native/libmultihash/blake2/ref/blake2.h @@ -0,0 +1,195 @@ +/* + BLAKE2 reference source code package - reference C implementations + + Copyright 2012, Samuel Neves . You may use this under the + terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at + your option. The terms of these licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ +#ifndef BLAKE2_H +#define BLAKE2_H + +#include +#include + +#if defined(_MSC_VER) +#define BLAKE2_PACKED(x) __pragma(pack(push, 1)) x __pragma(pack(pop)) +#else +#define BLAKE2_PACKED(x) x __attribute__((packed)) +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + + enum blake2s_constant + { + BLAKE2S_BLOCKBYTES = 64, + BLAKE2S_OUTBYTES = 32, + BLAKE2S_KEYBYTES = 32, + BLAKE2S_SALTBYTES = 8, + BLAKE2S_PERSONALBYTES = 8 + }; + + enum blake2b_constant + { + BLAKE2B_BLOCKBYTES = 128, + BLAKE2B_OUTBYTES = 64, + BLAKE2B_KEYBYTES = 64, + BLAKE2B_SALTBYTES = 16, + BLAKE2B_PERSONALBYTES = 16 + }; + + typedef struct blake2s_state__ + { + uint32_t h[8]; + uint32_t t[2]; + uint32_t f[2]; + uint8_t buf[BLAKE2S_BLOCKBYTES]; + size_t buflen; + size_t outlen; + uint8_t last_node; + } blake2s_state; + + typedef struct blake2b_state__ + { + uint64_t h[8]; + uint64_t t[2]; + uint64_t f[2]; + uint8_t buf[BLAKE2B_BLOCKBYTES]; + size_t buflen; + size_t outlen; + uint8_t last_node; + } blake2b_state; + + typedef struct blake2sp_state__ + { + blake2s_state S[8][1]; + blake2s_state R[1]; + uint8_t buf[8 * BLAKE2S_BLOCKBYTES]; + size_t buflen; + size_t outlen; + } blake2sp_state; + + typedef struct blake2bp_state__ + { + blake2b_state S[4][1]; + blake2b_state R[1]; + uint8_t buf[4 * BLAKE2B_BLOCKBYTES]; + size_t buflen; + size_t outlen; + } blake2bp_state; + + + BLAKE2_PACKED(struct blake2s_param__ + { + uint8_t digest_length; /* 1 */ + uint8_t key_length; /* 2 */ + uint8_t fanout; /* 3 */ + uint8_t depth; /* 4 */ + uint32_t leaf_length; /* 8 */ + uint32_t node_offset; /* 12 */ + uint16_t xof_length; /* 14 */ + uint8_t node_depth; /* 15 */ + uint8_t inner_length; /* 16 */ + /* uint8_t reserved[0]; */ + uint8_t salt[BLAKE2S_SALTBYTES]; /* 24 */ + uint8_t personal[BLAKE2S_PERSONALBYTES]; /* 32 */ + }); + + typedef struct blake2s_param__ blake2s_param; + + BLAKE2_PACKED(struct blake2b_param__ + { + uint8_t digest_length; /* 1 */ + uint8_t key_length; /* 2 */ + uint8_t fanout; /* 3 */ + uint8_t depth; /* 4 */ + uint32_t leaf_length; /* 8 */ + uint32_t node_offset; /* 12 */ + uint32_t xof_length; /* 16 */ + uint8_t node_depth; /* 17 */ + uint8_t inner_length; /* 18 */ + uint8_t reserved[14]; /* 32 */ + uint8_t salt[BLAKE2B_SALTBYTES]; /* 48 */ + uint8_t personal[BLAKE2B_PERSONALBYTES]; /* 64 */ + }); + + typedef struct blake2b_param__ blake2b_param; + + typedef struct blake2xs_state__ + { + blake2s_state S[1]; + blake2s_param P[1]; + } blake2xs_state; + + typedef struct blake2xb_state__ + { + blake2b_state S[1]; + blake2b_param P[1]; + } blake2xb_state; + + /* Padded structs result in a compile-time error */ + enum { + BLAKE2_DUMMY_1 = 1/(int)(sizeof(blake2s_param) == BLAKE2S_OUTBYTES), + BLAKE2_DUMMY_2 = 1/(int)(sizeof(blake2b_param) == BLAKE2B_OUTBYTES) + }; + + /* Streaming API */ + int blake2s_init( blake2s_state *S, size_t outlen ); + int blake2s_init_key( blake2s_state *S, size_t outlen, const void *key, size_t keylen ); + int blake2s_init_param( blake2s_state *S, const blake2s_param *P ); + int blake2s_update( blake2s_state *S, const void *in, size_t inlen ); + int blake2s_final( blake2s_state *S, void *out, size_t outlen ); + + int blake2b_init( blake2b_state *S, size_t outlen ); + int blake2b_init_key( blake2b_state *S, size_t outlen, const void *key, size_t keylen ); + int blake2b_init_param( blake2b_state *S, const blake2b_param *P ); + int blake2b_update( blake2b_state *S, const void *in, size_t inlen ); + int blake2b_final( blake2b_state *S, void *out, size_t outlen ); + + int blake2sp_init( blake2sp_state *S, size_t outlen ); + int blake2sp_init_key( blake2sp_state *S, size_t outlen, const void *key, size_t keylen ); + int blake2sp_update( blake2sp_state *S, const void *in, size_t inlen ); + int blake2sp_final( blake2sp_state *S, void *out, size_t outlen ); + + int blake2bp_init( blake2bp_state *S, size_t outlen ); + int blake2bp_init_key( blake2bp_state *S, size_t outlen, const void *key, size_t keylen ); + int blake2bp_update( blake2bp_state *S, const void *in, size_t inlen ); + int blake2bp_final( blake2bp_state *S, void *out, size_t outlen ); + + /* Variable output length API */ + int blake2xs_init( blake2xs_state *S, const size_t outlen ); + int blake2xs_init_key( blake2xs_state *S, const size_t outlen, const void *key, size_t keylen ); + int blake2xs_update( blake2xs_state *S, const void *in, size_t inlen ); + int blake2xs_final(blake2xs_state *S, void *out, size_t outlen); + + int blake2xb_init( blake2xb_state *S, const size_t outlen ); + int blake2xb_init_key( blake2xb_state *S, const size_t outlen, const void *key, size_t keylen ); + int blake2xb_update( blake2xb_state *S, const void *in, size_t inlen ); + int blake2xb_final(blake2xb_state *S, void *out, size_t outlen); + + /* Simple API */ + int blake2s( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen ); + int blake2b( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen ); + + int blake2sp( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen ); + int blake2bp( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen ); + + int blake2xs( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen ); + int blake2xb( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen ); + + /* This is simply an alias for blake2b */ + int blake2( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen ); + +#if defined(__cplusplus) +} +#endif + +#endif diff --git a/src/Native/libmultihash/blake2/ref/blake2b-ref.c b/src/Native/libmultihash/blake2/ref/blake2b-ref.c new file mode 100644 index 000000000..cd38b1ba0 --- /dev/null +++ b/src/Native/libmultihash/blake2/ref/blake2b-ref.c @@ -0,0 +1,379 @@ +/* + BLAKE2 reference source code package - reference C implementations + + Copyright 2012, Samuel Neves . You may use this under the + terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at + your option. The terms of these licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ + +#include +#include +#include + +#include "blake2.h" +#include "blake2-impl.h" + +static const uint64_t blake2b_IV[8] = +{ + 0x6a09e667f3bcc908ULL, 0xbb67ae8584caa73bULL, + 0x3c6ef372fe94f82bULL, 0xa54ff53a5f1d36f1ULL, + 0x510e527fade682d1ULL, 0x9b05688c2b3e6c1fULL, + 0x1f83d9abfb41bd6bULL, 0x5be0cd19137e2179ULL +}; + +static const uint8_t blake2b_sigma[12][16] = +{ + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 } , + { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 } , + { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 } , + { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 } , + { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 } , + { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 } , + { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 } , + { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 } , + { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 } , + { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13 , 0 } , + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 } , + { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 } +}; + + +static void blake2b_set_lastnode( blake2b_state *S ) +{ + S->f[1] = (uint64_t)-1; +} + +/* Some helper functions, not necessarily useful */ +static int blake2b_is_lastblock( const blake2b_state *S ) +{ + return S->f[0] != 0; +} + +static void blake2b_set_lastblock( blake2b_state *S ) +{ + if( S->last_node ) blake2b_set_lastnode( S ); + + S->f[0] = (uint64_t)-1; +} + +static void blake2b_increment_counter( blake2b_state *S, const uint64_t inc ) +{ + S->t[0] += inc; + S->t[1] += ( S->t[0] < inc ); +} + +static void blake2b_init0( blake2b_state *S ) +{ + size_t i; + memset( S, 0, sizeof( blake2b_state ) ); + + for( i = 0; i < 8; ++i ) S->h[i] = blake2b_IV[i]; +} + +/* init xors IV with input parameter block */ +int blake2b_init_param( blake2b_state *S, const blake2b_param *P ) +{ + const uint8_t *p = ( const uint8_t * )( P ); + size_t i; + + blake2b_init0( S ); + + /* IV XOR ParamBlock */ + for( i = 0; i < 8; ++i ) + S->h[i] ^= load64( p + sizeof( S->h[i] ) * i ); + + S->outlen = P->digest_length; + return 0; +} + + + +int blake2b_init( blake2b_state *S, size_t outlen ) +{ + blake2b_param P[1]; + + if ( ( !outlen ) || ( outlen > BLAKE2B_OUTBYTES ) ) return -1; + + P->digest_length = (uint8_t)outlen; + P->key_length = 0; + P->fanout = 1; + P->depth = 1; + store32( &P->leaf_length, 0 ); + store32( &P->node_offset, 0 ); + store32( &P->xof_length, 0 ); + P->node_depth = 0; + P->inner_length = 0; + memset( P->reserved, 0, sizeof( P->reserved ) ); + memset( P->salt, 0, sizeof( P->salt ) ); + memset( P->personal, 0, sizeof( P->personal ) ); + return blake2b_init_param( S, P ); +} + + +int blake2b_init_key( blake2b_state *S, size_t outlen, const void *key, size_t keylen ) +{ + blake2b_param P[1]; + + if ( ( !outlen ) || ( outlen > BLAKE2B_OUTBYTES ) ) return -1; + + if ( !key || !keylen || keylen > BLAKE2B_KEYBYTES ) return -1; + + P->digest_length = (uint8_t)outlen; + P->key_length = (uint8_t)keylen; + P->fanout = 1; + P->depth = 1; + store32( &P->leaf_length, 0 ); + store32( &P->node_offset, 0 ); + store32( &P->xof_length, 0 ); + P->node_depth = 0; + P->inner_length = 0; + memset( P->reserved, 0, sizeof( P->reserved ) ); + memset( P->salt, 0, sizeof( P->salt ) ); + memset( P->personal, 0, sizeof( P->personal ) ); + + if( blake2b_init_param( S, P ) < 0 ) return -1; + + { + uint8_t block[BLAKE2B_BLOCKBYTES]; + memset( block, 0, BLAKE2B_BLOCKBYTES ); + memcpy( block, key, keylen ); + blake2b_update( S, block, BLAKE2B_BLOCKBYTES ); + secure_zero_memory( block, BLAKE2B_BLOCKBYTES ); /* Burn the key from stack */ + } + return 0; +} + +#define G(r,i,a,b,c,d) \ + do { \ + a = a + b + m[blake2b_sigma[r][2*i+0]]; \ + d = rotr64(d ^ a, 32); \ + c = c + d; \ + b = rotr64(b ^ c, 24); \ + a = a + b + m[blake2b_sigma[r][2*i+1]]; \ + d = rotr64(d ^ a, 16); \ + c = c + d; \ + b = rotr64(b ^ c, 63); \ + } while(0) + +#define ROUND(r) \ + do { \ + G(r,0,v[ 0],v[ 4],v[ 8],v[12]); \ + G(r,1,v[ 1],v[ 5],v[ 9],v[13]); \ + G(r,2,v[ 2],v[ 6],v[10],v[14]); \ + G(r,3,v[ 3],v[ 7],v[11],v[15]); \ + G(r,4,v[ 0],v[ 5],v[10],v[15]); \ + G(r,5,v[ 1],v[ 6],v[11],v[12]); \ + G(r,6,v[ 2],v[ 7],v[ 8],v[13]); \ + G(r,7,v[ 3],v[ 4],v[ 9],v[14]); \ + } while(0) + +static void blake2b_compress( blake2b_state *S, const uint8_t block[BLAKE2B_BLOCKBYTES] ) +{ + uint64_t m[16]; + uint64_t v[16]; + size_t i; + + for( i = 0; i < 16; ++i ) { + m[i] = load64( block + i * sizeof( m[i] ) ); + } + + for( i = 0; i < 8; ++i ) { + v[i] = S->h[i]; + } + + v[ 8] = blake2b_IV[0]; + v[ 9] = blake2b_IV[1]; + v[10] = blake2b_IV[2]; + v[11] = blake2b_IV[3]; + v[12] = blake2b_IV[4] ^ S->t[0]; + v[13] = blake2b_IV[5] ^ S->t[1]; + v[14] = blake2b_IV[6] ^ S->f[0]; + v[15] = blake2b_IV[7] ^ S->f[1]; + + ROUND( 0 ); + ROUND( 1 ); + ROUND( 2 ); + ROUND( 3 ); + ROUND( 4 ); + ROUND( 5 ); + ROUND( 6 ); + ROUND( 7 ); + ROUND( 8 ); + ROUND( 9 ); + ROUND( 10 ); + ROUND( 11 ); + + for( i = 0; i < 8; ++i ) { + S->h[i] = S->h[i] ^ v[i] ^ v[i + 8]; + } +} + +#undef G +#undef ROUND + +int blake2b_update( blake2b_state *S, const void *pin, size_t inlen ) +{ + const unsigned char * in = (const unsigned char *)pin; + if( inlen > 0 ) + { + size_t left = S->buflen; + size_t fill = BLAKE2B_BLOCKBYTES - left; + if( inlen > fill ) + { + S->buflen = 0; + memcpy( S->buf + left, in, fill ); /* Fill buffer */ + blake2b_increment_counter( S, BLAKE2B_BLOCKBYTES ); + blake2b_compress( S, S->buf ); /* Compress */ + in += fill; inlen -= fill; + while(inlen > BLAKE2B_BLOCKBYTES) { + blake2b_increment_counter(S, BLAKE2B_BLOCKBYTES); + blake2b_compress( S, in ); + in += BLAKE2B_BLOCKBYTES; + inlen -= BLAKE2B_BLOCKBYTES; + } + } + memcpy( S->buf + S->buflen, in, inlen ); + S->buflen += inlen; + } + return 0; +} + +int blake2b_final( blake2b_state *S, void *out, size_t outlen ) +{ + uint8_t buffer[BLAKE2B_OUTBYTES] = {0}; + size_t i; + + if( out == NULL || outlen < S->outlen ) + return -1; + + if( blake2b_is_lastblock( S ) ) + return -1; + + blake2b_increment_counter( S, S->buflen ); + blake2b_set_lastblock( S ); + memset( S->buf + S->buflen, 0, BLAKE2B_BLOCKBYTES - S->buflen ); /* Padding */ + blake2b_compress( S, S->buf ); + + for( i = 0; i < 8; ++i ) /* Output full hash to temp buffer */ + store64( buffer + sizeof( S->h[i] ) * i, S->h[i] ); + + memcpy( out, buffer, S->outlen ); + secure_zero_memory(buffer, sizeof(buffer)); + return 0; +} + +/* inlen, at least, should be uint64_t. Others can be size_t. */ +int blake2b( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen ) +{ + blake2b_state S[1]; + + /* Verify parameters */ + if ( NULL == in && inlen > 0 ) return -1; + + if ( NULL == out ) return -1; + + if( NULL == key && keylen > 0 ) return -1; + + if( !outlen || outlen > BLAKE2B_OUTBYTES ) return -1; + + if( keylen > BLAKE2B_KEYBYTES ) return -1; + + if( keylen > 0 ) + { + if( blake2b_init_key( S, outlen, key, keylen ) < 0 ) return -1; + } + else + { + if( blake2b_init( S, outlen ) < 0 ) return -1; + } + + blake2b_update( S, ( const uint8_t * )in, inlen ); + blake2b_final( S, out, outlen ); + return 0; +} + +int blake2( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen ) { + return blake2b(out, outlen, in, inlen, key, keylen); +} + +#if defined(SUPERCOP) +int crypto_hash( unsigned char *out, unsigned char *in, unsigned long long inlen ) +{ + return blake2b( out, BLAKE2B_OUTBYTES, in, inlen, NULL, 0 ); +} +#endif + +#if defined(BLAKE2B_SELFTEST) +#include +#include "blake2-kat.h" +int main( void ) +{ + uint8_t key[BLAKE2B_KEYBYTES]; + uint8_t buf[BLAKE2_KAT_LENGTH]; + size_t i, step; + + for( i = 0; i < BLAKE2B_KEYBYTES; ++i ) + key[i] = ( uint8_t )i; + + for( i = 0; i < BLAKE2_KAT_LENGTH; ++i ) + buf[i] = ( uint8_t )i; + + /* Test simple API */ + for( i = 0; i < BLAKE2_KAT_LENGTH; ++i ) + { + uint8_t hash[BLAKE2B_OUTBYTES]; + blake2b( hash, BLAKE2B_OUTBYTES, buf, i, key, BLAKE2B_KEYBYTES ); + + if( 0 != memcmp( hash, blake2b_keyed_kat[i], BLAKE2B_OUTBYTES ) ) + { + goto fail; + } + } + + /* Test streaming API */ + for(step = 1; step < BLAKE2B_BLOCKBYTES; ++step) { + for (i = 0; i < BLAKE2_KAT_LENGTH; ++i) { + uint8_t hash[BLAKE2B_OUTBYTES]; + blake2b_state S; + uint8_t * p = buf; + size_t mlen = i; + int err = 0; + + if( (err = blake2b_init_key(&S, BLAKE2B_OUTBYTES, key, BLAKE2B_KEYBYTES)) < 0 ) { + goto fail; + } + + while (mlen >= step) { + if ( (err = blake2b_update(&S, p, step)) < 0 ) { + goto fail; + } + mlen -= step; + p += step; + } + if ( (err = blake2b_update(&S, p, mlen)) < 0) { + goto fail; + } + if ( (err = blake2b_final(&S, hash, BLAKE2B_OUTBYTES)) < 0) { + goto fail; + } + + if (0 != memcmp(hash, blake2b_keyed_kat[i], BLAKE2B_OUTBYTES)) { + goto fail; + } + } + } + + puts( "ok" ); + return 0; +fail: + puts("error"); + return -1; +} +#endif diff --git a/src/Native/libmultihash/blake2/ref/blake2bp-ref.c b/src/Native/libmultihash/blake2/ref/blake2bp-ref.c new file mode 100644 index 000000000..d58a15297 --- /dev/null +++ b/src/Native/libmultihash/blake2/ref/blake2bp-ref.c @@ -0,0 +1,359 @@ +/* + BLAKE2 reference source code package - reference C implementations + + Copyright 2012, Samuel Neves . You may use this under the + terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at + your option. The terms of these licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ + +#include +#include +#include +#include + +#if defined(_OPENMP) +#include +#endif + +#include "blake2.h" +#include "blake2-impl.h" + +#define PARALLELISM_DEGREE 4 + +/* + blake2b_init_param defaults to setting the expecting output length + from the digest_length parameter block field. + + In some cases, however, we do not want this, as the output length + of these instances is given by inner_length instead. +*/ +static int blake2bp_init_leaf_param( blake2b_state *S, const blake2b_param *P ) +{ + int err = blake2b_init_param(S, P); + S->outlen = P->inner_length; + return err; +} + +static int blake2bp_init_leaf( blake2b_state *S, size_t outlen, size_t keylen, uint64_t offset ) +{ + blake2b_param P[1]; + P->digest_length = (uint8_t)outlen; + P->key_length = (uint8_t)keylen; + P->fanout = PARALLELISM_DEGREE; + P->depth = 2; + store32( &P->leaf_length, 0 ); + store32( &P->node_offset, offset ); + store32( &P->xof_length, 0 ); + P->node_depth = 0; + P->inner_length = BLAKE2B_OUTBYTES; + memset( P->reserved, 0, sizeof( P->reserved ) ); + memset( P->salt, 0, sizeof( P->salt ) ); + memset( P->personal, 0, sizeof( P->personal ) ); + return blake2bp_init_leaf_param( S, P ); +} + +static int blake2bp_init_root( blake2b_state *S, size_t outlen, size_t keylen ) +{ + blake2b_param P[1]; + P->digest_length = (uint8_t)outlen; + P->key_length = (uint8_t)keylen; + P->fanout = PARALLELISM_DEGREE; + P->depth = 2; + store32( &P->leaf_length, 0 ); + store32( &P->node_offset, 0 ); + store32( &P->xof_length, 0 ); + P->node_depth = 1; + P->inner_length = BLAKE2B_OUTBYTES; + memset( P->reserved, 0, sizeof( P->reserved ) ); + memset( P->salt, 0, sizeof( P->salt ) ); + memset( P->personal, 0, sizeof( P->personal ) ); + return blake2b_init_param( S, P ); +} + + +int blake2bp_init( blake2bp_state *S, size_t outlen ) +{ + size_t i; + + if( !outlen || outlen > BLAKE2B_OUTBYTES ) return -1; + + memset( S->buf, 0, sizeof( S->buf ) ); + S->buflen = 0; + S->outlen = outlen; + + if( blake2bp_init_root( S->R, outlen, 0 ) < 0 ) + return -1; + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + if( blake2bp_init_leaf( S->S[i], outlen, 0, i ) < 0 ) return -1; + + S->R->last_node = 1; + S->S[PARALLELISM_DEGREE - 1]->last_node = 1; + return 0; +} + +int blake2bp_init_key( blake2bp_state *S, size_t outlen, const void *key, size_t keylen ) +{ + size_t i; + + if( !outlen || outlen > BLAKE2B_OUTBYTES ) return -1; + + if( !key || !keylen || keylen > BLAKE2B_KEYBYTES ) return -1; + + memset( S->buf, 0, sizeof( S->buf ) ); + S->buflen = 0; + S->outlen = outlen; + + if( blake2bp_init_root( S->R, outlen, keylen ) < 0 ) + return -1; + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + if( blake2bp_init_leaf( S->S[i], outlen, keylen, i ) < 0 ) return -1; + + S->R->last_node = 1; + S->S[PARALLELISM_DEGREE - 1]->last_node = 1; + { + uint8_t block[BLAKE2B_BLOCKBYTES]; + memset( block, 0, BLAKE2B_BLOCKBYTES ); + memcpy( block, key, keylen ); + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + blake2b_update( S->S[i], block, BLAKE2B_BLOCKBYTES ); + + secure_zero_memory( block, BLAKE2B_BLOCKBYTES ); /* Burn the key from stack */ + } + return 0; +} + + +int blake2bp_update( blake2bp_state *S, const void *pin, size_t inlen ) +{ + const unsigned char * in = (const unsigned char *)pin; + size_t left = S->buflen; + size_t fill = sizeof( S->buf ) - left; + size_t i; + + if( left && inlen >= fill ) + { + memcpy( S->buf + left, in, fill ); + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + blake2b_update( S->S[i], S->buf + i * BLAKE2B_BLOCKBYTES, BLAKE2B_BLOCKBYTES ); + + in += fill; + inlen -= fill; + left = 0; + } + +#if defined(_OPENMP) + #pragma omp parallel shared(S), num_threads(PARALLELISM_DEGREE) +#else + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) +#endif + { +#if defined(_OPENMP) + size_t i = omp_get_thread_num(); +#endif + size_t inlen__ = inlen; + const unsigned char *in__ = ( const unsigned char * )in; + in__ += i * BLAKE2B_BLOCKBYTES; + + while( inlen__ >= PARALLELISM_DEGREE * BLAKE2B_BLOCKBYTES ) + { + blake2b_update( S->S[i], in__, BLAKE2B_BLOCKBYTES ); + in__ += PARALLELISM_DEGREE * BLAKE2B_BLOCKBYTES; + inlen__ -= PARALLELISM_DEGREE * BLAKE2B_BLOCKBYTES; + } + } + + in += inlen - inlen % ( PARALLELISM_DEGREE * BLAKE2B_BLOCKBYTES ); + inlen %= PARALLELISM_DEGREE * BLAKE2B_BLOCKBYTES; + + if( inlen > 0 ) + memcpy( S->buf + left, in, inlen ); + + S->buflen = left + inlen; + return 0; +} + +int blake2bp_final( blake2bp_state *S, void *out, size_t outlen ) +{ + uint8_t hash[PARALLELISM_DEGREE][BLAKE2B_OUTBYTES]; + size_t i; + + if(out == NULL || outlen < S->outlen) { + return -1; + } + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + { + if( S->buflen > i * BLAKE2B_BLOCKBYTES ) + { + size_t left = S->buflen - i * BLAKE2B_BLOCKBYTES; + + if( left > BLAKE2B_BLOCKBYTES ) left = BLAKE2B_BLOCKBYTES; + + blake2b_update( S->S[i], S->buf + i * BLAKE2B_BLOCKBYTES, left ); + } + + blake2b_final( S->S[i], hash[i], BLAKE2B_OUTBYTES ); + } + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + blake2b_update( S->R, hash[i], BLAKE2B_OUTBYTES ); + + return blake2b_final( S->R, out, S->outlen ); +} + +int blake2bp( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen ) +{ + uint8_t hash[PARALLELISM_DEGREE][BLAKE2B_OUTBYTES]; + blake2b_state S[PARALLELISM_DEGREE][1]; + blake2b_state FS[1]; + size_t i; + + /* Verify parameters */ + if ( NULL == in && inlen > 0 ) return -1; + + if ( NULL == out ) return -1; + + if( NULL == key && keylen > 0 ) return -1; + + if( !outlen || outlen > BLAKE2B_OUTBYTES ) return -1; + + if( keylen > BLAKE2B_KEYBYTES ) return -1; + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + if( blake2bp_init_leaf( S[i], outlen, keylen, i ) < 0 ) return -1; + + S[PARALLELISM_DEGREE - 1]->last_node = 1; /* mark last node */ + + if( keylen > 0 ) + { + uint8_t block[BLAKE2B_BLOCKBYTES]; + memset( block, 0, BLAKE2B_BLOCKBYTES ); + memcpy( block, key, keylen ); + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + blake2b_update( S[i], block, BLAKE2B_BLOCKBYTES ); + + secure_zero_memory( block, BLAKE2B_BLOCKBYTES ); /* Burn the key from stack */ + } + +#if defined(_OPENMP) + #pragma omp parallel shared(S,hash), num_threads(PARALLELISM_DEGREE) +#else + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) +#endif + { +#if defined(_OPENMP) + size_t i = omp_get_thread_num(); +#endif + size_t inlen__ = inlen; + const unsigned char *in__ = ( const unsigned char * )in; + in__ += i * BLAKE2B_BLOCKBYTES; + + while( inlen__ >= PARALLELISM_DEGREE * BLAKE2B_BLOCKBYTES ) + { + blake2b_update( S[i], in__, BLAKE2B_BLOCKBYTES ); + in__ += PARALLELISM_DEGREE * BLAKE2B_BLOCKBYTES; + inlen__ -= PARALLELISM_DEGREE * BLAKE2B_BLOCKBYTES; + } + + if( inlen__ > i * BLAKE2B_BLOCKBYTES ) + { + const size_t left = inlen__ - i * BLAKE2B_BLOCKBYTES; + const size_t len = left <= BLAKE2B_BLOCKBYTES ? left : BLAKE2B_BLOCKBYTES; + blake2b_update( S[i], in__, len ); + } + + blake2b_final( S[i], hash[i], BLAKE2B_OUTBYTES ); + } + + if( blake2bp_init_root( FS, outlen, keylen ) < 0 ) + return -1; + + FS->last_node = 1; /* Mark as last node */ + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + blake2b_update( FS, hash[i], BLAKE2B_OUTBYTES ); + + return blake2b_final( FS, out, outlen );; +} + +#if defined(BLAKE2BP_SELFTEST) +#include +#include "blake2-kat.h" +int main( void ) +{ + uint8_t key[BLAKE2B_KEYBYTES]; + uint8_t buf[BLAKE2_KAT_LENGTH]; + size_t i, step; + + for( i = 0; i < BLAKE2B_KEYBYTES; ++i ) + key[i] = ( uint8_t )i; + + for( i = 0; i < BLAKE2_KAT_LENGTH; ++i ) + buf[i] = ( uint8_t )i; + + /* Test simple API */ + for( i = 0; i < BLAKE2_KAT_LENGTH; ++i ) + { + uint8_t hash[BLAKE2B_OUTBYTES]; + blake2bp( hash, BLAKE2B_OUTBYTES, buf, i, key, BLAKE2B_KEYBYTES ); + + if( 0 != memcmp( hash, blake2bp_keyed_kat[i], BLAKE2B_OUTBYTES ) ) + { + goto fail; + } + } + + /* Test streaming API */ + for(step = 1; step < BLAKE2B_BLOCKBYTES; ++step) { + for (i = 0; i < BLAKE2_KAT_LENGTH; ++i) { + uint8_t hash[BLAKE2B_OUTBYTES]; + blake2bp_state S; + uint8_t * p = buf; + size_t mlen = i; + int err = 0; + + if( (err = blake2bp_init_key(&S, BLAKE2B_OUTBYTES, key, BLAKE2B_KEYBYTES)) < 0 ) { + goto fail; + } + + while (mlen >= step) { + if ( (err = blake2bp_update(&S, p, step)) < 0 ) { + goto fail; + } + mlen -= step; + p += step; + } + if ( (err = blake2bp_update(&S, p, mlen)) < 0) { + goto fail; + } + if ( (err = blake2bp_final(&S, hash, BLAKE2B_OUTBYTES)) < 0) { + goto fail; + } + + if (0 != memcmp(hash, blake2bp_keyed_kat[i], BLAKE2B_OUTBYTES)) { + goto fail; + } + } + } + + puts( "ok" ); + return 0; +fail: + puts("error"); + return -1; +} +#endif diff --git a/src/Native/libmultihash/blake2/ref/blake2s-ref.c b/src/Native/libmultihash/blake2/ref/blake2s-ref.c new file mode 100644 index 000000000..c8b035f62 --- /dev/null +++ b/src/Native/libmultihash/blake2/ref/blake2s-ref.c @@ -0,0 +1,367 @@ +/* + BLAKE2 reference source code package - reference C implementations + + Copyright 2012, Samuel Neves . You may use this under the + terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at + your option. The terms of these licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ + +#include +#include +#include + +#include "blake2.h" +#include "blake2-impl.h" + +static const uint32_t blake2s_IV[8] = +{ + 0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL, 0xA54FF53AUL, + 0x510E527FUL, 0x9B05688CUL, 0x1F83D9ABUL, 0x5BE0CD19UL +}; + +static const uint8_t blake2s_sigma[10][16] = +{ + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 } , + { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 } , + { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 } , + { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 } , + { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 } , + { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 } , + { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 } , + { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 } , + { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 } , + { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13 , 0 } , +}; + +static void blake2s_set_lastnode( blake2s_state *S ) +{ + S->f[1] = (uint32_t)-1; +} + +/* Some helper functions, not necessarily useful */ +static int blake2s_is_lastblock( const blake2s_state *S ) +{ + return S->f[0] != 0; +} + +static void blake2s_set_lastblock( blake2s_state *S ) +{ + if( S->last_node ) blake2s_set_lastnode( S ); + + S->f[0] = (uint32_t)-1; +} + +static void blake2s_increment_counter( blake2s_state *S, const uint32_t inc ) +{ + S->t[0] += inc; + S->t[1] += ( S->t[0] < inc ); +} + +static void blake2s_init0( blake2s_state *S ) +{ + size_t i; + memset( S, 0, sizeof( blake2s_state ) ); + + for( i = 0; i < 8; ++i ) S->h[i] = blake2s_IV[i]; +} + +/* init2 xors IV with input parameter block */ +int blake2s_init_param( blake2s_state *S, const blake2s_param *P ) +{ + const unsigned char *p = ( const unsigned char * )( P ); + size_t i; + + blake2s_init0( S ); + + /* IV XOR ParamBlock */ + for( i = 0; i < 8; ++i ) + S->h[i] ^= load32( &p[i * 4] ); + + S->outlen = P->digest_length; + return 0; +} + + +/* Sequential blake2s initialization */ +int blake2s_init( blake2s_state *S, size_t outlen ) +{ + blake2s_param P[1]; + + /* Move interval verification here? */ + if ( ( !outlen ) || ( outlen > BLAKE2S_OUTBYTES ) ) return -1; + + P->digest_length = (uint8_t)outlen; + P->key_length = 0; + P->fanout = 1; + P->depth = 1; + store32( &P->leaf_length, 0 ); + store32( &P->node_offset, 0 ); + store16( &P->xof_length, 0 ); + P->node_depth = 0; + P->inner_length = 0; + /* memset(P->reserved, 0, sizeof(P->reserved) ); */ + memset( P->salt, 0, sizeof( P->salt ) ); + memset( P->personal, 0, sizeof( P->personal ) ); + return blake2s_init_param( S, P ); +} + +int blake2s_init_key( blake2s_state *S, size_t outlen, const void *key, size_t keylen ) +{ + blake2s_param P[1]; + + if ( ( !outlen ) || ( outlen > BLAKE2S_OUTBYTES ) ) return -1; + + if ( !key || !keylen || keylen > BLAKE2S_KEYBYTES ) return -1; + + P->digest_length = (uint8_t)outlen; + P->key_length = (uint8_t)keylen; + P->fanout = 1; + P->depth = 1; + store32( &P->leaf_length, 0 ); + store32( &P->node_offset, 0 ); + store16( &P->xof_length, 0 ); + P->node_depth = 0; + P->inner_length = 0; + /* memset(P->reserved, 0, sizeof(P->reserved) ); */ + memset( P->salt, 0, sizeof( P->salt ) ); + memset( P->personal, 0, sizeof( P->personal ) ); + + if( blake2s_init_param( S, P ) < 0 ) return -1; + + { + uint8_t block[BLAKE2S_BLOCKBYTES]; + memset( block, 0, BLAKE2S_BLOCKBYTES ); + memcpy( block, key, keylen ); + blake2s_update( S, block, BLAKE2S_BLOCKBYTES ); + secure_zero_memory( block, BLAKE2S_BLOCKBYTES ); /* Burn the key from stack */ + } + return 0; +} + +#define G(r,i,a,b,c,d) \ + do { \ + a = a + b + m[blake2s_sigma[r][2*i+0]]; \ + d = rotr32(d ^ a, 16); \ + c = c + d; \ + b = rotr32(b ^ c, 12); \ + a = a + b + m[blake2s_sigma[r][2*i+1]]; \ + d = rotr32(d ^ a, 8); \ + c = c + d; \ + b = rotr32(b ^ c, 7); \ + } while(0) + +#define ROUND(r) \ + do { \ + G(r,0,v[ 0],v[ 4],v[ 8],v[12]); \ + G(r,1,v[ 1],v[ 5],v[ 9],v[13]); \ + G(r,2,v[ 2],v[ 6],v[10],v[14]); \ + G(r,3,v[ 3],v[ 7],v[11],v[15]); \ + G(r,4,v[ 0],v[ 5],v[10],v[15]); \ + G(r,5,v[ 1],v[ 6],v[11],v[12]); \ + G(r,6,v[ 2],v[ 7],v[ 8],v[13]); \ + G(r,7,v[ 3],v[ 4],v[ 9],v[14]); \ + } while(0) + +static void blake2s_compress( blake2s_state *S, const uint8_t in[BLAKE2S_BLOCKBYTES] ) +{ + uint32_t m[16]; + uint32_t v[16]; + size_t i; + + for( i = 0; i < 16; ++i ) { + m[i] = load32( in + i * sizeof( m[i] ) ); + } + + for( i = 0; i < 8; ++i ) { + v[i] = S->h[i]; + } + + v[ 8] = blake2s_IV[0]; + v[ 9] = blake2s_IV[1]; + v[10] = blake2s_IV[2]; + v[11] = blake2s_IV[3]; + v[12] = S->t[0] ^ blake2s_IV[4]; + v[13] = S->t[1] ^ blake2s_IV[5]; + v[14] = S->f[0] ^ blake2s_IV[6]; + v[15] = S->f[1] ^ blake2s_IV[7]; + + ROUND( 0 ); + ROUND( 1 ); + ROUND( 2 ); + ROUND( 3 ); + ROUND( 4 ); + ROUND( 5 ); + ROUND( 6 ); + ROUND( 7 ); + ROUND( 8 ); + ROUND( 9 ); + + for( i = 0; i < 8; ++i ) { + S->h[i] = S->h[i] ^ v[i] ^ v[i + 8]; + } +} + +#undef G +#undef ROUND + +int blake2s_update( blake2s_state *S, const void *pin, size_t inlen ) +{ + const unsigned char * in = (const unsigned char *)pin; + if( inlen > 0 ) + { + size_t left = S->buflen; + size_t fill = BLAKE2S_BLOCKBYTES - left; + if( inlen > fill ) + { + S->buflen = 0; + memcpy( S->buf + left, in, fill ); /* Fill buffer */ + blake2s_increment_counter( S, BLAKE2S_BLOCKBYTES ); + blake2s_compress( S, S->buf ); /* Compress */ + in += fill; inlen -= fill; + while(inlen > BLAKE2S_BLOCKBYTES) { + blake2s_increment_counter(S, BLAKE2S_BLOCKBYTES); + blake2s_compress( S, in ); + in += BLAKE2S_BLOCKBYTES; + inlen -= BLAKE2S_BLOCKBYTES; + } + } + memcpy( S->buf + S->buflen, in, inlen ); + S->buflen += inlen; + } + return 0; +} + +int blake2s_final( blake2s_state *S, void *out, size_t outlen ) +{ + uint8_t buffer[BLAKE2S_OUTBYTES] = {0}; + size_t i; + + if( out == NULL || outlen < S->outlen ) + return -1; + + if( blake2s_is_lastblock( S ) ) + return -1; + + blake2s_increment_counter( S, ( uint32_t )S->buflen ); + blake2s_set_lastblock( S ); + memset( S->buf + S->buflen, 0, BLAKE2S_BLOCKBYTES - S->buflen ); /* Padding */ + blake2s_compress( S, S->buf ); + + for( i = 0; i < 8; ++i ) /* Output full hash to temp buffer */ + store32( buffer + sizeof( S->h[i] ) * i, S->h[i] ); + + memcpy( out, buffer, outlen ); + secure_zero_memory(buffer, sizeof(buffer)); + return 0; +} + +int blake2s( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen ) +{ + blake2s_state S[1]; + + /* Verify parameters */ + if ( NULL == in && inlen > 0 ) return -1; + + if ( NULL == out ) return -1; + + if ( NULL == key && keylen > 0) return -1; + + if( !outlen || outlen > BLAKE2S_OUTBYTES ) return -1; + + if( keylen > BLAKE2S_KEYBYTES ) return -1; + + if( keylen > 0 ) + { + if( blake2s_init_key( S, outlen, key, keylen ) < 0 ) return -1; + } + else + { + if( blake2s_init( S, outlen ) < 0 ) return -1; + } + + blake2s_update( S, ( const uint8_t * )in, inlen ); + blake2s_final( S, out, outlen ); + return 0; +} + +#if defined(SUPERCOP) +int crypto_hash( unsigned char *out, unsigned char *in, unsigned long long inlen ) +{ + return blake2s( out, BLAKE2S_OUTBYTES, in, inlen, NULL, 0 ); +} +#endif + +#if defined(BLAKE2S_SELFTEST) +#include +#include "blake2-kat.h" +int main( void ) +{ + uint8_t key[BLAKE2S_KEYBYTES]; + uint8_t buf[BLAKE2_KAT_LENGTH]; + size_t i, step; + + for( i = 0; i < BLAKE2S_KEYBYTES; ++i ) + key[i] = ( uint8_t )i; + + for( i = 0; i < BLAKE2_KAT_LENGTH; ++i ) + buf[i] = ( uint8_t )i; + + /* Test simple API */ + for( i = 0; i < BLAKE2_KAT_LENGTH; ++i ) + { + uint8_t hash[BLAKE2S_OUTBYTES]; + blake2s( hash, BLAKE2S_OUTBYTES, buf, i, key, BLAKE2S_KEYBYTES ); + + if( 0 != memcmp( hash, blake2s_keyed_kat[i], BLAKE2S_OUTBYTES ) ) + { + goto fail; + } + } + + /* Test streaming API */ + for(step = 1; step < BLAKE2S_BLOCKBYTES; ++step) { + for (i = 0; i < BLAKE2_KAT_LENGTH; ++i) { + uint8_t hash[BLAKE2S_OUTBYTES]; + blake2s_state S; + uint8_t * p = buf; + size_t mlen = i; + int err = 0; + + if( (err = blake2s_init_key(&S, BLAKE2S_OUTBYTES, key, BLAKE2S_KEYBYTES)) < 0 ) { + goto fail; + } + + while (mlen >= step) { + if ( (err = blake2s_update(&S, p, step)) < 0 ) { + goto fail; + } + mlen -= step; + p += step; + } + if ( (err = blake2s_update(&S, p, mlen)) < 0) { + goto fail; + } + if ( (err = blake2s_final(&S, hash, BLAKE2S_OUTBYTES)) < 0) { + goto fail; + } + + if (0 != memcmp(hash, blake2s_keyed_kat[i], BLAKE2S_OUTBYTES)) { + goto fail; + } + } + } + + puts( "ok" ); + return 0; +fail: + puts("error"); + return -1; +} +#endif diff --git a/src/Native/libmultihash/blake2/ref/blake2sp-ref.c b/src/Native/libmultihash/blake2/ref/blake2sp-ref.c new file mode 100644 index 000000000..b0e9baef7 --- /dev/null +++ b/src/Native/libmultihash/blake2/ref/blake2sp-ref.c @@ -0,0 +1,359 @@ +/* + BLAKE2 reference source code package - reference C implementations + + Copyright 2012, Samuel Neves . You may use this under the + terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at + your option. The terms of these licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ + +#include +#include +#include + +#if defined(_OPENMP) +#include +#endif + +#include "blake2.h" +#include "blake2-impl.h" + +#define PARALLELISM_DEGREE 8 + +/* + blake2sp_init_param defaults to setting the expecting output length + from the digest_length parameter block field. + + In some cases, however, we do not want this, as the output length + of these instances is given by inner_length instead. +*/ +static int blake2sp_init_leaf_param( blake2s_state *S, const blake2s_param *P ) +{ + int err = blake2s_init_param(S, P); + S->outlen = P->inner_length; + return err; +} + +static int blake2sp_init_leaf( blake2s_state *S, size_t outlen, size_t keylen, uint64_t offset ) +{ + blake2s_param P[1]; + P->digest_length = (uint8_t)outlen; + P->key_length = (uint8_t)keylen; + P->fanout = PARALLELISM_DEGREE; + P->depth = 2; + store32( &P->leaf_length, 0 ); + store32( &P->node_offset, offset ); + store16( &P->xof_length, 0 ); + P->node_depth = 0; + P->inner_length = BLAKE2S_OUTBYTES; + memset( P->salt, 0, sizeof( P->salt ) ); + memset( P->personal, 0, sizeof( P->personal ) ); + return blake2sp_init_leaf_param( S, P ); +} + +static int blake2sp_init_root( blake2s_state *S, size_t outlen, size_t keylen ) +{ + blake2s_param P[1]; + P->digest_length = (uint8_t)outlen; + P->key_length = (uint8_t)keylen; + P->fanout = PARALLELISM_DEGREE; + P->depth = 2; + store32( &P->leaf_length, 0 ); + store32( &P->node_offset, 0 ); + store16( &P->xof_length, 0 ); + P->node_depth = 1; + P->inner_length = BLAKE2S_OUTBYTES; + memset( P->salt, 0, sizeof( P->salt ) ); + memset( P->personal, 0, sizeof( P->personal ) ); + return blake2s_init_param( S, P ); +} + + +int blake2sp_init( blake2sp_state *S, size_t outlen ) +{ + size_t i; + + if( !outlen || outlen > BLAKE2S_OUTBYTES ) return -1; + + memset( S->buf, 0, sizeof( S->buf ) ); + S->buflen = 0; + S->outlen = outlen; + + if( blake2sp_init_root( S->R, outlen, 0 ) < 0 ) + return -1; + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + if( blake2sp_init_leaf( S->S[i], outlen, 0, i ) < 0 ) return -1; + + S->R->last_node = 1; + S->S[PARALLELISM_DEGREE - 1]->last_node = 1; + return 0; +} + +int blake2sp_init_key( blake2sp_state *S, size_t outlen, const void *key, size_t keylen ) +{ + size_t i; + + if( !outlen || outlen > BLAKE2S_OUTBYTES ) return -1; + + if( !key || !keylen || keylen > BLAKE2S_KEYBYTES ) return -1; + + memset( S->buf, 0, sizeof( S->buf ) ); + S->buflen = 0; + S->outlen = outlen; + + if( blake2sp_init_root( S->R, outlen, keylen ) < 0 ) + return -1; + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + if( blake2sp_init_leaf( S->S[i], outlen, keylen, i ) < 0 ) return -1; + + S->R->last_node = 1; + S->S[PARALLELISM_DEGREE - 1]->last_node = 1; + { + uint8_t block[BLAKE2S_BLOCKBYTES]; + memset( block, 0, BLAKE2S_BLOCKBYTES ); + memcpy( block, key, keylen ); + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + blake2s_update( S->S[i], block, BLAKE2S_BLOCKBYTES ); + + secure_zero_memory( block, BLAKE2S_BLOCKBYTES ); /* Burn the key from stack */ + } + return 0; +} + + +int blake2sp_update( blake2sp_state *S, const void *pin, size_t inlen ) +{ + const unsigned char * in = (const unsigned char *)pin; + size_t left = S->buflen; + size_t fill = sizeof( S->buf ) - left; + size_t i; + + if( left && inlen >= fill ) + { + memcpy( S->buf + left, in, fill ); + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + blake2s_update( S->S[i], S->buf + i * BLAKE2S_BLOCKBYTES, BLAKE2S_BLOCKBYTES ); + + in += fill; + inlen -= fill; + left = 0; + } + +#if defined(_OPENMP) + #pragma omp parallel shared(S), num_threads(PARALLELISM_DEGREE) +#else + for( i = 0; i < PARALLELISM_DEGREE; ++i ) +#endif + { +#if defined(_OPENMP) + size_t i = omp_get_thread_num(); +#endif + size_t inlen__ = inlen; + const unsigned char *in__ = ( const unsigned char * )in; + in__ += i * BLAKE2S_BLOCKBYTES; + + while( inlen__ >= PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES ) + { + blake2s_update( S->S[i], in__, BLAKE2S_BLOCKBYTES ); + in__ += PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES; + inlen__ -= PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES; + } + } + + in += inlen - inlen % ( PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES ); + inlen %= PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES; + + if( inlen > 0 ) + memcpy( S->buf + left, in, inlen ); + + S->buflen = left + inlen; + return 0; +} + + +int blake2sp_final( blake2sp_state *S, void *out, size_t outlen ) +{ + uint8_t hash[PARALLELISM_DEGREE][BLAKE2S_OUTBYTES]; + size_t i; + + if(out == NULL || outlen < S->outlen) { + return -1; + } + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + { + if( S->buflen > i * BLAKE2S_BLOCKBYTES ) + { + size_t left = S->buflen - i * BLAKE2S_BLOCKBYTES; + + if( left > BLAKE2S_BLOCKBYTES ) left = BLAKE2S_BLOCKBYTES; + + blake2s_update( S->S[i], S->buf + i * BLAKE2S_BLOCKBYTES, left ); + } + + blake2s_final( S->S[i], hash[i], BLAKE2S_OUTBYTES ); + } + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + blake2s_update( S->R, hash[i], BLAKE2S_OUTBYTES ); + + return blake2s_final( S->R, out, S->outlen ); +} + + +int blake2sp( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen ) +{ + uint8_t hash[PARALLELISM_DEGREE][BLAKE2S_OUTBYTES]; + blake2s_state S[PARALLELISM_DEGREE][1]; + blake2s_state FS[1]; + size_t i; + + /* Verify parameters */ + if ( NULL == in && inlen > 0 ) return -1; + + if ( NULL == out ) return -1; + + if ( NULL == key && keylen > 0) return -1; + + if( !outlen || outlen > BLAKE2S_OUTBYTES ) return -1; + + if( keylen > BLAKE2S_KEYBYTES ) return -1; + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + if( blake2sp_init_leaf( S[i], outlen, keylen, i ) < 0 ) return -1; + + S[PARALLELISM_DEGREE - 1]->last_node = 1; /* mark last node */ + + if( keylen > 0 ) + { + uint8_t block[BLAKE2S_BLOCKBYTES]; + memset( block, 0, BLAKE2S_BLOCKBYTES ); + memcpy( block, key, keylen ); + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + blake2s_update( S[i], block, BLAKE2S_BLOCKBYTES ); + + secure_zero_memory( block, BLAKE2S_BLOCKBYTES ); /* Burn the key from stack */ + } + +#if defined(_OPENMP) + #pragma omp parallel shared(S,hash), num_threads(PARALLELISM_DEGREE) +#else + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) +#endif + { +#if defined(_OPENMP) + size_t i = omp_get_thread_num(); +#endif + size_t inlen__ = inlen; + const unsigned char *in__ = ( const unsigned char * )in; + in__ += i * BLAKE2S_BLOCKBYTES; + + while( inlen__ >= PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES ) + { + blake2s_update( S[i], in__, BLAKE2S_BLOCKBYTES ); + in__ += PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES; + inlen__ -= PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES; + } + + if( inlen__ > i * BLAKE2S_BLOCKBYTES ) + { + const size_t left = inlen__ - i * BLAKE2S_BLOCKBYTES; + const size_t len = left <= BLAKE2S_BLOCKBYTES ? left : BLAKE2S_BLOCKBYTES; + blake2s_update( S[i], in__, len ); + } + + blake2s_final( S[i], hash[i], BLAKE2S_OUTBYTES ); + } + + if( blake2sp_init_root( FS, outlen, keylen ) < 0 ) + return -1; + + FS->last_node = 1; + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + blake2s_update( FS, hash[i], BLAKE2S_OUTBYTES ); + + return blake2s_final( FS, out, outlen ); +} + + + +#if defined(BLAKE2SP_SELFTEST) +#include +#include "blake2-kat.h" +int main( void ) +{ + uint8_t key[BLAKE2S_KEYBYTES]; + uint8_t buf[BLAKE2_KAT_LENGTH]; + size_t i, step; + + for( i = 0; i < BLAKE2S_KEYBYTES; ++i ) + key[i] = ( uint8_t )i; + + for( i = 0; i < BLAKE2_KAT_LENGTH; ++i ) + buf[i] = ( uint8_t )i; + + /* Test simple API */ + for( i = 0; i < BLAKE2_KAT_LENGTH; ++i ) + { + uint8_t hash[BLAKE2S_OUTBYTES]; + blake2sp( hash, BLAKE2S_OUTBYTES, buf, i, key, BLAKE2S_KEYBYTES ); + + if( 0 != memcmp( hash, blake2sp_keyed_kat[i], BLAKE2S_OUTBYTES ) ) + { + goto fail; + } + } + + /* Test streaming API */ + for(step = 1; step < BLAKE2S_BLOCKBYTES; ++step) { + for (i = 0; i < BLAKE2_KAT_LENGTH; ++i) { + uint8_t hash[BLAKE2S_OUTBYTES]; + blake2sp_state S; + uint8_t * p = buf; + size_t mlen = i; + int err = 0; + + if( (err = blake2sp_init_key(&S, BLAKE2S_OUTBYTES, key, BLAKE2S_KEYBYTES)) < 0 ) { + goto fail; + } + + while (mlen >= step) { + if ( (err = blake2sp_update(&S, p, step)) < 0 ) { + goto fail; + } + mlen -= step; + p += step; + } + if ( (err = blake2sp_update(&S, p, mlen)) < 0) { + goto fail; + } + if ( (err = blake2sp_final(&S, hash, BLAKE2S_OUTBYTES)) < 0) { + goto fail; + } + + if (0 != memcmp(hash, blake2sp_keyed_kat[i], BLAKE2S_OUTBYTES)) { + goto fail; + } + } + } + + puts( "ok" ); + return 0; +fail: + puts("error"); + return -1; +} +#endif diff --git a/src/Native/libmultihash/blake2/ref/blake2xb-ref.c b/src/Native/libmultihash/blake2/ref/blake2xb-ref.c new file mode 100644 index 000000000..b369ee7ce --- /dev/null +++ b/src/Native/libmultihash/blake2/ref/blake2xb-ref.c @@ -0,0 +1,241 @@ +/* + BLAKE2 reference source code package - reference C implementations + + Copyright 2016, JP Aumasson . + Copyright 2016, Samuel Neves . + + You may use this under the terms of the CC0, the OpenSSL Licence, or + the Apache Public License 2.0, at your option. The terms of these + licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ + +#include +#include +#include + +#include "blake2.h" +#include "blake2-impl.h" + +int blake2xb_init( blake2xb_state *S, const size_t outlen ) { + return blake2xb_init_key(S, outlen, NULL, 0); +} + +int blake2xb_init_key( blake2xb_state *S, const size_t outlen, const void *key, size_t keylen) +{ + if ( outlen == 0 || outlen > 0xFFFFFFFFUL ) { + return -1; + } + + if (NULL != key && keylen > BLAKE2B_KEYBYTES) { + return -1; + } + + if (NULL == key && keylen > 0) { + return -1; + } + + /* Initialize parameter block */ + S->P->digest_length = BLAKE2B_OUTBYTES; + S->P->key_length = keylen; + S->P->fanout = 1; + S->P->depth = 1; + store32( &S->P->leaf_length, 0 ); + store32( &S->P->node_offset, 0 ); + store32( &S->P->xof_length, outlen ); + S->P->node_depth = 0; + S->P->inner_length = 0; + memset( S->P->reserved, 0, sizeof( S->P->reserved ) ); + memset( S->P->salt, 0, sizeof( S->P->salt ) ); + memset( S->P->personal, 0, sizeof( S->P->personal ) ); + + if( blake2b_init_param( S->S, S->P ) < 0 ) { + return -1; + } + + if (keylen > 0) { + uint8_t block[BLAKE2B_BLOCKBYTES]; + memset(block, 0, BLAKE2B_BLOCKBYTES); + memcpy(block, key, keylen); + blake2b_update(S->S, block, BLAKE2B_BLOCKBYTES); + secure_zero_memory(block, BLAKE2B_BLOCKBYTES); + } + return 0; +} + +int blake2xb_update( blake2xb_state *S, const void *in, size_t inlen ) { + return blake2b_update( S->S, in, inlen ); +} + +int blake2xb_final( blake2xb_state *S, void *out, size_t outlen) { + + blake2b_state C[1]; + blake2b_param P[1]; + uint32_t xof_length = load32(&S->P->xof_length); + uint8_t root[BLAKE2B_BLOCKBYTES]; + size_t i; + + if (NULL == out) { + return -1; + } + + /* outlen must match the output size defined in xof_length, */ + /* unless it was -1, in which case anything goes except 0. */ + if(xof_length == 0xFFFFFFFFUL) { + if(outlen == 0) { + return -1; + } + } else { + if(outlen != xof_length) { + return -1; + } + } + + /* Finalize the root hash */ + if (blake2b_final(S->S, root, BLAKE2B_OUTBYTES) < 0) { + return -1; + } + + /* Set common block structure values */ + /* Copy values from parent instance, and only change the ones below */ + memcpy(P, S->P, sizeof(blake2b_param)); + P->key_length = 0; + P->fanout = 0; + P->depth = 0; + store32(&P->leaf_length, BLAKE2B_OUTBYTES); + P->inner_length = BLAKE2B_OUTBYTES; + P->node_depth = 0; + + for (i = 0; outlen > 0; ++i) { + const size_t block_size = (outlen < BLAKE2B_OUTBYTES) ? outlen : BLAKE2B_OUTBYTES; + /* Initialize state */ + P->digest_length = block_size; + store32(&P->node_offset, i); + blake2b_init_param(C, P); + /* Process key if needed */ + blake2b_update(C, root, BLAKE2B_OUTBYTES); + if (blake2b_final(C, (uint8_t *)out + i * BLAKE2B_OUTBYTES, block_size) < 0 ) { + return -1; + } + outlen -= block_size; + } + secure_zero_memory(root, sizeof(root)); + secure_zero_memory(P, sizeof(P)); + secure_zero_memory(C, sizeof(C)); + /* Put blake2xb in an invalid state? cf. blake2s_is_lastblock */ + return 0; + +} + +int blake2xb(void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen) +{ + blake2xb_state S[1]; + + /* Verify parameters */ + if (NULL == in && inlen > 0) + return -1; + + if (NULL == out) + return -1; + + if (NULL == key && keylen > 0) + return -1; + + if (keylen > BLAKE2B_KEYBYTES) + return -1; + + if (outlen == 0) + return -1; + + /* Initialize the root block structure */ + if (blake2xb_init_key(S, outlen, key, keylen) < 0) { + return -1; + } + + /* Absorb the input message */ + blake2xb_update(S, in, inlen); + + /* Compute the root node of the tree and the final hash using the counter construction */ + return blake2xb_final(S, out, outlen); +} + +#if defined(BLAKE2XB_SELFTEST) +#include +#include "blake2-kat.h" +int main( void ) +{ + uint8_t key[BLAKE2B_KEYBYTES]; + uint8_t buf[BLAKE2_KAT_LENGTH]; + size_t i, step, outlen; + + for( i = 0; i < BLAKE2B_KEYBYTES; ++i ) { + key[i] = ( uint8_t )i; + } + + for( i = 0; i < BLAKE2_KAT_LENGTH; ++i ) { + buf[i] = ( uint8_t )i; + } + + /* Testing length of outputs rather than inputs */ + /* (Test of input lengths mostly covered by blake2b tests) */ + + /* Test simple API */ + for( outlen = 1; outlen <= BLAKE2_KAT_LENGTH; ++outlen ) + { + uint8_t hash[BLAKE2_KAT_LENGTH] = {0}; + if( blake2xb( hash, outlen, buf, BLAKE2_KAT_LENGTH, key, BLAKE2B_KEYBYTES ) < 0 ) { + goto fail; + } + + if( 0 != memcmp( hash, blake2xb_keyed_kat[outlen-1], outlen ) ) + { + goto fail; + } + } + + /* Test streaming API */ + for(step = 1; step < BLAKE2B_BLOCKBYTES; ++step) { + for (outlen = 1; outlen <= BLAKE2_KAT_LENGTH; ++outlen) { + uint8_t hash[BLAKE2_KAT_LENGTH]; + blake2xb_state S; + uint8_t * p = buf; + size_t mlen = BLAKE2_KAT_LENGTH; + int err = 0; + + if( (err = blake2xb_init_key(&S, outlen, key, BLAKE2B_KEYBYTES)) < 0 ) { + goto fail; + } + + while (mlen >= step) { + if ( (err = blake2xb_update(&S, p, step)) < 0 ) { + goto fail; + } + mlen -= step; + p += step; + } + if ( (err = blake2xb_update(&S, p, mlen)) < 0) { + goto fail; + } + if ( (err = blake2xb_final(&S, hash, outlen)) < 0) { + goto fail; + } + + if (0 != memcmp(hash, blake2xb_keyed_kat[outlen-1], outlen)) { + goto fail; + } + } + } + + puts( "ok" ); + return 0; +fail: + puts("error"); + return -1; +} +#endif diff --git a/src/Native/libmultihash/blake2/ref/blake2xs-ref.c b/src/Native/libmultihash/blake2/ref/blake2xs-ref.c new file mode 100644 index 000000000..e7a89f868 --- /dev/null +++ b/src/Native/libmultihash/blake2/ref/blake2xs-ref.c @@ -0,0 +1,239 @@ +/* + BLAKE2 reference source code package - reference C implementations + + Copyright 2016, JP Aumasson . + Copyright 2016, Samuel Neves . + + You may use this under the terms of the CC0, the OpenSSL Licence, or + the Apache Public License 2.0, at your option. The terms of these + licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ + +#include +#include +#include + +#include "blake2.h" +#include "blake2-impl.h" + +int blake2xs_init( blake2xs_state *S, const size_t outlen ) { + return blake2xs_init_key(S, outlen, NULL, 0); +} + +int blake2xs_init_key( blake2xs_state *S, const size_t outlen, const void *key, size_t keylen ) +{ + if ( outlen == 0 || outlen > 0xFFFFUL ) { + return -1; + } + + if (NULL != key && keylen > BLAKE2B_KEYBYTES) { + return -1; + } + + if (NULL == key && keylen > 0) { + return -1; + } + + /* Initialize parameter block */ + S->P->digest_length = BLAKE2S_OUTBYTES; + S->P->key_length = keylen; + S->P->fanout = 1; + S->P->depth = 1; + store32( &S->P->leaf_length, 0 ); + store32( &S->P->node_offset, 0 ); + store16( &S->P->xof_length, outlen ); + S->P->node_depth = 0; + S->P->inner_length = 0; + memset( S->P->salt, 0, sizeof( S->P->salt ) ); + memset( S->P->personal, 0, sizeof( S->P->personal ) ); + + if( blake2s_init_param( S->S, S->P ) < 0 ) { + return -1; + } + + if (keylen > 0) { + uint8_t block[BLAKE2S_BLOCKBYTES]; + memset(block, 0, BLAKE2S_BLOCKBYTES); + memcpy(block, key, keylen); + blake2s_update(S->S, block, BLAKE2S_BLOCKBYTES); + secure_zero_memory(block, BLAKE2S_BLOCKBYTES); + } + return 0; +} + +int blake2xs_update( blake2xs_state *S, const void *in, size_t inlen ) { + return blake2s_update( S->S, in, inlen ); +} + +int blake2xs_final(blake2xs_state *S, void *out, size_t outlen) { + + blake2s_state C[1]; + blake2s_param P[1]; + uint16_t xof_length = load16(&S->P->xof_length); + uint8_t root[BLAKE2S_BLOCKBYTES]; + size_t i; + + if (NULL == out) { + return -1; + } + + /* outlen must match the output size defined in xof_length, */ + /* unless it was -1, in which case anything goes except 0. */ + if(xof_length == 0xFFFFUL) { + if(outlen == 0) { + return -1; + } + } else { + if(outlen != xof_length) { + return -1; + } + } + + /* Finalize the root hash */ + if (blake2s_final(S->S, root, BLAKE2S_OUTBYTES) < 0) { + return -1; + } + + /* Set common block structure values */ + /* Copy values from parent instance, and only change the ones below */ + memcpy(P, S->P, sizeof(blake2s_param)); + P->key_length = 0; + P->fanout = 0; + P->depth = 0; + store32(&P->leaf_length, BLAKE2S_OUTBYTES); + P->inner_length = BLAKE2S_OUTBYTES; + P->node_depth = 0; + + for (i = 0; outlen > 0; ++i) { + const size_t block_size = (outlen < BLAKE2S_OUTBYTES) ? outlen : BLAKE2S_OUTBYTES; + /* Initialize state */ + P->digest_length = block_size; + store32(&P->node_offset, i); + blake2s_init_param(C, P); + /* Process key if needed */ + blake2s_update(C, root, BLAKE2S_OUTBYTES); + if (blake2s_final(C, (uint8_t *)out + i * BLAKE2S_OUTBYTES, block_size) < 0) { + return -1; + } + outlen -= block_size; + } + secure_zero_memory(root, sizeof(root)); + secure_zero_memory(P, sizeof(P)); + secure_zero_memory(C, sizeof(C)); + /* Put blake2xs in an invalid state? cf. blake2s_is_lastblock */ + return 0; +} + +int blake2xs(void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen) +{ + blake2xs_state S[1]; + + /* Verify parameters */ + if (NULL == in && inlen > 0) + return -1; + + if (NULL == out) + return -1; + + if (NULL == key && keylen > 0) + return -1; + + if (keylen > BLAKE2S_KEYBYTES) + return -1; + + if (outlen == 0) + return -1; + + /* Initialize the root block structure */ + if (blake2xs_init_key(S, outlen, key, keylen) < 0) { + return -1; + } + + /* Absorb the input message */ + blake2xs_update(S, in, inlen); + + /* Compute the root node of the tree and the final hash using the counter construction */ + return blake2xs_final(S, out, outlen); +} + +#if defined(BLAKE2XS_SELFTEST) +#include +#include "blake2-kat.h" +int main( void ) +{ + uint8_t key[BLAKE2S_KEYBYTES]; + uint8_t buf[BLAKE2_KAT_LENGTH]; + size_t i, step, outlen; + + for( i = 0; i < BLAKE2S_KEYBYTES; ++i ) { + key[i] = ( uint8_t )i; + } + + for( i = 0; i < BLAKE2_KAT_LENGTH; ++i ) { + buf[i] = ( uint8_t )i; + } + + /* Testing length of outputs rather than inputs */ + /* (Test of input lengths mostly covered by blake2s tests) */ + + /* Test simple API */ + for( outlen = 1; outlen <= BLAKE2_KAT_LENGTH; ++outlen ) + { + uint8_t hash[BLAKE2_KAT_LENGTH] = {0}; + if( blake2xs( hash, outlen, buf, BLAKE2_KAT_LENGTH, key, BLAKE2S_KEYBYTES ) < 0 ) { + goto fail; + } + + if( 0 != memcmp( hash, blake2xs_keyed_kat[outlen-1], outlen ) ) + { + goto fail; + } + } + + /* Test streaming API */ + for(step = 1; step < BLAKE2S_BLOCKBYTES; ++step) { + for (outlen = 1; outlen <= BLAKE2_KAT_LENGTH; ++outlen) { + uint8_t hash[BLAKE2_KAT_LENGTH]; + blake2xs_state S; + uint8_t * p = buf; + size_t mlen = BLAKE2_KAT_LENGTH; + int err = 0; + + if( (err = blake2xs_init_key(&S, outlen, key, BLAKE2S_KEYBYTES)) < 0 ) { + goto fail; + } + + while (mlen >= step) { + if ( (err = blake2xs_update(&S, p, step)) < 0 ) { + goto fail; + } + mlen -= step; + p += step; + } + if ( (err = blake2xs_update(&S, p, mlen)) < 0) { + goto fail; + } + if ( (err = blake2xs_final(&S, hash, outlen)) < 0) { + goto fail; + } + + if (0 != memcmp(hash, blake2xs_keyed_kat[outlen-1], outlen)) { + goto fail; + } + } + } + + puts( "ok" ); + return 0; +fail: + puts("error"); + return -1; +} +#endif diff --git a/src/Native/libmultihash/blake2/ref/genkat-c.c b/src/Native/libmultihash/blake2/ref/genkat-c.c new file mode 100644 index 000000000..58a48fdab --- /dev/null +++ b/src/Native/libmultihash/blake2/ref/genkat-c.c @@ -0,0 +1,139 @@ +/* + BLAKE2 reference source code package - reference C implementations + + Copyright 2012, Samuel Neves . You may use this under the + terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at + your option. The terms of these licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ + +#include +#include +#include +#include + +#include "blake2.h" + +#define STR_(x) #x +#define STR(x) STR_(x) + +#define LENGTH 256 + +#define MAKE_KAT(name, size_prefix) \ + do { \ + printf("static const uint8_t " #name "_kat[BLAKE2_KAT_LENGTH][" #size_prefix \ + "_OUTBYTES] = \n{\n"); \ + \ + for (i = 0; i < LENGTH; ++i) { \ + name(hash, size_prefix##_OUTBYTES, in, i, NULL, 0); \ + printf("\t{\n\t\t"); \ + \ + for (j = 0; j < size_prefix##_OUTBYTES; ++j) \ + printf("0x%02X%s", hash[j], \ + (j + 1) == size_prefix##_OUTBYTES ? "\n" : j && !((j + 1) % 8) ? ",\n\t\t" : ", "); \ + \ + printf("\t},\n"); \ + } \ + \ + printf("};\n\n\n\n\n"); \ + } while (0) + +#define MAKE_KEYED_KAT(name, size_prefix) \ + do { \ + printf("static const uint8_t " #name "_keyed_kat[BLAKE2_KAT_LENGTH][" #size_prefix \ + "_OUTBYTES] = \n{\n"); \ + \ + for (i = 0; i < LENGTH; ++i) { \ + name(hash, size_prefix##_OUTBYTES, in, i, key, size_prefix##_KEYBYTES); \ + printf("\t{\n\t\t"); \ + \ + for (j = 0; j < size_prefix##_OUTBYTES; ++j) \ + printf("0x%02X%s", hash[j], \ + (j + 1) == size_prefix##_OUTBYTES ? "\n" : j && !((j + 1) % 8) ? ",\n\t\t" : ", "); \ + \ + printf("\t},\n"); \ + } \ + \ + printf("};\n\n\n\n\n"); \ + } while (0) + +#define MAKE_XOF_KAT(name) \ + do { \ + printf("static const uint8_t " #name "_kat[BLAKE2_KAT_LENGTH][BLAKE2_KAT_LENGTH] = \n{\n"); \ + \ + for (i = 1; i <= LENGTH; ++i) { \ + name(hash, i, in, LENGTH, NULL, 0); \ + printf("\t{\n\t\t"); \ + \ + for (j = 0; j < i; ++j) \ + printf("0x%02X%s", hash[j], \ + (j + 1) == LENGTH ? "\n" : j && !((j + 1) % 8) ? ",\n\t\t" : ", "); \ + \ + for (j = i; j < LENGTH; ++j) \ + printf("0x00%s", (j + 1) == LENGTH ? "\n" : j && !((j + 1) % 8) ? ",\n\t\t" : ", "); \ + \ + printf("\t},\n"); \ + } \ + \ + printf("};\n\n\n\n\n"); \ + } while (0) + +#define MAKE_XOF_KEYED_KAT(name, size_prefix) \ + do { \ + printf("static const uint8_t " #name \ + "_keyed_kat[BLAKE2_KAT_LENGTH][BLAKE2_KAT_LENGTH] = \n{\n"); \ + \ + for (i = 1; i <= LENGTH; ++i) { \ + name(hash, i, in, LENGTH, key, size_prefix##_KEYBYTES); \ + printf("\t{\n\t\t"); \ + \ + for (j = 0; j < i; ++j) \ + printf("0x%02X%s", hash[j], \ + (j + 1) == LENGTH ? "\n" : j && !((j + 1) % 8) ? ",\n\t\t" : ", "); \ + \ + for (j = i; j < LENGTH; ++j) \ + printf("0x00%s", (j + 1) == LENGTH ? "\n" : j && !((j + 1) % 8) ? ",\n\t\t" : ", "); \ + \ + printf("\t},\n"); \ + } \ + \ + printf("};\n\n\n\n\n"); \ + } while (0) + +int main() { + uint8_t key[64] = {0}; + uint8_t in[LENGTH] = {0}; + uint8_t hash[LENGTH] = {0}; + size_t i, j; + + for (i = 0; i < sizeof(in); ++i) + in[i] = i; + + for (i = 0; i < sizeof(key); ++i) + key[i] = i; + + puts("#ifndef BLAKE2_KAT_H\n" + "#define BLAKE2_KAT_H\n\n\n" + "#include \n\n" + "#define BLAKE2_KAT_LENGTH " STR(LENGTH) "\n\n\n"); + MAKE_KAT(blake2s, BLAKE2S); + MAKE_KEYED_KAT(blake2s, BLAKE2S); + MAKE_KAT(blake2b, BLAKE2B); + MAKE_KEYED_KAT(blake2b, BLAKE2B); + MAKE_KAT(blake2sp, BLAKE2S); + MAKE_KEYED_KAT(blake2sp, BLAKE2S); + MAKE_KAT(blake2bp, BLAKE2B); + MAKE_KEYED_KAT(blake2bp, BLAKE2B); + MAKE_XOF_KAT(blake2xs); + MAKE_XOF_KEYED_KAT(blake2xs, BLAKE2S); + MAKE_XOF_KAT(blake2xb); + MAKE_XOF_KEYED_KAT(blake2xb, BLAKE2B); + puts("#endif"); + return 0; +} diff --git a/src/Native/libmultihash/blake2/ref/genkat-json.c b/src/Native/libmultihash/blake2/ref/genkat-json.c new file mode 100644 index 000000000..0275fb513 --- /dev/null +++ b/src/Native/libmultihash/blake2/ref/genkat-json.c @@ -0,0 +1,154 @@ +/* + BLAKE2 reference source code package - reference C implementations + + Copyright 2012, Samuel Neves . You may use this under the + terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at + your option. The terms of these licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ + +#include +#include +#include +#include + +#include "blake2.h" + +#define STR_(x) #x +#define STR(x) STR_(x) + +#define LENGTH 256 + +#define MAKE_KAT(name, size_prefix, first) \ + do { \ + for (i = 0; i < LENGTH; ++i) { \ + printf("%s\n{\n", i == 0 && first ? "" : ","); \ + \ + printf(" \"hash\": \"" #name "\",\n"); \ + printf(" \"in\": \""); \ + for (j = 0; j < i; ++j) \ + printf("%02x", in[j]); \ + \ + printf("\",\n"); \ + printf(" \"key\": \"\",\n"); \ + printf(" \"out\": \""); \ + \ + name(hash, size_prefix##_OUTBYTES, in, i, NULL, 0); \ + \ + for (j = 0; j < size_prefix##_OUTBYTES; ++j) \ + printf("%02x", hash[j]); \ + printf("\"\n"); \ + printf("}"); \ + } \ + } while (0) + +#define MAKE_KEYED_KAT(name, size_prefix, first) \ + do { \ + for (i = 0; i < LENGTH; ++i) { \ + printf("%s\n{\n", i == 0 && first ? "" : ","); \ + \ + printf(" \"hash\": \"" #name "\",\n"); \ + printf(" \"in\": \""); \ + for (j = 0; j < i; ++j) \ + printf("%02x", in[j]); \ + \ + printf("\",\n"); \ + printf(" \"key\": \""); \ + for (j = 0; j < size_prefix##_KEYBYTES; ++j) \ + printf("%02x", key[j]); \ + printf("\",\n"); \ + printf(" \"out\": \""); \ + \ + name(hash, size_prefix##_OUTBYTES, in, i, key, size_prefix##_KEYBYTES); \ + \ + for (j = 0; j < size_prefix##_OUTBYTES; ++j) \ + printf("%02x", hash[j]); \ + printf("\"\n"); \ + printf("}"); \ + } \ + } while (0) + +#define MAKE_XOF_KAT(name, first) \ + do { \ + for (i = 1; i <= LENGTH; ++i) { \ + printf("%s\n{\n", i == 1 && first ? "" : ","); \ + \ + printf(" \"hash\": \"" #name "\",\n"); \ + printf(" \"in\": \""); \ + for (j = 0; j < LENGTH; ++j) \ + printf("%02x", in[j]); \ + \ + printf("\",\n"); \ + printf(" \"key\": \"\",\n"); \ + printf(" \"out\": \""); \ + \ + name(hash, i, in, LENGTH, NULL, 0); \ + \ + for (j = 0; j < i; ++j) \ + printf("%02x", hash[j]); \ + printf("\"\n"); \ + printf("}"); \ + } \ + } while (0) + +#define MAKE_XOF_KEYED_KAT(name, size_prefix, first) \ + do { \ + for (i = 1; i <= LENGTH; ++i) { \ + printf("%s\n{\n", i == 1 && first ? "" : ","); \ + \ + printf(" \"hash\": \"" #name "\",\n"); \ + printf(" \"in\": \""); \ + for (j = 0; j < LENGTH; ++j) \ + printf("%02x", in[j]); \ + \ + printf("\",\n"); \ + printf(" \"key\": \""); \ + for (j = 0; j < size_prefix##_KEYBYTES; ++j) \ + printf("%02x", key[j]); \ + printf("\",\n"); \ + printf(" \"out\": \""); \ + \ + name(hash, i, in, LENGTH, key, size_prefix##_KEYBYTES); \ + \ + for (j = 0; j < i; ++j) \ + printf("%02x", hash[j]); \ + printf("\"\n"); \ + printf("}"); \ + } \ + } while (0) + +int main() { + uint8_t key[64] = {0}; + uint8_t in[LENGTH] = {0}; + uint8_t hash[LENGTH] = {0}; + size_t i, j; + + for (i = 0; i < sizeof(in); ++i) + in[i] = i; + + for (i = 0; i < sizeof(key); ++i) + key[i] = i; + + printf("["); + MAKE_KAT(blake2s, BLAKE2S, 1); + MAKE_KEYED_KAT(blake2s, BLAKE2S, 0); + MAKE_KAT(blake2b, BLAKE2B, 0); + MAKE_KEYED_KAT(blake2b, BLAKE2B, 0); + MAKE_KAT(blake2sp, BLAKE2S, 0); + MAKE_KEYED_KAT(blake2sp, BLAKE2S, 0); + MAKE_KAT(blake2bp, BLAKE2B, 0); + MAKE_KEYED_KAT(blake2bp, BLAKE2B, 0); + MAKE_XOF_KAT(blake2xs, 0); + MAKE_XOF_KEYED_KAT(blake2xs, BLAKE2S, 0); + MAKE_XOF_KAT(blake2xb, 0); + MAKE_XOF_KEYED_KAT(blake2xb, BLAKE2B, 0); + printf("\n]\n"); + fflush(stdout); + return 0; +} diff --git a/src/Native/libmultihash/blake2/ref/makefile b/src/Native/libmultihash/blake2/ref/makefile new file mode 100644 index 000000000..ee3c30a6d --- /dev/null +++ b/src/Native/libmultihash/blake2/ref/makefile @@ -0,0 +1,40 @@ +CC=gcc +CFLAGS=-O2 -I../testvectors -Wall -Wextra -std=c89 -pedantic -Wno-long-long +BLAKEBINS=blake2s blake2b blake2sp blake2bp blake2xs blake2xb + +all: $(BLAKEBINS) check + +blake2s: blake2s-ref.c + $(CC) blake2s-ref.c -o $@ $(CFLAGS) -DBLAKE2S_SELFTEST + +blake2b: blake2b-ref.c + $(CC) blake2b-ref.c -o $@ $(CFLAGS) -DBLAKE2B_SELFTEST + +blake2sp: blake2sp-ref.c blake2s-ref.c + $(CC) blake2sp-ref.c blake2s-ref.c -o $@ $(CFLAGS) -DBLAKE2SP_SELFTEST + +blake2bp: blake2bp-ref.c blake2b-ref.c + $(CC) blake2bp-ref.c blake2b-ref.c -o $@ $(CFLAGS) -DBLAKE2BP_SELFTEST + +blake2xs: blake2xs-ref.c blake2s-ref.c + $(CC) blake2xs-ref.c blake2s-ref.c -o $@ $(CFLAGS) -DBLAKE2XS_SELFTEST + +blake2xb: blake2xb-ref.c blake2b-ref.c + $(CC) blake2xb-ref.c blake2b-ref.c -o $@ $(CFLAGS) -DBLAKE2XB_SELFTEST + +check: blake2s blake2b blake2sp blake2bp blake2xs blake2xb + ./blake2s + ./blake2b + ./blake2sp + ./blake2bp + ./blake2xs + ./blake2xb + +kat: + $(CC) $(CFLAGS) -o genkat-c genkat-c.c blake2b-ref.c blake2s-ref.c blake2sp-ref.c blake2bp-ref.c blake2xs-ref.c blake2xb-ref.c + $(CC) $(CFLAGS) -o genkat-json genkat-json.c blake2b-ref.c blake2s-ref.c blake2sp-ref.c blake2bp-ref.c blake2xs-ref.c blake2xb-ref.c + ./genkat-c > blake2-kat.h + ./genkat-json > blake2-kat.json + +clean: + rm -rf *.o genkat-c genkat-json blake2-kat.h blake2-kat.json $(BLAKEBINS) diff --git a/src/Native/libmultihash/blake2/sse/blake2-config.h b/src/Native/libmultihash/blake2/sse/blake2-config.h new file mode 100644 index 000000000..a524aa95f --- /dev/null +++ b/src/Native/libmultihash/blake2/sse/blake2-config.h @@ -0,0 +1,72 @@ +/* + BLAKE2 reference source code package - optimized C implementations + + Copyright 2012, Samuel Neves . You may use this under the + terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at + your option. The terms of these licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ +#ifndef BLAKE2_CONFIG_H +#define BLAKE2_CONFIG_H + +/* These don't work everywhere */ +#if defined(__SSE2__) || defined(__x86_64__) || defined(__amd64__) +#define HAVE_SSE2 +#endif + +#if defined(__SSSE3__) +#define HAVE_SSSE3 +#endif + +#if defined(__SSE4_1__) +#define HAVE_SSE41 +#endif + +#if defined(__AVX__) +#define HAVE_AVX +#endif + +#if defined(__XOP__) +#define HAVE_XOP +#endif + + +#ifdef HAVE_AVX2 +#ifndef HAVE_AVX +#define HAVE_AVX +#endif +#endif + +#ifdef HAVE_XOP +#ifndef HAVE_AVX +#define HAVE_AVX +#endif +#endif + +#ifdef HAVE_AVX +#ifndef HAVE_SSE41 +#define HAVE_SSE41 +#endif +#endif + +#ifdef HAVE_SSE41 +#ifndef HAVE_SSSE3 +#define HAVE_SSSE3 +#endif +#endif + +#ifdef HAVE_SSSE3 +#define HAVE_SSE2 +#endif + +#if !defined(HAVE_SSE2) +#error "This code requires at least SSE2." +#endif + +#endif diff --git a/src/Native/libmultihash/blake2/sse/blake2-impl.h b/src/Native/libmultihash/blake2/sse/blake2-impl.h new file mode 100644 index 000000000..c1df82e0c --- /dev/null +++ b/src/Native/libmultihash/blake2/sse/blake2-impl.h @@ -0,0 +1,160 @@ +/* + BLAKE2 reference source code package - reference C implementations + + Copyright 2012, Samuel Neves . You may use this under the + terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at + your option. The terms of these licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ +#ifndef BLAKE2_IMPL_H +#define BLAKE2_IMPL_H + +#include +#include + +#if !defined(__cplusplus) && (!defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L) + #if defined(_MSC_VER) + #define BLAKE2_INLINE __inline + #elif defined(__GNUC__) + #define BLAKE2_INLINE __inline__ + #else + #define BLAKE2_INLINE + #endif +#else + #define BLAKE2_INLINE inline +#endif + +static BLAKE2_INLINE uint32_t load32( const void *src ) +{ +#if defined(NATIVE_LITTLE_ENDIAN) + uint32_t w; + memcpy(&w, src, sizeof w); + return w; +#else + const uint8_t *p = ( const uint8_t * )src; + return (( uint32_t )( p[0] ) << 0) | + (( uint32_t )( p[1] ) << 8) | + (( uint32_t )( p[2] ) << 16) | + (( uint32_t )( p[3] ) << 24) ; +#endif +} + +static BLAKE2_INLINE uint64_t load64( const void *src ) +{ +#if defined(NATIVE_LITTLE_ENDIAN) + uint64_t w; + memcpy(&w, src, sizeof w); + return w; +#else + const uint8_t *p = ( const uint8_t * )src; + return (( uint64_t )( p[0] ) << 0) | + (( uint64_t )( p[1] ) << 8) | + (( uint64_t )( p[2] ) << 16) | + (( uint64_t )( p[3] ) << 24) | + (( uint64_t )( p[4] ) << 32) | + (( uint64_t )( p[5] ) << 40) | + (( uint64_t )( p[6] ) << 48) | + (( uint64_t )( p[7] ) << 56) ; +#endif +} + +static BLAKE2_INLINE uint16_t load16( const void *src ) +{ +#if defined(NATIVE_LITTLE_ENDIAN) + uint16_t w; + memcpy(&w, src, sizeof w); + return w; +#else + const uint8_t *p = ( const uint8_t * )src; + return ( uint16_t )((( uint32_t )( p[0] ) << 0) | + (( uint32_t )( p[1] ) << 8)); +#endif +} + +static BLAKE2_INLINE void store16( void *dst, uint16_t w ) +{ +#if defined(NATIVE_LITTLE_ENDIAN) + memcpy(dst, &w, sizeof w); +#else + uint8_t *p = ( uint8_t * )dst; + *p++ = ( uint8_t )w; w >>= 8; + *p++ = ( uint8_t )w; +#endif +} + +static BLAKE2_INLINE void store32( void *dst, uint32_t w ) +{ +#if defined(NATIVE_LITTLE_ENDIAN) + memcpy(dst, &w, sizeof w); +#else + uint8_t *p = ( uint8_t * )dst; + p[0] = (uint8_t)(w >> 0); + p[1] = (uint8_t)(w >> 8); + p[2] = (uint8_t)(w >> 16); + p[3] = (uint8_t)(w >> 24); +#endif +} + +static BLAKE2_INLINE void store64( void *dst, uint64_t w ) +{ +#if defined(NATIVE_LITTLE_ENDIAN) + memcpy(dst, &w, sizeof w); +#else + uint8_t *p = ( uint8_t * )dst; + p[0] = (uint8_t)(w >> 0); + p[1] = (uint8_t)(w >> 8); + p[2] = (uint8_t)(w >> 16); + p[3] = (uint8_t)(w >> 24); + p[4] = (uint8_t)(w >> 32); + p[5] = (uint8_t)(w >> 40); + p[6] = (uint8_t)(w >> 48); + p[7] = (uint8_t)(w >> 56); +#endif +} + +static BLAKE2_INLINE uint64_t load48( const void *src ) +{ + const uint8_t *p = ( const uint8_t * )src; + return (( uint64_t )( p[0] ) << 0) | + (( uint64_t )( p[1] ) << 8) | + (( uint64_t )( p[2] ) << 16) | + (( uint64_t )( p[3] ) << 24) | + (( uint64_t )( p[4] ) << 32) | + (( uint64_t )( p[5] ) << 40) ; +} + +static BLAKE2_INLINE void store48( void *dst, uint64_t w ) +{ + uint8_t *p = ( uint8_t * )dst; + p[0] = (uint8_t)(w >> 0); + p[1] = (uint8_t)(w >> 8); + p[2] = (uint8_t)(w >> 16); + p[3] = (uint8_t)(w >> 24); + p[4] = (uint8_t)(w >> 32); + p[5] = (uint8_t)(w >> 40); +} + +static BLAKE2_INLINE uint32_t rotr32( const uint32_t w, const unsigned c ) +{ + return ( w >> c ) | ( w << ( 32 - c ) ); +} + +static BLAKE2_INLINE uint64_t rotr64( const uint64_t w, const unsigned c ) +{ + return ( w >> c ) | ( w << ( 64 - c ) ); +} + +/* prevents compiler optimizing out memset() */ +static BLAKE2_INLINE void secure_zero_memory(void *v, size_t n) +{ + static void *(*const volatile memset_v)(void *, int, size_t) = &memset; + memset_v(v, 0, n); +} + +#endif diff --git a/src/Native/libmultihash/blake2/sse/blake2.h b/src/Native/libmultihash/blake2/sse/blake2.h new file mode 100644 index 000000000..ca390305e --- /dev/null +++ b/src/Native/libmultihash/blake2/sse/blake2.h @@ -0,0 +1,195 @@ +/* + BLAKE2 reference source code package - reference C implementations + + Copyright 2012, Samuel Neves . You may use this under the + terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at + your option. The terms of these licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ +#ifndef BLAKE2_H +#define BLAKE2_H + +#include +#include + +#if defined(_MSC_VER) +#define BLAKE2_PACKED(x) __pragma(pack(push, 1)) x __pragma(pack(pop)) +#else +#define BLAKE2_PACKED(x) x __attribute__((packed)) +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + + enum blake2s_constant + { + BLAKE2S_BLOCKBYTES = 64, + BLAKE2S_OUTBYTES = 32, + BLAKE2S_KEYBYTES = 32, + BLAKE2S_SALTBYTES = 8, + BLAKE2S_PERSONALBYTES = 8 + }; + + enum blake2b_constant + { + BLAKE2B_BLOCKBYTES = 128, + BLAKE2B_OUTBYTES = 64, + BLAKE2B_KEYBYTES = 64, + BLAKE2B_SALTBYTES = 16, + BLAKE2B_PERSONALBYTES = 16 + }; + + typedef struct blake2s_state__ + { + uint32_t h[8]; + uint32_t t[2]; + uint32_t f[2]; + uint8_t buf[BLAKE2S_BLOCKBYTES]; + size_t buflen; + size_t outlen; + uint8_t last_node; + } blake2s_state; + + typedef struct blake2b_state__ + { + uint64_t h[8]; + uint64_t t[2]; + uint64_t f[2]; + uint8_t buf[BLAKE2B_BLOCKBYTES]; + size_t buflen; + size_t outlen; + uint8_t last_node; + } blake2b_state; + + typedef struct blake2sp_state__ + { + blake2s_state S[8][1]; + blake2s_state R[1]; + uint8_t buf[8 * BLAKE2S_BLOCKBYTES]; + size_t buflen; + size_t outlen; + } blake2sp_state; + + typedef struct blake2bp_state__ + { + blake2b_state S[4][1]; + blake2b_state R[1]; + uint8_t buf[4 * BLAKE2B_BLOCKBYTES]; + size_t buflen; + size_t outlen; + } blake2bp_state; + + + BLAKE2_PACKED(struct blake2s_param__ + { + uint8_t digest_length; /* 1 */ + uint8_t key_length; /* 2 */ + uint8_t fanout; /* 3 */ + uint8_t depth; /* 4 */ + uint32_t leaf_length; /* 8 */ + uint32_t node_offset; /* 12 */ + uint16_t xof_length; /* 14 */ + uint8_t node_depth; /* 15 */ + uint8_t inner_length; /* 16 */ + /* uint8_t reserved[0]; */ + uint8_t salt[BLAKE2S_SALTBYTES]; /* 24 */ + uint8_t personal[BLAKE2S_PERSONALBYTES]; /* 32 */ + }); + + typedef struct blake2s_param__ blake2s_param; + + BLAKE2_PACKED(struct blake2b_param__ + { + uint8_t digest_length; /* 1 */ + uint8_t key_length; /* 2 */ + uint8_t fanout; /* 3 */ + uint8_t depth; /* 4 */ + uint32_t leaf_length; /* 8 */ + uint32_t node_offset; /* 12 */ + uint32_t xof_length; /* 16 */ + uint8_t node_depth; /* 17 */ + uint8_t inner_length; /* 18 */ + uint8_t reserved[14]; /* 32 */ + uint8_t salt[BLAKE2B_SALTBYTES]; /* 48 */ + uint8_t personal[BLAKE2B_PERSONALBYTES]; /* 64 */ + }); + + typedef struct blake2b_param__ blake2b_param; + + typedef struct blake2xs_state__ + { + blake2s_state S[1]; + blake2s_param P[1]; + } blake2xs_state; + + typedef struct blake2xb_state__ + { + blake2b_state S[1]; + blake2b_param P[1]; + } blake2xb_state; + + /* Padded structs result in a compile-time error */ + enum { + BLAKE2_DUMMY_1 = 1/(int)(sizeof(blake2s_param) == BLAKE2S_OUTBYTES), + BLAKE2_DUMMY_2 = 1/(int)(sizeof(blake2b_param) == BLAKE2B_OUTBYTES) + }; + + /* Streaming API */ + int blake2s_init( blake2s_state *S, size_t outlen ); + int blake2s_init_key( blake2s_state *S, size_t outlen, const void *key, size_t keylen ); + int blake2s_init_param( blake2s_state *S, const blake2s_param *P ); + int blake2s_update( blake2s_state *S, const void *in, size_t inlen ); + int blake2s_final( blake2s_state *S, void *out, size_t outlen ); + + int blake2b_init( blake2b_state *S, size_t outlen ); + int blake2b_init_key( blake2b_state *S, size_t outlen, const void *key, size_t keylen ); + int blake2b_init_param( blake2b_state *S, const blake2b_param *P ); + int blake2b_update( blake2b_state *S, const void *in, size_t inlen ); + int blake2b_final( blake2b_state *S, void *out, size_t outlen ); + + int blake2sp_init( blake2sp_state *S, size_t outlen ); + int blake2sp_init_key( blake2sp_state *S, size_t outlen, const void *key, size_t keylen ); + int blake2sp_update( blake2sp_state *S, const void *in, size_t inlen ); + int blake2sp_final( blake2sp_state *S, void *out, size_t outlen ); + + int blake2bp_init( blake2bp_state *S, size_t outlen ); + int blake2bp_init_key( blake2bp_state *S, size_t outlen, const void *key, size_t keylen ); + int blake2bp_update( blake2bp_state *S, const void *in, size_t inlen ); + int blake2bp_final( blake2bp_state *S, void *out, size_t outlen ); + + /* Variable output length API */ + int blake2xs_init( blake2xs_state *S, const size_t outlen ); + int blake2xs_init_key( blake2xs_state *S, const size_t outlen, const void *key, size_t keylen ); + int blake2xs_update( blake2xs_state *S, const void *in, size_t inlen ); + int blake2xs_final(blake2xs_state *S, void *out, size_t outlen); + + int blake2xb_init( blake2xb_state *S, const size_t outlen ); + int blake2xb_init_key( blake2xb_state *S, const size_t outlen, const void *key, size_t keylen ); + int blake2xb_update( blake2xb_state *S, const void *in, size_t inlen ); + int blake2xb_final(blake2xb_state *S, void *out, size_t outlen); + + /* Simple API */ + int blake2s( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen ); + int blake2b( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen ); + + int blake2sp( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen ); + int blake2bp( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen ); + + int blake2xs( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen ); + int blake2xb( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen ); + + /* This is simply an alias for blake2b */ + int blake2( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen ); + +#if defined(__cplusplus) +} +#endif + +#endif diff --git a/src/Native/libmultihash/blake2/sse/blake2b b/src/Native/libmultihash/blake2/sse/blake2b new file mode 100644 index 0000000000000000000000000000000000000000..7021207101358c3e1a6bb4097d0e66a8de37e349 GIT binary patch literal 46032 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

ra<$z92r4_H5O7!ovVn7E!t{bp>t=1Rsv| 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. You may use this under the + terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at + your option. The terms of these licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ +#ifndef BLAKE2B_LOAD_SSE2_H +#define BLAKE2B_LOAD_SSE2_H + +#define LOAD_MSG_0_1(b0, b1) b0 = _mm_set_epi64x(m2, m0); b1 = _mm_set_epi64x(m6, m4) +#define LOAD_MSG_0_2(b0, b1) b0 = _mm_set_epi64x(m3, m1); b1 = _mm_set_epi64x(m7, m5) +#define LOAD_MSG_0_3(b0, b1) b0 = _mm_set_epi64x(m10, m8); b1 = _mm_set_epi64x(m14, m12) +#define LOAD_MSG_0_4(b0, b1) b0 = _mm_set_epi64x(m11, m9); b1 = _mm_set_epi64x(m15, m13) +#define LOAD_MSG_1_1(b0, b1) b0 = _mm_set_epi64x(m4, m14); b1 = _mm_set_epi64x(m13, m9) +#define LOAD_MSG_1_2(b0, b1) b0 = _mm_set_epi64x(m8, m10); b1 = _mm_set_epi64x(m6, m15) +#define LOAD_MSG_1_3(b0, b1) b0 = _mm_set_epi64x(m0, m1); b1 = _mm_set_epi64x(m5, m11) +#define LOAD_MSG_1_4(b0, b1) b0 = _mm_set_epi64x(m2, m12); b1 = _mm_set_epi64x(m3, m7) +#define LOAD_MSG_2_1(b0, b1) b0 = _mm_set_epi64x(m12, m11); b1 = _mm_set_epi64x(m15, m5) +#define LOAD_MSG_2_2(b0, b1) b0 = _mm_set_epi64x(m0, m8); b1 = _mm_set_epi64x(m13, m2) +#define LOAD_MSG_2_3(b0, b1) b0 = _mm_set_epi64x(m3, m10); b1 = _mm_set_epi64x(m9, m7) +#define LOAD_MSG_2_4(b0, b1) b0 = _mm_set_epi64x(m6, m14); b1 = _mm_set_epi64x(m4, m1) +#define LOAD_MSG_3_1(b0, b1) b0 = _mm_set_epi64x(m3, m7); b1 = _mm_set_epi64x(m11, m13) +#define LOAD_MSG_3_2(b0, b1) b0 = _mm_set_epi64x(m1, m9); b1 = _mm_set_epi64x(m14, m12) +#define LOAD_MSG_3_3(b0, b1) b0 = _mm_set_epi64x(m5, m2); b1 = _mm_set_epi64x(m15, m4) +#define LOAD_MSG_3_4(b0, b1) b0 = _mm_set_epi64x(m10, m6); b1 = _mm_set_epi64x(m8, m0) +#define LOAD_MSG_4_1(b0, b1) b0 = _mm_set_epi64x(m5, m9); b1 = _mm_set_epi64x(m10, m2) +#define LOAD_MSG_4_2(b0, b1) b0 = _mm_set_epi64x(m7, m0); b1 = _mm_set_epi64x(m15, m4) +#define LOAD_MSG_4_3(b0, b1) b0 = _mm_set_epi64x(m11, m14); b1 = _mm_set_epi64x(m3, m6) +#define LOAD_MSG_4_4(b0, b1) b0 = _mm_set_epi64x(m12, m1); b1 = _mm_set_epi64x(m13, m8) +#define LOAD_MSG_5_1(b0, b1) b0 = _mm_set_epi64x(m6, m2); b1 = _mm_set_epi64x(m8, m0) +#define LOAD_MSG_5_2(b0, b1) b0 = _mm_set_epi64x(m10, m12); b1 = _mm_set_epi64x(m3, m11) +#define LOAD_MSG_5_3(b0, b1) b0 = _mm_set_epi64x(m7, m4); b1 = _mm_set_epi64x(m1, m15) +#define LOAD_MSG_5_4(b0, b1) b0 = _mm_set_epi64x(m5, m13); b1 = _mm_set_epi64x(m9, m14) +#define LOAD_MSG_6_1(b0, b1) b0 = _mm_set_epi64x(m1, m12); b1 = _mm_set_epi64x(m4, m14) +#define LOAD_MSG_6_2(b0, b1) b0 = _mm_set_epi64x(m15, m5); b1 = _mm_set_epi64x(m10, m13) +#define LOAD_MSG_6_3(b0, b1) b0 = _mm_set_epi64x(m6, m0); b1 = _mm_set_epi64x(m8, m9) +#define LOAD_MSG_6_4(b0, b1) b0 = _mm_set_epi64x(m3, m7); b1 = _mm_set_epi64x(m11, m2) +#define LOAD_MSG_7_1(b0, b1) b0 = _mm_set_epi64x(m7, m13); b1 = _mm_set_epi64x(m3, m12) +#define LOAD_MSG_7_2(b0, b1) b0 = _mm_set_epi64x(m14, m11); b1 = _mm_set_epi64x(m9, m1) +#define LOAD_MSG_7_3(b0, b1) b0 = _mm_set_epi64x(m15, m5); b1 = _mm_set_epi64x(m2, m8) +#define LOAD_MSG_7_4(b0, b1) b0 = _mm_set_epi64x(m4, m0); b1 = _mm_set_epi64x(m10, m6) +#define LOAD_MSG_8_1(b0, b1) b0 = _mm_set_epi64x(m14, m6); b1 = _mm_set_epi64x(m0, m11) +#define LOAD_MSG_8_2(b0, b1) b0 = _mm_set_epi64x(m9, m15); b1 = _mm_set_epi64x(m8, m3) +#define LOAD_MSG_8_3(b0, b1) b0 = _mm_set_epi64x(m13, m12); b1 = _mm_set_epi64x(m10, m1) +#define LOAD_MSG_8_4(b0, b1) b0 = _mm_set_epi64x(m7, m2); b1 = _mm_set_epi64x(m5, m4) +#define LOAD_MSG_9_1(b0, b1) b0 = _mm_set_epi64x(m8, m10); b1 = _mm_set_epi64x(m1, m7) +#define LOAD_MSG_9_2(b0, b1) b0 = _mm_set_epi64x(m4, m2); b1 = _mm_set_epi64x(m5, m6) +#define LOAD_MSG_9_3(b0, b1) b0 = _mm_set_epi64x(m9, m15); b1 = _mm_set_epi64x(m13, m3) +#define LOAD_MSG_9_4(b0, b1) b0 = _mm_set_epi64x(m14, m11); b1 = _mm_set_epi64x(m0, m12) +#define LOAD_MSG_10_1(b0, b1) b0 = _mm_set_epi64x(m2, m0); b1 = _mm_set_epi64x(m6, m4) +#define LOAD_MSG_10_2(b0, b1) b0 = _mm_set_epi64x(m3, m1); b1 = _mm_set_epi64x(m7, m5) +#define LOAD_MSG_10_3(b0, b1) b0 = _mm_set_epi64x(m10, m8); b1 = _mm_set_epi64x(m14, m12) +#define LOAD_MSG_10_4(b0, b1) b0 = _mm_set_epi64x(m11, m9); b1 = _mm_set_epi64x(m15, m13) +#define LOAD_MSG_11_1(b0, b1) b0 = _mm_set_epi64x(m4, m14); b1 = _mm_set_epi64x(m13, m9) +#define LOAD_MSG_11_2(b0, b1) b0 = _mm_set_epi64x(m8, m10); b1 = _mm_set_epi64x(m6, m15) +#define LOAD_MSG_11_3(b0, b1) b0 = _mm_set_epi64x(m0, m1); b1 = _mm_set_epi64x(m5, m11) +#define LOAD_MSG_11_4(b0, b1) b0 = _mm_set_epi64x(m2, m12); b1 = _mm_set_epi64x(m3, m7) + + +#endif diff --git a/src/Native/libmultihash/blake2/sse/blake2b-load-sse41.h b/src/Native/libmultihash/blake2/sse/blake2b-load-sse41.h new file mode 100644 index 000000000..0eca86599 --- /dev/null +++ b/src/Native/libmultihash/blake2/sse/blake2b-load-sse41.h @@ -0,0 +1,402 @@ +/* + BLAKE2 reference source code package - optimized C implementations + + Copyright 2012, Samuel Neves . You may use this under the + terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at + your option. The terms of these licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ +#ifndef BLAKE2B_LOAD_SSE41_H +#define BLAKE2B_LOAD_SSE41_H + +#define LOAD_MSG_0_1(b0, b1) \ +do \ +{ \ +b0 = _mm_unpacklo_epi64(m0, m1); \ +b1 = _mm_unpacklo_epi64(m2, m3); \ +} while(0) + + +#define LOAD_MSG_0_2(b0, b1) \ +do \ +{ \ +b0 = _mm_unpackhi_epi64(m0, m1); \ +b1 = _mm_unpackhi_epi64(m2, m3); \ +} while(0) + + +#define LOAD_MSG_0_3(b0, b1) \ +do \ +{ \ +b0 = _mm_unpacklo_epi64(m4, m5); \ +b1 = _mm_unpacklo_epi64(m6, m7); \ +} while(0) + + +#define LOAD_MSG_0_4(b0, b1) \ +do \ +{ \ +b0 = _mm_unpackhi_epi64(m4, m5); \ +b1 = _mm_unpackhi_epi64(m6, m7); \ +} while(0) + + +#define LOAD_MSG_1_1(b0, b1) \ +do \ +{ \ +b0 = _mm_unpacklo_epi64(m7, m2); \ +b1 = _mm_unpackhi_epi64(m4, m6); \ +} while(0) + + +#define LOAD_MSG_1_2(b0, b1) \ +do \ +{ \ +b0 = _mm_unpacklo_epi64(m5, m4); \ +b1 = _mm_alignr_epi8(m3, m7, 8); \ +} while(0) + + +#define LOAD_MSG_1_3(b0, b1) \ +do \ +{ \ +b0 = _mm_shuffle_epi32(m0, _MM_SHUFFLE(1,0,3,2)); \ +b1 = _mm_unpackhi_epi64(m5, m2); \ +} while(0) + + +#define LOAD_MSG_1_4(b0, b1) \ +do \ +{ \ +b0 = _mm_unpacklo_epi64(m6, m1); \ +b1 = _mm_unpackhi_epi64(m3, m1); \ +} while(0) + + +#define LOAD_MSG_2_1(b0, b1) \ +do \ +{ \ +b0 = _mm_alignr_epi8(m6, m5, 8); \ +b1 = _mm_unpackhi_epi64(m2, m7); \ +} while(0) + + +#define LOAD_MSG_2_2(b0, b1) \ +do \ +{ \ +b0 = _mm_unpacklo_epi64(m4, m0); \ +b1 = _mm_blend_epi16(m1, m6, 0xF0); \ +} while(0) + + +#define LOAD_MSG_2_3(b0, b1) \ +do \ +{ \ +b0 = _mm_blend_epi16(m5, m1, 0xF0); \ +b1 = _mm_unpackhi_epi64(m3, m4); \ +} while(0) + + +#define LOAD_MSG_2_4(b0, b1) \ +do \ +{ \ +b0 = _mm_unpacklo_epi64(m7, m3); \ +b1 = _mm_alignr_epi8(m2, m0, 8); \ +} while(0) + + +#define LOAD_MSG_3_1(b0, b1) \ +do \ +{ \ +b0 = _mm_unpackhi_epi64(m3, m1); \ +b1 = _mm_unpackhi_epi64(m6, m5); \ +} while(0) + + +#define LOAD_MSG_3_2(b0, b1) \ +do \ +{ \ +b0 = _mm_unpackhi_epi64(m4, m0); \ +b1 = _mm_unpacklo_epi64(m6, m7); \ +} while(0) + + +#define LOAD_MSG_3_3(b0, b1) \ +do \ +{ \ +b0 = _mm_blend_epi16(m1, m2, 0xF0); \ +b1 = _mm_blend_epi16(m2, m7, 0xF0); \ +} while(0) + + +#define LOAD_MSG_3_4(b0, b1) \ +do \ +{ \ +b0 = _mm_unpacklo_epi64(m3, m5); \ +b1 = _mm_unpacklo_epi64(m0, m4); \ +} while(0) + + +#define LOAD_MSG_4_1(b0, b1) \ +do \ +{ \ +b0 = _mm_unpackhi_epi64(m4, m2); \ +b1 = _mm_unpacklo_epi64(m1, m5); \ +} while(0) + + +#define LOAD_MSG_4_2(b0, b1) \ +do \ +{ \ +b0 = _mm_blend_epi16(m0, m3, 0xF0); \ +b1 = _mm_blend_epi16(m2, m7, 0xF0); \ +} while(0) + + +#define LOAD_MSG_4_3(b0, b1) \ +do \ +{ \ +b0 = _mm_blend_epi16(m7, m5, 0xF0); \ +b1 = _mm_blend_epi16(m3, m1, 0xF0); \ +} while(0) + + +#define LOAD_MSG_4_4(b0, b1) \ +do \ +{ \ +b0 = _mm_alignr_epi8(m6, m0, 8); \ +b1 = _mm_blend_epi16(m4, m6, 0xF0); \ +} while(0) + + +#define LOAD_MSG_5_1(b0, b1) \ +do \ +{ \ +b0 = _mm_unpacklo_epi64(m1, m3); \ +b1 = _mm_unpacklo_epi64(m0, m4); \ +} while(0) + + +#define LOAD_MSG_5_2(b0, b1) \ +do \ +{ \ +b0 = _mm_unpacklo_epi64(m6, m5); \ +b1 = _mm_unpackhi_epi64(m5, m1); \ +} while(0) + + +#define LOAD_MSG_5_3(b0, b1) \ +do \ +{ \ +b0 = _mm_blend_epi16(m2, m3, 0xF0); \ +b1 = _mm_unpackhi_epi64(m7, m0); \ +} while(0) + + +#define LOAD_MSG_5_4(b0, b1) \ +do \ +{ \ +b0 = _mm_unpackhi_epi64(m6, m2); \ +b1 = _mm_blend_epi16(m7, m4, 0xF0); \ +} while(0) + + +#define LOAD_MSG_6_1(b0, b1) \ +do \ +{ \ +b0 = _mm_blend_epi16(m6, m0, 0xF0); \ +b1 = _mm_unpacklo_epi64(m7, m2); \ +} while(0) + + +#define LOAD_MSG_6_2(b0, b1) \ +do \ +{ \ +b0 = _mm_unpackhi_epi64(m2, m7); \ +b1 = _mm_alignr_epi8(m5, m6, 8); \ +} while(0) + + +#define LOAD_MSG_6_3(b0, b1) \ +do \ +{ \ +b0 = _mm_unpacklo_epi64(m0, m3); \ +b1 = _mm_shuffle_epi32(m4, _MM_SHUFFLE(1,0,3,2)); \ +} while(0) + + +#define LOAD_MSG_6_4(b0, b1) \ +do \ +{ \ +b0 = _mm_unpackhi_epi64(m3, m1); \ +b1 = _mm_blend_epi16(m1, m5, 0xF0); \ +} while(0) + + +#define LOAD_MSG_7_1(b0, b1) \ +do \ +{ \ +b0 = _mm_unpackhi_epi64(m6, m3); \ +b1 = _mm_blend_epi16(m6, m1, 0xF0); \ +} while(0) + + +#define LOAD_MSG_7_2(b0, b1) \ +do \ +{ \ +b0 = _mm_alignr_epi8(m7, m5, 8); \ +b1 = _mm_unpackhi_epi64(m0, m4); \ +} while(0) + + +#define LOAD_MSG_7_3(b0, b1) \ +do \ +{ \ +b0 = _mm_unpackhi_epi64(m2, m7); \ +b1 = _mm_unpacklo_epi64(m4, m1); \ +} while(0) + + +#define LOAD_MSG_7_4(b0, b1) \ +do \ +{ \ +b0 = _mm_unpacklo_epi64(m0, m2); \ +b1 = _mm_unpacklo_epi64(m3, m5); \ +} while(0) + + +#define LOAD_MSG_8_1(b0, b1) \ +do \ +{ \ +b0 = _mm_unpacklo_epi64(m3, m7); \ +b1 = _mm_alignr_epi8(m0, m5, 8); \ +} while(0) + + +#define LOAD_MSG_8_2(b0, b1) \ +do \ +{ \ +b0 = _mm_unpackhi_epi64(m7, m4); \ +b1 = _mm_alignr_epi8(m4, m1, 8); \ +} while(0) + + +#define LOAD_MSG_8_3(b0, b1) \ +do \ +{ \ +b0 = m6; \ +b1 = _mm_alignr_epi8(m5, m0, 8); \ +} while(0) + + +#define LOAD_MSG_8_4(b0, b1) \ +do \ +{ \ +b0 = _mm_blend_epi16(m1, m3, 0xF0); \ +b1 = m2; \ +} while(0) + + +#define LOAD_MSG_9_1(b0, b1) \ +do \ +{ \ +b0 = _mm_unpacklo_epi64(m5, m4); \ +b1 = _mm_unpackhi_epi64(m3, m0); \ +} while(0) + + +#define LOAD_MSG_9_2(b0, b1) \ +do \ +{ \ +b0 = _mm_unpacklo_epi64(m1, m2); \ +b1 = _mm_blend_epi16(m3, m2, 0xF0); \ +} while(0) + + +#define LOAD_MSG_9_3(b0, b1) \ +do \ +{ \ +b0 = _mm_unpackhi_epi64(m7, m4); \ +b1 = _mm_unpackhi_epi64(m1, m6); \ +} while(0) + + +#define LOAD_MSG_9_4(b0, b1) \ +do \ +{ \ +b0 = _mm_alignr_epi8(m7, m5, 8); \ +b1 = _mm_unpacklo_epi64(m6, m0); \ +} while(0) + + +#define LOAD_MSG_10_1(b0, b1) \ +do \ +{ \ +b0 = _mm_unpacklo_epi64(m0, m1); \ +b1 = _mm_unpacklo_epi64(m2, m3); \ +} while(0) + + +#define LOAD_MSG_10_2(b0, b1) \ +do \ +{ \ +b0 = _mm_unpackhi_epi64(m0, m1); \ +b1 = _mm_unpackhi_epi64(m2, m3); \ +} while(0) + + +#define LOAD_MSG_10_3(b0, b1) \ +do \ +{ \ +b0 = _mm_unpacklo_epi64(m4, m5); \ +b1 = _mm_unpacklo_epi64(m6, m7); \ +} while(0) + + +#define LOAD_MSG_10_4(b0, b1) \ +do \ +{ \ +b0 = _mm_unpackhi_epi64(m4, m5); \ +b1 = _mm_unpackhi_epi64(m6, m7); \ +} while(0) + + +#define LOAD_MSG_11_1(b0, b1) \ +do \ +{ \ +b0 = _mm_unpacklo_epi64(m7, m2); \ +b1 = _mm_unpackhi_epi64(m4, m6); \ +} while(0) + + +#define LOAD_MSG_11_2(b0, b1) \ +do \ +{ \ +b0 = _mm_unpacklo_epi64(m5, m4); \ +b1 = _mm_alignr_epi8(m3, m7, 8); \ +} while(0) + + +#define LOAD_MSG_11_3(b0, b1) \ +do \ +{ \ +b0 = _mm_shuffle_epi32(m0, _MM_SHUFFLE(1,0,3,2)); \ +b1 = _mm_unpackhi_epi64(m5, m2); \ +} while(0) + + +#define LOAD_MSG_11_4(b0, b1) \ +do \ +{ \ +b0 = _mm_unpacklo_epi64(m6, m1); \ +b1 = _mm_unpackhi_epi64(m3, m1); \ +} while(0) + + +#endif diff --git a/src/Native/libmultihash/blake2/sse/blake2b-round.h b/src/Native/libmultihash/blake2/sse/blake2b-round.h new file mode 100644 index 000000000..6537fff32 --- /dev/null +++ b/src/Native/libmultihash/blake2/sse/blake2b-round.h @@ -0,0 +1,157 @@ +/* + BLAKE2 reference source code package - optimized C implementations + + Copyright 2012, Samuel Neves . You may use this under the + terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at + your option. The terms of these licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ +#ifndef BLAKE2B_ROUND_H +#define BLAKE2B_ROUND_H + +#define LOADU(p) _mm_loadu_si128( (const __m128i *)(p) ) +#define STOREU(p,r) _mm_storeu_si128((__m128i *)(p), r) + +#define TOF(reg) _mm_castsi128_ps((reg)) +#define TOI(reg) _mm_castps_si128((reg)) + +#define LIKELY(x) __builtin_expect((x),1) + + +/* Microarchitecture-specific macros */ +#ifndef HAVE_XOP +#ifdef HAVE_SSSE3 +#define _mm_roti_epi64(x, c) \ + (-(c) == 32) ? _mm_shuffle_epi32((x), _MM_SHUFFLE(2,3,0,1)) \ + : (-(c) == 24) ? _mm_shuffle_epi8((x), r24) \ + : (-(c) == 16) ? _mm_shuffle_epi8((x), r16) \ + : (-(c) == 63) ? _mm_xor_si128(_mm_srli_epi64((x), -(c)), _mm_add_epi64((x), (x))) \ + : _mm_xor_si128(_mm_srli_epi64((x), -(c)), _mm_slli_epi64((x), 64-(-(c)))) +#else +#define _mm_roti_epi64(r, c) _mm_xor_si128(_mm_srli_epi64( (r), -(c) ),_mm_slli_epi64( (r), 64-(-(c)) )) +#endif +#else +/* ... */ +#endif + + + +#define G1(row1l,row2l,row3l,row4l,row1h,row2h,row3h,row4h,b0,b1) \ + row1l = _mm_add_epi64(_mm_add_epi64(row1l, b0), row2l); \ + row1h = _mm_add_epi64(_mm_add_epi64(row1h, b1), row2h); \ + \ + row4l = _mm_xor_si128(row4l, row1l); \ + row4h = _mm_xor_si128(row4h, row1h); \ + \ + row4l = _mm_roti_epi64(row4l, -32); \ + row4h = _mm_roti_epi64(row4h, -32); \ + \ + row3l = _mm_add_epi64(row3l, row4l); \ + row3h = _mm_add_epi64(row3h, row4h); \ + \ + row2l = _mm_xor_si128(row2l, row3l); \ + row2h = _mm_xor_si128(row2h, row3h); \ + \ + row2l = _mm_roti_epi64(row2l, -24); \ + row2h = _mm_roti_epi64(row2h, -24); \ + +#define G2(row1l,row2l,row3l,row4l,row1h,row2h,row3h,row4h,b0,b1) \ + row1l = _mm_add_epi64(_mm_add_epi64(row1l, b0), row2l); \ + row1h = _mm_add_epi64(_mm_add_epi64(row1h, b1), row2h); \ + \ + row4l = _mm_xor_si128(row4l, row1l); \ + row4h = _mm_xor_si128(row4h, row1h); \ + \ + row4l = _mm_roti_epi64(row4l, -16); \ + row4h = _mm_roti_epi64(row4h, -16); \ + \ + row3l = _mm_add_epi64(row3l, row4l); \ + row3h = _mm_add_epi64(row3h, row4h); \ + \ + row2l = _mm_xor_si128(row2l, row3l); \ + row2h = _mm_xor_si128(row2h, row3h); \ + \ + row2l = _mm_roti_epi64(row2l, -63); \ + row2h = _mm_roti_epi64(row2h, -63); \ + +#if defined(HAVE_SSSE3) +#define DIAGONALIZE(row1l,row2l,row3l,row4l,row1h,row2h,row3h,row4h) \ + t0 = _mm_alignr_epi8(row2h, row2l, 8); \ + t1 = _mm_alignr_epi8(row2l, row2h, 8); \ + row2l = t0; \ + row2h = t1; \ + \ + t0 = row3l; \ + row3l = row3h; \ + row3h = t0; \ + \ + t0 = _mm_alignr_epi8(row4h, row4l, 8); \ + t1 = _mm_alignr_epi8(row4l, row4h, 8); \ + row4l = t1; \ + row4h = t0; + +#define UNDIAGONALIZE(row1l,row2l,row3l,row4l,row1h,row2h,row3h,row4h) \ + t0 = _mm_alignr_epi8(row2l, row2h, 8); \ + t1 = _mm_alignr_epi8(row2h, row2l, 8); \ + row2l = t0; \ + row2h = t1; \ + \ + t0 = row3l; \ + row3l = row3h; \ + row3h = t0; \ + \ + t0 = _mm_alignr_epi8(row4l, row4h, 8); \ + t1 = _mm_alignr_epi8(row4h, row4l, 8); \ + row4l = t1; \ + row4h = t0; +#else + +#define DIAGONALIZE(row1l,row2l,row3l,row4l,row1h,row2h,row3h,row4h) \ + t0 = row4l;\ + t1 = row2l;\ + row4l = row3l;\ + row3l = row3h;\ + row3h = row4l;\ + row4l = _mm_unpackhi_epi64(row4h, _mm_unpacklo_epi64(t0, t0)); \ + row4h = _mm_unpackhi_epi64(t0, _mm_unpacklo_epi64(row4h, row4h)); \ + row2l = _mm_unpackhi_epi64(row2l, _mm_unpacklo_epi64(row2h, row2h)); \ + row2h = _mm_unpackhi_epi64(row2h, _mm_unpacklo_epi64(t1, t1)) + +#define UNDIAGONALIZE(row1l,row2l,row3l,row4l,row1h,row2h,row3h,row4h) \ + t0 = row3l;\ + row3l = row3h;\ + row3h = t0;\ + t0 = row2l;\ + t1 = row4l;\ + row2l = _mm_unpackhi_epi64(row2h, _mm_unpacklo_epi64(row2l, row2l)); \ + row2h = _mm_unpackhi_epi64(t0, _mm_unpacklo_epi64(row2h, row2h)); \ + row4l = _mm_unpackhi_epi64(row4l, _mm_unpacklo_epi64(row4h, row4h)); \ + row4h = _mm_unpackhi_epi64(row4h, _mm_unpacklo_epi64(t1, t1)) + +#endif + +#if defined(HAVE_SSE41) +#include "blake2b-load-sse41.h" +#else +#include "blake2b-load-sse2.h" +#endif + +#define ROUND(r) \ + LOAD_MSG_ ##r ##_1(b0, b1); \ + G1(row1l,row2l,row3l,row4l,row1h,row2h,row3h,row4h,b0,b1); \ + LOAD_MSG_ ##r ##_2(b0, b1); \ + G2(row1l,row2l,row3l,row4l,row1h,row2h,row3h,row4h,b0,b1); \ + DIAGONALIZE(row1l,row2l,row3l,row4l,row1h,row2h,row3h,row4h); \ + LOAD_MSG_ ##r ##_3(b0, b1); \ + G1(row1l,row2l,row3l,row4l,row1h,row2h,row3h,row4h,b0,b1); \ + LOAD_MSG_ ##r ##_4(b0, b1); \ + G2(row1l,row2l,row3l,row4l,row1h,row2h,row3h,row4h,b0,b1); \ + UNDIAGONALIZE(row1l,row2l,row3l,row4l,row1h,row2h,row3h,row4h); + +#endif diff --git a/src/Native/libmultihash/blake2/sse/blake2b.c b/src/Native/libmultihash/blake2/sse/blake2b.c new file mode 100644 index 000000000..c8c1c5f17 --- /dev/null +++ b/src/Native/libmultihash/blake2/sse/blake2b.c @@ -0,0 +1,373 @@ +/* + BLAKE2 reference source code package - optimized C implementations + + Copyright 2012, Samuel Neves . You may use this under the + terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at + your option. The terms of these licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ + +#include +#include +#include + +#include "blake2.h" +#include "blake2-impl.h" + +#include "blake2-config.h" + +#ifdef _MSC_VER +#include /* for _mm_set_epi64x */ +#endif +#include +#if defined(HAVE_SSSE3) +#include +#endif +#if defined(HAVE_SSE41) +#include +#endif +#if defined(HAVE_AVX) +#include +#endif +#if defined(HAVE_XOP) +#include +#endif + +#include "blake2b-round.h" + +static const uint64_t blake2b_IV[8] = +{ + 0x6a09e667f3bcc908ULL, 0xbb67ae8584caa73bULL, + 0x3c6ef372fe94f82bULL, 0xa54ff53a5f1d36f1ULL, + 0x510e527fade682d1ULL, 0x9b05688c2b3e6c1fULL, + 0x1f83d9abfb41bd6bULL, 0x5be0cd19137e2179ULL +}; + +/* Some helper functions */ +static void blake2b_set_lastnode( blake2b_state *S ) +{ + S->f[1] = (uint64_t)-1; +} + +static int blake2b_is_lastblock( const blake2b_state *S ) +{ + return S->f[0] != 0; +} + +static void blake2b_set_lastblock( blake2b_state *S ) +{ + if( S->last_node ) blake2b_set_lastnode( S ); + + S->f[0] = (uint64_t)-1; +} + +static void blake2b_increment_counter( blake2b_state *S, const uint64_t inc ) +{ + S->t[0] += inc; + S->t[1] += ( S->t[0] < inc ); +} + +/* init xors IV with input parameter block */ +int blake2b_init_param( blake2b_state *S, const blake2b_param *P ) +{ + size_t i; + /*blake2b_init0( S ); */ + const unsigned char * v = ( const unsigned char * )( blake2b_IV ); + const unsigned char * p = ( const unsigned char * )( P ); + unsigned char * h = ( unsigned char * )( S->h ); + /* IV XOR ParamBlock */ + memset( S, 0, sizeof( blake2b_state ) ); + + for( i = 0; i < BLAKE2B_OUTBYTES; ++i ) h[i] = v[i] ^ p[i]; + + S->outlen = P->digest_length; + return 0; +} + + +/* Some sort of default parameter block initialization, for sequential blake2b */ +int blake2b_init( blake2b_state *S, size_t outlen ) +{ + blake2b_param P[1]; + + if ( ( !outlen ) || ( outlen > BLAKE2B_OUTBYTES ) ) return -1; + + P->digest_length = (uint8_t)outlen; + P->key_length = 0; + P->fanout = 1; + P->depth = 1; + store32( &P->leaf_length, 0 ); + store32( &P->node_offset, 0 ); + store32( &P->xof_length, 0 ); + P->node_depth = 0; + P->inner_length = 0; + memset( P->reserved, 0, sizeof( P->reserved ) ); + memset( P->salt, 0, sizeof( P->salt ) ); + memset( P->personal, 0, sizeof( P->personal ) ); + + return blake2b_init_param( S, P ); +} + +int blake2b_init_key( blake2b_state *S, size_t outlen, const void *key, size_t keylen ) +{ + blake2b_param P[1]; + + if ( ( !outlen ) || ( outlen > BLAKE2B_OUTBYTES ) ) return -1; + + if ( ( !keylen ) || keylen > BLAKE2B_KEYBYTES ) return -1; + + P->digest_length = (uint8_t)outlen; + P->key_length = (uint8_t)keylen; + P->fanout = 1; + P->depth = 1; + store32( &P->leaf_length, 0 ); + store32( &P->node_offset, 0 ); + store32( &P->xof_length, 0 ); + P->node_depth = 0; + P->inner_length = 0; + memset( P->reserved, 0, sizeof( P->reserved ) ); + memset( P->salt, 0, sizeof( P->salt ) ); + memset( P->personal, 0, sizeof( P->personal ) ); + + if( blake2b_init_param( S, P ) < 0 ) + return 0; + + { + uint8_t block[BLAKE2B_BLOCKBYTES]; + memset( block, 0, BLAKE2B_BLOCKBYTES ); + memcpy( block, key, keylen ); + blake2b_update( S, block, BLAKE2B_BLOCKBYTES ); + secure_zero_memory( block, BLAKE2B_BLOCKBYTES ); /* Burn the key from stack */ + } + return 0; +} + +static void blake2b_compress( blake2b_state *S, const uint8_t block[BLAKE2B_BLOCKBYTES] ) +{ + __m128i row1l, row1h; + __m128i row2l, row2h; + __m128i row3l, row3h; + __m128i row4l, row4h; + __m128i b0, b1; + __m128i t0, t1; +#if defined(HAVE_SSSE3) && !defined(HAVE_XOP) + const __m128i r16 = _mm_setr_epi8( 2, 3, 4, 5, 6, 7, 0, 1, 10, 11, 12, 13, 14, 15, 8, 9 ); + const __m128i r24 = _mm_setr_epi8( 3, 4, 5, 6, 7, 0, 1, 2, 11, 12, 13, 14, 15, 8, 9, 10 ); +#endif +#if defined(HAVE_SSE41) + const __m128i m0 = LOADU( block + 00 ); + const __m128i m1 = LOADU( block + 16 ); + const __m128i m2 = LOADU( block + 32 ); + const __m128i m3 = LOADU( block + 48 ); + const __m128i m4 = LOADU( block + 64 ); + const __m128i m5 = LOADU( block + 80 ); + const __m128i m6 = LOADU( block + 96 ); + const __m128i m7 = LOADU( block + 112 ); +#else + const uint64_t m0 = load64(block + 0 * sizeof(uint64_t)); + const uint64_t m1 = load64(block + 1 * sizeof(uint64_t)); + const uint64_t m2 = load64(block + 2 * sizeof(uint64_t)); + const uint64_t m3 = load64(block + 3 * sizeof(uint64_t)); + const uint64_t m4 = load64(block + 4 * sizeof(uint64_t)); + const uint64_t m5 = load64(block + 5 * sizeof(uint64_t)); + const uint64_t m6 = load64(block + 6 * sizeof(uint64_t)); + const uint64_t m7 = load64(block + 7 * sizeof(uint64_t)); + const uint64_t m8 = load64(block + 8 * sizeof(uint64_t)); + const uint64_t m9 = load64(block + 9 * sizeof(uint64_t)); + const uint64_t m10 = load64(block + 10 * sizeof(uint64_t)); + const uint64_t m11 = load64(block + 11 * sizeof(uint64_t)); + const uint64_t m12 = load64(block + 12 * sizeof(uint64_t)); + const uint64_t m13 = load64(block + 13 * sizeof(uint64_t)); + const uint64_t m14 = load64(block + 14 * sizeof(uint64_t)); + const uint64_t m15 = load64(block + 15 * sizeof(uint64_t)); +#endif + row1l = LOADU( &S->h[0] ); + row1h = LOADU( &S->h[2] ); + row2l = LOADU( &S->h[4] ); + row2h = LOADU( &S->h[6] ); + row3l = LOADU( &blake2b_IV[0] ); + row3h = LOADU( &blake2b_IV[2] ); + row4l = _mm_xor_si128( LOADU( &blake2b_IV[4] ), LOADU( &S->t[0] ) ); + row4h = _mm_xor_si128( LOADU( &blake2b_IV[6] ), LOADU( &S->f[0] ) ); + ROUND( 0 ); + ROUND( 1 ); + ROUND( 2 ); + ROUND( 3 ); + ROUND( 4 ); + ROUND( 5 ); + ROUND( 6 ); + ROUND( 7 ); + ROUND( 8 ); + ROUND( 9 ); + ROUND( 10 ); + ROUND( 11 ); + row1l = _mm_xor_si128( row3l, row1l ); + row1h = _mm_xor_si128( row3h, row1h ); + STOREU( &S->h[0], _mm_xor_si128( LOADU( &S->h[0] ), row1l ) ); + STOREU( &S->h[2], _mm_xor_si128( LOADU( &S->h[2] ), row1h ) ); + row2l = _mm_xor_si128( row4l, row2l ); + row2h = _mm_xor_si128( row4h, row2h ); + STOREU( &S->h[4], _mm_xor_si128( LOADU( &S->h[4] ), row2l ) ); + STOREU( &S->h[6], _mm_xor_si128( LOADU( &S->h[6] ), row2h ) ); +} + + +int blake2b_update( blake2b_state *S, const void *pin, size_t inlen ) +{ + const unsigned char * in = (const unsigned char *)pin; + if( inlen > 0 ) + { + size_t left = S->buflen; + size_t fill = BLAKE2B_BLOCKBYTES - left; + if( inlen > fill ) + { + S->buflen = 0; + memcpy( S->buf + left, in, fill ); /* Fill buffer */ + blake2b_increment_counter( S, BLAKE2B_BLOCKBYTES ); + blake2b_compress( S, S->buf ); /* Compress */ + in += fill; inlen -= fill; + while(inlen > BLAKE2B_BLOCKBYTES) { + blake2b_increment_counter(S, BLAKE2B_BLOCKBYTES); + blake2b_compress( S, in ); + in += BLAKE2B_BLOCKBYTES; + inlen -= BLAKE2B_BLOCKBYTES; + } + } + memcpy( S->buf + S->buflen, in, inlen ); + S->buflen += inlen; + } + return 0; +} + + +int blake2b_final( blake2b_state *S, void *out, size_t outlen ) +{ + if( out == NULL || outlen < S->outlen ) + return -1; + + if( blake2b_is_lastblock( S ) ) + return -1; + + blake2b_increment_counter( S, S->buflen ); + blake2b_set_lastblock( S ); + memset( S->buf + S->buflen, 0, BLAKE2B_BLOCKBYTES - S->buflen ); /* Padding */ + blake2b_compress( S, S->buf ); + + memcpy( out, &S->h[0], S->outlen ); + return 0; +} + + +int blake2b( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen ) +{ + blake2b_state S[1]; + + /* Verify parameters */ + if ( NULL == in && inlen > 0 ) return -1; + + if ( NULL == out ) return -1; + + if( NULL == key && keylen > 0 ) return -1; + + if( !outlen || outlen > BLAKE2B_OUTBYTES ) return -1; + + if( keylen > BLAKE2B_KEYBYTES ) return -1; + + if( keylen ) + { + if( blake2b_init_key( S, outlen, key, keylen ) < 0 ) return -1; + } + else + { + if( blake2b_init( S, outlen ) < 0 ) return -1; + } + + blake2b_update( S, ( const uint8_t * )in, inlen ); + blake2b_final( S, out, outlen ); + return 0; +} + +int blake2( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen ) { + return blake2b(out, outlen, in, inlen, key, keylen); +} + +#if defined(SUPERCOP) +int crypto_hash( unsigned char *out, unsigned char *in, unsigned long long inlen ) +{ + return blake2b( out, BLAKE2B_OUTBYTES, in, inlen, NULL, 0 ); +} +#endif + +#if defined(BLAKE2B_SELFTEST) +#include +#include "blake2-kat.h" +int main( void ) +{ + uint8_t key[BLAKE2B_KEYBYTES]; + uint8_t buf[BLAKE2_KAT_LENGTH]; + size_t i, step; + + for( i = 0; i < BLAKE2B_KEYBYTES; ++i ) + key[i] = ( uint8_t )i; + + for( i = 0; i < BLAKE2_KAT_LENGTH; ++i ) + buf[i] = ( uint8_t )i; + + /* Test simple API */ + for( i = 0; i < BLAKE2_KAT_LENGTH; ++i ) + { + uint8_t hash[BLAKE2B_OUTBYTES]; + blake2b( hash, BLAKE2B_OUTBYTES, buf, i, key, BLAKE2B_KEYBYTES ); + + if( 0 != memcmp( hash, blake2b_keyed_kat[i], BLAKE2B_OUTBYTES ) ) + { + goto fail; + } + } + + /* Test streaming API */ + for(step = 1; step < BLAKE2B_BLOCKBYTES; ++step) { + for (i = 0; i < BLAKE2_KAT_LENGTH; ++i) { + uint8_t hash[BLAKE2B_OUTBYTES]; + blake2b_state S; + uint8_t * p = buf; + size_t mlen = i; + int err = 0; + + if( (err = blake2b_init_key(&S, BLAKE2B_OUTBYTES, key, BLAKE2B_KEYBYTES)) < 0 ) { + goto fail; + } + + while (mlen >= step) { + if ( (err = blake2b_update(&S, p, step)) < 0 ) { + goto fail; + } + mlen -= step; + p += step; + } + if ( (err = blake2b_update(&S, p, mlen)) < 0) { + goto fail; + } + if ( (err = blake2b_final(&S, hash, BLAKE2B_OUTBYTES)) < 0) { + goto fail; + } + + if (0 != memcmp(hash, blake2b_keyed_kat[i], BLAKE2B_OUTBYTES)) { + goto fail; + } + } + } + + puts( "ok" ); + return 0; +fail: + puts("error"); + return -1; +} +#endif diff --git a/src/Native/libmultihash/blake2/sse/blake2bp b/src/Native/libmultihash/blake2/sse/blake2bp new file mode 100644 index 0000000000000000000000000000000000000000..301fe02d50b4ab3c803b95870a91514fae0a7c69 GIT binary patch literal 50488 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

. You may use this under the + terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at + your option. The terms of these licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> + +#if defined(_OPENMP) +#include <omp.h> +#endif + +#include "blake2.h" +#include "blake2-impl.h" + +#define PARALLELISM_DEGREE 4 + +/* + blake2b_init_param defaults to setting the expecting output length + from the digest_length parameter block field. + + In some cases, however, we do not want this, as the output length + of these instances is given by inner_length instead. +*/ +static int blake2bp_init_leaf_param( blake2b_state *S, const blake2b_param *P ) +{ + int err = blake2b_init_param(S, P); + S->outlen = P->inner_length; + return err; +} + +static int blake2bp_init_leaf( blake2b_state *S, size_t outlen, size_t keylen, uint64_t offset ) +{ + blake2b_param P[1]; + P->digest_length = (uint8_t)outlen; + P->key_length = (uint8_t)keylen; + P->fanout = PARALLELISM_DEGREE; + P->depth = 2; + P->leaf_length = 0; + P->node_offset = offset; + P->xof_length = 0; + P->node_depth = 0; + P->inner_length = BLAKE2B_OUTBYTES; + memset( P->reserved, 0, sizeof( P->reserved ) ); + memset( P->salt, 0, sizeof( P->salt ) ); + memset( P->personal, 0, sizeof( P->personal ) ); + return blake2bp_init_leaf_param( S, P ); +} + +static int blake2bp_init_root( blake2b_state *S, size_t outlen, size_t keylen ) +{ + blake2b_param P[1]; + P->digest_length = (uint8_t)outlen; + P->key_length = (uint8_t)keylen; + P->fanout = PARALLELISM_DEGREE; + P->depth = 2; + P->leaf_length = 0; + P->node_offset = 0; + P->xof_length = 0; + P->node_depth = 1; + P->inner_length = BLAKE2B_OUTBYTES; + memset( P->reserved, 0, sizeof( P->reserved ) ); + memset( P->salt, 0, sizeof( P->salt ) ); + memset( P->personal, 0, sizeof( P->personal ) ); + return blake2b_init_param( S, P ); +} + + +int blake2bp_init( blake2bp_state *S, size_t outlen ) +{ + size_t i; + if( !outlen || outlen > BLAKE2B_OUTBYTES ) return -1; + + memset( S->buf, 0, sizeof( S->buf ) ); + S->buflen = 0; + S->outlen = outlen; + + if( blake2bp_init_root( S->R, outlen, 0 ) < 0 ) + return -1; + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + if( blake2bp_init_leaf( S->S[i], outlen, 0, i ) < 0 ) return -1; + + S->R->last_node = 1; + S->S[PARALLELISM_DEGREE - 1]->last_node = 1; + return 0; +} + +int blake2bp_init_key( blake2bp_state *S, size_t outlen, const void *key, size_t keylen ) +{ + size_t i; + + if( !outlen || outlen > BLAKE2B_OUTBYTES ) return -1; + + if( !key || !keylen || keylen > BLAKE2B_KEYBYTES ) return -1; + + memset( S->buf, 0, sizeof( S->buf ) ); + S->buflen = 0; + S->outlen = outlen; + + if( blake2bp_init_root( S->R, outlen, keylen ) < 0 ) + return -1; + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + if( blake2bp_init_leaf( S->S[i], outlen, keylen, i ) < 0 ) return -1; + + S->R->last_node = 1; + S->S[PARALLELISM_DEGREE - 1]->last_node = 1; + { + uint8_t block[BLAKE2B_BLOCKBYTES]; + memset( block, 0, BLAKE2B_BLOCKBYTES ); + memcpy( block, key, keylen ); + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + blake2b_update( S->S[i], block, BLAKE2B_BLOCKBYTES ); + + secure_zero_memory( block, BLAKE2B_BLOCKBYTES ); /* Burn the key from stack */ + } + return 0; +} + + +int blake2bp_update( blake2bp_state *S, const void *pin, size_t inlen ) +{ + const unsigned char * in = (const unsigned char *)pin; + size_t left = S->buflen; + size_t fill = sizeof( S->buf ) - left; + size_t i; + + if( left && inlen >= fill ) + { + memcpy( S->buf + left, in, fill ); + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + blake2b_update( S->S[i], S->buf + i * BLAKE2B_BLOCKBYTES, BLAKE2B_BLOCKBYTES ); + + in += fill; + inlen -= fill; + left = 0; + } + +#if defined(_OPENMP) + #pragma omp parallel shared(S), num_threads(PARALLELISM_DEGREE) +#else + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) +#endif + { +#if defined(_OPENMP) + size_t i = omp_get_thread_num(); +#endif + size_t inlen__ = inlen; + const unsigned char *in__ = ( const unsigned char * )in; + in__ += i * BLAKE2B_BLOCKBYTES; + + while( inlen__ >= PARALLELISM_DEGREE * BLAKE2B_BLOCKBYTES ) + { + blake2b_update( S->S[i], in__, BLAKE2B_BLOCKBYTES ); + in__ += PARALLELISM_DEGREE * BLAKE2B_BLOCKBYTES; + inlen__ -= PARALLELISM_DEGREE * BLAKE2B_BLOCKBYTES; + } + } + + in += inlen - inlen % ( PARALLELISM_DEGREE * BLAKE2B_BLOCKBYTES ); + inlen %= PARALLELISM_DEGREE * BLAKE2B_BLOCKBYTES; + + if( inlen > 0 ) + memcpy( S->buf + left, in, inlen ); + + S->buflen = left + inlen; + return 0; +} + + + +int blake2bp_final( blake2bp_state *S, void *out, size_t outlen ) +{ + uint8_t hash[PARALLELISM_DEGREE][BLAKE2B_OUTBYTES]; + size_t i; + + if(out == NULL || outlen < S->outlen) { + return -1; + } + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + { + if( S->buflen > i * BLAKE2B_BLOCKBYTES ) + { + size_t left = S->buflen - i * BLAKE2B_BLOCKBYTES; + + if( left > BLAKE2B_BLOCKBYTES ) left = BLAKE2B_BLOCKBYTES; + + blake2b_update( S->S[i], S->buf + i * BLAKE2B_BLOCKBYTES, left ); + } + + blake2b_final( S->S[i], hash[i], BLAKE2B_OUTBYTES ); + } + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + blake2b_update( S->R, hash[i], BLAKE2B_OUTBYTES ); + + return blake2b_final( S->R, out, S->outlen ); +} + +int blake2bp( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen ) +{ + uint8_t hash[PARALLELISM_DEGREE][BLAKE2B_OUTBYTES]; + blake2b_state S[PARALLELISM_DEGREE][1]; + blake2b_state FS[1]; + size_t i; + + /* Verify parameters */ + if ( NULL == in && inlen > 0 ) return -1; + + if ( NULL == out ) return -1; + + if( NULL == key && keylen > 0 ) return -1; + + if( !outlen || outlen > BLAKE2B_OUTBYTES ) return -1; + + if( keylen > BLAKE2B_KEYBYTES ) return -1; + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + if( blake2bp_init_leaf( S[i], outlen, keylen, i ) < 0 ) return -1; + + S[PARALLELISM_DEGREE - 1]->last_node = 1; /* mark last node */ + + if( keylen > 0 ) + { + uint8_t block[BLAKE2B_BLOCKBYTES]; + memset( block, 0, BLAKE2B_BLOCKBYTES ); + memcpy( block, key, keylen ); + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + blake2b_update( S[i], block, BLAKE2B_BLOCKBYTES ); + + secure_zero_memory( block, BLAKE2B_BLOCKBYTES ); /* Burn the key from stack */ + } + +#if defined(_OPENMP) + #pragma omp parallel shared(S,hash), num_threads(PARALLELISM_DEGREE) +#else + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) +#endif + { +#if defined(_OPENMP) + size_t i = omp_get_thread_num(); +#endif + size_t inlen__ = inlen; + const unsigned char *in__ = ( const unsigned char * )in; + in__ += i * BLAKE2B_BLOCKBYTES; + + while( inlen__ >= PARALLELISM_DEGREE * BLAKE2B_BLOCKBYTES ) + { + blake2b_update( S[i], in__, BLAKE2B_BLOCKBYTES ); + in__ += PARALLELISM_DEGREE * BLAKE2B_BLOCKBYTES; + inlen__ -= PARALLELISM_DEGREE * BLAKE2B_BLOCKBYTES; + } + + if( inlen__ > i * BLAKE2B_BLOCKBYTES ) + { + const size_t left = inlen__ - i * BLAKE2B_BLOCKBYTES; + const size_t len = left <= BLAKE2B_BLOCKBYTES ? left : BLAKE2B_BLOCKBYTES; + blake2b_update( S[i], in__, len ); + } + + blake2b_final( S[i], hash[i], BLAKE2B_OUTBYTES ); + } + + if( blake2bp_init_root( FS, outlen, keylen ) < 0 ) + return -1; + + FS->last_node = 1; /* Mark as last node */ + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + blake2b_update( FS, hash[i], BLAKE2B_OUTBYTES ); + + return blake2b_final( FS, out, outlen ); +} + + +#if defined(BLAKE2BP_SELFTEST) +#include <string.h> +#include "blake2-kat.h" +int main( void ) +{ + uint8_t key[BLAKE2B_KEYBYTES]; + uint8_t buf[BLAKE2_KAT_LENGTH]; + size_t i, step; + + for( i = 0; i < BLAKE2B_KEYBYTES; ++i ) + key[i] = ( uint8_t )i; + + for( i = 0; i < BLAKE2_KAT_LENGTH; ++i ) + buf[i] = ( uint8_t )i; + + /* Test simple API */ + for( i = 0; i < BLAKE2_KAT_LENGTH; ++i ) + { + uint8_t hash[BLAKE2B_OUTBYTES]; + blake2bp( hash, BLAKE2B_OUTBYTES, buf, i, key, BLAKE2B_KEYBYTES ); + + if( 0 != memcmp( hash, blake2bp_keyed_kat[i], BLAKE2B_OUTBYTES ) ) + { + goto fail; + } + } + + /* Test streaming API */ + for(step = 1; step < BLAKE2B_BLOCKBYTES; ++step) { + for (i = 0; i < BLAKE2_KAT_LENGTH; ++i) { + uint8_t hash[BLAKE2B_OUTBYTES]; + blake2bp_state S; + uint8_t * p = buf; + size_t mlen = i; + int err = 0; + + if( (err = blake2bp_init_key(&S, BLAKE2B_OUTBYTES, key, BLAKE2B_KEYBYTES)) < 0 ) { + goto fail; + } + + while (mlen >= step) { + if ( (err = blake2bp_update(&S, p, step)) < 0 ) { + goto fail; + } + mlen -= step; + p += step; + } + if ( (err = blake2bp_update(&S, p, mlen)) < 0) { + goto fail; + } + if ( (err = blake2bp_final(&S, hash, BLAKE2B_OUTBYTES)) < 0) { + goto fail; + } + + if (0 != memcmp(hash, blake2bp_keyed_kat[i], BLAKE2B_OUTBYTES)) { + goto fail; + } + } + } + + puts( "ok" ); + return 0; +fail: + puts("error"); + return -1; +} +#endif diff --git a/src/Native/libmultihash/blake2/sse/blake2s b/src/Native/libmultihash/blake2/sse/blake2s new file mode 100644 index 0000000000000000000000000000000000000000..2eda2234bdf70c2801f5af69477b9c4710a7aadf GIT binary patch literal 29616 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 literal 0 HcmV?d00001 diff --git a/src/Native/libmultihash/blake2/sse/blake2s-load-sse2.h b/src/Native/libmultihash/blake2/sse/blake2s-load-sse2.h new file mode 100644 index 000000000..8359e81ab --- /dev/null +++ b/src/Native/libmultihash/blake2/sse/blake2s-load-sse2.h @@ -0,0 +1,60 @@ +/* + BLAKE2 reference source code package - optimized C implementations + + Copyright 2012, Samuel Neves <sneves@dei.uc.pt>. You may use this under the + terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at + your option. The terms of these licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ +#ifndef BLAKE2S_LOAD_SSE2_H +#define BLAKE2S_LOAD_SSE2_H + +#define LOAD_MSG_0_1(buf) buf = _mm_set_epi32(m6,m4,m2,m0) +#define LOAD_MSG_0_2(buf) buf = _mm_set_epi32(m7,m5,m3,m1) +#define LOAD_MSG_0_3(buf) buf = _mm_set_epi32(m12,m10,m8,m14) +#define LOAD_MSG_0_4(buf) buf = _mm_set_epi32(m13,m11,m9,m15) +#define LOAD_MSG_1_1(buf) buf = _mm_set_epi32(m13,m9,m4,m14) +#define LOAD_MSG_1_2(buf) buf = _mm_set_epi32(m6,m15,m8,m10) +#define LOAD_MSG_1_3(buf) buf = _mm_set_epi32(m11,m0,m1,m5) +#define LOAD_MSG_1_4(buf) buf = _mm_set_epi32(m7,m2,m12,m3) +#define LOAD_MSG_2_1(buf) buf = _mm_set_epi32(m15,m5,m12,m11) +#define LOAD_MSG_2_2(buf) buf = _mm_set_epi32(m13,m2,m0,m8) +#define LOAD_MSG_2_3(buf) buf = _mm_set_epi32(m7,m3,m10,m9) +#define LOAD_MSG_2_4(buf) buf = _mm_set_epi32(m1,m6,m14,m4) +#define LOAD_MSG_3_1(buf) buf = _mm_set_epi32(m11,m13,m3,m7) +#define LOAD_MSG_3_2(buf) buf = _mm_set_epi32(m14,m12,m1,m9) +#define LOAD_MSG_3_3(buf) buf = _mm_set_epi32(m4,m5,m2,m15) +#define LOAD_MSG_3_4(buf) buf = _mm_set_epi32(m0,m10,m6,m8) +#define LOAD_MSG_4_1(buf) buf = _mm_set_epi32(m10,m2,m5,m9) +#define LOAD_MSG_4_2(buf) buf = _mm_set_epi32(m15,m4,m7,m0) +#define LOAD_MSG_4_3(buf) buf = _mm_set_epi32(m6,m11,m14,m3) +#define LOAD_MSG_4_4(buf) buf = _mm_set_epi32(m8,m12,m1,m13) +#define LOAD_MSG_5_1(buf) buf = _mm_set_epi32(m8,m0,m6,m2) +#define LOAD_MSG_5_2(buf) buf = _mm_set_epi32(m3,m11,m10,m12) +#define LOAD_MSG_5_3(buf) buf = _mm_set_epi32(m15,m7,m4,m1) +#define LOAD_MSG_5_4(buf) buf = _mm_set_epi32(m14,m5,m13,m9) +#define LOAD_MSG_6_1(buf) buf = _mm_set_epi32(m4,m14,m1,m12) +#define LOAD_MSG_6_2(buf) buf = _mm_set_epi32(m10,m13,m15,m5) +#define LOAD_MSG_6_3(buf) buf = _mm_set_epi32(m9,m6,m0,m8) +#define LOAD_MSG_6_4(buf) buf = _mm_set_epi32(m2,m3,m7,m11) +#define LOAD_MSG_7_1(buf) buf = _mm_set_epi32(m3,m12,m7,m13) +#define LOAD_MSG_7_2(buf) buf = _mm_set_epi32(m9,m1,m14,m11) +#define LOAD_MSG_7_3(buf) buf = _mm_set_epi32(m8,m15,m5,m2) +#define LOAD_MSG_7_4(buf) buf = _mm_set_epi32(m6,m4,m0,m10) +#define LOAD_MSG_8_1(buf) buf = _mm_set_epi32(m0,m11,m14,m6) +#define LOAD_MSG_8_2(buf) buf = _mm_set_epi32(m8,m3,m9,m15) +#define LOAD_MSG_8_3(buf) buf = _mm_set_epi32(m1,m13,m12,m10) +#define LOAD_MSG_8_4(buf) buf = _mm_set_epi32(m4,m7,m2,m5) +#define LOAD_MSG_9_1(buf) buf = _mm_set_epi32(m1,m7,m8,m10) +#define LOAD_MSG_9_2(buf) buf = _mm_set_epi32(m5,m6,m4,m2) +#define LOAD_MSG_9_3(buf) buf = _mm_set_epi32(m3,m9,m15,m13) +#define LOAD_MSG_9_4(buf) buf = _mm_set_epi32(m12,m14,m11,m0) + + +#endif diff --git a/src/Native/libmultihash/blake2/sse/blake2s-load-sse41.h b/src/Native/libmultihash/blake2/sse/blake2s-load-sse41.h new file mode 100644 index 000000000..8d2b6b12f --- /dev/null +++ b/src/Native/libmultihash/blake2/sse/blake2s-load-sse41.h @@ -0,0 +1,236 @@ +/* + BLAKE2 reference source code package - optimized C implementations + + Copyright 2012, Samuel Neves <sneves@dei.uc.pt>. You may use this under the + terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at + your option. The terms of these licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ +#ifndef BLAKE2S_LOAD_SSE41_H +#define BLAKE2S_LOAD_SSE41_H + +#define LOAD_MSG_0_1(buf) \ +buf = TOI(_mm_shuffle_ps(TOF(m0), TOF(m1), _MM_SHUFFLE(2,0,2,0))); + +#define LOAD_MSG_0_2(buf) \ +buf = TOI(_mm_shuffle_ps(TOF(m0), TOF(m1), _MM_SHUFFLE(3,1,3,1))); + +#define LOAD_MSG_0_3(buf) \ +t0 = _mm_shuffle_epi32(m2, _MM_SHUFFLE(3,2,0,1)); \ +t1 = _mm_shuffle_epi32(m3, _MM_SHUFFLE(0,1,3,2)); \ +buf = _mm_blend_epi16(t0, t1, 0xC3); + +#define LOAD_MSG_0_4(buf) \ +t0 = _mm_blend_epi16(t0, t1, 0x3C); \ +buf = _mm_shuffle_epi32(t0, _MM_SHUFFLE(2,3,0,1)); + +#define LOAD_MSG_1_1(buf) \ +t0 = _mm_blend_epi16(m1, m2, 0x0C); \ +t1 = _mm_slli_si128(m3, 4); \ +t2 = _mm_blend_epi16(t0, t1, 0xF0); \ +buf = _mm_shuffle_epi32(t2, _MM_SHUFFLE(2,1,0,3)); + +#define LOAD_MSG_1_2(buf) \ +t0 = _mm_shuffle_epi32(m2,_MM_SHUFFLE(0,0,2,0)); \ +t1 = _mm_blend_epi16(m1,m3,0xC0); \ +t2 = _mm_blend_epi16(t0, t1, 0xF0); \ +buf = _mm_shuffle_epi32(t2, _MM_SHUFFLE(2,3,0,1)); + +#define LOAD_MSG_1_3(buf) \ +t0 = _mm_slli_si128(m1, 4); \ +t1 = _mm_blend_epi16(m2, t0, 0x30); \ +t2 = _mm_blend_epi16(m0, t1, 0xF0); \ +buf = _mm_shuffle_epi32(t2, _MM_SHUFFLE(3,0,1,2)); + +#define LOAD_MSG_1_4(buf) \ +t0 = _mm_unpackhi_epi32(m0,m1); \ +t1 = _mm_slli_si128(m3, 4); \ +t2 = _mm_blend_epi16(t0, t1, 0x0C); \ +buf = _mm_shuffle_epi32(t2, _MM_SHUFFLE(3,0,1,2)); + +#define LOAD_MSG_2_1(buf) \ +t0 = _mm_unpackhi_epi32(m2,m3); \ +t1 = _mm_blend_epi16(m3,m1,0x0C); \ +t2 = _mm_blend_epi16(t0, t1, 0x0F); \ +buf = _mm_shuffle_epi32(t2, _MM_SHUFFLE(3,1,0,2)); + +#define LOAD_MSG_2_2(buf) \ +t0 = _mm_unpacklo_epi32(m2,m0); \ +t1 = _mm_blend_epi16(t0, m0, 0xF0); \ +t2 = _mm_slli_si128(m3, 8); \ +buf = _mm_blend_epi16(t1, t2, 0xC0); + +#define LOAD_MSG_2_3(buf) \ +t0 = _mm_blend_epi16(m0, m2, 0x3C); \ +t1 = _mm_srli_si128(m1, 12); \ +t2 = _mm_blend_epi16(t0,t1,0x03); \ +buf = _mm_shuffle_epi32(t2, _MM_SHUFFLE(0,3,2,1)); + +#define LOAD_MSG_2_4(buf) \ +t0 = _mm_slli_si128(m3, 4); \ +t1 = _mm_blend_epi16(m0, m1, 0x33); \ +t2 = _mm_blend_epi16(t1, t0, 0xC0); \ +buf = _mm_shuffle_epi32(t2, _MM_SHUFFLE(1,2,3,0)); + +#define LOAD_MSG_3_1(buf) \ +t0 = _mm_unpackhi_epi32(m0,m1); \ +t1 = _mm_unpackhi_epi32(t0, m2); \ +t2 = _mm_blend_epi16(t1, m3, 0x0C); \ +buf = _mm_shuffle_epi32(t2, _MM_SHUFFLE(3,1,0,2)); + +#define LOAD_MSG_3_2(buf) \ +t0 = _mm_slli_si128(m2, 8); \ +t1 = _mm_blend_epi16(m3,m0,0x0C); \ +t2 = _mm_blend_epi16(t1, t0, 0xC0); \ +buf = _mm_shuffle_epi32(t2, _MM_SHUFFLE(2,0,1,3)); + +#define LOAD_MSG_3_3(buf) \ +t0 = _mm_blend_epi16(m0,m1,0x0F); \ +t1 = _mm_blend_epi16(t0, m3, 0xC0); \ +buf = _mm_shuffle_epi32(t1, _MM_SHUFFLE(0,1,2,3)); + +#define LOAD_MSG_3_4(buf) \ +t0 = _mm_alignr_epi8(m0, m1, 4); \ +buf = _mm_blend_epi16(t0, m2, 0x33); + +#define LOAD_MSG_4_1(buf) \ +t0 = _mm_unpacklo_epi64(m1,m2); \ +t1 = _mm_unpackhi_epi64(m0,m2); \ +t2 = _mm_blend_epi16(t0,t1,0x33); \ +buf = _mm_shuffle_epi32(t2, _MM_SHUFFLE(2,0,1,3)); + +#define LOAD_MSG_4_2(buf) \ +t0 = _mm_unpackhi_epi64(m1,m3); \ +t1 = _mm_unpacklo_epi64(m0,m1); \ +buf = _mm_blend_epi16(t0,t1,0x33); + +#define LOAD_MSG_4_3(buf) \ +t0 = _mm_unpackhi_epi64(m3,m1); \ +t1 = _mm_unpackhi_epi64(m2,m0); \ +t2 = _mm_blend_epi16(t1,t0,0x33); \ +buf = _mm_shuffle_epi32(t2, _MM_SHUFFLE(2,1,0,3)); + +#define LOAD_MSG_4_4(buf) \ +t0 = _mm_blend_epi16(m0,m2,0x03); \ +t1 = _mm_slli_si128(t0, 8); \ +t2 = _mm_blend_epi16(t1,m3,0x0F); \ +buf = _mm_shuffle_epi32(t2, _MM_SHUFFLE(2,0,3,1)); + +#define LOAD_MSG_5_1(buf) \ +t0 = _mm_unpackhi_epi32(m0,m1); \ +t1 = _mm_unpacklo_epi32(m0,m2); \ +buf = _mm_unpacklo_epi64(t0,t1); + +#define LOAD_MSG_5_2(buf) \ +t0 = _mm_srli_si128(m2, 4); \ +t1 = _mm_blend_epi16(m0,m3,0x03); \ +buf = _mm_blend_epi16(t1,t0,0x3C); + +#define LOAD_MSG_5_3(buf) \ +t0 = _mm_blend_epi16(m1,m0,0x0C); \ +t1 = _mm_srli_si128(m3, 4); \ +t2 = _mm_blend_epi16(t0,t1,0x30); \ +buf = _mm_shuffle_epi32(t2, _MM_SHUFFLE(2,3,0,1)); + +#define LOAD_MSG_5_4(buf) \ +t0 = _mm_unpacklo_epi64(m2,m1); \ +t1 = _mm_shuffle_epi32(m3, _MM_SHUFFLE(2,0,1,0)); \ +t2 = _mm_srli_si128(t0, 4); \ +buf = _mm_blend_epi16(t1,t2,0x33); + +#define LOAD_MSG_6_1(buf) \ +t0 = _mm_slli_si128(m1, 12); \ +t1 = _mm_blend_epi16(m0,m3,0x33); \ +buf = _mm_blend_epi16(t1,t0,0xC0); + +#define LOAD_MSG_6_2(buf) \ +t0 = _mm_blend_epi16(m3,m2,0x30); \ +t1 = _mm_srli_si128(m1, 4); \ +t2 = _mm_blend_epi16(t0,t1,0x03); \ +buf = _mm_shuffle_epi32(t2, _MM_SHUFFLE(2,1,3,0)); + +#define LOAD_MSG_6_3(buf) \ +t0 = _mm_unpacklo_epi64(m0,m2); \ +t1 = _mm_srli_si128(m1, 4); \ +t2 = _mm_blend_epi16(t0,t1,0x0C); \ +buf = _mm_shuffle_epi32(t2, _MM_SHUFFLE(3,1,0,2)); + +#define LOAD_MSG_6_4(buf) \ +t0 = _mm_unpackhi_epi32(m1,m2); \ +t1 = _mm_unpackhi_epi64(m0,t0); \ +buf = _mm_shuffle_epi32(t1, _MM_SHUFFLE(0,1,2,3)); + +#define LOAD_MSG_7_1(buf) \ +t0 = _mm_unpackhi_epi32(m0,m1); \ +t1 = _mm_blend_epi16(t0,m3,0x0F); \ +buf = _mm_shuffle_epi32(t1,_MM_SHUFFLE(2,0,3,1)); + +#define LOAD_MSG_7_2(buf) \ +t0 = _mm_blend_epi16(m2,m3,0x30); \ +t1 = _mm_srli_si128(m0,4); \ +t2 = _mm_blend_epi16(t0,t1,0x03); \ +buf = _mm_shuffle_epi32(t2, _MM_SHUFFLE(1,0,2,3)); + +#define LOAD_MSG_7_3(buf) \ +t0 = _mm_unpackhi_epi64(m0,m3); \ +t1 = _mm_unpacklo_epi64(m1,m2); \ +t2 = _mm_blend_epi16(t0,t1,0x3C); \ +buf = _mm_shuffle_epi32(t2,_MM_SHUFFLE(2,3,1,0)); + +#define LOAD_MSG_7_4(buf) \ +t0 = _mm_unpacklo_epi32(m0,m1); \ +t1 = _mm_unpackhi_epi32(m1,m2); \ +t2 = _mm_unpacklo_epi64(t0,t1); \ +buf = _mm_shuffle_epi32(t2, _MM_SHUFFLE(2,1,0,3)); + +#define LOAD_MSG_8_1(buf) \ +t0 = _mm_unpackhi_epi32(m1,m3); \ +t1 = _mm_unpacklo_epi64(t0,m0); \ +t2 = _mm_blend_epi16(t1,m2,0xC0); \ +buf = _mm_shufflehi_epi16(t2,_MM_SHUFFLE(1,0,3,2)); + +#define LOAD_MSG_8_2(buf) \ +t0 = _mm_unpackhi_epi32(m0,m3); \ +t1 = _mm_blend_epi16(m2,t0,0xF0); \ +buf = _mm_shuffle_epi32(t1,_MM_SHUFFLE(0,2,1,3)); + +#define LOAD_MSG_8_3(buf) \ +t0 = _mm_unpacklo_epi64(m0,m3); \ +t1 = _mm_srli_si128(m2,8); \ +t2 = _mm_blend_epi16(t0,t1,0x03); \ +buf = _mm_shuffle_epi32(t2, _MM_SHUFFLE(1,3,2,0)); + +#define LOAD_MSG_8_4(buf) \ +t0 = _mm_blend_epi16(m1,m0,0x30); \ +buf = _mm_shuffle_epi32(t0,_MM_SHUFFLE(0,3,2,1)); + +#define LOAD_MSG_9_1(buf) \ +t0 = _mm_blend_epi16(m0,m2,0x03); \ +t1 = _mm_blend_epi16(m1,m2,0x30); \ +t2 = _mm_blend_epi16(t1,t0,0x0F); \ +buf = _mm_shuffle_epi32(t2,_MM_SHUFFLE(1,3,0,2)); + +#define LOAD_MSG_9_2(buf) \ +t0 = _mm_slli_si128(m0,4); \ +t1 = _mm_blend_epi16(m1,t0,0xC0); \ +buf = _mm_shuffle_epi32(t1,_MM_SHUFFLE(1,2,0,3)); + +#define LOAD_MSG_9_3(buf) \ +t0 = _mm_unpackhi_epi32(m0,m3); \ +t1 = _mm_unpacklo_epi32(m2,m3); \ +t2 = _mm_unpackhi_epi64(t0,t1); \ +buf = _mm_shuffle_epi32(t2,_MM_SHUFFLE(0,2,1,3)); + +#define LOAD_MSG_9_4(buf) \ +t0 = _mm_blend_epi16(m3,m2,0xC0); \ +t1 = _mm_unpacklo_epi32(m0,m3); \ +t2 = _mm_blend_epi16(t0,t1,0x0F); \ +buf = _mm_shuffle_epi32(t2,_MM_SHUFFLE(1,2,3,0)); + +#endif diff --git a/src/Native/libmultihash/blake2/sse/blake2s-load-xop.h b/src/Native/libmultihash/blake2/sse/blake2s-load-xop.h new file mode 100644 index 000000000..426edc162 --- /dev/null +++ b/src/Native/libmultihash/blake2/sse/blake2s-load-xop.h @@ -0,0 +1,191 @@ +/* + BLAKE2 reference source code package - optimized C implementations + + Copyright 2012, Samuel Neves <sneves@dei.uc.pt>. You may use this under the + terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at + your option. The terms of these licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ +#ifndef BLAKE2S_LOAD_XOP_H +#define BLAKE2S_LOAD_XOP_H + +#define TOB(x) ((x)*4*0x01010101 + 0x03020100) /* ..or not TOB */ + +#if 0 +/* Basic VPPERM emulation, for testing purposes */ +static __m128i _mm_perm_epi8(const __m128i src1, const __m128i src2, const __m128i sel) +{ + const __m128i sixteen = _mm_set1_epi8(16); + const __m128i t0 = _mm_shuffle_epi8(src1, sel); + const __m128i s1 = _mm_shuffle_epi8(src2, _mm_sub_epi8(sel, sixteen)); + const __m128i mask = _mm_or_si128(_mm_cmpeq_epi8(sel, sixteen), + _mm_cmpgt_epi8(sel, sixteen)); /* (>=16) = 0xff : 00 */ + return _mm_blendv_epi8(t0, s1, mask); +} +#endif + +#define LOAD_MSG_0_1(buf) \ +buf = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(6),TOB(4),TOB(2),TOB(0)) ); + +#define LOAD_MSG_0_2(buf) \ +buf = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(7),TOB(5),TOB(3),TOB(1)) ); + +#define LOAD_MSG_0_3(buf) \ +buf = _mm_perm_epi8(m2, m3, _mm_set_epi32(TOB(4),TOB(2),TOB(0),TOB(6)) ); + +#define LOAD_MSG_0_4(buf) \ +buf = _mm_perm_epi8(m2, m3, _mm_set_epi32(TOB(5),TOB(3),TOB(1),TOB(7)) ); + +#define LOAD_MSG_1_1(buf) \ +t0 = _mm_perm_epi8(m1, m2, _mm_set_epi32(TOB(0),TOB(5),TOB(0),TOB(0)) ); \ +buf = _mm_perm_epi8(t0, m3, _mm_set_epi32(TOB(5),TOB(2),TOB(1),TOB(6)) ); + +#define LOAD_MSG_1_2(buf) \ +t1 = _mm_perm_epi8(m1, m2, _mm_set_epi32(TOB(2),TOB(0),TOB(4),TOB(6)) ); \ +buf = _mm_perm_epi8(t1, m3, _mm_set_epi32(TOB(3),TOB(7),TOB(1),TOB(0)) ); + +#define LOAD_MSG_1_3(buf) \ +t0 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(5),TOB(0),TOB(0),TOB(1)) ); \ +buf = _mm_perm_epi8(t0, m2, _mm_set_epi32(TOB(7),TOB(1),TOB(0),TOB(3)) ); + +#define LOAD_MSG_1_4(buf) \ +t1 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(3),TOB(7),TOB(2),TOB(0)) ); \ +buf = _mm_perm_epi8(t1, m3, _mm_set_epi32(TOB(2),TOB(1),TOB(4),TOB(3)) ); + +#define LOAD_MSG_2_1(buf) \ +t0 = _mm_perm_epi8(m1, m2, _mm_set_epi32(TOB(0),TOB(1),TOB(0),TOB(7)) ); \ +buf = _mm_perm_epi8(t0, m3, _mm_set_epi32(TOB(7),TOB(2),TOB(4),TOB(0)) ); + +#define LOAD_MSG_2_2(buf) \ +t1 = _mm_perm_epi8(m0, m2, _mm_set_epi32(TOB(0),TOB(2),TOB(0),TOB(4)) ); \ +buf = _mm_perm_epi8(t1, m3, _mm_set_epi32(TOB(5),TOB(2),TOB(1),TOB(0)) ); + +#define LOAD_MSG_2_3(buf) \ +t0 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(0),TOB(7),TOB(3),TOB(0)) ); \ +buf = _mm_perm_epi8(t0, m2, _mm_set_epi32(TOB(2),TOB(1),TOB(6),TOB(5)) ); + +#define LOAD_MSG_2_4(buf) \ +t1 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(4),TOB(1),TOB(6),TOB(0)) ); \ +buf = _mm_perm_epi8(t1, m3, _mm_set_epi32(TOB(2),TOB(1),TOB(6),TOB(3)) ); + +#define LOAD_MSG_3_1(buf) \ +t0 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(0),TOB(0),TOB(3),TOB(7)) ); \ +t0 = _mm_perm_epi8(t0, m2, _mm_set_epi32(TOB(7),TOB(2),TOB(1),TOB(0)) ); \ +buf = _mm_perm_epi8(t0, m3, _mm_set_epi32(TOB(3),TOB(5),TOB(1),TOB(0)) ); + +#define LOAD_MSG_3_2(buf) \ +t1 = _mm_perm_epi8(m0, m2, _mm_set_epi32(TOB(0),TOB(0),TOB(1),TOB(5)) ); \ +buf = _mm_perm_epi8(t1, m3, _mm_set_epi32(TOB(6),TOB(4),TOB(1),TOB(0)) ); + +#define LOAD_MSG_3_3(buf) \ +t0 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(0),TOB(4),TOB(5),TOB(2)) ); \ +buf = _mm_perm_epi8(t0, m3, _mm_set_epi32(TOB(2),TOB(1),TOB(0),TOB(7)) ); + +#define LOAD_MSG_3_4(buf) \ +t1 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(0),TOB(0),TOB(0),TOB(6)) ); \ +buf = _mm_perm_epi8(t1, m2, _mm_set_epi32(TOB(2),TOB(6),TOB(0),TOB(4)) ); + +#define LOAD_MSG_4_1(buf) \ +t0 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(0),TOB(2),TOB(5),TOB(0)) ); \ +buf = _mm_perm_epi8(t0, m2, _mm_set_epi32(TOB(6),TOB(2),TOB(1),TOB(5)) ); + +#define LOAD_MSG_4_2(buf) \ +t1 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(0),TOB(4),TOB(7),TOB(0)) ); \ +buf = _mm_perm_epi8(t1, m3, _mm_set_epi32(TOB(7),TOB(2),TOB(1),TOB(0)) ); + +#define LOAD_MSG_4_3(buf) \ +t0 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(3),TOB(6),TOB(0),TOB(0)) ); \ +t0 = _mm_perm_epi8(t0, m2, _mm_set_epi32(TOB(3),TOB(2),TOB(7),TOB(0)) ); \ +buf = _mm_perm_epi8(t0, m3, _mm_set_epi32(TOB(2),TOB(1),TOB(6),TOB(3)) ); + +#define LOAD_MSG_4_4(buf) \ +t1 = _mm_perm_epi8(m0, m2, _mm_set_epi32(TOB(0),TOB(4),TOB(0),TOB(1)) ); \ +buf = _mm_perm_epi8(t1, m3, _mm_set_epi32(TOB(2),TOB(4),TOB(0),TOB(5)) ); + +#define LOAD_MSG_5_1(buf) \ +t0 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(0),TOB(0),TOB(6),TOB(2)) ); \ +buf = _mm_perm_epi8(t0, m2, _mm_set_epi32(TOB(4),TOB(2),TOB(1),TOB(0)) ); + +#define LOAD_MSG_5_2(buf) \ +t1 = _mm_perm_epi8(m0, m2, _mm_set_epi32(TOB(3),TOB(7),TOB(6),TOB(0)) ); \ +buf = _mm_perm_epi8(t1, m3, _mm_set_epi32(TOB(3),TOB(2),TOB(1),TOB(4)) ); + +#define LOAD_MSG_5_3(buf) \ +t0 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(1),TOB(0),TOB(7),TOB(4)) ); \ +buf = _mm_perm_epi8(t0, m3, _mm_set_epi32(TOB(7),TOB(1),TOB(0),TOB(3)) ); + +#define LOAD_MSG_5_4(buf) \ +t1 = _mm_perm_epi8(m1, m2, _mm_set_epi32(TOB(5),TOB(0),TOB(1),TOB(0)) ); \ +buf = _mm_perm_epi8(t1, m3, _mm_set_epi32(TOB(6),TOB(1),TOB(5),TOB(3)) ); + +#define LOAD_MSG_6_1(buf) \ +t0 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(4),TOB(0),TOB(1),TOB(0)) ); \ +buf = _mm_perm_epi8(t0, m3, _mm_set_epi32(TOB(3),TOB(6),TOB(1),TOB(4)) ); + +#define LOAD_MSG_6_2(buf) \ +t1 = _mm_perm_epi8(m1, m2, _mm_set_epi32(TOB(6),TOB(0),TOB(0),TOB(1)) ); \ +buf = _mm_perm_epi8(t1, m3, _mm_set_epi32(TOB(3),TOB(5),TOB(7),TOB(0)) ); + +#define LOAD_MSG_6_3(buf) \ +t0 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(0),TOB(0),TOB(6),TOB(0)) ); \ +buf = _mm_perm_epi8(t0, m2, _mm_set_epi32(TOB(5),TOB(1),TOB(0),TOB(4)) ); + +#define LOAD_MSG_6_4(buf) \ +t1 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(0),TOB(2),TOB(3),TOB(7)) ); \ +buf = _mm_perm_epi8(t1, m2, _mm_set_epi32(TOB(2),TOB(1),TOB(0),TOB(7)) ); + +#define LOAD_MSG_7_1(buf) \ +t0 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(3),TOB(0),TOB(7),TOB(0)) ); \ +buf = _mm_perm_epi8(t0, m3, _mm_set_epi32(TOB(3),TOB(4),TOB(1),TOB(5)) ); + +#define LOAD_MSG_7_2(buf) \ +t1 = _mm_perm_epi8(m0, m2, _mm_set_epi32(TOB(5),TOB(1),TOB(0),TOB(7)) ); \ +buf = _mm_perm_epi8(t1, m3, _mm_set_epi32(TOB(3),TOB(2),TOB(6),TOB(0)) ); + +#define LOAD_MSG_7_3(buf) \ +t0 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(2),TOB(0),TOB(0),TOB(5)) ); \ +t0 = _mm_perm_epi8(t0, m2, _mm_set_epi32(TOB(3),TOB(4),TOB(1),TOB(0)) ); \ +buf = _mm_perm_epi8(t0, m3, _mm_set_epi32(TOB(2),TOB(7),TOB(0),TOB(3)) ); + +#define LOAD_MSG_7_4(buf) \ +t1 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(0),TOB(6),TOB(4),TOB(0)) ); \ +buf = _mm_perm_epi8(t1, m2, _mm_set_epi32(TOB(2),TOB(1),TOB(0),TOB(6)) ); + +#define LOAD_MSG_8_1(buf) \ +t0 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(0),TOB(0),TOB(0),TOB(6)) ); \ +t0 = _mm_perm_epi8(t0, m2, _mm_set_epi32(TOB(3),TOB(7),TOB(1),TOB(0)) ); \ +buf = _mm_perm_epi8(t0, m3, _mm_set_epi32(TOB(3),TOB(2),TOB(6),TOB(0)) ); + +#define LOAD_MSG_8_2(buf) \ +t1 = _mm_perm_epi8(m0, m2, _mm_set_epi32(TOB(4),TOB(3),TOB(5),TOB(0)) ); \ +buf = _mm_perm_epi8(t1, m3, _mm_set_epi32(TOB(3),TOB(2),TOB(1),TOB(7)) ); + +#define LOAD_MSG_8_3(buf) \ +t0 = _mm_perm_epi8(m0, m2, _mm_set_epi32(TOB(6),TOB(1),TOB(0),TOB(0)) ); \ +buf = _mm_perm_epi8(t0, m3, _mm_set_epi32(TOB(2),TOB(5),TOB(4),TOB(3)) ); \ + +#define LOAD_MSG_8_4(buf) \ +buf = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(4),TOB(7),TOB(2),TOB(5)) ); + +#define LOAD_MSG_9_1(buf) \ +t0 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(1),TOB(7),TOB(0),TOB(0)) ); \ +buf = _mm_perm_epi8(t0, m2, _mm_set_epi32(TOB(3),TOB(2),TOB(4),TOB(6)) ); + +#define LOAD_MSG_9_2(buf) \ +buf = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(5),TOB(6),TOB(4),TOB(2)) ); + +#define LOAD_MSG_9_3(buf) \ +t0 = _mm_perm_epi8(m0, m2, _mm_set_epi32(TOB(0),TOB(3),TOB(5),TOB(0)) ); \ +buf = _mm_perm_epi8(t0, m3, _mm_set_epi32(TOB(2),TOB(1),TOB(7),TOB(5)) ); + +#define LOAD_MSG_9_4(buf) \ +t1 = _mm_perm_epi8(m0, m2, _mm_set_epi32(TOB(0),TOB(0),TOB(0),TOB(7)) ); \ +buf = _mm_perm_epi8(t1, m3, _mm_set_epi32(TOB(4),TOB(6),TOB(0),TOB(3)) ); + +#endif diff --git a/src/Native/libmultihash/blake2/sse/blake2s-round.h b/src/Native/libmultihash/blake2/sse/blake2s-round.h new file mode 100644 index 000000000..b75c669c3 --- /dev/null +++ b/src/Native/libmultihash/blake2/sse/blake2s-round.h @@ -0,0 +1,88 @@ +/* + BLAKE2 reference source code package - optimized C implementations + + Copyright 2012, Samuel Neves <sneves@dei.uc.pt>. You may use this under the + terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at + your option. The terms of these licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ +#ifndef BLAKE2S_ROUND_H +#define BLAKE2S_ROUND_H + +#define LOADU(p) _mm_loadu_si128( (const __m128i *)(p) ) +#define STOREU(p,r) _mm_storeu_si128((__m128i *)(p), r) + +#define TOF(reg) _mm_castsi128_ps((reg)) +#define TOI(reg) _mm_castps_si128((reg)) + +#define LIKELY(x) __builtin_expect((x),1) + + +/* Microarchitecture-specific macros */ +#ifndef HAVE_XOP +#ifdef HAVE_SSSE3 +#define _mm_roti_epi32(r, c) ( \ + (8==-(c)) ? _mm_shuffle_epi8(r,r8) \ + : (16==-(c)) ? _mm_shuffle_epi8(r,r16) \ + : _mm_xor_si128(_mm_srli_epi32( (r), -(c) ),_mm_slli_epi32( (r), 32-(-(c)) )) ) +#else +#define _mm_roti_epi32(r, c) _mm_xor_si128(_mm_srli_epi32( (r), -(c) ),_mm_slli_epi32( (r), 32-(-(c)) )) +#endif +#else +/* ... */ +#endif + + +#define G1(row1,row2,row3,row4,buf) \ + row1 = _mm_add_epi32( _mm_add_epi32( row1, buf), row2 ); \ + row4 = _mm_xor_si128( row4, row1 ); \ + row4 = _mm_roti_epi32(row4, -16); \ + row3 = _mm_add_epi32( row3, row4 ); \ + row2 = _mm_xor_si128( row2, row3 ); \ + row2 = _mm_roti_epi32(row2, -12); + +#define G2(row1,row2,row3,row4,buf) \ + row1 = _mm_add_epi32( _mm_add_epi32( row1, buf), row2 ); \ + row4 = _mm_xor_si128( row4, row1 ); \ + row4 = _mm_roti_epi32(row4, -8); \ + row3 = _mm_add_epi32( row3, row4 ); \ + row2 = _mm_xor_si128( row2, row3 ); \ + row2 = _mm_roti_epi32(row2, -7); + +#define DIAGONALIZE(row1,row2,row3,row4) \ + row1 = _mm_shuffle_epi32( row1, _MM_SHUFFLE(2,1,0,3) ); \ + row4 = _mm_shuffle_epi32( row4, _MM_SHUFFLE(1,0,3,2) ); \ + row3 = _mm_shuffle_epi32( row3, _MM_SHUFFLE(0,3,2,1) ); + +#define UNDIAGONALIZE(row1,row2,row3,row4) \ + row1 = _mm_shuffle_epi32( row1, _MM_SHUFFLE(0,3,2,1) ); \ + row4 = _mm_shuffle_epi32( row4, _MM_SHUFFLE(1,0,3,2) ); \ + row3 = _mm_shuffle_epi32( row3, _MM_SHUFFLE(2,1,0,3) ); + +#if defined(HAVE_XOP) +#include "blake2s-load-xop.h" +#elif defined(HAVE_SSE41) +#include "blake2s-load-sse41.h" +#else +#include "blake2s-load-sse2.h" +#endif + +#define ROUND(r) \ + LOAD_MSG_ ##r ##_1(buf1); \ + G1(row1,row2,row3,row4,buf1); \ + LOAD_MSG_ ##r ##_2(buf2); \ + G2(row1,row2,row3,row4,buf2); \ + DIAGONALIZE(row1,row2,row3,row4); \ + LOAD_MSG_ ##r ##_3(buf3); \ + G1(row1,row2,row3,row4,buf3); \ + LOAD_MSG_ ##r ##_4(buf4); \ + G2(row1,row2,row3,row4,buf4); \ + UNDIAGONALIZE(row1,row2,row3,row4); \ + +#endif diff --git a/src/Native/libmultihash/blake2/sse/blake2s.c b/src/Native/libmultihash/blake2/sse/blake2s.c new file mode 100644 index 000000000..569c210e8 --- /dev/null +++ b/src/Native/libmultihash/blake2/sse/blake2s.c @@ -0,0 +1,363 @@ +/* + BLAKE2 reference source code package - optimized C implementations + + Copyright 2012, Samuel Neves <sneves@dei.uc.pt>. You may use this under the + terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at + your option. The terms of these licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ + +#include <stdint.h> +#include <string.h> +#include <stdio.h> + +#include "blake2.h" +#include "blake2-impl.h" + +#include "blake2-config.h" + + +#include <emmintrin.h> +#if defined(HAVE_SSSE3) +#include <tmmintrin.h> +#endif +#if defined(HAVE_SSE41) +#include <smmintrin.h> +#endif +#if defined(HAVE_AVX) +#include <immintrin.h> +#endif +#if defined(HAVE_XOP) +#include <x86intrin.h> +#endif + +#include "blake2s-round.h" + +static const uint32_t blake2s_IV[8] = +{ + 0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL, 0xA54FF53AUL, + 0x510E527FUL, 0x9B05688CUL, 0x1F83D9ABUL, 0x5BE0CD19UL +}; + +/* Some helper functions */ +static void blake2s_set_lastnode( blake2s_state *S ) +{ + S->f[1] = (uint32_t)-1; +} + +static int blake2s_is_lastblock( const blake2s_state *S ) +{ + return S->f[0] != 0; +} + +static void blake2s_set_lastblock( blake2s_state *S ) +{ + if( S->last_node ) blake2s_set_lastnode( S ); + + S->f[0] = (uint32_t)-1; +} + +static void blake2s_increment_counter( blake2s_state *S, const uint32_t inc ) +{ + uint64_t t = ( ( uint64_t )S->t[1] << 32 ) | S->t[0]; + t += inc; + S->t[0] = ( uint32_t )( t >> 0 ); + S->t[1] = ( uint32_t )( t >> 32 ); +} + +/* init2 xors IV with input parameter block */ +int blake2s_init_param( blake2s_state *S, const blake2s_param *P ) +{ + size_t i; + /*blake2s_init0( S ); */ + const uint8_t * v = ( const uint8_t * )( blake2s_IV ); + const uint8_t * p = ( const uint8_t * )( P ); + uint8_t * h = ( uint8_t * )( S->h ); + /* IV XOR ParamBlock */ + memset( S, 0, sizeof( blake2s_state ) ); + + for( i = 0; i < BLAKE2S_OUTBYTES; ++i ) h[i] = v[i] ^ p[i]; + + S->outlen = P->digest_length; + return 0; +} + + +/* Some sort of default parameter block initialization, for sequential blake2s */ +int blake2s_init( blake2s_state *S, size_t outlen ) +{ + blake2s_param P[1]; + + /* Move interval verification here? */ + if ( ( !outlen ) || ( outlen > BLAKE2S_OUTBYTES ) ) return -1; + + P->digest_length = (uint8_t)outlen; + P->key_length = 0; + P->fanout = 1; + P->depth = 1; + store32( &P->leaf_length, 0 ); + store32( &P->node_offset, 0 ); + store16( &P->xof_length, 0 ); + P->node_depth = 0; + P->inner_length = 0; + /* memset(P->reserved, 0, sizeof(P->reserved) ); */ + memset( P->salt, 0, sizeof( P->salt ) ); + memset( P->personal, 0, sizeof( P->personal ) ); + + return blake2s_init_param( S, P ); +} + + +int blake2s_init_key( blake2s_state *S, size_t outlen, const void *key, size_t keylen ) +{ + blake2s_param P[1]; + + /* Move interval verification here? */ + if ( ( !outlen ) || ( outlen > BLAKE2S_OUTBYTES ) ) return -1; + + if ( ( !key ) || ( !keylen ) || keylen > BLAKE2S_KEYBYTES ) return -1; + + P->digest_length = (uint8_t)outlen; + P->key_length = (uint8_t)keylen; + P->fanout = 1; + P->depth = 1; + store32( &P->leaf_length, 0 ); + store32( &P->node_offset, 0 ); + store16( &P->xof_length, 0 ); + P->node_depth = 0; + P->inner_length = 0; + /* memset(P->reserved, 0, sizeof(P->reserved) ); */ + memset( P->salt, 0, sizeof( P->salt ) ); + memset( P->personal, 0, sizeof( P->personal ) ); + + if( blake2s_init_param( S, P ) < 0 ) + return -1; + + { + uint8_t block[BLAKE2S_BLOCKBYTES]; + memset( block, 0, BLAKE2S_BLOCKBYTES ); + memcpy( block, key, keylen ); + blake2s_update( S, block, BLAKE2S_BLOCKBYTES ); + secure_zero_memory( block, BLAKE2S_BLOCKBYTES ); /* Burn the key from stack */ + } + return 0; +} + + +static void blake2s_compress( blake2s_state *S, const uint8_t block[BLAKE2S_BLOCKBYTES] ) +{ + __m128i row1, row2, row3, row4; + __m128i buf1, buf2, buf3, buf4; +#if defined(HAVE_SSE41) + __m128i t0, t1; +#if !defined(HAVE_XOP) + __m128i t2; +#endif +#endif + __m128i ff0, ff1; +#if defined(HAVE_SSSE3) && !defined(HAVE_XOP) + const __m128i r8 = _mm_set_epi8( 12, 15, 14, 13, 8, 11, 10, 9, 4, 7, 6, 5, 0, 3, 2, 1 ); + const __m128i r16 = _mm_set_epi8( 13, 12, 15, 14, 9, 8, 11, 10, 5, 4, 7, 6, 1, 0, 3, 2 ); +#endif +#if defined(HAVE_SSE41) + const __m128i m0 = LOADU( block + 00 ); + const __m128i m1 = LOADU( block + 16 ); + const __m128i m2 = LOADU( block + 32 ); + const __m128i m3 = LOADU( block + 48 ); +#else + const uint32_t m0 = load32(block + 0 * sizeof(uint32_t)); + const uint32_t m1 = load32(block + 1 * sizeof(uint32_t)); + const uint32_t m2 = load32(block + 2 * sizeof(uint32_t)); + const uint32_t m3 = load32(block + 3 * sizeof(uint32_t)); + const uint32_t m4 = load32(block + 4 * sizeof(uint32_t)); + const uint32_t m5 = load32(block + 5 * sizeof(uint32_t)); + const uint32_t m6 = load32(block + 6 * sizeof(uint32_t)); + const uint32_t m7 = load32(block + 7 * sizeof(uint32_t)); + const uint32_t m8 = load32(block + 8 * sizeof(uint32_t)); + const uint32_t m9 = load32(block + 9 * sizeof(uint32_t)); + const uint32_t m10 = load32(block + 10 * sizeof(uint32_t)); + const uint32_t m11 = load32(block + 11 * sizeof(uint32_t)); + const uint32_t m12 = load32(block + 12 * sizeof(uint32_t)); + const uint32_t m13 = load32(block + 13 * sizeof(uint32_t)); + const uint32_t m14 = load32(block + 14 * sizeof(uint32_t)); + const uint32_t m15 = load32(block + 15 * sizeof(uint32_t)); +#endif + row1 = ff0 = LOADU( &S->h[0] ); + row2 = ff1 = LOADU( &S->h[4] ); + row3 = _mm_loadu_si128( (__m128i const *)&blake2s_IV[0] ); + row4 = _mm_xor_si128( _mm_loadu_si128( (__m128i const *)&blake2s_IV[4] ), LOADU( &S->t[0] ) ); + ROUND( 0 ); + ROUND( 1 ); + ROUND( 2 ); + ROUND( 3 ); + ROUND( 4 ); + ROUND( 5 ); + ROUND( 6 ); + ROUND( 7 ); + ROUND( 8 ); + ROUND( 9 ); + STOREU( &S->h[0], _mm_xor_si128( ff0, _mm_xor_si128( row1, row3 ) ) ); + STOREU( &S->h[4], _mm_xor_si128( ff1, _mm_xor_si128( row2, row4 ) ) ); +} + +int blake2s_update( blake2s_state *S, const void *pin, size_t inlen ) +{ + const unsigned char * in = (const unsigned char *)pin; + if( inlen > 0 ) + { + size_t left = S->buflen; + size_t fill = BLAKE2S_BLOCKBYTES - left; + if( inlen > fill ) + { + S->buflen = 0; + memcpy( S->buf + left, in, fill ); /* Fill buffer */ + blake2s_increment_counter( S, BLAKE2S_BLOCKBYTES ); + blake2s_compress( S, S->buf ); /* Compress */ + in += fill; inlen -= fill; + while(inlen > BLAKE2S_BLOCKBYTES) { + blake2s_increment_counter(S, BLAKE2S_BLOCKBYTES); + blake2s_compress( S, in ); + in += BLAKE2S_BLOCKBYTES; + inlen -= BLAKE2S_BLOCKBYTES; + } + } + memcpy( S->buf + S->buflen, in, inlen ); + S->buflen += inlen; + } + return 0; +} + +int blake2s_final( blake2s_state *S, void *out, size_t outlen ) +{ + uint8_t buffer[BLAKE2S_OUTBYTES] = {0}; + size_t i; + + if( out == NULL || outlen < S->outlen ) + return -1; + + if( blake2s_is_lastblock( S ) ) + return -1; + + blake2s_increment_counter( S, (uint32_t)S->buflen ); + blake2s_set_lastblock( S ); + memset( S->buf + S->buflen, 0, BLAKE2S_BLOCKBYTES - S->buflen ); /* Padding */ + blake2s_compress( S, S->buf ); + + for( i = 0; i < 8; ++i ) /* Output full hash to temp buffer */ + store32( buffer + sizeof( S->h[i] ) * i, S->h[i] ); + + memcpy( out, buffer, S->outlen ); + secure_zero_memory( buffer, sizeof(buffer) ); + return 0; +} + +/* inlen, at least, should be uint64_t. Others can be size_t. */ +int blake2s( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen ) +{ + blake2s_state S[1]; + + /* Verify parameters */ + if ( NULL == in && inlen > 0 ) return -1; + + if ( NULL == out ) return -1; + + if ( NULL == key && keylen > 0) return -1; + + if( !outlen || outlen > BLAKE2S_OUTBYTES ) return -1; + + if( keylen > BLAKE2S_KEYBYTES ) return -1; + + if( keylen > 0 ) + { + if( blake2s_init_key( S, outlen, key, keylen ) < 0 ) return -1; + } + else + { + if( blake2s_init( S, outlen ) < 0 ) return -1; + } + + blake2s_update( S, ( const uint8_t * )in, inlen ); + blake2s_final( S, out, outlen ); + return 0; +} + +#if defined(SUPERCOP) +int crypto_hash( unsigned char *out, unsigned char *in, unsigned long long inlen ) +{ + return blake2s( out, BLAKE2S_OUTBYTES, in, inlen, NULL, 0 ); +} +#endif + +#if defined(BLAKE2S_SELFTEST) +#include <string.h> +#include "blake2-kat.h" +int main( void ) +{ + uint8_t key[BLAKE2S_KEYBYTES]; + uint8_t buf[BLAKE2_KAT_LENGTH]; + size_t i, step; + + for( i = 0; i < BLAKE2S_KEYBYTES; ++i ) + key[i] = ( uint8_t )i; + + for( i = 0; i < BLAKE2_KAT_LENGTH; ++i ) + buf[i] = ( uint8_t )i; + + /* Test simple API */ + for( i = 0; i < BLAKE2_KAT_LENGTH; ++i ) + { + uint8_t hash[BLAKE2S_OUTBYTES]; + blake2s( hash, BLAKE2S_OUTBYTES, buf, i, key, BLAKE2S_KEYBYTES ); + + if( 0 != memcmp( hash, blake2s_keyed_kat[i], BLAKE2S_OUTBYTES ) ) + { + goto fail; + } + } + + /* Test streaming API */ + for(step = 1; step < BLAKE2S_BLOCKBYTES; ++step) { + for (i = 0; i < BLAKE2_KAT_LENGTH; ++i) { + uint8_t hash[BLAKE2S_OUTBYTES]; + blake2s_state S; + uint8_t * p = buf; + size_t mlen = i; + int err = 0; + + if( (err = blake2s_init_key(&S, BLAKE2S_OUTBYTES, key, BLAKE2S_KEYBYTES)) < 0 ) { + goto fail; + } + + while (mlen >= step) { + if ( (err = blake2s_update(&S, p, step)) < 0 ) { + goto fail; + } + mlen -= step; + p += step; + } + if ( (err = blake2s_update(&S, p, mlen)) < 0) { + goto fail; + } + if ( (err = blake2s_final(&S, hash, BLAKE2S_OUTBYTES)) < 0) { + goto fail; + } + + if (0 != memcmp(hash, blake2s_keyed_kat[i], BLAKE2S_OUTBYTES)) { + goto fail; + } + } + } + + puts( "ok" ); + return 0; +fail: + puts("error"); + return -1; +} +#endif diff --git a/src/Native/libmultihash/blake2/sse/blake2sp b/src/Native/libmultihash/blake2/sse/blake2sp new file mode 100644 index 0000000000000000000000000000000000000000..fef18cd9d2a1d1f00b0293bdc0d7e57d8cfc8ad3 GIT binary patch literal 34072 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 L9MfS;k@SB6M=VYN literal 0 HcmV?d00001 diff --git a/src/Native/libmultihash/blake2/sse/blake2sp.c b/src/Native/libmultihash/blake2/sse/blake2sp.c new file mode 100644 index 000000000..ed0e1ad28 --- /dev/null +++ b/src/Native/libmultihash/blake2/sse/blake2sp.c @@ -0,0 +1,358 @@ +/* + BLAKE2 reference source code package - optimized C implementations + + Copyright 2012, Samuel Neves <sneves@dei.uc.pt>. You may use this under the + terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at + your option. The terms of these licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#if defined(_OPENMP) +#include <omp.h> +#endif + +#include "blake2.h" +#include "blake2-impl.h" + +#define PARALLELISM_DEGREE 8 + +/* + blake2sp_init_param defaults to setting the expecting output length + from the digest_length parameter block field. + + In some cases, however, we do not want this, as the output length + of these instances is given by inner_length instead. +*/ +static int blake2sp_init_leaf_param( blake2s_state *S, const blake2s_param *P ) +{ + int err = blake2s_init_param(S, P); + S->outlen = P->inner_length; + return err; +} + +static int blake2sp_init_leaf( blake2s_state *S, size_t outlen, size_t keylen, uint64_t offset ) +{ + blake2s_param P[1]; + P->digest_length = (uint8_t)outlen; + P->key_length = (uint8_t)keylen; + P->fanout = PARALLELISM_DEGREE; + P->depth = 2; + P->leaf_length = 0; + P->node_offset = offset; + P->xof_length = 0; + P->node_depth = 0; + P->inner_length = BLAKE2S_OUTBYTES; + memset( P->salt, 0, sizeof( P->salt ) ); + memset( P->personal, 0, sizeof( P->personal ) ); + return blake2sp_init_leaf_param( S, P ); +} + +static int blake2sp_init_root( blake2s_state *S, size_t outlen, size_t keylen ) +{ + blake2s_param P[1]; + P->digest_length = (uint8_t)outlen; + P->key_length = (uint8_t)keylen; + P->fanout = PARALLELISM_DEGREE; + P->depth = 2; + P->leaf_length = 0; + P->node_offset = 0; + P->xof_length = 0; + P->node_depth = 1; + P->inner_length = BLAKE2S_OUTBYTES; + memset( P->salt, 0, sizeof( P->salt ) ); + memset( P->personal, 0, sizeof( P->personal ) ); + return blake2s_init_param( S, P ); +} + + +int blake2sp_init( blake2sp_state *S, size_t outlen ) +{ + size_t i; + + if( !outlen || outlen > BLAKE2S_OUTBYTES ) return -1; + + memset( S->buf, 0, sizeof( S->buf ) ); + S->buflen = 0; + S->outlen = outlen; + + if( blake2sp_init_root( S->R, outlen, 0 ) < 0 ) + return -1; + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + if( blake2sp_init_leaf( S->S[i], outlen, 0, i ) < 0 ) return -1; + + S->R->last_node = 1; + S->S[PARALLELISM_DEGREE - 1]->last_node = 1; + return 0; +} + +int blake2sp_init_key( blake2sp_state *S, size_t outlen, const void *key, size_t keylen ) +{ + size_t i; + + if( !outlen || outlen > BLAKE2S_OUTBYTES ) return -1; + + if( !key || !keylen || keylen > BLAKE2S_KEYBYTES ) return -1; + + memset( S->buf, 0, sizeof( S->buf ) ); + S->buflen = 0; + S->outlen = outlen; + + if( blake2sp_init_root( S->R, outlen, keylen ) < 0 ) + return -1; + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + if( blake2sp_init_leaf( S->S[i], outlen, keylen, i ) < 0 ) return -1; + + S->R->last_node = 1; + S->S[PARALLELISM_DEGREE - 1]->last_node = 1; + { + uint8_t block[BLAKE2S_BLOCKBYTES]; + memset( block, 0, BLAKE2S_BLOCKBYTES ); + memcpy( block, key, keylen ); + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + blake2s_update( S->S[i], block, BLAKE2S_BLOCKBYTES ); + + secure_zero_memory( block, BLAKE2S_BLOCKBYTES ); /* Burn the key from stack */ + } + return 0; +} + + +int blake2sp_update( blake2sp_state *S, const void *pin, size_t inlen ) +{ + const unsigned char * in = (const unsigned char *)pin; + size_t left = S->buflen; + size_t fill = sizeof( S->buf ) - left; + size_t i; + + if( left && inlen >= fill ) + { + memcpy( S->buf + left, in, fill ); + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + blake2s_update( S->S[i], S->buf + i * BLAKE2S_BLOCKBYTES, BLAKE2S_BLOCKBYTES ); + + in += fill; + inlen -= fill; + left = 0; + } + +#if defined(_OPENMP) + #pragma omp parallel shared(S), num_threads(PARALLELISM_DEGREE) +#else + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) +#endif + { +#if defined(_OPENMP) + size_t i = omp_get_thread_num(); +#endif + size_t inlen__ = inlen; + const unsigned char *in__ = ( const unsigned char * )in; + in__ += i * BLAKE2S_BLOCKBYTES; + + while( inlen__ >= PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES ) + { + blake2s_update( S->S[i], in__, BLAKE2S_BLOCKBYTES ); + in__ += PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES; + inlen__ -= PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES; + } + } + + in += inlen - inlen % ( PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES ); + inlen %= PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES; + + if( inlen > 0 ) + memcpy( S->buf + left, in, inlen ); + + S->buflen = left + inlen; + return 0; +} + + +int blake2sp_final( blake2sp_state *S, void *out, size_t outlen ) +{ + uint8_t hash[PARALLELISM_DEGREE][BLAKE2S_OUTBYTES]; + size_t i; + + if(out == NULL || outlen < S->outlen) { + return -1; + } + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + { + if( S->buflen > i * BLAKE2S_BLOCKBYTES ) + { + size_t left = S->buflen - i * BLAKE2S_BLOCKBYTES; + + if( left > BLAKE2S_BLOCKBYTES ) left = BLAKE2S_BLOCKBYTES; + + blake2s_update( S->S[i], S->buf + i * BLAKE2S_BLOCKBYTES, left ); + } + + blake2s_final( S->S[i], hash[i], BLAKE2S_OUTBYTES ); + } + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + blake2s_update( S->R, hash[i], BLAKE2S_OUTBYTES ); + + return blake2s_final( S->R, out, S->outlen ); +} + + +int blake2sp( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen ) +{ + uint8_t hash[PARALLELISM_DEGREE][BLAKE2S_OUTBYTES]; + blake2s_state S[PARALLELISM_DEGREE][1]; + blake2s_state FS[1]; + size_t i; + + /* Verify parameters */ + if ( NULL == in && inlen > 0 ) return -1; + + if ( NULL == out ) return -1; + + if ( NULL == key && keylen > 0) return -1; + + if( !outlen || outlen > BLAKE2S_OUTBYTES ) return -1; + + if( keylen > BLAKE2S_KEYBYTES ) return -1; + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + if( blake2sp_init_leaf( S[i], outlen, keylen, i ) < 0 ) return -1; + + S[PARALLELISM_DEGREE - 1]->last_node = 1; /* mark last node */ + + if( keylen > 0 ) + { + uint8_t block[BLAKE2S_BLOCKBYTES]; + memset( block, 0, BLAKE2S_BLOCKBYTES ); + memcpy( block, key, keylen ); + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + blake2s_update( S[i], block, BLAKE2S_BLOCKBYTES ); + + secure_zero_memory( block, BLAKE2S_BLOCKBYTES ); /* Burn the key from stack */ + } + +#if defined(_OPENMP) + #pragma omp parallel shared(S,hash), num_threads(PARALLELISM_DEGREE) +#else + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) +#endif + { +#if defined(_OPENMP) + size_t i = omp_get_thread_num(); +#endif + size_t inlen__ = inlen; + const unsigned char *in__ = ( const unsigned char * )in; + in__ += i * BLAKE2S_BLOCKBYTES; + + while( inlen__ >= PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES ) + { + blake2s_update( S[i], in__, BLAKE2S_BLOCKBYTES ); + in__ += PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES; + inlen__ -= PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES; + } + + if( inlen__ > i * BLAKE2S_BLOCKBYTES ) + { + const size_t left = inlen__ - i * BLAKE2S_BLOCKBYTES; + const size_t len = left <= BLAKE2S_BLOCKBYTES ? left : BLAKE2S_BLOCKBYTES; + blake2s_update( S[i], in__, len ); + } + + blake2s_final( S[i], hash[i], BLAKE2S_OUTBYTES ); + } + + if( blake2sp_init_root( FS, outlen, keylen ) < 0 ) + return -1; + + FS->last_node = 1; + + for( i = 0; i < PARALLELISM_DEGREE; ++i ) + blake2s_update( FS, hash[i], BLAKE2S_OUTBYTES ); + + return blake2s_final( FS, out, outlen ); +} + +#if defined(BLAKE2SP_SELFTEST) +#include <string.h> +#include "blake2-kat.h" +int main( void ) +{ + uint8_t key[BLAKE2S_KEYBYTES]; + uint8_t buf[BLAKE2_KAT_LENGTH]; + size_t i, step; + + for( i = 0; i < BLAKE2S_KEYBYTES; ++i ) + key[i] = ( uint8_t )i; + + for( i = 0; i < BLAKE2_KAT_LENGTH; ++i ) + buf[i] = ( uint8_t )i; + + /* Test simple API */ + for( i = 0; i < BLAKE2_KAT_LENGTH; ++i ) + { + uint8_t hash[BLAKE2S_OUTBYTES]; + blake2sp( hash, BLAKE2S_OUTBYTES, buf, i, key, BLAKE2S_KEYBYTES ); + + if( 0 != memcmp( hash, blake2sp_keyed_kat[i], BLAKE2S_OUTBYTES ) ) + { + goto fail; + } + } + + /* Test streaming API */ + for(step = 1; step < BLAKE2S_BLOCKBYTES; ++step) { + for (i = 0; i < BLAKE2_KAT_LENGTH; ++i) { + uint8_t hash[BLAKE2S_OUTBYTES]; + blake2sp_state S; + uint8_t * p = buf; + size_t mlen = i; + int err = 0; + + if( (err = blake2sp_init_key(&S, BLAKE2S_OUTBYTES, key, BLAKE2S_KEYBYTES)) < 0 ) { + goto fail; + } + + while (mlen >= step) { + if ( (err = blake2sp_update(&S, p, step)) < 0 ) { + goto fail; + } + mlen -= step; + p += step; + } + if ( (err = blake2sp_update(&S, p, mlen)) < 0) { + goto fail; + } + if ( (err = blake2sp_final(&S, hash, BLAKE2S_OUTBYTES)) < 0) { + goto fail; + } + + if (0 != memcmp(hash, blake2sp_keyed_kat[i], BLAKE2S_OUTBYTES)) { + goto fail; + } + } + } + + puts( "ok" ); + return 0; +fail: + puts("error"); + return -1; +} +#endif diff --git a/src/Native/libmultihash/blake2/sse/blake2xb b/src/Native/libmultihash/blake2/sse/blake2xb new file mode 100644 index 0000000000000000000000000000000000000000..549eb3efbc1346303b8413d35b711ec777c59260 GIT binary patch literal 95488 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 literal 0 HcmV?d00001 diff --git a/src/Native/libmultihash/blake2/sse/blake2xb.c b/src/Native/libmultihash/blake2/sse/blake2xb.c new file mode 100644 index 000000000..2da56aefd --- /dev/null +++ b/src/Native/libmultihash/blake2/sse/blake2xb.c @@ -0,0 +1,241 @@ +/* + BLAKE2 reference source code package - reference C implementations + + Copyright 2016, JP Aumasson <jeanphilippe.aumasson@gmail.com>. + Copyright 2016, Samuel Neves <sneves@dei.uc.pt>. + + You may use this under the terms of the CC0, the OpenSSL Licence, or + the Apache Public License 2.0, at your option. The terms of these + licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ + +#include <stdint.h> +#include <string.h> +#include <stdio.h> + +#include "blake2.h" +#include "blake2-impl.h" + +int blake2xb_init( blake2xb_state *S, const size_t outlen ) { + return blake2xb_init_key(S, outlen, NULL, 0); +} + +int blake2xb_init_key( blake2xb_state *S, const size_t outlen, const void *key, size_t keylen) +{ + if ( outlen == 0 || outlen > 0xFFFFFFFFUL ) { + return -1; + } + + if (NULL != key && keylen > BLAKE2B_KEYBYTES) { + return -1; + } + + if (NULL == key && keylen > 0) { + return -1; + } + + /* Initialize parameter block */ + S->P->digest_length = BLAKE2B_OUTBYTES; + S->P->key_length = keylen; + S->P->fanout = 1; + S->P->depth = 1; + store32( &S->P->leaf_length, 0 ); + store32( &S->P->node_offset, 0 ); + store32( &S->P->xof_length, outlen ); + S->P->node_depth = 0; + S->P->inner_length = 0; + memset( S->P->reserved, 0, sizeof( S->P->reserved ) ); + memset( S->P->salt, 0, sizeof( S->P->salt ) ); + memset( S->P->personal, 0, sizeof( S->P->personal ) ); + + if( blake2b_init_param( S->S, S->P ) < 0 ) { + return -1; + } + + if (keylen > 0) { + uint8_t block[BLAKE2B_BLOCKBYTES]; + memset(block, 0, BLAKE2B_BLOCKBYTES); + memcpy(block, key, keylen); + blake2b_update(S->S, block, BLAKE2B_BLOCKBYTES); + secure_zero_memory(block, BLAKE2B_BLOCKBYTES); + } + return 0; +} + +int blake2xb_update( blake2xb_state *S, const void *in, size_t inlen ) { + return blake2b_update( S->S, in, inlen ); +} + +int blake2xb_final( blake2xb_state *S, void *out, size_t outlen) { + + blake2b_state C[1]; + blake2b_param P[1]; + uint32_t xof_length = load32(&S->P->xof_length); + uint8_t root[BLAKE2B_BLOCKBYTES]; + size_t i; + + if (NULL == out) { + return -1; + } + + /* outlen must match the output size defined in xof_length, */ + /* unless it was -1, in which case anything goes except 0. */ + if(xof_length == 0xFFFFFFFFUL) { + if(outlen == 0) { + return -1; + } + } else { + if(outlen != xof_length) { + return -1; + } + } + + /* Finalize the root hash */ + if (blake2b_final(S->S, root, BLAKE2B_OUTBYTES) < 0) { + return -1; + } + + /* Set common block structure values */ + /* Copy values from parent instance, and only change the ones below */ + memcpy(P, S->P, sizeof(blake2b_param)); + P->key_length = 0; + P->fanout = 0; + P->depth = 0; + store32(&P->leaf_length, BLAKE2B_OUTBYTES); + P->inner_length = BLAKE2B_OUTBYTES; + P->node_depth = 0; + + for (i = 0; outlen > 0; ++i) { + const size_t block_size = (outlen < BLAKE2B_OUTBYTES) ? outlen : BLAKE2B_OUTBYTES; + /* Initialize state */ + P->digest_length = block_size; + store32(&P->node_offset, i); + blake2b_init_param(C, P); + /* Process key if needed */ + blake2b_update(C, root, BLAKE2B_OUTBYTES); + if (blake2b_final(C, (uint8_t *)out + i * BLAKE2B_OUTBYTES, block_size) < 0 ) { + return -1; + } + outlen -= block_size; + } + secure_zero_memory(root, sizeof(root)); + secure_zero_memory(P, sizeof(P)); + secure_zero_memory(C, sizeof(C)); + /* Put blake2xb in an invalid state? cf. blake2s_is_lastblock */ + return 0; + +} + +int blake2xb(void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen) +{ + blake2xb_state S[1]; + + /* Verify parameters */ + if (NULL == in && inlen > 0) + return -1; + + if (NULL == out) + return -1; + + if (NULL == key && keylen > 0) + return -1; + + if (keylen > BLAKE2B_KEYBYTES) + return -1; + + if (outlen == 0) + return -1; + + /* Initialize the root block structure */ + if (blake2xb_init_key(S, outlen, key, keylen) < 0) { + return -1; + } + + /* Absorb the input message */ + blake2xb_update(S, in, inlen); + + /* Compute the root node of the tree and the final hash using the counter construction */ + return blake2xb_final(S, out, outlen); +} + +#if defined(BLAKE2XB_SELFTEST) +#include <string.h> +#include "blake2-kat.h" +int main( void ) +{ + uint8_t key[BLAKE2B_KEYBYTES]; + uint8_t buf[BLAKE2_KAT_LENGTH]; + size_t i, step, outlen; + + for( i = 0; i < BLAKE2B_KEYBYTES; ++i ) { + key[i] = ( uint8_t )i; + } + + for( i = 0; i < BLAKE2_KAT_LENGTH; ++i ) { + buf[i] = ( uint8_t )i; + } + + /* Testing length of ouputs rather than inputs */ + /* (Test of input lengths mostly covered by blake2s tests) */ + + /* Test simple API */ + for( outlen = 1; outlen <= BLAKE2_KAT_LENGTH; ++outlen ) + { + uint8_t hash[BLAKE2_KAT_LENGTH] = {0}; + if( blake2xb( hash, outlen, buf, BLAKE2_KAT_LENGTH, key, BLAKE2B_KEYBYTES ) < 0 ) { + goto fail; + } + + if( 0 != memcmp( hash, blake2xb_keyed_kat[outlen-1], outlen ) ) + { + goto fail; + } + } + + /* Test streaming API */ + for(step = 1; step < BLAKE2B_BLOCKBYTES; ++step) { + for (outlen = 1; outlen <= BLAKE2_KAT_LENGTH; ++outlen) { + uint8_t hash[BLAKE2_KAT_LENGTH]; + blake2xb_state S; + uint8_t * p = buf; + size_t mlen = BLAKE2_KAT_LENGTH; + int err = 0; + + if( (err = blake2xb_init_key(&S, outlen, key, BLAKE2B_KEYBYTES)) < 0 ) { + goto fail; + } + + while (mlen >= step) { + if ( (err = blake2xb_update(&S, p, step)) < 0 ) { + goto fail; + } + mlen -= step; + p += step; + } + if ( (err = blake2xb_update(&S, p, mlen)) < 0) { + goto fail; + } + if ( (err = blake2xb_final(&S, hash, outlen)) < 0) { + goto fail; + } + + if (0 != memcmp(hash, blake2xb_keyed_kat[outlen-1], outlen)) { + goto fail; + } + } + } + + puts( "ok" ); + return 0; +fail: + puts("error"); + return -1; +} +#endif diff --git a/src/Native/libmultihash/blake2/sse/blake2xs b/src/Native/libmultihash/blake2/sse/blake2xs new file mode 100644 index 0000000000000000000000000000000000000000..a5d986b9b16871dd40236bbdb25bf52cc2bafc18 GIT binary patch literal 91360 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%0D?j6&w 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 literal 0 HcmV?d00001 diff --git a/src/Native/libmultihash/blake2/sse/blake2xs.c b/src/Native/libmultihash/blake2/sse/blake2xs.c new file mode 100644 index 000000000..625693ee5 --- /dev/null +++ b/src/Native/libmultihash/blake2/sse/blake2xs.c @@ -0,0 +1,239 @@ +/* + BLAKE2 reference source code package - reference C implementations + + Copyright 2016, JP Aumasson <jeanphilippe.aumasson@gmail.com>. + Copyright 2016, Samuel Neves <sneves@dei.uc.pt>. + + You may use this under the terms of the CC0, the OpenSSL Licence, or + the Apache Public License 2.0, at your option. The terms of these + licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ + +#include <stdint.h> +#include <string.h> +#include <stdio.h> + +#include "blake2.h" +#include "blake2-impl.h" + +int blake2xs_init( blake2xs_state *S, const size_t outlen ) { + return blake2xs_init_key(S, outlen, NULL, 0); +} + +int blake2xs_init_key( blake2xs_state *S, const size_t outlen, const void *key, size_t keylen ) +{ + if ( outlen == 0 || outlen > 0xFFFFUL ) { + return -1; + } + + if (NULL != key && keylen > BLAKE2B_KEYBYTES) { + return -1; + } + + if (NULL == key && keylen > 0) { + return -1; + } + + /* Initialize parameter block */ + S->P->digest_length = BLAKE2S_OUTBYTES; + S->P->key_length = keylen; + S->P->fanout = 1; + S->P->depth = 1; + store32( &S->P->leaf_length, 0 ); + store32( &S->P->node_offset, 0 ); + store16( &S->P->xof_length, outlen ); + S->P->node_depth = 0; + S->P->inner_length = 0; + memset( S->P->salt, 0, sizeof( S->P->salt ) ); + memset( S->P->personal, 0, sizeof( S->P->personal ) ); + + if( blake2s_init_param( S->S, S->P ) < 0 ) { + return -1; + } + + if (keylen > 0) { + uint8_t block[BLAKE2S_BLOCKBYTES]; + memset(block, 0, BLAKE2S_BLOCKBYTES); + memcpy(block, key, keylen); + blake2s_update(S->S, block, BLAKE2S_BLOCKBYTES); + secure_zero_memory(block, BLAKE2S_BLOCKBYTES); + } + return 0; +} + +int blake2xs_update( blake2xs_state *S, const void *in, size_t inlen ) { + return blake2s_update( S->S, in, inlen ); +} + +int blake2xs_final(blake2xs_state *S, void *out, size_t outlen) { + + blake2s_state C[1]; + blake2s_param P[1]; + uint16_t xof_length = load16(&S->P->xof_length); + uint8_t root[BLAKE2S_BLOCKBYTES]; + size_t i; + + if (NULL == out) { + return -1; + } + + /* outlen must match the output size defined in xof_length, */ + /* unless it was -1, in which case anything goes except 0. */ + if(xof_length == 0xFFFFUL) { + if(outlen == 0) { + return -1; + } + } else { + if(outlen != xof_length) { + return -1; + } + } + + /* Finalize the root hash */ + if (blake2s_final(S->S, root, BLAKE2S_OUTBYTES) < 0) { + return -1; + } + + /* Set common block structure values */ + /* Copy values from parent instance, and only change the ones below */ + memcpy(P, S->P, sizeof(blake2s_param)); + P->key_length = 0; + P->fanout = 0; + P->depth = 0; + store32(&P->leaf_length, BLAKE2S_OUTBYTES); + P->inner_length = BLAKE2S_OUTBYTES; + P->node_depth = 0; + + for (i = 0; outlen > 0; ++i) { + const size_t block_size = (outlen < BLAKE2S_OUTBYTES) ? outlen : BLAKE2S_OUTBYTES; + /* Initialize state */ + P->digest_length = block_size; + store32(&P->node_offset, i); + blake2s_init_param(C, P); + /* Process key if needed */ + blake2s_update(C, root, BLAKE2S_OUTBYTES); + if (blake2s_final(C, (uint8_t *)out + i * BLAKE2S_OUTBYTES, block_size) < 0) { + return -1; + } + outlen -= block_size; + } + secure_zero_memory(root, sizeof(root)); + secure_zero_memory(P, sizeof(P)); + secure_zero_memory(C, sizeof(C)); + /* Put blake2xs in an invalid state? cf. blake2s_is_lastblock */ + return 0; +} + +int blake2xs(void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen) +{ + blake2xs_state S[1]; + + /* Verify parameters */ + if (NULL == in && inlen > 0) + return -1; + + if (NULL == out) + return -1; + + if (NULL == key && keylen > 0) + return -1; + + if (keylen > BLAKE2S_KEYBYTES) + return -1; + + if (outlen == 0) + return -1; + + /* Initialize the root block structure */ + if (blake2xs_init_key(S, outlen, key, keylen) < 0) { + return -1; + } + + /* Absorb the input message */ + blake2xs_update(S, in, inlen); + + /* Compute the root node of the tree and the final hash using the counter construction */ + return blake2xs_final(S, out, outlen); +} + +#if defined(BLAKE2XS_SELFTEST) +#include <string.h> +#include "blake2-kat.h" +int main( void ) +{ + uint8_t key[BLAKE2S_KEYBYTES]; + uint8_t buf[BLAKE2_KAT_LENGTH]; + size_t i, step, outlen; + + for( i = 0; i < BLAKE2S_KEYBYTES; ++i ) { + key[i] = ( uint8_t )i; + } + + for( i = 0; i < BLAKE2_KAT_LENGTH; ++i ) { + buf[i] = ( uint8_t )i; + } + + /* Testing length of ouputs rather than inputs */ + /* (Test of input lengths mostly covered by blake2s tests) */ + + /* Test simple API */ + for( outlen = 1; outlen <= BLAKE2_KAT_LENGTH; ++outlen ) + { + uint8_t hash[BLAKE2_KAT_LENGTH] = {0}; + if( blake2xs( hash, outlen, buf, BLAKE2_KAT_LENGTH, key, BLAKE2S_KEYBYTES ) < 0 ) { + goto fail; + } + + if( 0 != memcmp( hash, blake2xs_keyed_kat[outlen-1], outlen ) ) + { + goto fail; + } + } + + /* Test streaming API */ + for(step = 1; step < BLAKE2S_BLOCKBYTES; ++step) { + for (outlen = 1; outlen <= BLAKE2_KAT_LENGTH; ++outlen) { + uint8_t hash[BLAKE2_KAT_LENGTH]; + blake2xs_state S; + uint8_t * p = buf; + size_t mlen = BLAKE2_KAT_LENGTH; + int err = 0; + + if( (err = blake2xs_init_key(&S, outlen, key, BLAKE2S_KEYBYTES)) < 0 ) { + goto fail; + } + + while (mlen >= step) { + if ( (err = blake2xs_update(&S, p, step)) < 0 ) { + goto fail; + } + mlen -= step; + p += step; + } + if ( (err = blake2xs_update(&S, p, mlen)) < 0) { + goto fail; + } + if ( (err = blake2xs_final(&S, hash, outlen)) < 0) { + goto fail; + } + + if (0 != memcmp(hash, blake2xs_keyed_kat[outlen-1], outlen)) { + goto fail; + } + } + } + + puts( "ok" ); + return 0; +fail: + puts("error"); + return -1; +} +#endif diff --git a/src/Native/libmultihash/blake2/sse/genkat-c.c b/src/Native/libmultihash/blake2/sse/genkat-c.c new file mode 100644 index 000000000..58a48fdab --- /dev/null +++ b/src/Native/libmultihash/blake2/sse/genkat-c.c @@ -0,0 +1,139 @@ +/* + BLAKE2 reference source code package - reference C implementations + + Copyright 2012, Samuel Neves <sneves@dei.uc.pt>. You may use this under the + terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at + your option. The terms of these licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "blake2.h" + +#define STR_(x) #x +#define STR(x) STR_(x) + +#define LENGTH 256 + +#define MAKE_KAT(name, size_prefix) \ + do { \ + printf("static const uint8_t " #name "_kat[BLAKE2_KAT_LENGTH][" #size_prefix \ + "_OUTBYTES] = \n{\n"); \ + \ + for (i = 0; i < LENGTH; ++i) { \ + name(hash, size_prefix##_OUTBYTES, in, i, NULL, 0); \ + printf("\t{\n\t\t"); \ + \ + for (j = 0; j < size_prefix##_OUTBYTES; ++j) \ + printf("0x%02X%s", hash[j], \ + (j + 1) == size_prefix##_OUTBYTES ? "\n" : j && !((j + 1) % 8) ? ",\n\t\t" : ", "); \ + \ + printf("\t},\n"); \ + } \ + \ + printf("};\n\n\n\n\n"); \ + } while (0) + +#define MAKE_KEYED_KAT(name, size_prefix) \ + do { \ + printf("static const uint8_t " #name "_keyed_kat[BLAKE2_KAT_LENGTH][" #size_prefix \ + "_OUTBYTES] = \n{\n"); \ + \ + for (i = 0; i < LENGTH; ++i) { \ + name(hash, size_prefix##_OUTBYTES, in, i, key, size_prefix##_KEYBYTES); \ + printf("\t{\n\t\t"); \ + \ + for (j = 0; j < size_prefix##_OUTBYTES; ++j) \ + printf("0x%02X%s", hash[j], \ + (j + 1) == size_prefix##_OUTBYTES ? "\n" : j && !((j + 1) % 8) ? ",\n\t\t" : ", "); \ + \ + printf("\t},\n"); \ + } \ + \ + printf("};\n\n\n\n\n"); \ + } while (0) + +#define MAKE_XOF_KAT(name) \ + do { \ + printf("static const uint8_t " #name "_kat[BLAKE2_KAT_LENGTH][BLAKE2_KAT_LENGTH] = \n{\n"); \ + \ + for (i = 1; i <= LENGTH; ++i) { \ + name(hash, i, in, LENGTH, NULL, 0); \ + printf("\t{\n\t\t"); \ + \ + for (j = 0; j < i; ++j) \ + printf("0x%02X%s", hash[j], \ + (j + 1) == LENGTH ? "\n" : j && !((j + 1) % 8) ? ",\n\t\t" : ", "); \ + \ + for (j = i; j < LENGTH; ++j) \ + printf("0x00%s", (j + 1) == LENGTH ? "\n" : j && !((j + 1) % 8) ? ",\n\t\t" : ", "); \ + \ + printf("\t},\n"); \ + } \ + \ + printf("};\n\n\n\n\n"); \ + } while (0) + +#define MAKE_XOF_KEYED_KAT(name, size_prefix) \ + do { \ + printf("static const uint8_t " #name \ + "_keyed_kat[BLAKE2_KAT_LENGTH][BLAKE2_KAT_LENGTH] = \n{\n"); \ + \ + for (i = 1; i <= LENGTH; ++i) { \ + name(hash, i, in, LENGTH, key, size_prefix##_KEYBYTES); \ + printf("\t{\n\t\t"); \ + \ + for (j = 0; j < i; ++j) \ + printf("0x%02X%s", hash[j], \ + (j + 1) == LENGTH ? "\n" : j && !((j + 1) % 8) ? ",\n\t\t" : ", "); \ + \ + for (j = i; j < LENGTH; ++j) \ + printf("0x00%s", (j + 1) == LENGTH ? "\n" : j && !((j + 1) % 8) ? ",\n\t\t" : ", "); \ + \ + printf("\t},\n"); \ + } \ + \ + printf("};\n\n\n\n\n"); \ + } while (0) + +int main() { + uint8_t key[64] = {0}; + uint8_t in[LENGTH] = {0}; + uint8_t hash[LENGTH] = {0}; + size_t i, j; + + for (i = 0; i < sizeof(in); ++i) + in[i] = i; + + for (i = 0; i < sizeof(key); ++i) + key[i] = i; + + puts("#ifndef BLAKE2_KAT_H\n" + "#define BLAKE2_KAT_H\n\n\n" + "#include <stdint.h>\n\n" + "#define BLAKE2_KAT_LENGTH " STR(LENGTH) "\n\n\n"); + MAKE_KAT(blake2s, BLAKE2S); + MAKE_KEYED_KAT(blake2s, BLAKE2S); + MAKE_KAT(blake2b, BLAKE2B); + MAKE_KEYED_KAT(blake2b, BLAKE2B); + MAKE_KAT(blake2sp, BLAKE2S); + MAKE_KEYED_KAT(blake2sp, BLAKE2S); + MAKE_KAT(blake2bp, BLAKE2B); + MAKE_KEYED_KAT(blake2bp, BLAKE2B); + MAKE_XOF_KAT(blake2xs); + MAKE_XOF_KEYED_KAT(blake2xs, BLAKE2S); + MAKE_XOF_KAT(blake2xb); + MAKE_XOF_KEYED_KAT(blake2xb, BLAKE2B); + puts("#endif"); + return 0; +} diff --git a/src/Native/libmultihash/blake2/sse/genkat-json.c b/src/Native/libmultihash/blake2/sse/genkat-json.c new file mode 100644 index 000000000..0275fb513 --- /dev/null +++ b/src/Native/libmultihash/blake2/sse/genkat-json.c @@ -0,0 +1,154 @@ +/* + BLAKE2 reference source code package - reference C implementations + + Copyright 2012, Samuel Neves <sneves@dei.uc.pt>. You may use this under the + terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at + your option. The terms of these licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "blake2.h" + +#define STR_(x) #x +#define STR(x) STR_(x) + +#define LENGTH 256 + +#define MAKE_KAT(name, size_prefix, first) \ + do { \ + for (i = 0; i < LENGTH; ++i) { \ + printf("%s\n{\n", i == 0 && first ? "" : ","); \ + \ + printf(" \"hash\": \"" #name "\",\n"); \ + printf(" \"in\": \""); \ + for (j = 0; j < i; ++j) \ + printf("%02x", in[j]); \ + \ + printf("\",\n"); \ + printf(" \"key\": \"\",\n"); \ + printf(" \"out\": \""); \ + \ + name(hash, size_prefix##_OUTBYTES, in, i, NULL, 0); \ + \ + for (j = 0; j < size_prefix##_OUTBYTES; ++j) \ + printf("%02x", hash[j]); \ + printf("\"\n"); \ + printf("}"); \ + } \ + } while (0) + +#define MAKE_KEYED_KAT(name, size_prefix, first) \ + do { \ + for (i = 0; i < LENGTH; ++i) { \ + printf("%s\n{\n", i == 0 && first ? "" : ","); \ + \ + printf(" \"hash\": \"" #name "\",\n"); \ + printf(" \"in\": \""); \ + for (j = 0; j < i; ++j) \ + printf("%02x", in[j]); \ + \ + printf("\",\n"); \ + printf(" \"key\": \""); \ + for (j = 0; j < size_prefix##_KEYBYTES; ++j) \ + printf("%02x", key[j]); \ + printf("\",\n"); \ + printf(" \"out\": \""); \ + \ + name(hash, size_prefix##_OUTBYTES, in, i, key, size_prefix##_KEYBYTES); \ + \ + for (j = 0; j < size_prefix##_OUTBYTES; ++j) \ + printf("%02x", hash[j]); \ + printf("\"\n"); \ + printf("}"); \ + } \ + } while (0) + +#define MAKE_XOF_KAT(name, first) \ + do { \ + for (i = 1; i <= LENGTH; ++i) { \ + printf("%s\n{\n", i == 1 && first ? "" : ","); \ + \ + printf(" \"hash\": \"" #name "\",\n"); \ + printf(" \"in\": \""); \ + for (j = 0; j < LENGTH; ++j) \ + printf("%02x", in[j]); \ + \ + printf("\",\n"); \ + printf(" \"key\": \"\",\n"); \ + printf(" \"out\": \""); \ + \ + name(hash, i, in, LENGTH, NULL, 0); \ + \ + for (j = 0; j < i; ++j) \ + printf("%02x", hash[j]); \ + printf("\"\n"); \ + printf("}"); \ + } \ + } while (0) + +#define MAKE_XOF_KEYED_KAT(name, size_prefix, first) \ + do { \ + for (i = 1; i <= LENGTH; ++i) { \ + printf("%s\n{\n", i == 1 && first ? "" : ","); \ + \ + printf(" \"hash\": \"" #name "\",\n"); \ + printf(" \"in\": \""); \ + for (j = 0; j < LENGTH; ++j) \ + printf("%02x", in[j]); \ + \ + printf("\",\n"); \ + printf(" \"key\": \""); \ + for (j = 0; j < size_prefix##_KEYBYTES; ++j) \ + printf("%02x", key[j]); \ + printf("\",\n"); \ + printf(" \"out\": \""); \ + \ + name(hash, i, in, LENGTH, key, size_prefix##_KEYBYTES); \ + \ + for (j = 0; j < i; ++j) \ + printf("%02x", hash[j]); \ + printf("\"\n"); \ + printf("}"); \ + } \ + } while (0) + +int main() { + uint8_t key[64] = {0}; + uint8_t in[LENGTH] = {0}; + uint8_t hash[LENGTH] = {0}; + size_t i, j; + + for (i = 0; i < sizeof(in); ++i) + in[i] = i; + + for (i = 0; i < sizeof(key); ++i) + key[i] = i; + + printf("["); + MAKE_KAT(blake2s, BLAKE2S, 1); + MAKE_KEYED_KAT(blake2s, BLAKE2S, 0); + MAKE_KAT(blake2b, BLAKE2B, 0); + MAKE_KEYED_KAT(blake2b, BLAKE2B, 0); + MAKE_KAT(blake2sp, BLAKE2S, 0); + MAKE_KEYED_KAT(blake2sp, BLAKE2S, 0); + MAKE_KAT(blake2bp, BLAKE2B, 0); + MAKE_KEYED_KAT(blake2bp, BLAKE2B, 0); + MAKE_XOF_KAT(blake2xs, 0); + MAKE_XOF_KEYED_KAT(blake2xs, BLAKE2S, 0); + MAKE_XOF_KAT(blake2xb, 0); + MAKE_XOF_KEYED_KAT(blake2xb, BLAKE2B, 0); + printf("\n]\n"); + fflush(stdout); + return 0; +} diff --git a/src/Native/libmultihash/blake2s.c b/src/Native/libmultihash/blake2s.c deleted file mode 100644 index 466540a0e..000000000 --- a/src/Native/libmultihash/blake2s.c +++ /dev/null @@ -1,15 +0,0 @@ -#include "blake2s.h" -#include <stdlib.h> -#include <stdint.h> -#include <string.h> -#include <stdio.h> - -#include "sha3/sph_blake2s.h" - -void blake2s_hash(const char* input, char* output, int inlen) -{ - blake2s_state ctx_blake2s; - blake2s_init(&ctx_blake2s, BLAKE2S_OUTBYTES); - blake2s_update(&ctx_blake2s, input, inlen); - blake2s_final(&ctx_blake2s, output, BLAKE2S_OUTBYTES); -} diff --git a/src/Native/libmultihash/blake2s.h b/src/Native/libmultihash/blake2s.h deleted file mode 100644 index 4e13893c1..000000000 --- a/src/Native/libmultihash/blake2s.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef BLAKE2S_H -#define BLAKE2S_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include <stdint.h> - -void blake2s_hash(const char* input, char* output, int inlen); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/src/Native/libmultihash/exports.cpp b/src/Native/libmultihash/exports.cpp index e8c04a330..e410057f9 100644 --- a/src/Native/libmultihash/exports.cpp +++ b/src/Native/libmultihash/exports.cpp @@ -25,7 +25,6 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "x11.h" #include "groestl.h" #include "blake.h" -#include "blake2s.h" #include "fugue.h" #include "geek.h" #include "qubit.h" @@ -51,6 +50,12 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "verthash/h2.h" #include "equi/equihashverify.h" +#ifdef _WIN32 +#include "blake2/ref/blake2.h" +#else +#include "blake2/sse/blake2.h" +#endif + #ifdef _WIN32 #define MODULE_API __declspec(dllexport) #else @@ -134,9 +139,14 @@ extern "C" MODULE_API void blake_export(const char* input, char* output, uint32_ blake_hash(input, output, input_len); } -extern "C" MODULE_API void blake2s_export(const char* input, char* output, uint32_t input_len) +extern "C" MODULE_API void blake2s_export(const char* input, char* output, uint32_t input_len, uint32_t output_len) +{ + blake2s(output, output_len == -1 ? BLAKE2S_OUTBYTES : output_len, input, input_len, NULL, 0); +} + +extern "C" MODULE_API void blake2b_export(const char* input, char* output, uint32_t input_len, uint32_t output_len) { - blake2s_hash(input, output, input_len); + blake2b(output, output_len == -1 ? BLAKE2B_OUTBYTES : output_len, input, input_len, NULL, 0); } extern "C" MODULE_API void dcrypt_export(const char* input, char* output, uint32_t input_len) diff --git a/src/Native/libmultihash/libmultihash.vcxproj b/src/Native/libmultihash/libmultihash.vcxproj index 9ab2cd36a..3f3a659e5 100644 --- a/src/Native/libmultihash/libmultihash.vcxproj +++ b/src/Native/libmultihash/libmultihash.vcxproj @@ -170,7 +170,8 @@ <ItemGroup> <ClInclude Include="bcrypt.h" /> <ClInclude Include="blake.h" /> - <ClInclude Include="blake2s.h" /> + <ClInclude Include="blake2\ref\blake2-impl.h" /> + <ClInclude Include="blake2\ref\blake2.h" /> <ClInclude Include="boolberry.h" /> <ClInclude Include="brg_endian.h" /> <ClInclude Include="c11.h" /> @@ -216,7 +217,6 @@ <ClInclude Include="sha3\extra.h" /> <ClInclude Include="sha3\gost_streebog.h" /> <ClInclude Include="sha3\sph_blake.h" /> - <ClInclude Include="sha3\sph_blake2s.h" /> <ClInclude Include="sha3\sph_bmw.h" /> <ClInclude Include="sha3\sph_cubehash.h" /> <ClInclude Include="sha3\sph_echo.h" /> @@ -261,7 +261,12 @@ <ItemGroup> <ClCompile Include="bcrypt.c" /> <ClCompile Include="blake.c" /> - <ClCompile Include="blake2s.c" /> + <ClCompile Include="blake2\ref\blake2b-ref.c" /> + <ClCompile Include="blake2\ref\blake2bp-ref.c" /> + <ClCompile Include="blake2\ref\blake2s-ref.c" /> + <ClCompile Include="blake2\ref\blake2sp-ref.c" /> + <ClCompile Include="blake2\ref\blake2xb-ref.c" /> + <ClCompile Include="blake2\ref\blake2xs-ref.c" /> <ClCompile Include="c11.c" /> <ClCompile Include="dcrypt.c" /> <ClCompile Include="dllmain.cpp" /> @@ -305,7 +310,6 @@ <ClCompile Include="sha3\panama.c" /> <ClCompile Include="sha3\sm3.c" /> <ClCompile Include="sha3\sph_blake.c" /> - <ClCompile Include="sha3\sph_blake2s.c" /> <ClCompile Include="sha3\sph_bmw.c" /> <ClCompile Include="sha3\sph_cubehash.c" /> <ClCompile Include="sha3\sph_echo.c" /> diff --git a/src/Native/libmultihash/libmultihash.vcxproj.filters b/src/Native/libmultihash/libmultihash.vcxproj.filters index 29978ddef..cdbf39996 100644 --- a/src/Native/libmultihash/libmultihash.vcxproj.filters +++ b/src/Native/libmultihash/libmultihash.vcxproj.filters @@ -167,12 +167,6 @@ <ClInclude Include="sha3\sph_sha2.h"> <Filter>Header Files</Filter> </ClInclude> - <ClInclude Include="sha3\sph_blake2s.h"> - <Filter>Header Files</Filter> - </ClInclude> - <ClInclude Include="blake2s.h"> - <Filter>Header Files</Filter> - </ClInclude> <ClInclude Include="x16r.h"> <Filter>Header Files</Filter> </ClInclude> @@ -278,6 +272,12 @@ <ClInclude Include="verthash\h2.h"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="blake2\ref\blake2.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="blake2\ref\blake2-impl.h"> + <Filter>Header Files</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="stdafx.cpp"> @@ -430,12 +430,6 @@ <ClCompile Include="sha3\sph_sha2big.c"> <Filter>Source Files</Filter> </ClCompile> - <ClCompile Include="sha3\sph_blake2s.c"> - <Filter>Source Files</Filter> - </ClCompile> - <ClCompile Include="blake2s.c"> - <Filter>Source Files</Filter> - </ClCompile> <ClCompile Include="x16r.c"> <Filter>Source Files</Filter> </ClCompile> @@ -526,6 +520,24 @@ <ClCompile Include="verthash\tiny_sha3\sha3.c"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="blake2\ref\blake2bp-ref.c"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="blake2\ref\blake2b-ref.c"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="blake2\ref\blake2sp-ref.c"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="blake2\ref\blake2s-ref.c"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="blake2\ref\blake2xb-ref.c"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="blake2\ref\blake2xs-ref.c"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="Makefile" /> diff --git a/src/Native/libmultihash/x25x.c b/src/Native/libmultihash/x25x.c index 9e812a759..8532897f9 100644 --- a/src/Native/libmultihash/x25x.c +++ b/src/Native/libmultihash/x25x.c @@ -4,7 +4,6 @@ #include <stdint.h> #include "sha3/sph_blake.h" -#include "sha3/sph_blake2s.h" #include "sha3/sph_bmw.h" #include "sha3/sph_groestl.h" #include "sha3/sph_jh.h" @@ -27,7 +26,12 @@ #include "sha3/SWIFFTX.h" #include "sha3/sph_panama.h" #include "lane.h" -#include "blake2s.h" + +#ifdef _WIN32 +#include "blake2/ref/blake2.h" +#else +#include "blake2/sse/blake2.h" +#endif #define blake2s_salt32(out, in, inlen, key32) blake2s(out, in, key32, 32, inlen, 32) /* neoscrypt */ #define blake2s_simple(out, in, inlen) blake2s(out, in, NULL, 32, inlen, 0) @@ -35,7 +39,7 @@ typedef struct { unsigned char hash[64]; -} uint512; +} uint512; void x25x_hash(const char* input, char* output, uint32_t len) { From 04e97d1887bf740bf6cae20ee472de4b4ce88274 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Mon, 5 Jul 2021 18:49:21 +0200 Subject: [PATCH 002/145] WIP --- .../Bitcoin/BitcoinJobManagerBase.cs | 14 +- .../Blockchain/Ergo/ErgoJobManager.cs | 185 ++++++++-- .../Blockchain/Ergo/ErgoPayoutHandler.cs | 324 ++++++++++++++++++ .../Blockchain/Ergo/RPC/ErgoClient.cs | 5 +- 4 files changed, 491 insertions(+), 37 deletions(-) create mode 100644 src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs index 723fa2c90..64db89567 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs @@ -22,6 +22,7 @@ using NBitcoin; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using static Miningcore.Util.ActionUtils; namespace Miningcore.Blockchain.Bitcoin { @@ -534,17 +535,8 @@ protected override async Task PostStartInitAsync(CancellationToken ct) // Periodically update network stats Observable.Interval(TimeSpan.FromMinutes(10)) .Select(via => Observable.FromAsync(async () => - { - try - { - await (!hasLegacyDaemon ? UpdateNetworkStatsAsync() : UpdateNetworkStatsLegacyAsync()); - } - - catch(Exception ex) - { - logger.Error(ex); - } - })) + await Guard(()=> (!hasLegacyDaemon ? UpdateNetworkStatsAsync() : UpdateNetworkStatsLegacyAsync()), + ex => logger.Error(ex)))) .Concat() .Subscribe(); diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs index 94e7047b5..c2b07bb00 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs @@ -1,6 +1,9 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Net.Http; +using System.Reactive.Linq; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Autofac; @@ -31,9 +34,99 @@ public ErgoJobManager( private ErgoCoinTemplate coin; private ErgoClient daemon; + protected string network; + protected TimeSpan jobRebroadcastTimeout; private readonly IHttpClientFactory httpClientFactory; private readonly IExtraNonceProvider extraNonceProvider; + protected virtual void SetupJobUpdates() + { + jobRebroadcastTimeout = TimeSpan.FromSeconds(Math.Max(1, poolConfig.JobRebroadcastTimeout)); + var blockFound = blockFoundSubject.Synchronize(); + var pollTimerRestart = blockFoundSubject.Synchronize(); + + var triggers = new List<IObservable<(bool Force, string Via, string Data)>> + { + blockFound.Select(x => (false, JobRefreshBy.BlockFound, (string) null)) + }; + + if(true) // extraPoolConfig?.BtStream == null) + { + if(poolConfig.BlockRefreshInterval > 0) + { + // periodically update block-template + var pollingInterval = poolConfig.BlockRefreshInterval > 0 ? poolConfig.BlockRefreshInterval : 1000; + + triggers.Add(Observable.Timer(TimeSpan.FromMilliseconds(pollingInterval)) + .TakeUntil(pollTimerRestart) + .Select(_ => (false, JobRefreshBy.Poll, (string) null)) + .Repeat()); + } + + else + { + // get initial blocktemplate + triggers.Add(Observable.Interval(TimeSpan.FromMilliseconds(1000)) + .Select(_ => (false, JobRefreshBy.Initial, (string) null)) + .TakeWhile(_ => !hasInitialBlockTemplate)); + } + + // periodically update transactions for current template + if(poolConfig.JobRebroadcastTimeout > 0) + { + triggers.Add(Observable.Timer(jobRebroadcastTimeout) + .TakeUntil(pollTimerRestart) + .Select(_ => (true, JobRefreshBy.PollRefresh, (string) null)) + .Repeat()); + } + } + + //else + //{ + // var btStream = BtStreamSubscribe(extraPoolConfig.BtStream); + + // if(poolConfig.JobRebroadcastTimeout > 0) + // { + // var interval = TimeSpan.FromSeconds(Math.Max(1, poolConfig.JobRebroadcastTimeout - 0.1d)); + + // triggers.Add(btStream + // .Select(json => + // { + // var force = !lastJobRebroadcast.HasValue || (clock.Now - lastJobRebroadcast >= interval); + // return (force, !force ? JobRefreshBy.BlockTemplateStream : JobRefreshBy.BlockTemplateStreamRefresh, json); + // }) + // .Publish() + // .RefCount()); + // } + + // else + // { + // triggers.Add(btStream + // .Select(json => (false, JobRefreshBy.BlockTemplateStream, json)) + // .Publish() + // .RefCount()); + // } + + // // get initial blocktemplate + // triggers.Add(Observable.Interval(TimeSpan.FromMilliseconds(1000)) + // .Select(_ => (false, JobRefreshBy.Initial, (string) null)) + // .TakeWhile(_ => !hasInitialBlockTemplate)); + //} + + Jobs = Observable.Merge(triggers) + .Select(x => Observable.FromAsync(() => UpdateJob(x.Force, x.Via, x.Data))) + .Concat() + .Where(x => x.IsNew || x.Force) + .Do(x => + { + if(x.IsNew) + hasInitialBlockTemplate = true; + }) + .Select(x => GetJobParamsForStratum(x.IsNew)) + .Publish() + .RefCount(); + } + protected async Task ShowDaemonSyncProgressAsync() { var info = await Guard(() => daemon.GetNodeInfoAsync(), @@ -41,8 +134,7 @@ protected async Task ShowDaemonSyncProgressAsync() if(info.FullHeight.HasValue && info.HeadersHeight.HasValue) { - var totalBlocks = info.FullHeight.Value; - var percent = (double) info.HeadersHeight.Value / totalBlocks * 100; + var percent = (double) info.FullHeight.Value / info.HeadersHeight.Value * 100; logger.Info(() => $"Daemon has downloaded {percent:0.00}% of blockchain from {info.PeersCount} peers"); } @@ -55,32 +147,39 @@ protected async Task UpdateNetworkStatsAsync() { logger.LogInvoke(); - //try - //{ - // var infoResponse = await daemon.ExecuteCmdAnyAsync(logger, CryptonoteCommands.GetInfo); - - // if(infoResponse.Error != null) - // logger.Warn(() => $"Error(s) refreshing network stats: {infoResponse.Error.Message} (Code {infoResponse.Error.Code})"); - - // if(infoResponse.Response != null) - // { - // var info = infoResponse.Response.ToObject<GetInfoResponse>(); + var info = await Guard(() => daemon.GetNodeInfoAsync(), + ex=> logger.Debug(ex)); - // BlockchainStats.NetworkHashrate = info.Target > 0 ? (double) info.Difficulty / info.Target : 0; - // BlockchainStats.ConnectedPeers = info.OutgoingConnectionsCount + info.IncomingConnectionsCount; - // } - //} + if(info != null) + { + BlockchainStats.ConnectedPeers = info.PeersCount; + // TODO: BlockchainStats.NetworkHashrate = info.HeadersScore + } + } - //catch(Exception e) - //{ - // logger.Error(e); - //} + protected void ConfigureRewards() + { + // Donation to MiningCore development + if(network == "mainnet" && + DevDonation.Addresses.TryGetValue(poolConfig.Template.Symbol, out var address)) + { + poolConfig.RewardRecipients = poolConfig.RewardRecipients.Concat(new[] + { + new RewardRecipient + { + Address = address, + Percentage = DevDonation.Percent, + Type = "dev" + } + }).ToArray(); + } } #region API-Surface public IObservable<object> Jobs { get; private set; } public BlockchainStats BlockchainStats { get; } = new(); + public string Network => network; public ErgoCoinTemplate Coin => coin; @@ -109,9 +208,48 @@ public async Task<bool> ValidateAddress(string address, CancellationToken ct) #region Overrides - protected override Task PostStartInitAsync(CancellationToken ct) + protected override async Task PostStartInitAsync(CancellationToken ct) { - return Task.CompletedTask; + // validate pool address + if(string.IsNullOrEmpty(poolConfig.Address)) + logger.ThrowLogPoolStartupException($"Pool address is not configured"); + + var validity = await Guard(() => daemon.CheckAddressValidityAsync(poolConfig.Address, ct), + ex=> logger.ThrowLogPoolStartupException($"Error validating pool address: {ex}")); + + if(!validity.IsValid) + logger.ThrowLogPoolStartupException($"Daemon reports pool address {poolConfig.Address} as invalid: {validity.Error}"); + + var info = await Guard(() => daemon.GetNodeInfoAsync(ct), + ex=> logger.ThrowLogPoolStartupException($"Daemon reports: {ex.Message}")); + + // chain detection + var m = Regex.Match(info.Name, "ergo-([^-]+)-.+"); + if(!m.Success) + logger.ThrowLogPoolStartupException($"Unable to identify network type ({info.Name}"); + + network = m.Groups[1].Value.ToLower(); + + // Payment-processing setup + if(clusterConfig.PaymentProcessing?.Enabled == true && poolConfig.PaymentProcessing?.Enabled == true) + { + ConfigureRewards(); + } + + // update stats + BlockchainStats.NetworkType = network; + BlockchainStats.RewardType = "POW"; + + await UpdateNetworkStatsAsync(); + + // Periodically update network stats + Observable.Interval(TimeSpan.FromMinutes(10)) + .Select(via => Observable.FromAsync(async () => + await Guard(UpdateNetworkStatsAsync, ex => logger.Error(ex)))) + .Concat() + .Subscribe(); + + SetupJobUpdates(); } public override void Configure(PoolConfig poolConfig, ClusterConfig clusterConfig) @@ -159,7 +297,8 @@ protected override async Task EnsureDaemonsSynchedAsync(CancellationToken ct) var info = await Guard(() => daemon.GetNodeInfoAsync(ct), ex=> logger.Debug(ex)); - var isSynched = info?.Difficulty.HasValue == true; + var isSynched = info?.FullHeight.HasValue == true && info?.HeadersHeight.HasValue == true && + info.FullHeight.Value > info.HeadersHeight.Value; if(isSynched) { diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs new file mode 100644 index 000000000..80beb6013 --- /dev/null +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -0,0 +1,324 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Autofac; +using AutoMapper; +using Miningcore.Blockchain.Bitcoin; +using Miningcore.Blockchain.Bitcoin.Configuration; +using Miningcore.Blockchain.Bitcoin.DaemonResponses; +using Miningcore.Configuration; +using Miningcore.DaemonInterface; +using Miningcore.Extensions; +using Miningcore.Messaging; +using Miningcore.Payments; +using Miningcore.Persistence; +using Miningcore.Persistence.Model; +using Miningcore.Persistence.Repositories; +using Miningcore.Time; +using Miningcore.Util; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Block = Miningcore.Persistence.Model.Block; +using Contract = Miningcore.Contracts.Contract; + +namespace Miningcore.Blockchain.Ergo +{ + [CoinFamily(CoinFamily.Ergo)] + public class ErgoPayoutHandler : PayoutHandlerBase, + IPayoutHandler + { + public ErgoPayoutHandler( + IComponentContext ctx, + IConnectionFactory cf, + IMapper mapper, + IShareRepository shareRepo, + IBlockRepository blockRepo, + IBalanceRepository balanceRepo, + IPaymentRepository paymentRepo, + IMasterClock clock, + IMessageBus messageBus) : + base(cf, mapper, shareRepo, blockRepo, balanceRepo, paymentRepo, clock, messageBus) + { + Contract.RequiresNonNull(ctx, nameof(ctx)); + Contract.RequiresNonNull(balanceRepo, nameof(balanceRepo)); + Contract.RequiresNonNull(paymentRepo, nameof(paymentRepo)); + + this.ctx = ctx; + } + + protected readonly IComponentContext ctx; + protected DaemonClient daemon; + + protected override string LogCategory => "Bitcoin Payout Handler"; + + #region IPayoutHandler + + public virtual Task ConfigureAsync(ClusterConfig clusterConfig, PoolConfig poolConfig) + { + Contract.RequiresNonNull(poolConfig, nameof(poolConfig)); + + this.poolConfig = poolConfig; + this.clusterConfig = clusterConfig; + + //extraPoolConfig = poolConfig.Extra.SafeExtensionDataAs<BitcoinDaemonEndpointConfigExtra>(); + //extraPoolPaymentProcessingConfig = poolConfig.PaymentProcessing.Extra.SafeExtensionDataAs<BitcoinPoolPaymentProcessingConfigExtra>(); + + logger = LogUtil.GetPoolScopedLogger(typeof(BitcoinPayoutHandler), poolConfig); + + var jsonSerializerSettings = ctx.Resolve<JsonSerializerSettings>(); + daemon = new DaemonClient(jsonSerializerSettings, messageBus, clusterConfig.ClusterName ?? poolConfig.PoolName, poolConfig.Id); + daemon.Configure(poolConfig.Daemons); + + return Task.FromResult(true); + } + + public virtual async Task<Block[]> ClassifyBlocksAsync(Block[] blocks) + { + Contract.RequiresNonNull(poolConfig, nameof(poolConfig)); + Contract.RequiresNonNull(blocks, nameof(blocks)); + + //var coin = poolConfig.Template.As<CoinTemplate>(); + //var pageSize = 100; + //var pageCount = (int) Math.Ceiling(blocks.Length / (double) pageSize); + //var result = new List<Block>(); + + //for(var i = 0; i < pageCount; i++) + //{ + // // get a page full of blocks + // var page = blocks + // .Skip(i * pageSize) + // .Take(pageSize) + // .ToArray(); + + // // build command batch (block.TransactionConfirmationData is the hash of the blocks coinbase transaction) + // var batch = page.Select(block => new DaemonCmd(BitcoinCommands.GetTransaction, + // new[] { block.TransactionConfirmationData })).ToArray(); + + // // execute batch + // var results = await daemon.ExecuteBatchAnyAsync(logger, batch); + + // for(var j = 0; j < results.Length; j++) + // { + // var cmdResult = results[j]; + + // var transactionInfo = cmdResult.Response?.ToObject<Transaction>(); + // var block = page[j]; + + // // check error + // if(cmdResult.Error != null) + // { + // // Code -5 interpreted as "orphaned" + // if(cmdResult.Error.Code == -5) + // { + // block.Status = BlockStatus.Orphaned; + // block.Reward = 0; + // result.Add(block); + + // logger.Info(() => $"[{LogCategory}] Block {block.BlockHeight} classified as orphaned due to daemon error {cmdResult.Error.Code}"); + // } + + // else + // { + // logger.Warn(() => $"[{LogCategory}] Daemon reports error '{cmdResult.Error.Message}' (Code {cmdResult.Error.Code}) for transaction {page[j].TransactionConfirmationData}"); + // } + // } + + // // missing transaction details are interpreted as "orphaned" + // else if(transactionInfo?.Details == null || transactionInfo.Details.Length == 0) + // { + // block.Status = BlockStatus.Orphaned; + // block.Reward = 0; + // result.Add(block); + + // logger.Info(() => $"[{LogCategory}] Block {block.BlockHeight} classified as orphaned due to missing tx details"); + // } + + // else + // { + // switch(transactionInfo.Details[0].Category) + // { + // case "immature": + // // update progress + // var minConfirmations = extraPoolConfig?.MinimumConfirmations ?? BitcoinConstants.CoinbaseMinConfimations; + // block.ConfirmationProgress = Math.Min(1.0d, (double) transactionInfo.Confirmations / minConfirmations); + // block.Reward = transactionInfo.Amount; // update actual block-reward from coinbase-tx + // result.Add(block); + + // messageBus.NotifyBlockConfirmationProgress(poolConfig.Id, block, coin); + // break; + + // case "generate": + // // matured and spendable coinbase transaction + // block.Status = BlockStatus.Confirmed; + // block.ConfirmationProgress = 1; + // block.Reward = transactionInfo.Amount; // update actual block-reward from coinbase-tx + // result.Add(block); + + // logger.Info(() => $"[{LogCategory}] Unlocked block {block.BlockHeight} worth {FormatAmount(block.Reward)}"); + + // messageBus.NotifyBlockUnlocked(poolConfig.Id, block, coin); + // break; + + // default: + // logger.Info(() => $"[{LogCategory}] Block {block.BlockHeight} classified as orphaned. Category: {transactionInfo.Details[0].Category}"); + + // block.Status = BlockStatus.Orphaned; + // block.Reward = 0; + // result.Add(block); + + // messageBus.NotifyBlockUnlocked(poolConfig.Id, block, coin); + // break; + // } + // } + // } + //} + + //return result.ToArray(); + + return blocks; + } + + public virtual Task CalculateBlockEffortAsync(Block block, double accumulatedBlockShareDiff) + { + block.Effort = accumulatedBlockShareDiff / block.NetworkDifficulty; + + return Task.FromResult(true); + } + + public virtual async Task PayoutAsync(Balance[] balances) + { + Contract.RequiresNonNull(balances, nameof(balances)); + + // // build args + // var amounts = balances + // .Where(x => x.Amount > 0) + // .ToDictionary(x => x.Address, x => Math.Round(x.Amount, 4)); + + // if(amounts.Count == 0) + // return; + + // logger.Info(() => $"[{LogCategory}] Paying out {FormatAmount(balances.Sum(x => x.Amount))} to {balances.Length} addresses"); + + // object[] args; + + // if(extraPoolPaymentProcessingConfig?.MinersPayTxFees == true) + // { + // var identifier = !string.IsNullOrEmpty(clusterConfig.PaymentProcessing?.CoinbaseString) ? + // clusterConfig.PaymentProcessing.CoinbaseString.Trim() : "Miningcore"; + + // var comment = $"{identifier} Payment"; + // var subtractFeesFrom = amounts.Keys.ToArray(); + + // if(!poolConfig.Template.As<BitcoinTemplate>().HasMasterNodes) + // { + // args = new object[] + // { + // string.Empty, // default account + // amounts, // addresses and associated amounts + // 1, // only spend funds covered by this many confirmations + // comment, // tx comment + // subtractFeesFrom, // distribute transaction fee equally over all recipients, + + // // workaround for https://bitcoin.stackexchange.com/questions/102508/bitcoin-cli-sendtoaddress-error-fallbackfee-is-disabled-wait-a-few-blocks-or-en + // // using bitcoin regtest + // //true, + // //null, + // //"unset", + // //"1" + // }; + // } + + // else + // { + // args = new object[] + // { + // string.Empty, // default account + // amounts, // addresses and associated amounts + // 1, // only spend funds covered by this many confirmations + // false, // Whether to add confirmations to transactions locked via InstantSend + // comment, // tx comment + // subtractFeesFrom, // distribute transaction fee equally over all recipients + // false, // use_is: Send this transaction as InstantSend + // false, // Use anonymized funds only + // }; + // } + // } + + // else + // { + // args = new object[] + // { + // string.Empty, // default account + // amounts, // addresses and associated amounts + // }; + // } + + // var didUnlockWallet = false; + + //// send command + //tryTransfer: + // var result = await daemon.ExecuteCmdSingleAsync<string>(logger, BitcoinCommands.SendMany, args, new JsonSerializerSettings()); + + // if(result.Error == null) + // { + // if(didUnlockWallet) + // { + // // lock wallet + // logger.Info(() => $"[{LogCategory}] Locking wallet"); + // await daemon.ExecuteCmdSingleAsync<JToken>(logger, BitcoinCommands.WalletLock); + // } + + // // check result + // var txId = result.Response; + + // if(string.IsNullOrEmpty(txId)) + // logger.Error(() => $"[{LogCategory}] {BitcoinCommands.SendMany} did not return a transaction id!"); + // else + // logger.Info(() => $"[{LogCategory}] Payout transaction id: {txId}"); + + // await PersistPaymentsAsync(balances, txId); + + // NotifyPayoutSuccess(poolConfig.Id, balances, new[] { txId }, null); + // } + + // else + // { + // if(result.Error.Code == (int) BitcoinRPCErrorCode.RPC_WALLET_UNLOCK_NEEDED && !didUnlockWallet) + // { + // if(!string.IsNullOrEmpty(extraPoolPaymentProcessingConfig?.WalletPassword)) + // { + // logger.Info(() => $"[{LogCategory}] Unlocking wallet"); + + // var unlockResult = await daemon.ExecuteCmdSingleAsync<JToken>(logger, BitcoinCommands.WalletPassphrase, new[] + // { + // (object) extraPoolPaymentProcessingConfig.WalletPassword, + // (object) 5 // unlock for N seconds + // }); + + // if(unlockResult.Error == null) + // { + // didUnlockWallet = true; + // goto tryTransfer; + // } + + // else + // logger.Error(() => $"[{LogCategory}] {BitcoinCommands.WalletPassphrase} returned error: {result.Error.Message} code {result.Error.Code}"); + // } + + // else + // logger.Error(() => $"[{LogCategory}] Wallet is locked but walletPassword was not configured. Unable to send funds."); + // } + + // else + // { + // logger.Error(() => $"[{LogCategory}] {BitcoinCommands.SendMany} returned error: {result.Error.Message} code {result.Error.Code}"); + + // NotifyPayoutFailure(poolConfig.Id, balances, $"{BitcoinCommands.SendMany} returned error: {result.Error.Message} code {result.Error.Code}", null); + // } + // } + } + + #endregion // IPayoutHandler + } +} diff --git a/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs b/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs index a6a986548..e97336d74 100644 --- a/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs +++ b/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs @@ -5,7 +5,6 @@ //---------------------- using System.Collections.Generic; -using Newtonsoft.Json.Linq; #nullable enable @@ -9407,11 +9406,11 @@ public partial class NodeInfo /// <summary>Can be 'null' if no headers is applied since node launch</summary> [Newtonsoft.Json.JsonProperty("headersScore", Required = Newtonsoft.Json.Required.AllowNull)] - public JToken HeadersScore { get; set; }= default!; + public string HeadersScore { get; set; }= default!; /// <summary>Can be 'null' if no full block is applied since node launch</summary> [Newtonsoft.Json.JsonProperty("fullBlocksScore", Required = Newtonsoft.Json.Required.AllowNull)] - public JToken FullBlocksScore { get; set; }= default!; + public string FullBlocksScore { get; set; }= default!; /// <summary>Can be 'null' if genesis blocks is not produced yet</summary> [Newtonsoft.Json.JsonProperty("genesisBlockId", Required = Newtonsoft.Json.Required.AllowNull)] From 475811ab242d0e7ddc1edf5f74539e5e1b2591cb Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Mon, 5 Jul 2021 22:39:22 +0200 Subject: [PATCH 003/145] WIP --- .../Ergo/Configuration/ErgoPoolConfigExtra.cs | 11 ++ .../Blockchain/Ergo/ErgoBlockTemplate.cs | 4 + src/Miningcore/Blockchain/Ergo/ErgoJob.cs | 43 ++++- .../Blockchain/Ergo/ErgoJobManager.cs | 147 ++++++++++++++++-- .../Blockchain/Ergo/RPC/ErgoClient.cs | 12 +- 5 files changed, 201 insertions(+), 16 deletions(-) create mode 100644 src/Miningcore/Blockchain/Ergo/Configuration/ErgoPoolConfigExtra.cs create mode 100644 src/Miningcore/Blockchain/Ergo/ErgoBlockTemplate.cs diff --git a/src/Miningcore/Blockchain/Ergo/Configuration/ErgoPoolConfigExtra.cs b/src/Miningcore/Blockchain/Ergo/Configuration/ErgoPoolConfigExtra.cs new file mode 100644 index 000000000..c4927d19d --- /dev/null +++ b/src/Miningcore/Blockchain/Ergo/Configuration/ErgoPoolConfigExtra.cs @@ -0,0 +1,11 @@ +namespace Miningcore.Blockchain.Ergo.Configuration +{ + public class ErgoPoolConfigExtra + { + /// <summary> + /// Maximum number of tracked jobs. + /// Default: 12 - you should increase this value if your blockrefreshinterval is higher than 300ms + /// </summary> + public int? MaxActiveJobs { get; set; } + } +} diff --git a/src/Miningcore/Blockchain/Ergo/ErgoBlockTemplate.cs b/src/Miningcore/Blockchain/Ergo/ErgoBlockTemplate.cs new file mode 100644 index 000000000..e658cba74 --- /dev/null +++ b/src/Miningcore/Blockchain/Ergo/ErgoBlockTemplate.cs @@ -0,0 +1,4 @@ +namespace Miningcore.Blockchain.Ergo +{ + public record ErgoBlockTemplate(WorkMessage Work, NodeInfo Info); +} diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs index 3ecb1f7e2..daef30cdc 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs @@ -4,21 +4,54 @@ using System.Text; using System.Threading.Tasks; using Miningcore.Blockchain.Cryptonote; +using Miningcore.Configuration; +using Miningcore.Time; +using Newtonsoft.Json.Linq; namespace Miningcore.Blockchain.Ergo { public class ErgoJob { - public string PrevHash { get; set; } + public ErgoBlockTemplate BlockTemplate { get; private set; } + public double Difficulty { get; private set; } + public ulong Height => BlockTemplate.Work.Height; + public string JobId { get; protected set; } - public void PrepareWorkerJob(CryptonoteWorkerJob workerJob, out string blob, out string target) + protected object[] jobParams; + + public object GetJobParams(bool isNew) { - throw new NotImplementedException(); + jobParams[^1] = isNew; + return jobParams; } - public object GetJobParams(bool isNew) + public void Init(ErgoBlockTemplate blockTemplate, string jobId, PoolConfig poolConfig, ClusterConfig clusterConfig, IMasterClock clock, string network) { - throw new NotImplementedException(); + BlockTemplate = blockTemplate; + JobId = jobId; + + // Network difficulty + if(blockTemplate.Info.Difficulty.Type == JTokenType.Object) + { + if(((JObject) blockTemplate.Info.Difficulty).TryGetValue("proof-of-work", out var diffVal)) + Difficulty = diffVal.Value<double>(); + } + + else + Difficulty = blockTemplate.Info.Difficulty.Value<double>(); + + jobParams = new object[] + { + JobId, + Height, + BlockTemplate.Work.Msg, + string.Empty, + string.Empty, + BlockTemplate.Info.Parameters.BlockVersion, + BlockTemplate.Work.B, + string.Empty, + false + }; } } } diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs index c2b07bb00..74077e360 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs @@ -7,11 +7,21 @@ using System.Threading; using System.Threading.Tasks; using Autofac; +using Miningcore.Blockchain.Bitcoin; +using Miningcore.Blockchain.Bitcoin.Configuration; +using Miningcore.Blockchain.Bitcoin.DaemonResponses; +using Miningcore.Blockchain.Ergo.Configuration; +using NLog; using Miningcore.Configuration; +using Miningcore.DaemonInterface; using Miningcore.Extensions; +using Miningcore.JsonRpc; using Miningcore.Messaging; using Miningcore.Stratum; +using Miningcore.Time; using Miningcore.Util; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Contract = Miningcore.Contracts.Contract; using static Miningcore.Util.ActionUtils; @@ -22,12 +32,15 @@ public class ErgoJobManager : JobManagerBase<ErgoJob> public ErgoJobManager( IComponentContext ctx, IMessageBus messageBus, + IMasterClock clock, IHttpClientFactory httpClientFactory, IExtraNonceProvider extraNonceProvider) : base(ctx, messageBus) { Contract.RequiresNonNull(httpClientFactory, nameof(httpClientFactory)); + Contract.RequiresNonNull(clock, nameof(clock)); + this.clock = clock; this.extraNonceProvider = extraNonceProvider; this.httpClientFactory = httpClientFactory; } @@ -36,8 +49,13 @@ public ErgoJobManager( private ErgoClient daemon; protected string network; protected TimeSpan jobRebroadcastTimeout; + protected DateTime? lastJobRebroadcast; + protected readonly List<ErgoJob> validJobs = new(); + protected int maxActiveJobs = 4; private readonly IHttpClientFactory httpClientFactory; private readonly IExtraNonceProvider extraNonceProvider; + private readonly IMasterClock clock; + private ErgoPoolConfigExtra extraPoolConfig; protected virtual void SetupJobUpdates() { @@ -127,18 +145,118 @@ protected virtual void SetupJobUpdates() .RefCount(); } + protected async Task<(bool IsNew, bool Force)> UpdateJob(bool forceUpdate, string via = null, string json = null) + { + logger.LogInvoke(); + + try + { + if(forceUpdate) + lastJobRebroadcast = clock.Now; + + var blockTemplate = string.IsNullOrEmpty(json) ? + await GetBlockTemplateAsync() : + GetBlockTemplateFromJson(json); + + var job = currentJob; + + var isNew = job == null || + (blockTemplate != null && + (job.BlockTemplate?.Work.Msg != blockTemplate.Work.Msg || + blockTemplate.Work?.Height > job.BlockTemplate.Work.Height)); + + if(isNew) + messageBus.NotifyChainHeight(poolConfig.Id, blockTemplate.Work.Height, poolConfig.Template); + + if(isNew || forceUpdate) + { + job = new ErgoJob(); + + job.Init(blockTemplate, NextJobId(), poolConfig, clusterConfig, clock, network); + + lock(jobLock) + { + validJobs.Insert(0, job); + + // trim active jobs + while(validJobs.Count > maxActiveJobs) + validJobs.RemoveAt(validJobs.Count - 1); + } + + if(isNew) + { + if(via != null) + logger.Info(() => $"Detected new block {job.Height} [{via}]"); + else + logger.Info(() => $"Detected new block {job.Height}"); + + // update stats + BlockchainStats.LastNetworkBlockTime = clock.Now; + BlockchainStats.BlockHeight = job.Height; + BlockchainStats.NetworkDifficulty = job.Difficulty; + //BlockchainStats.NextNetworkTarget = blockTemplate.Target; + //BlockchainStats.NextNetworkBits = blockTemplate.Bits; + } + + else + { + if(via != null) + logger.Debug(() => $"Template update {job.Height} [{via}]"); + else + logger.Debug(() => $"Template update {job.Height}"); + } + + currentJob = job; + } + + return (isNew, forceUpdate); + } + + catch(Exception ex) + { + logger.Error(ex, () => $"Error during {nameof(UpdateJob)}"); + } + + return (false, forceUpdate); + } + + protected async Task<ErgoBlockTemplate> GetBlockTemplateAsync() + { + logger.LogInvoke(); + + var infoTask = daemon.GetNodeInfoAsync(); + var miningCandidateTask = daemon.MiningRequestBlockCandidateAsync(CancellationToken.None); + + await Task.WhenAll(infoTask, miningCandidateTask); + + var info = infoTask.Result; + var work = miningCandidateTask.Result; + + return new ErgoBlockTemplate(work, info); + } + + protected ErgoBlockTemplate GetBlockTemplateFromJson(string json) + { + logger.LogInvoke(); + + return JsonConvert.DeserializeObject<ErgoBlockTemplate>(json); + } + protected async Task ShowDaemonSyncProgressAsync() { var info = await Guard(() => daemon.GetNodeInfoAsync(), - ex=> logger.Debug(ex)); + ex => logger.Debug(ex)); - if(info.FullHeight.HasValue && info.HeadersHeight.HasValue) + if(info?.FullHeight.HasValue == true && info.HeadersHeight.HasValue) { var percent = (double) info.FullHeight.Value / info.HeadersHeight.Value * 100; logger.Info(() => $"Daemon has downloaded {percent:0.00}% of blockchain from {info.PeersCount} peers"); } + else if(!string.IsNullOrEmpty(info?.BestFullHeaderId)) + logger.Info(() => $"Daemon is downloading headers ..."); + else logger.Info(() => $"Waiting for daemon to resume syncing ..."); } @@ -148,11 +266,22 @@ protected async Task UpdateNetworkStatsAsync() logger.LogInvoke(); var info = await Guard(() => daemon.GetNodeInfoAsync(), - ex=> logger.Debug(ex)); + ex => logger.Debug(ex)); if(info != null) { BlockchainStats.ConnectedPeers = info.PeersCount; + + // Network difficulty + if(info.Difficulty.Type == JTokenType.Object) + { + if(((JObject) info.Difficulty).TryGetValue("proof-of-work", out var diffVal)) + BlockchainStats.NetworkDifficulty = diffVal.Value<double>(); + } + + else + BlockchainStats.NetworkDifficulty = info.Difficulty.Value<double>(); + // TODO: BlockchainStats.NetworkHashrate = info.HeadersScore } } @@ -199,7 +328,7 @@ public async Task<bool> ValidateAddress(string address, CancellationToken ct) return false; var validity = await Guard(() => daemon.CheckAddressValidityAsync(address, ct), - ex=> logger.Debug(ex)); + ex => logger.Debug(ex)); return validity?.IsValid == true; } @@ -256,6 +385,11 @@ public override void Configure(PoolConfig poolConfig, ClusterConfig clusterConfi { coin = poolConfig.Template.As<ErgoCoinTemplate>(); + extraPoolConfig = poolConfig.Extra.SafeExtensionDataAs<ErgoPoolConfigExtra>(); + + if(extraPoolConfig?.MaxActiveJobs.HasValue == true) + maxActiveJobs = extraPoolConfig.MaxActiveJobs.Value; + base.Configure(poolConfig, clusterConfig); } @@ -319,11 +453,6 @@ protected override async Task EnsureDaemonsSynchedAsync(CancellationToken ct) } } - protected Task<(bool IsNew, bool Force)> UpdateJob(bool forceUpdate, string via = null, string json = null) - { - return Task.FromResult((true, false)); - } - protected object GetJobParamsForStratum(bool isNew) { var job = currentJob; diff --git a/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs b/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs index e97336d74..13246af52 100644 --- a/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs +++ b/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs @@ -6,6 +6,10 @@ using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + + #nullable enable #pragma warning disable 108 // Disable "CS0108 '{derivedDto}.ToJson()' hides inherited member '{dtoBase}.ToJson()'. Use the new keyword if hiding was intended." @@ -9257,7 +9261,11 @@ public partial class WorkMessage [Newtonsoft.Json.JsonProperty("msg", Required = Newtonsoft.Json.Required.Always)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Msg { get; set; }= default!; - + + /// <summary>Block height</summary> + [Newtonsoft.Json.JsonProperty("h", Required = Newtonsoft.Json.Required.Always)] + public ulong Height { get; set; }= default!; + /// <summary>Work target value</summary> [Newtonsoft.Json.JsonProperty("b", Required = Newtonsoft.Json.Required.Always)] public int B { get; set; }= default!; @@ -9394,7 +9402,7 @@ public partial class NodeInfo /// <summary>Difficulty on current bestFullHeaderId. Can be 'null' if no full block is applied since node launch</summary> [Newtonsoft.Json.JsonProperty("difficulty", Required = Newtonsoft.Json.Required.AllowNull)] [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] - public long? Difficulty { get; set; }= default!; + public JToken Difficulty { get; set; }= default!; /// <summary>Current internal node time</summary> [Newtonsoft.Json.JsonProperty("currentTime", Required = Newtonsoft.Json.Required.Always)] From b8838e1f8a4b565cbea41256f81b7dc1dec3cafc Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Tue, 6 Jul 2021 00:04:34 +0200 Subject: [PATCH 004/145] Table init --- .../Blockchain/Ergo/ErgoConstants.cs | 59 +++++++++++++++++++ src/Miningcore/Program.cs | 2 + 2 files changed, 61 insertions(+) create mode 100644 src/Miningcore/Blockchain/Ergo/ErgoConstants.cs diff --git a/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs b/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs new file mode 100644 index 000000000..e08711fd6 --- /dev/null +++ b/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Numerics; +using System.Text; +using System.Threading.Tasks; +using NBitcoin; + +// ReSharper disable InconsistentNaming + +namespace Miningcore.Blockchain.Ergo +{ + public static class ErgoConstants + { + public static readonly BigInteger NBase = BigInteger.Pow(2, 26); + + public const ulong IncreaseStart = 600 * 1024; + public const ulong IncreasePeriodForN = 50 * 1024; + public const ulong NIncreasementHeightMax = 9216000; + + private static readonly BigInteger a = new(100); + private static readonly BigInteger b = new(105); + + public static readonly byte[] M = Enumerable.Range(0, 1024) + .SelectMany(x => + { + const double max = 4294967296d; + var top = (uint) Math.Floor(x / max); + var rem = (uint) (x - top * max); + + var result = BitConverter.GetBytes(rem) + .Concat(BitConverter.GetBytes(top)) + .Reverse() + .ToArray(); + + return result; + }).ToArray(); + + public static BigInteger N(ulong height) + { + height = Math.Min(NIncreasementHeightMax, height); + + if(height < IncreaseStart) + return NBase; + + if(height >= NIncreasementHeightMax) + return 2147387550; + + var res = NBase; + var iterationsNumber = ((height - IncreaseStart) / IncreasePeriodForN) + 1; + + for(var i = 0ul; i < iterationsNumber; i++) + res = res / a * b; + + return res; + } + } +} diff --git a/src/Miningcore/Program.cs b/src/Miningcore/Program.cs index bc6222d56..5c4c9a5c6 100644 --- a/src/Miningcore/Program.cs +++ b/src/Miningcore/Program.cs @@ -29,10 +29,12 @@ using Miningcore.Api.Controllers; using Miningcore.Api.Middlewares; using Miningcore.Api.Responses; +using Miningcore.Blockchain.Ergo; using Miningcore.Configuration; using Miningcore.Crypto.Hashing.Algorithms; using Miningcore.Crypto.Hashing.Equihash; using Miningcore.Crypto.Hashing.Ethash; +using Miningcore.Extensions; using Miningcore.Messaging; using Miningcore.Mining; using Miningcore.Native; From 455305fbcbc3844ac45e8f1042667471b35820bc Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Tue, 6 Jul 2021 23:21:24 +0200 Subject: [PATCH 005/145] Ergo basic mining --- .../Blockchain/Ergo/ErgoConstants.cs | 2 + src/Miningcore/Blockchain/Ergo/ErgoJob.cs | 188 ++++++++++++++++-- .../Blockchain/Ergo/ErgoJobManager.cs | 159 ++++++++++++--- src/Miningcore/Blockchain/Ergo/ErgoPool.cs | 57 ++++-- .../Blockchain/Ergo/ErgoWorkerContext.cs | 19 ++ .../Blockchain/Ergo/RPC/ErgoClient.cs | 19 +- src/Miningcore/Configuration/ClusterConfig.cs | 2 - src/Miningcore/Extensions/ArrayExtensions.cs | 16 +- .../Notifications/NotificationService.cs | 17 +- 9 files changed, 398 insertions(+), 81 deletions(-) create mode 100644 src/Miningcore/Blockchain/Ergo/ErgoWorkerContext.cs diff --git a/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs b/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs index e08711fd6..5b23d3e62 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs @@ -22,6 +22,8 @@ public static class ErgoConstants private static readonly BigInteger a = new(100); private static readonly BigInteger b = new(105); + public const uint DiffMultiplier = 256; + public static readonly byte[] M = Enumerable.Range(0, 1024) .SelectMany(x => { diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs index daef30cdc..5b1355d42 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs @@ -1,12 +1,18 @@ using System; -using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Globalization; +using System.IO; using System.Linq; using System.Text; -using System.Threading.Tasks; -using Miningcore.Blockchain.Cryptonote; -using Miningcore.Configuration; -using Miningcore.Time; -using Newtonsoft.Json.Linq; +using Miningcore.Blockchain.Bitcoin; +using Miningcore.Contracts; +using Miningcore.Crypto; +using Miningcore.Crypto.Hashing.Algorithms; +using Miningcore.Extensions; +using Miningcore.Stratum; +using Miningcore.Util; +using MoreLinq; +using NBitcoin; namespace Miningcore.Blockchain.Ergo { @@ -18,27 +24,173 @@ public class ErgoJob public string JobId { get; protected set; } protected object[] jobParams; + protected readonly ConcurrentDictionary<string, bool> submissions = new(StringComparer.OrdinalIgnoreCase); + protected static IHashAlgorithm hasher = new Blake2b(); + private Target blockTargetValue; + private System.Numerics.BigInteger b; - public object GetJobParams(bool isNew) + protected bool RegisterSubmit(string extraNonce1, string extraNonce2, string nTime, string nonce) + { + var key = new StringBuilder() + .Append(extraNonce1) + .Append(extraNonce2) + .Append(nTime) + .Append(nonce) + .ToString(); + + return submissions.TryAdd(key, true); + } + + protected virtual byte[] SerializeCoinbase(string msg, string extraNonce1, string extraNonce2) + { + var msgBytes = msg.HexToByteArray(); + var extraNonce1Bytes = extraNonce1.HexToByteArray(); + var extraNonce2Bytes = extraNonce2.HexToByteArray(); + + using(var stream = new MemoryStream()) + { + stream.Write(msgBytes); + stream.Write(extraNonce1Bytes); + stream.Write(extraNonce2Bytes); + + return stream.ToArray(); + } + } + + private System.Numerics.BigInteger[] GenIndexes(byte[] seed, ulong height) + { + var hash = new byte[32]; + hasher.Digest(seed, hash); + + // duplicate + var extendedHash = hash.Concat(hash).ToArray(); + + var result = Enumerable.Range(0, 32).Select(index => + { + var a = BitConverter.ToUInt32(extendedHash.Slice(index, 4).ToArray()).ToBigEndian(); + var b = ErgoConstants.N(height); + return a % b; + }) + .ToArray(); + + return result; + } + + protected virtual Share ProcessShareInternal(StratumConnection worker, string extraNonce2) + { + var context = worker.ContextAs<ErgoWorkerContext>(); + var extraNonce1 = context.ExtraNonce1; + +//extraNonce1 = "c8000001"; +//extraNonce2 = "1e8ee05a"; + + // build coinbase + var coinbase = SerializeCoinbase(BlockTemplate.Work.Msg, extraNonce1, extraNonce2); + + // hash + Span<byte> hashResult = stackalloc byte[32]; + hasher.Digest(coinbase, hashResult); + + // calculate i + var tmp = new System.Numerics.BigInteger(hashResult.Slice(24, 8), true, true); + var tmp2 = tmp % ErgoConstants.N(Height); + var i = tmp2.ToByteArray(false, true).PadFront(0, 4); + + // calculate e + var h = new System.Numerics.BigInteger(Height).ToByteArray(true, true).PadFront(0, 4); + var ihM = i.Concat(h).Concat(ErgoConstants.M).ToArray(); + hasher.Digest(ihM, hashResult); + var e = hashResult[1..].ToArray(); + + // calculate j + var eCoinbase = e.Concat(coinbase).ToArray(); + var jTmp = GenIndexes(eCoinbase, Height); + var j = jTmp.Select(x => x.ToByteArray(true, true).PadFront(0, 4)).ToArray(); + + // calculate f + var fTmp = j.Select(x => + { + var buf = x.Concat(h).Concat(ErgoConstants.M).ToArray(); + + // hash it + Span<byte> hash = stackalloc byte[32]; + hasher.Digest(buf, hash); + + // extract 31 bytes at end + return new System.Numerics.BigInteger(hash[1..], true, true); + }).ToArray(); + + var f = fTmp.Aggregate((a, b) => a + b); + + // calculate fH + var blockHash = f.ToByteArray(false, true).PadFront(0, 32); + hasher.Digest(blockHash, hashResult); + var fh = new System.Numerics.BigInteger(hashResult, true, true); + + // calc share-diff + var stratumDifficulty = context.Difficulty; + var isLowDifficultyShare = fh / new System.Numerics.BigInteger(context.Difficulty) > b; + + // check if the share meets the much harder block difficulty (block candidate) + var isBlockCandidate = b >= fh; + + // test if share meets at least workers current difficulty + if(!isBlockCandidate && isLowDifficultyShare) + throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share"); + + var result = new Share + { + BlockHeight = (long) Height, + NetworkDifficulty = Difficulty, + Difficulty = stratumDifficulty / ErgoConstants.DiffMultiplier + }; + + if(isBlockCandidate) + { + result.IsBlockCandidate = true; + + result.BlockHash = blockHash.ToHexString(); + } + + return result; + } + + public object[] GetJobParams(bool isNew) { jobParams[^1] = isNew; return jobParams; } - public void Init(ErgoBlockTemplate blockTemplate, string jobId, PoolConfig poolConfig, ClusterConfig clusterConfig, IMasterClock clock, string network) + public virtual Share ProcessShare(StratumConnection worker, string extraNonce2, string nTime, string nonce) + { + Contract.RequiresNonNull(worker, nameof(worker)); + Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(extraNonce2), $"{nameof(extraNonce2)} must not be empty"); + Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(nTime), $"{nameof(nTime)} must not be empty"); + Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(nonce), $"{nameof(nonce)} must not be empty"); + + var context = worker.ContextAs<ErgoWorkerContext>(); + + // validate nonce + if(nonce.Length != 16) + throw new StratumException(StratumError.Other, "incorrect size of nonce"); + + // dupe check + if(!RegisterSubmit(context.ExtraNonce1, extraNonce2, nTime, nonce)) + throw new StratumException(StratumError.DuplicateShare, "duplicate share"); + + return ProcessShareInternal(worker, extraNonce2); + } + + + public void Init(ErgoBlockTemplate blockTemplate, string jobId) { BlockTemplate = blockTemplate; JobId = jobId; - // Network difficulty - if(blockTemplate.Info.Difficulty.Type == JTokenType.Object) - { - if(((JObject) blockTemplate.Info.Difficulty).TryGetValue("proof-of-work", out var diffVal)) - Difficulty = diffVal.Value<double>(); - } - - else - Difficulty = blockTemplate.Info.Difficulty.Value<double>(); + // Parse difficulty + b = System.Numerics.BigInteger.Parse(BlockTemplate.Work.B, NumberStyles.Integer); + blockTargetValue = new Target(b); + Difficulty = blockTargetValue.Difficulty; jobParams = new object[] { @@ -48,7 +200,7 @@ public void Init(ErgoBlockTemplate blockTemplate, string jobId, PoolConfig poolC string.Empty, string.Empty, BlockTemplate.Info.Parameters.BlockVersion, - BlockTemplate.Work.B, + b, string.Empty, false }; diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs index 74077e360..6265f6b56 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Net.Http; using System.Reactive.Linq; @@ -8,15 +9,13 @@ using System.Threading.Tasks; using Autofac; using Miningcore.Blockchain.Bitcoin; -using Miningcore.Blockchain.Bitcoin.Configuration; -using Miningcore.Blockchain.Bitcoin.DaemonResponses; using Miningcore.Blockchain.Ergo.Configuration; using NLog; using Miningcore.Configuration; using Miningcore.DaemonInterface; using Miningcore.Extensions; -using Miningcore.JsonRpc; using Miningcore.Messaging; +using Miningcore.Notifications.Messages; using Miningcore.Stratum; using Miningcore.Time; using Miningcore.Util; @@ -47,17 +46,18 @@ public ErgoJobManager( private ErgoCoinTemplate coin; private ErgoClient daemon; - protected string network; - protected TimeSpan jobRebroadcastTimeout; - protected DateTime? lastJobRebroadcast; - protected readonly List<ErgoJob> validJobs = new(); - protected int maxActiveJobs = 4; + private string network; + private TimeSpan jobRebroadcastTimeout; + private DateTime? lastJobRebroadcast; + private readonly List<ErgoJob> validJobs = new(); + private int maxActiveJobs = 4; + private const int ExtranonceBytes = 4; private readonly IHttpClientFactory httpClientFactory; private readonly IExtraNonceProvider extraNonceProvider; private readonly IMasterClock clock; private ErgoPoolConfigExtra extraPoolConfig; - protected virtual void SetupJobUpdates() + private void SetupJobUpdates() { jobRebroadcastTimeout = TimeSpan.FromSeconds(Math.Max(1, poolConfig.JobRebroadcastTimeout)); var blockFound = blockFoundSubject.Synchronize(); @@ -145,7 +145,7 @@ protected virtual void SetupJobUpdates() .RefCount(); } - protected async Task<(bool IsNew, bool Force)> UpdateJob(bool forceUpdate, string via = null, string json = null) + private async Task<(bool IsNew, bool Force)> UpdateJob(bool forceUpdate, string via = null, string json = null) { logger.LogInvoke(); @@ -172,7 +172,7 @@ await GetBlockTemplateAsync() : { job = new ErgoJob(); - job.Init(blockTemplate, NextJobId(), poolConfig, clusterConfig, clock, network); + job.Init(blockTemplate, NextJobId()); lock(jobLock) { @@ -193,9 +193,6 @@ await GetBlockTemplateAsync() : // update stats BlockchainStats.LastNetworkBlockTime = clock.Now; BlockchainStats.BlockHeight = job.Height; - BlockchainStats.NetworkDifficulty = job.Difficulty; - //BlockchainStats.NextNetworkTarget = blockTemplate.Target; - //BlockchainStats.NextNetworkBits = blockTemplate.Bits; } else @@ -220,7 +217,7 @@ await GetBlockTemplateAsync() : return (false, forceUpdate); } - protected async Task<ErgoBlockTemplate> GetBlockTemplateAsync() + private async Task<ErgoBlockTemplate> GetBlockTemplateAsync() { logger.LogInvoke(); @@ -235,14 +232,14 @@ protected async Task<ErgoBlockTemplate> GetBlockTemplateAsync() return new ErgoBlockTemplate(work, info); } - protected ErgoBlockTemplate GetBlockTemplateFromJson(string json) + private ErgoBlockTemplate GetBlockTemplateFromJson(string json) { logger.LogInvoke(); return JsonConvert.DeserializeObject<ErgoBlockTemplate>(json); } - protected async Task ShowDaemonSyncProgressAsync() + private async Task ShowDaemonSyncProgressAsync() { var info = await Guard(() => daemon.GetNodeInfoAsync(), ex => logger.Debug(ex)); @@ -261,7 +258,7 @@ protected async Task ShowDaemonSyncProgressAsync() logger.Info(() => $"Waiting for daemon to resume syncing ..."); } - protected async Task UpdateNetworkStatsAsync() + private async Task UpdateNetworkStatsAsync() { logger.LogInvoke(); @@ -282,11 +279,13 @@ protected async Task UpdateNetworkStatsAsync() else BlockchainStats.NetworkDifficulty = info.Difficulty.Value<double>(); + BlockchainStats.NetworkDifficulty *= ErgoConstants.DiffMultiplier; + // TODO: BlockchainStats.NetworkHashrate = info.HeadersScore } } - protected void ConfigureRewards() + private void ConfigureRewards() { // Donation to MiningCore development if(network == "mainnet" && @@ -304,9 +303,31 @@ protected void ConfigureRewards() } } + private async Task<bool> SubmitBlockAsync(Share share, ErgoJob job, string nonce) + { + try + { + await daemon.MiningSubmitSolutionAsync(new PowSolutions + { + N = nonce, + //Pk = job.BlockTemplate.Work.Pk, + }); + + return true; + } + + catch(ApiException ex) + { + logger.Warn(() => $"Block {share.BlockHeight} submission failed with: {ex.Message}"); + messageBus.SendMessage(new AdminNotification("Block submission failed", $"Pool {poolConfig.Id} {(!string.IsNullOrEmpty(share.Source) ? $"[{share.Source.ToUpper()}] " : string.Empty)}failed to submit block {share.BlockHeight}: {ex.Message}")); + } + + return false; + } + #region API-Surface - public IObservable<object> Jobs { get; private set; } + public IObservable<object[]> Jobs { get; private set; } public BlockchainStats BlockchainStats { get; } = new(); public string Network => network; @@ -314,12 +335,100 @@ protected void ConfigureRewards() public object[] GetSubscriberData(StratumConnection worker) { - throw new NotImplementedException(); + Contract.RequiresNonNull(worker, nameof(worker)); + + var context = worker.ContextAs<ErgoWorkerContext>(); + + // assign unique ExtraNonce1 to worker (miner) + context.ExtraNonce1 = extraNonceProvider.Next(); + + // setup response data + var responseData = new object[] + { + context.ExtraNonce1, + BitcoinConstants.ExtranoncePlaceHolderLength - ExtranonceBytes, + }; + + return responseData; } - public ValueTask<Share> SubmitShareAsync(StratumConnection worker, object submission, double stratumDifficultyBase, CancellationToken ct) + public async ValueTask<Share> SubmitShareAsync(StratumConnection worker, object submission, double stratumDifficultyBase, CancellationToken ct) { - throw new NotImplementedException(); + Contract.RequiresNonNull(worker, nameof(worker)); + Contract.RequiresNonNull(submission, nameof(submission)); + + logger.LogInvoke(new[] { worker.ConnectionId }); + + if(submission is not object[] submitParams) + throw new StratumException(StratumError.Other, "invalid params"); + + var context = worker.ContextAs<ErgoWorkerContext>(); + + // extract params + var workerValue = (submitParams[0] as string)?.Trim(); + var jobId = submitParams[1] as string; + var extraNonce2 = submitParams[2] as string; + var nTime = submitParams[3] as string; + var nonce = submitParams[4] as string; + + if(string.IsNullOrEmpty(workerValue)) + throw new StratumException(StratumError.Other, "missing or invalid workername"); + + ErgoJob job; + + lock(jobLock) + { + job = validJobs.FirstOrDefault(x => x.JobId == jobId); + } + + if(job == null) + throw new StratumException(StratumError.JobNotFound, "job not found"); + + // extract worker/miner/payoutid + var split = workerValue.Split('.'); + var minerName = split[0]; + var workerName = split.Length > 1 ? split[1] : null; + + // validate & process + var share = job.ProcessShare(worker, extraNonce2, nTime, nonce); + + // enrich share with common data + share.PoolId = poolConfig.Id; + share.IpAddress = worker.RemoteEndpoint.Address.ToString(); + share.Miner = minerName; + share.Worker = workerName; + share.UserAgent = context.UserAgent; + share.Source = clusterConfig.ClusterName; + share.Created = clock.Now; + + // if block candidate, submit & check if accepted by network + if(share.IsBlockCandidate) + { + logger.Info(() => $"Submitting block {share.BlockHeight} [{share.BlockHash}]"); + + var acceptResponse = await SubmitBlockAsync(share, job, nonce); + + // is it still a block candidate? + share.IsBlockCandidate = acceptResponse; + + if(share.IsBlockCandidate) + { + logger.Info(() => $"Daemon accepted block {share.BlockHeight} [{share.BlockHash}] submitted by {minerName}"); + + OnBlockFound(); + + // persist the nonce to make block unlocking a bit more reliable + share.TransactionConfirmationData = nonce; + } + + else + { + // clear fields that no longer apply + share.TransactionConfirmationData = null; + } + } + + return share; } public async Task<bool> ValidateAddress(string address, CancellationToken ct) @@ -432,7 +541,7 @@ protected override async Task EnsureDaemonsSynchedAsync(CancellationToken ct) ex=> logger.Debug(ex)); var isSynched = info?.FullHeight.HasValue == true && info?.HeadersHeight.HasValue == true && - info.FullHeight.Value > info.HeadersHeight.Value; + info.FullHeight.Value >= info.HeadersHeight.Value; if(isSynched) { @@ -453,7 +562,7 @@ protected override async Task EnsureDaemonsSynchedAsync(CancellationToken ct) } } - protected object GetJobParamsForStratum(bool isNew) + private object[] GetJobParamsForStratum(bool isNew) { var job = currentJob; return job?.GetJobParams(isNew); diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs index 3c1b0a835..bb7da6cd7 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs @@ -38,7 +38,7 @@ public ErgoPool(IComponentContext ctx, { } - protected object currentJobParams; + protected object[] currentJobParams; protected ErgoJobManager manager; private ErgoCoinTemplate coin; @@ -49,7 +49,7 @@ protected virtual async Task OnSubscribeAsync(StratumConnection connection, Time if(request.Id == null) throw new StratumException(StratumError.MinusOne, "missing request id"); - var context = connection.ContextAs<BitcoinWorkerContext>(); + var context = connection.ContextAs<ErgoWorkerContext>(); var requestParams = request.ParamsAs<string[]>(); var data = new object[] @@ -68,10 +68,6 @@ protected virtual async Task OnSubscribeAsync(StratumConnection connection, Time // setup worker context context.IsSubscribed = true; context.UserAgent = requestParams?.Length > 0 ? requestParams[0].Trim() : null; - - // send intial update - await connection.NotifyAsync(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty }); - await connection.NotifyAsync(BitcoinStratumMethods.MiningNotify, currentJobParams); } protected virtual async Task OnAuthorizeAsync(StratumConnection connection, Timestamped<JsonRpcRequest> tsRequest, CancellationToken ct) @@ -81,7 +77,7 @@ protected virtual async Task OnAuthorizeAsync(StratumConnection connection, Time if(request.Id == null) throw new StratumException(StratumError.MinusOne, "missing request id"); - var context = connection.ContextAs<BitcoinWorkerContext>(); + var context = connection.ContextAs<ErgoWorkerContext>(); var requestParams = request.ParamsAs<string[]>(); var workerValue = requestParams?.Length > 0 ? requestParams[0] : null; var password = requestParams?.Length > 1 ? requestParams[1] : null; @@ -123,6 +119,10 @@ protected virtual async Task OnAuthorizeAsync(StratumConnection connection, Time await connection.NotifyAsync(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty }); } + + // send intial update + await connection.NotifyAsync(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty }); + await SendJob(connection, context, currentJobParams); } else @@ -141,7 +141,7 @@ protected virtual async Task OnAuthorizeAsync(StratumConnection connection, Time protected virtual async Task OnSubmitAsync(StratumConnection connection, Timestamped<JsonRpcRequest> tsRequest, CancellationToken ct) { var request = tsRequest.Value; - var context = connection.ContextAs<BitcoinWorkerContext>(); + var context = connection.ContextAs<ErgoWorkerContext>(); try { @@ -180,7 +180,7 @@ protected virtual async Task OnSubmitAsync(StratumConnection connection, Timesta // telemetry PublishTelemetry(TelemetryCategory.Share, clock.Now - tsRequest.Timestamp.UtcDateTime, true); -// TODO: logger.Info(() => $"[{connection.ConnectionId}] Share accepted: D={Math.Round(share.Difficulty * coin.ShareMultiplier, 3)}"); + logger.Info(() => $"[{connection.ConnectionId}] Share accepted: D={Math.Round(share.Difficulty * ErgoConstants.DiffMultiplier, 3)}"); // update pool stats if(share.IsBlockCandidate) @@ -210,7 +210,7 @@ protected virtual async Task OnSubmitAsync(StratumConnection connection, Timesta private async Task OnSuggestDifficultyAsync(StratumConnection connection, Timestamped<JsonRpcRequest> tsRequest) { var request = tsRequest.Value; - var context = connection.ContextAs<BitcoinWorkerContext>(); + var context = connection.ContextAs<ErgoWorkerContext>(); // acknowledge await connection.RespondAsync(true, request.Id); @@ -237,18 +237,18 @@ private async Task OnSuggestDifficultyAsync(StratumConnection connection, Timest } } - protected virtual async Task OnNewJobAsync(object jobParams) + protected virtual async Task OnNewJobAsync(object[] jobParams) { currentJobParams = jobParams; logger.Info(() => "Broadcasting job"); - var tasks = ForEachConnection(async client => + var tasks = ForEachConnection(async connection => { - if(!client.IsAlive) + if(!connection.IsAlive) return; - var context = client.ContextAs<BitcoinWorkerContext>(); + var context = connection.ContextAs<ErgoWorkerContext>(); if(context.IsSubscribed && context.IsAuthorized) { @@ -258,17 +258,16 @@ protected virtual async Task OnNewJobAsync(object jobParams) if(poolConfig.ClientConnectionTimeout > 0 && lastActivityAgo.TotalSeconds > poolConfig.ClientConnectionTimeout) { - logger.Info(() => $"[{client.ConnectionId}] Booting zombie-worker (idle-timeout exceeded)"); - CloseConnection(client); + logger.Info(() => $"[{connection.ConnectionId}] Booting zombie-worker (idle-timeout exceeded)"); + CloseConnection(connection); return; } // varDiff: if the client has a pending difficulty change, apply it now if(context.ApplyPendingDifficulty()) - await client.NotifyAsync(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty }); + await connection.NotifyAsync(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty }); - // send job - await client.NotifyAsync(BitcoinStratumMethods.MiningNotify, currentJobParams); + await SendJob(connection, context, currentJobParams); } }); @@ -283,6 +282,20 @@ protected virtual async Task OnNewJobAsync(object jobParams) } } + private async Task SendJob(StratumConnection connection, ErgoWorkerContext context, object[] jobParams) + { + // clone job params + var jobParamsActual = new object[currentJobParams.Length]; + + for(var i = 0; i < jobParamsActual.Length; i++) + jobParamsActual[i] = currentJobParams[i]; + + // correct difficulty + jobParamsActual[6] = (System.Numerics.BigInteger) jobParamsActual[6] * new System.Numerics.BigInteger(context.Difficulty); + + await connection.NotifyAsync(BitcoinStratumMethods.MiningNotify, jobParamsActual); + } + public override double HashrateFromShares(double shares, double interval) { var multiplier = BitcoinConstants.Pow2x32; @@ -352,7 +365,7 @@ protected override async Task InitStatsAsync() protected override WorkerContextBase CreateClientContext() { - return new BitcoinWorkerContext(); + return new ErgoWorkerContext(); } protected override async Task OnRequestAsync(StratumConnection connection, @@ -408,7 +421,7 @@ protected override async Task OnRequestAsync(StratumConnection connection, protected override async Task OnVarDiffUpdateAsync(StratumConnection connection, double newDiff) { - var context = connection.ContextAs<BitcoinWorkerContext>(); + var context = connection.ContextAs<ErgoWorkerContext>(); context.EnqueueNewDifficulty(newDiff); // apply immediately and notify client @@ -417,7 +430,7 @@ protected override async Task OnVarDiffUpdateAsync(StratumConnection connection, context.ApplyPendingDifficulty(); await connection.NotifyAsync(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty }); - await connection.NotifyAsync(BitcoinStratumMethods.MiningNotify, currentJobParams); + await SendJob(connection, context, currentJobParams); } } diff --git a/src/Miningcore/Blockchain/Ergo/ErgoWorkerContext.cs b/src/Miningcore/Blockchain/Ergo/ErgoWorkerContext.cs new file mode 100644 index 000000000..d34d560cb --- /dev/null +++ b/src/Miningcore/Blockchain/Ergo/ErgoWorkerContext.cs @@ -0,0 +1,19 @@ +using Miningcore.Mining; + +namespace Miningcore.Blockchain.Ergo +{ + public class ErgoWorkerContext : WorkerContextBase + { + /// <summary> + /// Usually a wallet address + /// </summary> + public string Miner { get; set; } + + /// <summary> + /// Arbitrary worker identififer for miners using multiple rigs + /// </summary> + public string Worker { get; set; } + + public string ExtraNonce1 { get; set; } + } +} diff --git a/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs b/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs index 13246af52..d8c7f0df3 100644 --- a/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs +++ b/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs @@ -5144,6 +5144,17 @@ public async System.Threading.Tasks.Task<WorkMessage> MiningRequestBlockCandidat { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } + + // var foo = JsonConvert.DeserializeObject<WorkMessage>( + // "{ " + + //" \"msg\" : \"bd5d3920fbec1eaba7687ad4c89b8a8ac614e308a2156a73ae13e2ee43c3ee79\", " + + //" \"b\" : 23243094411418442559241442600806652754957978391531899187164925523571, " + + //" \"h\" : 23850, " + + //" \"pk\" : \"035336013fdd41ca9d1079eb72e455da6e9e0258f04c28eee0bda68eff6755790d\" " + + //"}"); + + // return foo; + return objectResponse_.Object; } else @@ -8822,11 +8833,11 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti public partial class PowSolutions { /// <summary>Base16-encoded public key</summary> - [Newtonsoft.Json.JsonProperty("pk", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("pk")] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Pk { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("w", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("w")] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string W { get; set; }= default!; @@ -8834,7 +8845,7 @@ public partial class PowSolutions [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string N { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("d", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("d")] public int D { get; set; }= default!; private System.Collections.Generic.IDictionary<string, object> _additionalProperties = new System.Collections.Generic.Dictionary<string, object>(); @@ -9268,7 +9279,7 @@ public partial class WorkMessage /// <summary>Work target value</summary> [Newtonsoft.Json.JsonProperty("b", Required = Newtonsoft.Json.Required.Always)] - public int B { get; set; }= default!; + public string B { get; set; }= default!; /// <summary>Base16-encoded miner public key</summary> [Newtonsoft.Json.JsonProperty("pk", Required = Newtonsoft.Json.Required.Always)] diff --git a/src/Miningcore/Configuration/ClusterConfig.cs b/src/Miningcore/Configuration/ClusterConfig.cs index 7a5885d2c..e27c5b952 100644 --- a/src/Miningcore/Configuration/ClusterConfig.cs +++ b/src/Miningcore/Configuration/ClusterConfig.cs @@ -566,8 +566,6 @@ public partial class PushoverConfig public bool Enabled { get; set; } public string User { get; set; } public string Token { get; set; } - public bool NotifyBlockFound { get; set; } - public bool NotifyPaymentSuccess { get; set; } } public partial class AdminNotifications diff --git a/src/Miningcore/Extensions/ArrayExtensions.cs b/src/Miningcore/Extensions/ArrayExtensions.cs index 217a82471..6dbaeb6b8 100644 --- a/src/Miningcore/Extensions/ArrayExtensions.cs +++ b/src/Miningcore/Extensions/ArrayExtensions.cs @@ -126,10 +126,6 @@ public static string ToHexString(this Memory<byte> value, bool withPrefix = fals return ToHexString(value.Span, null, null, withPrefix); } - /// <summary> - /// Apparently mixing big-ending and little-endian isn't confusing enough so sometimes every - /// block of 4 bytes must be reversed before reversing the entire buffer - /// </summary> public static byte[] ReverseByteOrder(this byte[] bytes) { using(var stream = new MemoryStream()) @@ -167,5 +163,17 @@ public static T[] ReverseInPlace<T>(this T[] arr) Array.Reverse(arr); return arr; } + + public static byte[] PadFront(this byte[] bytes, byte padValue, int desiredLength) + { + if(bytes.Length >= desiredLength) + return bytes; + + var result = Enumerable.Repeat(padValue, desiredLength - bytes.Length) + .Concat(bytes) + .ToArray(); + + return result; + } } } diff --git a/src/Miningcore/Notifications/NotificationService.cs b/src/Miningcore/Notifications/NotificationService.cs index 11505cc07..35269aa99 100644 --- a/src/Miningcore/Notifications/NotificationService.cs +++ b/src/Miningcore/Notifications/NotificationService.cs @@ -67,10 +67,13 @@ private async Task OnBlockFoundNotificationAsync(BlockFoundNotification notifica const string subject = "Block Notification"; var message = $"Pool {notification.PoolId} found block candidate {notification.BlockHeight}"; - await Guard(()=> SendEmailAsync(adminEmail, subject, message, ct), LogGuarded); + if(clusterConfig.Notifications?.Admin?.NotifyBlockFound == true) + { + await Guard(() => SendEmailAsync(adminEmail, subject, message, ct), LogGuarded); - if(clusterConfig.Notifications?.Pushover?.Enabled == true && clusterConfig.Notifications.Pushover.NotifyBlockFound) - await Guard(()=> pushoverClient.PushMessage(subject, message, PushoverMessagePriority.None, ct), LogGuarded); + if(clusterConfig.Notifications?.Pushover?.Enabled == true) + await Guard(() => pushoverClient.PushMessage(subject, message, PushoverMessagePriority.None, ct), LogGuarded); + } } private async Task OnPaymentNotificationAsync(PaymentNotification notification, CancellationToken ct) @@ -89,10 +92,12 @@ private async Task OnPaymentNotificationAsync(PaymentNotification notification, var message = $"Paid {FormatAmount(notification.Amount, notification.PoolId)} from pool {notification.PoolId} to {notification.RecpientsCount} recipients in Transaction(s) {txLinks}."; if(clusterConfig.Notifications?.Admin?.NotifyPaymentSuccess == true) - await Guard(()=> SendEmailAsync(adminEmail, subject, message, ct), LogGuarded); + { + await Guard(() => SendEmailAsync(adminEmail, subject, message, ct), LogGuarded); - if(clusterConfig.Notifications?.Pushover?.Enabled == true && clusterConfig.Notifications.Pushover.NotifyPaymentSuccess) - await Guard(()=> pushoverClient.PushMessage(subject, message, PushoverMessagePriority.None, ct), LogGuarded); + if(clusterConfig.Notifications?.Pushover?.Enabled == true) + await Guard(() => pushoverClient.PushMessage(subject, message, PushoverMessagePriority.None, ct), LogGuarded); + } } else From 7d9983cad74a945ceac3e4ecbfe4f703da7b7ab9 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Wed, 7 Jul 2021 00:16:04 +0200 Subject: [PATCH 006/145] Hashrate shit --- src/Miningcore/Blockchain/Ergo/ErgoJob.cs | 40 +++++---------- src/Miningcore/Blockchain/Ergo/ErgoPool.cs | 51 ++----------------- .../Blockchain/Ergo/RPC/ErgoClient.cs | 10 ---- 3 files changed, 15 insertions(+), 86 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs index 5b1355d42..d0f9c919c 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs @@ -4,13 +4,11 @@ using System.IO; using System.Linq; using System.Text; -using Miningcore.Blockchain.Bitcoin; using Miningcore.Contracts; using Miningcore.Crypto; using Miningcore.Crypto.Hashing.Algorithms; using Miningcore.Extensions; using Miningcore.Stratum; -using Miningcore.Util; using MoreLinq; using NBitcoin; @@ -26,8 +24,7 @@ public class ErgoJob protected object[] jobParams; protected readonly ConcurrentDictionary<string, bool> submissions = new(StringComparer.OrdinalIgnoreCase); protected static IHashAlgorithm hasher = new Blake2b(); - private Target blockTargetValue; - private System.Numerics.BigInteger b; + private System.Numerics.BigInteger B; protected bool RegisterSubmit(string extraNonce1, string extraNonce2, string nTime, string nonce) { @@ -62,7 +59,6 @@ private System.Numerics.BigInteger[] GenIndexes(byte[] seed, ulong height) var hash = new byte[32]; hasher.Digest(seed, hash); - // duplicate var extendedHash = hash.Concat(hash).ToArray(); var result = Enumerable.Range(0, 32).Select(index => @@ -81,19 +77,13 @@ protected virtual Share ProcessShareInternal(StratumConnection worker, string ex var context = worker.ContextAs<ErgoWorkerContext>(); var extraNonce1 = context.ExtraNonce1; -//extraNonce1 = "c8000001"; -//extraNonce2 = "1e8ee05a"; - - // build coinbase + // hash coinbase var coinbase = SerializeCoinbase(BlockTemplate.Work.Msg, extraNonce1, extraNonce2); - - // hash Span<byte> hashResult = stackalloc byte[32]; hasher.Digest(coinbase, hashResult); // calculate i - var tmp = new System.Numerics.BigInteger(hashResult.Slice(24, 8), true, true); - var tmp2 = tmp % ErgoConstants.N(Height); + var tmp2 = new System.Numerics.BigInteger(hashResult.Slice(24, 8), true, true) % ErgoConstants.N(Height); var i = tmp2.ToByteArray(false, true).PadFront(0, 4); // calculate e @@ -108,7 +98,7 @@ protected virtual Share ProcessShareInternal(StratumConnection worker, string ex var j = jTmp.Select(x => x.ToByteArray(true, true).PadFront(0, 4)).ToArray(); // calculate f - var fTmp = j.Select(x => + var f = j.Select(x => { var buf = x.Concat(h).Concat(ErgoConstants.M).ToArray(); @@ -118,9 +108,7 @@ protected virtual Share ProcessShareInternal(StratumConnection worker, string ex // extract 31 bytes at end return new System.Numerics.BigInteger(hash[1..], true, true); - }).ToArray(); - - var f = fTmp.Aggregate((a, b) => a + b); + }).Aggregate((a, b) => a + b); // calculate fH var blockHash = f.ToByteArray(false, true).PadFront(0, 32); @@ -129,20 +117,20 @@ protected virtual Share ProcessShareInternal(StratumConnection worker, string ex // calc share-diff var stratumDifficulty = context.Difficulty; - var isLowDifficultyShare = fh / new System.Numerics.BigInteger(context.Difficulty) > b; + var isLowDifficulty = fh / new System.Numerics.BigInteger(context.Difficulty) > B; // check if the share meets the much harder block difficulty (block candidate) - var isBlockCandidate = b >= fh; + var isBlockCandidate = B >= fh; // test if share meets at least workers current difficulty - if(!isBlockCandidate && isLowDifficultyShare) + if(!isBlockCandidate && isLowDifficulty) throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share"); var result = new Share { BlockHeight = (long) Height, NetworkDifficulty = Difficulty, - Difficulty = stratumDifficulty / ErgoConstants.DiffMultiplier + Difficulty = stratumDifficulty }; if(isBlockCandidate) @@ -181,16 +169,12 @@ public virtual Share ProcessShare(StratumConnection worker, string extraNonce2, return ProcessShareInternal(worker, extraNonce2); } - public void Init(ErgoBlockTemplate blockTemplate, string jobId) { BlockTemplate = blockTemplate; JobId = jobId; - - // Parse difficulty - b = System.Numerics.BigInteger.Parse(BlockTemplate.Work.B, NumberStyles.Integer); - blockTargetValue = new Target(b); - Difficulty = blockTargetValue.Difficulty; + B = System.Numerics.BigInteger.Parse(BlockTemplate.Work.B, NumberStyles.Integer); + Difficulty = new Target(B).Difficulty; jobParams = new object[] { @@ -200,7 +184,7 @@ public void Init(ErgoBlockTemplate blockTemplate, string jobId) string.Empty, string.Empty, BlockTemplate.Info.Parameters.BlockVersion, - b, + B, string.Empty, false }; diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs index bb7da6cd7..8806c5910 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs @@ -180,7 +180,7 @@ protected virtual async Task OnSubmitAsync(StratumConnection connection, Timesta // telemetry PublishTelemetry(TelemetryCategory.Share, clock.Now - tsRequest.Timestamp.UtcDateTime, true); - logger.Info(() => $"[{connection.ConnectionId}] Share accepted: D={Math.Round(share.Difficulty * ErgoConstants.DiffMultiplier, 3)}"); + logger.Info(() => $"[{connection.ConnectionId}] Share accepted: D={Math.Round(share.Difficulty, 3)}"); // update pool stats if(share.IsBlockCandidate) @@ -207,36 +207,6 @@ protected virtual async Task OnSubmitAsync(StratumConnection connection, Timesta } } - private async Task OnSuggestDifficultyAsync(StratumConnection connection, Timestamped<JsonRpcRequest> tsRequest) - { - var request = tsRequest.Value; - var context = connection.ContextAs<ErgoWorkerContext>(); - - // acknowledge - await connection.RespondAsync(true, request.Id); - - try - { - var requestedDiff = (double) Convert.ChangeType(request.Params, TypeCode.Double); - - // client may suggest higher-than-base difficulty, but not a lower one - var poolEndpoint = poolConfig.Ports[connection.PoolEndpoint.Port]; - - if(requestedDiff > poolEndpoint.Difficulty) - { - context.SetDifficulty(requestedDiff); - await connection.NotifyAsync(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty }); - - logger.Info(() => $"[{connection.ConnectionId}] Difficulty set to {requestedDiff} as requested by miner"); - } - } - - catch(Exception ex) - { - logger.Error(ex, () => $"Unable to convert suggested difficulty {request.Params}"); - } - } - protected virtual async Task OnNewJobAsync(object[] jobParams) { currentJobParams = jobParams; @@ -301,7 +271,8 @@ public override double HashrateFromShares(double shares, double interval) var multiplier = BitcoinConstants.Pow2x32; var result = shares * multiplier / interval; - //result *= coin.HashrateMultiplier; + result /= ErgoConstants.DiffMultiplier; + result /= 3; return result; } @@ -389,22 +360,6 @@ protected override async Task OnRequestAsync(StratumConnection connection, await OnSubmitAsync(connection, tsRequest, ct); break; - case BitcoinStratumMethods.SuggestDifficulty: - await OnSuggestDifficultyAsync(connection, tsRequest); - break; - - case BitcoinStratumMethods.ExtraNonceSubscribe: - await connection.RespondAsync(true, request.Id); - break; - - case BitcoinStratumMethods.GetTransactions: - // ignored - break; - - case BitcoinStratumMethods.MiningMultiVersion: - // ignored - break; - default: logger.Debug(() => $"[{connection.ConnectionId}] Unsupported RPC request: {JsonConvert.SerializeObject(request, serializerSettings)}"); diff --git a/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs b/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs index d8c7f0df3..e631d067f 100644 --- a/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs +++ b/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs @@ -5145,16 +5145,6 @@ public async System.Threading.Tasks.Task<WorkMessage> MiningRequestBlockCandidat throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - // var foo = JsonConvert.DeserializeObject<WorkMessage>( - // "{ " + - //" \"msg\" : \"bd5d3920fbec1eaba7687ad4c89b8a8ac614e308a2156a73ae13e2ee43c3ee79\", " + - //" \"b\" : 23243094411418442559241442600806652754957978391531899187164925523571, " + - //" \"h\" : 23850, " + - //" \"pk\" : \"035336013fdd41ca9d1079eb72e455da6e9e0258f04c28eee0bda68eff6755790d\" " + - //"}"); - - // return foo; - return objectResponse_.Object; } else From 386adb6724692ca38e575f3d722fc69d2732113e Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Wed, 7 Jul 2021 00:25:18 +0200 Subject: [PATCH 007/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoJob.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs index d0f9c919c..0fac00a1a 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs @@ -56,11 +56,14 @@ protected virtual byte[] SerializeCoinbase(string msg, string extraNonce1, strin private System.Numerics.BigInteger[] GenIndexes(byte[] seed, ulong height) { + // hash seed var hash = new byte[32]; hasher.Digest(seed, hash); + // duplicate var extendedHash = hash.Concat(hash).ToArray(); + // map indexes var result = Enumerable.Range(0, 32).Select(index => { var a = BitConverter.ToUInt32(extendedHash.Slice(index, 4).ToArray()).ToBigEndian(); @@ -115,7 +118,7 @@ protected virtual Share ProcessShareInternal(StratumConnection worker, string ex hasher.Digest(blockHash, hashResult); var fh = new System.Numerics.BigInteger(hashResult, true, true); - // calc share-diff + // diff check var stratumDifficulty = context.Difficulty; var isLowDifficulty = fh / new System.Numerics.BigInteger(context.Difficulty) > B; From b63b0444c819f00470f205d9996437a3f8d8e509 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Wed, 7 Jul 2021 00:26:57 +0200 Subject: [PATCH 008/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoJob.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs index 0fac00a1a..1f2a644db 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs @@ -114,7 +114,7 @@ protected virtual Share ProcessShareInternal(StratumConnection worker, string ex }).Aggregate((a, b) => a + b); // calculate fH - var blockHash = f.ToByteArray(false, true).PadFront(0, 32); + var blockHash = f.ToByteArray(true, true).PadFront(0, 32); hasher.Digest(blockHash, hashResult); var fh = new System.Numerics.BigInteger(hashResult, true, true); From 772aefaf07455c9ae3ebbb6684812f92fdf9133a Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Wed, 7 Jul 2021 00:51:34 +0200 Subject: [PATCH 009/145] WIP --- .../Blockchain/Ergo/ErgoConstants.cs | 29 +--------- src/Miningcore/Blockchain/Ergo/ErgoJob.cs | 54 ++++++++++++++----- src/Miningcore/Blockchain/Ergo/ErgoPool.cs | 5 +- 3 files changed, 44 insertions(+), 44 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs b/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs index 5b23d3e62..41c3d242b 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs @@ -13,16 +13,8 @@ namespace Miningcore.Blockchain.Ergo { public static class ErgoConstants { - public static readonly BigInteger NBase = BigInteger.Pow(2, 26); - - public const ulong IncreaseStart = 600 * 1024; - public const ulong IncreasePeriodForN = 50 * 1024; - public const ulong NIncreasementHeightMax = 9216000; - - private static readonly BigInteger a = new(100); - private static readonly BigInteger b = new(105); - public const uint DiffMultiplier = 256; + public static double Pow2x26 = Math.Pow(2, 26); public static readonly byte[] M = Enumerable.Range(0, 1024) .SelectMany(x => @@ -38,24 +30,5 @@ public static class ErgoConstants return result; }).ToArray(); - - public static BigInteger N(ulong height) - { - height = Math.Min(NIncreasementHeightMax, height); - - if(height < IncreaseStart) - return NBase; - - if(height >= NIncreasementHeightMax) - return 2147387550; - - var res = NBase; - var iterationsNumber = ((height - IncreaseStart) / IncreasePeriodForN) + 1; - - for(var i = 0ul; i < iterationsNumber; i++) - res = res / a * b; - - return res; - } } } diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs index 1f2a644db..e7a7806cb 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs @@ -9,6 +9,7 @@ using Miningcore.Crypto.Hashing.Algorithms; using Miningcore.Extensions; using Miningcore.Stratum; +using System.Numerics; using MoreLinq; using NBitcoin; @@ -21,10 +22,36 @@ public class ErgoJob public ulong Height => BlockTemplate.Work.Height; public string JobId { get; protected set; } - protected object[] jobParams; - protected readonly ConcurrentDictionary<string, bool> submissions = new(StringComparer.OrdinalIgnoreCase); - protected static IHashAlgorithm hasher = new Blake2b(); - private System.Numerics.BigInteger B; + private object[] jobParams; + private readonly ConcurrentDictionary<string, bool> submissions = new(StringComparer.OrdinalIgnoreCase); + private static readonly IHashAlgorithm hasher = new Blake2b(); + private BigInteger B; + + private static readonly BigInteger nBase = BigInteger.Pow(2, 26); + private const ulong IncreaseStart = 600 * 1024; + private const ulong IncreasePeriodForN = 50 * 1024; + private const ulong NIncreasementHeightMax = 9216000; + private static readonly BigInteger a = new(100); + private static readonly BigInteger b = new(105); + + public static BigInteger CalculateN(ulong height) + { + height = Math.Min(NIncreasementHeightMax, height); + + if(height < IncreaseStart) + return nBase; + + if(height >= NIncreasementHeightMax) + return 2147387550; + + var res = nBase; + var iterationsNumber = ((height - IncreaseStart) / IncreasePeriodForN) + 1; + + for(var i = 0ul; i < iterationsNumber; i++) + res = res / a * b; + + return res; + } protected bool RegisterSubmit(string extraNonce1, string extraNonce2, string nTime, string nonce) { @@ -54,7 +81,7 @@ protected virtual byte[] SerializeCoinbase(string msg, string extraNonce1, strin } } - private System.Numerics.BigInteger[] GenIndexes(byte[] seed, ulong height) + private BigInteger[] GenIndexes(byte[] seed, ulong height) { // hash seed var hash = new byte[32]; @@ -67,7 +94,7 @@ private System.Numerics.BigInteger[] GenIndexes(byte[] seed, ulong height) var result = Enumerable.Range(0, 32).Select(index => { var a = BitConverter.ToUInt32(extendedHash.Slice(index, 4).ToArray()).ToBigEndian(); - var b = ErgoConstants.N(height); + var b = CalculateN(height); return a % b; }) .ToArray(); @@ -86,11 +113,12 @@ protected virtual Share ProcessShareInternal(StratumConnection worker, string ex hasher.Digest(coinbase, hashResult); // calculate i - var tmp2 = new System.Numerics.BigInteger(hashResult.Slice(24, 8), true, true) % ErgoConstants.N(Height); + var slice = hashResult.Slice(24, 8); + var tmp2 = new BigInteger(slice, true, true) % CalculateN(Height); var i = tmp2.ToByteArray(false, true).PadFront(0, 4); // calculate e - var h = new System.Numerics.BigInteger(Height).ToByteArray(true, true).PadFront(0, 4); + var h = new BigInteger(Height).ToByteArray(true, true).PadFront(0, 4); var ihM = i.Concat(h).Concat(ErgoConstants.M).ToArray(); hasher.Digest(ihM, hashResult); var e = hashResult[1..].ToArray(); @@ -110,17 +138,17 @@ protected virtual Share ProcessShareInternal(StratumConnection worker, string ex hasher.Digest(buf, hash); // extract 31 bytes at end - return new System.Numerics.BigInteger(hash[1..], true, true); - }).Aggregate((a, b) => a + b); + return new BigInteger(hash[1..], true, true); + }).Aggregate((x, y) => x + y); // calculate fH var blockHash = f.ToByteArray(true, true).PadFront(0, 32); hasher.Digest(blockHash, hashResult); - var fh = new System.Numerics.BigInteger(hashResult, true, true); + var fh = new BigInteger(hashResult, true, true); // diff check var stratumDifficulty = context.Difficulty; - var isLowDifficulty = fh / new System.Numerics.BigInteger(context.Difficulty) > B; + var isLowDifficulty = fh / new BigInteger(context.Difficulty) > B; // check if the share meets the much harder block difficulty (block candidate) var isBlockCandidate = B >= fh; @@ -176,7 +204,7 @@ public void Init(ErgoBlockTemplate blockTemplate, string jobId) { BlockTemplate = blockTemplate; JobId = jobId; - B = System.Numerics.BigInteger.Parse(BlockTemplate.Work.B, NumberStyles.Integer); + B = BigInteger.Parse(BlockTemplate.Work.B, NumberStyles.Integer); Difficulty = new Target(B).Difficulty; jobParams = new object[] diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs index 8806c5910..90190a46d 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs @@ -268,11 +268,10 @@ private async Task SendJob(StratumConnection connection, ErgoWorkerContext conte public override double HashrateFromShares(double shares, double interval) { - var multiplier = BitcoinConstants.Pow2x32; + var multiplier = ErgoConstants.Pow2x26; var result = shares * multiplier / interval; - result /= ErgoConstants.DiffMultiplier; - result /= 3; + result /= 10; return result; } From 17ec7739fc4d8e8f466ac79e4fbb08e2a3c9a38f Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Wed, 7 Jul 2021 07:57:52 +0200 Subject: [PATCH 010/145] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index a0d945ca9..3e404bc86 100644 --- a/README.md +++ b/README.md @@ -20,10 +20,15 @@ Refer to [this file](https://github.com/coinfoundry/miningcore/blob/master/src/Miningcore/coins.json) for a complete list. +## Support + +Feel free to visit the [Discussions Area](https://github.com/coinfoundry/miningcore/discussions). + ### Caveats #### Monero +- Monero's Wallet Daemon (monero-wallet-rpc) relies on HTTP digest authentication for authentication which is currently not supported by Miningcore. Therefore monero-wallet-rpc must be run with the `--disable-rpc-login` option. It is advisable to mitigate the resulting security risk by putting monero-wallet-rpc behind a reverse proxy like nginx with basic-authentication. - Miningcore utilizes RandomX's light-mode by default which consumes only **256 MB of memory per RandomX-VM**. A modern (2021) era CPU will be able to handle ~ 50 shares per second in this mode. - If you are running into throughput problems on your pool you can either increase the number of RandomX virtual machines in light-mode by adding `"randomXVmCount": x` to your pool configuration where x is at maximum equal to the machine's number of processor cores. Alternatively you can activate fast-mode by adding `"randomXFlagsAdd": "RANDOMX_FLAG_FULL_MEM"` to the pool configuration. Fast mode increases performance by 10x but requires roughly **3 GB of RAM per RandomX-VM**. From a073d4343b86047e88b8e80ee65bb21621fcaf67 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Wed, 7 Jul 2021 08:03:31 +0200 Subject: [PATCH 011/145] Update README.md --- README.md | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 3e404bc86..4bbd403f4 100644 --- a/README.md +++ b/README.md @@ -16,14 +16,14 @@ - Detailed per-pool logging to console & filesystem - Runs on Linux and Windows -### Supported Currencies - -Refer to [this file](https://github.com/coinfoundry/miningcore/blob/master/src/Miningcore/coins.json) for a complete list. - ## Support Feel free to visit the [Discussions Area](https://github.com/coinfoundry/miningcore/discussions). +### Supported Currencies + +Refer to [this file](https://github.com/coinfoundry/miningcore/blob/master/src/Miningcore/coins.json) for a complete list. + ### Caveats #### Monero @@ -50,9 +50,9 @@ Feel free to visit the [Discussions Area](https://github.com/coinfoundry/miningc - Be sure to copy the file `verthash.dat` from your vertcoin blockchain folder to your Miningcore server - In your Miningcore config file add this property to your vertcoin pool configuration: `"vertHashDataFile": "/path/to/verthash.dat",` -### Running pre-built Release Binaries +## Running Miningcore -#### Linux +### Linux: pre-built binaries - Install [.NET 5 Runtime](https://dotnet.microsoft.com/download/dotnet/5.0) - For Debian/Ubuntu, install these packages @@ -66,7 +66,7 @@ Feel free to visit the [Discussions Area](https://github.com/coinfoundry/miningc - Create a configuration file `config.json` as described [here](https://github.com/coinfoundry/miningcore/wiki/Configuration) - Run `dotnet Miningcore.dll -c config.json` -#### Windows +### Windows: pre-built binaries - Install [.NET 5 Runtime](https://dotnet.microsoft.com/download/dotnet/5.0) - Install [PostgreSQL Database](https://www.postgresql.org/) @@ -76,7 +76,9 @@ Feel free to visit the [Discussions Area](https://github.com/coinfoundry/miningc - Create a configuration file `config.json` as described [here](https://github.com/coinfoundry/miningcore/wiki/Configuration) - Run `dotnet Miningcore.dll -c config.json` -### Basic PostgreSQL Database setup +## Database setup + +Miningcore currently requires PostgreSQL 10 or higher. Create the database: @@ -100,7 +102,7 @@ $ wget https://raw.githubusercontent.com/coinfoundry/miningcore/master/src/Minin $ psql -d miningcore -U miningcore -f createdb.sql ``` -### Advanced PostgreSQL Database setup +### Advanced setup If you are planning to run a Multipool-Cluster, the simple setup might not perform well enough under high load. In this case you are strongly advised to use PostgreSQL 11 or higher. After performing the steps outlined in the basic setup above, perform these additional steps: @@ -121,13 +123,13 @@ CREATE TABLE shares_mypool1 PARTITION OF shares FOR VALUES IN ('mypool1'); Once you have done this for all of your existing pools you should now restore your shares from backup. -### [Configuration](https://github.com/coinfoundry/miningcore/wiki/Configuration) +## [Configuration](https://github.com/coinfoundry/miningcore/wiki/Configuration) ### [API](https://github.com/coinfoundry/miningcore/wiki/API) -### Building from Source +## Building from Source -#### Building on Ubuntu 20.04 +### Building on Ubuntu 20.04 ```console $ wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb @@ -141,7 +143,7 @@ $ cd miningcore/src/Miningcore $ dotnet publish -c Release --framework net5.0 -o ../../build ``` -#### Building on Windows +### Building on Windows Download and install the [.NET 5 SDK](https://dotnet.microsoft.com/download/dotnet/5.0) @@ -151,13 +153,13 @@ Download and install the [.NET 5 SDK](https://dotnet.microsoft.com/download/dotn > dotnet publish -c Release --framework net5.0 -o ..\..\build ``` -#### Building on Windows - Visual Studio +### Building on Windows - Visual Studio - Install [Visual Studio 2019](https://www.visualstudio.com/vs/). Visual Studio Community Edition is fine. - Open `Miningcore.sln` in Visual Studio -#### After successful build +### After successful build Create a configuration file `config.json` as described [here](https://github.com/coinfoundry/miningcore/wiki/Configuration) From df26258a6f639a50eed83e592ef8c9d764830074 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Wed, 7 Jul 2021 08:10:53 +0200 Subject: [PATCH 012/145] Update README.md --- README.md | 68 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 4bbd403f4..eec64c328 100644 --- a/README.md +++ b/README.md @@ -20,36 +20,6 @@ Feel free to visit the [Discussions Area](https://github.com/coinfoundry/miningcore/discussions). -### Supported Currencies - -Refer to [this file](https://github.com/coinfoundry/miningcore/blob/master/src/Miningcore/coins.json) for a complete list. - -### Caveats - -#### Monero - -- Monero's Wallet Daemon (monero-wallet-rpc) relies on HTTP digest authentication for authentication which is currently not supported by Miningcore. Therefore monero-wallet-rpc must be run with the `--disable-rpc-login` option. It is advisable to mitigate the resulting security risk by putting monero-wallet-rpc behind a reverse proxy like nginx with basic-authentication. -- Miningcore utilizes RandomX's light-mode by default which consumes only **256 MB of memory per RandomX-VM**. A modern (2021) era CPU will be able to handle ~ 50 shares per second in this mode. -- If you are running into throughput problems on your pool you can either increase the number of RandomX virtual machines in light-mode by adding `"randomXVmCount": x` to your pool configuration where x is at maximum equal to the machine's number of processor cores. Alternatively you can activate fast-mode by adding `"randomXFlagsAdd": "RANDOMX_FLAG_FULL_MEM"` to the pool configuration. Fast mode increases performance by 10x but requires roughly **3 GB of RAM per RandomX-VM**. - -#### ZCash - -- Pools needs to be configured with both a t-addr and z-addr (new configuration property "z-address" of the pool configuration element) -- First configured zcashd daemon needs to control both the t-addr and the z-addr (have the private key) -- To increase the share processing throughput it is advisable to increase the maximum number of concurrent equihash solvers through the new configuration property "equihashMaxThreads" of the cluster configuration element. Increasing this value by one increases the peak memory consumption of the pool cluster by 1 GB. -- Miners may use both t-addresses and z-addresses when connecting to the pool - -#### Ethereum - -- Miningcore implements the [Ethereum stratum mining protocol](https://github.com/nicehash/Specifications/blob/master/EthereumStratum_NiceHash_v1.0.0.txt) authored by NiceHash. This protocol is implemented by all major Ethereum miners. -- Claymore Miner must be configured to communicate using this protocol by supplying the `-esm 3` command line option -- Genoil's `ethminer` must be configured to communicate using this protocol by supplying the `-SP 2` command line option - -#### Vertcoin - -- Be sure to copy the file `verthash.dat` from your vertcoin blockchain folder to your Miningcore server -- In your Miningcore config file add this property to your vertcoin pool configuration: `"vertHashDataFile": "/path/to/verthash.dat",` - ## Running Miningcore ### Linux: pre-built binaries @@ -123,9 +93,9 @@ CREATE TABLE shares_mypool1 PARTITION OF shares FOR VALUES IN ('mypool1'); Once you have done this for all of your existing pools you should now restore your shares from backup. -## [Configuration](https://github.com/coinfoundry/miningcore/wiki/Configuration) +## Configuration -### [API](https://github.com/coinfoundry/miningcore/wiki/API) +Please refer to this Wiki Page: https://github.com/coinfoundry/miningcore/wiki/Configuration ## Building from Source @@ -168,6 +138,40 @@ $ cd ../../build $ Miningcore -c config.json ``` +### Supported Currencies + +Refer to [this file](https://github.com/coinfoundry/miningcore/blob/master/src/Miningcore/coins.json) for a complete list. + +### Caveats + +#### Monero + +- Monero's Wallet Daemon (monero-wallet-rpc) relies on HTTP digest authentication for authentication which is currently not supported by Miningcore. Therefore monero-wallet-rpc must be run with the `--disable-rpc-login` option. It is advisable to mitigate the resulting security risk by putting monero-wallet-rpc behind a reverse proxy like nginx with basic-authentication. +- Miningcore utilizes RandomX's light-mode by default which consumes only **256 MB of memory per RandomX-VM**. A modern (2021) era CPU will be able to handle ~ 50 shares per second in this mode. +- If you are running into throughput problems on your pool you can either increase the number of RandomX virtual machines in light-mode by adding `"randomXVmCount": x` to your pool configuration where x is at maximum equal to the machine's number of processor cores. Alternatively you can activate fast-mode by adding `"randomXFlagsAdd": "RANDOMX_FLAG_FULL_MEM"` to the pool configuration. Fast mode increases performance by 10x but requires roughly **3 GB of RAM per RandomX-VM**. + +#### ZCash + +- Pools needs to be configured with both a t-addr and z-addr (new configuration property "z-address" of the pool configuration element) +- First configured zcashd daemon needs to control both the t-addr and the z-addr (have the private key) +- To increase the share processing throughput it is advisable to increase the maximum number of concurrent equihash solvers through the new configuration property "equihashMaxThreads" of the cluster configuration element. Increasing this value by one increases the peak memory consumption of the pool cluster by 1 GB. +- Miners may use both t-addresses and z-addresses when connecting to the pool + +#### Ethereum + +- Miningcore implements the [Ethereum stratum mining protocol](https://github.com/nicehash/Specifications/blob/master/EthereumStratum_NiceHash_v1.0.0.txt) authored by NiceHash. This protocol is implemented by all major Ethereum miners. +- Claymore Miner must be configured to communicate using this protocol by supplying the `-esm 3` command line option +- Genoil's `ethminer` must be configured to communicate using this protocol by supplying the `-SP 2` command line option + +#### Vertcoin + +- Be sure to copy the file `verthash.dat` from your vertcoin blockchain folder to your Miningcore server +- In your Miningcore config file add this property to your vertcoin pool configuration: `"vertHashDataFile": "/path/to/verthash.dat",` + +## API + +Miningcore comes with an integrated REST API. Please refer to this page for instructions: https://github.com/coinfoundry/miningcore/wiki/API + ## Running a production pool A public production pool requires a web-frontend for your users to check their hashrate, earnings etc. Miningcore does not include such frontend but there are several community projects that can be used as starting point. From 293384690406ec1f2cf0cf9cb3056d8cb1e70c3b Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Wed, 7 Jul 2021 08:58:19 +0200 Subject: [PATCH 013/145] improve GenIndexes --- src/Miningcore/Blockchain/Ergo/ErgoJob.cs | 32 ++++++++++++++--------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs index e7a7806cb..e337eedb4 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Concurrent; +using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; @@ -38,11 +39,13 @@ public static BigInteger CalculateN(ulong height) { height = Math.Min(NIncreasementHeightMax, height); - if(height < IncreaseStart) - return nBase; - - if(height >= NIncreasementHeightMax) - return 2147387550; + switch (height) + { + case < IncreaseStart: + return nBase; + case >= NIncreasementHeightMax: + return 2147387550; + } var res = nBase; var iterationsNumber = ((height - IncreaseStart) / IncreasePeriodForN) + 1; @@ -84,20 +87,23 @@ protected virtual byte[] SerializeCoinbase(string msg, string extraNonce1, strin private BigInteger[] GenIndexes(byte[] seed, ulong height) { // hash seed - var hash = new byte[32]; + Span<byte> hash = stackalloc byte[32]; hasher.Digest(seed, hash); // duplicate - var extendedHash = hash.Concat(hash).ToArray(); + Span<byte> extendedHash = stackalloc byte[64]; + hash.CopyTo(extendedHash); + hash.CopyTo(extendedHash.Slice(32, 32)); // map indexes - var result = Enumerable.Range(0, 32).Select(index => + var result = new BigInteger[32]; + + for(var i = 0; i < 32; i++) { - var a = BitConverter.ToUInt32(extendedHash.Slice(index, 4).ToArray()).ToBigEndian(); - var b = CalculateN(height); - return a % b; - }) - .ToArray(); + var x = BitConverter.ToUInt32(extendedHash.Slice(i, 4)).ToBigEndian(); + var y = CalculateN(height); + result[i] = x % y; + } return result; } From f87e0755c636e6f5db32f7a33308116ff995d2a4 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Wed, 7 Jul 2021 16:12:41 +0200 Subject: [PATCH 014/145] Block unlocking --- .../Bitcoin/BitcoinPayoutHandler.cs | 6 +- .../ErgoDaemonEndpointConfigExtra.cs | 10 + .../Ergo/Configuration/ErgoPoolConfigExtra.cs | 2 + .../Blockchain/Ergo/ErgoClientExtensions.cs | 29 + .../Blockchain/Ergo/ErgoClientFactory.cs | 37 + .../Blockchain/Ergo/ErgoConstants.cs | 5 + src/Miningcore/Blockchain/Ergo/ErgoJob.cs | 36 +- .../Blockchain/Ergo/ErgoJobManager.cs | 30 +- .../Blockchain/Ergo/ErgoPayoutHandler.cs | 274 +++-- src/Miningcore/Blockchain/Ergo/ErgoPool.cs | 6 +- .../Blockchain/Ergo/RPC/ErgoClient.cs | 1014 ++++++++--------- src/Miningcore/Blockchain/Ergo/RPC/ergo.nswag | 6 +- src/Miningcore/Payments/PayoutManager.cs | 10 +- 13 files changed, 806 insertions(+), 659 deletions(-) create mode 100644 src/Miningcore/Blockchain/Ergo/Configuration/ErgoDaemonEndpointConfigExtra.cs create mode 100644 src/Miningcore/Blockchain/Ergo/ErgoClientExtensions.cs create mode 100644 src/Miningcore/Blockchain/Ergo/ErgoClientFactory.cs diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs index f857f1f02..298db6b12 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs @@ -87,6 +87,7 @@ public virtual async Task<Block[]> ClassifyBlocksAsync(Block[] blocks) var pageSize = 100; var pageCount = (int) Math.Ceiling(blocks.Length / (double) pageSize); var result = new List<Block>(); + var minConfirmations = extraPoolConfig?.MinimumConfirmations ?? BitcoinConstants.CoinbaseMinConfimations; for(var i = 0; i < pageCount; i++) { @@ -121,12 +122,12 @@ public virtual async Task<Block[]> ClassifyBlocksAsync(Block[] blocks) result.Add(block); logger.Info(() => $"[{LogCategory}] Block {block.BlockHeight} classified as orphaned due to daemon error {cmdResult.Error.Code}"); + + messageBus.NotifyBlockUnlocked(poolConfig.Id, block, coin); } else - { logger.Warn(() => $"[{LogCategory}] Daemon reports error '{cmdResult.Error.Message}' (Code {cmdResult.Error.Code}) for transaction {page[j].TransactionConfirmationData}"); - } } // missing transaction details are interpreted as "orphaned" @@ -145,7 +146,6 @@ public virtual async Task<Block[]> ClassifyBlocksAsync(Block[] blocks) { case "immature": // update progress - var minConfirmations = extraPoolConfig?.MinimumConfirmations ?? BitcoinConstants.CoinbaseMinConfimations; block.ConfirmationProgress = Math.Min(1.0d, (double) transactionInfo.Confirmations / minConfirmations); block.Reward = transactionInfo.Amount; // update actual block-reward from coinbase-tx result.Add(block); diff --git a/src/Miningcore/Blockchain/Ergo/Configuration/ErgoDaemonEndpointConfigExtra.cs b/src/Miningcore/Blockchain/Ergo/Configuration/ErgoDaemonEndpointConfigExtra.cs new file mode 100644 index 000000000..38857c775 --- /dev/null +++ b/src/Miningcore/Blockchain/Ergo/Configuration/ErgoDaemonEndpointConfigExtra.cs @@ -0,0 +1,10 @@ +namespace Miningcore.Blockchain.Ergo.Configuration +{ + public class ErgoDaemonEndpointConfigExtra + { + /// <summary> + /// The Ergo Node's API key in clear-text - not the hash + /// </summary> + public string ApiKey { get; set; } + } +} diff --git a/src/Miningcore/Blockchain/Ergo/Configuration/ErgoPoolConfigExtra.cs b/src/Miningcore/Blockchain/Ergo/Configuration/ErgoPoolConfigExtra.cs index c4927d19d..ead4a357d 100644 --- a/src/Miningcore/Blockchain/Ergo/Configuration/ErgoPoolConfigExtra.cs +++ b/src/Miningcore/Blockchain/Ergo/Configuration/ErgoPoolConfigExtra.cs @@ -7,5 +7,7 @@ public class ErgoPoolConfigExtra /// Default: 12 - you should increase this value if your blockrefreshinterval is higher than 300ms /// </summary> public int? MaxActiveJobs { get; set; } + + public int? MinimumConfirmations { get; set; } } } diff --git a/src/Miningcore/Blockchain/Ergo/ErgoClientExtensions.cs b/src/Miningcore/Blockchain/Ergo/ErgoClientExtensions.cs new file mode 100644 index 000000000..ce9ce1d1d --- /dev/null +++ b/src/Miningcore/Blockchain/Ergo/ErgoClientExtensions.cs @@ -0,0 +1,29 @@ +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Miningcore.Blockchain.Ergo +{ + public partial class ErgoClient + { + public string ApiKey { get; set; } + + private Task PrepareRequestAsync(HttpClient client, HttpRequestMessage request, StringBuilder url) + { + request.Headers.Add("api_key", ApiKey); + + return Task.CompletedTask; + } + + private Task PrepareRequestAsync(HttpClient client, HttpRequestMessage request, string url) + { + return Task.CompletedTask; + } + + private static Task ProcessResponseAsync(HttpClient client, HttpResponseMessage response, CancellationToken ct) + { + return Task.CompletedTask; + } + } +} diff --git a/src/Miningcore/Blockchain/Ergo/ErgoClientFactory.cs b/src/Miningcore/Blockchain/Ergo/ErgoClientFactory.cs new file mode 100644 index 000000000..0a2eb7a61 --- /dev/null +++ b/src/Miningcore/Blockchain/Ergo/ErgoClientFactory.cs @@ -0,0 +1,37 @@ +using System; +using System.Linq; +using System.Net.Http; +using Miningcore.Blockchain.Ergo.Configuration; +using Miningcore.Configuration; +using Miningcore.Extensions; +using Miningcore.Util; +using NLog; + +namespace Miningcore.Blockchain.Ergo +{ + public static class ErgoClientFactory + { + public static ErgoClient CreateClient(PoolConfig poolConfig, ClusterConfig clusterConfig, IHttpClientFactory httpClientFactory, ILogger logger) + { + var epConfig = poolConfig.Daemons.First(); + var extra = epConfig.Extra.SafeExtensionDataAs<ErgoDaemonEndpointConfigExtra>(); + + if(logger != null && clusterConfig.PaymentProcessing?.Enabled == true && + poolConfig.PaymentProcessing?.Enabled == true && string.IsNullOrEmpty(extra?.ApiKey)) + logger.ThrowLogPoolStartupException("Ergo daemon apiKey not provided"); + + var baseUrl = new UriBuilder(epConfig.Ssl || epConfig.Http2 ? Uri.UriSchemeHttps : Uri.UriSchemeHttp, + epConfig.Host, epConfig.Port, epConfig.HttpPath); + + var result = new ErgoClient(baseUrl.ToString(), httpClientFactory.CreateClient()) + { + ApiKey = extra.ApiKey + }; + +#if DEBUG + result.ReadResponseAsString = true; +#endif + return result; + } + } +} diff --git a/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs b/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs index 41c3d242b..fd5d0a09c 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Numerics; using System.Text; +using System.Text.RegularExpressions; using System.Threading.Tasks; using NBitcoin; @@ -16,6 +17,10 @@ public static class ErgoConstants public const uint DiffMultiplier = 256; public static double Pow2x26 = Math.Pow(2, 26); + public const double SmallestUnit = 1000000000; + + public static Regex RegexChain = new("ergo-([^-]+)-.+", RegexOptions.Compiled); + public static readonly byte[] M = Enumerable.Range(0, 1024) .SelectMany(x => { diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs index e337eedb4..596fff08b 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Concurrent; -using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; @@ -11,7 +10,6 @@ using Miningcore.Extensions; using Miningcore.Stratum; using System.Numerics; -using MoreLinq; using NBitcoin; namespace Miningcore.Blockchain.Ergo @@ -20,7 +18,7 @@ public class ErgoJob { public ErgoBlockTemplate BlockTemplate { get; private set; } public double Difficulty { get; private set; } - public ulong Height => BlockTemplate.Work.Height; + public uint Height => BlockTemplate.Work.Height; public string JobId { get; protected set; } private object[] jobParams; @@ -28,14 +26,12 @@ public class ErgoJob private static readonly IHashAlgorithm hasher = new Blake2b(); private BigInteger B; - private static readonly BigInteger nBase = BigInteger.Pow(2, 26); - private const ulong IncreaseStart = 600 * 1024; - private const ulong IncreasePeriodForN = 50 * 1024; - private const ulong NIncreasementHeightMax = 9216000; - private static readonly BigInteger a = new(100); - private static readonly BigInteger b = new(105); + private static readonly uint nBase = (uint) Math.Pow(2, 26); + private const uint IncreaseStart = 600 * 1024; + private const uint IncreasePeriodForN = 50 * 1024; + private const uint NIncreasementHeightMax = 9216000; - public static BigInteger CalculateN(ulong height) + public static uint CalcN(uint height) { height = Math.Min(NIncreasementHeightMax, height); @@ -47,13 +43,13 @@ public static BigInteger CalculateN(ulong height) return 2147387550; } - var res = nBase; - var iterationsNumber = ((height - IncreaseStart) / IncreasePeriodForN) + 1; + var step = nBase; + var iterationsNumber = (height - IncreaseStart) / IncreasePeriodForN + 1; - for(var i = 0ul; i < iterationsNumber; i++) - res = res / a * b; + for(var i = 0; i < iterationsNumber; i++) + step = step / 100 * 105; - return res; + return step; } protected bool RegisterSubmit(string extraNonce1, string extraNonce2, string nTime, string nonce) @@ -84,7 +80,7 @@ protected virtual byte[] SerializeCoinbase(string msg, string extraNonce1, strin } } - private BigInteger[] GenIndexes(byte[] seed, ulong height) + private BigInteger[] GenIndexes(byte[] seed, uint height) { // hash seed Span<byte> hash = stackalloc byte[32]; @@ -101,7 +97,7 @@ private BigInteger[] GenIndexes(byte[] seed, ulong height) for(var i = 0; i < 32; i++) { var x = BitConverter.ToUInt32(extendedHash.Slice(i, 4)).ToBigEndian(); - var y = CalculateN(height); + var y = CalcN(height); result[i] = x % y; } @@ -120,7 +116,7 @@ protected virtual Share ProcessShareInternal(StratumConnection worker, string ex // calculate i var slice = hashResult.Slice(24, 8); - var tmp2 = new BigInteger(slice, true, true) % CalculateN(Height); + var tmp2 = new BigInteger(slice, true, true) % CalcN(Height); var i = tmp2.ToByteArray(false, true).PadFront(0, 4); // calculate e @@ -165,9 +161,9 @@ protected virtual Share ProcessShareInternal(StratumConnection worker, string ex var result = new Share { - BlockHeight = (long) Height, + BlockHeight = Height, NetworkDifficulty = Difficulty, - Difficulty = stratumDifficulty + Difficulty = stratumDifficulty / ErgoConstants.DiffMultiplier }; if(isBlockCandidate) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs index 6265f6b56..a773b873e 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Net.Http; using System.Reactive.Linq; @@ -12,7 +11,6 @@ using Miningcore.Blockchain.Ergo.Configuration; using NLog; using Miningcore.Configuration; -using Miningcore.DaemonInterface; using Miningcore.Extensions; using Miningcore.Messaging; using Miningcore.Notifications.Messages; @@ -209,6 +207,11 @@ await GetBlockTemplateAsync() : return (isNew, forceUpdate); } + catch(ApiException<ApiError> ex) + { + logger.Error(() => $"Error during {nameof(UpdateJob)}: {ex.Result.Detail ?? ex.Result.Reason}"); + } + catch(Exception ex) { logger.Error(ex, () => $"Error during {nameof(UpdateJob)}"); @@ -316,7 +319,13 @@ await daemon.MiningSubmitSolutionAsync(new PowSolutions return true; } - catch(ApiException ex) + catch(ApiException<ApiError> ex) + { + logger.Warn(() => $"Block {share.BlockHeight} submission failed with: {ex.Result.Detail ?? ex.Result.Reason ?? ex.Message}"); + messageBus.SendMessage(new AdminNotification("Block submission failed", $"Pool {poolConfig.Id} {(!string.IsNullOrEmpty(share.Source) ? $"[{share.Source.ToUpper()}] " : string.Empty)}failed to submit block {share.BlockHeight}: {ex.Result.Detail ?? ex.Result.Reason ?? ex.Message}")); + } + + catch(Exception ex) { logger.Warn(() => $"Block {share.BlockHeight} submission failed with: {ex.Message}"); messageBus.SendMessage(new AdminNotification("Block submission failed", $"Pool {poolConfig.Id} {(!string.IsNullOrEmpty(share.Source) ? $"[{share.Source.ToUpper()}] " : string.Empty)}failed to submit block {share.BlockHeight}: {ex.Message}")); @@ -462,7 +471,7 @@ protected override async Task PostStartInitAsync(CancellationToken ct) ex=> logger.ThrowLogPoolStartupException($"Daemon reports: {ex.Message}")); // chain detection - var m = Regex.Match(info.Name, "ergo-([^-]+)-.+"); + var m = ErgoConstants.RegexChain.Match(info.Name); if(!m.Success) logger.ThrowLogPoolStartupException($"Unable to identify network type ({info.Name}"); @@ -471,6 +480,12 @@ protected override async Task PostStartInitAsync(CancellationToken ct) // Payment-processing setup if(clusterConfig.PaymentProcessing?.Enabled == true && poolConfig.PaymentProcessing?.Enabled == true) { + // check configured address belongs to wallet + var walletAddresses = await daemon.WalletAddressesAsync(ct); + + if(!walletAddresses.Contains(poolConfig.Address)) + logger.ThrowLogPoolStartupException($"Pool address {info.Name} is not controlled by wallet"); + ConfigureRewards(); } @@ -504,12 +519,7 @@ public override void Configure(PoolConfig poolConfig, ClusterConfig clusterConfi protected override void ConfigureDaemons() { - var epConfig = poolConfig.Daemons.First(); - - var baseUrl = new UriBuilder(epConfig.Ssl || epConfig.Http2 ? Uri.UriSchemeHttps : Uri.UriSchemeHttp, - epConfig.Host, epConfig.Port, epConfig.HttpPath); - - daemon = new ErgoClient(baseUrl.ToString(), httpClientFactory.CreateClient()); + daemon = ErgoClientFactory.CreateClient(poolConfig, clusterConfig, httpClientFactory, logger); } protected override async Task<bool> AreDaemonsHealthyAsync() diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index 80beb6013..99a23596b 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -1,12 +1,15 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net.Http; +using System.Text.RegularExpressions; using System.Threading.Tasks; using Autofac; using AutoMapper; using Miningcore.Blockchain.Bitcoin; using Miningcore.Blockchain.Bitcoin.Configuration; using Miningcore.Blockchain.Bitcoin.DaemonResponses; +using Miningcore.Blockchain.Ergo.Configuration; using Miningcore.Configuration; using Miningcore.DaemonInterface; using Miningcore.Extensions; @@ -21,6 +24,7 @@ using Newtonsoft.Json.Linq; using Block = Miningcore.Persistence.Model.Block; using Contract = Miningcore.Contracts.Contract; +using static Miningcore.Util.ActionUtils; namespace Miningcore.Blockchain.Ergo { @@ -36,6 +40,7 @@ public ErgoPayoutHandler( IBlockRepository blockRepo, IBalanceRepository balanceRepo, IPaymentRepository paymentRepo, + IHttpClientFactory httpClientFactory, IMasterClock clock, IMessageBus messageBus) : base(cf, mapper, shareRepo, blockRepo, balanceRepo, paymentRepo, clock, messageBus) @@ -43,34 +48,39 @@ public ErgoPayoutHandler( Contract.RequiresNonNull(ctx, nameof(ctx)); Contract.RequiresNonNull(balanceRepo, nameof(balanceRepo)); Contract.RequiresNonNull(paymentRepo, nameof(paymentRepo)); + Contract.RequiresNonNull(httpClientFactory, nameof(httpClientFactory)); this.ctx = ctx; + this.httpClientFactory = httpClientFactory; } protected readonly IComponentContext ctx; - protected DaemonClient daemon; + protected ErgoClient daemon; + private ErgoPoolConfigExtra extraPoolConfig; + private readonly IHttpClientFactory httpClientFactory; + private string network; protected override string LogCategory => "Bitcoin Payout Handler"; #region IPayoutHandler - public virtual Task ConfigureAsync(ClusterConfig clusterConfig, PoolConfig poolConfig) + public virtual async Task ConfigureAsync(ClusterConfig clusterConfig, PoolConfig poolConfig) { Contract.RequiresNonNull(poolConfig, nameof(poolConfig)); + logger = LogUtil.GetPoolScopedLogger(typeof(ErgoPayoutHandler), poolConfig); + this.poolConfig = poolConfig; this.clusterConfig = clusterConfig; - //extraPoolConfig = poolConfig.Extra.SafeExtensionDataAs<BitcoinDaemonEndpointConfigExtra>(); + extraPoolConfig = poolConfig.Extra.SafeExtensionDataAs<ErgoPoolConfigExtra>(); //extraPoolPaymentProcessingConfig = poolConfig.PaymentProcessing.Extra.SafeExtensionDataAs<BitcoinPoolPaymentProcessingConfigExtra>(); - logger = LogUtil.GetPoolScopedLogger(typeof(BitcoinPayoutHandler), poolConfig); - - var jsonSerializerSettings = ctx.Resolve<JsonSerializerSettings>(); - daemon = new DaemonClient(jsonSerializerSettings, messageBus, clusterConfig.ClusterName ?? poolConfig.PoolName, poolConfig.Id); - daemon.Configure(poolConfig.Daemons); + daemon = ErgoClientFactory.CreateClient(poolConfig, clusterConfig, httpClientFactory, null); - return Task.FromResult(true); + // detect chain + var info = await daemon.GetNodeInfoAsync(); + network = ErgoConstants.RegexChain.Match(info.Name).Groups[1].Value.ToLower(); } public virtual async Task<Block[]> ClassifyBlocksAsync(Block[] blocks) @@ -78,105 +88,153 @@ public virtual async Task<Block[]> ClassifyBlocksAsync(Block[] blocks) Contract.RequiresNonNull(poolConfig, nameof(poolConfig)); Contract.RequiresNonNull(blocks, nameof(blocks)); - //var coin = poolConfig.Template.As<CoinTemplate>(); - //var pageSize = 100; - //var pageCount = (int) Math.Ceiling(blocks.Length / (double) pageSize); - //var result = new List<Block>(); - - //for(var i = 0; i < pageCount; i++) - //{ - // // get a page full of blocks - // var page = blocks - // .Skip(i * pageSize) - // .Take(pageSize) - // .ToArray(); - - // // build command batch (block.TransactionConfirmationData is the hash of the blocks coinbase transaction) - // var batch = page.Select(block => new DaemonCmd(BitcoinCommands.GetTransaction, - // new[] { block.TransactionConfirmationData })).ToArray(); - - // // execute batch - // var results = await daemon.ExecuteBatchAnyAsync(logger, batch); - - // for(var j = 0; j < results.Length; j++) - // { - // var cmdResult = results[j]; - - // var transactionInfo = cmdResult.Response?.ToObject<Transaction>(); - // var block = page[j]; - - // // check error - // if(cmdResult.Error != null) - // { - // // Code -5 interpreted as "orphaned" - // if(cmdResult.Error.Code == -5) - // { - // block.Status = BlockStatus.Orphaned; - // block.Reward = 0; - // result.Add(block); - - // logger.Info(() => $"[{LogCategory}] Block {block.BlockHeight} classified as orphaned due to daemon error {cmdResult.Error.Code}"); - // } - - // else - // { - // logger.Warn(() => $"[{LogCategory}] Daemon reports error '{cmdResult.Error.Message}' (Code {cmdResult.Error.Code}) for transaction {page[j].TransactionConfirmationData}"); - // } - // } - - // // missing transaction details are interpreted as "orphaned" - // else if(transactionInfo?.Details == null || transactionInfo.Details.Length == 0) - // { - // block.Status = BlockStatus.Orphaned; - // block.Reward = 0; - // result.Add(block); - - // logger.Info(() => $"[{LogCategory}] Block {block.BlockHeight} classified as orphaned due to missing tx details"); - // } - - // else - // { - // switch(transactionInfo.Details[0].Category) - // { - // case "immature": - // // update progress - // var minConfirmations = extraPoolConfig?.MinimumConfirmations ?? BitcoinConstants.CoinbaseMinConfimations; - // block.ConfirmationProgress = Math.Min(1.0d, (double) transactionInfo.Confirmations / minConfirmations); - // block.Reward = transactionInfo.Amount; // update actual block-reward from coinbase-tx - // result.Add(block); - - // messageBus.NotifyBlockConfirmationProgress(poolConfig.Id, block, coin); - // break; - - // case "generate": - // // matured and spendable coinbase transaction - // block.Status = BlockStatus.Confirmed; - // block.ConfirmationProgress = 1; - // block.Reward = transactionInfo.Amount; // update actual block-reward from coinbase-tx - // result.Add(block); - - // logger.Info(() => $"[{LogCategory}] Unlocked block {block.BlockHeight} worth {FormatAmount(block.Reward)}"); - - // messageBus.NotifyBlockUnlocked(poolConfig.Id, block, coin); - // break; - - // default: - // logger.Info(() => $"[{LogCategory}] Block {block.BlockHeight} classified as orphaned. Category: {transactionInfo.Details[0].Category}"); - - // block.Status = BlockStatus.Orphaned; - // block.Reward = 0; - // result.Add(block); - - // messageBus.NotifyBlockUnlocked(poolConfig.Id, block, coin); - // break; - // } - // } - // } - //} - - //return result.ToArray(); - - return blocks; + if(blocks.Length == 0) + return blocks; + + var coin = poolConfig.Template.As<ErgoCoinTemplate>(); + var pageSize = 100; + var pageCount = (int) Math.Ceiling(blocks.Length / (double) pageSize); + var result = new List<Block>(); + var minConfirmations = extraPoolConfig?.MinimumConfirmations ?? (network == "mainnet" ? 100 : 10); + var minerRewardsPubKey = await daemon.MiningReadMinerRewardPubkeyAsync(); + var minerRewardsAddress = await daemon.MiningReadMinerRewardAddressAsync(); + + for(var i = 0; i < pageCount; i++) + { + // get a page full of blocks + var page = blocks + .Skip(i * pageSize) + .Take(pageSize) + .ToArray(); + + // fetch header ids for blocks in page + var headerBatch = page.Select(block => daemon.GetFullBlockAtAsync((int) block.BlockHeight)).ToArray(); + + await Guard(()=> Task.WhenAll(headerBatch), + ex=> logger.Debug(ex)); + + for(var j = 0; j < page.Length; j++) + { + var block = page[j]; + var headerTask = headerBatch[j]; + + if(!headerTask.IsCompletedSuccessfully) + { + if(headerTask.IsFaulted) + logger.Warn(()=> $"Failed to fetch block {block.BlockHeight}: {headerTask.Exception?.InnerException?.Message ?? headerTask.Exception?.Message}"); + else + logger.Warn(()=> $"Failed to fetch block {block.BlockHeight}: {headerTask.Status.ToString().ToLower()}"); + + continue; + } + + var headerIds = headerTask.Result; + + // fetch blocks + var blockBatch = headerIds.Select(x=> daemon.GetFullBlockByIdAsync(x)).ToArray(); + + await Guard(()=> Task.WhenAll(blockBatch), + ex=> logger.Debug(ex)); + + var blockHandled = false; + var pkMismatchCount = 0; + var nonceMismatchCount = 0; + var coinbaseNonWalletTxCount = 0; + + foreach (var blockTask in blockBatch) + { + if(blockHandled) + break; + + if(!blockTask.IsCompletedSuccessfully) + continue; + + var fullBlock = blockTask.Result; + + // only consider blocks with pow-solution pk matching ours + if(fullBlock.Header.PowSolutions.Pk != minerRewardsPubKey.RewardPubKey) + { + pkMismatchCount++; + continue; + } + + // only consider blocks with pow-solution nonce matching what we have on file + if(fullBlock.Header.PowSolutions.N != block.TransactionConfirmationData) + { + nonceMismatchCount++; + continue; + } + + var coinbaseWalletTxFound = false; + + foreach(var blockTx in fullBlock.BlockTransactions.Transactions) + { + var walletTx = await Guard(()=> daemon.WalletGetTransactionAsync(blockTx.Id)); + var coinbaseOutput = walletTx?.Outputs?.FirstOrDefault(x => x.Address == minerRewardsAddress.RewardAddress); + + if(coinbaseOutput != null) + { + coinbaseWalletTxFound = true; + + // enough confirmations? + if(walletTx.NumConfirmations >= minConfirmations) + { + // matured and spendable coinbase transaction + block.Status = BlockStatus.Confirmed; + block.ConfirmationProgress = 1; + block.Reward = (decimal) (coinbaseOutput.Value / ErgoConstants.SmallestUnit); + result.Add(block); + + logger.Info(() => $"[{LogCategory}] Unlocked block {block.BlockHeight} worth {FormatAmount(block.Reward)}"); + + messageBus.NotifyBlockUnlocked(poolConfig.Id, block, coin); + + blockHandled = true; + break; + } + + else + { + // update progress + block.ConfirmationProgress = Math.Min(1.0d, (double) walletTx.NumConfirmations / minConfirmations); + block.Reward = (decimal) (coinbaseOutput.Value / ErgoConstants.SmallestUnit); + result.Add(block); + + messageBus.NotifyBlockConfirmationProgress(poolConfig.Id, block, coin); + } + } + } + + if(!blockHandled && !coinbaseWalletTxFound) + coinbaseNonWalletTxCount++; + } + + if(!blockHandled) + { + string orphanReason = null; + + if(pkMismatchCount == blockBatch.Length) + orphanReason = "pk mismatch"; + else if(nonceMismatchCount == blockBatch.Length) + orphanReason = "nonce mismatch"; + else if(coinbaseNonWalletTxCount == blockBatch.Length) + orphanReason = "no related coinbase tx found in wallet"; + + if(!string.IsNullOrEmpty(orphanReason)) + { + block.Status = BlockStatus.Orphaned; + block.Reward = 0; + result.Add(block); + + logger.Info(() => $"[{LogCategory}] Block {block.BlockHeight} classified as orphaned due to {orphanReason}"); + + messageBus.NotifyBlockUnlocked(poolConfig.Id, block, coin); + } + } + } + } + + return result.ToArray(); } public virtual Task CalculateBlockEffortAsync(Block block, double accumulatedBlockShareDiff) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs index 90190a46d..69be68c3c 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs @@ -180,7 +180,7 @@ protected virtual async Task OnSubmitAsync(StratumConnection connection, Timesta // telemetry PublishTelemetry(TelemetryCategory.Share, clock.Now - tsRequest.Timestamp.UtcDateTime, true); - logger.Info(() => $"[{connection.ConnectionId}] Share accepted: D={Math.Round(share.Difficulty, 3)}"); + logger.Info(() => $"[{connection.ConnectionId}] Share accepted: D={Math.Round(share.Difficulty * ErgoConstants.DiffMultiplier, 3)}"); // update pool stats if(share.IsBlockCandidate) @@ -268,10 +268,10 @@ private async Task SendJob(StratumConnection connection, ErgoWorkerContext conte public override double HashrateFromShares(double shares, double interval) { - var multiplier = ErgoConstants.Pow2x26; + var multiplier = ErgoConstants.DiffMultiplier; var result = shares * multiplier / interval; - result /= 10; + result /= 3; return result; } diff --git a/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs b/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs index e631d067f..cc613a404 100644 --- a/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs +++ b/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs @@ -1,15 +1,13 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + //---------------------- // <auto-generated> // Generated using the NSwag toolchain v13.11.3.0 (NJsonSchema v10.4.4.0 (Newtonsoft.Json v12.0.0.0)) (http://NSwag.org) // </auto-generated> //---------------------- -using System.Collections.Generic; - -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - - #nullable enable #pragma warning disable 108 // Disable "CS0108 '{derivedDto}.ToJson()' hides inherited member '{dtoBase}.ToJson()'. Use the new keyword if hiding was intended." @@ -50,15 +48,11 @@ public string BaseUrl set { _baseUrl = value; } } - - public Newtonsoft.Json.JsonSerializerSettings JsonSerializerSettings { get { return _settings.Value; } } + protected Newtonsoft.Json.JsonSerializerSettings JsonSerializerSettings { get { return _settings.Value; } } partial void UpdateJsonSerializerSettings(Newtonsoft.Json.JsonSerializerSettings settings); - partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, string url); - partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, System.Text.StringBuilder urlBuilder); - partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response); /// <summary>Get the Array of header ids</summary> /// <param name="limit">The number of items in list to return</param> /// <param name="offset">The number of items in list to skip</param> @@ -98,12 +92,12 @@ public string BaseUrl request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -116,7 +110,7 @@ public string BaseUrl headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -183,12 +177,12 @@ public async System.Threading.Tasks.Task SendMinedBlockAsync(FullBlock body, Sys request_.Content = content_; request_.Method = new System.Net.Http.HttpMethod("POST"); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -201,7 +195,7 @@ public async System.Threading.Tasks.Task SendMinedBlockAsync(FullBlock body, Sys headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -264,12 +258,12 @@ public async System.Threading.Tasks.Task SendMinedBlockAsync(FullBlock body, Sys request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -282,7 +276,7 @@ public async System.Threading.Tasks.Task SendMinedBlockAsync(FullBlock body, Sys headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -367,12 +361,12 @@ public async System.Threading.Tasks.Task SendMinedBlockAsync(FullBlock body, Sys request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -385,7 +379,7 @@ public async System.Threading.Tasks.Task SendMinedBlockAsync(FullBlock body, Sys headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -453,12 +447,12 @@ public async System.Threading.Tasks.Task<FullBlock> GetFullBlockByIdAsync(string request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -471,7 +465,7 @@ public async System.Threading.Tasks.Task<FullBlock> GetFullBlockByIdAsync(string headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -549,12 +543,12 @@ public async System.Threading.Tasks.Task<BlockHeader> GetBlockHeaderByIdAsync(st request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -567,7 +561,7 @@ public async System.Threading.Tasks.Task<BlockHeader> GetBlockHeaderByIdAsync(st headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -645,12 +639,12 @@ public async System.Threading.Tasks.Task<BlockTransactions> GetBlockTransactions request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -663,7 +657,7 @@ public async System.Threading.Tasks.Task<BlockTransactions> GetBlockTransactions headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -747,12 +741,12 @@ public async System.Threading.Tasks.Task<MerkleProof> GetProofForTxAsync(string request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -765,7 +759,7 @@ public async System.Threading.Tasks.Task<MerkleProof> GetProofForTxAsync(string headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -833,12 +827,12 @@ public async System.Threading.Tasks.Task<MerkleProof> GetProofForTxAsync(string request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -851,7 +845,7 @@ public async System.Threading.Tasks.Task<MerkleProof> GetProofForTxAsync(string headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -918,12 +912,12 @@ public async System.Threading.Tasks.Task GetModifierByIdAsync(string modifierId, { request_.Method = new System.Net.Http.HttpMethod("GET"); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -936,7 +930,7 @@ public async System.Threading.Tasks.Task GetModifierByIdAsync(string modifierId, headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -1009,12 +1003,12 @@ public async System.Threading.Tasks.Task<PopowHeader> GetPopowHeaderByIdAsync(st request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -1027,7 +1021,7 @@ public async System.Threading.Tasks.Task<PopowHeader> GetPopowHeaderByIdAsync(st headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -1105,12 +1099,12 @@ public async System.Threading.Tasks.Task<PopowHeader> GetPopowHeaderByHeightAsyn request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -1123,7 +1117,7 @@ public async System.Threading.Tasks.Task<PopowHeader> GetPopowHeaderByHeightAsyn headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -1207,12 +1201,12 @@ public async System.Threading.Tasks.Task<NipopowProof> GetPopowProofAsync(double request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -1225,7 +1219,7 @@ public async System.Threading.Tasks.Task<NipopowProof> GetPopowProofAsync(double headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -1305,12 +1299,12 @@ public async System.Threading.Tasks.Task<NipopowProof> GetPopowProofByHeaderIdAs request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -1323,7 +1317,7 @@ public async System.Threading.Tasks.Task<NipopowProof> GetPopowProofByHeaderIdAs headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -1385,12 +1379,12 @@ public async System.Threading.Tasks.Task<NodeInfo> GetNodeInfoAsync(System.Threa request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -1403,7 +1397,7 @@ public async System.Threading.Tasks.Task<NodeInfo> GetNodeInfoAsync(System.Threa headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -1471,12 +1465,12 @@ public async System.Threading.Tasks.Task<string> SendTransactionAsync(ErgoTransa request_.Method = new System.Net.Http.HttpMethod("POST"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -1489,7 +1483,7 @@ public async System.Threading.Tasks.Task<string> SendTransactionAsync(ErgoTransa headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -1557,12 +1551,12 @@ public async System.Threading.Tasks.Task<string> CheckTransactionAsync(ErgoTrans request_.Method = new System.Net.Http.HttpMethod("POST"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -1575,7 +1569,7 @@ public async System.Threading.Tasks.Task<string> CheckTransactionAsync(ErgoTrans headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -1650,12 +1644,12 @@ public async System.Threading.Tasks.Task<string> CheckTransactionAsync(ErgoTrans request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -1668,7 +1662,7 @@ public async System.Threading.Tasks.Task<string> CheckTransactionAsync(ErgoTrans headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -1743,12 +1737,12 @@ public async System.Threading.Tasks.Task<string> CheckTransactionAsync(ErgoTrans request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -1761,7 +1755,7 @@ public async System.Threading.Tasks.Task<string> CheckTransactionAsync(ErgoTrans headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -1836,12 +1830,12 @@ public async System.Threading.Tasks.Task<int> GetRecommendedFeeAsync(int waitTim request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -1854,7 +1848,7 @@ public async System.Threading.Tasks.Task<int> GetRecommendedFeeAsync(int waitTim headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -1929,12 +1923,12 @@ public async System.Threading.Tasks.Task<int> GetExpectedWaitTimeAsync(int fee, request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -1947,7 +1941,7 @@ public async System.Threading.Tasks.Task<int> GetExpectedWaitTimeAsync(int fee, headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -2009,12 +2003,12 @@ public async System.Threading.Tasks.Task<int> GetExpectedWaitTimeAsync(int fee, request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -2027,7 +2021,7 @@ public async System.Threading.Tasks.Task<int> GetExpectedWaitTimeAsync(int fee, headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -2089,12 +2083,12 @@ public async System.Threading.Tasks.Task<int> GetExpectedWaitTimeAsync(int fee, request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -2107,7 +2101,7 @@ public async System.Threading.Tasks.Task<int> GetExpectedWaitTimeAsync(int fee, headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -2174,12 +2168,12 @@ public async System.Threading.Tasks.Task ConnectToPeerAsync(string body, System. request_.Content = content_; request_.Method = new System.Net.Http.HttpMethod("POST"); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -2192,7 +2186,7 @@ public async System.Threading.Tasks.Task ConnectToPeerAsync(string body, System. headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -2249,12 +2243,12 @@ public async System.Threading.Tasks.Task ConnectToPeerAsync(string body, System. request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -2267,7 +2261,7 @@ public async System.Threading.Tasks.Task ConnectToPeerAsync(string body, System. headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -2329,12 +2323,12 @@ public async System.Threading.Tasks.Task<PeersStatus> GetPeersStatusAsync(System request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -2347,7 +2341,7 @@ public async System.Threading.Tasks.Task<PeersStatus> GetPeersStatusAsync(System headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -2409,12 +2403,12 @@ public async System.Threading.Tasks.Task<string> GetRandomSeedAsync(System.Threa request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -2427,7 +2421,7 @@ public async System.Threading.Tasks.Task<string> GetRandomSeedAsync(System.Threa headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -2495,12 +2489,12 @@ public async System.Threading.Tasks.Task<AddressValidity> CheckAddressValidityAs request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -2513,7 +2507,7 @@ public async System.Threading.Tasks.Task<AddressValidity> CheckAddressValidityAs headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -2581,12 +2575,12 @@ public async System.Threading.Tasks.Task<string> AddressToRawAsync(string addres request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -2599,7 +2593,7 @@ public async System.Threading.Tasks.Task<string> AddressToRawAsync(string addres headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -2667,12 +2661,12 @@ public async System.Threading.Tasks.Task<string> RawToAddressAsync(string pubkey request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -2685,7 +2679,7 @@ public async System.Threading.Tasks.Task<string> RawToAddressAsync(string pubkey headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -2725,7 +2719,7 @@ public async System.Threading.Tasks.Task<string> RawToAddressAsync(string pubkey /// <param name="ergoTreeHex">ErgoTree to derive an address from</param> /// <returns>Ergo address</returns> /// <exception cref="ApiException">A server side error occurred.</exception> - public System.Threading.Tasks.Task<string> ErgoTreeToAddressAsync(string ergoTreeHex) + public System.Threading.Tasks.Task<AddressHolder> ErgoTreeToAddressAsync(string ergoTreeHex) { return ErgoTreeToAddressAsync(ergoTreeHex, System.Threading.CancellationToken.None); } @@ -2735,7 +2729,7 @@ public System.Threading.Tasks.Task<string> ErgoTreeToAddressAsync(string ergoTre /// <param name="ergoTreeHex">ErgoTree to derive an address from</param> /// <returns>Ergo address</returns> /// <exception cref="ApiException">A server side error occurred.</exception> - public async System.Threading.Tasks.Task<string> ErgoTreeToAddressAsync(string ergoTreeHex, System.Threading.CancellationToken cancellationToken) + public async System.Threading.Tasks.Task<AddressHolder> ErgoTreeToAddressAsync(string ergoTreeHex, System.Threading.CancellationToken cancellationToken) { if (ergoTreeHex == null) throw new System.ArgumentNullException("ergoTreeHex"); @@ -2753,12 +2747,12 @@ public async System.Threading.Tasks.Task<string> ErgoTreeToAddressAsync(string e request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -2771,12 +2765,12 @@ public async System.Threading.Tasks.Task<string> ErgoTreeToAddressAsync(string e headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) { - var objectResponse_ = await ReadObjectResponseAsync<string>(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync<AddressHolder>(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); @@ -2839,12 +2833,12 @@ public async System.Threading.Tasks.Task<string> GetRandomSeedWithLengthAsync(st request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -2857,7 +2851,7 @@ public async System.Threading.Tasks.Task<string> GetRandomSeedWithLengthAsync(st headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -2925,12 +2919,12 @@ public async System.Threading.Tasks.Task<string> HashBlake2bAsync(string body, S request_.Method = new System.Net.Http.HttpMethod("POST"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -2943,7 +2937,7 @@ public async System.Threading.Tasks.Task<string> HashBlake2bAsync(string body, S headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -3011,12 +3005,12 @@ public async System.Threading.Tasks.Task<Response> WalletInitAsync(Body body, Sy request_.Method = new System.Net.Http.HttpMethod("POST"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -3029,7 +3023,7 @@ public async System.Threading.Tasks.Task<Response> WalletInitAsync(Body body, Sy headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -3096,12 +3090,12 @@ public async System.Threading.Tasks.Task WalletRestoreAsync(Body2 body, System.T request_.Content = content_; request_.Method = new System.Net.Http.HttpMethod("POST"); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -3114,7 +3108,7 @@ public async System.Threading.Tasks.Task WalletRestoreAsync(Body2 body, System.T headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -3177,12 +3171,12 @@ public async System.Threading.Tasks.Task<Response2> CheckSeedAsync(Body3 body, S request_.Method = new System.Net.Http.HttpMethod("POST"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -3195,7 +3189,7 @@ public async System.Threading.Tasks.Task<Response2> CheckSeedAsync(Body3 body, S headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -3262,12 +3256,12 @@ public async System.Threading.Tasks.Task WalletUnlockAsync(Body4 body, System.Th request_.Content = content_; request_.Method = new System.Net.Http.HttpMethod("POST"); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -3280,7 +3274,7 @@ public async System.Threading.Tasks.Task WalletUnlockAsync(Body4 body, System.Th headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -3336,12 +3330,12 @@ public async System.Threading.Tasks.Task WalletLockAsync(System.Threading.Cancel { request_.Method = new System.Net.Http.HttpMethod("GET"); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -3354,7 +3348,7 @@ public async System.Threading.Tasks.Task WalletLockAsync(System.Threading.Cancel headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -3410,12 +3404,12 @@ public async System.Threading.Tasks.Task WalletRescanAsync(System.Threading.Canc { request_.Method = new System.Net.Http.HttpMethod("GET"); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -3428,7 +3422,7 @@ public async System.Threading.Tasks.Task WalletRescanAsync(System.Threading.Canc headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -3485,12 +3479,12 @@ public async System.Threading.Tasks.Task<Response3> GetWalletStatusAsync(System. request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -3503,7 +3497,7 @@ public async System.Threading.Tasks.Task<Response3> GetWalletStatusAsync(System. headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -3570,12 +3564,12 @@ public async System.Threading.Tasks.Task WalletUpdateChangeAddressAsync(Body5 bo request_.Content = content_; request_.Method = new System.Net.Http.HttpMethod("POST"); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -3588,7 +3582,7 @@ public async System.Threading.Tasks.Task WalletUpdateChangeAddressAsync(Body5 bo headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -3651,12 +3645,12 @@ public async System.Threading.Tasks.Task<Response4> WalletDeriveKeyAsync(Body6 b request_.Method = new System.Net.Http.HttpMethod("POST"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -3669,7 +3663,7 @@ public async System.Threading.Tasks.Task<Response4> WalletDeriveKeyAsync(Body6 b headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -3731,12 +3725,12 @@ public async System.Threading.Tasks.Task<Response5> WalletDeriveNextKeyAsync(Sys request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -3749,7 +3743,7 @@ public async System.Threading.Tasks.Task<Response5> WalletDeriveNextKeyAsync(Sys headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -3811,12 +3805,12 @@ public async System.Threading.Tasks.Task<BalancesSnapshot> WalletBalancesAsync(S request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -3829,7 +3823,7 @@ public async System.Threading.Tasks.Task<BalancesSnapshot> WalletBalancesAsync(S headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -3916,12 +3910,12 @@ public async System.Threading.Tasks.Task<BalancesSnapshot> WalletBalancesAsync(S request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -3934,7 +3928,7 @@ public async System.Threading.Tasks.Task<BalancesSnapshot> WalletBalancesAsync(S headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -3974,7 +3968,7 @@ public async System.Threading.Tasks.Task<BalancesSnapshot> WalletBalancesAsync(S /// <param name="id">Transaction id</param> /// <returns>Wallet-related transaction</returns> /// <exception cref="ApiException">A server side error occurred.</exception> - public System.Threading.Tasks.Task<System.Collections.Generic.ICollection<WalletTransaction>> WalletGetTransactionAsync(string id) + public System.Threading.Tasks.Task<WalletTransaction> WalletGetTransactionAsync(string id) { return WalletGetTransactionAsync(id, System.Threading.CancellationToken.None); } @@ -3984,7 +3978,7 @@ public async System.Threading.Tasks.Task<BalancesSnapshot> WalletBalancesAsync(S /// <param name="id">Transaction id</param> /// <returns>Wallet-related transaction</returns> /// <exception cref="ApiException">A server side error occurred.</exception> - public async System.Threading.Tasks.Task<System.Collections.Generic.ICollection<WalletTransaction>> WalletGetTransactionAsync(string id, System.Threading.CancellationToken cancellationToken) + public async System.Threading.Tasks.Task<WalletTransaction> WalletGetTransactionAsync(string id, System.Threading.CancellationToken cancellationToken) { if (id == null) throw new System.ArgumentNullException("id"); @@ -4003,12 +3997,12 @@ public async System.Threading.Tasks.Task<BalancesSnapshot> WalletBalancesAsync(S request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -4021,12 +4015,12 @@ public async System.Threading.Tasks.Task<BalancesSnapshot> WalletBalancesAsync(S headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) { - var objectResponse_ = await ReadObjectResponseAsync<System.Collections.Generic.ICollection<WalletTransaction>>(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync<WalletTransaction>(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); @@ -4100,12 +4094,12 @@ public async System.Threading.Tasks.Task<BalancesSnapshot> WalletBalancesAsync(S request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -4118,7 +4112,7 @@ public async System.Threading.Tasks.Task<BalancesSnapshot> WalletBalancesAsync(S headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -4203,12 +4197,12 @@ public async System.Threading.Tasks.Task<BalancesSnapshot> WalletBalancesAsync(S request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -4221,7 +4215,7 @@ public async System.Threading.Tasks.Task<BalancesSnapshot> WalletBalancesAsync(S headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -4291,12 +4285,12 @@ public async System.Threading.Tasks.Task<BalancesSnapshot> WalletBalancesAsync(S request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -4309,7 +4303,7 @@ public async System.Threading.Tasks.Task<BalancesSnapshot> WalletBalancesAsync(S headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -4384,12 +4378,12 @@ public async System.Threading.Tasks.Task<BalancesSnapshot> WalletBalancesAsync(S request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -4402,7 +4396,7 @@ public async System.Threading.Tasks.Task<BalancesSnapshot> WalletBalancesAsync(S headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -4464,12 +4458,12 @@ public async System.Threading.Tasks.Task<BalancesSnapshot> WalletBalancesUnconfi request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -4482,7 +4476,7 @@ public async System.Threading.Tasks.Task<BalancesSnapshot> WalletBalancesUnconfi headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -4544,12 +4538,12 @@ public async System.Threading.Tasks.Task<BalancesSnapshot> WalletBalancesUnconfi request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -4562,7 +4556,7 @@ public async System.Threading.Tasks.Task<BalancesSnapshot> WalletBalancesUnconfi headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -4634,12 +4628,12 @@ public async System.Threading.Tasks.Task<ErgoTransaction> WalletTransactionGener request_.Method = new System.Net.Http.HttpMethod("POST"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -4652,7 +4646,7 @@ public async System.Threading.Tasks.Task<ErgoTransaction> WalletTransactionGener headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -4732,12 +4726,12 @@ public async System.Threading.Tasks.Task<UnsignedErgoTransaction> WalletUnsigned request_.Method = new System.Net.Http.HttpMethod("POST"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -4750,7 +4744,7 @@ public async System.Threading.Tasks.Task<UnsignedErgoTransaction> WalletUnsigned headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -4832,12 +4826,12 @@ public async System.Threading.Tasks.Task<ErgoTransaction> WalletTransactionSignA request_.Method = new System.Net.Http.HttpMethod("POST"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -4850,7 +4844,7 @@ public async System.Threading.Tasks.Task<ErgoTransaction> WalletTransactionSignA headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -4930,12 +4924,12 @@ public async System.Threading.Tasks.Task<string> WalletTransactionGenerateAndSen request_.Method = new System.Net.Http.HttpMethod("POST"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -4948,7 +4942,7 @@ public async System.Threading.Tasks.Task<string> WalletTransactionGenerateAndSen headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -5026,12 +5020,12 @@ public async System.Threading.Tasks.Task<string> WalletPaymentTransactionGenerat request_.Method = new System.Net.Http.HttpMethod("POST"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -5044,7 +5038,7 @@ public async System.Threading.Tasks.Task<string> WalletPaymentTransactionGenerat headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -5116,12 +5110,12 @@ public async System.Threading.Tasks.Task<WorkMessage> MiningRequestBlockCandidat request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -5134,7 +5128,7 @@ public async System.Threading.Tasks.Task<WorkMessage> MiningRequestBlockCandidat headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -5144,7 +5138,6 @@ public async System.Threading.Tasks.Task<WorkMessage> MiningRequestBlockCandidat { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - return objectResponse_.Object; } else @@ -5203,12 +5196,12 @@ public async System.Threading.Tasks.Task<WorkMessage> MiningRequestBlockCandidat request_.Method = new System.Net.Http.HttpMethod("POST"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -5221,7 +5214,7 @@ public async System.Threading.Tasks.Task<WorkMessage> MiningRequestBlockCandidat headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -5283,12 +5276,12 @@ public async System.Threading.Tasks.Task<Response6> MiningReadMinerRewardAddress request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -5301,7 +5294,7 @@ public async System.Threading.Tasks.Task<Response6> MiningReadMinerRewardAddress headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -5363,12 +5356,12 @@ public async System.Threading.Tasks.Task<Response7> MiningReadMinerRewardPubkeyA request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -5381,7 +5374,7 @@ public async System.Threading.Tasks.Task<Response7> MiningReadMinerRewardPubkeyA headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -5448,12 +5441,12 @@ public async System.Threading.Tasks.Task MiningSubmitSolutionAsync(PowSolutions request_.Content = content_; request_.Method = new System.Net.Http.HttpMethod("POST"); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -5466,7 +5459,7 @@ public async System.Threading.Tasks.Task MiningSubmitSolutionAsync(PowSolutions headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -5539,12 +5532,12 @@ public async System.Threading.Tasks.Task<ErgoTransactionOutput> GetBoxByIdAsync( request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -5557,7 +5550,7 @@ public async System.Threading.Tasks.Task<ErgoTransactionOutput> GetBoxByIdAsync( headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -5635,12 +5628,12 @@ public async System.Threading.Tasks.Task<SerializedBox> GetBoxByIdBinaryAsync(st request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -5653,7 +5646,7 @@ public async System.Threading.Tasks.Task<SerializedBox> GetBoxByIdBinaryAsync(st headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -5731,12 +5724,12 @@ public async System.Threading.Tasks.Task<ErgoTransactionOutput> GetBoxWithPoolBy request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -5749,7 +5742,7 @@ public async System.Threading.Tasks.Task<ErgoTransactionOutput> GetBoxWithPoolBy headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -5827,12 +5820,12 @@ public async System.Threading.Tasks.Task<SerializedBox> GetBoxWithPoolByIdBinary request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -5845,7 +5838,7 @@ public async System.Threading.Tasks.Task<SerializedBox> GetBoxWithPoolByIdBinary headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -5917,12 +5910,12 @@ public async System.Threading.Tasks.Task<SerializedBox> GetBoxWithPoolByIdBinary request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -5935,7 +5928,7 @@ public async System.Threading.Tasks.Task<SerializedBox> GetBoxWithPoolByIdBinary headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -6013,12 +6006,12 @@ public async System.Threading.Tasks.Task<AddressHolder> ScriptP2SAddressAsync(So request_.Method = new System.Net.Http.HttpMethod("POST"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -6031,7 +6024,7 @@ public async System.Threading.Tasks.Task<AddressHolder> ScriptP2SAddressAsync(So headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -6109,12 +6102,12 @@ public async System.Threading.Tasks.Task<AddressHolder> ScriptP2SHAddressAsync(S request_.Method = new System.Net.Http.HttpMethod("POST"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -6127,7 +6120,7 @@ public async System.Threading.Tasks.Task<AddressHolder> ScriptP2SHAddressAsync(S headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -6205,12 +6198,12 @@ public async System.Threading.Tasks.Task<Response8> AddressToTreeAsync(string ad request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -6223,7 +6216,7 @@ public async System.Threading.Tasks.Task<Response8> AddressToTreeAsync(string ad headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -6291,12 +6284,12 @@ public async System.Threading.Tasks.Task<Response9> AddressToBytesAsync(string a request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -6309,7 +6302,7 @@ public async System.Threading.Tasks.Task<Response9> AddressToBytesAsync(string a headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -6377,12 +6370,12 @@ public async System.Threading.Tasks.Task<CryptoResult> ExecuteWithContextAsync(E request_.Method = new System.Net.Http.HttpMethod("POST"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -6395,7 +6388,7 @@ public async System.Threading.Tasks.Task<CryptoResult> ExecuteWithContextAsync(E headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -6473,12 +6466,12 @@ public async System.Threading.Tasks.Task<ScanId> RegisterScanAsync(ScanRequest b request_.Method = new System.Net.Http.HttpMethod("POST"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -6491,7 +6484,7 @@ public async System.Threading.Tasks.Task<ScanId> RegisterScanAsync(ScanRequest b headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -6569,12 +6562,12 @@ public async System.Threading.Tasks.Task<ScanId> DeregisterScanAsync(ScanId body request_.Method = new System.Net.Http.HttpMethod("POST"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -6587,7 +6580,7 @@ public async System.Threading.Tasks.Task<ScanId> DeregisterScanAsync(ScanId body headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -6659,12 +6652,12 @@ public async System.Threading.Tasks.Task<ScanId> DeregisterScanAsync(ScanId body request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -6677,7 +6670,7 @@ public async System.Threading.Tasks.Task<ScanId> DeregisterScanAsync(ScanId body headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -6758,12 +6751,12 @@ public async System.Threading.Tasks.Task<ScanId> DeregisterScanAsync(ScanId body request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -6776,7 +6769,7 @@ public async System.Threading.Tasks.Task<ScanId> DeregisterScanAsync(ScanId body headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -6844,12 +6837,12 @@ public async System.Threading.Tasks.Task<ScanIdBoxId> ScanStopTrackingAsync(Scan request_.Method = new System.Net.Http.HttpMethod("POST"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -6862,7 +6855,7 @@ public async System.Threading.Tasks.Task<ScanIdBoxId> ScanStopTrackingAsync(Scan headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -6930,12 +6923,12 @@ public async System.Threading.Tasks.Task<TransactionHintsBag> GenerateCommitment request_.Method = new System.Net.Http.HttpMethod("POST"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -6948,7 +6941,7 @@ public async System.Threading.Tasks.Task<TransactionHintsBag> GenerateCommitment headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -7026,12 +7019,12 @@ public async System.Threading.Tasks.Task<TransactionHintsBag> ExtractHintsAsync( request_.Method = new System.Net.Http.HttpMethod("POST"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -7044,7 +7037,7 @@ public async System.Threading.Tasks.Task<TransactionHintsBag> ExtractHintsAsync( headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -7122,12 +7115,12 @@ public async System.Threading.Tasks.Task<string> AddBoxAsync(ScanIdsBox body, Sy request_.Method = new System.Net.Http.HttpMethod("POST"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -7140,7 +7133,7 @@ public async System.Threading.Tasks.Task<string> AddBoxAsync(ScanIdsBox body, Sy headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -7202,12 +7195,12 @@ public async System.Threading.Tasks.Task NodeShutdownAsync(System.Threading.Canc request_.Content = new System.Net.Http.StringContent(string.Empty, System.Text.Encoding.UTF8, "application/json"); request_.Method = new System.Net.Http.HttpMethod("POST"); - PrepareRequest(client_, request_, urlBuilder_); + await PrepareRequestAsync(client_, request_, urlBuilder_).ConfigureAwait(false); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - PrepareRequest(client_, request_, url_); + await PrepareRequestAsync(client_, request_, url_).ConfigureAwait(false); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var disposeResponse_ = true; @@ -7220,7 +7213,7 @@ public async System.Threading.Tasks.Task NodeShutdownAsync(System.Threading.Canc headers_[item_.Key] = item_.Value; } - ProcessResponse(client_, response_); + await ProcessResponseAsync(client_, response_, cancellationToken).ConfigureAwait(false); var status_ = (int)response_.StatusCode; if (status_ == 200) @@ -7357,11 +7350,11 @@ private string ConvertToString(object? value, System.Globalization.CultureInfo c [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] public partial class ErgoTransactionInput { - [Newtonsoft.Json.JsonProperty("boxId", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("boxId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string BoxId { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("spendingProof", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("spendingProof", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public SpendingProof SpendingProof { get; set; } = new SpendingProof(); @@ -7383,7 +7376,7 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] public partial class ErgoTransactionDataInput { - [Newtonsoft.Json.JsonProperty("boxId", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("boxId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string BoxId { get; set; }= default!; @@ -7405,7 +7398,7 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] public partial class ErgoTransactionUnsignedInput { - [Newtonsoft.Json.JsonProperty("boxId", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("boxId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string BoxId { get; set; }= default!; @@ -7428,12 +7421,12 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] public partial class SpendingProof { - [Newtonsoft.Json.JsonProperty("proofBytes", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("proofBytes", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string ProofBytes { get; set; }= default!; /// <summary>Variables to be put into context</summary> - [Newtonsoft.Json.JsonProperty("extension", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("extension", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public System.Collections.Generic.IDictionary<string, string> Extension { get; set; } = new System.Collections.Generic.Dictionary<string, string>(); @@ -7452,11 +7445,11 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] public partial class SerializedBox { - [Newtonsoft.Json.JsonProperty("boxId", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("boxId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string BoxId { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("bytes", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("bytes", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Bytes { get; set; }= default!; @@ -7479,23 +7472,27 @@ public partial class ErgoTransactionOutput public string BoxId { get; set; }= default!; /// <summary>Amount of Ergo token</summary> - [Newtonsoft.Json.JsonProperty("value", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("value", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Range(0D, double.MaxValue)] public long Value { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("ergoTree", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("ergoTree", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string ErgoTree { get; set; }= default!; - + + [Newtonsoft.Json.JsonProperty("address", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Address { get; set; }= default!; + /// <summary>Height the output was created at</summary> - [Newtonsoft.Json.JsonProperty("creationHeight", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("creationHeight", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public int CreationHeight { get; set; }= default!; /// <summary>Assets list in the transaction</summary> [Newtonsoft.Json.JsonProperty("assets", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public System.Collections.Generic.ICollection<Asset> Assets { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("additionalRegisters", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("additionalRegisters", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public Registers AdditionalRegisters { get; set; } = new Registers(); @@ -7521,52 +7518,52 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] public partial class WalletBox { - [Newtonsoft.Json.JsonProperty("box", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("box", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public ErgoTransactionOutput Box { get; set; } = new ErgoTransactionOutput(); /// <summary>Number of confirmations, if the box is included into the blockchain</summary> - [Newtonsoft.Json.JsonProperty("confirmationsNum", Required = Newtonsoft.Json.Required.AllowNull)] + [Newtonsoft.Json.JsonProperty("confirmationsNum", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] public int? ConfirmationsNum { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("address", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("address", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Address { get; set; }= default!; /// <summary>Transaction which created the box</summary> - [Newtonsoft.Json.JsonProperty("creationTransaction", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("creationTransaction", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string CreationTransaction { get; set; }= default!; /// <summary>Transaction which created the box</summary> - [Newtonsoft.Json.JsonProperty("spendingTransaction", Required = Newtonsoft.Json.Required.AllowNull)] + [Newtonsoft.Json.JsonProperty("spendingTransaction", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string? SpendingTransaction { get; set; }= default!; /// <summary>The height the box was spent at</summary> - [Newtonsoft.Json.JsonProperty("spendingHeight", Required = Newtonsoft.Json.Required.AllowNull)] + [Newtonsoft.Json.JsonProperty("spendingHeight", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] public int? SpendingHeight { get; set; }= default!; /// <summary>The height the transaction containing the box was included in a block at</summary> - [Newtonsoft.Json.JsonProperty("inclusionHeight", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("inclusionHeight", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] public int InclusionHeight { get; set; }= default!; /// <summary>A flag signalling whether the box is created on main chain</summary> - [Newtonsoft.Json.JsonProperty("onchain", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("onchain", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public bool Onchain { get; set; }= default!; /// <summary>A flag signalling whether the box was spent</summary> - [Newtonsoft.Json.JsonProperty("spent", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("spent", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public bool Spent { get; set; }= default!; /// <summary>An index of a box in the creating transaction</summary> - [Newtonsoft.Json.JsonProperty("creationOutIndex", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("creationOutIndex", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public int CreationOutIndex { get; set; }= default!; /// <summary>Scan identifiers the box relates to</summary> - [Newtonsoft.Json.JsonProperty("scans", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("scans", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public System.Collections.Generic.ICollection<int> Scans { get; set; } = new System.Collections.ObjectModel.Collection<int>(); @@ -7590,17 +7587,17 @@ public partial class UnsignedErgoTransaction public string Id { get; set; }= default!; /// <summary>Unsigned inputs of the transaction</summary> - [Newtonsoft.Json.JsonProperty("inputs", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("inputs", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public System.Collections.Generic.ICollection<ErgoTransactionUnsignedInput> Inputs { get; set; } = new System.Collections.ObjectModel.Collection<ErgoTransactionUnsignedInput>(); /// <summary>Data inputs of the transaction</summary> - [Newtonsoft.Json.JsonProperty("dataInputs", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("dataInputs", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public System.Collections.Generic.ICollection<ErgoTransactionDataInput> DataInputs { get; set; } = new System.Collections.ObjectModel.Collection<ErgoTransactionDataInput>(); /// <summary>Outputs of the transaction</summary> - [Newtonsoft.Json.JsonProperty("outputs", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("outputs", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public System.Collections.Generic.ICollection<ErgoTransactionOutput> Outputs { get; set; } = new System.Collections.ObjectModel.Collection<ErgoTransactionOutput>(); @@ -7624,17 +7621,17 @@ public partial class ErgoTransaction public string Id { get; set; }= default!; /// <summary>Inputs of the transaction</summary> - [Newtonsoft.Json.JsonProperty("inputs", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("inputs", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public System.Collections.Generic.ICollection<ErgoTransactionInput> Inputs { get; set; } = new System.Collections.ObjectModel.Collection<ErgoTransactionInput>(); /// <summary>Data inputs of the transaction</summary> - [Newtonsoft.Json.JsonProperty("dataInputs", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("dataInputs", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public System.Collections.Generic.ICollection<ErgoTransactionDataInput> DataInputs { get; set; } = new System.Collections.ObjectModel.Collection<ErgoTransactionDataInput>(); /// <summary>Outputs of the transaction</summary> - [Newtonsoft.Json.JsonProperty("outputs", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("outputs", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public System.Collections.Generic.ICollection<ErgoTransactionOutput> Outputs { get; set; } = new System.Collections.ObjectModel.Collection<ErgoTransactionOutput>(); @@ -7662,30 +7659,30 @@ public partial class WalletTransaction public string Id { get; set; }= default!; /// <summary>Transaction inputs</summary> - [Newtonsoft.Json.JsonProperty("inputs", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("inputs", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public System.Collections.Generic.ICollection<ErgoTransactionInput> Inputs { get; set; } = new System.Collections.ObjectModel.Collection<ErgoTransactionInput>(); /// <summary>Transaction data inputs</summary> - [Newtonsoft.Json.JsonProperty("dataInputs", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("dataInputs", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public System.Collections.Generic.ICollection<ErgoTransactionDataInput> DataInputs { get; set; } = new System.Collections.ObjectModel.Collection<ErgoTransactionDataInput>(); /// <summary>Transaction outputs</summary> - [Newtonsoft.Json.JsonProperty("outputs", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("outputs", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public System.Collections.Generic.ICollection<ErgoTransactionOutput> Outputs { get; set; } = new System.Collections.ObjectModel.Collection<ErgoTransactionOutput>(); /// <summary>Height of a block the transaction was included in</summary> - [Newtonsoft.Json.JsonProperty("inclusionHeight", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("inclusionHeight", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public int InclusionHeight { get; set; }= default!; /// <summary>Number of transaction confirmations</summary> - [Newtonsoft.Json.JsonProperty("numConfirmations", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("numConfirmations", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public int NumConfirmations { get; set; }= default!; /// <summary>Scan identifiers the transaction relates to</summary> - [Newtonsoft.Json.JsonProperty("scans", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("scans", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public System.Collections.Generic.ICollection<int> Scans { get; set; } = new System.Collections.ObjectModel.Collection<int>(); @@ -7710,27 +7707,27 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti public partial class DhtSecret { /// <summary>Hex-encoded big-endian 256-bits secret exponent</summary> - [Newtonsoft.Json.JsonProperty("secret", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("secret", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Secret { get; set; }= default!; /// <summary>Hex-encoded "g" generator for the Diffie-Hellman tuple (secp256k1 curve point)</summary> - [Newtonsoft.Json.JsonProperty("g", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("g", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string G { get; set; }= default!; /// <summary>Hex-encoded "h" generator for the Diffie-Hellman tuple (secp256k1 curve point)</summary> - [Newtonsoft.Json.JsonProperty("h", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("h", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string H { get; set; }= default!; /// <summary>Hex-encoded "u" group element of the Diffie-Hellman tuple (secp256k1 curve point)</summary> - [Newtonsoft.Json.JsonProperty("u", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("u", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string U { get; set; }= default!; /// <summary>Hex-encoded "v" group element of the Diffie-Hellman tuple (secp256k1 curve point)</summary> - [Newtonsoft.Json.JsonProperty("v", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("v", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string V { get; set; }= default!; @@ -7751,7 +7748,7 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti public partial class TransactionSigningRequest { /// <summary>Unsigned transaction to sign</summary> - [Newtonsoft.Json.JsonProperty("tx", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("tx", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public UnsignedErgoTransaction Tx { get; set; } = new UnsignedErgoTransaction(); @@ -7768,7 +7765,7 @@ public partial class TransactionSigningRequest public TransactionHintsBag Hints { get; set; }= default!; /// <summary>Secrets used for signing</summary> - [Newtonsoft.Json.JsonProperty("secrets", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("secrets", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public Secrets Secrets { get; set; } = new Secrets(); @@ -7788,7 +7785,7 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] public partial class AddressHolder { - [Newtonsoft.Json.JsonProperty("address", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("address", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Address { get; set; }= default!; @@ -7809,12 +7806,12 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti public partial class BoxesRequestHolder { /// <summary>Target assets</summary> - [Newtonsoft.Json.JsonProperty("targetAssets", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("targetAssets", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public System.Collections.Generic.ICollection<System.Collections.Generic.ICollection<object>> TargetAssets { get; set; } = new List<ICollection<object>>(); /// <summary>Target balance</summary> - [Newtonsoft.Json.JsonProperty("targetBalance", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("targetBalance", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public long TargetBalance { get; set; }= default!; private System.Collections.Generic.IDictionary<string, object> _additionalProperties = new System.Collections.Generic.Dictionary<string, object>(); @@ -7834,7 +7831,7 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti public partial class RequestsHolder { /// <summary>Sequence of transaction requests</summary> - [Newtonsoft.Json.JsonProperty("requests", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("requests", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public System.Collections.Generic.ICollection<Requests> Requests { get; set; } = new System.Collections.ObjectModel.Collection<Requests>(); @@ -7866,7 +7863,7 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti public partial class SourceHolder { /// <summary>Sigma source to be compiled</summary> - [Newtonsoft.Json.JsonProperty("source", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("source", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Source { get; set; }= default!; @@ -7885,19 +7882,19 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] public partial class ErgoLikeTransaction { - [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Id { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("inputs", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("inputs", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public System.Collections.Generic.ICollection<ErgoTransactionInput> Inputs { get; set; } = new System.Collections.ObjectModel.Collection<ErgoTransactionInput>(); - [Newtonsoft.Json.JsonProperty("dataInputs", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("dataInputs", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public System.Collections.Generic.ICollection<ErgoTransactionDataInput> DataInputs { get; set; } = new System.Collections.ObjectModel.Collection<ErgoTransactionDataInput>(); - [Newtonsoft.Json.JsonProperty("outputs", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("outputs", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public System.Collections.Generic.ICollection<ErgoTransactionOutput> Outputs { get; set; } = new System.Collections.ObjectModel.Collection<ErgoTransactionOutput>(); @@ -7920,34 +7917,34 @@ public partial class SigmaHeader [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string Id { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("timestamp", Required = Newtonsoft.Json.Required.Always)] - public int Timestamp { get; set; }= default!; + [Newtonsoft.Json.JsonProperty("timestamp", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long Timestamp { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("version", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("version", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public int Version { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("adProofsRoot", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("adProofsRoot", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string AdProofsRoot { get; set; }= default!; [Newtonsoft.Json.JsonProperty("adProofsId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string AdProofsId { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("stateRoot", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("stateRoot", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public AvlTreeData StateRoot { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("transactionsRoot", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("transactionsRoot", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string TransactionsRoot { get; set; }= default!; [Newtonsoft.Json.JsonProperty("transactionsId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string TransactionsId { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("nBits", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("nBits", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Range(0D, double.MaxValue)] public long NBits { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("extensionHash", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("extensionHash", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string ExtensionHash { get; set; }= default!; @@ -7957,7 +7954,7 @@ public partial class SigmaHeader [Newtonsoft.Json.JsonProperty("extensionId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string ExtensionId { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("height", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("height", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] public int Height { get; set; }= default!; @@ -7965,14 +7962,14 @@ public partial class SigmaHeader [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] public int Size { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("parentId", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("parentId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string ParentId { get; set; }= default!; [Newtonsoft.Json.JsonProperty("powSolutions", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public PowSolutions PowSolutions { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("votes", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("votes", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Votes { get; set; }= default!; @@ -8004,25 +8001,25 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] public partial class PreHeader { - [Newtonsoft.Json.JsonProperty("timestamp", Required = Newtonsoft.Json.Required.Always)] - public int Timestamp { get; set; }= default!; + [Newtonsoft.Json.JsonProperty("timestamp", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long Timestamp { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("version", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("version", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public int Version { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("nBits", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("nBits", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Range(0D, double.MaxValue)] public long NBits { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("height", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("height", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] public int Height { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("parentId", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("parentId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string ParentId { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("votes", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("votes", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Votes { get; set; }= default!; @@ -8044,7 +8041,7 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] public partial class AvlTreeData { - [Newtonsoft.Json.JsonProperty("digest", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("digest", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Digest { get; set; }= default!; @@ -8073,54 +8070,54 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti public partial class ErgoLikeContext { /// <summary>state root before current block application</summary> - [Newtonsoft.Json.JsonProperty("lastBlockUtxoRoot", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("lastBlockUtxoRoot", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public AvlTreeData LastBlockUtxoRoot { get; set; }= default!; /// <summary>fixed number of last block headers in descending order (first header is the newest one)</summary> - [Newtonsoft.Json.JsonProperty("headers", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("headers", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public System.Collections.Generic.ICollection<SigmaHeader> Headers { get; set; } = new System.Collections.ObjectModel.Collection<SigmaHeader>(); /// <summary>fields of block header with the current `spendingTransaction`, that can be predicted by a miner before its formation</summary> - [Newtonsoft.Json.JsonProperty("preHeader", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("preHeader", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public PreHeader PreHeader { get; set; } = new PreHeader(); /// <summary>boxes, that corresponds to id's of `spendingTransaction.dataInputs`</summary> - [Newtonsoft.Json.JsonProperty("dataBoxes", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("dataBoxes", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public System.Collections.Generic.ICollection<ErgoTransactionOutput> DataBoxes { get; set; } = new System.Collections.ObjectModel.Collection<ErgoTransactionOutput>(); /// <summary>boxes, that corresponds to id's of `spendingTransaction.inputs`</summary> - [Newtonsoft.Json.JsonProperty("boxesToSpend", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("boxesToSpend", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public System.Collections.Generic.ICollection<ErgoTransactionOutput> BoxesToSpend { get; set; } = new System.Collections.ObjectModel.Collection<ErgoTransactionOutput>(); /// <summary>transaction that contains `self` box</summary> - [Newtonsoft.Json.JsonProperty("spendingTransaction", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("spendingTransaction", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public ErgoLikeTransaction SpendingTransaction { get; set; } = new ErgoLikeTransaction(); /// <summary>index of the box in `boxesToSpend` that contains the script we're evaluating</summary> - [Newtonsoft.Json.JsonProperty("selfIndex", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("selfIndex", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public long SelfIndex { get; set; }= default!; /// <summary>prover-defined key-value pairs, that may be used inside a script</summary> - [Newtonsoft.Json.JsonProperty("extension", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("extension", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public object Extension { get; set; } = new object(); /// <summary>validation parameters passed to Interpreter.verify to detect soft-fork conditions</summary> - [Newtonsoft.Json.JsonProperty("validationSettings", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("validationSettings", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string ValidationSettings { get; set; }= default!; /// <summary>hard limit on accumulated execution cost, if exceeded lead to CostLimitException to be thrown</summary> - [Newtonsoft.Json.JsonProperty("costLimit", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("costLimit", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public long CostLimit { get; set; }= default!; /// <summary>initial value of execution cost already accumulated before Interpreter.verify is called</summary> - [Newtonsoft.Json.JsonProperty("initCost", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("initCost", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public long InitCost { get; set; }= default!; private System.Collections.Generic.IDictionary<string, object> _additionalProperties = new System.Collections.Generic.Dictionary<string, object>(); @@ -8139,16 +8136,16 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti public partial class ExecuteScript { /// <summary>Sigma script to be executed</summary> - [Newtonsoft.Json.JsonProperty("script", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("script", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Script { get; set; }= default!; /// <summary>Environment for compiler</summary> - [Newtonsoft.Json.JsonProperty("namedConstants", Required = Newtonsoft.Json.Required.AllowNull)] + [Newtonsoft.Json.JsonProperty("namedConstants", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public object? NamedConstants { get; set; }= default!; /// <summary>Interpreter context</summary> - [Newtonsoft.Json.JsonProperty("context", Required = Newtonsoft.Json.Required.AllowNull)] + [Newtonsoft.Json.JsonProperty("context", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public ErgoLikeContext? Context { get; set; }= default!; private System.Collections.Generic.IDictionary<string, object> _additionalProperties = new System.Collections.Generic.Dictionary<string, object>(); @@ -8168,7 +8165,7 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti public partial class SigmaBoolean { /// <summary>Sigma opCode</summary> - [Newtonsoft.Json.JsonProperty("op", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("op", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public int Op { get; set; }= default!; [Newtonsoft.Json.JsonProperty("h", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] @@ -8257,11 +8254,11 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti public partial class CryptoResult { /// <summary>value of SigmaProp type which represents a statement verifiable via sigma protocol</summary> - [Newtonsoft.Json.JsonProperty("value", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("value", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public SigmaBoolean Value { get; set; }= default!; /// <summary>Estimated cost of contract execution</summary> - [Newtonsoft.Json.JsonProperty("cost", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("cost", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public long Cost { get; set; }= default!; private System.Collections.Generic.IDictionary<string, object> _additionalProperties = new System.Collections.Generic.Dictionary<string, object>(); @@ -8279,7 +8276,7 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] public partial class ScanningPredicate { - [Newtonsoft.Json.JsonProperty("predicate", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("predicate", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Predicate { get; set; }= default!; @@ -8457,10 +8454,10 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] public partial class ScanIdBoxId { - [Newtonsoft.Json.JsonProperty("scanId", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("scanId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public int ScanId { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("boxId", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("boxId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string BoxId { get; set; }= default!; @@ -8480,11 +8477,11 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] public partial class ScanIdsBox { - [Newtonsoft.Json.JsonProperty("scanIds", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("scanIds", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public System.Collections.Generic.ICollection<int> ScanIds { get; set; } = new System.Collections.ObjectModel.Collection<int>(); - [Newtonsoft.Json.JsonProperty("box", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("box", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public ErgoTransactionOutput Box { get; set; } = new ErgoTransactionOutput(); @@ -8505,12 +8502,12 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti public partial class DlogCommitment { /// <summary>Hex-encoded big-endian 256-bits secret exponent</summary> - [Newtonsoft.Json.JsonProperty("r", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("r", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string R { get; set; }= default!; /// <summary>Hex-encoded "g" generator for the Diffie-Hellman tuple (secp256k1 curve point)</summary> - [Newtonsoft.Json.JsonProperty("a", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("a", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string A { get; set; }= default!; @@ -8531,17 +8528,17 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti public partial class HintExtractionRequest { /// <summary>Transaction to extract prover hints from</summary> - [Newtonsoft.Json.JsonProperty("tx", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("tx", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public ErgoTransaction Tx { get; set; } = new ErgoTransaction(); /// <summary>Real signers of the transaction</summary> - [Newtonsoft.Json.JsonProperty("real", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("real", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public System.Collections.Generic.ICollection<SigmaBoolean> Real { get; set; } = new System.Collections.ObjectModel.Collection<SigmaBoolean>(); /// <summary>Simulated signers of the transaction</summary> - [Newtonsoft.Json.JsonProperty("simulated", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("simulated", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public System.Collections.Generic.ICollection<SigmaBoolean> Simulated { get; set; } = new System.Collections.ObjectModel.Collection<SigmaBoolean>(); @@ -8569,15 +8566,15 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] public partial class Commitment { - [Newtonsoft.Json.JsonProperty("hint", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("hint", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] public CommitmentHint Hint { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("pubkey", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("pubkey", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public SigmaBoolean Pubkey { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("position", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("position", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Position { get; set; }= default!; @@ -8586,7 +8583,7 @@ public partial class Commitment public CommitmentType Type { get; set; }= default!; /// <summary>a group element of the commitment</summary> - [Newtonsoft.Json.JsonProperty("a", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("a", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string A { get; set; }= default!; @@ -8609,23 +8606,23 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] public partial class SecretProven { - [Newtonsoft.Json.JsonProperty("hint", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("hint", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] public SecretProvenHint Hint { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("challenge", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("challenge", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Challenge { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("pubkey", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("pubkey", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public SigmaBoolean Pubkey { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("proof", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("proof", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Proof { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("position", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("position", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Position { get; set; }= default!; @@ -8677,7 +8674,7 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti public partial class GenerateCommitmentsRequest { /// <summary>Unsigned transaction to sign</summary> - [Newtonsoft.Json.JsonProperty("tx", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("tx", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public UnsignedErgoTransaction Tx { get; set; } = new UnsignedErgoTransaction(); @@ -8709,12 +8706,12 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] public partial class PaymentRequest { - [Newtonsoft.Json.JsonProperty("address", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("address", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Address { get; set; }= default!; /// <summary>Payment amount</summary> - [Newtonsoft.Json.JsonProperty("value", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("value", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public long Value { get; set; }= default!; /// <summary>Assets list in the transaction</summary> @@ -8749,21 +8746,21 @@ public partial class AssetIssueRequest public int ErgValue { get; set; }= default!; /// <summary>Supply amount</summary> - [Newtonsoft.Json.JsonProperty("amount", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("amount", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public long Amount { get; set; }= default!; /// <summary>Assets name</summary> - [Newtonsoft.Json.JsonProperty("name", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("name", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Name { get; set; }= default!; /// <summary>Assets description</summary> - [Newtonsoft.Json.JsonProperty("description", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("description", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Description { get; set; }= default!; /// <summary>Number of decimal places</summary> - [Newtonsoft.Json.JsonProperty("decimals", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("decimals", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public int Decimals { get; set; }= default!; /// <summary>Optional, possible values for registers R7...R9</summary> @@ -8786,24 +8783,24 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] public partial class FullBlock { - [Newtonsoft.Json.JsonProperty("header", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("header", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public BlockHeader Header { get; set; } = new BlockHeader(); - [Newtonsoft.Json.JsonProperty("blockTransactions", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("blockTransactions", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public BlockTransactions BlockTransactions { get; set; } = new BlockTransactions(); - [Newtonsoft.Json.JsonProperty("adProofs", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("adProofs", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public BlockADProofs AdProofs { get; set; } = new BlockADProofs(); - [Newtonsoft.Json.JsonProperty("extension", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("extension", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public Extension Extension { get; set; } = new Extension(); /// <summary>Size in bytes</summary> - [Newtonsoft.Json.JsonProperty("size", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("size", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public int Size { get; set; }= default!; private System.Collections.Generic.IDictionary<string, object> _additionalProperties = new System.Collections.Generic.Dictionary<string, object>(); @@ -8823,19 +8820,19 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti public partial class PowSolutions { /// <summary>Base16-encoded public key</summary> - [Newtonsoft.Json.JsonProperty("pk")] + [Newtonsoft.Json.JsonProperty("pk", NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Pk { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("w")] + [Newtonsoft.Json.JsonProperty("w", NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string W { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("n", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("n", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string N { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("d")] + [Newtonsoft.Json.JsonProperty("d", NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public int D { get; set; }= default!; private System.Collections.Generic.IDictionary<string, object> _additionalProperties = new System.Collections.Generic.Dictionary<string, object>(); @@ -8853,49 +8850,49 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] public partial class BlockHeaderWithoutPow { - [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Id { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("timestamp", Required = Newtonsoft.Json.Required.Always)] - public int Timestamp { get; set; }= default!; + [Newtonsoft.Json.JsonProperty("timestamp", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long Timestamp { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("version", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("version", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public int Version { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("adProofsRoot", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("adProofsRoot", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string AdProofsRoot { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("stateRoot", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("stateRoot", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string StateRoot { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("transactionsRoot", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("transactionsRoot", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string TransactionsRoot { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("nBits", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("nBits", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Range(0D, double.MaxValue)] public long NBits { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("extensionHash", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("extensionHash", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string ExtensionHash { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("height", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("height", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] public int Height { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("difficulty", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("difficulty", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] public int Difficulty { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("parentId", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("parentId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string ParentId { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("votes", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("votes", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Votes { get; set; }= default!; @@ -8927,12 +8924,12 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] public partial class PopowHeader { - [Newtonsoft.Json.JsonProperty("header", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("header", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public BlockHeader Header { get; set; } = new BlockHeader(); /// <summary>Array of header interlinks</summary> - [Newtonsoft.Json.JsonProperty("interlinks", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("interlinks", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public System.Collections.Generic.ICollection<string> Interlinks { get; set; } = new System.Collections.ObjectModel.Collection<string>(); @@ -8952,24 +8949,24 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti public partial class NipopowProof { /// <summary>security parameter (min ÎĽ-level superchain length)</summary> - [Newtonsoft.Json.JsonProperty("m", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("m", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public double M { get; set; }= default!; /// <summary>security parameter (min suffix length, &gt;= 1)</summary> - [Newtonsoft.Json.JsonProperty("k", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("k", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public double K { get; set; }= default!; /// <summary>proof prefix headers</summary> - [Newtonsoft.Json.JsonProperty("prefix", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("prefix", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public System.Collections.Generic.ICollection<PopowHeader> Prefix { get; set; } = new System.Collections.ObjectModel.Collection<PopowHeader>(); - [Newtonsoft.Json.JsonProperty("suffixHead", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("suffixHead", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public PopowHeader SuffixHead { get; set; } = new PopowHeader(); /// <summary>tail of the proof suffix headers</summary> - [Newtonsoft.Json.JsonProperty("suffixTail", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("suffixTail", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public System.Collections.Generic.ICollection<BlockHeader> SuffixTail { get; set; } = new System.Collections.ObjectModel.Collection<BlockHeader>(); @@ -8988,53 +8985,53 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] public partial class BlockHeader { - [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Id { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("timestamp", Required = Newtonsoft.Json.Required.Always)] - public int Timestamp { get; set; }= default!; + [Newtonsoft.Json.JsonProperty("timestamp", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long Timestamp { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("version", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("version", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public int Version { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("adProofsRoot", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("adProofsRoot", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string AdProofsRoot { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("stateRoot", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("stateRoot", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string StateRoot { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("transactionsRoot", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("transactionsRoot", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string TransactionsRoot { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("nBits", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("nBits", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Range(0D, double.MaxValue)] public long NBits { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("extensionHash", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("extensionHash", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string ExtensionHash { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("powSolutions", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("powSolutions", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public PowSolutions PowSolutions { get; set; } = new PowSolutions(); - [Newtonsoft.Json.JsonProperty("height", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("height", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] public int Height { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("difficulty", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("difficulty", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] - public int Difficulty { get; set; }= default!; + public string Difficulty { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("parentId", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("parentId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string ParentId { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("votes", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("votes", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Votes { get; set; }= default!; @@ -9066,16 +9063,16 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] public partial class BlockTransactions { - [Newtonsoft.Json.JsonProperty("headerId", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("headerId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string HeaderId { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("transactions", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("transactions", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public Transactions Transactions { get; set; } = new Transactions(); /// <summary>Size in bytes</summary> - [Newtonsoft.Json.JsonProperty("size", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("size", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public int Size { get; set; }= default!; private System.Collections.Generic.IDictionary<string, object> _additionalProperties = new System.Collections.Generic.Dictionary<string, object>(); @@ -9093,20 +9090,20 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] public partial class BlockADProofs { - [Newtonsoft.Json.JsonProperty("headerId", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("headerId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string HeaderId { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("proofBytes", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("proofBytes", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string ProofBytes { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("digest", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("digest", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Digest { get; set; }= default!; /// <summary>Size in bytes</summary> - [Newtonsoft.Json.JsonProperty("size", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("size", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public int Size { get; set; }= default!; private System.Collections.Generic.IDictionary<string, object> _additionalProperties = new System.Collections.Generic.Dictionary<string, object>(); @@ -9124,16 +9121,16 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] public partial class Extension { - [Newtonsoft.Json.JsonProperty("headerId", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("headerId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string HeaderId { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("digest", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("digest", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Digest { get; set; }= default!; /// <summary>List of key-value records</summary> - [Newtonsoft.Json.JsonProperty("fields", Required = Newtonsoft.Json.Required.AllowNull)] + [Newtonsoft.Json.JsonProperty("fields", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public System.Collections.Generic.ICollection<KeyValueItem>? Fields { get; set; }= default!; private System.Collections.Generic.IDictionary<string, object> _additionalProperties = new System.Collections.Generic.Dictionary<string, object>(); @@ -9161,12 +9158,12 @@ public partial class CandidateBlock [Newtonsoft.Json.JsonProperty("version", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public int Version { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("extensionHash", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("extensionHash", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string ExtensionHash { get; set; }= default!; [Newtonsoft.Json.JsonProperty("timestamp", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public int Timestamp { get; set; }= default!; + public long Timestamp { get; set; }= default!; [Newtonsoft.Json.JsonProperty("stateRoot", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string StateRoot { get; set; }= default!; @@ -9178,7 +9175,7 @@ public partial class CandidateBlock [Newtonsoft.Json.JsonProperty("adProofBytes", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string AdProofBytes { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("parentId", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("parentId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string ParentId { get; set; }= default!; @@ -9208,11 +9205,11 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti public partial class MerkleProof { /// <summary>Base16-encoded Merkle tree leaf bytes</summary> - [Newtonsoft.Json.JsonProperty("leaf", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("leaf", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Leaf { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("levels", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("levels", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public System.Collections.Generic.ICollection<System.Collections.Generic.ICollection<object>> Levels { get; set; } = new List<ICollection<object>>(); @@ -9233,12 +9230,12 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti public partial class ProofOfUpcomingTransactions { /// <summary>Base16-encoded serialized header without Proof-of-Work</summary> - [Newtonsoft.Json.JsonProperty("msgPreimage", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("msgPreimage", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string MsgPreimage { get; set; }= default!; /// <summary>Merkle proofs of transactions included into blocks (not necessarily all the block transactions)</summary> - [Newtonsoft.Json.JsonProperty("txProofs", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("txProofs", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public System.Collections.Generic.ICollection<MerkleProof> TxProofs { get; set; } = new System.Collections.ObjectModel.Collection<MerkleProof>(); @@ -9259,20 +9256,20 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti public partial class WorkMessage { /// <summary>Base16-encoded block header bytes without PoW solution</summary> - [Newtonsoft.Json.JsonProperty("msg", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("msg", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Msg { get; set; }= default!; - + + /// <summary>Work target value</summary> + [Newtonsoft.Json.JsonProperty("b", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string B { get; set; }= default!; + /// <summary>Block height</summary> [Newtonsoft.Json.JsonProperty("h", Required = Newtonsoft.Json.Required.Always)] - public ulong Height { get; set; }= default!; + public uint Height { get; set; }= default!; - /// <summary>Work target value</summary> - [Newtonsoft.Json.JsonProperty("b", Required = Newtonsoft.Json.Required.Always)] - public string B { get; set; }= default!; - /// <summary>Base16-encoded miner public key</summary> - [Newtonsoft.Json.JsonProperty("pk", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("pk", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Pk { get; set; }= default!; @@ -9294,7 +9291,7 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] public partial class Peer { - [Newtonsoft.Json.JsonProperty("address", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("address", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Address { get; set; }= default!; @@ -9326,7 +9323,7 @@ public partial class PeersStatus [Newtonsoft.Json.JsonProperty("lastIncomingMessage", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public int LastIncomingMessage { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("currentNetworkTime", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("currentNetworkTime", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public int CurrentNetworkTime { get; set; }= default!; private System.Collections.Generic.IDictionary<string, object> _additionalProperties = new System.Collections.Generic.Dictionary<string, object>(); @@ -9344,89 +9341,89 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] public partial class NodeInfo { - [Newtonsoft.Json.JsonProperty("name", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("name", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Name { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("appVersion", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("appVersion", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string AppVersion { get; set; }= default!; /// <summary>Can be 'null' if state is empty (no full block is applied since node launch)</summary> - [Newtonsoft.Json.JsonProperty("fullHeight", Required = Newtonsoft.Json.Required.AllowNull)] + [Newtonsoft.Json.JsonProperty("fullHeight", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] public int? FullHeight { get; set; }= default!; /// <summary>Can be 'null' if state is empty (no header applied since node launch)</summary> - [Newtonsoft.Json.JsonProperty("headersHeight", Required = Newtonsoft.Json.Required.AllowNull)] + [Newtonsoft.Json.JsonProperty("headersHeight", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] public int? HeadersHeight { get; set; }= default!; /// <summary>Can be 'null' if no full block is applied since node launch</summary> - [Newtonsoft.Json.JsonProperty("bestFullHeaderId", Required = Newtonsoft.Json.Required.AllowNull)] + [Newtonsoft.Json.JsonProperty("bestFullHeaderId", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string? BestFullHeaderId { get; set; }= default!; /// <summary>Can be 'null' if no full block is applied since node launch</summary> - [Newtonsoft.Json.JsonProperty("previousFullHeaderId", Required = Newtonsoft.Json.Required.AllowNull)] + [Newtonsoft.Json.JsonProperty("previousFullHeaderId", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string? PreviousFullHeaderId { get; set; }= default!; /// <summary>Can be 'null' if no header applied since node launch</summary> - [Newtonsoft.Json.JsonProperty("bestHeaderId", Required = Newtonsoft.Json.Required.AllowNull)] + [Newtonsoft.Json.JsonProperty("bestHeaderId", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string? BestHeaderId { get; set; }= default!; /// <summary>Can be 'null' if state is empty (no full block is applied since node launch)</summary> - [Newtonsoft.Json.JsonProperty("stateRoot", Required = Newtonsoft.Json.Required.AllowNull)] + [Newtonsoft.Json.JsonProperty("stateRoot", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string? StateRoot { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("stateType", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("stateType", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] public NodeInfoStateType StateType { get; set; }= default!; /// <summary>Can be 'null' if no full block is applied since node launch</summary> - [Newtonsoft.Json.JsonProperty("stateVersion", Required = Newtonsoft.Json.Required.AllowNull)] + [Newtonsoft.Json.JsonProperty("stateVersion", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string? StateVersion { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("isMining", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("isMining", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public bool IsMining { get; set; }= default!; /// <summary>Number of connected peers</summary> - [Newtonsoft.Json.JsonProperty("peersCount", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("peersCount", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] public int PeersCount { get; set; }= default!; /// <summary>Current unconfirmed transactions count</summary> - [Newtonsoft.Json.JsonProperty("unconfirmedCount", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("unconfirmedCount", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Range(0, 10000)] public int UnconfirmedCount { get; set; }= default!; /// <summary>Difficulty on current bestFullHeaderId. Can be 'null' if no full block is applied since node launch</summary> - [Newtonsoft.Json.JsonProperty("difficulty", Required = Newtonsoft.Json.Required.AllowNull)] + [Newtonsoft.Json.JsonProperty("difficulty", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] public JToken Difficulty { get; set; }= default!; /// <summary>Current internal node time</summary> - [Newtonsoft.Json.JsonProperty("currentTime", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("currentTime", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public long CurrentTime { get; set; }= default!; /// <summary>Time when the node was started</summary> - [Newtonsoft.Json.JsonProperty("launchTime", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("launchTime", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public long LaunchTime { get; set; }= default!; /// <summary>Can be 'null' if no headers is applied since node launch</summary> - [Newtonsoft.Json.JsonProperty("headersScore", Required = Newtonsoft.Json.Required.AllowNull)] + [Newtonsoft.Json.JsonProperty("headersScore", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string HeadersScore { get; set; }= default!; /// <summary>Can be 'null' if no full block is applied since node launch</summary> - [Newtonsoft.Json.JsonProperty("fullBlocksScore", Required = Newtonsoft.Json.Required.AllowNull)] + [Newtonsoft.Json.JsonProperty("fullBlocksScore", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string FullBlocksScore { get; set; }= default!; /// <summary>Can be 'null' if genesis blocks is not produced yet</summary> - [Newtonsoft.Json.JsonProperty("genesisBlockId", Required = Newtonsoft.Json.Required.AllowNull)] + [Newtonsoft.Json.JsonProperty("genesisBlockId", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string? GenesisBlockId { get; set; }= default!; /// <summary>current parameters</summary> - [Newtonsoft.Json.JsonProperty("parameters", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("parameters", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public Parameters Parameters { get; set; } = new Parameters(); @@ -9446,50 +9443,50 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti public partial class Parameters { /// <summary>Height when current parameters were considered(not actual height). Can be '0' if state is empty</summary> - [Newtonsoft.Json.JsonProperty("height", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("height", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] public int Height { get; set; }= default!; /// <summary>Storage fee coefficient (per byte per storage period ~4 years)</summary> - [Newtonsoft.Json.JsonProperty("storageFeeFactor", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("storageFeeFactor", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] public int StorageFeeFactor { get; set; }= default!; /// <summary>Minimum value per byte of an output</summary> - [Newtonsoft.Json.JsonProperty("minValuePerByte", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("minValuePerByte", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] public int MinValuePerByte { get; set; }= default!; /// <summary>Maximum block size (in bytes)</summary> - [Newtonsoft.Json.JsonProperty("maxBlockSize", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("maxBlockSize", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] public int MaxBlockSize { get; set; }= default!; /// <summary>Maximum cumulative computational cost of input scripts in block transactions</summary> - [Newtonsoft.Json.JsonProperty("maxBlockCost", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("maxBlockCost", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] public int MaxBlockCost { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("blockVersion", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("blockVersion", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public int BlockVersion { get; set; }= default!; /// <summary>Validation cost of a single token</summary> - [Newtonsoft.Json.JsonProperty("tokenAccessCost", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("tokenAccessCost", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] public int TokenAccessCost { get; set; }= default!; /// <summary>Validation cost per one transaction input</summary> - [Newtonsoft.Json.JsonProperty("inputCost", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("inputCost", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] public int InputCost { get; set; }= default!; /// <summary>Validation cost per one data input</summary> - [Newtonsoft.Json.JsonProperty("dataInputCost", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("dataInputCost", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] public int DataInputCost { get; set; }= default!; /// <summary>Validation cost per one transaction output</summary> - [Newtonsoft.Json.JsonProperty("outputCost", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("outputCost", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue)] public int OutputCost { get; set; }= default!; @@ -9545,12 +9542,12 @@ public partial class FeeHistogram : System.Collections.ObjectModel.Collection<Fe [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] public partial class Asset { - [Newtonsoft.Json.JsonProperty("tokenId", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("tokenId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string TokenId { get; set; }= default!; /// <summary>Amount of the token</summary> - [Newtonsoft.Json.JsonProperty("amount", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("amount", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public long Amount { get; set; }= default!; private System.Collections.Generic.IDictionary<string, object> _additionalProperties = new System.Collections.Generic.Dictionary<string, object>(); @@ -9601,10 +9598,10 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] public partial class BalancesSnapshot { - [Newtonsoft.Json.JsonProperty("height", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("height", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public int Height { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("balance", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("balance", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public long Balance { get; set; }= default!; [Newtonsoft.Json.JsonProperty("assets", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] @@ -9626,11 +9623,11 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] public partial class AddressValidity { - [Newtonsoft.Json.JsonProperty("address", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("address", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Address { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("isValid", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("isValid", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public bool IsValid { get; set; }= default!; [Newtonsoft.Json.JsonProperty("error", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] @@ -9652,16 +9649,16 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti public partial class ApiError { /// <summary>Error code</summary> - [Newtonsoft.Json.JsonProperty("error", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("error", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public int Error { get; set; }= default!; /// <summary>String error code</summary> - [Newtonsoft.Json.JsonProperty("reason", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("reason", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Reason { get; set; }= default!; /// <summary>Detailed error description</summary> - [Newtonsoft.Json.JsonProperty("detail", Required = Newtonsoft.Json.Required.AllowNull)] + [Newtonsoft.Json.JsonProperty("detail", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string? Detail { get; set; }= default!; private System.Collections.Generic.IDictionary<string, object> _additionalProperties = new System.Collections.Generic.Dictionary<string, object>(); @@ -9680,7 +9677,7 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti public partial class Body { /// <summary>Password to encrypt wallet file with</summary> - [Newtonsoft.Json.JsonProperty("pass", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("pass", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Pass { get; set; }= default!; @@ -9704,12 +9701,12 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti public partial class Body2 { /// <summary>Password to encrypt wallet file with</summary> - [Newtonsoft.Json.JsonProperty("pass", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("pass", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Pass { get; set; }= default!; /// <summary>Mnemonic seed</summary> - [Newtonsoft.Json.JsonProperty("mnemonic", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("mnemonic", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Mnemonic { get; set; }= default!; @@ -9733,7 +9730,7 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti public partial class Body3 { /// <summary>Mnemonic seed (optional)</summary> - [Newtonsoft.Json.JsonProperty("mnemonic", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("mnemonic", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Mnemonic { get; set; }= default!; @@ -9757,7 +9754,7 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti public partial class Body4 { /// <summary>Password to decrypt wallet file with</summary> - [Newtonsoft.Json.JsonProperty("pass", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("pass", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Pass { get; set; }= default!; @@ -9777,7 +9774,7 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti public partial class Body5 { /// <summary>Pay2PubKey address</summary> - [Newtonsoft.Json.JsonProperty("address", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("address", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Address { get; set; }= default!; @@ -9797,7 +9794,7 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti public partial class Body6 { /// <summary>Derivation path for a new secret to derive</summary> - [Newtonsoft.Json.JsonProperty("derivationPath", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("derivationPath", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string DerivationPath { get; set; }= default!; @@ -9817,7 +9814,7 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti public partial class Response { /// <summary>Mnemonic seed phrase</summary> - [Newtonsoft.Json.JsonProperty("mnemonic", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("mnemonic", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Mnemonic { get; set; }= default!; @@ -9837,7 +9834,7 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti public partial class Response2 { /// <summary>true if passphrase matches wallet, false otherwise</summary> - [Newtonsoft.Json.JsonProperty("matched", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("matched", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public bool Matched { get; set; }= default!; private System.Collections.Generic.IDictionary<string, object> _additionalProperties = new System.Collections.Generic.Dictionary<string, object>(); @@ -9856,20 +9853,20 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti public partial class Response3 { /// <summary>true if wallet is initialized, false otherwise</summary> - [Newtonsoft.Json.JsonProperty("isInitialized", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("isInitialized", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public bool IsInitialized { get; set; }= default!; /// <summary>true if wallet is unlocked, false otherwise</summary> - [Newtonsoft.Json.JsonProperty("isUnlocked", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("isUnlocked", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public bool IsUnlocked { get; set; }= default!; /// <summary>address to send change to. Empty when wallet is not initialized or locked. By default change address correponds to root key address, could be set via /wallet/updateChangeAddress method.</summary> - [Newtonsoft.Json.JsonProperty("changeAddress", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("changeAddress", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string ChangeAddress { get; set; }= default!; /// <summary>last scanned height for the wallet (and external scans)</summary> - [Newtonsoft.Json.JsonProperty("walletHeight", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("walletHeight", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public int WalletHeight { get; set; }= default!; private System.Collections.Generic.IDictionary<string, object> _additionalProperties = new System.Collections.Generic.Dictionary<string, object>(); @@ -9887,7 +9884,7 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] public partial class Response4 { - [Newtonsoft.Json.JsonProperty("address", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("address", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Address { get; set; }= default!; @@ -9907,11 +9904,11 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti public partial class Response5 { /// <summary>Derivation path of the resulted secret</summary> - [Newtonsoft.Json.JsonProperty("derivationPath", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("derivationPath", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string DerivationPath { get; set; }= default!; - [Newtonsoft.Json.JsonProperty("address", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("address", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string Address { get; set; }= default!; @@ -9930,10 +9927,10 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] public partial class Response6 { - [Newtonsoft.Json.JsonProperty("rewardAddress", Required = Newtonsoft.Json.Required.Always)] + [Newtonsoft.Json.JsonProperty("rewardAddress", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] public string RewardAddress { get; set; }= default!; - + private System.Collections.Generic.IDictionary<string, object> _additionalProperties = new System.Collections.Generic.Dictionary<string, object>(); [Newtonsoft.Json.JsonExtensionData] @@ -9942,16 +9939,15 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti get { return _additionalProperties; } set { _additionalProperties = value; } } - - } [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] public partial class Response7 { - [Newtonsoft.Json.JsonProperty("rewardAddress", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public string RewardAddress { get; set; }= default!; - + [Newtonsoft.Json.JsonProperty("rewardPubKey", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string RewardPubKey { get; set; }= default!; + private System.Collections.Generic.IDictionary<string, object> _additionalProperties = new System.Collections.Generic.Dictionary<string, object>(); [Newtonsoft.Json.JsonExtensionData] @@ -9960,8 +9956,6 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti get { return _additionalProperties; } set { _additionalProperties = value; } } - - } [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] diff --git a/src/Miningcore/Blockchain/Ergo/RPC/ergo.nswag b/src/Miningcore/Blockchain/Ergo/RPC/ergo.nswag index ca36861d9..5da96ca99 100644 --- a/src/Miningcore/Blockchain/Ergo/RPC/ergo.nswag +++ b/src/Miningcore/Blockchain/Ergo/RPC/ergo.nswag @@ -27,8 +27,8 @@ "useBaseUrl": true, "generateBaseUrlProperty": true, "generateSyncMethods": false, - "generatePrepareRequestAndProcessResponseAsAsyncMethods": false, - "exposeJsonSerializerSettings": true, + "generatePrepareRequestAndProcessResponseAsAsyncMethods": true, + "exposeJsonSerializerSettings": false, "clientClassAccessModifier": "public", "typeAccessModifier": "public", "generateContractsOutput": false, @@ -56,7 +56,7 @@ "generateResponseClasses": true, "responseClass": "SwaggerResponse", "namespace": "Miningcore.Blockchain.Ergo", - "requiredPropertiesMustBeDefined": true, + "requiredPropertiesMustBeDefined": false, "dateType": "System.DateTimeOffset", "jsonConverters": null, "anyType": "object", diff --git a/src/Miningcore/Payments/PayoutManager.cs b/src/Miningcore/Payments/PayoutManager.cs index 631989002..d311ee454 100644 --- a/src/Miningcore/Payments/PayoutManager.cs +++ b/src/Miningcore/Payments/PayoutManager.cs @@ -56,6 +56,7 @@ public PayoutManager(IComponentContext ctx, clusterConfig.PaymentProcessing.Interval : 600); } + private static readonly ILogger logger = LogManager.GetCurrentClassLogger(); private readonly IBalanceRepository balanceRepo; private readonly IBlockRepository blockRepo; private readonly IConnectionFactory cf; @@ -66,7 +67,12 @@ public PayoutManager(IComponentContext ctx, private readonly ConcurrentDictionary<string, IMiningPool> pools = new(); private readonly ClusterConfig clusterConfig; private readonly CompositeDisposable disposables = new(); - private static readonly ILogger logger = LogManager.GetCurrentClassLogger(); + + #if !DEBUG + private static readonly TimeSpan initialRunDelay = TimeSpan.FromMinutes(1); + #else + private static readonly TimeSpan initialRunDelay = TimeSpan.FromSeconds(15); + #endif private void AttachPool(IMiningPool pool) { @@ -256,7 +262,7 @@ protected override async Task ExecuteAsync(CancellationToken ct) logger.Info(() => "Online"); // Allow all pools to actually come up before the first payment processing run - await Task.Delay(TimeSpan.FromMinutes(1), ct); + await Task.Delay(initialRunDelay, ct); while(!ct.IsCancellationRequested) { From 8de72770609d6366eedda8d0bf0cfe3ecaf5c251 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Wed, 7 Jul 2021 17:30:54 +0200 Subject: [PATCH 015/145] Payment processing & hashrate --- .../Blockchain/Ergo/ErgoConstants.cs | 2 +- .../Blockchain/Ergo/ErgoPayoutHandler.cs | 168 +++++------------- src/Miningcore/Blockchain/Ergo/ErgoPool.cs | 6 +- .../Blockchain/Ergo/RPC/ErgoClient.cs | 11 ++ 4 files changed, 56 insertions(+), 131 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs b/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs index fd5d0a09c..0845e97d1 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs @@ -17,7 +17,7 @@ public static class ErgoConstants public const uint DiffMultiplier = 256; public static double Pow2x26 = Math.Pow(2, 26); - public const double SmallestUnit = 1000000000; + public const decimal SmallestUnit = 1000000000; public static Regex RegexChain = new("ergo-([^-]+)-.+", RegexOptions.Compiled); diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index 99a23596b..4039ad559 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -248,133 +248,47 @@ public virtual async Task PayoutAsync(Balance[] balances) { Contract.RequiresNonNull(balances, nameof(balances)); - // // build args - // var amounts = balances - // .Where(x => x.Amount > 0) - // .ToDictionary(x => x.Address, x => Math.Round(x.Amount, 4)); - - // if(amounts.Count == 0) - // return; - - // logger.Info(() => $"[{LogCategory}] Paying out {FormatAmount(balances.Sum(x => x.Amount))} to {balances.Length} addresses"); - - // object[] args; - - // if(extraPoolPaymentProcessingConfig?.MinersPayTxFees == true) - // { - // var identifier = !string.IsNullOrEmpty(clusterConfig.PaymentProcessing?.CoinbaseString) ? - // clusterConfig.PaymentProcessing.CoinbaseString.Trim() : "Miningcore"; - - // var comment = $"{identifier} Payment"; - // var subtractFeesFrom = amounts.Keys.ToArray(); - - // if(!poolConfig.Template.As<BitcoinTemplate>().HasMasterNodes) - // { - // args = new object[] - // { - // string.Empty, // default account - // amounts, // addresses and associated amounts - // 1, // only spend funds covered by this many confirmations - // comment, // tx comment - // subtractFeesFrom, // distribute transaction fee equally over all recipients, - - // // workaround for https://bitcoin.stackexchange.com/questions/102508/bitcoin-cli-sendtoaddress-error-fallbackfee-is-disabled-wait-a-few-blocks-or-en - // // using bitcoin regtest - // //true, - // //null, - // //"unset", - // //"1" - // }; - // } - - // else - // { - // args = new object[] - // { - // string.Empty, // default account - // amounts, // addresses and associated amounts - // 1, // only spend funds covered by this many confirmations - // false, // Whether to add confirmations to transactions locked via InstantSend - // comment, // tx comment - // subtractFeesFrom, // distribute transaction fee equally over all recipients - // false, // use_is: Send this transaction as InstantSend - // false, // Use anonymized funds only - // }; - // } - // } - - // else - // { - // args = new object[] - // { - // string.Empty, // default account - // amounts, // addresses and associated amounts - // }; - // } - - // var didUnlockWallet = false; - - //// send command - //tryTransfer: - // var result = await daemon.ExecuteCmdSingleAsync<string>(logger, BitcoinCommands.SendMany, args, new JsonSerializerSettings()); - - // if(result.Error == null) - // { - // if(didUnlockWallet) - // { - // // lock wallet - // logger.Info(() => $"[{LogCategory}] Locking wallet"); - // await daemon.ExecuteCmdSingleAsync<JToken>(logger, BitcoinCommands.WalletLock); - // } - - // // check result - // var txId = result.Response; - - // if(string.IsNullOrEmpty(txId)) - // logger.Error(() => $"[{LogCategory}] {BitcoinCommands.SendMany} did not return a transaction id!"); - // else - // logger.Info(() => $"[{LogCategory}] Payout transaction id: {txId}"); - - // await PersistPaymentsAsync(balances, txId); - - // NotifyPayoutSuccess(poolConfig.Id, balances, new[] { txId }, null); - // } - - // else - // { - // if(result.Error.Code == (int) BitcoinRPCErrorCode.RPC_WALLET_UNLOCK_NEEDED && !didUnlockWallet) - // { - // if(!string.IsNullOrEmpty(extraPoolPaymentProcessingConfig?.WalletPassword)) - // { - // logger.Info(() => $"[{LogCategory}] Unlocking wallet"); - - // var unlockResult = await daemon.ExecuteCmdSingleAsync<JToken>(logger, BitcoinCommands.WalletPassphrase, new[] - // { - // (object) extraPoolPaymentProcessingConfig.WalletPassword, - // (object) 5 // unlock for N seconds - // }); - - // if(unlockResult.Error == null) - // { - // didUnlockWallet = true; - // goto tryTransfer; - // } - - // else - // logger.Error(() => $"[{LogCategory}] {BitcoinCommands.WalletPassphrase} returned error: {result.Error.Message} code {result.Error.Code}"); - // } - - // else - // logger.Error(() => $"[{LogCategory}] Wallet is locked but walletPassword was not configured. Unable to send funds."); - // } - - // else - // { - // logger.Error(() => $"[{LogCategory}] {BitcoinCommands.SendMany} returned error: {result.Error.Message} code {result.Error.Code}"); - - // NotifyPayoutFailure(poolConfig.Id, balances, $"{BitcoinCommands.SendMany} returned error: {result.Error.Message} code {result.Error.Code}", null); - // } - // } + // build args + var amounts = balances + .Where(x => x.Amount > 0) + .ToDictionary(x => x.Address, x => Math.Round(x.Amount, 4)); + + if(amounts.Count == 0) + return; + + logger.Info(() => $"[{LogCategory}] Paying out {FormatAmount(balances.Sum(x => x.Amount))} to {balances.Length} addresses"); + + // Create request batch + var requests = amounts.Select(x => new PaymentRequest + { + Address = x.Key, + Value = (long) (x.Value * ErgoConstants.SmallestUnit), + }).ToArray(); + + var txId = await Guard(() => daemon.WalletPaymentTransactionGenerateAndSendAsync(requests), ex => + { + var error = ex.Message; + + if(ex is ApiException<ApiError> apiException) + { + error = apiException.Result.Detail ?? apiException.Result.Reason; + + logger.Warn(() => $"Failed to initiate batch payment transaction: {error}"); + } + + NotifyPayoutFailure(poolConfig.Id, balances, $"/wallet/payment/send returned error: {error}", null); + + throw ex; + }); + + if(!string.IsNullOrEmpty(txId)) + { + logger.Info(() => $"[{LogCategory}] Payout transaction id: {txId}"); + + await PersistPaymentsAsync(balances, txId); + + NotifyPayoutSuccess(poolConfig.Id, balances, new[] { txId }, null); + } } #endregion // IPayoutHandler diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs index 69be68c3c..644ac9446 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs @@ -268,10 +268,10 @@ private async Task SendJob(StratumConnection connection, ErgoWorkerContext conte public override double HashrateFromShares(double shares, double interval) { - var multiplier = ErgoConstants.DiffMultiplier; - var result = shares * multiplier / interval; + var multiplier = ErgoConstants.Pow2x26; + var result = shares * multiplier * ErgoConstants.DiffMultiplier / interval; - result /= 3; + result /= 8; return result; } diff --git a/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs b/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs index cc613a404..f77ac2b76 100644 --- a/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs +++ b/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs @@ -10022,6 +10022,17 @@ public System.Collections.Generic.IDictionary<string, object> AdditionalProperti [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.4.0 (Newtonsoft.Json v12.0.0.0)")] public partial class Requests { + [Newtonsoft.Json.JsonProperty("address", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Address { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("value", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + [System.ComponentModel.DataAnnotations.Range(0D, double.MaxValue)] + public long Value { get; set; }= default!; + + [Newtonsoft.Json.JsonProperty("assets", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.Generic.ICollection<Asset> Assets { get; set; }= default!; + private System.Collections.Generic.IDictionary<string, object> _additionalProperties = new System.Collections.Generic.Dictionary<string, object>(); [Newtonsoft.Json.JsonExtensionData] From 24b320e49dc0972bc6bd3bccd3c20d30d33a656c Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Wed, 7 Jul 2021 17:36:27 +0200 Subject: [PATCH 016/145] ergo -> coins.,json --- src/Miningcore/coins.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Miningcore/coins.json b/src/Miningcore/coins.json index 1eebda1fc..70d9f8a9a 100644 --- a/src/Miningcore/coins.json +++ b/src/Miningcore/coins.json @@ -1487,5 +1487,13 @@ }, "explorerTxLink": "https://explorer.callisto.network/tx/{0}", "explorerAccountLink": "https://explorer.callisto.network/address/{0}" + }, + "ergo": { + "name": "ergo", + "symbol": "ERG", + "family": "ergo", + "explorerBlockLink": "https://explorer.ergoplatform.com/en/blocks/$hash$", + "explorerTxLink": "https://explorer.ergoplatform.com/en/transactions/{0}", + "explorerAccountLink": "https://explorer.ergoplatform.com/en/addresses/{0}" } } From 57cebd3b65b07141c2db3102214cf738fdd3b1d2 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Thu, 8 Jul 2021 00:15:22 +0200 Subject: [PATCH 017/145] Ergo btStream support --- .../Ergo/Configuration/ErgoPoolConfigExtra.cs | 7 ++ .../Blockchain/Ergo/ErgoBlockTemplate.cs | 2 +- src/Miningcore/Blockchain/Ergo/ErgoJob.cs | 4 +- .../Blockchain/Ergo/ErgoJobManager.cs | 79 +++++++++---------- src/Miningcore/Mining/BtStreamReceiver.cs | 7 +- 5 files changed, 53 insertions(+), 46 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/Configuration/ErgoPoolConfigExtra.cs b/src/Miningcore/Blockchain/Ergo/Configuration/ErgoPoolConfigExtra.cs index ead4a357d..309784738 100644 --- a/src/Miningcore/Blockchain/Ergo/Configuration/ErgoPoolConfigExtra.cs +++ b/src/Miningcore/Blockchain/Ergo/Configuration/ErgoPoolConfigExtra.cs @@ -1,3 +1,5 @@ +using Miningcore.Configuration; + namespace Miningcore.Blockchain.Ergo.Configuration { public class ErgoPoolConfigExtra @@ -8,6 +10,11 @@ public class ErgoPoolConfigExtra /// </summary> public int? MaxActiveJobs { get; set; } + /// <summary> + /// Blocktemplate stream published via ZMQ + /// </summary> + public ZmqPubSubEndpointConfig BtStream { get; set; } + public int? MinimumConfirmations { get; set; } } } diff --git a/src/Miningcore/Blockchain/Ergo/ErgoBlockTemplate.cs b/src/Miningcore/Blockchain/Ergo/ErgoBlockTemplate.cs index e658cba74..a564b3b91 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoBlockTemplate.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoBlockTemplate.cs @@ -1,4 +1,4 @@ namespace Miningcore.Blockchain.Ergo { - public record ErgoBlockTemplate(WorkMessage Work, NodeInfo Info); + public record ErgoBlockTemplate(WorkMessage Work); } diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs index 596fff08b..cdcdcca6a 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs @@ -202,7 +202,7 @@ public virtual Share ProcessShare(StratumConnection worker, string extraNonce2, return ProcessShareInternal(worker, extraNonce2); } - public void Init(ErgoBlockTemplate blockTemplate, string jobId) + public void Init(ErgoBlockTemplate blockTemplate, int blockVersion, string jobId) { BlockTemplate = blockTemplate; JobId = jobId; @@ -216,7 +216,7 @@ public void Init(ErgoBlockTemplate blockTemplate, string jobId) BlockTemplate.Work.Msg, string.Empty, string.Empty, - BlockTemplate.Info.Parameters.BlockVersion, + blockVersion, B, string.Empty, false diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs index a773b873e..56356f4ca 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs @@ -54,6 +54,7 @@ public ErgoJobManager( private readonly IExtraNonceProvider extraNonceProvider; private readonly IMasterClock clock; private ErgoPoolConfigExtra extraPoolConfig; + private int blockVersion; private void SetupJobUpdates() { @@ -66,7 +67,7 @@ private void SetupJobUpdates() blockFound.Select(x => (false, JobRefreshBy.BlockFound, (string) null)) }; - if(true) // extraPoolConfig?.BtStream == null) + if(extraPoolConfig?.BtStream == null) { if(poolConfig.BlockRefreshInterval > 0) { @@ -97,37 +98,37 @@ private void SetupJobUpdates() } } - //else - //{ - // var btStream = BtStreamSubscribe(extraPoolConfig.BtStream); - - // if(poolConfig.JobRebroadcastTimeout > 0) - // { - // var interval = TimeSpan.FromSeconds(Math.Max(1, poolConfig.JobRebroadcastTimeout - 0.1d)); - - // triggers.Add(btStream - // .Select(json => - // { - // var force = !lastJobRebroadcast.HasValue || (clock.Now - lastJobRebroadcast >= interval); - // return (force, !force ? JobRefreshBy.BlockTemplateStream : JobRefreshBy.BlockTemplateStreamRefresh, json); - // }) - // .Publish() - // .RefCount()); - // } - - // else - // { - // triggers.Add(btStream - // .Select(json => (false, JobRefreshBy.BlockTemplateStream, json)) - // .Publish() - // .RefCount()); - // } - - // // get initial blocktemplate - // triggers.Add(Observable.Interval(TimeSpan.FromMilliseconds(1000)) - // .Select(_ => (false, JobRefreshBy.Initial, (string) null)) - // .TakeWhile(_ => !hasInitialBlockTemplate)); - //} + else + { + var btStream = BtStreamSubscribe(extraPoolConfig.BtStream); + + if(poolConfig.JobRebroadcastTimeout > 0) + { + var interval = TimeSpan.FromSeconds(Math.Max(1, poolConfig.JobRebroadcastTimeout - 0.1d)); + + triggers.Add(btStream + .Select(json => + { + var force = !lastJobRebroadcast.HasValue || (clock.Now - lastJobRebroadcast >= interval); + return (force, !force ? JobRefreshBy.BlockTemplateStream : JobRefreshBy.BlockTemplateStreamRefresh, json); + }) + .Publish() + .RefCount()); + } + + else + { + triggers.Add(btStream + .Select(json => (false, JobRefreshBy.BlockTemplateStream, json)) + .Publish() + .RefCount()); + } + + // get initial blocktemplate + triggers.Add(Observable.Interval(TimeSpan.FromMilliseconds(1000)) + .Select(_ => (false, JobRefreshBy.Initial, (string) null)) + .TakeWhile(_ => !hasInitialBlockTemplate)); + } Jobs = Observable.Merge(triggers) .Select(x => Observable.FromAsync(() => UpdateJob(x.Force, x.Via, x.Data))) @@ -170,7 +171,7 @@ await GetBlockTemplateAsync() : { job = new ErgoJob(); - job.Init(blockTemplate, NextJobId()); + job.Init(blockTemplate, blockVersion, NextJobId()); lock(jobLock) { @@ -224,15 +225,9 @@ private async Task<ErgoBlockTemplate> GetBlockTemplateAsync() { logger.LogInvoke(); - var infoTask = daemon.GetNodeInfoAsync(); - var miningCandidateTask = daemon.MiningRequestBlockCandidateAsync(CancellationToken.None); + var work = await daemon.MiningRequestBlockCandidateAsync(CancellationToken.None); - await Task.WhenAll(infoTask, miningCandidateTask); - - var info = infoTask.Result; - var work = miningCandidateTask.Result; - - return new ErgoBlockTemplate(work, info); + return new ErgoBlockTemplate(work); } private ErgoBlockTemplate GetBlockTemplateFromJson(string json) @@ -470,6 +465,8 @@ protected override async Task PostStartInitAsync(CancellationToken ct) var info = await Guard(() => daemon.GetNodeInfoAsync(ct), ex=> logger.ThrowLogPoolStartupException($"Daemon reports: {ex.Message}")); + blockVersion = info.Parameters.BlockVersion; + // chain detection var m = ErgoConstants.RegexChain.Match(info.Name); if(!m.Success) diff --git a/src/Miningcore/Mining/BtStreamReceiver.cs b/src/Miningcore/Mining/BtStreamReceiver.cs index 0d9b7d05b..47987c525 100644 --- a/src/Miningcore/Mining/BtStreamReceiver.cs +++ b/src/Miningcore/Mining/BtStreamReceiver.cs @@ -47,11 +47,14 @@ public BtStreamReceiver( private static ZSocket SetupSubSocket(ZmqPubSubEndpointConfig relay) { var subSocket = new ZSocket(ZSocketType.SUB); - subSocket.SetupCurveTlsClient(relay.SharedEncryptionKey, logger); + + if(!string.IsNullOrEmpty(relay.SharedEncryptionKey)) + subSocket.SetupCurveTlsClient(relay.SharedEncryptionKey, logger); + subSocket.Connect(relay.Url); subSocket.SubscribeAll(); - if(subSocket.CurveServerKey != null) + if(subSocket.CurveServerKey != null && subSocket.CurveServerKey.Any(x => x != 0)) logger.Info($"Monitoring Bt-Stream source {relay.Url} using Curve public-key {subSocket.CurveServerKey.ToHexString()}"); else logger.Info($"Monitoring Bt-Stream source {relay.Url}"); From 891dc22597e5e13065a4b54fe95fd10656a54dd1 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Thu, 8 Jul 2021 00:52:34 +0200 Subject: [PATCH 018/145] ErgoClient basic auth support --- .../Blockchain/Ergo/ErgoClientExtensions.cs | 7 ++++-- .../Blockchain/Ergo/ErgoClientFactory.cs | 24 +++++++++++++++---- .../Blockchain/Ergo/ErgoJobManager.cs | 2 +- .../Blockchain/Ergo/ErgoPayoutHandler.cs | 2 +- 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoClientExtensions.cs b/src/Miningcore/Blockchain/Ergo/ErgoClientExtensions.cs index ce9ce1d1d..933323769 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoClientExtensions.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoClientExtensions.cs @@ -1,4 +1,6 @@ +using System.Collections.Generic; using System.Net.Http; +using System.Net.Http.Headers; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -7,11 +9,12 @@ namespace Miningcore.Blockchain.Ergo { public partial class ErgoClient { - public string ApiKey { get; set; } + public Dictionary<string, string> RequestHeaders { get; } = new(); private Task PrepareRequestAsync(HttpClient client, HttpRequestMessage request, StringBuilder url) { - request.Headers.Add("api_key", ApiKey); + foreach(var pair in RequestHeaders) + request.Headers.Add(pair.Key, pair.Value); return Task.CompletedTask; } diff --git a/src/Miningcore/Blockchain/Ergo/ErgoClientFactory.cs b/src/Miningcore/Blockchain/Ergo/ErgoClientFactory.cs index 0a2eb7a61..f0c08264f 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoClientFactory.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoClientFactory.cs @@ -1,6 +1,9 @@ using System; using System.Linq; +using System.Net; using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; using Miningcore.Blockchain.Ergo.Configuration; using Miningcore.Configuration; using Miningcore.Extensions; @@ -11,7 +14,7 @@ namespace Miningcore.Blockchain.Ergo { public static class ErgoClientFactory { - public static ErgoClient CreateClient(PoolConfig poolConfig, ClusterConfig clusterConfig, IHttpClientFactory httpClientFactory, ILogger logger) + public static ErgoClient CreateClient(PoolConfig poolConfig, ClusterConfig clusterConfig, ILogger logger) { var epConfig = poolConfig.Daemons.First(); var extra = epConfig.Extra.SafeExtensionDataAs<ErgoDaemonEndpointConfigExtra>(); @@ -23,10 +26,23 @@ public static ErgoClient CreateClient(PoolConfig poolConfig, ClusterConfig clust var baseUrl = new UriBuilder(epConfig.Ssl || epConfig.Http2 ? Uri.UriSchemeHttps : Uri.UriSchemeHttp, epConfig.Host, epConfig.Port, epConfig.HttpPath); - var result = new ErgoClient(baseUrl.ToString(), httpClientFactory.CreateClient()) + var result = new ErgoClient(baseUrl.ToString(), new HttpClient(new HttpClientHandler { - ApiKey = extra.ApiKey - }; + AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip, + + ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true, + })); + + if(!string.IsNullOrEmpty(extra.ApiKey)) + result.RequestHeaders["api_key"] = extra.ApiKey; + + if(!string.IsNullOrEmpty(epConfig.User)) + { + var auth = $"{epConfig.User}:{epConfig.Password}"; + var base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(auth)); + + result.RequestHeaders["Authorization"] = new AuthenticationHeaderValue("Basic", base64).ToString(); + } #if DEBUG result.ReadResponseAsString = true; diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs index 56356f4ca..2ca425953 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs @@ -516,7 +516,7 @@ public override void Configure(PoolConfig poolConfig, ClusterConfig clusterConfi protected override void ConfigureDaemons() { - daemon = ErgoClientFactory.CreateClient(poolConfig, clusterConfig, httpClientFactory, logger); + daemon = ErgoClientFactory.CreateClient(poolConfig, clusterConfig, logger); } protected override async Task<bool> AreDaemonsHealthyAsync() diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index 4039ad559..0b9eccd8b 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -76,7 +76,7 @@ public virtual async Task ConfigureAsync(ClusterConfig clusterConfig, PoolConfig extraPoolConfig = poolConfig.Extra.SafeExtensionDataAs<ErgoPoolConfigExtra>(); //extraPoolPaymentProcessingConfig = poolConfig.PaymentProcessing.Extra.SafeExtensionDataAs<BitcoinPoolPaymentProcessingConfigExtra>(); - daemon = ErgoClientFactory.CreateClient(poolConfig, clusterConfig, httpClientFactory, null); + daemon = ErgoClientFactory.CreateClient(poolConfig, clusterConfig, null); // detect chain var info = await daemon.GetNodeInfoAsync(); From 5ecec4e900584e7d18532920ac079aa9c4fcf75a Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Thu, 8 Jul 2021 08:10:51 +0200 Subject: [PATCH 019/145] Wallet unlock during payments --- .../ErgoPaymentProcessingConfigExtra.cs | 7 ++ .../Blockchain/Ergo/ErgoPayoutHandler.cs | 70 +++++++++++++------ 2 files changed, 56 insertions(+), 21 deletions(-) create mode 100644 src/Miningcore/Blockchain/Ergo/Configuration/ErgoPaymentProcessingConfigExtra.cs diff --git a/src/Miningcore/Blockchain/Ergo/Configuration/ErgoPaymentProcessingConfigExtra.cs b/src/Miningcore/Blockchain/Ergo/Configuration/ErgoPaymentProcessingConfigExtra.cs new file mode 100644 index 000000000..486cdf268 --- /dev/null +++ b/src/Miningcore/Blockchain/Ergo/Configuration/ErgoPaymentProcessingConfigExtra.cs @@ -0,0 +1,7 @@ +namespace Miningcore.Blockchain.Ergo.Configuration +{ + public class ErgoPaymentProcessingConfigExtra + { + public string WalletPassword { get; set; } + } +} diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index 0b9eccd8b..56cd8fcd8 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -59,9 +59,23 @@ public ErgoPayoutHandler( private ErgoPoolConfigExtra extraPoolConfig; private readonly IHttpClientFactory httpClientFactory; private string network; + private ErgoPaymentProcessingConfigExtra extraPoolPaymentProcessingConfig; protected override string LogCategory => "Bitcoin Payout Handler"; + private void ReportAndRethrowApiError(string action, Exception ex, bool rethrow = true) + { + var error = ex.Message; + + if(ex is ApiException<ApiError> apiException) + error = apiException.Result.Detail ?? apiException.Result.Reason; + + logger.Warn(() => $"{action}: {error}"); + + if(rethrow) + throw ex; + } + #region IPayoutHandler public virtual async Task ConfigureAsync(ClusterConfig clusterConfig, PoolConfig poolConfig) @@ -74,7 +88,7 @@ public virtual async Task ConfigureAsync(ClusterConfig clusterConfig, PoolConfig this.clusterConfig = clusterConfig; extraPoolConfig = poolConfig.Extra.SafeExtensionDataAs<ErgoPoolConfigExtra>(); - //extraPoolPaymentProcessingConfig = poolConfig.PaymentProcessing.Extra.SafeExtensionDataAs<BitcoinPoolPaymentProcessingConfigExtra>(); + extraPoolPaymentProcessingConfig = poolConfig.PaymentProcessing.Extra.SafeExtensionDataAs<ErgoPaymentProcessingConfigExtra>(); daemon = ErgoClientFactory.CreateClient(poolConfig, clusterConfig, null); @@ -258,36 +272,50 @@ public virtual async Task PayoutAsync(Balance[] balances) logger.Info(() => $"[{LogCategory}] Paying out {FormatAmount(balances.Sum(x => x.Amount))} to {balances.Length} addresses"); - // Create request batch - var requests = amounts.Select(x => new PaymentRequest - { - Address = x.Key, - Value = (long) (x.Value * ErgoConstants.SmallestUnit), - }).ToArray(); + // unlock wallet + await Guard(() => daemon.WalletUnlockAsync(new Body4 {Pass = extraPoolPaymentProcessingConfig.WalletPassword ?? string.Empty}), + ex => ReportAndRethrowApiError("Failed to unlock wallet", ex)); - var txId = await Guard(() => daemon.WalletPaymentTransactionGenerateAndSendAsync(requests), ex => + try { - var error = ex.Message; + // Create request batch + var requests = amounts.Select(x => new PaymentRequest + { + Address = x.Key, + Value = (long) (x.Value * ErgoConstants.SmallestUnit), + }).ToArray(); - if(ex is ApiException<ApiError> apiException) + var txId = await Guard(() => daemon.WalletPaymentTransactionGenerateAndSendAsync(requests), ex => { - error = apiException.Result.Detail ?? apiException.Result.Reason; + var error = ex.Message; - logger.Warn(() => $"Failed to initiate batch payment transaction: {error}"); - } + if(ex is ApiException<ApiError> apiException) + { + error = apiException.Result.Detail ?? apiException.Result.Reason; - NotifyPayoutFailure(poolConfig.Id, balances, $"/wallet/payment/send returned error: {error}", null); + logger.Warn(() => $"Failed to initiate batch payment transaction: {error}"); + } - throw ex; - }); + NotifyPayoutFailure(poolConfig.Id, balances, $"/wallet/payment/send returned error: {error}", null); - if(!string.IsNullOrEmpty(txId)) - { - logger.Info(() => $"[{LogCategory}] Payout transaction id: {txId}"); + throw ex; + }); + + if(!string.IsNullOrEmpty(txId)) + { + logger.Info(() => $"[{LogCategory}] Payout transaction id: {txId}"); - await PersistPaymentsAsync(balances, txId); + await PersistPaymentsAsync(balances, txId); - NotifyPayoutSuccess(poolConfig.Id, balances, new[] { txId }, null); + NotifyPayoutSuccess(poolConfig.Id, balances, new[] { txId }, null); + } + } + + finally + { + // lock wallet + await Guard(()=> daemon.WalletLockAsync(), + ex=> ReportAndRethrowApiError("Failed to lock wallet", ex)); } } From e847694cf8ed0262c53a28d811e36303650e54e6 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Thu, 8 Jul 2021 08:31:31 +0200 Subject: [PATCH 020/145] cleanup --- src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index 56cd8fcd8..f514e776a 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -2,16 +2,11 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; -using System.Text.RegularExpressions; using System.Threading.Tasks; using Autofac; using AutoMapper; -using Miningcore.Blockchain.Bitcoin; -using Miningcore.Blockchain.Bitcoin.Configuration; -using Miningcore.Blockchain.Bitcoin.DaemonResponses; using Miningcore.Blockchain.Ergo.Configuration; using Miningcore.Configuration; -using Miningcore.DaemonInterface; using Miningcore.Extensions; using Miningcore.Messaging; using Miningcore.Payments; @@ -20,8 +15,6 @@ using Miningcore.Persistence.Repositories; using Miningcore.Time; using Miningcore.Util; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using Block = Miningcore.Persistence.Model.Block; using Contract = Miningcore.Contracts.Contract; using static Miningcore.Util.ActionUtils; From d0db483d30b86967ba46759f135da6fd0ab17cde Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Thu, 8 Jul 2021 17:24:25 +0200 Subject: [PATCH 021/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoJob.cs | 35 ++++++++++++++++------ src/Miningcore/Blockchain/Ergo/ErgoPool.cs | 11 +++++-- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs index cdcdcca6a..f26b83379 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs @@ -11,13 +11,14 @@ using Miningcore.Stratum; using System.Numerics; using NBitcoin; +using NLog; namespace Miningcore.Blockchain.Ergo { public class ErgoJob { public ErgoBlockTemplate BlockTemplate { get; private set; } - public double Difficulty { get; private set; } + public double Difficulty => target.Difficulty; public uint Height => BlockTemplate.Work.Height; public string JobId { get; protected set; } @@ -27,6 +28,7 @@ public class ErgoJob private BigInteger B; private static readonly uint nBase = (uint) Math.Pow(2, 26); + private Target target; private const uint IncreaseStart = 600 * 1024; private const uint IncreasePeriodForN = 50 * 1024; private const uint NIncreasementHeightMax = 9216000; @@ -146,18 +148,34 @@ protected virtual Share ProcessShareInternal(StratumConnection worker, string ex // calculate fH var blockHash = f.ToByteArray(true, true).PadFront(0, 32); hasher.Digest(blockHash, hashResult); - var fh = new BigInteger(hashResult, true, true); + var shareTarget = new Target(new BigInteger(hashResult, true, true)); // diff check var stratumDifficulty = context.Difficulty; - var isLowDifficulty = fh / new BigInteger(context.Difficulty) > B; + var stratumTarget = new Target(target.ToBigInteger() * (BigInteger) stratumDifficulty); + var ratio = shareTarget.Difficulty / stratumTarget.Difficulty; // check if the share meets the much harder block difficulty (block candidate) - var isBlockCandidate = B >= fh; + var isBlockCandidate = target >= shareTarget; // test if share meets at least workers current difficulty - if(!isBlockCandidate && isLowDifficulty) - throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share"); + if(!isBlockCandidate && ratio < 0.99) + { + // check if share matched the previous difficulty from before a vardiff retarget + if(context.VarDiff?.LastUpdate != null && context.PreviousDifficulty.HasValue) + { + ratio = shareTarget.Difficulty / context.PreviousDifficulty.Value; + + if(ratio < 0.99) + throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareTarget.Difficulty})"); + + // use previous difficulty + stratumDifficulty = context.PreviousDifficulty.Value; + } + + else + throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareTarget.Difficulty})"); + } var result = new Share { @@ -206,8 +224,7 @@ public void Init(ErgoBlockTemplate blockTemplate, int blockVersion, string jobId { BlockTemplate = blockTemplate; JobId = jobId; - B = BigInteger.Parse(BlockTemplate.Work.B, NumberStyles.Integer); - Difficulty = new Target(B).Difficulty; + target = new Target(BigInteger.Parse(BlockTemplate.Work.B, NumberStyles.Integer)); jobParams = new object[] { @@ -217,7 +234,7 @@ public void Init(ErgoBlockTemplate blockTemplate, int blockVersion, string jobId string.Empty, string.Empty, blockVersion, - B, + target, string.Empty, false }; diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs index 644ac9446..5813d0d9f 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using System.Linq; +using System.Numerics; using Autofac; using AutoMapper; using Miningcore.Blockchain.Bitcoin; @@ -18,6 +19,8 @@ using Miningcore.Persistence.Repositories; using Miningcore.Stratum; using Miningcore.Time; +using Miningcore.Util; +using NBitcoin; using Newtonsoft.Json; using NLog; @@ -255,13 +258,15 @@ protected virtual async Task OnNewJobAsync(object[] jobParams) private async Task SendJob(StratumConnection connection, ErgoWorkerContext context, object[] jobParams) { // clone job params - var jobParamsActual = new object[currentJobParams.Length]; + var jobParamsActual = new object[jobParams.Length]; for(var i = 0; i < jobParamsActual.Length; i++) - jobParamsActual[i] = currentJobParams[i]; + jobParamsActual[i] = jobParams[i]; // correct difficulty - jobParamsActual[6] = (System.Numerics.BigInteger) jobParamsActual[6] * new System.Numerics.BigInteger(context.Difficulty); + var target = (Target) jobParamsActual[6]; + var diff = new BigRational(target.ToBigInteger() * (BigInteger) (context.Difficulty * 256), 256).GetWholePart(); + jobParamsActual[6] = diff.ToString(); await connection.NotifyAsync(BitcoinStratumMethods.MiningNotify, jobParamsActual); } From acb8f734e1f570085e6c04a32e93d9a87c38a9d3 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Thu, 8 Jul 2021 21:38:02 +0200 Subject: [PATCH 022/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoConstants.cs | 3 +++ src/Miningcore/Blockchain/Ergo/ErgoJob.cs | 13 +++++++------ src/Miningcore/Blockchain/Ergo/ErgoPool.cs | 17 ++++++++++++----- .../Blockchain/Ergo/ErgoWorkerContext.cs | 10 ++++++++++ 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs b/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs index 0845e97d1..1f6599916 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs @@ -14,6 +14,8 @@ namespace Miningcore.Blockchain.Ergo { public static class ErgoConstants { + public const double ArtificialDiffCeiling = 0x10000; + public const uint DiffMultiplier = 256; public static double Pow2x26 = Math.Pow(2, 26); @@ -25,6 +27,7 @@ public static class ErgoConstants .SelectMany(x => { const double max = 4294967296d; + var top = (uint) Math.Floor(x / max); var rem = (uint) (x - top * max); diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs index f26b83379..e0d8ed6fb 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs @@ -10,6 +10,8 @@ using Miningcore.Extensions; using Miningcore.Stratum; using System.Numerics; +using Miningcore.Blockchain.Bitcoin; +using Miningcore.Util; using NBitcoin; using NLog; @@ -151,9 +153,8 @@ protected virtual Share ProcessShareInternal(StratumConnection worker, string ex var shareTarget = new Target(new BigInteger(hashResult, true, true)); // diff check - var stratumDifficulty = context.Difficulty; - var stratumTarget = new Target(target.ToBigInteger() * (BigInteger) stratumDifficulty); - var ratio = shareTarget.Difficulty / stratumTarget.Difficulty; + var stratumDifficulty = context.EffectiveDifficulty; + var ratio = shareTarget.Difficulty / stratumDifficulty; // check if the share meets the much harder block difficulty (block candidate) var isBlockCandidate = target >= shareTarget; @@ -162,15 +163,15 @@ protected virtual Share ProcessShareInternal(StratumConnection worker, string ex if(!isBlockCandidate && ratio < 0.99) { // check if share matched the previous difficulty from before a vardiff retarget - if(context.VarDiff?.LastUpdate != null && context.PreviousDifficulty.HasValue) + if(context.VarDiff?.LastUpdate != null && context.PreviousEffectiveDifficulty.HasValue) { - ratio = shareTarget.Difficulty / context.PreviousDifficulty.Value; + ratio = shareTarget.Difficulty / context.PreviousEffectiveDifficulty.Value; if(ratio < 0.99) throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareTarget.Difficulty})"); // use previous difficulty - stratumDifficulty = context.PreviousDifficulty.Value; + stratumDifficulty = context.PreviousEffectiveDifficulty.Value; } else diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs index 5813d0d9f..a3abc3a2f 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs @@ -263,20 +263,25 @@ private async Task SendJob(StratumConnection connection, ErgoWorkerContext conte for(var i = 0; i < jobParamsActual.Length; i++) jobParamsActual[i] = jobParams[i]; - // correct difficulty + // emit target var target = (Target) jobParamsActual[6]; - var diff = new BigRational(target.ToBigInteger() * (BigInteger) (context.Difficulty * 256), 256).GetWholePart(); + var tmp = ErgoConstants.ArtificialDiffCeiling / context.Difficulty; + var diff = new BigRational(target.ToBigInteger() * (BigInteger) (tmp * 256), 256).GetWholePart(); jobParamsActual[6] = diff.ToString(); + // also remember effective diff (based on target sent to miner), independent of artificial diff + context.EffectiveDifficulty = new Target(diff).Difficulty; + await connection.NotifyAsync(BitcoinStratumMethods.MiningNotify, jobParamsActual); } public override double HashrateFromShares(double shares, double interval) { - var multiplier = ErgoConstants.Pow2x26; - var result = shares * multiplier * ErgoConstants.DiffMultiplier / interval; + var multiplier = BitcoinConstants.Pow2x32 * ErgoConstants.DiffMultiplier; + var result = shares * multiplier / interval; - result /= 8; + //result /= 8; + result *= 1.25; return result; } @@ -388,6 +393,8 @@ protected override async Task OnVarDiffUpdateAsync(StratumConnection connection, { context.ApplyPendingDifficulty(); + context.PreviousEffectiveDifficulty = context.EffectiveDifficulty; + await connection.NotifyAsync(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty }); await SendJob(connection, context, currentJobParams); } diff --git a/src/Miningcore/Blockchain/Ergo/ErgoWorkerContext.cs b/src/Miningcore/Blockchain/Ergo/ErgoWorkerContext.cs index d34d560cb..f8d4e05c2 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoWorkerContext.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoWorkerContext.cs @@ -15,5 +15,15 @@ public class ErgoWorkerContext : WorkerContextBase public string Worker { get; set; } public string ExtraNonce1 { get; set; } + + /// <summary> + /// Effective adjusted diff + /// </summary> + public double EffectiveDifficulty { get; set; } + + /// <summary> + /// Previous effective adjusted diff + /// </summary> + public double? PreviousEffectiveDifficulty { get; set; } } } From d1b77723087dbc15cc15c48c7ff3c59abc78f8a8 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Fri, 9 Jul 2021 08:33:24 +0200 Subject: [PATCH 023/145] Improve sync progress reporting --- src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs index 2ca425953..fa899398a 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs @@ -249,11 +249,8 @@ private async Task ShowDaemonSyncProgressAsync() logger.Info(() => $"Daemon has downloaded {percent:0.00}% of blockchain from {info.PeersCount} peers"); } - else if(!string.IsNullOrEmpty(info?.BestFullHeaderId)) - logger.Info(() => $"Daemon is downloading headers ..."); - else - logger.Info(() => $"Waiting for daemon to resume syncing ..."); + logger.Info(() => $"Daemon is downloading headers ..."); } private async Task UpdateNetworkStatsAsync() From 153dfe1045ea4e4bc8045a93fd09930ad4ec793d Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Fri, 9 Jul 2021 14:48:11 +0200 Subject: [PATCH 024/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoJob.cs | 27 ++++++---- .../Blockchain/Ergo/ErgoJobManager.cs | 52 +++---------------- .../Blockchain/Ergo/ErgoPayoutHandler.cs | 11 +++- src/Miningcore/Blockchain/Ergo/ErgoPool.cs | 13 +++-- 4 files changed, 41 insertions(+), 62 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs index e0d8ed6fb..13c387d2a 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs @@ -27,6 +27,7 @@ public class ErgoJob private object[] jobParams; private readonly ConcurrentDictionary<string, bool> submissions = new(StringComparer.OrdinalIgnoreCase); private static readonly IHashAlgorithm hasher = new Blake2b(); + private int extraNonceBytes; private BigInteger B; private static readonly uint nBase = (uint) Math.Pow(2, 26); @@ -58,12 +59,14 @@ public static uint CalcN(uint height) protected bool RegisterSubmit(string extraNonce1, string extraNonce2, string nTime, string nonce) { - var key = new StringBuilder() - .Append(extraNonce1) - .Append(extraNonce2) - .Append(nTime) - .Append(nonce) - .ToString(); + var key = nonce; + + //var key = new StringBuilder() + // .Append(extraNonce1) + // .Append(extraNonce2) + // .Append(nTime) + // .Append(nonce) + // .ToString(); return submissions.TryAdd(key, true); } @@ -211,18 +214,24 @@ public virtual Share ProcessShare(StratumConnection worker, string extraNonce2, var context = worker.ContextAs<ErgoWorkerContext>(); // validate nonce - if(nonce.Length != 16) + if(nonce.Length != context.ExtraNonce1.Length + extraNonceBytes * 2) throw new StratumException(StratumError.Other, "incorrect size of nonce"); + // currently unused + if(nTime == "undefined") + nTime = string.Empty; + // dupe check if(!RegisterSubmit(context.ExtraNonce1, extraNonce2, nTime, nonce)) - throw new StratumException(StratumError.DuplicateShare, "duplicate share"); + throw new StratumException(StratumError.DuplicateShare, $"duplicate share [{nonce}]"); return ProcessShareInternal(worker, extraNonce2); } - public void Init(ErgoBlockTemplate blockTemplate, int blockVersion, string jobId) + public void Init(ErgoBlockTemplate blockTemplate, int blockVersion, int extraNonceBytes, string jobId) { + this.extraNonceBytes = extraNonceBytes; + BlockTemplate = blockTemplate; JobId = jobId; target = new Target(BigInteger.Parse(BlockTemplate.Work.B, NumberStyles.Integer)); diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs index fa899398a..ef4743d45 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs @@ -1,13 +1,10 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Net.Http; using System.Reactive.Linq; -using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Autofac; -using Miningcore.Blockchain.Bitcoin; using Miningcore.Blockchain.Ergo.Configuration; using NLog; using Miningcore.Configuration; @@ -30,27 +27,22 @@ public ErgoJobManager( IComponentContext ctx, IMessageBus messageBus, IMasterClock clock, - IHttpClientFactory httpClientFactory, IExtraNonceProvider extraNonceProvider) : base(ctx, messageBus) { - Contract.RequiresNonNull(httpClientFactory, nameof(httpClientFactory)); Contract.RequiresNonNull(clock, nameof(clock)); + Contract.RequiresNonNull(extraNonceProvider, nameof(extraNonceProvider)); this.clock = clock; this.extraNonceProvider = extraNonceProvider; - this.httpClientFactory = httpClientFactory; } private ErgoCoinTemplate coin; private ErgoClient daemon; private string network; - private TimeSpan jobRebroadcastTimeout; - private DateTime? lastJobRebroadcast; private readonly List<ErgoJob> validJobs = new(); private int maxActiveJobs = 4; private const int ExtranonceBytes = 4; - private readonly IHttpClientFactory httpClientFactory; private readonly IExtraNonceProvider extraNonceProvider; private readonly IMasterClock clock; private ErgoPoolConfigExtra extraPoolConfig; @@ -58,7 +50,6 @@ public ErgoJobManager( private void SetupJobUpdates() { - jobRebroadcastTimeout = TimeSpan.FromSeconds(Math.Max(1, poolConfig.JobRebroadcastTimeout)); var blockFound = blockFoundSubject.Synchronize(); var pollTimerRestart = blockFoundSubject.Synchronize(); @@ -87,42 +78,16 @@ private void SetupJobUpdates() .Select(_ => (false, JobRefreshBy.Initial, (string) null)) .TakeWhile(_ => !hasInitialBlockTemplate)); } - - // periodically update transactions for current template - if(poolConfig.JobRebroadcastTimeout > 0) - { - triggers.Add(Observable.Timer(jobRebroadcastTimeout) - .TakeUntil(pollTimerRestart) - .Select(_ => (true, JobRefreshBy.PollRefresh, (string) null)) - .Repeat()); - } } else { var btStream = BtStreamSubscribe(extraPoolConfig.BtStream); - if(poolConfig.JobRebroadcastTimeout > 0) - { - var interval = TimeSpan.FromSeconds(Math.Max(1, poolConfig.JobRebroadcastTimeout - 0.1d)); - - triggers.Add(btStream - .Select(json => - { - var force = !lastJobRebroadcast.HasValue || (clock.Now - lastJobRebroadcast >= interval); - return (force, !force ? JobRefreshBy.BlockTemplateStream : JobRefreshBy.BlockTemplateStreamRefresh, json); - }) - .Publish() - .RefCount()); - } - - else - { - triggers.Add(btStream - .Select(json => (false, JobRefreshBy.BlockTemplateStream, json)) - .Publish() - .RefCount()); - } + triggers.Add(btStream + .Select(json => (false, JobRefreshBy.BlockTemplateStream, json)) + .Publish() + .RefCount()); // get initial blocktemplate triggers.Add(Observable.Interval(TimeSpan.FromMilliseconds(1000)) @@ -150,9 +115,6 @@ private void SetupJobUpdates() try { - if(forceUpdate) - lastJobRebroadcast = clock.Now; - var blockTemplate = string.IsNullOrEmpty(json) ? await GetBlockTemplateAsync() : GetBlockTemplateFromJson(json); @@ -171,7 +133,7 @@ await GetBlockTemplateAsync() : { job = new ErgoJob(); - job.Init(blockTemplate, blockVersion, NextJobId()); + job.Init(blockTemplate, blockVersion, ExtranonceBytes, NextJobId()); lock(jobLock) { @@ -347,7 +309,7 @@ public object[] GetSubscriberData(StratumConnection worker) var responseData = new object[] { context.ExtraNonce1, - BitcoinConstants.ExtranoncePlaceHolderLength - ExtranonceBytes, + ExtranonceBytes, }; return responseData; diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index f514e776a..c97bc3980 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -267,7 +267,16 @@ public virtual async Task PayoutAsync(Balance[] balances) // unlock wallet await Guard(() => daemon.WalletUnlockAsync(new Body4 {Pass = extraPoolPaymentProcessingConfig.WalletPassword ?? string.Empty}), - ex => ReportAndRethrowApiError("Failed to unlock wallet", ex)); + ex => + { + if(ex is ApiException<ApiError> apiException) + { + if(apiException.Result.Detail?.ToLower()?.Contains("already unlocked") == true) + return; + } + + ReportAndRethrowApiError("Failed to unlock wallet", ex); + }); try { diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs index a3abc3a2f..21d1965f9 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs @@ -119,12 +119,10 @@ protected virtual async Task OnAuthorizeAsync(StratumConnection connection, Time context.SetDifficulty(staticDiff.Value); logger.Info(() => $"[{connection.ConnectionId}] Setting static difficulty of {staticDiff.Value}"); - - await connection.NotifyAsync(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty }); } // send intial update - await connection.NotifyAsync(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty }); + await connection.NotifyAsync(BitcoinStratumMethods.SetDifficulty, new object[] { 1 }); await SendJob(connection, context, currentJobParams); } @@ -183,7 +181,7 @@ protected virtual async Task OnSubmitAsync(StratumConnection connection, Timesta // telemetry PublishTelemetry(TelemetryCategory.Share, clock.Now - tsRequest.Timestamp.UtcDateTime, true); - logger.Info(() => $"[{connection.ConnectionId}] Share accepted: D={Math.Round(share.Difficulty * ErgoConstants.DiffMultiplier, 3)}"); + logger.Info(() => $"[{connection.ConnectionId}] Share accepted: D={Math.Round(share.Difficulty * ErgoConstants.DiffMultiplier, 3)} [{requestParams[4]}]"); // update pool stats if(share.IsBlockCandidate) @@ -238,9 +236,10 @@ protected virtual async Task OnNewJobAsync(object[] jobParams) // varDiff: if the client has a pending difficulty change, apply it now if(context.ApplyPendingDifficulty()) - await connection.NotifyAsync(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty }); - - await SendJob(connection, context, currentJobParams); + { + await connection.NotifyAsync(BitcoinStratumMethods.SetDifficulty, new object[] {1}); + await SendJob(connection, context, currentJobParams); + } } }); From d7994f1e72d1ab859e9ec3772edf176e173c353f Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Fri, 9 Jul 2021 15:53:47 +0200 Subject: [PATCH 025/145] Ergo extranonce size --- src/Miningcore/Blockchain/Abstractions.cs | 1 + .../Ergo/Configuration/ErgoPoolConfigExtra.cs | 2 ++ .../Blockchain/Ergo/ErgoExtraNonceProvider.cs | 9 +++++++++ src/Miningcore/Blockchain/Ergo/ErgoJob.cs | 2 +- src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs | 4 +++- src/Miningcore/Blockchain/Ergo/ErgoPool.cs | 10 ++++++++-- src/Miningcore/Blockchain/ExtraNonceProviderBase.cs | 2 ++ 7 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 src/Miningcore/Blockchain/Ergo/ErgoExtraNonceProvider.cs diff --git a/src/Miningcore/Blockchain/Abstractions.cs b/src/Miningcore/Blockchain/Abstractions.cs index 09f73a660..61d99eeb3 100644 --- a/src/Miningcore/Blockchain/Abstractions.cs +++ b/src/Miningcore/Blockchain/Abstractions.cs @@ -17,6 +17,7 @@ public class BlockchainStats public interface IExtraNonceProvider { + int ByteSize { get; } string Next(); } } diff --git a/src/Miningcore/Blockchain/Ergo/Configuration/ErgoPoolConfigExtra.cs b/src/Miningcore/Blockchain/Ergo/Configuration/ErgoPoolConfigExtra.cs index 309784738..81673584a 100644 --- a/src/Miningcore/Blockchain/Ergo/Configuration/ErgoPoolConfigExtra.cs +++ b/src/Miningcore/Blockchain/Ergo/Configuration/ErgoPoolConfigExtra.cs @@ -15,6 +15,8 @@ public class ErgoPoolConfigExtra /// </summary> public ZmqPubSubEndpointConfig BtStream { get; set; } + public int? ExtraNonce1Size { get; set; } + public int? MinimumConfirmations { get; set; } } } diff --git a/src/Miningcore/Blockchain/Ergo/ErgoExtraNonceProvider.cs b/src/Miningcore/Blockchain/Ergo/ErgoExtraNonceProvider.cs new file mode 100644 index 000000000..5e7d68beb --- /dev/null +++ b/src/Miningcore/Blockchain/Ergo/ErgoExtraNonceProvider.cs @@ -0,0 +1,9 @@ +namespace Miningcore.Blockchain.Ergo +{ + public class ErgoExtraNonceProvider : ExtraNonceProviderBase + { + public ErgoExtraNonceProvider(int size, byte? clusterInstanceId) : base(size, clusterInstanceId) + { + } + } +} diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs index 13c387d2a..cfbfc11a8 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs @@ -223,7 +223,7 @@ public virtual Share ProcessShare(StratumConnection worker, string extraNonce2, // dupe check if(!RegisterSubmit(context.ExtraNonce1, extraNonce2, nTime, nonce)) - throw new StratumException(StratumError.DuplicateShare, $"duplicate share [{nonce}]"); + throw new StratumException(StratumError.DuplicateShare, $"duplicate share"); return ProcessShareInternal(worker, extraNonce2); } diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs index ef4743d45..f2b95e61b 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs @@ -35,6 +35,8 @@ public ErgoJobManager( this.clock = clock; this.extraNonceProvider = extraNonceProvider; + + ExtranonceBytes = 8 - extraNonceProvider.ByteSize; } private ErgoCoinTemplate coin; @@ -42,7 +44,7 @@ public ErgoJobManager( private string network; private readonly List<ErgoJob> validJobs = new(); private int maxActiveJobs = 4; - private const int ExtranonceBytes = 4; + private readonly int ExtranonceBytes; private readonly IExtraNonceProvider extraNonceProvider; private readonly IMasterClock clock; private ErgoPoolConfigExtra extraPoolConfig; diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs index 21d1965f9..80b06fdf5 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs @@ -9,7 +9,9 @@ using Autofac; using AutoMapper; using Miningcore.Blockchain.Bitcoin; +using Miningcore.Blockchain.Ergo.Configuration; using Miningcore.Configuration; +using Miningcore.Extensions; using Miningcore.JsonRpc; using Miningcore.Messaging; using Miningcore.Mining; @@ -43,6 +45,7 @@ public ErgoPool(IComponentContext ctx, protected object[] currentJobParams; protected ErgoJobManager manager; + private ErgoPoolConfigExtra extraPoolConfig; private ErgoCoinTemplate coin; protected virtual async Task OnSubscribeAsync(StratumConnection connection, Timestamped<JsonRpcRequest> tsRequest) @@ -181,7 +184,7 @@ protected virtual async Task OnSubmitAsync(StratumConnection connection, Timesta // telemetry PublishTelemetry(TelemetryCategory.Share, clock.Now - tsRequest.Timestamp.UtcDateTime, true); - logger.Info(() => $"[{connection.ConnectionId}] Share accepted: D={Math.Round(share.Difficulty * ErgoConstants.DiffMultiplier, 3)} [{requestParams[4]}]"); + logger.Info(() => $"[{connection.ConnectionId}] Share accepted: D={Math.Round(share.Difficulty * ErgoConstants.DiffMultiplier, 3)}"); // update pool stats if(share.IsBlockCandidate) @@ -290,14 +293,17 @@ public override double HashrateFromShares(double shares, double interval) public override void Configure(PoolConfig poolConfig, ClusterConfig clusterConfig) { coin = poolConfig.Template.As<ErgoCoinTemplate>(); + extraPoolConfig = poolConfig.Extra.SafeExtensionDataAs<ErgoPoolConfigExtra>(); base.Configure(poolConfig, clusterConfig); } protected override async Task SetupJobManager(CancellationToken ct) { + var extraNonce1Size = extraPoolConfig?.ExtraNonce1Size ?? 2; + manager = ctx.Resolve<ErgoJobManager>( - new TypedParameter(typeof(IExtraNonceProvider), new BitcoinExtraNonceProvider(clusterConfig.InstanceId))); + new TypedParameter(typeof(IExtraNonceProvider), new ErgoExtraNonceProvider(extraNonce1Size, clusterConfig.InstanceId))); manager.Configure(poolConfig, clusterConfig); diff --git a/src/Miningcore/Blockchain/ExtraNonceProviderBase.cs b/src/Miningcore/Blockchain/ExtraNonceProviderBase.cs index b8df73afc..2f0ddf7c6 100644 --- a/src/Miningcore/Blockchain/ExtraNonceProviderBase.cs +++ b/src/Miningcore/Blockchain/ExtraNonceProviderBase.cs @@ -42,6 +42,8 @@ public ExtraNonceProviderBase(int extranonceBytes, byte? instanceId) #region IExtraNonceProvider + public int ByteSize => extranonceBytes; + public string Next() { ulong value; From ff500356e175bf5871e645a5eda70f6ddc6f6895 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Fri, 9 Jul 2021 16:42:46 +0200 Subject: [PATCH 026/145] Simplify submissions --- src/Miningcore/Blockchain/Ergo/ErgoJob.cs | 37 +++++++++-------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs index cfbfc11a8..da983afe1 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs @@ -57,31 +57,22 @@ public static uint CalcN(uint height) return step; } - protected bool RegisterSubmit(string extraNonce1, string extraNonce2, string nTime, string nonce) + protected bool RegisterSubmit(string nTime, string nonce) { - var key = nonce; - - //var key = new StringBuilder() - // .Append(extraNonce1) - // .Append(extraNonce2) - // .Append(nTime) - // .Append(nonce) - // .ToString(); + var key = new StringBuilder() + .Append(nTime) + .Append(nonce) + .ToString(); return submissions.TryAdd(key, true); } - protected virtual byte[] SerializeCoinbase(string msg, string extraNonce1, string extraNonce2) + protected virtual byte[] SerializeCoinbase(string msg, string nonce) { - var msgBytes = msg.HexToByteArray(); - var extraNonce1Bytes = extraNonce1.HexToByteArray(); - var extraNonce2Bytes = extraNonce2.HexToByteArray(); - using(var stream = new MemoryStream()) { - stream.Write(msgBytes); - stream.Write(extraNonce1Bytes); - stream.Write(extraNonce2Bytes); + stream.Write(msg.HexToByteArray()); + stream.Write(nonce.HexToByteArray()); return stream.ToArray(); } @@ -111,13 +102,12 @@ private BigInteger[] GenIndexes(byte[] seed, uint height) return result; } - protected virtual Share ProcessShareInternal(StratumConnection worker, string extraNonce2) + protected virtual Share ProcessShareInternal(StratumConnection worker, string nonce) { var context = worker.ContextAs<ErgoWorkerContext>(); - var extraNonce1 = context.ExtraNonce1; // hash coinbase - var coinbase = SerializeCoinbase(BlockTemplate.Work.Msg, extraNonce1, extraNonce2); + var coinbase = SerializeCoinbase(BlockTemplate.Work.Msg, nonce); Span<byte> hashResult = stackalloc byte[32]; hasher.Digest(coinbase, hashResult); @@ -217,15 +207,18 @@ public virtual Share ProcessShare(StratumConnection worker, string extraNonce2, if(nonce.Length != context.ExtraNonce1.Length + extraNonceBytes * 2) throw new StratumException(StratumError.Other, "incorrect size of nonce"); + if(!nonce.StartsWith(context.ExtraNonce1)) + throw new StratumException(StratumError.Other, "incorrect extraNonce2 in nonce"); + // currently unused if(nTime == "undefined") nTime = string.Empty; // dupe check - if(!RegisterSubmit(context.ExtraNonce1, extraNonce2, nTime, nonce)) + if(!RegisterSubmit(nTime, nonce)) throw new StratumException(StratumError.DuplicateShare, $"duplicate share"); - return ProcessShareInternal(worker, extraNonce2); + return ProcessShareInternal(worker, nonce); } public void Init(ErgoBlockTemplate blockTemplate, int blockVersion, int extraNonceBytes, string jobId) From a7b6853ee0e5ce45ba6cbcc8a85d22ad77a5f4dc Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Fri, 9 Jul 2021 18:36:18 +0200 Subject: [PATCH 027/145] Id Bits --- src/Miningcore/Blockchain/Ergo/ErgoJob.cs | 13 +++++-------- src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs | 8 ++++---- src/Miningcore/Blockchain/ExtraNonceProviderBase.cs | 2 +- src/Miningcore/coins.json | 2 +- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs index da983afe1..557d3578c 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs @@ -10,10 +10,7 @@ using Miningcore.Extensions; using Miningcore.Stratum; using System.Numerics; -using Miningcore.Blockchain.Bitcoin; -using Miningcore.Util; using NBitcoin; -using NLog; namespace Miningcore.Blockchain.Ergo { @@ -27,7 +24,7 @@ public class ErgoJob private object[] jobParams; private readonly ConcurrentDictionary<string, bool> submissions = new(StringComparer.OrdinalIgnoreCase); private static readonly IHashAlgorithm hasher = new Blake2b(); - private int extraNonceBytes; + private int extraNonceSize; private BigInteger B; private static readonly uint nBase = (uint) Math.Pow(2, 26); @@ -204,11 +201,11 @@ public virtual Share ProcessShare(StratumConnection worker, string extraNonce2, var context = worker.ContextAs<ErgoWorkerContext>(); // validate nonce - if(nonce.Length != context.ExtraNonce1.Length + extraNonceBytes * 2) + if(nonce.Length != context.ExtraNonce1.Length + extraNonceSize * 2) throw new StratumException(StratumError.Other, "incorrect size of nonce"); if(!nonce.StartsWith(context.ExtraNonce1)) - throw new StratumException(StratumError.Other, "incorrect extraNonce2 in nonce"); + throw new StratumException(StratumError.Other, $"incorrect extraNonce2 in nonce (expected {context.ExtraNonce1}, got {nonce.Substring(0, Math.Min(nonce.Length, context.ExtraNonce1.Length))})"); // currently unused if(nTime == "undefined") @@ -221,9 +218,9 @@ public virtual Share ProcessShare(StratumConnection worker, string extraNonce2, return ProcessShareInternal(worker, nonce); } - public void Init(ErgoBlockTemplate blockTemplate, int blockVersion, int extraNonceBytes, string jobId) + public void Init(ErgoBlockTemplate blockTemplate, int blockVersion, int extraNonceSize, string jobId) { - this.extraNonceBytes = extraNonceBytes; + this.extraNonceSize = extraNonceSize; BlockTemplate = blockTemplate; JobId = jobId; diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs index f2b95e61b..2c8f99987 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs @@ -36,7 +36,7 @@ public ErgoJobManager( this.clock = clock; this.extraNonceProvider = extraNonceProvider; - ExtranonceBytes = 8 - extraNonceProvider.ByteSize; + extraNonceSize = 8 - extraNonceProvider.ByteSize; } private ErgoCoinTemplate coin; @@ -44,7 +44,7 @@ public ErgoJobManager( private string network; private readonly List<ErgoJob> validJobs = new(); private int maxActiveJobs = 4; - private readonly int ExtranonceBytes; + private readonly int extraNonceSize; private readonly IExtraNonceProvider extraNonceProvider; private readonly IMasterClock clock; private ErgoPoolConfigExtra extraPoolConfig; @@ -135,7 +135,7 @@ await GetBlockTemplateAsync() : { job = new ErgoJob(); - job.Init(blockTemplate, blockVersion, ExtranonceBytes, NextJobId()); + job.Init(blockTemplate, blockVersion, extraNonceSize, NextJobId()); lock(jobLock) { @@ -311,7 +311,7 @@ public object[] GetSubscriberData(StratumConnection worker) var responseData = new object[] { context.ExtraNonce1, - ExtranonceBytes, + extraNonceSize, }; return responseData; diff --git a/src/Miningcore/Blockchain/ExtraNonceProviderBase.cs b/src/Miningcore/Blockchain/ExtraNonceProviderBase.cs index 2f0ddf7c6..fd73a69e7 100644 --- a/src/Miningcore/Blockchain/ExtraNonceProviderBase.cs +++ b/src/Miningcore/Blockchain/ExtraNonceProviderBase.cs @@ -31,7 +31,7 @@ public ExtraNonceProviderBase(int extranonceBytes, byte? instanceId) counter = 0; } - private const int IdBits = 5; + private const int IdBits = 4; private readonly object counterLock = new(); protected ulong counter; protected byte id; diff --git a/src/Miningcore/coins.json b/src/Miningcore/coins.json index 70d9f8a9a..9ec81fa07 100644 --- a/src/Miningcore/coins.json +++ b/src/Miningcore/coins.json @@ -1489,7 +1489,7 @@ "explorerAccountLink": "https://explorer.callisto.network/address/{0}" }, "ergo": { - "name": "ergo", + "name": "Ergo", "symbol": "ERG", "family": "ergo", "explorerBlockLink": "https://explorer.ergoplatform.com/en/blocks/$hash$", From fc90ce46a5318231db30a544fc5587014d173813 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Fri, 9 Jul 2021 18:39:16 +0200 Subject: [PATCH 028/145] Extra nonce overflow log warning --- src/Miningcore/Blockchain/ExtraNonceProviderBase.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Miningcore/Blockchain/ExtraNonceProviderBase.cs b/src/Miningcore/Blockchain/ExtraNonceProviderBase.cs index fd73a69e7..2ff74f5f7 100644 --- a/src/Miningcore/Blockchain/ExtraNonceProviderBase.cs +++ b/src/Miningcore/Blockchain/ExtraNonceProviderBase.cs @@ -1,5 +1,6 @@ using System; using System.Security.Cryptography; +using NLog; namespace Miningcore.Blockchain { @@ -31,6 +32,8 @@ public ExtraNonceProviderBase(int extranonceBytes, byte? instanceId) counter = 0; } + private static readonly ILogger logger = LogManager.GetCurrentClassLogger(); + private const int IdBits = 4; private readonly object counterLock = new(); protected ulong counter; @@ -51,8 +54,13 @@ public string Next() lock(counterLock) { counter++; + if(counter > nonceMax) + { + logger.Warn(()=> $"Range exhausted. Rolling over to 0."); + counter = 0; + } // encode to hex value = ((ulong) id << idShift) | counter; From e4c1f62ba235a942779f1a35b8d7e88aab9670c1 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Fri, 9 Jul 2021 18:50:51 +0200 Subject: [PATCH 029/145] Extranonce provider logging --- .../Blockchain/Bitcoin/BitcoinExtraNonceProvider.cs | 2 +- src/Miningcore/Blockchain/Bitcoin/BitcoinPool.cs | 2 +- .../Blockchain/Equihash/EquihashExtraNonceProvider.cs | 2 +- src/Miningcore/Blockchain/Equihash/EquihashPool.cs | 2 +- .../Blockchain/Ergo/ErgoExtraNonceProvider.cs | 2 +- src/Miningcore/Blockchain/Ergo/ErgoPool.cs | 2 +- .../Blockchain/Ethereum/EthereumExtraNonceProvider.cs | 2 +- src/Miningcore/Blockchain/Ethereum/EthereumPool.cs | 2 +- src/Miningcore/Blockchain/ExtraNonceProviderBase.cs | 11 ++++++++--- src/Miningcore/Util/LogUtil.cs | 5 +++++ 10 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinExtraNonceProvider.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinExtraNonceProvider.cs index 160757d2b..b504bb19a 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinExtraNonceProvider.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinExtraNonceProvider.cs @@ -6,7 +6,7 @@ namespace Miningcore.Blockchain.Bitcoin { public class BitcoinExtraNonceProvider : ExtraNonceProviderBase { - public BitcoinExtraNonceProvider(byte? clusterInstanceId) : base(4, clusterInstanceId) + public BitcoinExtraNonceProvider(string poolId, byte? clusterInstanceId) : base(poolId, 4, clusterInstanceId) { } } diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinPool.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinPool.cs index 29554652e..9c697b29c 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinPool.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinPool.cs @@ -374,7 +374,7 @@ public override void Configure(PoolConfig poolConfig, ClusterConfig clusterConfi protected override async Task SetupJobManager(CancellationToken ct) { manager = ctx.Resolve<BitcoinJobManager>( - new TypedParameter(typeof(IExtraNonceProvider), new BitcoinExtraNonceProvider(clusterConfig.InstanceId))); + new TypedParameter(typeof(IExtraNonceProvider), new BitcoinExtraNonceProvider(poolConfig.Id, clusterConfig.InstanceId))); manager.Configure(poolConfig, clusterConfig); diff --git a/src/Miningcore/Blockchain/Equihash/EquihashExtraNonceProvider.cs b/src/Miningcore/Blockchain/Equihash/EquihashExtraNonceProvider.cs index 23401680f..cac1ad40b 100644 --- a/src/Miningcore/Blockchain/Equihash/EquihashExtraNonceProvider.cs +++ b/src/Miningcore/Blockchain/Equihash/EquihashExtraNonceProvider.cs @@ -7,7 +7,7 @@ namespace Miningcore.Blockchain.Equihash { public class EquihashExtraNonceProvider : ExtraNonceProviderBase { - public EquihashExtraNonceProvider(byte? clusterInstanceId) : base(3, clusterInstanceId) + public EquihashExtraNonceProvider(string poolId, byte? clusterInstanceId) : base(poolId, 3, clusterInstanceId) { } } diff --git a/src/Miningcore/Blockchain/Equihash/EquihashPool.cs b/src/Miningcore/Blockchain/Equihash/EquihashPool.cs index 8b678e583..2d47eef7d 100644 --- a/src/Miningcore/Blockchain/Equihash/EquihashPool.cs +++ b/src/Miningcore/Blockchain/Equihash/EquihashPool.cs @@ -63,7 +63,7 @@ public override void Configure(PoolConfig poolConfig, ClusterConfig clusterConfi protected override async Task SetupJobManager(CancellationToken ct) { manager = ctx.Resolve<EquihashJobManager>( - new TypedParameter(typeof(IExtraNonceProvider), new EquihashExtraNonceProvider(clusterConfig.InstanceId))); + new TypedParameter(typeof(IExtraNonceProvider), new EquihashExtraNonceProvider(poolConfig.Id, clusterConfig.InstanceId))); manager.Configure(poolConfig, clusterConfig); diff --git a/src/Miningcore/Blockchain/Ergo/ErgoExtraNonceProvider.cs b/src/Miningcore/Blockchain/Ergo/ErgoExtraNonceProvider.cs index 5e7d68beb..eea4731f6 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoExtraNonceProvider.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoExtraNonceProvider.cs @@ -2,7 +2,7 @@ namespace Miningcore.Blockchain.Ergo { public class ErgoExtraNonceProvider : ExtraNonceProviderBase { - public ErgoExtraNonceProvider(int size, byte? clusterInstanceId) : base(size, clusterInstanceId) + public ErgoExtraNonceProvider(string poolId, int size, byte? clusterInstanceId) : base(poolId, size, clusterInstanceId) { } } diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs index 80b06fdf5..1904d33b4 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs @@ -303,7 +303,7 @@ protected override async Task SetupJobManager(CancellationToken ct) var extraNonce1Size = extraPoolConfig?.ExtraNonce1Size ?? 2; manager = ctx.Resolve<ErgoJobManager>( - new TypedParameter(typeof(IExtraNonceProvider), new ErgoExtraNonceProvider(extraNonce1Size, clusterConfig.InstanceId))); + new TypedParameter(typeof(IExtraNonceProvider), new ErgoExtraNonceProvider(poolConfig.Id, extraNonce1Size, clusterConfig.InstanceId))); manager.Configure(poolConfig, clusterConfig); diff --git a/src/Miningcore/Blockchain/Ethereum/EthereumExtraNonceProvider.cs b/src/Miningcore/Blockchain/Ethereum/EthereumExtraNonceProvider.cs index 6f28ff2b8..38f452216 100644 --- a/src/Miningcore/Blockchain/Ethereum/EthereumExtraNonceProvider.cs +++ b/src/Miningcore/Blockchain/Ethereum/EthereumExtraNonceProvider.cs @@ -9,7 +9,7 @@ namespace Miningcore.Blockchain.Ethereum { public class EthereumExtraNonceProvider : ExtraNonceProviderBase { - public EthereumExtraNonceProvider(byte? clusterInstanceId) : base(2, clusterInstanceId) + public EthereumExtraNonceProvider(string poolId, byte? clusterInstanceId) : base(poolId, 2, clusterInstanceId) { } } diff --git a/src/Miningcore/Blockchain/Ethereum/EthereumPool.cs b/src/Miningcore/Blockchain/Ethereum/EthereumPool.cs index 6646238f6..f8f0c8d6f 100644 --- a/src/Miningcore/Blockchain/Ethereum/EthereumPool.cs +++ b/src/Miningcore/Blockchain/Ethereum/EthereumPool.cs @@ -282,7 +282,7 @@ public override void Configure(PoolConfig poolConfig, ClusterConfig clusterConfi protected override async Task SetupJobManager(CancellationToken ct) { manager = ctx.Resolve<EthereumJobManager>( - new TypedParameter(typeof(IExtraNonceProvider), new EthereumExtraNonceProvider(clusterConfig.InstanceId))); + new TypedParameter(typeof(IExtraNonceProvider), new EthereumExtraNonceProvider(poolConfig.Id, clusterConfig.InstanceId))); manager.Configure(poolConfig, clusterConfig); diff --git a/src/Miningcore/Blockchain/ExtraNonceProviderBase.cs b/src/Miningcore/Blockchain/ExtraNonceProviderBase.cs index 2ff74f5f7..d524bf062 100644 --- a/src/Miningcore/Blockchain/ExtraNonceProviderBase.cs +++ b/src/Miningcore/Blockchain/ExtraNonceProviderBase.cs @@ -1,13 +1,16 @@ using System; using System.Security.Cryptography; +using Miningcore.Util; using NLog; namespace Miningcore.Blockchain { public class ExtraNonceProviderBase : IExtraNonceProvider { - public ExtraNonceProviderBase(int extranonceBytes, byte? instanceId) + public ExtraNonceProviderBase(string poolId, int extranonceBytes, byte? instanceId) { + this.logger = LogUtil.GetPoolScopedLogger(this.GetType(), poolId); + this.extranonceBytes = extranonceBytes; idShift = (extranonceBytes * 8) - IdBits; nonceMax = (1UL << idShift) - 1; @@ -30,9 +33,11 @@ public ExtraNonceProviderBase(int extranonceBytes, byte? instanceId) id = (byte) (id & mask); counter = 0; + + logger.Info(()=> $"ExtraNonceProvider using {IdBits} bits for instance, {extranonceBytes * 8 - IdBits} bits for values ({nonceMax} maximum), instance = 0x{id:X}"); } - private static readonly ILogger logger = LogManager.GetCurrentClassLogger(); + private readonly ILogger logger; private const int IdBits = 4; private readonly object counterLock = new(); @@ -57,7 +62,7 @@ public string Next() if(counter > nonceMax) { - logger.Warn(()=> $"Range exhausted. Rolling over to 0."); + logger.Warn(()=> $"ExtraNonceProvider Range exhausted. Rolling over to 0."); counter = 0; } diff --git a/src/Miningcore/Util/LogUtil.cs b/src/Miningcore/Util/LogUtil.cs index a45c3dc87..df26e2536 100644 --- a/src/Miningcore/Util/LogUtil.cs +++ b/src/Miningcore/Util/LogUtil.cs @@ -13,6 +13,11 @@ public static ILogger GetPoolScopedLogger(Type type, PoolConfig poolConfig) return LogManager.GetLogger(poolConfig.Id); } + public static ILogger GetPoolScopedLogger(Type type, string poolId) + { + return LogManager.GetLogger(poolId); + } + public static void ThrowLogPoolStartupException(this ILogger logger, string msg, string category = null) { var output = !string.IsNullOrEmpty(category) ? $"[{category}] {msg}" : msg; From 419e8b9d0b033979325bbe954635eb17a35f45f0 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Fri, 9 Jul 2021 18:53:23 +0200 Subject: [PATCH 030/145] Logging --- src/Miningcore/Blockchain/ExtraNonceProviderBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Miningcore/Blockchain/ExtraNonceProviderBase.cs b/src/Miningcore/Blockchain/ExtraNonceProviderBase.cs index d524bf062..1c367e8a8 100644 --- a/src/Miningcore/Blockchain/ExtraNonceProviderBase.cs +++ b/src/Miningcore/Blockchain/ExtraNonceProviderBase.cs @@ -34,7 +34,7 @@ public ExtraNonceProviderBase(string poolId, int extranonceBytes, byte? instance id = (byte) (id & mask); counter = 0; - logger.Info(()=> $"ExtraNonceProvider using {IdBits} bits for instance, {extranonceBytes * 8 - IdBits} bits for values ({nonceMax} maximum), instance = 0x{id:X}"); + logger.Info(()=> $"ExtraNonceProvider using {IdBits} bits for instance, {extranonceBytes * 8 - IdBits} bits for {nonceMax} values, instance = 0x{id:X}"); } private readonly ILogger logger; From 4ea03d35249a8203f14413759bceba3ce59eed45 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Fri, 9 Jul 2021 18:56:29 +0200 Subject: [PATCH 031/145] WIP --- src/Miningcore/Blockchain/ExtraNonceProviderBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Miningcore/Blockchain/ExtraNonceProviderBase.cs b/src/Miningcore/Blockchain/ExtraNonceProviderBase.cs index 1c367e8a8..73afb094c 100644 --- a/src/Miningcore/Blockchain/ExtraNonceProviderBase.cs +++ b/src/Miningcore/Blockchain/ExtraNonceProviderBase.cs @@ -62,7 +62,7 @@ public string Next() if(counter > nonceMax) { - logger.Warn(()=> $"ExtraNonceProvider Range exhausted. Rolling over to 0."); + logger.Warn(()=> $"ExtraNonceProvider range exhausted! Rolling over to 0."); counter = 0; } From 93a32564961b80158d9d13d3af6140e1ad57ce95 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Fri, 9 Jul 2021 21:25:05 +0200 Subject: [PATCH 032/145] instance id size validation --- src/Miningcore/Blockchain/ExtraNonceProviderBase.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Miningcore/Blockchain/ExtraNonceProviderBase.cs b/src/Miningcore/Blockchain/ExtraNonceProviderBase.cs index 73afb094c..1a6a0504b 100644 --- a/src/Miningcore/Blockchain/ExtraNonceProviderBase.cs +++ b/src/Miningcore/Blockchain/ExtraNonceProviderBase.cs @@ -14,13 +14,20 @@ public ExtraNonceProviderBase(string poolId, int extranonceBytes, byte? instance this.extranonceBytes = extranonceBytes; idShift = (extranonceBytes * 8) - IdBits; nonceMax = (1UL << idShift) - 1; + idMax = (1U << IdBits) - 1; stringFormat = "x" + extranonceBytes * 2; // generate instanceId if not provided var mask = (1L << IdBits) - 1; if(instanceId.HasValue) + { id = instanceId.Value; + + if(id > idMax) + logger.ThrowLogPoolStartupException($"Provided instance id to large to fit into {IdBits} bits (limit {idMax})"); + } + else { using(var rng = RandomNumberGenerator.Create()) @@ -34,7 +41,7 @@ public ExtraNonceProviderBase(string poolId, int extranonceBytes, byte? instance id = (byte) (id & mask); counter = 0; - logger.Info(()=> $"ExtraNonceProvider using {IdBits} bits for instance, {extranonceBytes * 8 - IdBits} bits for {nonceMax} values, instance = 0x{id:X}"); + logger.Info(()=> $"ExtraNonceProvider using {IdBits} bits for instance id, {extranonceBytes * 8 - IdBits} bits for {nonceMax} values, instance id = 0x{id:X}"); } private readonly ILogger logger; @@ -45,6 +52,7 @@ public ExtraNonceProviderBase(string poolId, int extranonceBytes, byte? instance protected byte id; protected readonly int extranonceBytes; protected readonly int idShift; + protected readonly uint idMax; protected readonly ulong nonceMax; protected readonly string stringFormat; From 16db1ef3d83d2aa78a7526a911d624e7233e3bb8 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Sat, 10 Jul 2021 13:52:04 +0200 Subject: [PATCH 033/145] Tons of improvements --- .../Blockchain/Bitcoin/BitcoinPool.cs | 16 +++++- .../Bitcoin/BitcoinWorkerContext.cs | 7 ++- .../Blockchain/Cryptonote/CryptonotePool.cs | 15 +++++- .../Blockchain/Equihash/EquihashPool.cs | 15 +++++- src/Miningcore/Blockchain/Ergo/ErgoJob.cs | 30 +++++------ src/Miningcore/Blockchain/Ergo/ErgoPool.cs | 50 ++++++++++++------- .../Blockchain/Ergo/ErgoWorkerContext.cs | 12 ++--- .../Blockchain/Ethereum/EthereumPool.cs | 15 +++++- .../Ethereum/EthereumWorkerContext.cs | 3 ++ src/Miningcore/Mining/PoolBase.cs | 20 ++------ 10 files changed, 118 insertions(+), 65 deletions(-) diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinPool.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinPool.cs index 9c697b29c..b06091c97 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinPool.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinPool.cs @@ -112,7 +112,20 @@ protected virtual async Task OnAuthorizeAsync(StratumConnection connection, Time var staticDiff = GetStaticDiffFromPassparts(passParts); // Nicehash support - staticDiff = await GetNicehashStaticMinDiff(connection, context.UserAgent, staticDiff, coin.Name, coin.GetAlgorithmName()); + var nicehashDiff = await GetNicehashStaticMinDiff(connection, context.UserAgent, coin.Name, coin.GetAlgorithmName()); + + if(nicehashDiff.HasValue) + { + if(!staticDiff.HasValue || nicehashDiff > staticDiff) + { + logger.Info(() => $"[{connection.ConnectionId}] Nicehash detected. Using API supplied difficulty of {nicehashDiff.Value}"); + + staticDiff = nicehashDiff; + } + + else + logger.Info(() => $"[{connection.ConnectionId}] Nicehash detected. Using miner supplied difficulty of {staticDiff.Value}"); + } // Static diff if(staticDiff.HasValue && @@ -483,6 +496,7 @@ protected override async Task OnRequestAsync(StratumConnection connection, protected override async Task OnVarDiffUpdateAsync(StratumConnection connection, double newDiff) { var context = connection.ContextAs<BitcoinWorkerContext>(); + context.EnqueueNewDifficulty(newDiff); // apply immediately and notify client diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinWorkerContext.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinWorkerContext.cs index ddfd76c53..019c17a3c 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinWorkerContext.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinWorkerContext.cs @@ -9,11 +9,14 @@ public class BitcoinWorkerContext : WorkerContextBase /// </summary> public string Miner { get; set; } - /// <summary> - /// Arbitrary worker identififer for miners using multiple rigs + /// <summary> + /// Arbitrary worker identififer for miners using multiple rigs /// </summary> public string Worker { get; set; } + /// <summary> + /// Unique value assigned per worker + /// </summary> public string ExtraNonce1 { get; set; } /// <summary> diff --git a/src/Miningcore/Blockchain/Cryptonote/CryptonotePool.cs b/src/Miningcore/Blockchain/Cryptonote/CryptonotePool.cs index 6b6a2fb03..d494b6a4f 100644 --- a/src/Miningcore/Blockchain/Cryptonote/CryptonotePool.cs +++ b/src/Miningcore/Blockchain/Cryptonote/CryptonotePool.cs @@ -93,7 +93,20 @@ private async Task OnLoginAsync(StratumConnection connection, Timestamped<JsonRp var staticDiff = GetStaticDiffFromPassparts(passParts); // Nicehash support - staticDiff = await GetNicehashStaticMinDiff(connection, context.UserAgent, staticDiff, manager.Coin.Name, manager.Coin.GetAlgorithmName()); + var nicehashDiff = await GetNicehashStaticMinDiff(connection, context.UserAgent, manager.Coin.Name, manager.Coin.GetAlgorithmName()); + + if(nicehashDiff.HasValue) + { + if(!staticDiff.HasValue || nicehashDiff > staticDiff) + { + logger.Info(() => $"[{connection.ConnectionId}] Nicehash detected. Using API supplied difficulty of {nicehashDiff.Value}"); + + staticDiff = nicehashDiff; + } + + else + logger.Info(() => $"[{connection.ConnectionId}] Nicehash detected. Using miner supplied difficulty of {staticDiff.Value}"); + } // Static diff if(staticDiff.HasValue && diff --git a/src/Miningcore/Blockchain/Equihash/EquihashPool.cs b/src/Miningcore/Blockchain/Equihash/EquihashPool.cs index 2d47eef7d..a7d3639ad 100644 --- a/src/Miningcore/Blockchain/Equihash/EquihashPool.cs +++ b/src/Miningcore/Blockchain/Equihash/EquihashPool.cs @@ -169,7 +169,20 @@ protected async Task OnAuthorizeAsync(StratumConnection connection, Timestamped< var staticDiff = GetStaticDiffFromPassparts(passParts); // Nicehash support - staticDiff = await GetNicehashStaticMinDiff(connection, context.UserAgent, staticDiff, coin.Name, coin.GetAlgorithmName()); + var nicehashDiff = await GetNicehashStaticMinDiff(connection, context.UserAgent, coin.Name, coin.GetAlgorithmName()); + + if(nicehashDiff.HasValue) + { + if(!staticDiff.HasValue || nicehashDiff > staticDiff) + { + logger.Info(() => $"[{connection.ConnectionId}] Nicehash detected. Using API supplied difficulty of {nicehashDiff.Value}"); + + staticDiff = nicehashDiff; + } + + else + logger.Info(() => $"[{connection.ConnectionId}] Nicehash detected. Using miner supplied difficulty of {staticDiff.Value}"); + } // Static diff if(staticDiff.HasValue && diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs index 557d3578c..b58dedb0a 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs @@ -17,7 +17,7 @@ namespace Miningcore.Blockchain.Ergo public class ErgoJob { public ErgoBlockTemplate BlockTemplate { get; private set; } - public double Difficulty => target.Difficulty; + public double Difficulty => bTarget.Difficulty; public uint Height => BlockTemplate.Work.Height; public string JobId { get; protected set; } @@ -25,10 +25,10 @@ public class ErgoJob private readonly ConcurrentDictionary<string, bool> submissions = new(StringComparer.OrdinalIgnoreCase); private static readonly IHashAlgorithm hasher = new Blake2b(); private int extraNonceSize; - private BigInteger B; private static readonly uint nBase = (uint) Math.Pow(2, 26); - private Target target; + private Target bTarget; + private BigInteger b; private const uint IncreaseStart = 600 * 1024; private const uint IncreasePeriodForN = 50 * 1024; private const uint NIncreasementHeightMax = 9216000; @@ -140,32 +140,33 @@ protected virtual Share ProcessShareInternal(StratumConnection worker, string no // calculate fH var blockHash = f.ToByteArray(true, true).PadFront(0, 32); hasher.Digest(blockHash, hashResult); - var shareTarget = new Target(new BigInteger(hashResult, true, true)); + var fh = new BigInteger(hashResult, true, true); + var fhTarget = new Target(fh); // diff check - var stratumDifficulty = context.EffectiveDifficulty; - var ratio = shareTarget.Difficulty / stratumDifficulty; + var stratumDifficulty = context.Difficulty; + var ratio = fhTarget.Difficulty / stratumDifficulty; // check if the share meets the much harder block difficulty (block candidate) - var isBlockCandidate = target >= shareTarget; + var isBlockCandidate = fh < b; // test if share meets at least workers current difficulty if(!isBlockCandidate && ratio < 0.99) { // check if share matched the previous difficulty from before a vardiff retarget - if(context.VarDiff?.LastUpdate != null && context.PreviousEffectiveDifficulty.HasValue) + if(context.VarDiff?.LastUpdate != null && context.PreviousDifficulty.HasValue) { - ratio = shareTarget.Difficulty / context.PreviousEffectiveDifficulty.Value; + ratio = fhTarget.Difficulty / context.PreviousDifficulty.Value; if(ratio < 0.99) - throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareTarget.Difficulty})"); + throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({fhTarget.Difficulty})"); // use previous difficulty - stratumDifficulty = context.PreviousEffectiveDifficulty.Value; + stratumDifficulty = context.PreviousDifficulty.Value; } else - throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareTarget.Difficulty})"); + throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({fhTarget.Difficulty})"); } var result = new Share @@ -224,7 +225,8 @@ public void Init(ErgoBlockTemplate blockTemplate, int blockVersion, int extraNon BlockTemplate = blockTemplate; JobId = jobId; - target = new Target(BigInteger.Parse(BlockTemplate.Work.B, NumberStyles.Integer)); + b = BigInteger.Parse(BlockTemplate.Work.B, NumberStyles.Integer); + bTarget = new Target(b); jobParams = new object[] { @@ -234,7 +236,7 @@ public void Init(ErgoBlockTemplate blockTemplate, int blockVersion, int extraNon string.Empty, string.Empty, blockVersion, - target, + null, // to filled out by ErgoPool.SendJob string.Empty, false }; diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs index 1904d33b4..37b6545ea 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs @@ -111,7 +111,20 @@ protected virtual async Task OnAuthorizeAsync(StratumConnection connection, Time var staticDiff = GetStaticDiffFromPassparts(passParts); // Nicehash support - staticDiff = await GetNicehashStaticMinDiff(connection, context.UserAgent, staticDiff, coin.Name, coin.GetAlgorithmName()); + var nicehashDiff = await GetNicehashStaticMinDiff(connection, context.UserAgent, coin.Name, coin.GetAlgorithmName()); + + if(nicehashDiff.HasValue) + { + if(!staticDiff.HasValue || nicehashDiff > staticDiff) + { + logger.Info(() => $"[{connection.ConnectionId}] Nicehash detected. Using API supplied difficulty of {nicehashDiff.Value}"); + + staticDiff = nicehashDiff; + } + + else + logger.Info(() => $"[{connection.ConnectionId}] Nicehash detected. Using miner supplied difficulty of {staticDiff.Value}"); + } // Static diff if(staticDiff.HasValue && @@ -125,7 +138,6 @@ protected virtual async Task OnAuthorizeAsync(StratumConnection connection, Time } // send intial update - await connection.NotifyAsync(BitcoinStratumMethods.SetDifficulty, new object[] { 1 }); await SendJob(connection, context, currentJobParams); } @@ -239,10 +251,7 @@ protected virtual async Task OnNewJobAsync(object[] jobParams) // varDiff: if the client has a pending difficulty change, apply it now if(context.ApplyPendingDifficulty()) - { - await connection.NotifyAsync(BitcoinStratumMethods.SetDifficulty, new object[] {1}); await SendJob(connection, context, currentJobParams); - } } }); @@ -265,15 +274,13 @@ private async Task SendJob(StratumConnection connection, ErgoWorkerContext conte for(var i = 0; i < jobParamsActual.Length; i++) jobParamsActual[i] = jobParams[i]; - // emit target - var target = (Target) jobParamsActual[6]; - var tmp = ErgoConstants.ArtificialDiffCeiling / context.Difficulty; - var diff = new BigRational(target.ToBigInteger() * (BigInteger) (tmp * 256), 256).GetWholePart(); - jobParamsActual[6] = diff.ToString(); + var target = new BigRational(BitcoinConstants.Diff1 * (BigInteger) (1 / context.Difficulty * 0x10000), 0x10000).GetWholePart(); + jobParamsActual[6] = target.ToString(); - // also remember effective diff (based on target sent to miner), independent of artificial diff - context.EffectiveDifficulty = new Target(diff).Difficulty; + // send static diff of 1 since actual diff gets pre-multiplied to target + await connection.NotifyAsync(BitcoinStratumMethods.SetDifficulty, new object[] { 1 }); + // send target await connection.NotifyAsync(BitcoinStratumMethods.MiningNotify, jobParamsActual); } @@ -282,9 +289,6 @@ public override double HashrateFromShares(double shares, double interval) var multiplier = BitcoinConstants.Pow2x32 * ErgoConstants.DiffMultiplier; var result = shares * multiplier / interval; - //result /= 8; - result *= 1.25; - return result; } @@ -388,19 +392,27 @@ protected override async Task OnRequestAsync(StratumConnection connection, } } + protected override async Task<double?> GetNicehashStaticMinDiff(StratumConnection connection, string userAgent, string coinName, string algoName) + { + var result= await base.GetNicehashStaticMinDiff(connection, userAgent, coinName, algoName); + + // adjust value to fit with our target value calculation + if(result.HasValue) + result = result.Value / uint.MaxValue; + + return result; + } + protected override async Task OnVarDiffUpdateAsync(StratumConnection connection, double newDiff) { var context = connection.ContextAs<ErgoWorkerContext>(); + context.EnqueueNewDifficulty(newDiff); - // apply immediately and notify client if(context.HasPendingDifficulty) { context.ApplyPendingDifficulty(); - context.PreviousEffectiveDifficulty = context.EffectiveDifficulty; - - await connection.NotifyAsync(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty }); await SendJob(connection, context, currentJobParams); } } diff --git a/src/Miningcore/Blockchain/Ergo/ErgoWorkerContext.cs b/src/Miningcore/Blockchain/Ergo/ErgoWorkerContext.cs index f8d4e05c2..179881c70 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoWorkerContext.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoWorkerContext.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Miningcore.Mining; namespace Miningcore.Blockchain.Ergo @@ -14,16 +15,9 @@ public class ErgoWorkerContext : WorkerContextBase /// </summary> public string Worker { get; set; } - public string ExtraNonce1 { get; set; } - - /// <summary> - /// Effective adjusted diff - /// </summary> - public double EffectiveDifficulty { get; set; } - /// <summary> - /// Previous effective adjusted diff + /// Unique value assigned per worker /// </summary> - public double? PreviousEffectiveDifficulty { get; set; } + public string ExtraNonce1 { get; set; } } } diff --git a/src/Miningcore/Blockchain/Ethereum/EthereumPool.cs b/src/Miningcore/Blockchain/Ethereum/EthereumPool.cs index f8f0c8d6f..0ac5363c0 100644 --- a/src/Miningcore/Blockchain/Ethereum/EthereumPool.cs +++ b/src/Miningcore/Blockchain/Ethereum/EthereumPool.cs @@ -107,7 +107,20 @@ private async Task OnAuthorizeAsync(StratumConnection connection, Timestamped<Js var staticDiff = GetStaticDiffFromPassparts(passParts); // Nicehash support - staticDiff = await GetNicehashStaticMinDiff(connection, context.UserAgent, staticDiff, coin.Name, coin.GetAlgorithmName()); + var nicehashDiff = await GetNicehashStaticMinDiff(connection, context.UserAgent, coin.Name, coin.GetAlgorithmName()); + + if(nicehashDiff.HasValue) + { + if(!staticDiff.HasValue || nicehashDiff > staticDiff) + { + logger.Info(() => $"[{connection.ConnectionId}] Nicehash detected. Using API supplied difficulty of {nicehashDiff.Value}"); + + staticDiff = nicehashDiff; + } + + else + logger.Info(() => $"[{connection.ConnectionId}] Nicehash detected. Using miner supplied difficulty of {staticDiff.Value}"); + } // Static diff if(staticDiff.HasValue && diff --git a/src/Miningcore/Blockchain/Ethereum/EthereumWorkerContext.cs b/src/Miningcore/Blockchain/Ethereum/EthereumWorkerContext.cs index 612b1c269..b50ac4465 100644 --- a/src/Miningcore/Blockchain/Ethereum/EthereumWorkerContext.cs +++ b/src/Miningcore/Blockchain/Ethereum/EthereumWorkerContext.cs @@ -16,6 +16,9 @@ public class EthereumWorkerContext : WorkerContextBase public bool IsInitialWorkSent { get; set; } = false; + /// <summary> + /// Unique value assigned per worker + /// </summary> public string ExtraNonce1 { get; set; } } } diff --git a/src/Miningcore/Mining/PoolBase.cs b/src/Miningcore/Mining/PoolBase.cs index 623eb30af..c7b3c9e40 100644 --- a/src/Miningcore/Mining/PoolBase.cs +++ b/src/Miningcore/Mining/PoolBase.cs @@ -294,29 +294,15 @@ protected void ConsiderBan(StratumConnection connection, WorkerContextBase conte } } - protected async Task<double?> GetNicehashStaticMinDiff(StratumConnection connection, string userAgent, - double? staticDiff, string coinName, string algoName) + protected virtual async Task<double?> GetNicehashStaticMinDiff(StratumConnection connection, string userAgent, string coinName, string algoName) { if(userAgent.Contains(NicehashConstants.NicehashUA, StringComparison.OrdinalIgnoreCase) && clusterConfig.Nicehash?.EnableAutoDiff == true) { - var nicehashDiff = await nicehashService.GetStaticDiff(coinName, algoName, CancellationToken.None); - - if(nicehashDiff.HasValue) - { - if(!staticDiff.HasValue || nicehashDiff > staticDiff) - { - logger.Info(() => $"[{connection.ConnectionId}] Nicehash detected. Using API supplied difficulty of {nicehashDiff.Value}"); - - return nicehashDiff; - } - - else - logger.Info(() => $"[{connection.ConnectionId}] Nicehash detected. Using custom difficulty of {staticDiff.Value}"); - } + return await nicehashService.GetStaticDiff(coinName, algoName, CancellationToken.None); } - return staticDiff; + return null; } private StratumEndpoint PoolEndpoint2IPEndpoint(int port, PoolEndpoint pep) From f4d4b226e58658fec12588733a22d1042a091f92 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Sat, 10 Jul 2021 14:17:54 +0200 Subject: [PATCH 034/145] Simplify blocktemplate --- .../Blockchain/Ergo/ErgoBlockTemplate.cs | 4 ---- src/Miningcore/Blockchain/Ergo/ErgoJob.cs | 20 +++++++++---------- .../Blockchain/Ergo/ErgoJobManager.cs | 14 ++++++------- 3 files changed, 17 insertions(+), 21 deletions(-) delete mode 100644 src/Miningcore/Blockchain/Ergo/ErgoBlockTemplate.cs diff --git a/src/Miningcore/Blockchain/Ergo/ErgoBlockTemplate.cs b/src/Miningcore/Blockchain/Ergo/ErgoBlockTemplate.cs deleted file mode 100644 index a564b3b91..000000000 --- a/src/Miningcore/Blockchain/Ergo/ErgoBlockTemplate.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Miningcore.Blockchain.Ergo -{ - public record ErgoBlockTemplate(WorkMessage Work); -} diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs index b58dedb0a..303589c71 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs @@ -16,9 +16,9 @@ namespace Miningcore.Blockchain.Ergo { public class ErgoJob { - public ErgoBlockTemplate BlockTemplate { get; private set; } + public WorkMessage BlockTemplate { get; private set; } public double Difficulty => bTarget.Difficulty; - public uint Height => BlockTemplate.Work.Height; + public uint Height => BlockTemplate.Height; public string JobId { get; protected set; } private object[] jobParams; @@ -104,7 +104,7 @@ protected virtual Share ProcessShareInternal(StratumConnection worker, string no var context = worker.ContextAs<ErgoWorkerContext>(); // hash coinbase - var coinbase = SerializeCoinbase(BlockTemplate.Work.Msg, nonce); + var coinbase = SerializeCoinbase(BlockTemplate.Msg, nonce); Span<byte> hashResult = stackalloc byte[32]; hasher.Digest(coinbase, hashResult); @@ -147,11 +147,8 @@ protected virtual Share ProcessShareInternal(StratumConnection worker, string no var stratumDifficulty = context.Difficulty; var ratio = fhTarget.Difficulty / stratumDifficulty; - // check if the share meets the much harder block difficulty (block candidate) - var isBlockCandidate = fh < b; - // test if share meets at least workers current difficulty - if(!isBlockCandidate && ratio < 0.99) + if(ratio < 0.99) { // check if share matched the previous difficulty from before a vardiff retarget if(context.VarDiff?.LastUpdate != null && context.PreviousDifficulty.HasValue) @@ -169,6 +166,9 @@ protected virtual Share ProcessShareInternal(StratumConnection worker, string no throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({fhTarget.Difficulty})"); } + // check if the share meets the much harder block difficulty (block candidate) + var isBlockCandidate = fh < b; + var result = new Share { BlockHeight = Height, @@ -219,20 +219,20 @@ public virtual Share ProcessShare(StratumConnection worker, string extraNonce2, return ProcessShareInternal(worker, nonce); } - public void Init(ErgoBlockTemplate blockTemplate, int blockVersion, int extraNonceSize, string jobId) + public void Init(WorkMessage blockTemplate, int blockVersion, int extraNonceSize, string jobId) { this.extraNonceSize = extraNonceSize; BlockTemplate = blockTemplate; JobId = jobId; - b = BigInteger.Parse(BlockTemplate.Work.B, NumberStyles.Integer); + b = BigInteger.Parse(BlockTemplate.B, NumberStyles.Integer); bTarget = new Target(b); jobParams = new object[] { JobId, Height, - BlockTemplate.Work.Msg, + BlockTemplate.Msg, string.Empty, string.Empty, blockVersion, diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs index 2c8f99987..c829c0c9d 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs @@ -125,11 +125,11 @@ await GetBlockTemplateAsync() : var isNew = job == null || (blockTemplate != null && - (job.BlockTemplate?.Work.Msg != blockTemplate.Work.Msg || - blockTemplate.Work?.Height > job.BlockTemplate.Work.Height)); + (job.BlockTemplate?.Msg != blockTemplate.Msg || + blockTemplate?.Height > job.BlockTemplate.Height)); if(isNew) - messageBus.NotifyChainHeight(poolConfig.Id, blockTemplate.Work.Height, poolConfig.Template); + messageBus.NotifyChainHeight(poolConfig.Id, blockTemplate.Height, poolConfig.Template); if(isNew || forceUpdate) { @@ -185,20 +185,20 @@ await GetBlockTemplateAsync() : return (false, forceUpdate); } - private async Task<ErgoBlockTemplate> GetBlockTemplateAsync() + private async Task<WorkMessage> GetBlockTemplateAsync() { logger.LogInvoke(); var work = await daemon.MiningRequestBlockCandidateAsync(CancellationToken.None); - return new ErgoBlockTemplate(work); + return work; } - private ErgoBlockTemplate GetBlockTemplateFromJson(string json) + private WorkMessage GetBlockTemplateFromJson(string json) { logger.LogInvoke(); - return JsonConvert.DeserializeObject<ErgoBlockTemplate>(json); + return JsonConvert.DeserializeObject<WorkMessage>(json); } private async Task ShowDaemonSyncProgressAsync() From 7327de723665ea33a26c482e4a92ef9e32cb3e94 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Sat, 10 Jul 2021 14:37:57 +0200 Subject: [PATCH 035/145] Cleanup --- src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs index c829c0c9d..b5a8eb08e 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs @@ -269,7 +269,6 @@ private async Task<bool> SubmitBlockAsync(Share share, ErgoJob job, string nonce await daemon.MiningSubmitSolutionAsync(new PowSolutions { N = nonce, - //Pk = job.BlockTemplate.Work.Pk, }); return true; From 2ced15be4d2dd265d552d47b3ff5ea3e3f0864f1 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Sat, 10 Jul 2021 14:50:43 +0200 Subject: [PATCH 036/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoJob.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs index 303589c71..c38307820 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs @@ -216,6 +216,8 @@ public virtual Share ProcessShare(StratumConnection worker, string extraNonce2, if(!RegisterSubmit(nTime, nonce)) throw new StratumException(StratumError.DuplicateShare, $"duplicate share"); +nonce = "40010004b44ca7db"; + return ProcessShareInternal(worker, nonce); } @@ -225,6 +227,13 @@ public void Init(WorkMessage blockTemplate, int blockVersion, int extraNonceSize BlockTemplate = blockTemplate; JobId = jobId; + +BlockTemplate.B = "3670501044486883791856677069209410167401146918778074074113413089885"; +BlockTemplate.Height = 26524; +BlockTemplate.Msg = "25d09e052990fcd25f24eac8f1ddb5df6fb7ee3c01f07366181bf308256196f2"; +BlockTemplate.Pk = "0303fc9708d4261ecb8f60c76d13b77ea754fc20a48ad0ce48f9ed2cae09ea1937"; + + b = BigInteger.Parse(BlockTemplate.B, NumberStyles.Integer); bTarget = new Target(b); From f9e59f30379f9020d8c229caf8d910d611bdd27c Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Sat, 10 Jul 2021 14:51:58 +0200 Subject: [PATCH 037/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoJob.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs index c38307820..41f8778f2 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs @@ -216,8 +216,6 @@ public virtual Share ProcessShare(StratumConnection worker, string extraNonce2, if(!RegisterSubmit(nTime, nonce)) throw new StratumException(StratumError.DuplicateShare, $"duplicate share"); -nonce = "40010004b44ca7db"; - return ProcessShareInternal(worker, nonce); } @@ -228,12 +226,6 @@ public void Init(WorkMessage blockTemplate, int blockVersion, int extraNonceSize BlockTemplate = blockTemplate; JobId = jobId; -BlockTemplate.B = "3670501044486883791856677069209410167401146918778074074113413089885"; -BlockTemplate.Height = 26524; -BlockTemplate.Msg = "25d09e052990fcd25f24eac8f1ddb5df6fb7ee3c01f07366181bf308256196f2"; -BlockTemplate.Pk = "0303fc9708d4261ecb8f60c76d13b77ea754fc20a48ad0ce48f9ed2cae09ea1937"; - - b = BigInteger.Parse(BlockTemplate.B, NumberStyles.Integer); bTarget = new Target(b); From 0a32642b7a203d2dc21025ddcefaf8004be93800 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Sat, 10 Jul 2021 15:55:39 +0200 Subject: [PATCH 038/145] WIP --- src/Miningcore/Mining/PoolBase.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Miningcore/Mining/PoolBase.cs b/src/Miningcore/Mining/PoolBase.cs index c7b3c9e40..0f082d32d 100644 --- a/src/Miningcore/Mining/PoolBase.cs +++ b/src/Miningcore/Mining/PoolBase.cs @@ -28,6 +28,7 @@ using Newtonsoft.Json; using NLog; using Contract = Miningcore.Contracts.Contract; +// ReSharper disable InconsistentlySynchronizedField namespace Miningcore.Mining { @@ -376,9 +377,6 @@ public virtual async Task RunAsync(CancellationToken ct) await ServeStratum(ct, ipEndpoints); } - - messageBus.NotifyPoolStatus(this, PoolStatus.Offline); - logger.Info(() => "Pool Offline"); } catch(PoolStartupAbortException) From 245e66e047ca86d0d4addff431a2a6de974ebc65 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Sat, 10 Jul 2021 20:07:19 +0200 Subject: [PATCH 039/145] WIP --- src/Miningcore/Mining/ShareReceiver.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Miningcore/Mining/ShareReceiver.cs b/src/Miningcore/Mining/ShareReceiver.cs index 872928b33..2bcc31c35 100644 --- a/src/Miningcore/Mining/ShareReceiver.cs +++ b/src/Miningcore/Mining/ShareReceiver.cs @@ -277,7 +277,7 @@ private void ProcessMessage(string url, ZMessage msg) var shareMultiplier = pool.Config.Template.Family == CoinFamily.Bitcoin ? pool.Config.Template.As<BitcoinTemplate>().ShareMultiplier : 1; - poolContext.Logger.Info(() => $"External {(!string.IsNullOrEmpty(share.Source) ? $"[{share.Source.ToUpper()}] " : string.Empty)}share accepted: D={Math.Round(share.Difficulty * shareMultiplier, 3)}"); + poolContext.Logger.Info(() => $"External {(!string.IsNullOrEmpty(share.Source) ? $"[{share.Source.ToUpper()}] " : string.Empty)}share accepted: D={Math.Round(share.Difficulty * shareMultiplier, 4)}"); if(pool.NetworkStats != null) { @@ -297,7 +297,7 @@ private void ProcessMessage(string url, ZMessage msg) } else - logger.Info(() => $"External {(!string.IsNullOrEmpty(share.Source) ? $"[{share.Source.ToUpper()}] " : string.Empty)}share accepted: D={Math.Round(share.Difficulty, 3)}"); + logger.Info(() => $"External {(!string.IsNullOrEmpty(share.Source) ? $"[{share.Source.ToUpper()}] " : string.Empty)}share accepted: D={Math.Round(share.Difficulty, 4)}"); } protected override async Task ExecuteAsync(CancellationToken ct) From 923335f089623c211446b369e4b95ce8ad240259 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Sun, 11 Jul 2021 10:54:16 +0200 Subject: [PATCH 040/145] External share diff --- src/Miningcore/Blockchain/Bitcoin/BitcoinPool.cs | 2 ++ .../Blockchain/Cryptonote/CryptonotePool.cs | 2 ++ src/Miningcore/Blockchain/Equihash/EquihashPool.cs | 2 ++ src/Miningcore/Blockchain/Ergo/ErgoConstants.cs | 2 +- src/Miningcore/Blockchain/Ergo/ErgoJob.cs | 2 +- src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs | 2 +- src/Miningcore/Blockchain/Ergo/ErgoPool.cs | 8 ++++---- src/Miningcore/Blockchain/Ethereum/EthereumPool.cs | 2 ++ src/Miningcore/Mining/Abstractions.cs | 1 + src/Miningcore/Mining/PoolBase.cs | 1 + src/Miningcore/Mining/ShareReceiver.cs | 14 ++++++-------- 11 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinPool.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinPool.cs index b06091c97..fde95ec97 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinPool.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinPool.cs @@ -375,6 +375,8 @@ public override double HashrateFromShares(double shares, double interval) return result; } + public override double ShareMultiplier => coin.ShareMultiplier; + #region Overrides public override void Configure(PoolConfig poolConfig, ClusterConfig clusterConfig) diff --git a/src/Miningcore/Blockchain/Cryptonote/CryptonotePool.cs b/src/Miningcore/Blockchain/Cryptonote/CryptonotePool.cs index d494b6a4f..19d611e40 100644 --- a/src/Miningcore/Blockchain/Cryptonote/CryptonotePool.cs +++ b/src/Miningcore/Blockchain/Cryptonote/CryptonotePool.cs @@ -430,6 +430,8 @@ public override double HashrateFromShares(double shares, double interval) return result; } + public override double ShareMultiplier => 1; + protected override async Task OnVarDiffUpdateAsync(StratumConnection connection, double newDiff) { await base.OnVarDiffUpdateAsync(connection, newDiff); diff --git a/src/Miningcore/Blockchain/Equihash/EquihashPool.cs b/src/Miningcore/Blockchain/Equihash/EquihashPool.cs index a7d3639ad..ea60bd43d 100644 --- a/src/Miningcore/Blockchain/Equihash/EquihashPool.cs +++ b/src/Miningcore/Blockchain/Equihash/EquihashPool.cs @@ -412,6 +412,8 @@ public override double HashrateFromShares(double shares, double interval) return result; } + public override double ShareMultiplier => 1; + protected override async Task OnVarDiffUpdateAsync(StratumConnection connection, double newDiff) { var context = connection.ContextAs<BitcoinWorkerContext>(); diff --git a/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs b/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs index 1f6599916..ab314a914 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs @@ -16,7 +16,7 @@ public static class ErgoConstants { public const double ArtificialDiffCeiling = 0x10000; - public const uint DiffMultiplier = 256; + public const uint ShareMultiplier = 256; public static double Pow2x26 = Math.Pow(2, 26); public const decimal SmallestUnit = 1000000000; diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs index 41f8778f2..12a0daf47 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs @@ -173,7 +173,7 @@ protected virtual Share ProcessShareInternal(StratumConnection worker, string no { BlockHeight = Height, NetworkDifficulty = Difficulty, - Difficulty = stratumDifficulty / ErgoConstants.DiffMultiplier + Difficulty = stratumDifficulty / ErgoConstants.ShareMultiplier }; if(isBlockCandidate) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs index b5a8eb08e..b14474568 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs @@ -238,7 +238,7 @@ private async Task UpdateNetworkStatsAsync() else BlockchainStats.NetworkDifficulty = info.Difficulty.Value<double>(); - BlockchainStats.NetworkDifficulty *= ErgoConstants.DiffMultiplier; + BlockchainStats.NetworkDifficulty *= ErgoConstants.ShareMultiplier; // TODO: BlockchainStats.NetworkHashrate = info.HeadersScore } diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs index 37b6545ea..5c659bc52 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs @@ -22,9 +22,7 @@ using Miningcore.Stratum; using Miningcore.Time; using Miningcore.Util; -using NBitcoin; using Newtonsoft.Json; -using NLog; namespace Miningcore.Blockchain.Ergo { @@ -196,7 +194,7 @@ protected virtual async Task OnSubmitAsync(StratumConnection connection, Timesta // telemetry PublishTelemetry(TelemetryCategory.Share, clock.Now - tsRequest.Timestamp.UtcDateTime, true); - logger.Info(() => $"[{connection.ConnectionId}] Share accepted: D={Math.Round(share.Difficulty * ErgoConstants.DiffMultiplier, 3)}"); + logger.Info(() => $"[{connection.ConnectionId}] Share accepted: D={Math.Round(share.Difficulty * ErgoConstants.ShareMultiplier, 3)}"); // update pool stats if(share.IsBlockCandidate) @@ -286,12 +284,14 @@ private async Task SendJob(StratumConnection connection, ErgoWorkerContext conte public override double HashrateFromShares(double shares, double interval) { - var multiplier = BitcoinConstants.Pow2x32 * ErgoConstants.DiffMultiplier; + var multiplier = BitcoinConstants.Pow2x32 * ErgoConstants.ShareMultiplier; var result = shares * multiplier / interval; return result; } + public override double ShareMultiplier => ErgoConstants.ShareMultiplier; + #region Overrides public override void Configure(PoolConfig poolConfig, ClusterConfig clusterConfig) diff --git a/src/Miningcore/Blockchain/Ethereum/EthereumPool.cs b/src/Miningcore/Blockchain/Ethereum/EthereumPool.cs index 0ac5363c0..d474cf316 100644 --- a/src/Miningcore/Blockchain/Ethereum/EthereumPool.cs +++ b/src/Miningcore/Blockchain/Ethereum/EthereumPool.cs @@ -390,6 +390,8 @@ public override double HashrateFromShares(double shares, double interval) return result; } + public override double ShareMultiplier => 1; + protected override async Task OnVarDiffUpdateAsync(StratumConnection client, double newDiff) { await base.OnVarDiffUpdateAsync(client, newDiff); diff --git a/src/Miningcore/Mining/Abstractions.cs b/src/Miningcore/Mining/Abstractions.cs index 43a0c007f..d708a3313 100644 --- a/src/Miningcore/Mining/Abstractions.cs +++ b/src/Miningcore/Mining/Abstractions.cs @@ -10,6 +10,7 @@ public interface IMiningPool PoolConfig Config { get; } PoolStats PoolStats { get; } BlockchainStats NetworkStats { get; } + double ShareMultiplier { get; } void Configure(PoolConfig poolConfig, ClusterConfig clusterConfig); double HashrateFromShares(double shares, double interval); Task RunAsync(CancellationToken ct); diff --git a/src/Miningcore/Mining/PoolBase.cs b/src/Miningcore/Mining/PoolBase.cs index 0f082d32d..a33660941 100644 --- a/src/Miningcore/Mining/PoolBase.cs +++ b/src/Miningcore/Mining/PoolBase.cs @@ -351,6 +351,7 @@ public virtual void Configure(PoolConfig poolConfig, ClusterConfig clusterConfig } public abstract double HashrateFromShares(double shares, double interval); + public abstract double ShareMultiplier { get; } public virtual async Task RunAsync(CancellationToken ct) { diff --git a/src/Miningcore/Mining/ShareReceiver.cs b/src/Miningcore/Mining/ShareReceiver.cs index 2bcc31c35..8fcdd673e 100644 --- a/src/Miningcore/Mining/ShareReceiver.cs +++ b/src/Miningcore/Mining/ShareReceiver.cs @@ -58,7 +58,7 @@ public ShareReceiver( ContractResolver = new CamelCasePropertyNamesContractResolver() }; - class PoolContext + private class PoolContext { public PoolContext(IMiningPool pool, ILogger logger) { @@ -66,10 +66,10 @@ public PoolContext(IMiningPool pool, ILogger logger) Logger = logger; } - public readonly IMiningPool Pool; - public readonly ILogger Logger; - public DateTime? LastBlock; - public long BlockHeight; + public IMiningPool Pool { get; } + public ILogger Logger { get; } + public DateTime? LastBlock { get; set; } + public long BlockHeight { get; set; } } private void AttachPool(IMiningPool pool) @@ -273,9 +273,7 @@ private void ProcessMessage(string url, ZMessage msg) if(poolContext != null) { var pool = poolContext.Pool; - - var shareMultiplier = pool.Config.Template.Family == CoinFamily.Bitcoin ? - pool.Config.Template.As<BitcoinTemplate>().ShareMultiplier : 1; + var shareMultiplier = poolContext.Pool.ShareMultiplier; poolContext.Logger.Info(() => $"External {(!string.IsNullOrEmpty(share.Source) ? $"[{share.Source.ToUpper()}] " : string.Empty)}share accepted: D={Math.Round(share.Difficulty * shareMultiplier, 4)}"); From 0ae24a87120104655692b79d65008eabd3f437ed Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Sun, 11 Jul 2021 13:48:12 +0200 Subject: [PATCH 041/145] WIP --- src/Miningcore/Blockchain/Bitcoin/BitcoinPool.cs | 2 +- src/Miningcore/Blockchain/Cryptonote/CryptonotePool.cs | 2 +- src/Miningcore/Blockchain/Equihash/EquihashPool.cs | 2 +- src/Miningcore/Blockchain/Ergo/ErgoPool.cs | 2 +- src/Miningcore/Blockchain/Ethereum/EthereumPool.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinPool.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinPool.cs index fde95ec97..0ba702aca 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinPool.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinPool.cs @@ -214,7 +214,7 @@ protected virtual async Task OnSubmitAsync(StratumConnection connection, Timesta // update client stats context.Stats.InvalidShares++; - logger.Info(() => $"[{connection.ConnectionId}] Share rejected: {ex.Message}"); + logger.Info(() => $"[{connection.ConnectionId}] Share rejected: {ex.Message} [{context.UserAgent}]"); // banning ConsiderBan(connection, context, poolConfig.Banning); diff --git a/src/Miningcore/Blockchain/Cryptonote/CryptonotePool.cs b/src/Miningcore/Blockchain/Cryptonote/CryptonotePool.cs index 19d611e40..e8d3d83af 100644 --- a/src/Miningcore/Blockchain/Cryptonote/CryptonotePool.cs +++ b/src/Miningcore/Blockchain/Cryptonote/CryptonotePool.cs @@ -270,7 +270,7 @@ private async Task OnSubmitAsync(StratumConnection connection, Timestamped<JsonR // update client stats context.Stats.InvalidShares++; - logger.Info(() => $"[{connection.ConnectionId}] Share rejected: {ex.Message}"); + logger.Info(() => $"[{connection.ConnectionId}] Share rejected: {ex.Message} [{context.UserAgent}]"); // banning ConsiderBan(connection, context, poolConfig.Banning); diff --git a/src/Miningcore/Blockchain/Equihash/EquihashPool.cs b/src/Miningcore/Blockchain/Equihash/EquihashPool.cs index ea60bd43d..f0575e38b 100644 --- a/src/Miningcore/Blockchain/Equihash/EquihashPool.cs +++ b/src/Miningcore/Blockchain/Equihash/EquihashPool.cs @@ -275,7 +275,7 @@ protected virtual async Task OnSubmitAsync(StratumConnection connection, Timesta // update client stats context.Stats.InvalidShares++; - logger.Info(() => $"[{connection.ConnectionId}] Share rejected: {ex.Message}"); + logger.Info(() => $"[{connection.ConnectionId}] Share rejected: {ex.Message} [{context.UserAgent}]"); // banning ConsiderBan(connection, context, poolConfig.Banning); diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs index 5c659bc52..8cab4151e 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs @@ -212,7 +212,7 @@ protected virtual async Task OnSubmitAsync(StratumConnection connection, Timesta // update client stats context.Stats.InvalidShares++; - logger.Info(() => $"[{connection.ConnectionId}] Share rejected: {ex.Message}"); + logger.Info(() => $"[{connection.ConnectionId}] Share rejected: {ex.Message} [{context.UserAgent}]"); // banning ConsiderBan(connection, context, poolConfig.Banning); diff --git a/src/Miningcore/Blockchain/Ethereum/EthereumPool.cs b/src/Miningcore/Blockchain/Ethereum/EthereumPool.cs index d474cf316..573d05740 100644 --- a/src/Miningcore/Blockchain/Ethereum/EthereumPool.cs +++ b/src/Miningcore/Blockchain/Ethereum/EthereumPool.cs @@ -214,7 +214,7 @@ private async Task OnSubmitAsync(StratumConnection connection, Timestamped<JsonR // update client stats context.Stats.InvalidShares++; - logger.Info(() => $"[{connection.ConnectionId}] Share rejected: {ex.Message}"); + logger.Info(() => $"[{connection.ConnectionId}] Share rejected: {ex.Message} [{context.UserAgent}]"); // banning ConsiderBan(connection, context, poolConfig.Banning); From 29e74ada1eb3a07fdc5013022f20f764f2ea1fa4 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Sun, 11 Jul 2021 20:46:41 +0200 Subject: [PATCH 042/145] Reconnect fixes --- src/Miningcore/Mining/BtStreamReceiver.cs | 38 ++++++++++++++++++----- src/Miningcore/Mining/ShareReceiver.cs | 38 ++++++++++++++++++----- 2 files changed, 60 insertions(+), 16 deletions(-) diff --git a/src/Miningcore/Mining/BtStreamReceiver.cs b/src/Miningcore/Mining/BtStreamReceiver.cs index 47987c525..83bcad2c1 100644 --- a/src/Miningcore/Mining/BtStreamReceiver.cs +++ b/src/Miningcore/Mining/BtStreamReceiver.cs @@ -44,7 +44,7 @@ public BtStreamReceiver( private readonly IMessageBus messageBus; private readonly ClusterConfig clusterConfig; - private static ZSocket SetupSubSocket(ZmqPubSubEndpointConfig relay) + private static ZSocket SetupSubSocket(ZmqPubSubEndpointConfig relay, bool silent = false) { var subSocket = new ZSocket(ZSocketType.SUB); @@ -54,10 +54,13 @@ private static ZSocket SetupSubSocket(ZmqPubSubEndpointConfig relay) subSocket.Connect(relay.Url); subSocket.SubscribeAll(); - if(subSocket.CurveServerKey != null && subSocket.CurveServerKey.Any(x => x != 0)) - logger.Info($"Monitoring Bt-Stream source {relay.Url} using Curve public-key {subSocket.CurveServerKey.ToHexString()}"); - else - logger.Info($"Monitoring Bt-Stream source {relay.Url}"); + if(!silent) + { + if(subSocket.CurveServerKey != null && subSocket.CurveServerKey.Any(x => x != 0)) + logger.Info($"Monitoring Bt-Stream source {relay.Url} using Curve public-key {subSocket.CurveServerKey.ToHexString()}"); + else + logger.Info($"Monitoring Bt-Stream source {relay.Url}"); + } return subSocket; } @@ -112,7 +115,7 @@ protected override async Task ExecuteAsync(CancellationToken ct) await Task.Run(() => { - var timeout = TimeSpan.FromMilliseconds(1000); + var timeout = TimeSpan.FromMilliseconds(5000); var reconnectTimeout = TimeSpan.FromSeconds(300); var relays = endpoints @@ -129,7 +132,7 @@ await Task.Run(() => try { // setup sockets - var sockets = relays.Select(SetupSubSocket).ToArray(); + var sockets = relays.Select(x=> SetupSubSocket(x)).ToArray(); using(new CompositeDisposable(sockets)) { @@ -157,7 +160,7 @@ await Task.Run(() => { // re-create socket sockets[i].Dispose(); - sockets[i] = SetupSubSocket(relays[i]); + sockets[i] = SetupSubSocket(relays[i], true); // reset clock lastMessageReceived[i] = clock.Now; @@ -169,6 +172,25 @@ await Task.Run(() => if(error != null) logger.Error(() => $"{nameof(ShareReceiver)}: {error.Name} [{error.Name}] during receive"); } + + else + { + // check for timeouts + for(var i = 0; i < messages.Length; i++) + { + if(clock.Now - lastMessageReceived[i] > reconnectTimeout) + { + // re-create socket + sockets[i].Dispose(); + sockets[i] = SetupSubSocket(relays[i], true); + + // reset clock + lastMessageReceived[i] = clock.Now; + + logger.Info(() => $"Receive timeout of {reconnectTimeout.TotalSeconds} seconds exceeded. Re-connecting to {relays[i].Url} ..."); + } + } + } } } } diff --git a/src/Miningcore/Mining/ShareReceiver.cs b/src/Miningcore/Mining/ShareReceiver.cs index 8fcdd673e..c4cdf84dd 100644 --- a/src/Miningcore/Mining/ShareReceiver.cs +++ b/src/Miningcore/Mining/ShareReceiver.cs @@ -89,7 +89,7 @@ private Task StartMessageReceiver(CancellationToken ct) return Task.Run(() => { Thread.CurrentThread.Name = "ShareReceiver Socket Poller"; - var timeout = TimeSpan.FromMilliseconds(1000); + var timeout = TimeSpan.FromMilliseconds(5000); var reconnectTimeout = TimeSpan.FromSeconds(60); var relays = clusterConfig.ShareRelays @@ -104,7 +104,7 @@ private Task StartMessageReceiver(CancellationToken ct) try { // setup sockets - var sockets = relays.Select(SetupSubSocket).ToArray(); + var sockets = relays.Select(x=> SetupSubSocket(x)).ToArray(); using(new CompositeDisposable(sockets)) { @@ -129,7 +129,7 @@ private Task StartMessageReceiver(CancellationToken ct) { // re-create socket sockets[i].Dispose(); - sockets[i] = SetupSubSocket(relays[i]); + sockets[i] = SetupSubSocket(relays[i], true); // reset clock lastMessageReceived[i] = clock.Now; @@ -141,6 +141,25 @@ private Task StartMessageReceiver(CancellationToken ct) if(error != null) logger.Error(() => $"{nameof(ShareReceiver)}: {error.Name} [{error.Name}] during receive"); } + + else + { + // check for timeouts + for(var i = 0; i < messages.Length; i++) + { + if(clock.Now - lastMessageReceived[i] > reconnectTimeout) + { + // re-create socket + sockets[i].Dispose(); + sockets[i] = SetupSubSocket(relays[i], true); + + // reset clock + lastMessageReceived[i] = clock.Now; + + logger.Info(() => $"Receive timeout of {reconnectTimeout.TotalSeconds} seconds exceeded. Re-connecting to {relays[i].Url} ..."); + } + } + } } } } @@ -156,17 +175,20 @@ private Task StartMessageReceiver(CancellationToken ct) }, ct); } - private static ZSocket SetupSubSocket(ShareRelayEndpointConfig relay) + private static ZSocket SetupSubSocket(ShareRelayEndpointConfig relay, bool silent = false) { var subSocket = new ZSocket(ZSocketType.SUB); subSocket.SetupCurveTlsClient(relay.SharedEncryptionKey, logger); subSocket.Connect(relay.Url); subSocket.SubscribeAll(); - if(subSocket.CurveServerKey != null) - logger.Info($"Monitoring external stratum {relay.Url} using Curve public-key {subSocket.CurveServerKey.ToHexString()}"); - else - logger.Info($"Monitoring external stratum {relay.Url}"); + if(!silent) + { + if(subSocket.CurveServerKey != null) + logger.Info($"Monitoring external stratum {relay.Url} using Curve public-key {subSocket.CurveServerKey.ToHexString()}"); + else + logger.Info($"Monitoring external stratum {relay.Url}"); + } return subSocket; } From 6f94296b51004ea1db1af86ab06c49e9c62905d0 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Sun, 11 Jul 2021 23:05:49 +0200 Subject: [PATCH 043/145] Formatting --- .../Postgres/Repositories/BlockRepository.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Miningcore/Persistence/Postgres/Repositories/BlockRepository.cs b/src/Miningcore/Persistence/Postgres/Repositories/BlockRepository.cs index 8360926c2..2b5cbcf81 100644 --- a/src/Miningcore/Persistence/Postgres/Repositories/BlockRepository.cs +++ b/src/Miningcore/Persistence/Postgres/Repositories/BlockRepository.cs @@ -67,8 +67,8 @@ public async Task<Block[]> PageBlocksAsync(IDbConnection con, string poolId, Blo offset = page * pageSize, pageSize })) - .Select(mapper.Map<Block>) - .ToArray(); + .Select(mapper.Map<Block>) + .ToArray(); } public async Task<Block[]> PageBlocksAsync(IDbConnection con, BlockStatus[] status, int page, int pageSize) @@ -82,8 +82,8 @@ public async Task<Block[]> PageBlocksAsync(IDbConnection con, BlockStatus[] stat offset = page * pageSize, pageSize })) - .Select(mapper.Map<Block>) - .ToArray(); + .Select(mapper.Map<Block>) + .ToArray(); } public async Task<Block[]> GetPendingBlocksForPoolAsync(IDbConnection con, string poolId) @@ -110,8 +110,8 @@ public async Task<Block> GetBlockBeforeAsync(IDbConnection con, string poolId, B before, status = status.Select(x => x.ToString().ToLower()).ToArray() })) - .Select(mapper.Map<Block>) - .FirstOrDefault(); + .Select(mapper.Map<Block>) + .FirstOrDefault(); } public Task<uint> GetPoolBlockCountAsync(IDbConnection con, string poolId) From 290b3ef7d4bb1bd60145394799e96681711c3239 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Tue, 13 Jul 2021 10:17:42 +0200 Subject: [PATCH 044/145] Network stats update --- .../Blockchain/Ergo/ErgoJobManager.cs | 43 +++---------------- 1 file changed, 6 insertions(+), 37 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs index b14474568..84be83ea7 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs @@ -156,6 +156,11 @@ await GetBlockTemplateAsync() : // update stats BlockchainStats.LastNetworkBlockTime = clock.Now; BlockchainStats.BlockHeight = job.Height; + BlockchainStats.NetworkDifficulty = job.Difficulty; + + var blockTimeAvg = 120; + BlockchainStats.NetworkHashrate = BlockchainStats.NetworkDifficulty / blockTimeAvg; + BlockchainStats.NetworkHashrate *= Math.Pow(2, 32); } else @@ -217,33 +222,6 @@ private async Task ShowDaemonSyncProgressAsync() logger.Info(() => $"Daemon is downloading headers ..."); } - private async Task UpdateNetworkStatsAsync() - { - logger.LogInvoke(); - - var info = await Guard(() => daemon.GetNodeInfoAsync(), - ex => logger.Debug(ex)); - - if(info != null) - { - BlockchainStats.ConnectedPeers = info.PeersCount; - - // Network difficulty - if(info.Difficulty.Type == JTokenType.Object) - { - if(((JObject) info.Difficulty).TryGetValue("proof-of-work", out var diffVal)) - BlockchainStats.NetworkDifficulty = diffVal.Value<double>(); - } - - else - BlockchainStats.NetworkDifficulty = info.Difficulty.Value<double>(); - - BlockchainStats.NetworkDifficulty *= ErgoConstants.ShareMultiplier; - - // TODO: BlockchainStats.NetworkHashrate = info.HeadersScore - } - } - private void ConfigureRewards() { // Donation to MiningCore development @@ -441,7 +419,7 @@ protected override async Task PostStartInitAsync(CancellationToken ct) var walletAddresses = await daemon.WalletAddressesAsync(ct); if(!walletAddresses.Contains(poolConfig.Address)) - logger.ThrowLogPoolStartupException($"Pool address {info.Name} is not controlled by wallet"); + logger.ThrowLogPoolStartupException($"Pool address {poolConfig.Address} is not controlled by wallet"); ConfigureRewards(); } @@ -450,15 +428,6 @@ protected override async Task PostStartInitAsync(CancellationToken ct) BlockchainStats.NetworkType = network; BlockchainStats.RewardType = "POW"; - await UpdateNetworkStatsAsync(); - - // Periodically update network stats - Observable.Interval(TimeSpan.FromMinutes(10)) - .Select(via => Observable.FromAsync(async () => - await Guard(UpdateNetworkStatsAsync, ex => logger.Error(ex)))) - .Concat() - .Subscribe(); - SetupJobUpdates(); } From 6b6ae587b49afc3ad0717b499b07664f2ee70313 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Tue, 13 Jul 2021 10:33:45 +0200 Subject: [PATCH 045/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs index 84be83ea7..d9b31a092 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs @@ -160,7 +160,6 @@ await GetBlockTemplateAsync() : var blockTimeAvg = 120; BlockchainStats.NetworkHashrate = BlockchainStats.NetworkDifficulty / blockTimeAvg; - BlockchainStats.NetworkHashrate *= Math.Pow(2, 32); } else From 2a2a020a834eff62bafbc84a845ac1e1e953e04a Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Tue, 13 Jul 2021 12:28:19 +0200 Subject: [PATCH 046/145] Add flat pool side hashrate bonus to account for miner dataset generation --- src/Miningcore/Blockchain/Ergo/ErgoPool.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs index 8cab4151e..5f436aaaa 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs @@ -287,6 +287,9 @@ public override double HashrateFromShares(double shares, double interval) var multiplier = BitcoinConstants.Pow2x32 * ErgoConstants.ShareMultiplier; var result = shares * multiplier / interval; + // add flat pool side hashrate bonus to account for miner dataset generation + result *= 1.15; + return result; } From 9c94a08a334106d6d07be7da0de9badc24b907f5 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Tue, 13 Jul 2021 13:48:21 +0200 Subject: [PATCH 047/145] more logging --- src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index c97bc3980..e3278efc4 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -263,9 +263,9 @@ public virtual async Task PayoutAsync(Balance[] balances) if(amounts.Count == 0) return; - logger.Info(() => $"[{LogCategory}] Paying out {FormatAmount(balances.Sum(x => x.Amount))} to {balances.Length} addresses"); - // unlock wallet + logger.Info(() => $"[{LogCategory}] Unlocking wallet"); + await Guard(() => daemon.WalletUnlockAsync(new Body4 {Pass = extraPoolPaymentProcessingConfig.WalletPassword ?? string.Empty}), ex => { @@ -278,6 +278,10 @@ await Guard(() => daemon.WalletUnlockAsync(new Body4 {Pass = extraPoolPaymentPro ReportAndRethrowApiError("Failed to unlock wallet", ex); }); + logger.Info(() => $"[{LogCategory}] Wallet unlocked"); + + logger.Info(() => $"[{LogCategory}] Paying out {FormatAmount(balances.Sum(x => x.Amount))} to {balances.Length} addresses"); + try { // Create request batch @@ -316,6 +320,8 @@ await Guard(() => daemon.WalletUnlockAsync(new Body4 {Pass = extraPoolPaymentPro finally { // lock wallet + logger.Info(() => $"[{LogCategory}] Locking wallet"); + await Guard(()=> daemon.WalletLockAsync(), ex=> ReportAndRethrowApiError("Failed to lock wallet", ex)); } From 0c37dd81d41f33d92de8b2e9c8e14aa98ef0a1e4 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Tue, 13 Jul 2021 14:17:41 +0200 Subject: [PATCH 048/145] confirmations --- src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index e3278efc4..261a2c915 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -54,7 +54,7 @@ public ErgoPayoutHandler( private string network; private ErgoPaymentProcessingConfigExtra extraPoolPaymentProcessingConfig; - protected override string LogCategory => "Bitcoin Payout Handler"; + protected override string LogCategory => "Ergo Payout Handler"; private void ReportAndRethrowApiError(string action, Exception ex, bool rethrow = true) { @@ -102,7 +102,7 @@ public virtual async Task<Block[]> ClassifyBlocksAsync(Block[] blocks) var pageSize = 100; var pageCount = (int) Math.Ceiling(blocks.Length / (double) pageSize); var result = new List<Block>(); - var minConfirmations = extraPoolConfig?.MinimumConfirmations ?? (network == "mainnet" ? 100 : 10); + var minConfirmations = extraPoolConfig?.MinimumConfirmations ?? (network == "mainnet" ? 720 : 72); var minerRewardsPubKey = await daemon.MiningReadMinerRewardPubkeyAsync(); var minerRewardsAddress = await daemon.MiningReadMinerRewardAddressAsync(); From 6455710b68594d727bad9273e18371ba0d43f15a Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Tue, 13 Jul 2021 14:29:41 +0200 Subject: [PATCH 049/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index 261a2c915..710f76f3d 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -264,7 +264,7 @@ public virtual async Task PayoutAsync(Balance[] balances) return; // unlock wallet - logger.Info(() => $"[{LogCategory}] Unlocking wallet"); + logger.Info(() => $"[{LogCategory}] Unlocking wallet using {extraPoolPaymentProcessingConfig.WalletPassword}"); await Guard(() => daemon.WalletUnlockAsync(new Body4 {Pass = extraPoolPaymentProcessingConfig.WalletPassword ?? string.Empty}), ex => From 52acdce2de46f608709d1e2f4da9ca4f470ad9f2 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Tue, 13 Jul 2021 14:52:21 +0200 Subject: [PATCH 050/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index 710f76f3d..e39f8bae1 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -264,9 +264,12 @@ public virtual async Task PayoutAsync(Balance[] balances) return; // unlock wallet - logger.Info(() => $"[{LogCategory}] Unlocking wallet using {extraPoolPaymentProcessingConfig.WalletPassword}"); + logger.Info(() => $"[{LogCategory}] Unlocking wallet"); - await Guard(() => daemon.WalletUnlockAsync(new Body4 {Pass = extraPoolPaymentProcessingConfig.WalletPassword ?? string.Empty}), + await Guard(() => daemon.WalletUnlockAsync(new Body4 + { + Pass = extraPoolPaymentProcessingConfig.WalletPassword ?? string.Empty + }), ex => { if(ex is ApiException<ApiError> apiException) @@ -299,6 +302,9 @@ await Guard(() => daemon.WalletUnlockAsync(new Body4 {Pass = extraPoolPaymentPro { error = apiException.Result.Detail ?? apiException.Result.Reason; + if(error.Contains("reason:")) + error = error.Substring(error.IndexOf("reason:")); + logger.Warn(() => $"Failed to initiate batch payment transaction: {error}"); } From c366dd9a45af160865bb860b769f888560da70cc Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Tue, 13 Jul 2021 14:58:44 +0200 Subject: [PATCH 051/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index e39f8bae1..47deb10c8 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -297,6 +297,7 @@ await Guard(() => daemon.WalletUnlockAsync(new Body4 var txId = await Guard(() => daemon.WalletPaymentTransactionGenerateAndSendAsync(requests), ex => { var error = ex.Message; + var noThrow = false; if(ex is ApiException<ApiError> apiException) { @@ -306,11 +307,14 @@ await Guard(() => daemon.WalletUnlockAsync(new Body4 error = error.Substring(error.IndexOf("reason:")); logger.Warn(() => $"Failed to initiate batch payment transaction: {error}"); + + noThrow = true; } NotifyPayoutFailure(poolConfig.Id, balances, $"/wallet/payment/send returned error: {error}", null); - throw ex; + if(!noThrow) + throw ex; }); if(!string.IsNullOrEmpty(txId)) From 3797fcd69930ffb8318d5229976af28f198dbdd8 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Tue, 13 Jul 2021 15:21:07 +0200 Subject: [PATCH 052/145] Shares double --- src/Miningcore/Api/Responses/GetMinerStatsResponse.cs | 2 +- src/Miningcore/Persistence/Model/Projections/MinerStats.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Miningcore/Api/Responses/GetMinerStatsResponse.cs b/src/Miningcore/Api/Responses/GetMinerStatsResponse.cs index a46525674..dbb17e8c2 100644 --- a/src/Miningcore/Api/Responses/GetMinerStatsResponse.cs +++ b/src/Miningcore/Api/Responses/GetMinerStatsResponse.cs @@ -24,7 +24,7 @@ public class WorkerPerformanceStatsContainer public class MinerStats { - public ulong PendingShares { get; set; } + public double PendingShares { get; set; } public decimal PendingBalance { get; set; } public decimal TotalPaid { get; set; } public decimal TodayPaid { get; set; } diff --git a/src/Miningcore/Persistence/Model/Projections/MinerStats.cs b/src/Miningcore/Persistence/Model/Projections/MinerStats.cs index 1a4dedc0d..741878bad 100644 --- a/src/Miningcore/Persistence/Model/Projections/MinerStats.cs +++ b/src/Miningcore/Persistence/Model/Projections/MinerStats.cs @@ -17,7 +17,7 @@ public class WorkerPerformanceStatsContainer public class MinerStats { - public ulong PendingShares { get; set; } + public double PendingShares { get; set; } public decimal PendingBalance { get; set; } public decimal TotalPaid { get; set; } public decimal TodayPaid { get; set; } From 5aa1155dc6c10b369109803848980fae46672809 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Tue, 13 Jul 2021 15:36:28 +0200 Subject: [PATCH 053/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index 47deb10c8..b7d299e96 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -334,6 +334,8 @@ await Guard(() => daemon.WalletUnlockAsync(new Body4 await Guard(()=> daemon.WalletLockAsync(), ex=> ReportAndRethrowApiError("Failed to lock wallet", ex)); + + logger.Info(() => $"[{LogCategory}] Wallet locked"); } } From d8b5c0b3e9a0e1de2046f0c1a8ea40793cab3dfb Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Tue, 13 Jul 2021 15:55:37 +0200 Subject: [PATCH 054/145] WIP --- .../Blockchain/Ergo/ErgoPayoutHandler.cs | 65 +++++++++---------- 1 file changed, 30 insertions(+), 35 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index b7d299e96..0d94cc545 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -266,20 +266,23 @@ public virtual async Task PayoutAsync(Balance[] balances) // unlock wallet logger.Info(() => $"[{LogCategory}] Unlocking wallet"); - await Guard(() => daemon.WalletUnlockAsync(new Body4 - { - Pass = extraPoolPaymentProcessingConfig.WalletPassword ?? string.Empty - }), - ex => - { - if(ex is ApiException<ApiError> apiException) - { - if(apiException.Result.Detail?.ToLower()?.Contains("already unlocked") == true) - return; - } + try + { + var walletPassword = extraPoolPaymentProcessingConfig.WalletPassword ?? string.Empty; + + await daemon.WalletUnlockAsync(new Body4 { Pass = walletPassword }); + } - ReportAndRethrowApiError("Failed to unlock wallet", ex); - }); + catch(ApiException<ApiError> ex) + { + var error = ex.Result.Detail; + + if(!error.ToLower().Contains("already unlocked")) + { + logger.Error(() => $"[{LogCategory}] Failed to unlock wallet: {error}"); + return; + } + } logger.Info(() => $"[{LogCategory}] Wallet unlocked"); @@ -294,28 +297,7 @@ await Guard(() => daemon.WalletUnlockAsync(new Body4 Value = (long) (x.Value * ErgoConstants.SmallestUnit), }).ToArray(); - var txId = await Guard(() => daemon.WalletPaymentTransactionGenerateAndSendAsync(requests), ex => - { - var error = ex.Message; - var noThrow = false; - - if(ex is ApiException<ApiError> apiException) - { - error = apiException.Result.Detail ?? apiException.Result.Reason; - - if(error.Contains("reason:")) - error = error.Substring(error.IndexOf("reason:")); - - logger.Warn(() => $"Failed to initiate batch payment transaction: {error}"); - - noThrow = true; - } - - NotifyPayoutFailure(poolConfig.Id, balances, $"/wallet/payment/send returned error: {error}", null); - - if(!noThrow) - throw ex; - }); + var txId = await daemon.WalletPaymentTransactionGenerateAndSendAsync(requests); if(!string.IsNullOrEmpty(txId)) { @@ -325,6 +307,19 @@ await Guard(() => daemon.WalletUnlockAsync(new Body4 NotifyPayoutSuccess(poolConfig.Id, balances, new[] { txId }, null); } + + else + logger.Error(() => $"[{LogCategory}] Payment transaction failed to return a transaction id"); + } + + catch(ApiException<ApiError> ex) + { + var error = ex.Result.Detail ?? ex.Result.Reason; + + if(error.Contains("reason:")) + error = error.Substring(error.IndexOf("reason:")); + + logger.Error(() => $"[{LogCategory}] Payment transaction failed: {error}"); } finally From bc50a0af5642ab1342eb90a17fa3141c482d0480 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Tue, 13 Jul 2021 16:28:23 +0200 Subject: [PATCH 055/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index 0d94cc545..b570479d1 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -280,6 +280,8 @@ public virtual async Task PayoutAsync(Balance[] balances) if(!error.ToLower().Contains("already unlocked")) { logger.Error(() => $"[{LogCategory}] Failed to unlock wallet: {error}"); + + NotifyPayoutFailure(poolConfig.Id, balances, $"Failed to unlock wallet: {error}", null); return; } } @@ -309,7 +311,11 @@ public virtual async Task PayoutAsync(Balance[] balances) } else + { logger.Error(() => $"[{LogCategory}] Payment transaction failed to return a transaction id"); + + NotifyPayoutFailure(poolConfig.Id, balances, $"Payment transaction failed to return a transaction id", null); + } } catch(ApiException<ApiError> ex) @@ -320,6 +326,8 @@ public virtual async Task PayoutAsync(Balance[] balances) error = error.Substring(error.IndexOf("reason:")); logger.Error(() => $"[{LogCategory}] Payment transaction failed: {error}"); + + NotifyPayoutFailure(poolConfig.Id, balances, $"Payment transaction failed: {error}", null); } finally From 4f767a1aa8a1fedd39abd7905902313220ea68e0 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Tue, 13 Jul 2021 16:41:19 +0200 Subject: [PATCH 056/145] refactor --- .../Blockchain/Ergo/ErgoPayoutHandler.cs | 118 ++++++++++-------- 1 file changed, 66 insertions(+), 52 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index b570479d1..82683ae36 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -56,6 +56,13 @@ public ErgoPayoutHandler( protected override string LogCategory => "Ergo Payout Handler"; + private class PaymentException : Exception + { + public PaymentException(string msg) : base(msg) + { + } + } + private void ReportAndRethrowApiError(string action, Exception ex, bool rethrow = true) { var error = ex.Message; @@ -69,6 +76,39 @@ private void ReportAndRethrowApiError(string action, Exception ex, bool rethrow throw ex; } + private async Task UnlockWallet() + { + logger.Info(() => $"[{LogCategory}] Unlocking wallet"); + + var walletPassword = extraPoolPaymentProcessingConfig.WalletPassword ?? string.Empty; + + await Guard(() => daemon.WalletUnlockAsync(new Body4 {Pass = walletPassword}), ex => + { + if (ex is ApiException<ApiError> apiException) + { + var error = apiException.Result.Detail; + + if (error != null && !error.ToLower().Contains("already unlocked")) + throw new PaymentException($"Failed to unlock wallet: {error}"); + } + + else + throw ex; + }); + + logger.Info(() => $"[{LogCategory}] Wallet unlocked"); + } + + private async Task LockWallet() + { + logger.Info(() => $"[{LogCategory}] Locking wallet"); + + await Guard(() => daemon.WalletLockAsync(), + ex => ReportAndRethrowApiError("Failed to lock wallet", ex)); + + logger.Info(() => $"[{LogCategory}] Wallet locked"); + } + #region IPayoutHandler public virtual async Task ConfigureAsync(ClusterConfig clusterConfig, PoolConfig poolConfig) @@ -263,35 +303,12 @@ public virtual async Task PayoutAsync(Balance[] balances) if(amounts.Count == 0) return; - // unlock wallet - logger.Info(() => $"[{LogCategory}] Unlocking wallet"); - try { - var walletPassword = extraPoolPaymentProcessingConfig.WalletPassword ?? string.Empty; + await UnlockWallet(); - await daemon.WalletUnlockAsync(new Body4 { Pass = walletPassword }); - } + logger.Info(() => $"[{LogCategory}] Paying out {FormatAmount(balances.Sum(x => x.Amount))} to {balances.Length} addresses"); - catch(ApiException<ApiError> ex) - { - var error = ex.Result.Detail; - - if(!error.ToLower().Contains("already unlocked")) - { - logger.Error(() => $"[{LogCategory}] Failed to unlock wallet: {error}"); - - NotifyPayoutFailure(poolConfig.Id, balances, $"Failed to unlock wallet: {error}", null); - return; - } - } - - logger.Info(() => $"[{LogCategory}] Wallet unlocked"); - - logger.Info(() => $"[{LogCategory}] Paying out {FormatAmount(balances.Sum(x => x.Amount))} to {balances.Length} addresses"); - - try - { // Create request batch var requests = amounts.Select(x => new PaymentRequest { @@ -299,46 +316,43 @@ public virtual async Task PayoutAsync(Balance[] balances) Value = (long) (x.Value * ErgoConstants.SmallestUnit), }).ToArray(); - var txId = await daemon.WalletPaymentTransactionGenerateAndSendAsync(requests); - - if(!string.IsNullOrEmpty(txId)) + var txId = await Guard(()=> daemon.WalletPaymentTransactionGenerateAndSendAsync(requests), ex => { - logger.Info(() => $"[{LogCategory}] Payout transaction id: {txId}"); + if(ex is ApiException<ApiError> apiException) + { + var error = apiException.Result.Detail ?? apiException.Result.Reason; - await PersistPaymentsAsync(balances, txId); + if(error.Contains("reason:")) + error = error.Substring(error.IndexOf("reason:")); - NotifyPayoutSuccess(poolConfig.Id, balances, new[] { txId }, null); - } + throw new PaymentException($"Payment transaction failed: {error}"); + } - else - { - logger.Error(() => $"[{LogCategory}] Payment transaction failed to return a transaction id"); + else + throw ex; + }); - NotifyPayoutFailure(poolConfig.Id, balances, $"Payment transaction failed to return a transaction id", null); - } - } + if(string.IsNullOrEmpty(txId)) + throw new PaymentException("Payment transaction failed to return a transaction id"); - catch(ApiException<ApiError> ex) - { - var error = ex.Result.Detail ?? ex.Result.Reason; - - if(error.Contains("reason:")) - error = error.Substring(error.IndexOf("reason:")); + // payment successful + logger.Info(() => $"[{LogCategory}] Payout transaction id: {txId}"); - logger.Error(() => $"[{LogCategory}] Payment transaction failed: {error}"); + await PersistPaymentsAsync(balances, txId); - NotifyPayoutFailure(poolConfig.Id, balances, $"Payment transaction failed: {error}", null); + NotifyPayoutSuccess(poolConfig.Id, balances, new[] {txId}, null); } - finally + catch(PaymentException ex) { - // lock wallet - logger.Info(() => $"[{LogCategory}] Locking wallet"); + logger.Error(() => $"[{LogCategory}] {ex.Message}"); - await Guard(()=> daemon.WalletLockAsync(), - ex=> ReportAndRethrowApiError("Failed to lock wallet", ex)); + NotifyPayoutFailure(poolConfig.Id, balances, ex.Message, null); + } - logger.Info(() => $"[{LogCategory}] Wallet locked"); + finally + { + await LockWallet(); } } From aa25689ebe42654ec1c21c978c34fcfa4a80ca76 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Tue, 13 Jul 2021 17:12:20 +0200 Subject: [PATCH 057/145] WIP --- .../Blockchain/Ergo/ErgoPayoutHandler.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index 82683ae36..3e2230ef7 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -305,9 +305,18 @@ public virtual async Task PayoutAsync(Balance[] balances) try { - await UnlockWallet(); + // get wallet status + var status = await daemon.GetWalletStatusAsync(); - logger.Info(() => $"[{LogCategory}] Paying out {FormatAmount(balances.Sum(x => x.Amount))} to {balances.Length} addresses"); + if(!status.IsInitialized) + throw new PaymentException($"Wallet is not initialized"); + + if(!status.IsUnlocked) + await UnlockWallet(); + + // get balance + var walletBalances = await daemon.WalletBalancesAsync(); + logger.Info(() => $"[{LogCategory}] Current wallet balance is {FormatAmount(walletBalances.Balance / ErgoConstants.SmallestUnit)}"); // Create request batch var requests = amounts.Select(x => new PaymentRequest @@ -316,6 +325,8 @@ public virtual async Task PayoutAsync(Balance[] balances) Value = (long) (x.Value * ErgoConstants.SmallestUnit), }).ToArray(); + logger.Info(() => $"[{LogCategory}] Paying out {FormatAmount(balances.Sum(x => x.Amount))} to {balances.Length} addresses"); + var txId = await Guard(()=> daemon.WalletPaymentTransactionGenerateAndSendAsync(requests), ex => { if(ex is ApiException<ApiError> apiException) From f67c0f904482dcab4263a3bccb7538f4cb377c19 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Wed, 14 Jul 2021 07:13:57 +0200 Subject: [PATCH 058/145] WIP --- src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs b/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs index f77ac2b76..b9ac5fd6b 100644 --- a/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs +++ b/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs @@ -9603,9 +9603,10 @@ public partial class BalancesSnapshot [Newtonsoft.Json.JsonProperty("balance", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public long Balance { get; set; }= default!; - - [Newtonsoft.Json.JsonProperty("assets", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public System.Collections.Generic.ICollection<Asset> Assets { get; set; }= default!; + + // The node returns an object for this rather than an array, causing deserialize to crash + //[Newtonsoft.Json.JsonProperty("assets", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + //public System.Collections.Generic.ICollection<Asset> Assets { get; set; }= default!; private System.Collections.Generic.IDictionary<string, object> _additionalProperties = new System.Collections.Generic.Dictionary<string, object>(); From c3125b09fa0d45727640308dec713949057b053c Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Wed, 14 Jul 2021 11:22:15 +0200 Subject: [PATCH 059/145] Provide pool instance to payment infrastructure --- .../Bitcoin/BitcoinPayoutHandler.cs | 7 ++- .../Cryptonote/CryptonotePayoutHandler.cs | 13 ++--- .../Equihash/EquihashPayoutHandler.cs | 3 +- .../Ethereum/EthereumPayoutHandler.cs | 11 ++-- src/Miningcore/Payments/Abstractions.cs | 12 ++-- .../PaymentSchemes/PPLNSPaymentScheme.cs | 5 +- .../PaymentSchemes/PROPPaymentScheme.cs | 5 +- .../PaymentSchemes/SOLOPaymentScheme.cs | 5 +- src/Miningcore/Payments/PayoutHandlerBase.cs | 7 ++- src/Miningcore/Payments/PayoutManager.cs | 56 ++++++++++--------- 10 files changed, 67 insertions(+), 57 deletions(-) diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs index 6a3e532e8..627d1c206 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs @@ -12,6 +12,7 @@ using Miningcore.DaemonInterface; using Miningcore.Extensions; using Miningcore.Messaging; +using Miningcore.Mining; using Miningcore.Notifications; using Miningcore.Notifications.Messages; using Miningcore.Payments; @@ -78,7 +79,7 @@ public virtual Task ConfigureAsync(ClusterConfig clusterConfig, PoolConfig poolC return Task.FromResult(true); } - public virtual async Task<Block[]> ClassifyBlocksAsync(Block[] blocks) + public virtual async Task<Block[]> ClassifyBlocksAsync(IMiningPool pool, Block[] blocks) { Contract.RequiresNonNull(poolConfig, nameof(poolConfig)); Contract.RequiresNonNull(blocks, nameof(blocks)); @@ -187,14 +188,14 @@ public virtual async Task<Block[]> ClassifyBlocksAsync(Block[] blocks) return result.ToArray(); } - public virtual Task CalculateBlockEffortAsync(Block block, double accumulatedBlockShareDiff) + public virtual Task CalculateBlockEffortAsync(IMiningPool pool, Block block, double accumulatedBlockShareDiff) { block.Effort = accumulatedBlockShareDiff / block.NetworkDifficulty; return Task.FromResult(true); } - public virtual async Task PayoutAsync(Balance[] balances) + public virtual async Task PayoutAsync(IMiningPool pool, Balance[] balances) { Contract.RequiresNonNull(balances, nameof(balances)); diff --git a/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs b/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs index 3e8683c63..4d6a0f997 100644 --- a/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs +++ b/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs @@ -12,6 +12,7 @@ using Miningcore.DaemonInterface; using Miningcore.Extensions; using Miningcore.Messaging; +using Miningcore.Mining; using Miningcore.Native; using Miningcore.Payments; using Miningcore.Persistence; @@ -336,14 +337,12 @@ public async Task ConfigureAsync(ClusterConfig clusterConfig, PoolConfig poolCon // detect network await UpdateNetworkTypeAsync(); -var response1 = await walletDaemon.ExecuteCmdSingleAsync<GetBalanceResponse>(logger, CryptonoteWalletCommands.GetBalance); - // detect transfer_split support var response = await walletDaemon.ExecuteCmdSingleAsync<TransferResponse>(logger, CryptonoteWalletCommands.TransferSplit); walletSupportsTransferSplit = response.Error.Code != CryptonoteConstants.MoneroRpcMethodNotFound; } - public async Task<Block[]> ClassifyBlocksAsync(Block[] blocks) + public async Task<Block[]> ClassifyBlocksAsync(IMiningPool pool, Block[] blocks) { Contract.RequiresNonNull(poolConfig, nameof(poolConfig)); Contract.RequiresNonNull(blocks, nameof(blocks)); @@ -420,16 +419,16 @@ public async Task<Block[]> ClassifyBlocksAsync(Block[] blocks) return result.ToArray(); } - public Task CalculateBlockEffortAsync(Block block, double accumulatedBlockShareDiff) + public Task CalculateBlockEffortAsync(IMiningPool pool, Block block, double accumulatedBlockShareDiff) { block.Effort = accumulatedBlockShareDiff / block.NetworkDifficulty; return Task.FromResult(true); } - public override async Task<decimal> UpdateBlockRewardBalancesAsync(IDbConnection con, IDbTransaction tx, Block block, PoolConfig pool) + public override async Task<decimal> UpdateBlockRewardBalancesAsync(IDbConnection con, IDbTransaction tx, IMiningPool pool, Block block) { - var blockRewardRemaining = await base.UpdateBlockRewardBalancesAsync(con, tx, block, pool); + var blockRewardRemaining = await base.UpdateBlockRewardBalancesAsync(con, tx, pool, block); // Deduct static reserve for tx fees blockRewardRemaining -= CryptonoteConstants.StaticTransactionFeeReserve; @@ -437,7 +436,7 @@ public override async Task<decimal> UpdateBlockRewardBalancesAsync(IDbConnection return blockRewardRemaining; } - public async Task PayoutAsync(Balance[] balances) + public async Task PayoutAsync(IMiningPool pool, Balance[] balances) { Contract.RequiresNonNull(balances, nameof(balances)); diff --git a/src/Miningcore/Blockchain/Equihash/EquihashPayoutHandler.cs b/src/Miningcore/Blockchain/Equihash/EquihashPayoutHandler.cs index 6f0dc38dd..b35a9a446 100644 --- a/src/Miningcore/Blockchain/Equihash/EquihashPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Equihash/EquihashPayoutHandler.cs @@ -11,6 +11,7 @@ using Miningcore.Configuration; using Miningcore.Extensions; using Miningcore.Messaging; +using Miningcore.Mining; using Miningcore.Persistence; using Miningcore.Persistence.Model; using Miningcore.Persistence.Repositories; @@ -67,7 +68,7 @@ public override async Task ConfigureAsync(ClusterConfig clusterConfig, PoolConfi supportsNativeShielding = response.Error.Code != (int) BitcoinRPCErrorCode.RPC_METHOD_NOT_FOUND; } - public override async Task PayoutAsync(Balance[] balances) + public override async Task PayoutAsync(IMiningPool pool, Balance[] balances) { Contract.RequiresNonNull(balances, nameof(balances)); diff --git a/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs b/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs index 16fe1fd99..f98c9acf3 100644 --- a/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs @@ -13,6 +13,7 @@ using Miningcore.DaemonInterface; using Miningcore.Extensions; using Miningcore.Messaging; +using Miningcore.Mining; using Miningcore.Payments; using Miningcore.Persistence; using Miningcore.Persistence.Model; @@ -83,7 +84,7 @@ public async Task ConfigureAsync(ClusterConfig clusterConfig, PoolConfig poolCon await DetectChainAsync(); } - public async Task<Block[]> ClassifyBlocksAsync(Block[] blocks) + public async Task<Block[]> ClassifyBlocksAsync(IMiningPool pool, Block[] blocks) { Contract.RequiresNonNull(poolConfig, nameof(poolConfig)); Contract.RequiresNonNull(blocks, nameof(blocks)); @@ -224,16 +225,16 @@ public async Task<Block[]> ClassifyBlocksAsync(Block[] blocks) return result.ToArray(); } - public Task CalculateBlockEffortAsync(Block block, double accumulatedBlockShareDiff) + public Task CalculateBlockEffortAsync(IMiningPool pool, Block block, double accumulatedBlockShareDiff) { block.Effort = accumulatedBlockShareDiff / block.NetworkDifficulty; return Task.FromResult(true); } - public override async Task<decimal> UpdateBlockRewardBalancesAsync(IDbConnection con, IDbTransaction tx, Block block, PoolConfig pool) + public override async Task<decimal> UpdateBlockRewardBalancesAsync(IDbConnection con, IDbTransaction tx, IMiningPool pool, Block block) { - var blockRewardRemaining = await base.UpdateBlockRewardBalancesAsync(con, tx, block, pool); + var blockRewardRemaining = await base.UpdateBlockRewardBalancesAsync(con, tx, pool, block); // Deduct static reserve for tx fees blockRewardRemaining -= EthereumConstants.StaticTransactionFeeReserve; @@ -241,7 +242,7 @@ public override async Task<decimal> UpdateBlockRewardBalancesAsync(IDbConnection return blockRewardRemaining; } - public async Task PayoutAsync(Balance[] balances) + public async Task PayoutAsync(IMiningPool pool, Balance[] balances) { // ensure we have peers var infoResponse = await daemon.ExecuteCmdSingleAsync<string>(logger, EC.GetPeerCount); diff --git a/src/Miningcore/Payments/Abstractions.cs b/src/Miningcore/Payments/Abstractions.cs index 10d637178..b846a9ca5 100644 --- a/src/Miningcore/Payments/Abstractions.cs +++ b/src/Miningcore/Payments/Abstractions.cs @@ -1,6 +1,7 @@ using System.Data; using System.Threading.Tasks; using Miningcore.Configuration; +using Miningcore.Mining; using Miningcore.Persistence.Model; namespace Miningcore.Payments @@ -9,17 +10,16 @@ public interface IPayoutHandler { Task ConfigureAsync(ClusterConfig clusterConfig, PoolConfig poolConfig); - Task<Block[]> ClassifyBlocksAsync(Block[] blocks); - Task CalculateBlockEffortAsync(Block block, double accumulatedBlockShareDiff); - Task<decimal> UpdateBlockRewardBalancesAsync(IDbConnection con, IDbTransaction tx, Block block, PoolConfig pool); - Task PayoutAsync(Balance[] balances); + Task<Block[]> ClassifyBlocksAsync(IMiningPool pool, Block[] blocks); + Task CalculateBlockEffortAsync(IMiningPool pool, Block block, double accumulatedBlockShareDiff); + Task<decimal> UpdateBlockRewardBalancesAsync(IDbConnection con, IDbTransaction tx, IMiningPool pool, Block block); + Task PayoutAsync(IMiningPool pool, Balance[] balances); string FormatAmount(decimal amount); } public interface IPayoutScheme { - Task UpdateBalancesAsync(IDbConnection con, IDbTransaction tx, PoolConfig poolConfig, - IPayoutHandler payoutHandler, Block block, decimal blockReward); + Task UpdateBalancesAsync(IDbConnection con, IDbTransaction tx, IMiningPool pool, IPayoutHandler payoutHandler, Block block, decimal blockReward); } } diff --git a/src/Miningcore/Payments/PaymentSchemes/PPLNSPaymentScheme.cs b/src/Miningcore/Payments/PaymentSchemes/PPLNSPaymentScheme.cs index 8ab9b0dd4..09ae831ec 100644 --- a/src/Miningcore/Payments/PaymentSchemes/PPLNSPaymentScheme.cs +++ b/src/Miningcore/Payments/PaymentSchemes/PPLNSPaymentScheme.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Miningcore.Configuration; using Miningcore.Extensions; +using Miningcore.Mining; using Miningcore.Persistence; using Miningcore.Persistence.Model; using Miningcore.Persistence.Repositories; @@ -57,9 +58,9 @@ private class Config #region IPayoutScheme - public async Task UpdateBalancesAsync(IDbConnection con, IDbTransaction tx, PoolConfig poolConfig, - IPayoutHandler payoutHandler, Block block, decimal blockReward) + public async Task UpdateBalancesAsync(IDbConnection con, IDbTransaction tx, IMiningPool pool, IPayoutHandler payoutHandler, Block block, decimal blockReward) { + var poolConfig = pool.Config; var payoutConfig = poolConfig.PaymentProcessing.PayoutSchemeConfig; // PPLNS window (see https://bitcointalk.org/index.php?topic=39832) diff --git a/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs b/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs index fed1ef70f..4ccbf72d3 100644 --- a/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs +++ b/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Miningcore.Configuration; using Miningcore.Extensions; +using Miningcore.Mining; using Miningcore.Persistence; using Miningcore.Persistence.Model; using Miningcore.Persistence.Repositories; @@ -57,9 +58,9 @@ private class Config #region IPayoutScheme - public async Task UpdateBalancesAsync(IDbConnection con, IDbTransaction tx, PoolConfig poolConfig, - IPayoutHandler payoutHandler, Block block, decimal blockReward) + public async Task UpdateBalancesAsync(IDbConnection con, IDbTransaction tx, IMiningPool pool, IPayoutHandler payoutHandler, Block block, decimal blockReward) { + var poolConfig = pool.Config; var payoutConfig = poolConfig.PaymentProcessing.PayoutSchemeConfig; var shares = new Dictionary<string, double>(); var rewards = new Dictionary<string, decimal>(); diff --git a/src/Miningcore/Payments/PaymentSchemes/SOLOPaymentScheme.cs b/src/Miningcore/Payments/PaymentSchemes/SOLOPaymentScheme.cs index 198e69e7c..009534465 100644 --- a/src/Miningcore/Payments/PaymentSchemes/SOLOPaymentScheme.cs +++ b/src/Miningcore/Payments/PaymentSchemes/SOLOPaymentScheme.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks; using Miningcore.Configuration; +using Miningcore.Mining; using Miningcore.Persistence; using Miningcore.Persistence.Model; using Miningcore.Persistence.Repositories; @@ -35,8 +36,10 @@ public SOLOPaymentScheme( #region IPayoutScheme - public async Task UpdateBalancesAsync(IDbConnection con, IDbTransaction tx, PoolConfig poolConfig, IPayoutHandler payoutHandler, Block block, decimal blockReward) + public async Task UpdateBalancesAsync(IDbConnection con, IDbTransaction tx, IMiningPool pool, IPayoutHandler payoutHandler, Block block, decimal blockReward) { + var poolConfig = pool.Config; + // calculate rewards var rewards = new Dictionary<string, decimal>(); var shareCutOffDate = CalculateRewards(block, blockReward, rewards); diff --git a/src/Miningcore/Payments/PayoutHandlerBase.cs b/src/Miningcore/Payments/PayoutHandlerBase.cs index 11cffde35..16da8733c 100644 --- a/src/Miningcore/Payments/PayoutHandlerBase.cs +++ b/src/Miningcore/Payments/PayoutHandlerBase.cs @@ -1,13 +1,14 @@ using System; -using System.Data; +using System.Data; using System.Data.Common; using System.Linq; -using System.Threading.Tasks; +using System.Threading.Tasks; using AutoMapper; using Miningcore.Blockchain; using Miningcore.Configuration; using Miningcore.Extensions; using Miningcore.Messaging; +using Miningcore.Mining; using Miningcore.Notifications.Messages; using Miningcore.Persistence; using Miningcore.Persistence.Model; @@ -85,7 +86,7 @@ protected virtual void OnRetry(Exception ex, TimeSpan timeSpan, int retry, objec logger.Warn(() => $"[{LogCategory}] Retry {1} in {timeSpan} due to: {ex}"); } - public virtual async Task<decimal> UpdateBlockRewardBalancesAsync(IDbConnection con, IDbTransaction tx, Block block, PoolConfig pool) + public virtual async Task<decimal> UpdateBlockRewardBalancesAsync(IDbConnection con, IDbTransaction tx, IMiningPool pool, Block block) { var blockRewardRemaining = block.Reward; diff --git a/src/Miningcore/Payments/PayoutManager.cs b/src/Miningcore/Payments/PayoutManager.cs index d311ee454..f05705d79 100644 --- a/src/Miningcore/Payments/PayoutManager.cs +++ b/src/Miningcore/Payments/PayoutManager.cs @@ -87,31 +87,33 @@ private void OnPoolStatusNotification(PoolStatusNotification notification) private async Task ProcessPoolsAsync() { - foreach(var pool in pools.Values.ToArray().Select(x=> x.Config).Where(x => x.Enabled && x.PaymentProcessing.Enabled)) + foreach(var pool in pools.Values.ToArray().Where(x => x.Config.Enabled && x.Config.PaymentProcessing.Enabled)) { - logger.Info(() => $"Processing payments for pool {pool.Id}"); + var config = pool.Config; + + logger.Info(() => $"Processing payments for pool {config.Id}"); try { - var family = HandleFamilyOverride(pool.Template.Family, pool); + var family = HandleFamilyOverride(config.Template.Family, config); // resolve payout handler var handlerImpl = ctx.Resolve<IEnumerable<Meta<Lazy<IPayoutHandler, CoinFamilyAttribute>>>>() .First(x => x.Value.Metadata.SupportedFamilies.Contains(family)).Value; var handler = handlerImpl.Value; - await handler.ConfigureAsync(clusterConfig, pool); + await handler.ConfigureAsync(clusterConfig, config); // resolve payout scheme - var scheme = ctx.ResolveKeyed<IPayoutScheme>(pool.PaymentProcessing.PayoutScheme); + var scheme = ctx.ResolveKeyed<IPayoutScheme>(config.PaymentProcessing.PayoutScheme); - await UpdatePoolBalancesAsync(pool, handler, scheme); - await PayoutPoolBalancesAsync(pool, handler); + await UpdatePoolBalancesAsync(pool, config, handler, scheme); + await PayoutPoolBalancesAsync(pool, config, handler); } catch(InvalidOperationException ex) { - logger.Error(ex.InnerException ?? ex, () => $"[{pool.Id}] Payment processing failed"); + logger.Error(ex.InnerException ?? ex, () => $"[{config.Id}] Payment processing failed"); } catch(AggregateException ex) @@ -119,18 +121,18 @@ private async Task ProcessPoolsAsync() switch(ex.InnerException) { case HttpRequestException httpEx: - logger.Error(() => $"[{pool.Id}] Payment processing failed: {httpEx.Message}"); + logger.Error(() => $"[{config.Id}] Payment processing failed: {httpEx.Message}"); break; default: - logger.Error(ex.InnerException, () => $"[{pool.Id}] Payment processing failed"); + logger.Error(ex.InnerException, () => $"[{config.Id}] Payment processing failed"); break; } } catch(Exception ex) { - logger.Error(ex, () => $"[{pool.Id}] Payment processing failed"); + logger.Error(ex, () => $"[{config.Id}] Payment processing failed"); } } } @@ -151,31 +153,31 @@ private static CoinFamily HandleFamilyOverride(CoinFamily family, PoolConfig poo return family; } - private async Task UpdatePoolBalancesAsync(PoolConfig pool, IPayoutHandler handler, IPayoutScheme scheme) + private async Task UpdatePoolBalancesAsync(IMiningPool pool, PoolConfig config, IPayoutHandler handler, IPayoutScheme scheme) { // get pending blockRepo for pool - var pendingBlocks = await cf.Run(con => blockRepo.GetPendingBlocksForPoolAsync(con, pool.Id)); + var pendingBlocks = await cf.Run(con => blockRepo.GetPendingBlocksForPoolAsync(con, config.Id)); // classify - var updatedBlocks = await handler.ClassifyBlocksAsync(pendingBlocks); + var updatedBlocks = await handler.ClassifyBlocksAsync(pool, pendingBlocks); if(updatedBlocks.Any()) { foreach(var block in updatedBlocks.OrderBy(x => x.Created)) { - logger.Info(() => $"Processing payments for pool {pool.Id}, block {block.BlockHeight}"); + logger.Info(() => $"Processing payments for pool {config.Id}, block {block.BlockHeight}"); await cf.RunTx(async (con, tx) => { if(!block.Effort.HasValue) // fill block effort if empty - await CalculateBlockEffortAsync(pool, block, handler); + await CalculateBlockEffortAsync(pool, config, block, handler); switch(block.Status) { case BlockStatus.Confirmed: // blockchains that do not support block-reward payments via coinbase Tx // must generate balance records for all reward recipients instead - var blockReward = await handler.UpdateBlockRewardBalancesAsync(con, tx, block, pool); + var blockReward = await handler.UpdateBlockRewardBalancesAsync(con, tx, pool, block); await scheme.UpdateBalancesAsync(con, tx, pool, handler, block, blockReward); await blockRepo.UpdateBlockAsync(con, tx, block); @@ -191,30 +193,30 @@ await cf.RunTx(async (con, tx) => } else - logger.Info(() => $"No updated blocks for pool {pool.Id}"); + logger.Info(() => $"No updated blocks for pool {config.Id}"); } - private async Task PayoutPoolBalancesAsync(PoolConfig pool, IPayoutHandler handler) + private async Task PayoutPoolBalancesAsync(IMiningPool pool, PoolConfig config, IPayoutHandler handler) { var poolBalancesOverMinimum = await cf.Run(con => - balanceRepo.GetPoolBalancesOverThresholdAsync(con, pool.Id, pool.PaymentProcessing.MinimumPayment)); + balanceRepo.GetPoolBalancesOverThresholdAsync(con, config.Id, config.PaymentProcessing.MinimumPayment)); if(poolBalancesOverMinimum.Length > 0) { try { - await handler.PayoutAsync(poolBalancesOverMinimum); + await handler.PayoutAsync(pool, poolBalancesOverMinimum); } catch(Exception ex) { - await NotifyPayoutFailureAsync(poolBalancesOverMinimum, pool, ex); + await NotifyPayoutFailureAsync(poolBalancesOverMinimum, config, ex); throw; } } else - logger.Info(() => $"No balances over configured minimum payout for pool {pool.Id}"); + logger.Info(() => $"No balances over configured minimum payout for pool {config.Id}"); } private Task NotifyPayoutFailureAsync(Balance[] balances, PoolConfig pool, Exception ex) @@ -224,14 +226,14 @@ private Task NotifyPayoutFailureAsync(Balance[] balances, PoolConfig pool, Excep return Task.FromResult(true); } - private async Task CalculateBlockEffortAsync(PoolConfig pool, Block block, IPayoutHandler handler) + private async Task CalculateBlockEffortAsync(IMiningPool pool, PoolConfig config, Block block, IPayoutHandler handler) { // get share date-range var from = DateTime.MinValue; var to = block.Created; // get last block for pool - var lastBlock = await cf.Run(con => blockRepo.GetBlockBeforeAsync(con, pool.Id, new[] + var lastBlock = await cf.Run(con => blockRepo.GetBlockBeforeAsync(con, config.Id, new[] { BlockStatus.Confirmed, BlockStatus.Orphaned, @@ -243,11 +245,11 @@ private async Task CalculateBlockEffortAsync(PoolConfig pool, Block block, IPayo // get combined diff of all shares for block var accumulatedShareDiffForBlock = await cf.Run(con => - shareRepo.GetAccumulatedShareDifficultyBetweenCreatedAsync(con, pool.Id, from, to)); + shareRepo.GetAccumulatedShareDifficultyBetweenCreatedAsync(con, config.Id, from, to)); // handler has the final say if(accumulatedShareDiffForBlock.HasValue) - await handler.CalculateBlockEffortAsync(block, accumulatedShareDiffForBlock.Value); + await handler.CalculateBlockEffortAsync(pool, block, accumulatedShareDiffForBlock.Value); } protected override async Task ExecuteAsync(CancellationToken ct) From 8a4452e5992eb568650e7e684993861dcf402984 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Wed, 14 Jul 2021 11:46:18 +0200 Subject: [PATCH 060/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index 3e2230ef7..df5721324 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -9,6 +9,7 @@ using Miningcore.Configuration; using Miningcore.Extensions; using Miningcore.Messaging; +using Miningcore.Mining; using Miningcore.Payments; using Miningcore.Persistence; using Miningcore.Persistence.Model; @@ -130,7 +131,7 @@ public virtual async Task ConfigureAsync(ClusterConfig clusterConfig, PoolConfig network = ErgoConstants.RegexChain.Match(info.Name).Groups[1].Value.ToLower(); } - public virtual async Task<Block[]> ClassifyBlocksAsync(Block[] blocks) + public virtual async Task<Block[]> ClassifyBlocksAsync(IMiningPool pool, Block[] blocks) { Contract.RequiresNonNull(poolConfig, nameof(poolConfig)); Contract.RequiresNonNull(blocks, nameof(blocks)); @@ -284,14 +285,14 @@ await Guard(()=> Task.WhenAll(blockBatch), return result.ToArray(); } - public virtual Task CalculateBlockEffortAsync(Block block, double accumulatedBlockShareDiff) + public virtual Task CalculateBlockEffortAsync(IMiningPool pool, Block block, double accumulatedBlockShareDiff) { block.Effort = accumulatedBlockShareDiff / block.NetworkDifficulty; return Task.FromResult(true); } - public virtual async Task PayoutAsync(Balance[] balances) + public virtual async Task PayoutAsync(IMiningPool pool, Balance[] balances) { Contract.RequiresNonNull(balances, nameof(balances)); From 6246a4af7298f5263f72afa1dce8af87351c3086 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Wed, 14 Jul 2021 11:45:26 +0200 Subject: [PATCH 061/145] More refactoring --- src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs | 2 +- .../Payments/PaymentSchemes/PPLNSPaymentScheme.cs | 6 +++--- src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs index 627d1c206..a058d5770 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs @@ -190,7 +190,7 @@ public virtual async Task<Block[]> ClassifyBlocksAsync(IMiningPool pool, Block[] public virtual Task CalculateBlockEffortAsync(IMiningPool pool, Block block, double accumulatedBlockShareDiff) { - block.Effort = accumulatedBlockShareDiff / block.NetworkDifficulty; + block.Effort = accumulatedBlockShareDiff * pool.ShareMultiplier / block.NetworkDifficulty; return Task.FromResult(true); } diff --git a/src/Miningcore/Payments/PaymentSchemes/PPLNSPaymentScheme.cs b/src/Miningcore/Payments/PaymentSchemes/PPLNSPaymentScheme.cs index 09ae831ec..5c8a0ca60 100644 --- a/src/Miningcore/Payments/PaymentSchemes/PPLNSPaymentScheme.cs +++ b/src/Miningcore/Payments/PaymentSchemes/PPLNSPaymentScheme.cs @@ -69,7 +69,7 @@ public async Task UpdateBalancesAsync(IDbConnection con, IDbTransaction tx, IMin // calculate rewards var shares = new Dictionary<string, double>(); var rewards = new Dictionary<string, decimal>(); - var shareCutOffDate = await CalculateRewardsAsync(poolConfig, window, block, blockReward, shares, rewards); + var shareCutOffDate = await CalculateRewardsAsync(pool, window, block, blockReward, shares, rewards); // update balances foreach(var address in rewards.Keys) @@ -155,9 +155,10 @@ private async Task LogDiscardedSharesAsync(PoolConfig poolConfig, Block block, D #endregion // IPayoutScheme - private async Task<DateTime?> CalculateRewardsAsync(PoolConfig poolConfig, decimal window, Block block, decimal blockReward, + private async Task<DateTime?> CalculateRewardsAsync(IMiningPool pool, decimal window, Block block, decimal blockReward, Dictionary<string, double> shares, Dictionary<string, decimal> rewards) { + var poolConfig = pool.Config; var done = false; var before = block.Created; var inclusive = true; @@ -166,7 +167,6 @@ private async Task LogDiscardedSharesAsync(PoolConfig poolConfig, Block block, D var accumulatedScore = 0.0m; var blockRewardRemaining = blockReward; DateTime? shareCutOffDate = null; - //var sw = new Stopwatch(); while(!done) { diff --git a/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs b/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs index 4ccbf72d3..9b79be5b6 100644 --- a/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs +++ b/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs @@ -61,10 +61,9 @@ private class Config public async Task UpdateBalancesAsync(IDbConnection con, IDbTransaction tx, IMiningPool pool, IPayoutHandler payoutHandler, Block block, decimal blockReward) { var poolConfig = pool.Config; - var payoutConfig = poolConfig.PaymentProcessing.PayoutSchemeConfig; var shares = new Dictionary<string, double>(); var rewards = new Dictionary<string, decimal>(); - var shareCutOffDate = await CalculateRewardsAsync(poolConfig, block, blockReward, shares, rewards); + var shareCutOffDate = await CalculateRewardsAsync(pool, block, blockReward, shares, rewards); // update balances foreach(var address in rewards.Keys) @@ -148,9 +147,10 @@ private async Task LogDiscardedSharesAsync(PoolConfig poolConfig, Block block, D #endregion // IPayoutScheme - private async Task<DateTime?> CalculateRewardsAsync(PoolConfig poolConfig, Block block, decimal blockReward, + private async Task<DateTime?> CalculateRewardsAsync(IMiningPool pool, Block block, decimal blockReward, Dictionary<string, double> shares, Dictionary<string, decimal> rewards) { + var poolConfig = pool.Config; var done = false; var before = block.Created; var inclusive = true; From fcddeb104645cff135abd192ab0954f35fd9f995 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Wed, 14 Jul 2021 11:22:15 +0200 Subject: [PATCH 062/145] Provide pool instance to payment infrastructure --- src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs b/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs index 9b79be5b6..236fa1837 100644 --- a/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs +++ b/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs @@ -60,10 +60,12 @@ private class Config public async Task UpdateBalancesAsync(IDbConnection con, IDbTransaction tx, IMiningPool pool, IPayoutHandler payoutHandler, Block block, decimal blockReward) { + var poolConfig = pool.Config; + var payoutConfig = poolConfig.PaymentProcessing.PayoutSchemeConfig; var poolConfig = pool.Config; var shares = new Dictionary<string, double>(); var rewards = new Dictionary<string, decimal>(); - var shareCutOffDate = await CalculateRewardsAsync(pool, block, blockReward, shares, rewards); + var shareCutOffDate = await CalculateRewardsAsync(poolConfig, block, blockReward, shares, rewards); // update balances foreach(var address in rewards.Keys) From ea32f67f62a8eb0333c8b3504690ae40a7e075c9 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Wed, 14 Jul 2021 11:45:26 +0200 Subject: [PATCH 063/145] More refactoring --- src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs b/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs index 236fa1837..b2cded71a 100644 --- a/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs +++ b/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs @@ -62,10 +62,9 @@ public async Task UpdateBalancesAsync(IDbConnection con, IDbTransaction tx, IMin { var poolConfig = pool.Config; var payoutConfig = poolConfig.PaymentProcessing.PayoutSchemeConfig; - var poolConfig = pool.Config; var shares = new Dictionary<string, double>(); var rewards = new Dictionary<string, decimal>(); - var shareCutOffDate = await CalculateRewardsAsync(poolConfig, block, blockReward, shares, rewards); + var shareCutOffDate = await CalculateRewardsAsync(pool, block, blockReward, shares, rewards); // update balances foreach(var address in rewards.Keys) @@ -149,10 +148,9 @@ private async Task LogDiscardedSharesAsync(PoolConfig poolConfig, Block block, D #endregion // IPayoutScheme - private async Task<DateTime?> CalculateRewardsAsync(IMiningPool pool, Block block, decimal blockReward, + private async Task<DateTime?> CalculateRewardsAsync(PoolConfig poolConfig, Block block, decimal blockReward, Dictionary<string, double> shares, Dictionary<string, decimal> rewards) { - var poolConfig = pool.Config; var done = false; var before = block.Created; var inclusive = true; From da4cd9c634a9b0627c62c6aef342118e00983c19 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Wed, 14 Jul 2021 14:56:57 +0200 Subject: [PATCH 064/145] Revert change --- src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs index a058d5770..627d1c206 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs @@ -190,7 +190,7 @@ public virtual async Task<Block[]> ClassifyBlocksAsync(IMiningPool pool, Block[] public virtual Task CalculateBlockEffortAsync(IMiningPool pool, Block block, double accumulatedBlockShareDiff) { - block.Effort = accumulatedBlockShareDiff * pool.ShareMultiplier / block.NetworkDifficulty; + block.Effort = accumulatedBlockShareDiff / block.NetworkDifficulty; return Task.FromResult(true); } From 8d385443f80a9cc4c52464abef35684766e121ba Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Wed, 14 Jul 2021 15:09:27 +0200 Subject: [PATCH 065/145] Adjustable share diff at payment level --- src/Miningcore/Payments/Abstractions.cs | 1 + .../Payments/PaymentSchemes/PPLNSPaymentScheme.cs | 6 +++--- src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs | 6 +++--- src/Miningcore/Payments/PayoutHandlerBase.cs | 5 +++++ 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Miningcore/Payments/Abstractions.cs b/src/Miningcore/Payments/Abstractions.cs index b846a9ca5..1db909c8e 100644 --- a/src/Miningcore/Payments/Abstractions.cs +++ b/src/Miningcore/Payments/Abstractions.cs @@ -16,6 +16,7 @@ public interface IPayoutHandler Task PayoutAsync(IMiningPool pool, Balance[] balances); string FormatAmount(decimal amount); + double AdjustShareDifficulty(double difficulty); } public interface IPayoutScheme diff --git a/src/Miningcore/Payments/PaymentSchemes/PPLNSPaymentScheme.cs b/src/Miningcore/Payments/PaymentSchemes/PPLNSPaymentScheme.cs index 5c8a0ca60..0856d9b93 100644 --- a/src/Miningcore/Payments/PaymentSchemes/PPLNSPaymentScheme.cs +++ b/src/Miningcore/Payments/PaymentSchemes/PPLNSPaymentScheme.cs @@ -69,7 +69,7 @@ public async Task UpdateBalancesAsync(IDbConnection con, IDbTransaction tx, IMin // calculate rewards var shares = new Dictionary<string, double>(); var rewards = new Dictionary<string, decimal>(); - var shareCutOffDate = await CalculateRewardsAsync(pool, window, block, blockReward, shares, rewards); + var shareCutOffDate = await CalculateRewardsAsync(pool, payoutHandler, window, block, blockReward, shares, rewards); // update balances foreach(var address in rewards.Keys) @@ -155,7 +155,7 @@ private async Task LogDiscardedSharesAsync(PoolConfig poolConfig, Block block, D #endregion // IPayoutScheme - private async Task<DateTime?> CalculateRewardsAsync(IMiningPool pool, decimal window, Block block, decimal blockReward, + private async Task<DateTime?> CalculateRewardsAsync(IMiningPool pool, IPayoutHandler payoutHandler, decimal window, Block block, decimal blockReward, Dictionary<string, double> shares, Dictionary<string, decimal> rewards) { var poolConfig = pool.Config; @@ -191,7 +191,7 @@ private async Task LogDiscardedSharesAsync(PoolConfig poolConfig, Block block, D else shares[address] += share.Difficulty; - var score = (decimal) (share.Difficulty / share.NetworkDifficulty); + var score = (decimal) (payoutHandler.AdjustShareDifficulty(share.Difficulty) / share.NetworkDifficulty); // if accumulated score would cross threshold, cap it to the remaining value if(accumulatedScore + score >= window) diff --git a/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs b/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs index b2cded71a..e228c6441 100644 --- a/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs +++ b/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs @@ -64,7 +64,7 @@ public async Task UpdateBalancesAsync(IDbConnection con, IDbTransaction tx, IMin var payoutConfig = poolConfig.PaymentProcessing.PayoutSchemeConfig; var shares = new Dictionary<string, double>(); var rewards = new Dictionary<string, decimal>(); - var shareCutOffDate = await CalculateRewardsAsync(pool, block, blockReward, shares, rewards); + var shareCutOffDate = await CalculateRewardsAsync(pool, payoutHandler, block, blockReward, shares, rewards); // update balances foreach(var address in rewards.Keys) @@ -148,7 +148,7 @@ private async Task LogDiscardedSharesAsync(PoolConfig poolConfig, Block block, D #endregion // IPayoutScheme - private async Task<DateTime?> CalculateRewardsAsync(PoolConfig poolConfig, Block block, decimal blockReward, + private async Task<DateTime?> CalculateRewardsAsync(IMiningPool pool, IPayoutHandler payoutHandler, Block block, decimal blockReward, Dictionary<string, double> shares, Dictionary<string, decimal> rewards) { var done = false; @@ -182,7 +182,7 @@ private async Task LogDiscardedSharesAsync(PoolConfig poolConfig, Block block, D else shares[address] += share.Difficulty; - var score = (decimal) (share.Difficulty / share.NetworkDifficulty); + var score = (decimal) (payoutHandler.AdjustShareDifficulty(share.Difficulty) / share.NetworkDifficulty); if(!scores.ContainsKey(address)) scores[address] = score; diff --git a/src/Miningcore/Payments/PayoutHandlerBase.cs b/src/Miningcore/Payments/PayoutHandlerBase.cs index 16da8733c..d2e97ab68 100644 --- a/src/Miningcore/Payments/PayoutHandlerBase.cs +++ b/src/Miningcore/Payments/PayoutHandlerBase.cs @@ -153,6 +153,11 @@ await cf.RunTx(async (con, tx) => } } + public double AdjustShareDifficulty(double difficulty) + { + return difficulty; + } + public string FormatAmount(decimal amount) { var coin = poolConfig.Template.As<CoinTemplate>(); From b98944eefe8cf94d75ac88223d40107cb11aa8e4 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Wed, 14 Jul 2021 15:32:37 +0200 Subject: [PATCH 066/145] Kerge --- src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs b/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs index e228c6441..85bc64832 100644 --- a/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs +++ b/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs @@ -151,6 +151,7 @@ private async Task LogDiscardedSharesAsync(PoolConfig poolConfig, Block block, D private async Task<DateTime?> CalculateRewardsAsync(IMiningPool pool, IPayoutHandler payoutHandler, Block block, decimal blockReward, Dictionary<string, double> shares, Dictionary<string, decimal> rewards) { + var poolConfig = pool.Config; var done = false; var before = block.Created; var inclusive = true; From c255ea5e7c238236f4f071172c4846d8d3c130e2 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Wed, 14 Jul 2021 15:44:13 +0200 Subject: [PATCH 067/145] Wording --- .../Blockchain/Bitcoin/BitcoinPayoutHandler.cs | 8 ++------ .../Blockchain/Cryptonote/CryptonotePayoutHandler.cs | 12 ++++++------ .../Blockchain/Equihash/EquihashPayoutHandler.cs | 4 ++-- .../Blockchain/Ethereum/EthereumPayoutHandler.cs | 2 +- src/Miningcore/Payments/Abstractions.cs | 2 +- 5 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs index 627d1c206..05e05d476 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Data; -using System.Globalization; using System.Linq; using System.Threading.Tasks; using Autofac; @@ -13,8 +11,6 @@ using Miningcore.Extensions; using Miningcore.Messaging; using Miningcore.Mining; -using Miningcore.Notifications; -using Miningcore.Notifications.Messages; using Miningcore.Payments; using Miningcore.Persistence; using Miningcore.Persistence.Model; @@ -207,7 +203,7 @@ public virtual async Task PayoutAsync(IMiningPool pool, Balance[] balances) if(amounts.Count == 0) return; - logger.Info(() => $"[{LogCategory}] Paying out {FormatAmount(balances.Sum(x => x.Amount))} to {balances.Length} addresses"); + logger.Info(() => $"[{LogCategory}] Paying {FormatAmount(balances.Sum(x => x.Amount))} to {balances.Length} addresses"); object[] args; @@ -284,7 +280,7 @@ public virtual async Task PayoutAsync(IMiningPool pool, Balance[] balances) if(string.IsNullOrEmpty(txId)) logger.Error(() => $"[{LogCategory}] {BitcoinCommands.SendMany} did not return a transaction id!"); else - logger.Info(() => $"[{LogCategory}] Payout transaction id: {txId}"); + logger.Info(() => $"[{LogCategory}] Payment transaction id: {txId}"); await PersistPaymentsAsync(balances, txId); diff --git a/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs b/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs index 4d6a0f997..19f43ce0c 100644 --- a/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs +++ b/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs @@ -68,7 +68,7 @@ private async Task<bool> HandleTransferResponseAsync(DaemonResponse<TransferResp var txHash = response.Response.TxHash; var txFee = (decimal) response.Response.Fee / coin.SmallestUnit; - logger.Info(() => $"[{LogCategory}] Payout transaction id: {txHash}, TxFee {FormatAmount(txFee)}, TxKey {response.Response.TxKey}"); + logger.Info(() => $"[{LogCategory}] Payment transaction id: {txHash}, TxFee {FormatAmount(txFee)}, TxKey {response.Response.TxKey}"); await PersistPaymentsAsync(balances, txHash); NotifyPayoutSuccess(poolConfig.Id, balances, new[] { txHash }, txFee); @@ -93,7 +93,7 @@ private async Task<bool> HandleTransferResponseAsync(DaemonResponse<TransferSpli var txHashes = response.Response.TxHashList; var txFees = response.Response.FeeList.Select(x => (decimal) x / coin.SmallestUnit).ToArray(); - logger.Info(() => $"[{LogCategory}] Split-Payout transaction ids: {string.Join(", ", txHashes)}, Corresponding TxFees were {string.Join(", ", txFees.Select(FormatAmount))}"); + logger.Info(() => $"[{LogCategory}] Split-Payment transaction ids: {string.Join(", ", txHashes)}, Corresponding TxFees were {string.Join(", ", txFees.Select(FormatAmount))}"); await PersistPaymentsAsync(balances, txHashes.First()); NotifyPayoutSuccess(poolConfig.Id, balances, txHashes, txFees.Sum()); @@ -156,7 +156,7 @@ private async Task<bool> EnsureBalance(decimal requiredAmount, CryptonoteCoinTem if(unlockedBalance < requiredAmount) { - logger.Info(() => $"[{LogCategory}] {FormatAmount(requiredAmount)} unlocked balance required for payout, but only have {FormatAmount(unlockedBalance)} of {FormatAmount(balance)} available yet. Will try again."); + logger.Info(() => $"[{LogCategory}] {FormatAmount(requiredAmount)} unlocked balance required for payment, but only have {FormatAmount(unlockedBalance)} of {FormatAmount(balance)} available yet. Will try again."); return false; } @@ -194,7 +194,7 @@ private async Task<bool> PayoutBatch(Balance[] balances) if(request.Destinations.Length == 0) return true; - logger.Info(() => $"[{LogCategory}] Paying out {FormatAmount(balances.Sum(x => x.Amount))} to {balances.Length} addresses:\n{string.Join("\n", balances.OrderByDescending(x => x.Amount).Select(x => $"{FormatAmount(x.Amount)} to {x.Address}"))}"); + logger.Info(() => $"[{LogCategory}] Paying {FormatAmount(balances.Sum(x => x.Amount))} to {balances.Length} addresses:\n{string.Join("\n", balances.OrderByDescending(x => x.Amount).Select(x => $"{FormatAmount(x.Amount)} to {x.Address}"))}"); // send command var transferResponse = await walletDaemon.ExecuteCmdSingleAsync<TransferResponse>(logger, CryptonoteWalletCommands.Transfer, request); @@ -269,9 +269,9 @@ private async Task<bool> PayoutToPaymentId(Balance balance) request.PaymentId = paymentId; if(!isIntegratedAddress) - logger.Info(() => $"[{LogCategory}] Paying out {FormatAmount(balance.Amount)} to address {balance.Address} with paymentId {paymentId}"); + logger.Info(() => $"[{LogCategory}] Paying {FormatAmount(balance.Amount)} to address {balance.Address} with paymentId {paymentId}"); else - logger.Info(() => $"[{LogCategory}] Paying out {FormatAmount(balance.Amount)} to integrated address {balance.Address}"); + logger.Info(() => $"[{LogCategory}] Paying {FormatAmount(balance.Amount)} to integrated address {balance.Address}"); // send command var result = await walletDaemon.ExecuteCmdSingleAsync<TransferResponse>(logger, CryptonoteWalletCommands.Transfer, request); diff --git a/src/Miningcore/Blockchain/Equihash/EquihashPayoutHandler.cs b/src/Miningcore/Blockchain/Equihash/EquihashPayoutHandler.cs index b35a9a446..58ef1851f 100644 --- a/src/Miningcore/Blockchain/Equihash/EquihashPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Equihash/EquihashPayoutHandler.cs @@ -118,7 +118,7 @@ public override async Task PayoutAsync(IMiningPool pool, Balance[] balances) return; } - logger.Info(() => $"[{LogCategory}] Paying out {FormatAmount(pageAmount)} to {page.Length} addresses"); + logger.Info(() => $"[{LogCategory}] Paying {FormatAmount(pageAmount)} to {page.Length} addresses"); var args = new object[] { @@ -141,7 +141,7 @@ public override async Task PayoutAsync(IMiningPool pool, Balance[] balances) logger.Error(() => $"[{LogCategory}] {EquihashCommands.ZSendMany} did not return a operation id!"); else { - logger.Info(() => $"[{LogCategory}] Tracking payout operation id: {operationId}"); + logger.Info(() => $"[{LogCategory}] Tracking payment operation id: {operationId}"); var continueWaiting = true; diff --git a/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs b/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs index f98c9acf3..af671b7b3 100644 --- a/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs @@ -407,7 +407,7 @@ private async Task<string> PayoutAsync(Balance balance) throw new Exception($"{EC.SendTx} did not return a valid transaction hash"); var txHash = response.Response; - logger.Info(() => $"[{LogCategory}] Payout transaction id: {txHash}"); + logger.Info(() => $"[{LogCategory}] Payment transaction id: {txHash}"); // update db await PersistPaymentsAsync(new[] { balance }, txHash); diff --git a/src/Miningcore/Payments/Abstractions.cs b/src/Miningcore/Payments/Abstractions.cs index 1db909c8e..a139913e7 100644 --- a/src/Miningcore/Payments/Abstractions.cs +++ b/src/Miningcore/Payments/Abstractions.cs @@ -14,9 +14,9 @@ public interface IPayoutHandler Task CalculateBlockEffortAsync(IMiningPool pool, Block block, double accumulatedBlockShareDiff); Task<decimal> UpdateBlockRewardBalancesAsync(IDbConnection con, IDbTransaction tx, IMiningPool pool, Block block); Task PayoutAsync(IMiningPool pool, Balance[] balances); + double AdjustShareDifficulty(double difficulty); string FormatAmount(decimal amount); - double AdjustShareDifficulty(double difficulty); } public interface IPayoutScheme From 961817d7241cfd6b5f78c4b3c7850d16b4e9e767 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Wed, 14 Jul 2021 15:52:10 +0200 Subject: [PATCH 068/145] Make it virtual --- src/Miningcore/Payments/PayoutHandlerBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Miningcore/Payments/PayoutHandlerBase.cs b/src/Miningcore/Payments/PayoutHandlerBase.cs index d2e97ab68..f22468be7 100644 --- a/src/Miningcore/Payments/PayoutHandlerBase.cs +++ b/src/Miningcore/Payments/PayoutHandlerBase.cs @@ -153,7 +153,7 @@ await cf.RunTx(async (con, tx) => } } - public double AdjustShareDifficulty(double difficulty) + public virtual double AdjustShareDifficulty(double difficulty) { return difficulty; } From d52816872c8c311bd54078b795c46de614b90da5 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Wed, 14 Jul 2021 15:57:49 +0200 Subject: [PATCH 069/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index df5721324..fda365099 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -287,11 +287,16 @@ await Guard(()=> Task.WhenAll(blockBatch), public virtual Task CalculateBlockEffortAsync(IMiningPool pool, Block block, double accumulatedBlockShareDiff) { - block.Effort = accumulatedBlockShareDiff / block.NetworkDifficulty; + block.Effort = accumulatedBlockShareDiff * ErgoConstants.ShareMultiplier / block.NetworkDifficulty; return Task.FromResult(true); } + public override double AdjustShareDifficulty(double difficulty) + { + return difficulty * ErgoConstants.ShareMultiplier; + } + public virtual async Task PayoutAsync(IMiningPool pool, Balance[] balances) { Contract.RequiresNonNull(balances, nameof(balances)); From 4240cfd8412de1eefe71a3445329bb2f1e055049 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Wed, 14 Jul 2021 16:02:09 +0200 Subject: [PATCH 070/145] WIP --- src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs b/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs index 018a0212a..85bc64832 100644 --- a/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs +++ b/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs @@ -62,7 +62,6 @@ public async Task UpdateBalancesAsync(IDbConnection con, IDbTransaction tx, IMin { var poolConfig = pool.Config; var payoutConfig = poolConfig.PaymentProcessing.PayoutSchemeConfig; - var poolConfig = pool.Config; var shares = new Dictionary<string, double>(); var rewards = new Dictionary<string, decimal>(); var shareCutOffDate = await CalculateRewardsAsync(pool, payoutHandler, block, blockReward, shares, rewards); From 486b81ea0e6018b4af701f536de20b2eca1c6da7 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Wed, 14 Jul 2021 16:02:25 +0200 Subject: [PATCH 071/145] WIP --- src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs b/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs index 85bc64832..06e1312c7 100644 --- a/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs +++ b/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs @@ -61,7 +61,6 @@ private class Config public async Task UpdateBalancesAsync(IDbConnection con, IDbTransaction tx, IMiningPool pool, IPayoutHandler payoutHandler, Block block, decimal blockReward) { var poolConfig = pool.Config; - var payoutConfig = poolConfig.PaymentProcessing.PayoutSchemeConfig; var shares = new Dictionary<string, double>(); var rewards = new Dictionary<string, decimal>(); var shareCutOffDate = await CalculateRewardsAsync(pool, payoutHandler, block, blockReward, shares, rewards); From 886e0db37a6a96162b929ef17f783f8c7a32711c Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Wed, 14 Jul 2021 17:24:52 +0200 Subject: [PATCH 072/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index fda365099..327fef25e 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -331,7 +331,7 @@ public virtual async Task PayoutAsync(IMiningPool pool, Balance[] balances) Value = (long) (x.Value * ErgoConstants.SmallestUnit), }).ToArray(); - logger.Info(() => $"[{LogCategory}] Paying out {FormatAmount(balances.Sum(x => x.Amount))} to {balances.Length} addresses"); + logger.Info(() => $"[{LogCategory}] Paying {FormatAmount(balances.Sum(x => x.Amount))} to {balances.Length} addresses"); var txId = await Guard(()=> daemon.WalletPaymentTransactionGenerateAndSendAsync(requests), ex => { @@ -353,7 +353,7 @@ public virtual async Task PayoutAsync(IMiningPool pool, Balance[] balances) throw new PaymentException("Payment transaction failed to return a transaction id"); // payment successful - logger.Info(() => $"[{LogCategory}] Payout transaction id: {txId}"); + logger.Info(() => $"[{LogCategory}] Payment transaction id: {txId}"); await PersistPaymentsAsync(balances, txId); From 68114b21d415ef20eae078cf4ac67bde49eca1d0 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Wed, 14 Jul 2021 17:25:35 +0200 Subject: [PATCH 073/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index 327fef25e..68d599741 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -311,6 +311,8 @@ public virtual async Task PayoutAsync(IMiningPool pool, Balance[] balances) try { + logger.Info(() => $"[{LogCategory}] Paying {FormatAmount(balances.Sum(x => x.Amount))} to {balances.Length} addresses"); + // get wallet status var status = await daemon.GetWalletStatusAsync(); @@ -331,8 +333,6 @@ public virtual async Task PayoutAsync(IMiningPool pool, Balance[] balances) Value = (long) (x.Value * ErgoConstants.SmallestUnit), }).ToArray(); - logger.Info(() => $"[{LogCategory}] Paying {FormatAmount(balances.Sum(x => x.Amount))} to {balances.Length} addresses"); - var txId = await Guard(()=> daemon.WalletPaymentTransactionGenerateAndSendAsync(requests), ex => { if(ex is ApiException<ApiError> apiException) From a90edd8f88344a638e7d6d687789d8650c43c1f7 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Fri, 16 Jul 2021 00:13:37 +0200 Subject: [PATCH 074/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoPool.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs index 5f436aaaa..69c18dc60 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs @@ -355,7 +355,7 @@ protected override async Task InitStatsAsync() blockchainStats = manager.BlockchainStats; } - protected override WorkerContextBase CreateClientContext() + protected override WorkerContextBase CreateWorkerContext() { return new ErgoWorkerContext(); } From 45664c4e2aa05661c7c0428288e28593aa20ced0 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Fri, 16 Jul 2021 00:20:14 +0200 Subject: [PATCH 075/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoPool.cs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs index 69c18dc60..486e70801 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs @@ -23,6 +23,7 @@ using Miningcore.Time; using Miningcore.Util; using Newtonsoft.Json; +using static Miningcore.Util.ActionUtils; namespace Miningcore.Blockchain.Ergo { @@ -227,7 +228,7 @@ protected virtual async Task OnNewJobAsync(object[] jobParams) logger.Info(() => "Broadcasting job"); - var tasks = ForEachConnection(async connection => + await Guard(()=> Task.WhenAll(ForEachConnection(async connection => { if(!connection.IsAlive) return; @@ -251,17 +252,7 @@ protected virtual async Task OnNewJobAsync(object[] jobParams) if(context.ApplyPendingDifficulty()) await SendJob(connection, context, currentJobParams); } - }); - - try - { - await Task.WhenAll(tasks); - } - - catch(Exception ex) - { - logger.Debug(() => $"{nameof(OnNewJobAsync)}: {ex.Message}"); - } + })), ex=> logger.Debug(() => $"{nameof(OnNewJobAsync)}: {ex.Message}")); } private async Task SendJob(StratumConnection connection, ErgoWorkerContext context, object[] jobParams) From 3ea4ae031c9a12d7e3912c797ef376ac082bf88c Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Fri, 16 Jul 2021 00:31:59 +0200 Subject: [PATCH 076/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoPool.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs index 486e70801..dd608f5b3 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs @@ -222,13 +222,13 @@ protected virtual async Task OnSubmitAsync(StratumConnection connection, Timesta } } - protected virtual async Task OnNewJobAsync(object[] jobParams) + protected virtual Task OnNewJobAsync(object[] jobParams) { currentJobParams = jobParams; logger.Info(() => "Broadcasting job"); - await Guard(()=> Task.WhenAll(ForEachConnection(async connection => + return Guard(()=> Task.WhenAll(ForEachConnection(async connection => { if(!connection.IsAlive) return; From f314fe7839151a80f40c5f8bfd557b618d44c70b Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Fri, 16 Jul 2021 00:43:03 +0200 Subject: [PATCH 077/145] And more compaction --- .../Blockchain/Bitcoin/BitcoinPool.cs | 25 ++++++------------- .../Blockchain/Cryptonote/CryptonotePool.cs | 21 ++++------------ .../Blockchain/Equihash/EquihashPool.cs | 25 ++++++------------- .../Blockchain/Ethereum/EthereumPool.cs | 25 ++++++------------- src/Miningcore/Mining/PoolBase.cs | 16 ++++++++++++ 5 files changed, 42 insertions(+), 70 deletions(-) diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinPool.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinPool.cs index f97b26e1a..bc6eb48ff 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinPool.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinPool.cs @@ -333,26 +333,15 @@ protected virtual Task OnNewJobAsync(object jobParams) var context = connection.ContextAs<BitcoinWorkerContext>(); - if(context.IsSubscribed && context.IsAuthorized) - { - // check alive - var lastActivityAgo = clock.Now - context.LastActivity; - - if(poolConfig.ClientConnectionTimeout > 0 && - lastActivityAgo.TotalSeconds > poolConfig.ClientConnectionTimeout) - { - logger.Info(() => $"[{connection.ConnectionId}] Booting zombie-worker (idle-timeout exceeded)"); - CloseConnection(connection); - return; - } + if(!context.IsSubscribed || !context.IsAuthorized || CloseIfDead(connection, context)) + return; - // varDiff: if the client has a pending difficulty change, apply it now - if(context.ApplyPendingDifficulty()) - await connection.NotifyAsync(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty }); + // varDiff: if the client has a pending difficulty change, apply it now + if(context.ApplyPendingDifficulty()) + await connection.NotifyAsync(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty }); - // send job - await connection.NotifyAsync(BitcoinStratumMethods.MiningNotify, currentJobParams); - } + // send job + await connection.NotifyAsync(BitcoinStratumMethods.MiningNotify, currentJobParams); })), ex=> logger.Debug(() => $"{nameof(OnNewJobAsync)}: {ex.Message}")); } diff --git a/src/Miningcore/Blockchain/Cryptonote/CryptonotePool.cs b/src/Miningcore/Blockchain/Cryptonote/CryptonotePool.cs index 9b6dd9328..f0e162c46 100644 --- a/src/Miningcore/Blockchain/Cryptonote/CryptonotePool.cs +++ b/src/Miningcore/Blockchain/Cryptonote/CryptonotePool.cs @@ -296,23 +296,12 @@ private Task OnNewJobAsync() var context = connection.ContextAs<CryptonoteWorkerContext>(); - if(context.IsSubscribed && context.IsAuthorized) - { - // check alive - var lastActivityAgo = clock.Now - context.LastActivity; - - if(poolConfig.ClientConnectionTimeout > 0 && - lastActivityAgo.TotalSeconds > poolConfig.ClientConnectionTimeout) - { - logger.Info(() => $"[[{connection.ConnectionId}] Booting zombie-worker (idle-timeout exceeded)"); - CloseConnection(connection); - return; - } + if(!context.IsSubscribed || !context.IsAuthorized || CloseIfDead(connection, context)) + return; - // send job - var job = CreateWorkerJob(connection); - await connection.NotifyAsync(CryptonoteStratumMethods.JobNotify, job); - } + // send job + var job = CreateWorkerJob(connection); + await connection.NotifyAsync(CryptonoteStratumMethods.JobNotify, job); })), ex=> logger.Debug(() => $"{nameof(OnNewJobAsync)}: {ex.Message}")); } diff --git a/src/Miningcore/Blockchain/Equihash/EquihashPool.cs b/src/Miningcore/Blockchain/Equihash/EquihashPool.cs index e2a119840..c4da79296 100644 --- a/src/Miningcore/Blockchain/Equihash/EquihashPool.cs +++ b/src/Miningcore/Blockchain/Equihash/EquihashPool.cs @@ -370,26 +370,15 @@ protected Task OnNewJobAsync(object jobParams) var context = connection.ContextAs<BitcoinWorkerContext>(); - if(context.IsSubscribed && context.IsAuthorized) - { - // check alive - var lastActivityAgo = clock.Now - context.LastActivity; - - if(poolConfig.ClientConnectionTimeout > 0 && - lastActivityAgo.TotalSeconds > poolConfig.ClientConnectionTimeout) - { - logger.Info(() => $"[{connection.ConnectionId}] Booting zombie-worker (idle-timeout exceeded)"); - CloseConnection(connection); - return; - } + if(!context.IsSubscribed || !context.IsAuthorized || CloseIfDead(connection, context)) + return; - // varDiff: if the client has a pending difficulty change, apply it now - if(context.ApplyPendingDifficulty()) - await connection.NotifyAsync(EquihashStratumMethods.SetTarget, new object[] { EncodeTarget(context.Difficulty) }); + // varDiff: if the client has a pending difficulty change, apply it now + if(context.ApplyPendingDifficulty()) + await connection.NotifyAsync(EquihashStratumMethods.SetTarget, new object[] { EncodeTarget(context.Difficulty) }); - // send job - await connection.NotifyAsync(BitcoinStratumMethods.MiningNotify, currentJobParams); - } + // send job + await connection.NotifyAsync(BitcoinStratumMethods.MiningNotify, currentJobParams); })), ex=> logger.Debug(() => $"{nameof(OnNewJobAsync)}: {ex.Message}")); } diff --git a/src/Miningcore/Blockchain/Ethereum/EthereumPool.cs b/src/Miningcore/Blockchain/Ethereum/EthereumPool.cs index 0cbfc35aa..f8467460a 100644 --- a/src/Miningcore/Blockchain/Ethereum/EthereumPool.cs +++ b/src/Miningcore/Blockchain/Ethereum/EthereumPool.cs @@ -259,26 +259,15 @@ protected virtual Task OnNewJobAsync(object jobParams) var context = connection.ContextAs<EthereumWorkerContext>(); - if(context.IsSubscribed && context.IsAuthorized && context.IsInitialWorkSent) - { - // check alive - var lastActivityAgo = clock.Now - context.LastActivity; - - if(poolConfig.ClientConnectionTimeout > 0 && - lastActivityAgo.TotalSeconds > poolConfig.ClientConnectionTimeout) - { - logger.Info(() => $"[{connection.ConnectionId}] Booting zombie-worker (idle-timeout exceeded)"); - CloseConnection(connection); - return; - } + if(!context.IsSubscribed || !context.IsAuthorized || CloseIfDead(connection, context)) + return; - // varDiff: if the client has a pending difficulty change, apply it now - if(context.ApplyPendingDifficulty()) - await connection.NotifyAsync(EthereumStratumMethods.SetDifficulty, new object[] { context.Difficulty }); + // varDiff: if the client has a pending difficulty change, apply it now + if(context.ApplyPendingDifficulty()) + await connection.NotifyAsync(EthereumStratumMethods.SetDifficulty, new object[] { context.Difficulty }); - // send job - await connection.NotifyAsync(EthereumStratumMethods.MiningNotify, currentJobParams); - } + // send job + await connection.NotifyAsync(EthereumStratumMethods.MiningNotify, currentJobParams); })), ex=> logger.Debug(() => $"{nameof(OnNewJobAsync)}: {ex.Message}")); } diff --git a/src/Miningcore/Mining/PoolBase.cs b/src/Miningcore/Mining/PoolBase.cs index 972d55c28..aee3d86c3 100644 --- a/src/Miningcore/Mining/PoolBase.cs +++ b/src/Miningcore/Mining/PoolBase.cs @@ -226,6 +226,22 @@ protected virtual Task OnVarDiffUpdateAsync(StratumConnection connection, double #endregion // VarDiff + protected bool CloseIfDead(StratumConnection connection, WorkerContextBase context) + { + var lastActivityAgo = clock.Now - context.LastActivity; + + if(poolConfig.ClientConnectionTimeout > 0 && + lastActivityAgo.TotalSeconds > poolConfig.ClientConnectionTimeout) + { + logger.Info(() => $"[[{connection.ConnectionId}] Booting zombie-worker (idle-timeout exceeded)"); + CloseConnection(connection); + + return true; + } + + return false; + } + protected void SetupBanning(ClusterConfig clusterConfig) { if(poolConfig.Banning?.Enabled == true) From 66ef4ed86f563bbd6de2f9e7c8a66f076887f893 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Fri, 16 Jul 2021 00:44:30 +0200 Subject: [PATCH 078/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoPool.cs | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs index dd608f5b3..2d9b4f41d 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs @@ -235,23 +235,12 @@ protected virtual Task OnNewJobAsync(object[] jobParams) var context = connection.ContextAs<ErgoWorkerContext>(); - if(context.IsSubscribed && context.IsAuthorized) - { - // check alive - var lastActivityAgo = clock.Now - context.LastActivity; - - if(poolConfig.ClientConnectionTimeout > 0 && - lastActivityAgo.TotalSeconds > poolConfig.ClientConnectionTimeout) - { - logger.Info(() => $"[{connection.ConnectionId}] Booting zombie-worker (idle-timeout exceeded)"); - CloseConnection(connection); - return; - } + if(!context.IsSubscribed || !context.IsAuthorized || CloseIfDead(connection, context)) + return; - // varDiff: if the client has a pending difficulty change, apply it now - if(context.ApplyPendingDifficulty()) - await SendJob(connection, context, currentJobParams); - } + // varDiff: if the client has a pending difficulty change, apply it now + if(context.ApplyPendingDifficulty()) + await SendJob(connection, context, currentJobParams); })), ex=> logger.Debug(() => $"{nameof(OnNewJobAsync)}: {ex.Message}")); } From 153eebc78445ac74b1154e73d3bcbfb68e1891e3 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Fri, 16 Jul 2021 00:45:27 +0200 Subject: [PATCH 079/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoPool.cs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs index 2d9b4f41d..f14fbd639 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs @@ -299,18 +299,9 @@ protected override async Task SetupJobManager(CancellationToken ct) if(poolConfig.EnableInternalStratum == true) { disposables.Add(manager.Jobs - .Select(job => Observable.FromAsync(async () => - { - try - { - await OnNewJobAsync(job); - } - - catch(Exception ex) - { - logger.Debug(() => $"{nameof(OnNewJobAsync)}: {ex.Message}"); - } - })) + .Select(job => Observable.FromAsync(() => + Guard(()=> OnNewJobAsync(job), + ex=> logger.Debug(() => $"{nameof(OnNewJobAsync)}: {ex.Message}")))) .Concat() .Subscribe(_ => { }, ex => { From fdb60f5b8e8b00ef2a97c06260798b4e39748ebb Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Fri, 16 Jul 2021 00:45:47 +0200 Subject: [PATCH 080/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoPool.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs index f14fbd639..cb88f5bcc 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs @@ -308,7 +308,7 @@ protected override async Task SetupJobManager(CancellationToken ct) logger.Debug(ex, nameof(OnNewJobAsync)); })); - // we need work before opening the gates + // start with initial blocktemplate await manager.Jobs.Take(1).ToTask(ct); } From 0aba8448713809f58e6316781307be50388397b6 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Fri, 16 Jul 2021 15:41:06 +0200 Subject: [PATCH 081/145] WIP --- .../Blockchain/Ergo/ErgoJobManager.cs | 8 ++-- .../Blockchain/Ergo/ErgoPayoutHandler.cs | 39 ++++++++++--------- src/Miningcore/Blockchain/Ergo/ErgoPool.cs | 2 +- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs index d9b31a092..9295c1d4a 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs @@ -447,9 +447,9 @@ protected override void ConfigureDaemons() daemon = ErgoClientFactory.CreateClient(poolConfig, clusterConfig, logger); } - protected override async Task<bool> AreDaemonsHealthyAsync() + protected override async Task<bool> AreDaemonsHealthyAsync(CancellationToken ct) { - var info = await Guard(() => daemon.GetNodeInfoAsync(), + var info = await Guard(() => daemon.GetNodeInfoAsync(ct), ex=> logger.ThrowLogPoolStartupException($"Daemon reports: {ex.Message}")); if(info?.IsMining != true) @@ -458,9 +458,9 @@ protected override async Task<bool> AreDaemonsHealthyAsync() return true; } - protected override async Task<bool> AreDaemonsConnectedAsync() + protected override async Task<bool> AreDaemonsConnectedAsync(CancellationToken ct) { - var info = await Guard(() => daemon.GetNodeInfoAsync(), + var info = await Guard(() => daemon.GetNodeInfoAsync(ct), ex=> logger.Debug(ex)); return info?.PeersCount > 0; diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index 68d599741..605453628 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; +using System.Threading; using System.Threading.Tasks; using Autofac; using AutoMapper; @@ -77,13 +78,13 @@ private void ReportAndRethrowApiError(string action, Exception ex, bool rethrow throw ex; } - private async Task UnlockWallet() + private async Task UnlockWallet(CancellationToken ct) { logger.Info(() => $"[{LogCategory}] Unlocking wallet"); var walletPassword = extraPoolPaymentProcessingConfig.WalletPassword ?? string.Empty; - await Guard(() => daemon.WalletUnlockAsync(new Body4 {Pass = walletPassword}), ex => + await Guard(() => daemon.WalletUnlockAsync(new Body4 {Pass = walletPassword}, ct), ex => { if (ex is ApiException<ApiError> apiException) { @@ -100,11 +101,11 @@ await Guard(() => daemon.WalletUnlockAsync(new Body4 {Pass = walletPassword}), e logger.Info(() => $"[{LogCategory}] Wallet unlocked"); } - private async Task LockWallet() + private async Task LockWallet(CancellationToken ct) { logger.Info(() => $"[{LogCategory}] Locking wallet"); - await Guard(() => daemon.WalletLockAsync(), + await Guard(() => daemon.WalletLockAsync(ct), ex => ReportAndRethrowApiError("Failed to lock wallet", ex)); logger.Info(() => $"[{LogCategory}] Wallet locked"); @@ -112,7 +113,7 @@ await Guard(() => daemon.WalletLockAsync(), #region IPayoutHandler - public virtual async Task ConfigureAsync(ClusterConfig clusterConfig, PoolConfig poolConfig) + public virtual async Task ConfigureAsync(ClusterConfig clusterConfig, PoolConfig poolConfig, CancellationToken ct) { Contract.RequiresNonNull(poolConfig, nameof(poolConfig)); @@ -127,11 +128,11 @@ public virtual async Task ConfigureAsync(ClusterConfig clusterConfig, PoolConfig daemon = ErgoClientFactory.CreateClient(poolConfig, clusterConfig, null); // detect chain - var info = await daemon.GetNodeInfoAsync(); + var info = await daemon.GetNodeInfoAsync(ct); network = ErgoConstants.RegexChain.Match(info.Name).Groups[1].Value.ToLower(); } - public virtual async Task<Block[]> ClassifyBlocksAsync(IMiningPool pool, Block[] blocks) + public virtual async Task<Block[]> ClassifyBlocksAsync(IMiningPool pool, Block[] blocks, CancellationToken ct) { Contract.RequiresNonNull(poolConfig, nameof(poolConfig)); Contract.RequiresNonNull(blocks, nameof(blocks)); @@ -144,8 +145,8 @@ public virtual async Task<Block[]> ClassifyBlocksAsync(IMiningPool pool, Block[] var pageCount = (int) Math.Ceiling(blocks.Length / (double) pageSize); var result = new List<Block>(); var minConfirmations = extraPoolConfig?.MinimumConfirmations ?? (network == "mainnet" ? 720 : 72); - var minerRewardsPubKey = await daemon.MiningReadMinerRewardPubkeyAsync(); - var minerRewardsAddress = await daemon.MiningReadMinerRewardAddressAsync(); + var minerRewardsPubKey = await daemon.MiningReadMinerRewardPubkeyAsync(ct); + var minerRewardsAddress = await daemon.MiningReadMinerRewardAddressAsync(ct); for(var i = 0; i < pageCount; i++) { @@ -156,7 +157,7 @@ public virtual async Task<Block[]> ClassifyBlocksAsync(IMiningPool pool, Block[] .ToArray(); // fetch header ids for blocks in page - var headerBatch = page.Select(block => daemon.GetFullBlockAtAsync((int) block.BlockHeight)).ToArray(); + var headerBatch = page.Select(block => daemon.GetFullBlockAtAsync((int) block.BlockHeight, ct)).ToArray(); await Guard(()=> Task.WhenAll(headerBatch), ex=> logger.Debug(ex)); @@ -179,7 +180,7 @@ await Guard(()=> Task.WhenAll(headerBatch), var headerIds = headerTask.Result; // fetch blocks - var blockBatch = headerIds.Select(x=> daemon.GetFullBlockByIdAsync(x)).ToArray(); + var blockBatch = headerIds.Select(x=> daemon.GetFullBlockByIdAsync(x, ct)).ToArray(); await Guard(()=> Task.WhenAll(blockBatch), ex=> logger.Debug(ex)); @@ -217,7 +218,7 @@ await Guard(()=> Task.WhenAll(blockBatch), foreach(var blockTx in fullBlock.BlockTransactions.Transactions) { - var walletTx = await Guard(()=> daemon.WalletGetTransactionAsync(blockTx.Id)); + var walletTx = await Guard(()=> daemon.WalletGetTransactionAsync(blockTx.Id, ct)); var coinbaseOutput = walletTx?.Outputs?.FirstOrDefault(x => x.Address == minerRewardsAddress.RewardAddress); if(coinbaseOutput != null) @@ -285,7 +286,7 @@ await Guard(()=> Task.WhenAll(blockBatch), return result.ToArray(); } - public virtual Task CalculateBlockEffortAsync(IMiningPool pool, Block block, double accumulatedBlockShareDiff) + public virtual Task CalculateBlockEffortAsync(IMiningPool pool, Block block, double accumulatedBlockShareDiff, CancellationToken ct) { block.Effort = accumulatedBlockShareDiff * ErgoConstants.ShareMultiplier / block.NetworkDifficulty; @@ -297,7 +298,7 @@ public override double AdjustShareDifficulty(double difficulty) return difficulty * ErgoConstants.ShareMultiplier; } - public virtual async Task PayoutAsync(IMiningPool pool, Balance[] balances) + public virtual async Task PayoutAsync(IMiningPool pool, Balance[] balances, CancellationToken ct) { Contract.RequiresNonNull(balances, nameof(balances)); @@ -314,16 +315,16 @@ public virtual async Task PayoutAsync(IMiningPool pool, Balance[] balances) logger.Info(() => $"[{LogCategory}] Paying {FormatAmount(balances.Sum(x => x.Amount))} to {balances.Length} addresses"); // get wallet status - var status = await daemon.GetWalletStatusAsync(); + var status = await daemon.GetWalletStatusAsync(ct); if(!status.IsInitialized) throw new PaymentException($"Wallet is not initialized"); if(!status.IsUnlocked) - await UnlockWallet(); + await UnlockWallet(ct); // get balance - var walletBalances = await daemon.WalletBalancesAsync(); + var walletBalances = await daemon.WalletBalancesAsync(ct); logger.Info(() => $"[{LogCategory}] Current wallet balance is {FormatAmount(walletBalances.Balance / ErgoConstants.SmallestUnit)}"); // Create request batch @@ -333,7 +334,7 @@ public virtual async Task PayoutAsync(IMiningPool pool, Balance[] balances) Value = (long) (x.Value * ErgoConstants.SmallestUnit), }).ToArray(); - var txId = await Guard(()=> daemon.WalletPaymentTransactionGenerateAndSendAsync(requests), ex => + var txId = await Guard(()=> daemon.WalletPaymentTransactionGenerateAndSendAsync(requests, ct), ex => { if(ex is ApiException<ApiError> apiException) { @@ -369,7 +370,7 @@ public virtual async Task PayoutAsync(IMiningPool pool, Balance[] balances) finally { - await LockWallet(); + await LockWallet(ct); } } diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs index cb88f5bcc..4b44903c5 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs @@ -183,7 +183,7 @@ protected virtual async Task OnSubmitAsync(StratumConnection connection, Timesta // submit var requestParams = request.ParamsAs<string[]>(); - var poolEndpoint = poolConfig.Ports[connection.PoolEndpoint.Port]; + var poolEndpoint = poolConfig.Ports[connection.LocalEndpoint.Port]; var share = await manager.SubmitShareAsync(connection, requestParams, poolEndpoint.Difficulty, ct); From 87c58fde2dc2a50179d6877a2412609d91b5ffce Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Fri, 16 Jul 2021 21:03:34 +0200 Subject: [PATCH 082/145] WIP --- .../Blockchain/Ergo/ErgoJobManager.cs | 42 +++++++------------ 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs index 9295c1d4a..fd3fcafdc 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs @@ -60,29 +60,7 @@ private void SetupJobUpdates() blockFound.Select(x => (false, JobRefreshBy.BlockFound, (string) null)) }; - if(extraPoolConfig?.BtStream == null) - { - if(poolConfig.BlockRefreshInterval > 0) - { - // periodically update block-template - var pollingInterval = poolConfig.BlockRefreshInterval > 0 ? poolConfig.BlockRefreshInterval : 1000; - - triggers.Add(Observable.Timer(TimeSpan.FromMilliseconds(pollingInterval)) - .TakeUntil(pollTimerRestart) - .Select(_ => (false, JobRefreshBy.Poll, (string) null)) - .Repeat()); - } - - else - { - // get initial blocktemplate - triggers.Add(Observable.Interval(TimeSpan.FromMilliseconds(1000)) - .Select(_ => (false, JobRefreshBy.Initial, (string) null)) - .TakeWhile(_ => !hasInitialBlockTemplate)); - } - } - - else + if(extraPoolConfig?.BtStream != null) { var btStream = BtStreamSubscribe(extraPoolConfig.BtStream); @@ -90,13 +68,21 @@ private void SetupJobUpdates() .Select(json => (false, JobRefreshBy.BlockTemplateStream, json)) .Publish() .RefCount()); - - // get initial blocktemplate - triggers.Add(Observable.Interval(TimeSpan.FromMilliseconds(1000)) - .Select(_ => (false, JobRefreshBy.Initial, (string) null)) - .TakeWhile(_ => !hasInitialBlockTemplate)); } + // periodically update block-template + var pollingInterval = poolConfig.BlockRefreshInterval > 0 ? poolConfig.BlockRefreshInterval : 1000; + + triggers.Add(Observable.Timer(TimeSpan.FromMilliseconds(pollingInterval)) + .TakeUntil(pollTimerRestart) + .Select(_ => (false, JobRefreshBy.Poll, (string) null)) + .Repeat()); + + // get initial blocktemplate + triggers.Add(Observable.Interval(TimeSpan.FromMilliseconds(1000)) + .Select(_ => (false, JobRefreshBy.Initial, (string) null)) + .TakeWhile(_ => !hasInitialBlockTemplate)); + Jobs = Observable.Merge(triggers) .Select(x => Observable.FromAsync(() => UpdateJob(x.Force, x.Via, x.Data))) .Concat() From e9d71bb8c1774598cf711f1d5485b470c2428600 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Sat, 17 Jul 2021 09:38:14 +0200 Subject: [PATCH 083/145] Refactor --- src/Miningcore/Stratum/StratumServer.cs | 34 +++++++++++++++++-------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/src/Miningcore/Stratum/StratumServer.cs b/src/Miningcore/Stratum/StratumServer.cs index 3e1e4eb7d..bc779252b 100644 --- a/src/Miningcore/Stratum/StratumServer.cs +++ b/src/Miningcore/Stratum/StratumServer.cs @@ -129,29 +129,25 @@ private async Task Listen(Socket server, StratumEndpoint port, X509Certificate2 private void AcceptConnection(Socket socket, StratumEndpoint port, X509Certificate2 cert, CancellationToken ct) { - Task.Run(() => + Task.Run(() => Guard(() => { - var client = (IPEndPoint) socket.RemoteEndPoint; + var remoteEndpoint = (IPEndPoint) socket.RemoteEndPoint; // dispose of banned clients as early as possible - if(client != null && banManager?.IsBanned(client.Address) == true) - { - logger.Debug(() => $"Disconnecting banned ip {client.Address}"); - socket.Close(); + if (DisconnectIfBanned(socket, remoteEndpoint)) return; - } var connectionId = CorrelationIdGenerator.GetNextId(); - var connection = new StratumConnection(logger, clock, connectionId); - var remoteEndpoint = (IPEndPoint) socket.RemoteEndPoint; - logger.Info(() => $"[{connectionId}] Accepting connection from {remoteEndpoint.Address}:{remoteEndpoint.Port} ..."); + // init connection + var connection = new StratumConnection(logger, clock, connectionId); + RegisterConnection(connection, connectionId); OnConnect(connection, port.IPEndPoint); connection.DispatchAsync(socket, ct, port, remoteEndpoint, cert, OnRequestAsync, OnConnectionComplete, OnConnectionError); - }, ct); + }, ex=> logger.Error(ex)), ct); } protected virtual void RegisterConnection(StratumConnection connection, string connectionId) @@ -304,6 +300,22 @@ private X509Certificate2 GetTlsCert(StratumEndpoint port) return cert; } + private bool DisconnectIfBanned(Socket socket, IPEndPoint remoteEndpoint) + { + if(remoteEndpoint == null || banManager == null) + return false; + + if (banManager.IsBanned(remoteEndpoint.Address)) + { + logger.Debug(() => $"Disconnecting banned ip {remoteEndpoint.Address}"); + socket.Close(); + + return true; + } + + return false; + } + protected void ForEachConnection(Action<StratumConnection> action) { StratumConnection[] tmp; From e2ff9e05012cc68da289e9791b18de6cb038f2c4 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Sat, 17 Jul 2021 09:40:13 +0200 Subject: [PATCH 084/145] WIP --- src/Miningcore/Stratum/StratumServer.cs | 34 +++++++++++++++++-------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/src/Miningcore/Stratum/StratumServer.cs b/src/Miningcore/Stratum/StratumServer.cs index 3e1e4eb7d..bc779252b 100644 --- a/src/Miningcore/Stratum/StratumServer.cs +++ b/src/Miningcore/Stratum/StratumServer.cs @@ -129,29 +129,25 @@ private async Task Listen(Socket server, StratumEndpoint port, X509Certificate2 private void AcceptConnection(Socket socket, StratumEndpoint port, X509Certificate2 cert, CancellationToken ct) { - Task.Run(() => + Task.Run(() => Guard(() => { - var client = (IPEndPoint) socket.RemoteEndPoint; + var remoteEndpoint = (IPEndPoint) socket.RemoteEndPoint; // dispose of banned clients as early as possible - if(client != null && banManager?.IsBanned(client.Address) == true) - { - logger.Debug(() => $"Disconnecting banned ip {client.Address}"); - socket.Close(); + if (DisconnectIfBanned(socket, remoteEndpoint)) return; - } var connectionId = CorrelationIdGenerator.GetNextId(); - var connection = new StratumConnection(logger, clock, connectionId); - var remoteEndpoint = (IPEndPoint) socket.RemoteEndPoint; - logger.Info(() => $"[{connectionId}] Accepting connection from {remoteEndpoint.Address}:{remoteEndpoint.Port} ..."); + // init connection + var connection = new StratumConnection(logger, clock, connectionId); + RegisterConnection(connection, connectionId); OnConnect(connection, port.IPEndPoint); connection.DispatchAsync(socket, ct, port, remoteEndpoint, cert, OnRequestAsync, OnConnectionComplete, OnConnectionError); - }, ct); + }, ex=> logger.Error(ex)), ct); } protected virtual void RegisterConnection(StratumConnection connection, string connectionId) @@ -304,6 +300,22 @@ private X509Certificate2 GetTlsCert(StratumEndpoint port) return cert; } + private bool DisconnectIfBanned(Socket socket, IPEndPoint remoteEndpoint) + { + if(remoteEndpoint == null || banManager == null) + return false; + + if (banManager.IsBanned(remoteEndpoint.Address)) + { + logger.Debug(() => $"Disconnecting banned ip {remoteEndpoint.Address}"); + socket.Close(); + + return true; + } + + return false; + } + protected void ForEachConnection(Action<StratumConnection> action) { StratumConnection[] tmp; From 93f6f0b4501ee4cc9e1fd614fb4cee4f262547b6 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Sat, 17 Jul 2021 09:58:06 +0200 Subject: [PATCH 085/145] Mini opt --- src/Miningcore/Stratum/StratumConnection.cs | 27 ++++++++++----------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/Miningcore/Stratum/StratumConnection.cs b/src/Miningcore/Stratum/StratumConnection.cs index 062aa5e83..b43818776 100644 --- a/src/Miningcore/Stratum/StratumConnection.cs +++ b/src/Miningcore/Stratum/StratumConnection.cs @@ -315,24 +315,23 @@ private async Task SendMessage(object msg) try { - await using(var stream = new MemoryStream(buffer, true)) + var stream = new MemoryStream(buffer, true); + + // serialize + using(var writer = new StreamWriter(stream, StratumConstants.Encoding, MaxOutboundRequestLength, true)) { - // serialize - await using(var writer = new StreamWriter(stream, StratumConstants.Encoding, MaxOutboundRequestLength, true)) - { - serializer.Serialize(writer, msg); - } + serializer.Serialize(writer, msg); + } - stream.WriteByte((byte) '\n'); // terminator + stream.WriteByte((byte) '\n'); // terminator - // send - using(var ctsTimeout = new CancellationTokenSource()) - { - ctsTimeout.CancelAfter(sendTimeout); + // send + using(var ctsTimeout = new CancellationTokenSource()) + { + ctsTimeout.CancelAfter(sendTimeout); - await networkStream.WriteAsync(buffer, 0, (int) stream.Position, ctsTimeout.Token); - await networkStream.FlushAsync(ctsTimeout.Token); - } + await networkStream.WriteAsync(buffer, 0, (int) stream.Position, ctsTimeout.Token); + await networkStream.FlushAsync(ctsTimeout.Token); } } From 82b3299d4ae98d72ee156e490681ec93ccf70b5d Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Sat, 17 Jul 2021 10:07:42 +0200 Subject: [PATCH 086/145] Fix cancellation bug --- src/Miningcore/Stratum/StratumConnection.cs | 35 ++++++++++++--------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/Miningcore/Stratum/StratumConnection.cs b/src/Miningcore/Stratum/StratumConnection.cs index b43818776..1e3378b09 100644 --- a/src/Miningcore/Stratum/StratumConnection.cs +++ b/src/Miningcore/Stratum/StratumConnection.cs @@ -303,11 +303,11 @@ private async Task ProcessSendQueueAsync(CancellationToken ct) { var msg = await sendQueue.ReceiveAsync(ct); - await SendMessage(msg); + await SendMessage(msg, ct); } } - private async Task SendMessage(object msg) + private async Task SendMessage(object msg, CancellationToken ct) { logger.Debug(() => $"[{ConnectionId}] Sending: {JsonConvert.SerializeObject(msg)}"); @@ -315,22 +315,13 @@ private async Task SendMessage(object msg) try { - var stream = new MemoryStream(buffer, true); - - // serialize - using(var writer = new StreamWriter(stream, StratumConstants.Encoding, MaxOutboundRequestLength, true)) - { - serializer.Serialize(writer, msg); - } - - stream.WriteByte((byte) '\n'); // terminator - - // send - using(var ctsTimeout = new CancellationTokenSource()) + using(var ctsTimeout = CancellationTokenSource.CreateLinkedTokenSource(ct)) { ctsTimeout.CancelAfter(sendTimeout); - await networkStream.WriteAsync(buffer, 0, (int) stream.Position, ctsTimeout.Token); + var cb = SerializeMessage(msg, buffer); + + await networkStream.WriteAsync(buffer, 0, cb, ctsTimeout.Token); await networkStream.FlushAsync(ctsTimeout.Token); } } @@ -341,6 +332,20 @@ private async Task SendMessage(object msg) } } + private static int SerializeMessage(object msg, byte[] buffer) + { + var stream = new MemoryStream(buffer, true); + + using (var writer = new StreamWriter(stream, StratumConstants.Encoding, MaxOutboundRequestLength, true)) + { + serializer.Serialize(writer, msg); + } + + stream.WriteByte((byte) '\n'); // terminator + + return (int) stream.Position; + } + private async Task ProcessRequestAsync( CancellationToken ct, Func<StratumConnection, JsonRpcRequest, CancellationToken, Task> onRequestAsync, From 9eb4e839b626333aa50f97fea0637104989c67be Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Sun, 18 Jul 2021 14:42:34 +0200 Subject: [PATCH 087/145] Long overdue JsonRpc overhaul. Multiple daemons are no longer used even if configured. --- .../Blockchain/Bitcoin/BitcoinJobManager.cs | 12 +- .../Bitcoin/BitcoinJobManagerBase.cs | 97 +++---- .../Bitcoin/BitcoinPayoutHandler.cs | 17 +- .../Cryptonote/CryptonoteJobManager.cs | 77 ++---- .../Cryptonote/CryptonotePayoutHandler.cs | 42 ++- .../ZCashAsyncOperationStatus.cs | 2 +- .../Blockchain/Equihash/EquihashJobManager.cs | 23 +- .../Equihash/EquihashPayoutHandler.cs | 68 ++--- .../Blockchain/Ethereum/EthereumJobManager.cs | 119 +++----- .../Ethereum/EthereumPayoutHandler.cs | 50 ++-- .../DaemonInterface/DaemonClientException.cs | 19 -- src/Miningcore/DaemonInterface/DaemonCmd.cs | 23 -- .../DaemonInterface/DaemonResponse.cs | 12 - .../{JsonRpcException.cs => JsonRpcError.cs} | 12 +- src/Miningcore/JsonRpc/JsonRpcResponse.cs | 6 +- .../DaemonClient.cs => JsonRpc/RpcClient.cs} | 260 ++++-------------- src/Miningcore/JsonRpc/RpcRequest.cs | 19 ++ src/Miningcore/JsonRpc/RpcResponse.cs | 6 + .../PaymentSchemes/PPLNSPaymentScheme.cs | 4 +- src/Miningcore/Stratum/StratumConnection.cs | 2 +- 20 files changed, 303 insertions(+), 567 deletions(-) delete mode 100644 src/Miningcore/DaemonInterface/DaemonClientException.cs delete mode 100644 src/Miningcore/DaemonInterface/DaemonCmd.cs delete mode 100644 src/Miningcore/DaemonInterface/DaemonResponse.cs rename src/Miningcore/JsonRpc/{JsonRpcException.cs => JsonRpcError.cs} (87%) rename src/Miningcore/{DaemonInterface/DaemonClient.cs => JsonRpc/RpcClient.cs} (60%) create mode 100644 src/Miningcore/JsonRpc/RpcRequest.cs create mode 100644 src/Miningcore/JsonRpc/RpcResponse.cs diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManager.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManager.cs index 315a781b1..9b48813a2 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManager.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManager.cs @@ -8,7 +8,6 @@ using Miningcore.Configuration; using Miningcore.Contracts; using Miningcore.Crypto; -using Miningcore.DaemonInterface; using Miningcore.Extensions; using Miningcore.JsonRpc; using Miningcore.Messaging; @@ -48,26 +47,23 @@ protected override object[] GetBlockTemplateParams() return result; } - protected async Task<DaemonResponse<BlockTemplate>> GetBlockTemplateAsync(CancellationToken ct) + protected async Task<RpcResponse<BlockTemplate>> GetBlockTemplateAsync(CancellationToken ct) { logger.LogInvoke(); - var result = await daemon.ExecuteCmdAnyAsync<BlockTemplate>(logger, + var result = await rpcClient.ExecuteAsync<BlockTemplate>(logger, BitcoinCommands.GetBlockTemplate, ct, extraPoolConfig?.GBTArgs ?? (object) GetBlockTemplateParams()); return result; } - protected DaemonResponse<BlockTemplate> GetBlockTemplateFromJson(string json) + protected RpcResponse<BlockTemplate> GetBlockTemplateFromJson(string json) { logger.LogInvoke(); var result = JsonConvert.DeserializeObject<JsonRpcResponse>(json); - return new DaemonResponse<BlockTemplate> - { - Response = result.ResultAs<BlockTemplate>(), - }; + return new RpcResponse<BlockTemplate>(result.ResultAs<BlockTemplate>()); } private BitcoinJob CreateJob() diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs index 897e55a85..c5df3e35c 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs @@ -12,8 +12,8 @@ using Miningcore.Blockchain.Bitcoin.DaemonResponses; using Miningcore.Configuration; using Miningcore.Contracts; -using Miningcore.DaemonInterface; using Miningcore.Extensions; +using Miningcore.JsonRpc; using Miningcore.Messaging; using Miningcore.Notifications.Messages; using Miningcore.Stratum; @@ -45,7 +45,7 @@ protected BitcoinJobManagerBase( } protected readonly IMasterClock clock; - protected DaemonClient daemon; + protected RpcClient rpcClient; protected readonly IExtraNonceProvider extraNonceProvider; protected const int ExtranonceBytes = 4; protected int maxActiveJobs = 4; @@ -99,7 +99,7 @@ protected virtual void SetupJobUpdates(CancellationToken ct) { logger.Info(() => $"Subscribing to ZMQ push-updates from {string.Join(", ", zmq.Values)}"); - var blockNotify = daemon.ZmqSubscribe(logger, ct, zmq) + var blockNotify = rpcClient.ZmqSubscribe(logger, ct, zmq) .Select(msg => { using(msg) @@ -207,20 +207,19 @@ protected virtual async Task ShowDaemonSyncProgressAsync(CancellationToken ct) return; } - var infos = await daemon.ExecuteCmdAllAsync<BlockchainInfo>(logger, BitcoinCommands.GetBlockchainInfo, ct); + var info = await rpcClient.ExecuteAsync<BlockchainInfo>(logger, BitcoinCommands.GetBlockchainInfo, ct); - if(infos.Length > 0) + if(info != null) { - var blockCount = infos - .Max(x => x.Response?.Blocks); + var blockCount = info.Response?.Blocks; if(blockCount.HasValue) { // get list of peers and their highest block height to compare to ours - var peerInfo = await daemon.ExecuteCmdAnyAsync<PeerInfo[]>(logger, BitcoinCommands.GetPeerInfo, ct); + var peerInfo = await rpcClient.ExecuteAsync<PeerInfo[]>(logger, BitcoinCommands.GetPeerInfo, ct); var peers = peerInfo.Response; - if(peers != null && peers.Length > 0) + if(peers is {Length: > 0}) { var totalBlocks = peers.Max(x => x.StartingHeight); var percent = totalBlocks > 0 ? (double) blockCount / totalBlocks * 100 : 0; @@ -236,10 +235,10 @@ private async Task UpdateNetworkStatsAsync(CancellationToken ct) try { - var results = await daemon.ExecuteBatchAnyAsync(logger, ct, - new DaemonCmd(BitcoinCommands.GetMiningInfo), - new DaemonCmd(BitcoinCommands.GetNetworkInfo), - new DaemonCmd(BitcoinCommands.GetNetworkHashPS) + var results = await rpcClient.ExecuteBatchAsync(logger, ct, + new RpcRequest(BitcoinCommands.GetMiningInfo), + new RpcRequest(BitcoinCommands.GetNetworkInfo), + new RpcRequest(BitcoinCommands.GetNetworkHashPS) ); if(results.Any(x => x.Error != null)) @@ -270,11 +269,11 @@ private async Task UpdateNetworkStatsAsync(CancellationToken ct) protected virtual async Task<(bool Accepted, string CoinbaseTx)> SubmitBlockAsync(Share share, string blockHex) { // execute command batch - var results = await daemon.ExecuteBatchAnyAsync(logger, CancellationToken.None, + var results = await rpcClient.ExecuteBatchAsync(logger, CancellationToken.None, hasSubmitBlockMethod - ? new DaemonCmd(BitcoinCommands.SubmitBlock, new[] { blockHex }) - : new DaemonCmd(BitcoinCommands.GetBlockTemplate, new { mode = "submit", data = blockHex }), - new DaemonCmd(BitcoinCommands.GetBlock, new[] { share.BlockHash })); + ? new RpcRequest(BitcoinCommands.SubmitBlock, new[] { blockHex }) + : new RpcRequest(BitcoinCommands.GetBlockTemplate, new { mode = "submit", data = blockHex }), + new RpcRequest(BitcoinCommands.GetBlock, new[] { share.BlockHash })); // did submission succeed? var submitResult = results[0]; @@ -305,36 +304,30 @@ private async Task UpdateNetworkStatsAsync(CancellationToken ct) protected virtual async Task<bool> AreDaemonsHealthyLegacyAsync(CancellationToken ct) { - var responses = await daemon.ExecuteCmdAllAsync<DaemonInfo>(logger, BitcoinCommands.GetInfo, ct); - - if(responses.Where(x => x.Error?.InnerException?.GetType() == typeof(DaemonClientException)) - .Select(x => (DaemonClientException) x.Error.InnerException) - .Any(x => x.Code == HttpStatusCode.Unauthorized)) - logger.ThrowLogPoolStartupException("Daemon reports invalid credentials"); + var response = await rpcClient.ExecuteAsync<DaemonInfo>(logger, BitcoinCommands.GetInfo, ct); - return responses.All(x => x.Error == null); + return response.Error == null; } protected virtual async Task<bool> AreDaemonsConnectedLegacyAsync(CancellationToken ct) { - var response = await daemon.ExecuteCmdAnyAsync<DaemonInfo>(logger, BitcoinCommands.GetInfo, ct); + var response = await rpcClient.ExecuteAsync<DaemonInfo>(logger, BitcoinCommands.GetInfo, ct); return response.Error == null && response.Response.Connections > 0; } protected virtual async Task ShowDaemonSyncProgressLegacyAsync(CancellationToken ct) { - var infos = await daemon.ExecuteCmdAllAsync<DaemonInfo>(logger, BitcoinCommands.GetInfo, ct); + var info = await rpcClient.ExecuteAsync<DaemonInfo>(logger, BitcoinCommands.GetInfo, ct); - if(infos.Length > 0) + if(info != null) { - var blockCount = infos - .Max(x => x.Response?.Blocks); + var blockCount = info.Response?.Blocks; if(blockCount.HasValue) { // get list of peers and their highest block height to compare to ours - var peerInfo = await daemon.ExecuteCmdAnyAsync<PeerInfo[]>(logger, BitcoinCommands.GetPeerInfo, ct); + var peerInfo = await rpcClient.ExecuteAsync<PeerInfo[]>(logger, BitcoinCommands.GetPeerInfo, ct); var peers = peerInfo.Response; if(peers != null && peers.Length > 0) @@ -353,8 +346,8 @@ private async Task UpdateNetworkStatsLegacyAsync(CancellationToken ct) try { - var results = await daemon.ExecuteBatchAnyAsync(logger, ct, - new DaemonCmd(BitcoinCommands.GetConnectionCount) + var results = await rpcClient.ExecuteBatchAsync(logger, ct, + new RpcRequest(BitcoinCommands.GetConnectionCount) ); if(results.Any(x => x.Error != null)) @@ -385,8 +378,7 @@ protected override void ConfigureDaemons() { var jsonSerializerSettings = ctx.Resolve<JsonSerializerSettings>(); - daemon = new DaemonClient(jsonSerializerSettings, messageBus, clusterConfig.ClusterName ?? poolConfig.PoolName, poolConfig.Id); - daemon.Configure(poolConfig.Daemons); + rpcClient = new RpcClient(poolConfig.Daemons.First(), jsonSerializerSettings, messageBus, poolConfig.Id); } protected override async Task<bool> AreDaemonsHealthyAsync(CancellationToken ct) @@ -394,14 +386,9 @@ protected override async Task<bool> AreDaemonsHealthyAsync(CancellationToken ct) if(hasLegacyDaemon) return await AreDaemonsHealthyLegacyAsync(ct); - var responses = await daemon.ExecuteCmdAllAsync<BlockchainInfo>(logger, BitcoinCommands.GetBlockchainInfo, ct); - - if(responses.Where(x => x.Error?.InnerException?.GetType() == typeof(DaemonClientException)) - .Select(x => (DaemonClientException) x.Error.InnerException) - .Any(x => x.Code == HttpStatusCode.Unauthorized)) - logger.ThrowLogPoolStartupException("Daemon reports invalid credentials"); + var response = await rpcClient.ExecuteAsync<BlockchainInfo>(logger, BitcoinCommands.GetBlockchainInfo, ct); - return responses.All(x => x.Error == null); + return response.Error == null; } protected override async Task<bool> AreDaemonsConnectedAsync(CancellationToken ct) @@ -409,7 +396,7 @@ protected override async Task<bool> AreDaemonsConnectedAsync(CancellationToken c if(hasLegacyDaemon) return await AreDaemonsConnectedLegacyAsync(ct); - var response = await daemon.ExecuteCmdAnyAsync<NetworkInfo>(logger, BitcoinCommands.GetNetworkInfo, ct); + var response = await rpcClient.ExecuteAsync<NetworkInfo>(logger, BitcoinCommands.GetNetworkInfo, ct); return response.Error == null && response.Response?.Connections > 0; } @@ -420,10 +407,10 @@ protected override async Task EnsureDaemonsSynchedAsync(CancellationToken ct) while(true) { - var responses = await daemon.ExecuteCmdAllAsync<BlockTemplate>(logger, + var response = await rpcClient.ExecuteAsync<BlockTemplate>(logger, BitcoinCommands.GetBlockTemplate, ct, GetBlockTemplateParams()); - var isSynched = responses.All(x => x.Error == null); + var isSynched = response.Error == null; if(isSynched) { @@ -446,16 +433,16 @@ protected override async Task EnsureDaemonsSynchedAsync(CancellationToken ct) protected override async Task PostStartInitAsync(CancellationToken ct) { - var commands = new[] + var requests = new[] { - new DaemonCmd(BitcoinCommands.ValidateAddress, new[] { poolConfig.Address }), - new DaemonCmd(BitcoinCommands.SubmitBlock), - new DaemonCmd(!hasLegacyDaemon ? BitcoinCommands.GetBlockchainInfo : BitcoinCommands.GetInfo), - new DaemonCmd(BitcoinCommands.GetDifficulty), - new DaemonCmd(BitcoinCommands.GetAddressInfo, new[] { poolConfig.Address }), + new RpcRequest(BitcoinCommands.ValidateAddress, new[] { poolConfig.Address }), + new RpcRequest(BitcoinCommands.SubmitBlock), + new RpcRequest(!hasLegacyDaemon ? BitcoinCommands.GetBlockchainInfo : BitcoinCommands.GetInfo), + new RpcRequest(BitcoinCommands.GetDifficulty), + new RpcRequest(BitcoinCommands.GetAddressInfo, new[] { poolConfig.Address }), }; - var results = await daemon.ExecuteBatchAnyAsync(logger, ct, commands); + var results = await rpcClient.ExecuteBatchAsync(logger, ct, requests); if(results.Any(x => x.Error != null)) { @@ -463,8 +450,9 @@ protected override async Task PostStartInitAsync(CancellationToken ct) // filter out optional RPCs var errors = results - .Where(x => x.Error != null && commands[resultList.IndexOf(x)].Method != BitcoinCommands.SubmitBlock) - .Where(x => x.Error != null && commands[resultList.IndexOf(x)].Method != BitcoinCommands.GetAddressInfo) + .Where((x, i) => x.Error != null && + requests[i].Method != BitcoinCommands.SubmitBlock && + requests[i].Method != BitcoinCommands.GetAddressInfo) .ToArray(); if(errors.Any()) @@ -612,8 +600,7 @@ public virtual async Task<bool> ValidateAddressAsync(string address, Cancellatio if(string.IsNullOrEmpty(address)) return false; - var result = await daemon.ExecuteCmdAnyAsync<ValidateAddressResponse>(logger, ct, - BitcoinCommands.ValidateAddress, new[] { address }); + var result = await rpcClient.ExecuteAsync<ValidateAddressResponse>(logger, BitcoinCommands.ValidateAddress, ct, new[] { address }); return result.Response is {IsValid: true}; } diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs index e41bbaf42..01773d7b1 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs @@ -8,8 +8,8 @@ using Miningcore.Blockchain.Bitcoin.Configuration; using Miningcore.Blockchain.Bitcoin.DaemonResponses; using Miningcore.Configuration; -using Miningcore.DaemonInterface; using Miningcore.Extensions; +using Miningcore.JsonRpc; using Miningcore.Messaging; using Miningcore.Mining; using Miningcore.Payments; @@ -49,7 +49,7 @@ public BitcoinPayoutHandler( } protected readonly IComponentContext ctx; - protected DaemonClient daemon; + protected RpcClient rpcClient; protected BitcoinDaemonEndpointConfigExtra extraPoolConfig; protected BitcoinPoolPaymentProcessingConfigExtra extraPoolPaymentProcessingConfig; @@ -70,8 +70,7 @@ public virtual Task ConfigureAsync(ClusterConfig clusterConfig, PoolConfig poolC logger = LogUtil.GetPoolScopedLogger(typeof(BitcoinPayoutHandler), poolConfig); var jsonSerializerSettings = ctx.Resolve<JsonSerializerSettings>(); - daemon = new DaemonClient(jsonSerializerSettings, messageBus, clusterConfig.ClusterName ?? poolConfig.PoolName, poolConfig.Id); - daemon.Configure(poolConfig.Daemons); + rpcClient = new RpcClient(poolConfig.Daemons.First(), jsonSerializerSettings, messageBus, poolConfig.Id); return Task.FromResult(true); } @@ -101,11 +100,11 @@ public virtual async Task<Block[]> ClassifyBlocksAsync(IMiningPool pool, Block[] .ToArray(); // build command batch (block.TransactionConfirmationData is the hash of the blocks coinbase transaction) - var batch = page.Select(block => new DaemonCmd(BitcoinCommands.GetTransaction, + var batch = page.Select(block => new RpcRequest(BitcoinCommands.GetTransaction, new[] { block.TransactionConfirmationData })).ToArray(); // execute batch - var results = await daemon.ExecuteBatchAnyAsync(logger, ct, batch); + var results = await rpcClient.ExecuteBatchAsync(logger, ct, batch); for(var j = 0; j < results.Length; j++) { @@ -264,7 +263,7 @@ public virtual async Task PayoutAsync(IMiningPool pool, Balance[] balances, Canc // send command tryTransfer: - var result = await daemon.ExecuteCmdSingleAsync<string>(logger, BitcoinCommands.SendMany, ct, args, new JsonSerializerSettings()); + var result = await rpcClient.ExecuteAsync<string>(logger, BitcoinCommands.SendMany, ct, args); if(result.Error == null) { @@ -272,7 +271,7 @@ public virtual async Task PayoutAsync(IMiningPool pool, Balance[] balances, Canc { // lock wallet logger.Info(() => $"[{LogCategory}] Locking wallet"); - await daemon.ExecuteCmdSingleAsync<JToken>(logger, BitcoinCommands.WalletLock, ct); + await rpcClient.ExecuteAsync<JToken>(logger, BitcoinCommands.WalletLock, ct); } // check result @@ -296,7 +295,7 @@ public virtual async Task PayoutAsync(IMiningPool pool, Balance[] balances, Canc { logger.Info(() => $"[{LogCategory}] Unlocking wallet"); - var unlockResult = await daemon.ExecuteCmdSingleAsync<JToken>(logger, BitcoinCommands.WalletPassphrase, ct, new[] + var unlockResult = await rpcClient.ExecuteAsync<JToken>(logger, BitcoinCommands.WalletPassphrase, ct, new[] { (object) extraPoolPaymentProcessingConfig.WalletPassword, (object) 5 // unlock for N seconds diff --git a/src/Miningcore/Blockchain/Cryptonote/CryptonoteJobManager.cs b/src/Miningcore/Blockchain/Cryptonote/CryptonoteJobManager.cs index 6976671a8..53e986455 100644 --- a/src/Miningcore/Blockchain/Cryptonote/CryptonoteJobManager.cs +++ b/src/Miningcore/Blockchain/Cryptonote/CryptonoteJobManager.cs @@ -16,7 +16,6 @@ using Miningcore.Blockchain.Cryptonote.DaemonResponses; using Miningcore.Blockchain.Cryptonote.StratumRequests; using Miningcore.Configuration; -using Miningcore.DaemonInterface; using Miningcore.Extensions; using Miningcore.JsonRpc; using Miningcore.Messaging; @@ -51,8 +50,8 @@ public CryptonoteJobManager( private byte[] instanceId; private DaemonEndpointConfig[] daemonEndpoints; - private DaemonClient daemon; - private DaemonClient walletDaemon; + private RpcClient rpcClient; + private RpcClient rpcClientWallet; private readonly IMasterClock clock; private CryptonoteNetworkType networkType; private CryptonotePoolConfigExtra extraPoolConfig; @@ -148,7 +147,7 @@ protected async Task<bool> UpdateJob(CancellationToken ct, string via = null, st return false; } - private async Task<DaemonResponse<GetBlockTemplateResponse>> GetBlockTemplateAsync(CancellationToken ct) + private async Task<RpcResponse<GetBlockTemplateResponse>> GetBlockTemplateAsync(CancellationToken ct) { logger.LogInvoke(); @@ -158,35 +157,31 @@ private async Task<DaemonResponse<GetBlockTemplateResponse>> GetBlockTemplateAsy ReserveSize = CryptonoteConstants.ReserveSize }; - return await daemon.ExecuteCmdAnyAsync<GetBlockTemplateResponse>(logger, CryptonoteCommands.GetBlockTemplate, ct, request); + return await rpcClient.ExecuteAsync<GetBlockTemplateResponse>(logger, CryptonoteCommands.GetBlockTemplate, ct, request); } - private DaemonResponse<GetBlockTemplateResponse> GetBlockTemplateFromJson(string json) + private RpcResponse<GetBlockTemplateResponse> GetBlockTemplateFromJson(string json) { logger.LogInvoke(); var result = JsonConvert.DeserializeObject<JsonRpcResponse>(json); - return new DaemonResponse<GetBlockTemplateResponse> - { - Response = result.ResultAs<GetBlockTemplateResponse>(), - }; + return new RpcResponse<GetBlockTemplateResponse>(result.ResultAs<GetBlockTemplateResponse>()); } private async Task ShowDaemonSyncProgressAsync(CancellationToken ct) { - var infos = await daemon.ExecuteCmdAllAsync<GetInfoResponse>(logger, CryptonoteCommands.GetInfo, ct); - var firstValidResponse = infos.FirstOrDefault(x => x.Error == null && x.Response != null)?.Response; + var response = await rpcClient.ExecuteAsync<GetInfoResponse>(logger, CryptonoteCommands.GetInfo, ct); + var info = response.Response; - if(firstValidResponse != null) + if(info != null) { - var lowestHeight = infos.Where(x => x.Error == null && x.Response != null) - .Min(x => x.Response.Height); + var lowestHeight = info.Height; - var totalBlocks = firstValidResponse.TargetHeight; + var totalBlocks = info.TargetHeight; var percent = (double) lowestHeight / totalBlocks * 100; - logger.Info(() => $"Daemons have downloaded {percent:0.00}% of blockchain from {firstValidResponse.OutgoingConnectionsCount} peers"); + logger.Info(() => $"Daemons have downloaded {percent:0.00}% of blockchain from {info.OutgoingConnectionsCount} peers"); } } @@ -196,14 +191,14 @@ private async Task UpdateNetworkStatsAsync(CancellationToken ct) try { - var infoResponse = await daemon.ExecuteCmdAnyAsync(logger, CryptonoteCommands.GetInfo, ct); + var response = await rpcClient.ExecuteAsync(logger, CryptonoteCommands.GetInfo, ct); - if(infoResponse.Error != null) - logger.Warn(() => $"Error(s) refreshing network stats: {infoResponse.Error.Message} (Code {infoResponse.Error.Code})"); + if(response.Error != null) + logger.Warn(() => $"Error(s) refreshing network stats: {response.Error.Message} (Code {response.Error.Code})"); - if(infoResponse.Response != null) + if(response.Response != null) { - var info = infoResponse.Response.ToObject<GetInfoResponse>(); + var info = response.Response.ToObject<GetInfoResponse>(); BlockchainStats.NetworkHashrate = info.Target > 0 ? (double) info.Difficulty / info.Target : 0; BlockchainStats.ConnectedPeers = info.OutgoingConnectionsCount + info.IncomingConnectionsCount; @@ -218,7 +213,7 @@ private async Task UpdateNetworkStatsAsync(CancellationToken ct) private async Task<bool> SubmitBlockAsync(Share share, string blobHex, string blobHash) { - var response = await daemon.ExecuteCmdAnyAsync<SubmitResponse>(logger, CryptonoteCommands.SubmitBlock, CancellationToken.None, new[] { blobHex }); + var response = await rpcClient.ExecuteAsync<SubmitResponse>(logger, CryptonoteCommands.SubmitBlock, CancellationToken.None, new[] { blobHex }); if(response.Error != null || response?.Response?.Status != "OK") { @@ -439,41 +434,29 @@ protected override void ConfigureDaemons() { var jsonSerializerSettings = ctx.Resolve<JsonSerializerSettings>(); - daemon = new DaemonClient(jsonSerializerSettings, messageBus, clusterConfig.ClusterName ?? poolConfig.PoolName, poolConfig.Id); - daemon.Configure(daemonEndpoints); + rpcClient = new RpcClient(daemonEndpoints.First(), jsonSerializerSettings, messageBus, poolConfig.Id); if(clusterConfig.PaymentProcessing?.Enabled == true && poolConfig.PaymentProcessing?.Enabled == true) { // also setup wallet daemon - walletDaemon = new DaemonClient(jsonSerializerSettings, messageBus, clusterConfig.ClusterName ?? poolConfig.PoolName, poolConfig.Id); - walletDaemon.Configure(walletDaemonEndpoints); + rpcClientWallet = new RpcClient(walletDaemonEndpoints.First(), jsonSerializerSettings, messageBus, poolConfig.Id); } } protected override async Task<bool> AreDaemonsHealthyAsync(CancellationToken ct) { // test daemons - var responses = await daemon.ExecuteCmdAllAsync<GetInfoResponse>(logger, CryptonoteCommands.GetInfo, ct); + var response = await rpcClient.ExecuteAsync<GetInfoResponse>(logger, CryptonoteCommands.GetInfo, ct); - if(responses.Where(x => x.Error?.InnerException?.GetType() == typeof(DaemonClientException)) - .Select(x => (DaemonClientException) x.Error.InnerException) - .Any(x => x.Code == HttpStatusCode.Unauthorized)) - logger.ThrowLogPoolStartupException("Daemon reports invalid credentials"); - - if(responses.Any(x => x.Error != null)) + if(response.Error != null) return false; if(clusterConfig.PaymentProcessing?.Enabled == true && poolConfig.PaymentProcessing?.Enabled == true) { // test wallet daemons - var responses2 = await walletDaemon.ExecuteCmdAllAsync<object>(logger, CryptonoteWalletCommands.GetAddress, ct); - - if(responses2.Where(x => x.Error?.InnerException?.GetType() == typeof(DaemonClientException)) - .Select(x => (DaemonClientException) x.Error.InnerException) - .Any(x => x.Code == HttpStatusCode.Unauthorized)) - logger.ThrowLogPoolStartupException("Wallet-Daemon reports invalid credentials"); + var responses2 = await rpcClientWallet.ExecuteAsync<object>(logger, CryptonoteWalletCommands.GetAddress, ct); - return responses2.All(x => x.Error == null); + return responses2.Error == null; } return true; @@ -481,7 +464,7 @@ protected override async Task<bool> AreDaemonsHealthyAsync(CancellationToken ct) protected override async Task<bool> AreDaemonsConnectedAsync(CancellationToken ct) { - var response = await daemon.ExecuteCmdAnyAsync<GetInfoResponse>(logger, CryptonoteCommands.GetInfo, ct); + var response = await rpcClient.ExecuteAsync<GetInfoResponse>(logger, CryptonoteCommands.GetInfo, ct); return response.Error == null && response.Response != null && (response.Response.OutgoingConnectionsCount + response.Response.IncomingConnectionsCount) > 0; @@ -499,10 +482,10 @@ protected override async Task EnsureDaemonsSynchedAsync(CancellationToken ct) ReserveSize = CryptonoteConstants.ReserveSize }; - var responses = await daemon.ExecuteCmdAllAsync<GetBlockTemplateResponse>(logger, + var response = await rpcClient.ExecuteAsync<GetBlockTemplateResponse>(logger, CryptonoteCommands.GetBlockTemplate, ct, request); - var isSynched = responses.All(x => x.Error == null || x.Error.Code != -9); + var isSynched = response.Error is not {Code: -9}; if(isSynched) { @@ -529,14 +512,14 @@ protected override async Task PostStartInitAsync(CancellationToken ct) // coin config var coin = poolConfig.Template.As<CryptonoteCoinTemplate>(); - var infoResponse = await daemon.ExecuteCmdAnyAsync(logger, CryptonoteCommands.GetInfo, ct); + var infoResponse = await rpcClient.ExecuteAsync(logger, CryptonoteCommands.GetInfo, ct); if(infoResponse.Error != null) logger.ThrowLogPoolStartupException($"Init RPC failed: {infoResponse.Error.Message} (Code {infoResponse.Error.Code})"); if(clusterConfig.PaymentProcessing?.Enabled == true && poolConfig.PaymentProcessing?.Enabled == true) { - var addressResponse = await walletDaemon.ExecuteCmdAnyAsync<GetAddressResponse>(logger, ct, CryptonoteWalletCommands.GetAddress); + var addressResponse = await rpcClientWallet.ExecuteAsync<GetAddressResponse>(logger, CryptonoteWalletCommands.GetAddress, ct); // ensure pool owns wallet if(clusterConfig.PaymentProcessing?.Enabled == true && addressResponse.Response?.Address != poolConfig.Address) @@ -669,7 +652,7 @@ protected virtual void SetupJobUpdates(CancellationToken ct) { logger.Info(() => $"Subscribing to ZMQ push-updates from {string.Join(", ", zmq.Values)}"); - var blockNotify = daemon.ZmqSubscribe(logger, ct, zmq) + var blockNotify = rpcClient.ZmqSubscribe(logger, ct, zmq) .Where(msg => { bool result = false; diff --git a/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs b/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs index a3259059e..cf9a0c120 100644 --- a/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs +++ b/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs @@ -10,8 +10,8 @@ using Miningcore.Blockchain.Cryptonote.DaemonRequests; using Miningcore.Blockchain.Cryptonote.DaemonResponses; using Miningcore.Configuration; -using Miningcore.DaemonInterface; using Miningcore.Extensions; +using Miningcore.JsonRpc; using Miningcore.Messaging; using Miningcore.Mining; using Miningcore.Native; @@ -52,15 +52,15 @@ public CryptonotePayoutHandler( } private readonly IComponentContext ctx; - private DaemonClient daemon; - private DaemonClient walletDaemon; + private RpcClient rpcClient; + private RpcClient rpcClientWallet; private CryptonoteNetworkType? networkType; private CryptonotePoolPaymentProcessingConfigExtra extraConfig; private bool walletSupportsTransferSplit; protected override string LogCategory => "Cryptonote Payout Handler"; - private async Task<bool> HandleTransferResponseAsync(DaemonResponse<TransferResponse> response, params Balance[] balances) + private async Task<bool> HandleTransferResponseAsync(RpcResponse<TransferResponse> response, params Balance[] balances) { var coin = poolConfig.Template.As<CryptonoteCoinTemplate>(); @@ -85,7 +85,7 @@ private async Task<bool> HandleTransferResponseAsync(DaemonResponse<TransferResp } } - private async Task<bool> HandleTransferResponseAsync(DaemonResponse<TransferSplitResponse> response, params Balance[] balances) + private async Task<bool> HandleTransferResponseAsync(RpcResponse<TransferSplitResponse> response, params Balance[] balances) { var coin = poolConfig.Template.As<CryptonoteCoinTemplate>(); @@ -114,7 +114,7 @@ private async Task UpdateNetworkTypeAsync(CancellationToken ct) { if(!networkType.HasValue) { - var infoResponse = await daemon.ExecuteCmdAnyAsync(logger, CNC.GetInfo, ct, true); + var infoResponse = await rpcClient.ExecuteAsync(logger, CNC.GetInfo, ct, true); var info = infoResponse.Response.ToObject<GetInfoResponse>(); // chain detection @@ -144,7 +144,7 @@ private async Task UpdateNetworkTypeAsync(CancellationToken ct) private async Task<bool> EnsureBalance(decimal requiredAmount, CryptonoteCoinTemplate coin, CancellationToken ct) { - var response = await walletDaemon.ExecuteCmdSingleAsync<GetBalanceResponse>(logger, CryptonoteWalletCommands.GetBalance, ct); + var response = await rpcClientWallet.ExecuteAsync<GetBalanceResponse>(logger, CryptonoteWalletCommands.GetBalance, ct); if(response.Error != null) { @@ -198,7 +198,7 @@ private async Task<bool> PayoutBatch(Balance[] balances, CancellationToken ct) logger.Info(() => $"[{LogCategory}] Paying {FormatAmount(balances.Sum(x => x.Amount))} to {balances.Length} addresses:\n{string.Join("\n", balances.OrderByDescending(x => x.Amount).Select(x => $"{FormatAmount(x.Amount)} to {x.Address}"))}"); // send command - var transferResponse = await walletDaemon.ExecuteCmdSingleAsync<TransferResponse>(logger, CryptonoteWalletCommands.Transfer, ct, request); + var transferResponse = await rpcClientWallet.ExecuteAsync<TransferResponse>(logger, CryptonoteWalletCommands.Transfer, ct, request); // gracefully handle error -4 (transaction would be too large. try /transfer_split) if(transferResponse.Error?.Code == -4) @@ -208,7 +208,7 @@ private async Task<bool> PayoutBatch(Balance[] balances, CancellationToken ct) logger.Error(() => $"[{LogCategory}] Daemon command '{CryptonoteWalletCommands.Transfer}' returned error: {transferResponse.Error.Message} code {transferResponse.Error.Code}"); logger.Info(() => $"[{LogCategory}] Retrying transfer using {CryptonoteWalletCommands.TransferSplit}"); - var transferSplitResponse = await walletDaemon.ExecuteCmdSingleAsync<TransferSplitResponse>(logger, CryptonoteWalletCommands.TransferSplit, ct, request); + var transferSplitResponse = await rpcClientWallet.ExecuteAsync<TransferSplitResponse>(logger, CryptonoteWalletCommands.TransferSplit, ct, request); return await HandleTransferResponseAsync(transferSplitResponse, balances); } @@ -275,7 +275,7 @@ private async Task<bool> PayoutToPaymentId(Balance balance, CancellationToken ct logger.Info(() => $"[{LogCategory}] Paying {FormatAmount(balance.Amount)} to integrated address {balance.Address}"); // send command - var result = await walletDaemon.ExecuteCmdSingleAsync<TransferResponse>(logger, CryptonoteWalletCommands.Transfer, ct, request); + var result = await rpcClientWallet.ExecuteAsync<TransferResponse>(logger, CryptonoteWalletCommands.Transfer, ct, request); if(walletSupportsTransferSplit) { @@ -284,7 +284,7 @@ private async Task<bool> PayoutToPaymentId(Balance balance, CancellationToken ct { logger.Info(() => $"[{LogCategory}] Retrying transfer using {CryptonoteWalletCommands.TransferSplit}"); - result = await walletDaemon.ExecuteCmdSingleAsync<TransferResponse>(logger, CryptonoteWalletCommands.TransferSplit, ct, request); + result = await rpcClientWallet.ExecuteAsync<TransferResponse>(logger, CryptonoteWalletCommands.TransferSplit, ct, request); } } @@ -317,8 +317,7 @@ public async Task ConfigureAsync(ClusterConfig clusterConfig, PoolConfig poolCon }) .ToArray(); - daemon = new DaemonClient(jsonSerializerSettings, messageBus, clusterConfig.ClusterName ?? poolConfig.PoolName, poolConfig.Id); - daemon.Configure(daemonEndpoints); + rpcClient = new RpcClient(daemonEndpoints.First(), jsonSerializerSettings, messageBus, poolConfig.Id); // configure wallet daemon var walletDaemonEndpoints = poolConfig.Daemons @@ -332,14 +331,13 @@ public async Task ConfigureAsync(ClusterConfig clusterConfig, PoolConfig poolCon }) .ToArray(); - walletDaemon = new DaemonClient(jsonSerializerSettings, messageBus, clusterConfig.ClusterName ?? poolConfig.PoolName, poolConfig.Id); - walletDaemon.Configure(walletDaemonEndpoints); + rpcClientWallet = new RpcClient(walletDaemonEndpoints.First(), jsonSerializerSettings, messageBus, poolConfig.Id); // detect network await UpdateNetworkTypeAsync(ct); // detect transfer_split support - var response = await walletDaemon.ExecuteCmdSingleAsync<TransferResponse>(logger, CryptonoteWalletCommands.TransferSplit, ct); + var response = await rpcClientWallet.ExecuteAsync<TransferResponse>(logger, CryptonoteWalletCommands.TransferSplit, ct); walletSupportsTransferSplit = response.Error.Code != CryptonoteConstants.MoneroRpcMethodNotFound; } @@ -366,7 +364,7 @@ public async Task<Block[]> ClassifyBlocksAsync(IMiningPool pool, Block[] blocks, { var block = page[j]; - var rpcResult = await daemon.ExecuteCmdAnyAsync<GetBlockHeaderResponse>(logger, + var rpcResult = await rpcClient.ExecuteAsync<GetBlockHeaderResponse>(logger, CNC.GetBlockHeaderByHeight, ct, new GetBlockHeaderByHeightRequest { @@ -444,7 +442,7 @@ public async Task PayoutAsync(IMiningPool pool, Balance[] balances, Cancellation var coin = poolConfig.Template.As<CryptonoteCoinTemplate>(); #if !DEBUG // ensure we have peers - var infoResponse = await daemon.ExecuteCmdAnyAsync<GetInfoResponse>(logger, CNC.GetInfo, ct); + var infoResponse = await daemon.ExecuteCmdAsync<GetInfoResponse>(logger, CNC.GetInfo, ct); if (infoResponse.Error != null || infoResponse.Response == null || infoResponse.Response.IncomingConnectionsCount + infoResponse.Response.OutgoingConnectionsCount < 3) { @@ -467,7 +465,7 @@ public async Task PayoutAsync(IMiningPool pool, Balance[] balances, Cancellation if(addressPrefix != coin.AddressPrefix && addressIntegratedPrefix != coin.AddressPrefixIntegrated) { - logger.Warn(() => $"[{LogCategory}] Excluding payment to invalid address {x.Address}"); + logger.Warn(() => $"[{LogCategory}] Excluding payment to invalid address: {x.Address}"); return false; } @@ -477,7 +475,7 @@ public async Task PayoutAsync(IMiningPool pool, Balance[] balances, Cancellation if(addressPrefix != coin.AddressPrefixStagenet && addressIntegratedPrefix != coin.AddressPrefixIntegratedStagenet) { - logger.Warn(() => $"[{LogCategory}] Excluding payment to invalid address {x.Address}"); + logger.Warn(() => $"[{LogCategory}] Excluding payment to invalid address: {x.Address}"); return false; } @@ -487,7 +485,7 @@ public async Task PayoutAsync(IMiningPool pool, Balance[] balances, Cancellation if(addressPrefix != coin.AddressPrefixTestnet && addressIntegratedPrefix != coin.AddressPrefixIntegratedTestnet) { - logger.Warn(() => $"[{LogCategory}] Excluding payment to invalid address {x.Address}"); + logger.Warn(() => $"[{LogCategory}] Excluding payment to invalid address: {x.Address}"); return false; } @@ -561,7 +559,7 @@ public async Task PayoutAsync(IMiningPool pool, Balance[] balances, Cancellation } // save wallet - await walletDaemon.ExecuteCmdSingleAsync<JToken>(logger, CryptonoteWalletCommands.Store, ct); + await rpcClientWallet.ExecuteAsync<JToken>(logger, CryptonoteWalletCommands.Store, ct); } #endregion // IPayoutHandler diff --git a/src/Miningcore/Blockchain/Equihash/DaemonResponses/ZCashAsyncOperationStatus.cs b/src/Miningcore/Blockchain/Equihash/DaemonResponses/ZCashAsyncOperationStatus.cs index 428c8ca6f..a0d35dcef 100644 --- a/src/Miningcore/Blockchain/Equihash/DaemonResponses/ZCashAsyncOperationStatus.cs +++ b/src/Miningcore/Blockchain/Equihash/DaemonResponses/ZCashAsyncOperationStatus.cs @@ -14,6 +14,6 @@ public class ZCashAsyncOperationStatus public string Status { get; set; } public JToken Result { get; set; } - public JsonRpcException Error { get; set; } + public JsonRpcError Error { get; set; } } } diff --git a/src/Miningcore/Blockchain/Equihash/EquihashJobManager.cs b/src/Miningcore/Blockchain/Equihash/EquihashJobManager.cs index 17501457c..3bc331dc5 100644 --- a/src/Miningcore/Blockchain/Equihash/EquihashJobManager.cs +++ b/src/Miningcore/Blockchain/Equihash/EquihashJobManager.cs @@ -12,7 +12,6 @@ using Miningcore.Configuration; using Miningcore.Contracts; using Miningcore.Crypto.Hashing.Equihash; -using Miningcore.DaemonInterface; using Miningcore.Extensions; using Miningcore.JsonRpc; using Miningcore.Messaging; @@ -36,9 +35,10 @@ public EquihashJobManager( } private EquihashCoinTemplate coin; - public EquihashCoinTemplate.EquihashNetworkParams ChainConfig { get; private set; } private EquihashSolver solver; + public EquihashCoinTemplate.EquihashNetworkParams ChainConfig { get; private set; } + protected override void PostChainIdentifyConfigure() { ChainConfig = coin.GetNetwork(network.ChainName); @@ -47,33 +47,30 @@ protected override void PostChainIdentifyConfigure() base.PostChainIdentifyConfigure(); } - private async Task<DaemonResponse<EquihashBlockTemplate>> GetBlockTemplateAsync(CancellationToken ct) + private async Task<RpcResponse<EquihashBlockTemplate>> GetBlockTemplateAsync(CancellationToken ct) { logger.LogInvoke(); - var subsidyResponse = await daemon.ExecuteCmdAnyAsync<ZCashBlockSubsidy>(logger, BitcoinCommands.GetBlockSubsidy, ct); + var subsidyResponse = await rpcClient.ExecuteAsync<ZCashBlockSubsidy>(logger, BitcoinCommands.GetBlockSubsidy, ct); - var result = await daemon.ExecuteCmdAnyAsync<EquihashBlockTemplate>(logger, + var result = await rpcClient.ExecuteAsync<EquihashBlockTemplate>(logger, BitcoinCommands.GetBlockTemplate, ct, extraPoolConfig?.GBTArgs ?? (object) GetBlockTemplateParams()); if(subsidyResponse.Error == null && result.Error == null && result.Response != null) result.Response.Subsidy = subsidyResponse.Response; else if(subsidyResponse.Error?.Code != (int) BitcoinRPCErrorCode.RPC_METHOD_NOT_FOUND) - result.Error = new JsonRpcException(-1, $"{BitcoinCommands.GetBlockSubsidy} failed", null); + result = new RpcResponse<EquihashBlockTemplate>(null, new JsonRpcError(-1, $"{BitcoinCommands.GetBlockSubsidy} failed", null)); return result; } - private DaemonResponse<EquihashBlockTemplate> GetBlockTemplateFromJson(string json) + private RpcResponse<EquihashBlockTemplate> GetBlockTemplateFromJson(string json) { logger.LogInvoke(); var result = JsonConvert.DeserializeObject<JsonRpcResponse>(json); - return new DaemonResponse<EquihashBlockTemplate> - { - Response = result.ResultAs<EquihashBlockTemplate>(), - }; + return new RpcResponse<EquihashBlockTemplate>(result.ResultAs<EquihashBlockTemplate>()); } protected override IDestination AddressToDestination(string address, BitcoinAddressType? addressType) @@ -210,8 +207,8 @@ public override async Task<bool> ValidateAddressAsync(string address, Cancellati return true; // handle z-addr - var result = await daemon.ExecuteCmdAnyAsync<ValidateAddressResponse>(logger, ct, - EquihashCommands.ZValidateAddress, new[] { address }); + var result = await rpcClient.ExecuteAsync<ValidateAddressResponse>(logger, + EquihashCommands.ZValidateAddress, ct, new[] { address }); return result.Response is {IsValid: true}; } diff --git a/src/Miningcore/Blockchain/Equihash/EquihashPayoutHandler.cs b/src/Miningcore/Blockchain/Equihash/EquihashPayoutHandler.cs index 3c914c4d2..38d30b286 100644 --- a/src/Miningcore/Blockchain/Equihash/EquihashPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Equihash/EquihashPayoutHandler.cs @@ -58,14 +58,14 @@ public override async Task ConfigureAsync(ClusterConfig clusterConfig, PoolConfi poolExtraConfig = poolConfig.Extra.SafeExtensionDataAs<EquihashPoolConfigExtra>(); // detect network - var blockchainInfoResponse = await daemon.ExecuteCmdSingleAsync<BlockchainInfo>(logger, BitcoinCommands.GetBlockchainInfo, ct); + var blockchainInfoResponse = await rpcClient.ExecuteAsync<BlockchainInfo>(logger, BitcoinCommands.GetBlockchainInfo, ct); network = Network.GetNetwork(blockchainInfoResponse.Response.Chain.ToLower()); chainConfig = poolConfig.Template.As<EquihashCoinTemplate>().GetNetwork(network.ChainName); // detect z_shieldcoinbase support - var response = await daemon.ExecuteCmdSingleAsync<JObject>(logger, EquihashCommands.ZShieldCoinbase, ct); + var response = await rpcClient.ExecuteAsync<JObject>(logger, EquihashCommands.ZShieldCoinbase, ct); supportsNativeShielding = response.Error.Code != (int) BitcoinRPCErrorCode.RPC_METHOD_NOT_FOUND; } @@ -107,13 +107,13 @@ public override async Task PayoutAsync(IMiningPool pool, Balance[] balances, Can var pageAmount = amounts.Sum(x => x.Amount); // check shielded balance - var balanceResult = await daemon.ExecuteCmdSingleAsync<object>(logger, EquihashCommands.ZGetBalance, ct, new object[] + var balanceResponse = await rpcClient.ExecuteAsync<object>(logger, EquihashCommands.ZGetBalance, ct, new object[] { poolExtraConfig.ZAddress, // default account ZMinConfirmations, // only spend funds covered by this many confirmations }); - if(balanceResult.Error != null || (decimal) (double) balanceResult.Response - TransferFee < pageAmount) + if(balanceResponse.Error != null || (decimal) (double) balanceResponse.Response - TransferFee < pageAmount) { logger.Info(() => $"[{LogCategory}] Insufficient shielded balance for payment of {FormatAmount(pageAmount)}"); return; @@ -131,11 +131,11 @@ public override async Task PayoutAsync(IMiningPool pool, Balance[] balances, Can // send command tryTransfer: - var result = await daemon.ExecuteCmdSingleAsync<string>(logger, EquihashCommands.ZSendMany, ct, args); + var response = await rpcClient.ExecuteAsync<string>(logger, EquihashCommands.ZSendMany, ct, args); - if(result.Error == null) + if(response.Error == null) { - var operationId = result.Response; + var operationId = response.Response; // check result if(string.IsNullOrEmpty(operationId)) @@ -148,7 +148,7 @@ public override async Task PayoutAsync(IMiningPool pool, Balance[] balances, Can while(continueWaiting) { - var operationResultResponse = await daemon.ExecuteCmdSingleAsync<ZCashAsyncOperationStatus[]>(logger, + var operationResultResponse = await rpcClient.ExecuteAsync<ZCashAsyncOperationStatus[]>(logger, EquihashCommands.ZGetOperationResult, ct, new object[] { new object[] { operationId } }); if(operationResultResponse.Error == null && @@ -185,26 +185,26 @@ public override async Task PayoutAsync(IMiningPool pool, Balance[] balances, Can } logger.Info(() => $"[{LogCategory}] Waiting for completion: {operationId}"); - await Task.Delay(TimeSpan.FromSeconds(10)); + await Task.Delay(TimeSpan.FromSeconds(10), ct); } } } else { - if(result.Error.Code == (int) BitcoinRPCErrorCode.RPC_WALLET_UNLOCK_NEEDED && !didUnlockWallet) + if(response.Error.Code == (int) BitcoinRPCErrorCode.RPC_WALLET_UNLOCK_NEEDED && !didUnlockWallet) { if(!string.IsNullOrEmpty(extraPoolPaymentProcessingConfig?.WalletPassword)) { logger.Info(() => $"[{LogCategory}] Unlocking wallet"); - var unlockResult = await daemon.ExecuteCmdSingleAsync<JToken>(logger, BitcoinCommands.WalletPassphrase, ct, new[] + var unlockResponse = await rpcClient.ExecuteAsync<JToken>(logger, BitcoinCommands.WalletPassphrase, ct, new[] { (object) extraPoolPaymentProcessingConfig.WalletPassword, (object) 5 // unlock for N seconds }); - if(unlockResult.Error == null) + if(unlockResponse.Error == null) { didUnlockWallet = true; goto tryTransfer; @@ -212,8 +212,8 @@ public override async Task PayoutAsync(IMiningPool pool, Balance[] balances, Can else { - logger.Error(() => $"[{LogCategory}] {BitcoinCommands.WalletPassphrase} returned error: {result.Error.Message} code {result.Error.Code}"); - NotifyPayoutFailure(poolConfig.Id, page, $"{BitcoinCommands.WalletPassphrase} returned error: {result.Error.Message} code {result.Error.Code}", null); + logger.Error(() => $"[{LogCategory}] {BitcoinCommands.WalletPassphrase} returned error: {response.Error.Message} code {response.Error.Code}"); + NotifyPayoutFailure(poolConfig.Id, page, $"{BitcoinCommands.WalletPassphrase} returned error: {response.Error.Message} code {response.Error.Code}", null); break; } } @@ -228,16 +228,16 @@ public override async Task PayoutAsync(IMiningPool pool, Balance[] balances, Can else { - logger.Error(() => $"[{LogCategory}] {EquihashCommands.ZSendMany} returned error: {result.Error.Message} code {result.Error.Code}"); + logger.Error(() => $"[{LogCategory}] {EquihashCommands.ZSendMany} returned error: {response.Error.Message} code {response.Error.Code}"); - NotifyPayoutFailure(poolConfig.Id, page, $"{EquihashCommands.ZSendMany} returned error: {result.Error.Message} code {result.Error.Code}", null); + NotifyPayoutFailure(poolConfig.Id, page, $"{EquihashCommands.ZSendMany} returned error: {response.Error.Message} code {response.Error.Code}", null); } } } // lock wallet logger.Info(() => $"[{LogCategory}] Locking wallet"); - await daemon.ExecuteCmdSingleAsync<JToken>(logger, BitcoinCommands.WalletLock, ct); + await rpcClient.ExecuteAsync<JToken>(logger, BitcoinCommands.WalletLock, ct); } #endregion // IPayoutHandler @@ -257,19 +257,19 @@ private async Task ShieldCoinbaseAsync(CancellationToken ct) poolExtraConfig.ZAddress, // dest: pool's z-addr }; - var result = await daemon.ExecuteCmdSingleAsync<ZCashShieldingResponse>(logger, EquihashCommands.ZShieldCoinbase, ct, args); + var response = await rpcClient.ExecuteAsync<ZCashShieldingResponse>(logger, EquihashCommands.ZShieldCoinbase, ct, args); - if(result.Error != null) + if(response.Error != null) { - if(result.Error.Code == -6) + if(response.Error.Code == -6) logger.Info(() => $"[{LogCategory}] No funds to shield"); else - logger.Error(() => $"[{LogCategory}] {EquihashCommands.ZShieldCoinbase} returned error: {result.Error.Message} code {result.Error.Code}"); + logger.Error(() => $"[{LogCategory}] {EquihashCommands.ZShieldCoinbase} returned error: {response.Error.Message} code {response.Error.Code}"); return; } - var operationId = result.Response.OperationId; + var operationId = response.Response.OperationId; logger.Info(() => $"[{LogCategory}] {EquihashCommands.ZShieldCoinbase} operation id: {operationId}"); @@ -277,7 +277,7 @@ private async Task ShieldCoinbaseAsync(CancellationToken ct) while(continueWaiting) { - var operationResultResponse = await daemon.ExecuteCmdSingleAsync<ZCashAsyncOperationStatus[]>(logger, + var operationResultResponse = await rpcClient.ExecuteAsync<ZCashAsyncOperationStatus[]>(logger, EquihashCommands.ZGetOperationResult, ct, new object[] { new object[] { operationId } }); if(operationResultResponse.Error == null && @@ -309,7 +309,7 @@ private async Task ShieldCoinbaseAsync(CancellationToken ct) } logger.Info(() => $"[{LogCategory}] Waiting for shielding operation completion: {operationId}"); - await Task.Delay(TimeSpan.FromSeconds(10)); + await Task.Delay(TimeSpan.FromSeconds(10), ct); } } @@ -318,15 +318,15 @@ private async Task ShieldCoinbaseEmulatedAsync(CancellationToken ct) logger.Info(() => $"[{LogCategory}] Shielding ZCash Coinbase funds (emulated)"); // get t-addr unspent balance for just the coinbase address (pool wallet) - var unspentResult = await daemon.ExecuteCmdSingleAsync<Utxo[]>(logger, BitcoinCommands.ListUnspent, ct); + var unspentResponse = await rpcClient.ExecuteAsync<Utxo[]>(logger, BitcoinCommands.ListUnspent, ct); - if(unspentResult.Error != null) + if(unspentResponse.Error != null) { - logger.Error(() => $"[{LogCategory}] {BitcoinCommands.ListUnspent} returned error: {unspentResult.Error.Message} code {unspentResult.Error.Code}"); + logger.Error(() => $"[{LogCategory}] {BitcoinCommands.ListUnspent} returned error: {unspentResponse.Error.Message} code {unspentResponse.Error.Code}"); return; } - var balance = unspentResult.Response + var balance = unspentResponse.Response .Where(x => x.Spendable && x.Address == poolConfig.Address) .Sum(x => x.Amount); @@ -358,15 +358,15 @@ private async Task ShieldCoinbaseEmulatedAsync(CancellationToken ct) }; // send command - var sendResult = await daemon.ExecuteCmdSingleAsync<string>(logger, EquihashCommands.ZSendMany, ct, args); + var sendResponse = await rpcClient.ExecuteAsync<string>(logger, EquihashCommands.ZSendMany, ct, args); - if(sendResult.Error != null) + if(sendResponse.Error != null) { - logger.Error(() => $"[{LogCategory}] {EquihashCommands.ZSendMany} returned error: {unspentResult.Error.Message} code {unspentResult.Error.Code}"); + logger.Error(() => $"[{LogCategory}] {EquihashCommands.ZSendMany} returned error: {unspentResponse.Error.Message} code {unspentResponse.Error.Code}"); return; } - var operationId = sendResult.Response; + var operationId = sendResponse.Response; logger.Info(() => $"[{LogCategory}] {EquihashCommands.ZSendMany} operation id: {operationId}"); @@ -374,7 +374,7 @@ private async Task ShieldCoinbaseEmulatedAsync(CancellationToken ct) while(continueWaiting) { - var operationResultResponse = await daemon.ExecuteCmdSingleAsync<ZCashAsyncOperationStatus[]>(logger, + var operationResultResponse = await rpcClient.ExecuteAsync<ZCashAsyncOperationStatus[]>(logger, EquihashCommands.ZGetOperationResult, ct, new object[] { new object[] { operationId } }); if(operationResultResponse.Error == null && @@ -407,7 +407,7 @@ private async Task ShieldCoinbaseEmulatedAsync(CancellationToken ct) } logger.Info(() => $"[{LogCategory}] Waiting for shielding transfer completion: {operationId}"); - await Task.Delay(TimeSpan.FromSeconds(10)); + await Task.Delay(TimeSpan.FromSeconds(10), ct); } } } diff --git a/src/Miningcore/Blockchain/Ethereum/EthereumJobManager.cs b/src/Miningcore/Blockchain/Ethereum/EthereumJobManager.cs index d54e3f6b9..a430c3fb5 100644 --- a/src/Miningcore/Blockchain/Ethereum/EthereumJobManager.cs +++ b/src/Miningcore/Blockchain/Ethereum/EthereumJobManager.cs @@ -3,20 +3,15 @@ using System.Globalization; using System.IO; using System.Linq; -using System.Net; using System.Numerics; using System.Reactive.Linq; -using System.Reactive.Threading.Tasks; -using System.Text; using System.Threading; using System.Threading.Tasks; using Autofac; -using Miningcore.Blockchain.Bitcoin; using Miningcore.Blockchain.Ethereum.Configuration; using Miningcore.Blockchain.Ethereum.DaemonResponses; using Miningcore.Configuration; using Miningcore.Crypto.Hashing.Ethash; -using Miningcore.DaemonInterface; using Miningcore.Extensions; using Miningcore.JsonRpc; using Miningcore.Messaging; @@ -25,7 +20,6 @@ using Miningcore.Time; using Miningcore.Util; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using NLog; using Block = Miningcore.Blockchain.Ethereum.DaemonResponses.Block; using Contract = Miningcore.Contracts.Contract; @@ -59,7 +53,7 @@ public EthereumJobManager( } private DaemonEndpointConfig[] daemonEndpoints; - private DaemonClient daemon; + private RpcClient rpcClient; private EthereumNetworkType networkType; private GethChainType chainType; private EthashFull ethash; @@ -150,23 +144,23 @@ private async Task<EthereumBlockTemplate> GetBlockTemplateAsync(CancellationToke { logger.LogInvoke(); - var commands = new[] + var requests = new[] { - new DaemonCmd(EC.GetWork), - new DaemonCmd(EC.GetBlockByNumber, new[] { (object) "latest", true }) + new RpcRequest(EC.GetWork), + new RpcRequest(EC.GetBlockByNumber, new[] { (object) "latest", true }) }; - var results = await daemon.ExecuteBatchAnyAsync(logger, ct, commands); + var responses = await rpcClient.ExecuteBatchAsync(logger, ct, requests); - if(results.Any(x => x.Error != null)) + if(responses.Any(x => x.Error != null)) { - logger.Warn(() => $"Error(s) refreshing blocktemplate: {results.First(x => x.Error != null).Error.Message}"); + logger.Warn(() => $"Error(s) refreshing blocktemplate: {responses.First(x => x.Error != null).Error.Message}"); return null; } // extract results - var work = results[0].Response.ToObject<string[]>(); - var block = results[1].Response.ToObject<Block>(); + var work = responses[0].Response.ToObject<string[]>(); + var block = responses[1].Response.ToObject<Block>(); // append blockheight (Recent versions of geth return this as the 4th element in the getWork response, older geth does not) if(work.Length < 4) @@ -194,39 +188,33 @@ private async Task<EthereumBlockTemplate> GetBlockTemplateAsync(CancellationToke private async Task ShowDaemonSyncProgressAsync(CancellationToken ct) { - var responses = await daemon.ExecuteCmdAllAsync<object>(logger, EC.GetSyncState, ct); - var firstValidResponse = responses.FirstOrDefault(x => x.Error == null && x.Response != null)?.Response; + var syncStateResponse = await rpcClient.ExecuteAsync<object>(logger, EC.GetSyncState, ct); - if(firstValidResponse != null) + if(syncStateResponse.Error == null) { // eth_syncing returns false if not synching - if(firstValidResponse is bool) + if(syncStateResponse.Response is false) return; - var syncStates = responses.Where(x => x.Error == null && x.Response != null && firstValidResponse is JObject) - .Select(x => ((JObject) x.Response).ToObject<SyncState>()) - .ToArray(); - - if(syncStates.Any()) + if(syncStateResponse.Response is SyncState syncState) { // get peer count - var response = await daemon.ExecuteCmdAllAsync<string>(logger, EC.GetPeerCount, ct); - var validResponses = response.Where(x => x.Error == null && x.Response != null).ToArray(); - var peerCount = validResponses.Any() ? validResponses.Max(x => x.Response.IntegralFromHex<uint>()) : 0; + var getPeerCountResponse = await rpcClient.ExecuteAsync<string>(logger, EC.GetPeerCount, ct); + var peerCount = getPeerCountResponse.Response.IntegralFromHex<uint>(); - if(syncStates.Any(x => x.WarpChunksAmount != 0)) + if(syncState.WarpChunksAmount != 0) { - var warpChunkAmount = syncStates.Min(x => x.WarpChunksAmount); - var warpChunkProcessed = syncStates.Max(x => x.WarpChunksProcessed); + var warpChunkAmount = syncState.WarpChunksAmount; + var warpChunkProcessed = syncState.WarpChunksProcessed; var percent = (double) warpChunkProcessed / warpChunkAmount * 100; logger.Info(() => $"Daemons have downloaded {percent:0.00}% of warp-chunks from {peerCount} peers"); } - else if(syncStates.Any(x => x.HighestBlock != 0)) + else if(syncState.HighestBlock != 0) { - var lowestHeight = syncStates.Min(x => x.CurrentBlock); - var totalBlocks = syncStates.Max(x => x.HighestBlock); + var lowestHeight = syncState.CurrentBlock; + var totalBlocks = syncState.HighestBlock; var percent = (double) lowestHeight / totalBlocks * 100; logger.Info(() => $"Daemons have downloaded {percent:0.00}% of blockchain from {peerCount} peers"); @@ -241,17 +229,17 @@ private async Task UpdateNetworkStatsAsync(CancellationToken ct) try { - var commands = new[] + var requests = new[] { - new DaemonCmd(EC.GetPeerCount), - new DaemonCmd(EC.GetBlockByNumber, new[] { (object) "latest", true }) + new RpcRequest(EC.GetPeerCount), + new RpcRequest(EC.GetBlockByNumber, new[] { (object) "latest", true }) }; - var results = await daemon.ExecuteBatchAnyAsync(logger, ct, commands); + var responses = await rpcClient.ExecuteBatchAsync(logger, ct, requests); - if(results.Any(x => x.Error != null)) + if(responses.Any(x => x.Error != null)) { - var errors = results.Where(x => x.Error != null) + var errors = responses.Where(x => x.Error != null) .ToArray(); if(errors.Any()) @@ -259,8 +247,8 @@ private async Task UpdateNetworkStatsAsync(CancellationToken ct) } // extract results - var peerCount = results[0].Response.ToObject<string>().IntegralFromHex<int>(); - var latestBlockInfo = results[1].Response.ToObject<Block>(); + var peerCount = responses[0].Response.ToObject<string>().IntegralFromHex<int>(); + var latestBlockInfo = responses[1].Response.ToObject<Block>(); var latestBlockHeight = latestBlockInfo.Height.Value; var latestBlockTimestamp = latestBlockInfo.Timestamp; @@ -268,8 +256,8 @@ private async Task UpdateNetworkStatsAsync(CancellationToken ct) var sampleSize = (ulong) 300; var sampleBlockNumber = latestBlockHeight - sampleSize; - var sampleBlockResults = await daemon.ExecuteCmdAllAsync<Block>(logger, EC.GetBlockByNumber, ct, new[] { (object) sampleBlockNumber.ToStringHexWithPrefix(), true }); - var sampleBlockTimestamp = sampleBlockResults.First(x => x.Error == null && x.Response?.Height != null).Response.Timestamp; + var sampleBlockResults = await rpcClient.ExecuteAsync<Block>(logger, EC.GetBlockByNumber, ct, new[] { (object) sampleBlockNumber.ToStringHexWithPrefix(), true }); + var sampleBlockTimestamp = sampleBlockResults.Response.Timestamp; var blockTime = (double) (latestBlockTimestamp - sampleBlockTimestamp) / sampleSize; var networkHashrate = (double) (latestBlockDifficulty / blockTime); @@ -287,7 +275,7 @@ private async Task UpdateNetworkStatsAsync(CancellationToken ct) private async Task<bool> SubmitBlockAsync(Share share, string fullNonceHex, string headerHash, string mixHash) { // submit work - var response = await daemon.ExecuteCmdAnyAsync<object>(logger, EC.SubmitWork, CancellationToken.None, new[] + var response = await rpcClient.ExecuteAsync<object>(logger, EC.SubmitWork, CancellationToken.None, new[] { fullNonceHex, headerHash, @@ -325,20 +313,6 @@ private object[] GetJobParamsForStratum(bool isNew) return new object[0]; } - private JsonRpcRequest DeserializeRequest(byte[] data) - { - using(var stream = new MemoryStream(data)) - { - using(var reader = new StreamReader(stream, Encoding.UTF8)) - { - using(var jreader = new JsonTextReader(reader)) - { - return serializer.Deserialize<JsonRpcRequest>(jreader); - } - } - } - } - #region API-Surface public IObservable<object> Jobs { get; private set; } @@ -442,25 +416,19 @@ protected override void ConfigureDaemons() { var jsonSerializerSettings = ctx.Resolve<JsonSerializerSettings>(); - daemon = new DaemonClient(jsonSerializerSettings, messageBus, clusterConfig.ClusterName ?? poolConfig.PoolName, poolConfig.Id); - daemon.Configure(daemonEndpoints); + rpcClient = new RpcClient(daemonEndpoints.First(), jsonSerializerSettings, messageBus, poolConfig.Id); } protected override async Task<bool> AreDaemonsHealthyAsync(CancellationToken ct) { - var responses = await daemon.ExecuteCmdAllAsync<Block>(logger, EC.GetBlockByNumber, ct, new[] { (object) "latest", true }); - - if(responses.Where(x => x.Error?.InnerException?.GetType() == typeof(DaemonClientException)) - .Select(x => (DaemonClientException) x.Error.InnerException) - .Any(x => x.Code == HttpStatusCode.Unauthorized)) - logger.ThrowLogPoolStartupException("Daemon reports invalid credentials"); + var response = await rpcClient.ExecuteAsync<Block>(logger, EC.GetBlockByNumber, ct, new[] { (object) "latest", true }); - return responses.All(x => x.Error == null); + return response.Error == null; } protected override async Task<bool> AreDaemonsConnectedAsync(CancellationToken ct) { - var response = await daemon.ExecuteCmdAnyAsync<string>(logger, EC.GetPeerCount, ct); + var response = await rpcClient.ExecuteAsync<string>(logger, EC.GetPeerCount, ct); return response.Error == null && response.Response.IntegralFromHex<uint>() > 0; } @@ -471,10 +439,9 @@ protected override async Task EnsureDaemonsSynchedAsync(CancellationToken ct) while(true) { - var responses = await daemon.ExecuteCmdAllAsync<object>(logger, EC.GetSyncState, ct); + var responses = await rpcClient.ExecuteAsync<object>(logger, EC.GetSyncState, ct); - var isSynched = responses.All(x => x.Error == null && - x.Response is false); + var isSynched = responses.Response is false; if(isSynched) { @@ -499,12 +466,12 @@ protected override async Task PostStartInitAsync(CancellationToken ct) { var commands = new[] { - new DaemonCmd(EC.GetNetVersion), - new DaemonCmd(EC.GetAccounts), - new DaemonCmd(EC.GetCoinbase), + new RpcRequest(EC.GetNetVersion), + new RpcRequest(EC.GetAccounts), + new RpcRequest(EC.GetCoinbase), }; - var results = await daemon.ExecuteBatchAnyAsync(logger, ct, commands); + var results = await rpcClient.ExecuteBatchAsync(logger, ct, commands); if(results.Any(x => x.Error != null)) { @@ -559,6 +526,8 @@ protected override async Task PostStartInitAsync(CancellationToken ct) } } + ConfigureRewards(); + SetupJobUpdates(ct); } diff --git a/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs b/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs index 7c3a807c3..9ffaae492 100644 --- a/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs @@ -11,8 +11,8 @@ using Miningcore.Blockchain.Ethereum.DaemonRequests; using Miningcore.Blockchain.Ethereum.DaemonResponses; using Miningcore.Configuration; -using Miningcore.DaemonInterface; using Miningcore.Extensions; +using Miningcore.JsonRpc; using Miningcore.Messaging; using Miningcore.Mining; using Miningcore.Payments; @@ -52,7 +52,7 @@ public EthereumPayoutHandler( } private readonly IComponentContext ctx; - private DaemonClient daemon; + private RpcClient rpcClient; private EthereumNetworkType networkType; private GethChainType chainType; private const int BlockSearchOffset = 50; @@ -75,12 +75,7 @@ public async Task ConfigureAsync(ClusterConfig clusterConfig, PoolConfig poolCon // configure standard daemon var jsonSerializerSettings = ctx.Resolve<JsonSerializerSettings>(); - var daemonEndpoints = poolConfig.Daemons - .Where(x => string.IsNullOrEmpty(x.Category)) - .ToArray(); - - daemon = new DaemonClient(jsonSerializerSettings, messageBus, clusterConfig.ClusterName ?? poolConfig.PoolName, poolConfig.Id); - daemon.Configure(daemonEndpoints); + rpcClient = new RpcClient(poolConfig.Daemons.First(x => string.IsNullOrEmpty(x.Category)), jsonSerializerSettings, messageBus, poolConfig.Id); await DetectChainAsync(ct); } @@ -105,8 +100,8 @@ public async Task<Block[]> ClassifyBlocksAsync(IMiningPool pool, Block[] blocks, .ToArray(); // get latest block - var latestBlockResponses = await daemon.ExecuteCmdAllAsync<DaemonResponses.Block>(logger, EC.GetBlockByNumber, ct, new[] { (object) "latest", true }); - var latestBlockHeight = latestBlockResponses.First(x => x.Error == null && x.Response?.Height != null).Response.Height.Value; + var latestBlockResponse = await rpcClient.ExecuteAsync<DaemonResponses.Block>(logger, EC.GetBlockByNumber, ct, new[] { (object) "latest", true }); + var latestBlockHeight = latestBlockResponse.Response.Height.Value; // execute batch var blockInfos = await FetchBlocks(blockCache, ct, page.Select(block => (long) block.BlockHeight).ToArray()); @@ -128,9 +123,9 @@ public async Task<Block[]> ClassifyBlocksAsync(IMiningPool pool, Block[] blocks, // mature? if(latestBlockHeight - block.BlockHeight >= EthereumConstants.MinConfimations) { - var blockHashResponses = await daemon.ExecuteCmdAllAsync<DaemonResponses.Block>(logger, EC.GetBlockByNumber, ct, + var blockHashResponse = await rpcClient.ExecuteAsync<DaemonResponses.Block>(logger, EC.GetBlockByNumber, ct, new[] { (object) block.BlockHeight.ToStringHexWithPrefix(), true }); - var blockHash = blockHashResponses.First(x => x.Error == null && x.Response?.Hash != null).Response.Hash; + var blockHash = blockHashResponse.Response.Hash; block.Hash = blockHash; block.Status = BlockStatus.Confirmed; @@ -170,13 +165,13 @@ public async Task<Block[]> ClassifyBlocksAsync(IMiningPool pool, Block[] blocks, if(blockInfo2.Uncles.Length > 0) { // fetch all uncles in a single RPC batch request - var uncleBatch = blockInfo2.Uncles.Select((x, index) => new DaemonCmd(EC.GetUncleByBlockNumberAndIndex, + var uncleBatch = blockInfo2.Uncles.Select((x, index) => new RpcRequest(EC.GetUncleByBlockNumberAndIndex, new[] { blockInfo2.Height.Value.ToStringHexWithPrefix(), index.ToStringHexWithPrefix() })) .ToArray(); logger.Info(() => $"[{LogCategory}] Fetching {blockInfo2.Uncles.Length} uncles for block {blockInfo2.Height}"); - var uncleResponses = await daemon.ExecuteBatchAnyAsync(logger, ct, uncleBatch); + var uncleResponses = await rpcClient.ExecuteBatchAsync(logger, ct, uncleBatch); logger.Info(() => $"[{LogCategory}] Fetched {uncleResponses.Count(x => x.Error == null && x.Response != null)} uncles for block {blockInfo2.Height}"); @@ -189,9 +184,9 @@ public async Task<Block[]> ClassifyBlocksAsync(IMiningPool pool, Block[] blocks, // mature? if(latestBlockHeight - uncle.Height.Value >= EthereumConstants.MinConfimations) { - var blockHashUncleResponses = await daemon.ExecuteCmdAllAsync<DaemonResponses.Block>(logger, EC.GetBlockByNumber, ct, + var blockHashUncleResponse = await rpcClient.ExecuteAsync<DaemonResponses.Block>(logger, EC.GetBlockByNumber, ct, new[] { (object) uncle.Height.Value.ToStringHexWithPrefix(), true }); - var blockHashUncle = blockHashUncleResponses.First(x => x.Error == null && x.Response?.Hash != null).Response.Hash; + var blockHashUncle = blockHashUncleResponse.Response.Hash; block.Hash = blockHashUncle; block.Status = BlockStatus.Confirmed; @@ -248,7 +243,7 @@ public override async Task<decimal> UpdateBlockRewardBalancesAsync(IDbConnection public async Task PayoutAsync(IMiningPool pool, Balance[] balances, CancellationToken ct) { // ensure we have peers - var infoResponse = await daemon.ExecuteCmdSingleAsync<string>(logger, EC.GetPeerCount, ct); + var infoResponse = await rpcClient.ExecuteAsync<string>(logger, EC.GetPeerCount, ct); if(networkType == EthereumNetworkType.Mainnet && (infoResponse.Error != null || string.IsNullOrEmpty(infoResponse.Response) || @@ -288,14 +283,14 @@ public async Task PayoutAsync(IMiningPool pool, Balance[] balances, Cancellation if(cacheMisses.Any()) { - var blockBatch = cacheMisses.Select(height => new DaemonCmd(EC.GetBlockByNumber, + var blockBatch = cacheMisses.Select(height => new RpcRequest(EC.GetBlockByNumber, new[] { (object) height.ToStringHexWithPrefix(), true })).ToArray(); - var tmp = await daemon.ExecuteBatchAnyAsync(logger, ct, blockBatch); + var tmp = await rpcClient.ExecuteBatchAsync(logger, ct, blockBatch); var transformed = tmp .Where(x => x.Error == null && x.Response != null) @@ -334,10 +329,10 @@ internal static decimal GetBaseBlockReward(GethChainType chainType, ulong height private async Task<decimal> GetTxRewardAsync(DaemonResponses.Block blockInfo, CancellationToken ct) { // fetch all tx receipts in a single RPC batch request - var batch = blockInfo.Transactions.Select(tx => new DaemonCmd(EC.GetTxReceipt, new[] { tx.Hash })) + var batch = blockInfo.Transactions.Select(tx => new RpcRequest(EC.GetTxReceipt, new[] { tx.Hash })) .ToArray(); - var results = await daemon.ExecuteBatchAnyAsync(logger, ct, batch); + var results = await rpcClient.ExecuteBatchAsync(logger, ct, batch); if(results.Any(x => x.Error != null)) throw new Exception($"Error fetching tx receipts: {string.Join(", ", results.Where(x => x.Error != null).Select(y => y.Error.Message))}"); @@ -366,10 +361,10 @@ private async Task DetectChainAsync(CancellationToken ct) { var commands = new[] { - new DaemonCmd(EC.GetNetVersion), + new RpcRequest(EC.GetNetVersion), }; - var results = await daemon.ExecuteBatchAnyAsync(logger, ct, commands); + var results = await rpcClient.ExecuteBatchAsync(logger, ct, commands); if(results.Any(x => x.Error != null)) { @@ -398,10 +393,10 @@ private async Task<string> PayoutAsync(Balance balance, CancellationToken ct) { From = poolConfig.Address, To = balance.Address, - Value = writeHex(amount), + Value = amount.ToString("x").TrimStart('0'), }; - var response = await daemon.ExecuteCmdSingleAsync<string>(logger, EC.SendTx, ct, new[] { request }); + var response = await rpcClient.ExecuteAsync<string>(logger, EC.SendTx, ct, new[] { request }); if(response.Error != null) throw new Exception($"{EC.SendTx} returned error: {response.Error.Message} code {response.Error.Code}"); @@ -418,10 +413,5 @@ private async Task<string> PayoutAsync(Balance balance, CancellationToken ct) // done return txHash; } - - private static string writeHex(BigInteger value) - { - return (value.ToString("x").TrimStart('0')); - } } } diff --git a/src/Miningcore/DaemonInterface/DaemonClientException.cs b/src/Miningcore/DaemonInterface/DaemonClientException.cs deleted file mode 100644 index 017e63548..000000000 --- a/src/Miningcore/DaemonInterface/DaemonClientException.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Net; - -namespace Miningcore.DaemonInterface -{ - public class DaemonClientException : Exception - { - public DaemonClientException(string msg) : base(msg) - { - } - - public DaemonClientException(HttpStatusCode code, string msg) : base(msg) - { - Code = code; - } - - public HttpStatusCode Code { get; set; } - } -} diff --git a/src/Miningcore/DaemonInterface/DaemonCmd.cs b/src/Miningcore/DaemonInterface/DaemonCmd.cs deleted file mode 100644 index 55347629b..000000000 --- a/src/Miningcore/DaemonInterface/DaemonCmd.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Miningcore.DaemonInterface -{ - public class DaemonCmd - { - public DaemonCmd() - { - } - - public DaemonCmd(string method) - { - Method = method; - } - - public DaemonCmd(string method, object payload) - { - Method = method; - Payload = payload; - } - - public string Method { get; set; } - public object Payload { get; set; } - } -} diff --git a/src/Miningcore/DaemonInterface/DaemonResponse.cs b/src/Miningcore/DaemonInterface/DaemonResponse.cs deleted file mode 100644 index 7824e26c8..000000000 --- a/src/Miningcore/DaemonInterface/DaemonResponse.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Miningcore.Configuration; -using Miningcore.JsonRpc; - -namespace Miningcore.DaemonInterface -{ - public class DaemonResponse<T> - { - public JsonRpcException Error { get; set; } - public T Response { get; set; } - public AuthenticatedNetworkEndpointConfig Instance { get; set; } - } -} diff --git a/src/Miningcore/JsonRpc/JsonRpcException.cs b/src/Miningcore/JsonRpc/JsonRpcError.cs similarity index 87% rename from src/Miningcore/JsonRpc/JsonRpcException.cs rename to src/Miningcore/JsonRpc/JsonRpcError.cs index 658a25b2e..0101c31e3 100644 --- a/src/Miningcore/JsonRpc/JsonRpcException.cs +++ b/src/Miningcore/JsonRpc/JsonRpcError.cs @@ -27,9 +27,9 @@ namespace Miningcore.JsonRpc /// The remainder of the space is available for application defined errors. /// </summary> [JsonObject(MemberSerialization.OptIn)] - public class JsonRpcException + public record JsonRpcError { - public JsonRpcException(int code, string message, object data, Exception inner = null) + public JsonRpcError(int code, string message, object data, Exception inner = null) { Code = code; Message = message; @@ -38,15 +38,15 @@ public JsonRpcException(int code, string message, object data, Exception inner = } [JsonProperty(PropertyName = "code")] - public int Code { get; set; } + public int Code { get; } [JsonProperty(PropertyName = "message")] - public string Message { get; set; } + public string Message { get; } [JsonProperty(PropertyName = "data")] - public object Data { get; set; } + public object Data { get; } [JsonIgnore] - public Exception InnerException { get; set; } + public Exception InnerException { get; } } } diff --git a/src/Miningcore/JsonRpc/JsonRpcResponse.cs b/src/Miningcore/JsonRpc/JsonRpcResponse.cs index 7609a57b0..14c40e16d 100644 --- a/src/Miningcore/JsonRpc/JsonRpcResponse.cs +++ b/src/Miningcore/JsonRpc/JsonRpcResponse.cs @@ -14,7 +14,7 @@ public JsonRpcResponse(object result, object id = null) : base(result, id) { } - public JsonRpcResponse(JsonRpcException ex, object id = null, object result = null) : base(ex, id, result) + public JsonRpcResponse(JsonRpcError ex, object id = null, object result = null) : base(ex, id, result) { } } @@ -35,7 +35,7 @@ public JsonRpcResponse(T result, object id = null) Id = id; } - public JsonRpcResponse(JsonRpcException ex, object id, object result) + public JsonRpcResponse(JsonRpcError ex, object id, object result) { Error = ex; Id = id; @@ -51,7 +51,7 @@ public JsonRpcResponse(JsonRpcException ex, object id, object result) public object Result { get; set; } [JsonProperty(PropertyName = "error")] - public JsonRpcException Error { get; set; } + public JsonRpcError Error { get; set; } [JsonProperty(PropertyName = "id", NullValueHandling = NullValueHandling.Ignore)] public object Id { get; set; } diff --git a/src/Miningcore/DaemonInterface/DaemonClient.cs b/src/Miningcore/JsonRpc/RpcClient.cs similarity index 60% rename from src/Miningcore/DaemonInterface/DaemonClient.cs rename to src/Miningcore/JsonRpc/RpcClient.cs index 6e39947ff..7c7591a18 100644 --- a/src/Miningcore/DaemonInterface/DaemonClient.cs +++ b/src/Miningcore/JsonRpc/RpcClient.cs @@ -14,7 +14,6 @@ using System.Threading.Tasks; using Miningcore.Configuration; using Miningcore.Extensions; -using Miningcore.JsonRpc; using Miningcore.Messaging; using Miningcore.Notifications.Messages; using Miningcore.Util; @@ -24,19 +23,20 @@ using ZeroMQ; using Contract = Miningcore.Contracts.Contract; -namespace Miningcore.DaemonInterface +namespace Miningcore.JsonRpc { /// <summary> - /// Provides JsonRpc based interface to a cluster of blockchain daemons for improved fault tolerance + /// JsonRpc interface to blockchain node /// </summary> - public class DaemonClient + public class RpcClient { - public DaemonClient(JsonSerializerSettings serializerSettings, IMessageBus messageBus, string server, string poolId) + public RpcClient(DaemonEndpointConfig endPoint, JsonSerializerSettings serializerSettings, IMessageBus messageBus, string poolId) { Contract.RequiresNonNull(serializerSettings, nameof(serializerSettings)); Contract.RequiresNonNull(messageBus, nameof(messageBus)); Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(poolId), $"{nameof(poolId)} must not be empty"); + this.endPoint = endPoint; this.serializerSettings = serializerSettings; this.messageBus = messageBus; this.poolId = poolId; @@ -49,7 +49,7 @@ public DaemonClient(JsonSerializerSettings serializerSettings, IMessageBus messa private readonly JsonSerializerSettings serializerSettings; - protected DaemonEndpointConfig[] endPoints; + protected readonly DaemonEndpointConfig endPoint; private readonly JsonSerializer serializer; private static readonly HttpClient httpClient = new(new HttpClientHandler @@ -71,153 +71,62 @@ protected void PublishTelemetry(TelemetryCategory cat, TimeSpan elapsed, string #region API-Surface - public void Configure(DaemonEndpointConfig[] endPoints) - { - Contract.RequiresNonNull(endPoints, nameof(endPoints)); - Contract.Requires<ArgumentException>(endPoints.Length > 0, $"{nameof(endPoints)} must not be empty"); - - this.endPoints = endPoints; - } - - /// <summary> - /// Executes the request against all configured demons and returns their responses as an array - /// </summary> - public Task<DaemonResponse<JToken>[]> ExecuteCmdAllAsync(ILogger logger, string method, CancellationToken ct) - { - return ExecuteCmdAllAsync<JToken>(logger, method, ct); - } - - /// <summary> - /// Executes the request against all configured demons and returns their responses as an array - /// </summary> - public async Task<DaemonResponse<TResponse>[]> ExecuteCmdAllAsync<TResponse>(ILogger logger, string method, CancellationToken ct, - object payload = null, JsonSerializerSettings payloadJsonSerializerSettings = null) + public async Task<RpcResponse<TResponse>> ExecuteAsync<TResponse>(ILogger logger, string method, CancellationToken ct, + object payload = null, bool throwOnError = false) where TResponse : class { Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(method), $"{nameof(method)} must not be empty"); - logger.LogInvoke(new object[] { "\"" + method + "\"" }); - - var tasks = endPoints.Select(endPoint => BuildRequestTask(logger, endPoint, method, payload, CancellationToken.None, payloadJsonSerializerSettings)).ToArray(); + logger.LogInvoke(new object[] { method }); try { - await Task.WhenAll(tasks); + var response = await RequestAsync(logger, ct, endPoint, method, payload); + + if(response.Result is JToken token) + return new RpcResponse<TResponse>(token.ToObject<TResponse>(serializer), response.Error); + + return new RpcResponse<TResponse>((TResponse) response.Result, response.Error); } - catch(Exception) + catch(TaskCanceledException) { - // ignored + return new RpcResponse<TResponse>(null, new JsonRpcError(-500, "Cancelled", null)); } - var results = tasks.Select((x, i) => MapDaemonResponse<TResponse>(i, x)) - .ToArray(); - - return results; - } - - /// <summary> - /// Executes the request against all configured demons and returns the first successful response - /// </summary> - public Task<DaemonResponse<JToken>> ExecuteCmdAnyAsync(ILogger logger, string method, CancellationToken ct, bool throwOnError = false) - { - return ExecuteCmdAnyAsync<JToken>(logger, method, ct, null, null, throwOnError); - } - - /// <summary> - /// Executes the request against all configured demons and returns the first successful response - /// </summary> - public async Task<DaemonResponse<TResponse>> ExecuteCmdAnyAsync<TResponse>(ILogger logger, string method, CancellationToken ct, object payload = null, - JsonSerializerSettings payloadJsonSerializerSettings = null, bool throwOnError = false) - where TResponse : class - { - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(method), $"{nameof(method)} must not be empty"); - - logger.LogInvoke(new object[] { "\"" + method + "\"" }); - - var tasks = endPoints.Select(endPoint => BuildRequestTask(logger, endPoint, method, payload, CancellationToken.None, payloadJsonSerializerSettings)).ToArray(); - - var taskFirstCompleted = await Task.WhenAny(tasks); - var result = MapDaemonResponse<TResponse>(0, taskFirstCompleted, throwOnError); - return result; - } - - /// <summary> - /// Executes the request against all configured demons and returns the first successful response - /// </summary> - public async Task<DaemonResponse<TResponse>> ExecuteCmdAnyAsync<TResponse>(ILogger logger, CancellationToken ct, string method, object payload = null, - JsonSerializerSettings payloadJsonSerializerSettings = null, bool throwOnError = false) - where TResponse : class - { - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(method), $"{nameof(method)} must not be empty"); - - logger.LogInvoke(new object[] { "\"" + method + "\"" }); - - var tasks = endPoints.Select(endPoint => BuildRequestTask(logger, endPoint, method, payload, ct, payloadJsonSerializerSettings)).ToArray(); + catch(Exception ex) + { + if(throwOnError) + throw; - var taskFirstCompleted = await Task.WhenAny(tasks); - var result = MapDaemonResponse<TResponse>(0, taskFirstCompleted, throwOnError); - return result; + return new RpcResponse<TResponse>(null, new JsonRpcError(-500, ex.Message, null, ex)); + } } - /// <summary> - /// Executes the request against all configured demons and returns the first successful response - /// </summary> - public Task<DaemonResponse<JToken>> ExecuteCmdSingleAsync(ILogger logger, string method, CancellationToken ct) + public Task<RpcResponse<JToken>> ExecuteAsync(ILogger logger, string method, CancellationToken ct, bool throwOnError = false) { - return ExecuteCmdAnyAsync<JToken>(logger, method, ct); + return ExecuteAsync<JToken>(logger, method, ct, null, throwOnError); } - /// <summary> - /// Executes the request against all configured demons and returns the first successful response - /// </summary> - public async Task<DaemonResponse<TResponse>> ExecuteCmdSingleAsync<TResponse>(ILogger logger, string method, CancellationToken ct, object payload = null, - JsonSerializerSettings payloadJsonSerializerSettings = null) - where TResponse : class - { - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(method), $"{nameof(method)} must not be empty"); - - logger.LogInvoke(new object[] { "\"" + method + "\"" }); - - var task = BuildRequestTask(logger, endPoints.First(), method, payload, CancellationToken.None, payloadJsonSerializerSettings); - await Task.WhenAny(task); - - var result = MapDaemonResponse<TResponse>(0, task); - return result; - } - - /// <summary> - /// Executes the request against all configured demons and returns the first successful response - /// </summary> - public async Task<DaemonResponse<TResponse>> ExecuteCmdSingleAsync<TResponse>(ILogger logger, CancellationToken ct, string method, object payload = null, - JsonSerializerSettings payloadJsonSerializerSettings = null) - where TResponse : class - { - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(method), $"{nameof(method)} must not be empty"); - - logger.LogInvoke(new object[] { "\"" + method + "\"" }); - - var task = BuildRequestTask(logger, endPoints.First(), method, payload, ct, payloadJsonSerializerSettings); - await Task.WhenAny(task); - - var result = MapDaemonResponse<TResponse>(0, task); - return result; - } - - /// <summary> - /// Executes the requests against all configured demons and returns the first successful response array - /// </summary> - public async Task<DaemonResponse<JToken>[]> ExecuteBatchAnyAsync(ILogger logger, CancellationToken ct, params DaemonCmd[] batch) + public async Task<RpcResponse<JToken>[]> ExecuteBatchAsync(ILogger logger, CancellationToken ct, params RpcRequest[] batch) { Contract.RequiresNonNull(batch, nameof(batch)); - logger.LogInvoke(batch.Select(x => "\"" + x.Method + "\"").ToArray()); + logger.LogInvoke(string.Join(", ", batch.Select(x=> x.Method))); + + try + { + var response = await BatchRequestAsync(logger, ct, endPoint, batch); - var tasks = endPoints.Select(endPoint => BuildBatchRequestTask(logger, ct, endPoint, batch)).ToArray(); + return response + .Select(x => new RpcResponse<JToken>(x.Result != null ? JToken.FromObject(x.Result) : null, x.Error)) + .ToArray(); + } - var taskFirstCompleted = await Task.WhenAny(tasks); - var result = MapDaemonBatchResponse(0, taskFirstCompleted); - return result; + catch(Exception ex) + { + return Enumerable.Repeat(new RpcResponse<JToken>(null, new JsonRpcError(-500, ex.Message, null, ex)), batch.Length).ToArray(); + } } public IObservable<byte[]> WebsocketSubscribe(ILogger logger, CancellationToken ct, Dictionary<DaemonEndpointConfig, @@ -246,8 +155,7 @@ public IObservable<ZMessage> ZmqSubscribe(ILogger logger, CancellationToken ct, #endregion // API-Surface - private async Task<JsonRpcResponse> BuildRequestTask(ILogger logger, DaemonEndpointConfig endPoint, string method, object payload, - CancellationToken ct, JsonSerializerSettings payloadJsonSerializerSettings = null) + private async Task<JsonRpcResponse> RequestAsync(ILogger logger, CancellationToken ct, DaemonEndpointConfig endPoint, string method, object payload) { var rpcRequestId = GetRequestId(); @@ -257,22 +165,22 @@ private async Task<JsonRpcResponse> BuildRequestTask(ILogger logger, DaemonEndpo // build rpc request var rpcRequest = new JsonRpcRequest<object>(method, payload, rpcRequestId); - // build request url - var protocol = (endPoint.Ssl || endPoint.Http2) ? "https" : "http"; + // build url + var protocol = (endPoint.Ssl || endPoint.Http2) ? Uri.UriSchemeHttps : Uri.UriSchemeHttp; var requestUrl = $"{protocol}://{endPoint.Host}:{endPoint.Port}"; if(!string.IsNullOrEmpty(endPoint.HttpPath)) requestUrl += $"{(endPoint.HttpPath.StartsWith("/") ? string.Empty : "/")}{endPoint.HttpPath}"; - // build http request + // build request using(var request = new HttpRequestMessage(HttpMethod.Post, requestUrl)) { - request.Headers.ConnectionClose = false; // enable keep-alive - if(endPoint.Http2) request.Version = new Version(2, 0); + else + request.Headers.ConnectionClose = false; // enable keep-alive - // build request content - var json = JsonConvert.SerializeObject(rpcRequest, payloadJsonSerializerSettings ?? serializerSettings); + // build content + var json = JsonConvert.SerializeObject(rpcRequest, serializerSettings); request.Content = new StringContent(json, Encoding.UTF8, "application/json"); // build auth header @@ -306,7 +214,7 @@ private async Task<JsonRpcResponse> BuildRequestTask(ILogger logger, DaemonEndpo } } - private async Task<JsonRpcResponse<JToken>[]> BuildBatchRequestTask(ILogger logger, CancellationToken ct, DaemonEndpointConfig endPoint, DaemonCmd[] batch) + private async Task<JsonRpcResponse<JToken>[]> BatchRequestAsync(ILogger logger, CancellationToken ct, DaemonEndpointConfig endPoint, RpcRequest[] batch) { // telemetry var sw = Stopwatch.StartNew(); @@ -314,19 +222,19 @@ private async Task<JsonRpcResponse<JToken>[]> BuildBatchRequestTask(ILogger logg // build rpc request var rpcRequests = batch.Select(x => new JsonRpcRequest<object>(x.Method, x.Payload, GetRequestId())); - // build request url - var protocol = (endPoint.Ssl || endPoint.Http2) ? "https" : "http"; + // build url + var protocol = (endPoint.Ssl || endPoint.Http2) ? Uri.UriSchemeHttps : Uri.UriSchemeHttp; var requestUrl = $"{protocol}://{endPoint.Host}:{endPoint.Port}"; if(!string.IsNullOrEmpty(endPoint.HttpPath)) requestUrl += $"{(endPoint.HttpPath.StartsWith("/") ? string.Empty : "/")}{endPoint.HttpPath}"; - // build http request + // build request using(var request = new HttpRequestMessage(HttpMethod.Post, requestUrl)) { - request.Headers.ConnectionClose = false; // enable keep-alive - if(endPoint.Http2) request.Version = new Version(2, 0); + else + request.Headers.ConnectionClose = false; // enable keep-alive // build request content var json = JsonConvert.SerializeObject(rpcRequests, serializerSettings); @@ -368,68 +276,6 @@ protected string GetRequestId() return rpcRequestId; } - private DaemonResponse<TResponse> MapDaemonResponse<TResponse>(int i, Task<JsonRpcResponse> x, bool throwOnError = false) - where TResponse : class - { - var resp = new DaemonResponse<TResponse> - { - Instance = endPoints[i] - }; - - if(x.IsFaulted) - { - Exception inner; - - if(x.Exception.InnerExceptions.Count == 1) - inner = x.Exception.InnerException; - else - inner = x.Exception; - - if(throwOnError) - throw inner; - - resp.Error = new JsonRpcException(-500, x.Exception.Message, null, inner); - } - - else if(x.IsCanceled) - { - resp.Error = new JsonRpcException(-500, "Cancelled", null); - } - - else - { - Debug.Assert(x.IsCompletedSuccessfully); - - if(x.Result?.Result is JToken token) - resp.Response = token?.ToObject<TResponse>(serializer); - else - resp.Response = (TResponse) x.Result?.Result; - - resp.Error = x.Result?.Error; - } - - return resp; - } - - private DaemonResponse<JToken>[] MapDaemonBatchResponse(int i, Task<JsonRpcResponse<JToken>[]> x) - { - if(x.IsFaulted) - return x.Result?.Select(y => new DaemonResponse<JToken> - { - Instance = endPoints[i], - Error = new JsonRpcException(-500, x.Exception.Message, null) - }).ToArray(); - - Debug.Assert(x.IsCompletedSuccessfully); - - return x.Result?.Select(y => new DaemonResponse<JToken> - { - Instance = endPoints[i], - Response = y.Result != null ? JToken.FromObject(y.Result) : null, - Error = y.Error - }).ToArray(); - } - private IObservable<byte[]> WebsocketSubscribeEndpoint(ILogger logger, CancellationToken ct, NetworkEndpointConfig endPoint, (int Port, string HttpPath, bool Ssl) conf, string method, object payload = null, JsonSerializerSettings payloadJsonSerializerSettings = null) diff --git a/src/Miningcore/JsonRpc/RpcRequest.cs b/src/Miningcore/JsonRpc/RpcRequest.cs new file mode 100644 index 000000000..e1b15d13c --- /dev/null +++ b/src/Miningcore/JsonRpc/RpcRequest.cs @@ -0,0 +1,19 @@ +namespace Miningcore.JsonRpc +{ + public record RpcRequest + { + public RpcRequest(string method) + { + Method = method; + } + + public RpcRequest(string method, object payload) + { + Method = method; + Payload = payload; + } + + public string Method { get; } + public object Payload { get; } + } +} diff --git a/src/Miningcore/JsonRpc/RpcResponse.cs b/src/Miningcore/JsonRpc/RpcResponse.cs new file mode 100644 index 000000000..c5559bdaa --- /dev/null +++ b/src/Miningcore/JsonRpc/RpcResponse.cs @@ -0,0 +1,6 @@ +using Miningcore.Configuration; + +namespace Miningcore.JsonRpc +{ + public record RpcResponse<T>(T Response, JsonRpcError Error = null); +} diff --git a/src/Miningcore/Payments/PaymentSchemes/PPLNSPaymentScheme.cs b/src/Miningcore/Payments/PaymentSchemes/PPLNSPaymentScheme.cs index f8c7d44d9..00b12ce96 100644 --- a/src/Miningcore/Payments/PaymentSchemes/PPLNSPaymentScheme.cs +++ b/src/Miningcore/Payments/PaymentSchemes/PPLNSPaymentScheme.cs @@ -156,7 +156,7 @@ private async Task LogDiscardedSharesAsync(PoolConfig poolConfig, Block block, D #endregion // IPayoutScheme - private async Task<DateTime?> CalculateRewardsAsync(IMiningPool pool, IPayoutHandler payoutHandler, decimal window, Block block, decimal blockReward, + private async Task<DateTime?> CalculateRewardsAsync(IMiningPool pool, IPayoutHandler payoutHandler,decimal window, Block block, decimal blockReward, Dictionary<string, double> shares, Dictionary<string, decimal> rewards, CancellationToken ct) { var poolConfig = pool.Config; @@ -169,7 +169,7 @@ private async Task LogDiscardedSharesAsync(PoolConfig poolConfig, Block block, D var blockRewardRemaining = blockReward; DateTime? shareCutOffDate = null; - while(!done && ct.IsCancellationRequested) + while(!done && !ct.IsCancellationRequested) { logger.Info(() => $"Fetching page {currentPage} of shares for pool {poolConfig.Id}, block {block.BlockHeight}"); diff --git a/src/Miningcore/Stratum/StratumConnection.cs b/src/Miningcore/Stratum/StratumConnection.cs index 1e3378b09..b73976b8e 100644 --- a/src/Miningcore/Stratum/StratumConnection.cs +++ b/src/Miningcore/Stratum/StratumConnection.cs @@ -188,7 +188,7 @@ public ValueTask RespondAsync<T>(T payload, object id) public ValueTask RespondErrorAsync(StratumError code, string message, object id, object result = null, object data = null) { - return RespondAsync(new JsonRpcResponse(new JsonRpcException((int) code, message, null), id, result)); + return RespondAsync(new JsonRpcResponse(new JsonRpcError((int) code, message, null), id, result)); } public ValueTask RespondAsync<T>(JsonRpcResponse<T> response) From cebd31315e2bf7fdeb2e6d7c2068ab930e1c9f8b Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Sun, 18 Jul 2021 14:42:34 +0200 Subject: [PATCH 088/145] Long overdue JsonRpc overhaul. Multiple daemons are no longer used even if configured. --- .../Blockchain/Bitcoin/BitcoinJobManager.cs | 12 +- .../Bitcoin/BitcoinJobManagerBase.cs | 97 +++---- .../Bitcoin/BitcoinPayoutHandler.cs | 17 +- .../Cryptonote/CryptonoteJobManager.cs | 77 ++---- .../Cryptonote/CryptonotePayoutHandler.cs | 42 ++- .../ZCashAsyncOperationStatus.cs | 2 +- .../Blockchain/Equihash/EquihashJobManager.cs | 23 +- .../Equihash/EquihashPayoutHandler.cs | 68 ++--- .../Blockchain/Ethereum/EthereumJobManager.cs | 119 +++----- .../Ethereum/EthereumPayoutHandler.cs | 50 ++-- .../DaemonInterface/DaemonClientException.cs | 19 -- src/Miningcore/DaemonInterface/DaemonCmd.cs | 23 -- .../DaemonInterface/DaemonResponse.cs | 12 - .../{JsonRpcException.cs => JsonRpcError.cs} | 12 +- src/Miningcore/JsonRpc/JsonRpcResponse.cs | 6 +- .../DaemonClient.cs => JsonRpc/RpcClient.cs} | 260 ++++-------------- src/Miningcore/JsonRpc/RpcRequest.cs | 19 ++ src/Miningcore/JsonRpc/RpcResponse.cs | 6 + .../PaymentSchemes/PPLNSPaymentScheme.cs | 4 +- src/Miningcore/Stratum/StratumConnection.cs | 2 +- 20 files changed, 303 insertions(+), 567 deletions(-) delete mode 100644 src/Miningcore/DaemonInterface/DaemonClientException.cs delete mode 100644 src/Miningcore/DaemonInterface/DaemonCmd.cs delete mode 100644 src/Miningcore/DaemonInterface/DaemonResponse.cs rename src/Miningcore/JsonRpc/{JsonRpcException.cs => JsonRpcError.cs} (87%) rename src/Miningcore/{DaemonInterface/DaemonClient.cs => JsonRpc/RpcClient.cs} (60%) create mode 100644 src/Miningcore/JsonRpc/RpcRequest.cs create mode 100644 src/Miningcore/JsonRpc/RpcResponse.cs diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManager.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManager.cs index 315a781b1..9b48813a2 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManager.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManager.cs @@ -8,7 +8,6 @@ using Miningcore.Configuration; using Miningcore.Contracts; using Miningcore.Crypto; -using Miningcore.DaemonInterface; using Miningcore.Extensions; using Miningcore.JsonRpc; using Miningcore.Messaging; @@ -48,26 +47,23 @@ protected override object[] GetBlockTemplateParams() return result; } - protected async Task<DaemonResponse<BlockTemplate>> GetBlockTemplateAsync(CancellationToken ct) + protected async Task<RpcResponse<BlockTemplate>> GetBlockTemplateAsync(CancellationToken ct) { logger.LogInvoke(); - var result = await daemon.ExecuteCmdAnyAsync<BlockTemplate>(logger, + var result = await rpcClient.ExecuteAsync<BlockTemplate>(logger, BitcoinCommands.GetBlockTemplate, ct, extraPoolConfig?.GBTArgs ?? (object) GetBlockTemplateParams()); return result; } - protected DaemonResponse<BlockTemplate> GetBlockTemplateFromJson(string json) + protected RpcResponse<BlockTemplate> GetBlockTemplateFromJson(string json) { logger.LogInvoke(); var result = JsonConvert.DeserializeObject<JsonRpcResponse>(json); - return new DaemonResponse<BlockTemplate> - { - Response = result.ResultAs<BlockTemplate>(), - }; + return new RpcResponse<BlockTemplate>(result.ResultAs<BlockTemplate>()); } private BitcoinJob CreateJob() diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs index 897e55a85..c5df3e35c 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs @@ -12,8 +12,8 @@ using Miningcore.Blockchain.Bitcoin.DaemonResponses; using Miningcore.Configuration; using Miningcore.Contracts; -using Miningcore.DaemonInterface; using Miningcore.Extensions; +using Miningcore.JsonRpc; using Miningcore.Messaging; using Miningcore.Notifications.Messages; using Miningcore.Stratum; @@ -45,7 +45,7 @@ protected BitcoinJobManagerBase( } protected readonly IMasterClock clock; - protected DaemonClient daemon; + protected RpcClient rpcClient; protected readonly IExtraNonceProvider extraNonceProvider; protected const int ExtranonceBytes = 4; protected int maxActiveJobs = 4; @@ -99,7 +99,7 @@ protected virtual void SetupJobUpdates(CancellationToken ct) { logger.Info(() => $"Subscribing to ZMQ push-updates from {string.Join(", ", zmq.Values)}"); - var blockNotify = daemon.ZmqSubscribe(logger, ct, zmq) + var blockNotify = rpcClient.ZmqSubscribe(logger, ct, zmq) .Select(msg => { using(msg) @@ -207,20 +207,19 @@ protected virtual async Task ShowDaemonSyncProgressAsync(CancellationToken ct) return; } - var infos = await daemon.ExecuteCmdAllAsync<BlockchainInfo>(logger, BitcoinCommands.GetBlockchainInfo, ct); + var info = await rpcClient.ExecuteAsync<BlockchainInfo>(logger, BitcoinCommands.GetBlockchainInfo, ct); - if(infos.Length > 0) + if(info != null) { - var blockCount = infos - .Max(x => x.Response?.Blocks); + var blockCount = info.Response?.Blocks; if(blockCount.HasValue) { // get list of peers and their highest block height to compare to ours - var peerInfo = await daemon.ExecuteCmdAnyAsync<PeerInfo[]>(logger, BitcoinCommands.GetPeerInfo, ct); + var peerInfo = await rpcClient.ExecuteAsync<PeerInfo[]>(logger, BitcoinCommands.GetPeerInfo, ct); var peers = peerInfo.Response; - if(peers != null && peers.Length > 0) + if(peers is {Length: > 0}) { var totalBlocks = peers.Max(x => x.StartingHeight); var percent = totalBlocks > 0 ? (double) blockCount / totalBlocks * 100 : 0; @@ -236,10 +235,10 @@ private async Task UpdateNetworkStatsAsync(CancellationToken ct) try { - var results = await daemon.ExecuteBatchAnyAsync(logger, ct, - new DaemonCmd(BitcoinCommands.GetMiningInfo), - new DaemonCmd(BitcoinCommands.GetNetworkInfo), - new DaemonCmd(BitcoinCommands.GetNetworkHashPS) + var results = await rpcClient.ExecuteBatchAsync(logger, ct, + new RpcRequest(BitcoinCommands.GetMiningInfo), + new RpcRequest(BitcoinCommands.GetNetworkInfo), + new RpcRequest(BitcoinCommands.GetNetworkHashPS) ); if(results.Any(x => x.Error != null)) @@ -270,11 +269,11 @@ private async Task UpdateNetworkStatsAsync(CancellationToken ct) protected virtual async Task<(bool Accepted, string CoinbaseTx)> SubmitBlockAsync(Share share, string blockHex) { // execute command batch - var results = await daemon.ExecuteBatchAnyAsync(logger, CancellationToken.None, + var results = await rpcClient.ExecuteBatchAsync(logger, CancellationToken.None, hasSubmitBlockMethod - ? new DaemonCmd(BitcoinCommands.SubmitBlock, new[] { blockHex }) - : new DaemonCmd(BitcoinCommands.GetBlockTemplate, new { mode = "submit", data = blockHex }), - new DaemonCmd(BitcoinCommands.GetBlock, new[] { share.BlockHash })); + ? new RpcRequest(BitcoinCommands.SubmitBlock, new[] { blockHex }) + : new RpcRequest(BitcoinCommands.GetBlockTemplate, new { mode = "submit", data = blockHex }), + new RpcRequest(BitcoinCommands.GetBlock, new[] { share.BlockHash })); // did submission succeed? var submitResult = results[0]; @@ -305,36 +304,30 @@ private async Task UpdateNetworkStatsAsync(CancellationToken ct) protected virtual async Task<bool> AreDaemonsHealthyLegacyAsync(CancellationToken ct) { - var responses = await daemon.ExecuteCmdAllAsync<DaemonInfo>(logger, BitcoinCommands.GetInfo, ct); - - if(responses.Where(x => x.Error?.InnerException?.GetType() == typeof(DaemonClientException)) - .Select(x => (DaemonClientException) x.Error.InnerException) - .Any(x => x.Code == HttpStatusCode.Unauthorized)) - logger.ThrowLogPoolStartupException("Daemon reports invalid credentials"); + var response = await rpcClient.ExecuteAsync<DaemonInfo>(logger, BitcoinCommands.GetInfo, ct); - return responses.All(x => x.Error == null); + return response.Error == null; } protected virtual async Task<bool> AreDaemonsConnectedLegacyAsync(CancellationToken ct) { - var response = await daemon.ExecuteCmdAnyAsync<DaemonInfo>(logger, BitcoinCommands.GetInfo, ct); + var response = await rpcClient.ExecuteAsync<DaemonInfo>(logger, BitcoinCommands.GetInfo, ct); return response.Error == null && response.Response.Connections > 0; } protected virtual async Task ShowDaemonSyncProgressLegacyAsync(CancellationToken ct) { - var infos = await daemon.ExecuteCmdAllAsync<DaemonInfo>(logger, BitcoinCommands.GetInfo, ct); + var info = await rpcClient.ExecuteAsync<DaemonInfo>(logger, BitcoinCommands.GetInfo, ct); - if(infos.Length > 0) + if(info != null) { - var blockCount = infos - .Max(x => x.Response?.Blocks); + var blockCount = info.Response?.Blocks; if(blockCount.HasValue) { // get list of peers and their highest block height to compare to ours - var peerInfo = await daemon.ExecuteCmdAnyAsync<PeerInfo[]>(logger, BitcoinCommands.GetPeerInfo, ct); + var peerInfo = await rpcClient.ExecuteAsync<PeerInfo[]>(logger, BitcoinCommands.GetPeerInfo, ct); var peers = peerInfo.Response; if(peers != null && peers.Length > 0) @@ -353,8 +346,8 @@ private async Task UpdateNetworkStatsLegacyAsync(CancellationToken ct) try { - var results = await daemon.ExecuteBatchAnyAsync(logger, ct, - new DaemonCmd(BitcoinCommands.GetConnectionCount) + var results = await rpcClient.ExecuteBatchAsync(logger, ct, + new RpcRequest(BitcoinCommands.GetConnectionCount) ); if(results.Any(x => x.Error != null)) @@ -385,8 +378,7 @@ protected override void ConfigureDaemons() { var jsonSerializerSettings = ctx.Resolve<JsonSerializerSettings>(); - daemon = new DaemonClient(jsonSerializerSettings, messageBus, clusterConfig.ClusterName ?? poolConfig.PoolName, poolConfig.Id); - daemon.Configure(poolConfig.Daemons); + rpcClient = new RpcClient(poolConfig.Daemons.First(), jsonSerializerSettings, messageBus, poolConfig.Id); } protected override async Task<bool> AreDaemonsHealthyAsync(CancellationToken ct) @@ -394,14 +386,9 @@ protected override async Task<bool> AreDaemonsHealthyAsync(CancellationToken ct) if(hasLegacyDaemon) return await AreDaemonsHealthyLegacyAsync(ct); - var responses = await daemon.ExecuteCmdAllAsync<BlockchainInfo>(logger, BitcoinCommands.GetBlockchainInfo, ct); - - if(responses.Where(x => x.Error?.InnerException?.GetType() == typeof(DaemonClientException)) - .Select(x => (DaemonClientException) x.Error.InnerException) - .Any(x => x.Code == HttpStatusCode.Unauthorized)) - logger.ThrowLogPoolStartupException("Daemon reports invalid credentials"); + var response = await rpcClient.ExecuteAsync<BlockchainInfo>(logger, BitcoinCommands.GetBlockchainInfo, ct); - return responses.All(x => x.Error == null); + return response.Error == null; } protected override async Task<bool> AreDaemonsConnectedAsync(CancellationToken ct) @@ -409,7 +396,7 @@ protected override async Task<bool> AreDaemonsConnectedAsync(CancellationToken c if(hasLegacyDaemon) return await AreDaemonsConnectedLegacyAsync(ct); - var response = await daemon.ExecuteCmdAnyAsync<NetworkInfo>(logger, BitcoinCommands.GetNetworkInfo, ct); + var response = await rpcClient.ExecuteAsync<NetworkInfo>(logger, BitcoinCommands.GetNetworkInfo, ct); return response.Error == null && response.Response?.Connections > 0; } @@ -420,10 +407,10 @@ protected override async Task EnsureDaemonsSynchedAsync(CancellationToken ct) while(true) { - var responses = await daemon.ExecuteCmdAllAsync<BlockTemplate>(logger, + var response = await rpcClient.ExecuteAsync<BlockTemplate>(logger, BitcoinCommands.GetBlockTemplate, ct, GetBlockTemplateParams()); - var isSynched = responses.All(x => x.Error == null); + var isSynched = response.Error == null; if(isSynched) { @@ -446,16 +433,16 @@ protected override async Task EnsureDaemonsSynchedAsync(CancellationToken ct) protected override async Task PostStartInitAsync(CancellationToken ct) { - var commands = new[] + var requests = new[] { - new DaemonCmd(BitcoinCommands.ValidateAddress, new[] { poolConfig.Address }), - new DaemonCmd(BitcoinCommands.SubmitBlock), - new DaemonCmd(!hasLegacyDaemon ? BitcoinCommands.GetBlockchainInfo : BitcoinCommands.GetInfo), - new DaemonCmd(BitcoinCommands.GetDifficulty), - new DaemonCmd(BitcoinCommands.GetAddressInfo, new[] { poolConfig.Address }), + new RpcRequest(BitcoinCommands.ValidateAddress, new[] { poolConfig.Address }), + new RpcRequest(BitcoinCommands.SubmitBlock), + new RpcRequest(!hasLegacyDaemon ? BitcoinCommands.GetBlockchainInfo : BitcoinCommands.GetInfo), + new RpcRequest(BitcoinCommands.GetDifficulty), + new RpcRequest(BitcoinCommands.GetAddressInfo, new[] { poolConfig.Address }), }; - var results = await daemon.ExecuteBatchAnyAsync(logger, ct, commands); + var results = await rpcClient.ExecuteBatchAsync(logger, ct, requests); if(results.Any(x => x.Error != null)) { @@ -463,8 +450,9 @@ protected override async Task PostStartInitAsync(CancellationToken ct) // filter out optional RPCs var errors = results - .Where(x => x.Error != null && commands[resultList.IndexOf(x)].Method != BitcoinCommands.SubmitBlock) - .Where(x => x.Error != null && commands[resultList.IndexOf(x)].Method != BitcoinCommands.GetAddressInfo) + .Where((x, i) => x.Error != null && + requests[i].Method != BitcoinCommands.SubmitBlock && + requests[i].Method != BitcoinCommands.GetAddressInfo) .ToArray(); if(errors.Any()) @@ -612,8 +600,7 @@ public virtual async Task<bool> ValidateAddressAsync(string address, Cancellatio if(string.IsNullOrEmpty(address)) return false; - var result = await daemon.ExecuteCmdAnyAsync<ValidateAddressResponse>(logger, ct, - BitcoinCommands.ValidateAddress, new[] { address }); + var result = await rpcClient.ExecuteAsync<ValidateAddressResponse>(logger, BitcoinCommands.ValidateAddress, ct, new[] { address }); return result.Response is {IsValid: true}; } diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs index e41bbaf42..01773d7b1 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs @@ -8,8 +8,8 @@ using Miningcore.Blockchain.Bitcoin.Configuration; using Miningcore.Blockchain.Bitcoin.DaemonResponses; using Miningcore.Configuration; -using Miningcore.DaemonInterface; using Miningcore.Extensions; +using Miningcore.JsonRpc; using Miningcore.Messaging; using Miningcore.Mining; using Miningcore.Payments; @@ -49,7 +49,7 @@ public BitcoinPayoutHandler( } protected readonly IComponentContext ctx; - protected DaemonClient daemon; + protected RpcClient rpcClient; protected BitcoinDaemonEndpointConfigExtra extraPoolConfig; protected BitcoinPoolPaymentProcessingConfigExtra extraPoolPaymentProcessingConfig; @@ -70,8 +70,7 @@ public virtual Task ConfigureAsync(ClusterConfig clusterConfig, PoolConfig poolC logger = LogUtil.GetPoolScopedLogger(typeof(BitcoinPayoutHandler), poolConfig); var jsonSerializerSettings = ctx.Resolve<JsonSerializerSettings>(); - daemon = new DaemonClient(jsonSerializerSettings, messageBus, clusterConfig.ClusterName ?? poolConfig.PoolName, poolConfig.Id); - daemon.Configure(poolConfig.Daemons); + rpcClient = new RpcClient(poolConfig.Daemons.First(), jsonSerializerSettings, messageBus, poolConfig.Id); return Task.FromResult(true); } @@ -101,11 +100,11 @@ public virtual async Task<Block[]> ClassifyBlocksAsync(IMiningPool pool, Block[] .ToArray(); // build command batch (block.TransactionConfirmationData is the hash of the blocks coinbase transaction) - var batch = page.Select(block => new DaemonCmd(BitcoinCommands.GetTransaction, + var batch = page.Select(block => new RpcRequest(BitcoinCommands.GetTransaction, new[] { block.TransactionConfirmationData })).ToArray(); // execute batch - var results = await daemon.ExecuteBatchAnyAsync(logger, ct, batch); + var results = await rpcClient.ExecuteBatchAsync(logger, ct, batch); for(var j = 0; j < results.Length; j++) { @@ -264,7 +263,7 @@ public virtual async Task PayoutAsync(IMiningPool pool, Balance[] balances, Canc // send command tryTransfer: - var result = await daemon.ExecuteCmdSingleAsync<string>(logger, BitcoinCommands.SendMany, ct, args, new JsonSerializerSettings()); + var result = await rpcClient.ExecuteAsync<string>(logger, BitcoinCommands.SendMany, ct, args); if(result.Error == null) { @@ -272,7 +271,7 @@ public virtual async Task PayoutAsync(IMiningPool pool, Balance[] balances, Canc { // lock wallet logger.Info(() => $"[{LogCategory}] Locking wallet"); - await daemon.ExecuteCmdSingleAsync<JToken>(logger, BitcoinCommands.WalletLock, ct); + await rpcClient.ExecuteAsync<JToken>(logger, BitcoinCommands.WalletLock, ct); } // check result @@ -296,7 +295,7 @@ public virtual async Task PayoutAsync(IMiningPool pool, Balance[] balances, Canc { logger.Info(() => $"[{LogCategory}] Unlocking wallet"); - var unlockResult = await daemon.ExecuteCmdSingleAsync<JToken>(logger, BitcoinCommands.WalletPassphrase, ct, new[] + var unlockResult = await rpcClient.ExecuteAsync<JToken>(logger, BitcoinCommands.WalletPassphrase, ct, new[] { (object) extraPoolPaymentProcessingConfig.WalletPassword, (object) 5 // unlock for N seconds diff --git a/src/Miningcore/Blockchain/Cryptonote/CryptonoteJobManager.cs b/src/Miningcore/Blockchain/Cryptonote/CryptonoteJobManager.cs index 6976671a8..53e986455 100644 --- a/src/Miningcore/Blockchain/Cryptonote/CryptonoteJobManager.cs +++ b/src/Miningcore/Blockchain/Cryptonote/CryptonoteJobManager.cs @@ -16,7 +16,6 @@ using Miningcore.Blockchain.Cryptonote.DaemonResponses; using Miningcore.Blockchain.Cryptonote.StratumRequests; using Miningcore.Configuration; -using Miningcore.DaemonInterface; using Miningcore.Extensions; using Miningcore.JsonRpc; using Miningcore.Messaging; @@ -51,8 +50,8 @@ public CryptonoteJobManager( private byte[] instanceId; private DaemonEndpointConfig[] daemonEndpoints; - private DaemonClient daemon; - private DaemonClient walletDaemon; + private RpcClient rpcClient; + private RpcClient rpcClientWallet; private readonly IMasterClock clock; private CryptonoteNetworkType networkType; private CryptonotePoolConfigExtra extraPoolConfig; @@ -148,7 +147,7 @@ protected async Task<bool> UpdateJob(CancellationToken ct, string via = null, st return false; } - private async Task<DaemonResponse<GetBlockTemplateResponse>> GetBlockTemplateAsync(CancellationToken ct) + private async Task<RpcResponse<GetBlockTemplateResponse>> GetBlockTemplateAsync(CancellationToken ct) { logger.LogInvoke(); @@ -158,35 +157,31 @@ private async Task<DaemonResponse<GetBlockTemplateResponse>> GetBlockTemplateAsy ReserveSize = CryptonoteConstants.ReserveSize }; - return await daemon.ExecuteCmdAnyAsync<GetBlockTemplateResponse>(logger, CryptonoteCommands.GetBlockTemplate, ct, request); + return await rpcClient.ExecuteAsync<GetBlockTemplateResponse>(logger, CryptonoteCommands.GetBlockTemplate, ct, request); } - private DaemonResponse<GetBlockTemplateResponse> GetBlockTemplateFromJson(string json) + private RpcResponse<GetBlockTemplateResponse> GetBlockTemplateFromJson(string json) { logger.LogInvoke(); var result = JsonConvert.DeserializeObject<JsonRpcResponse>(json); - return new DaemonResponse<GetBlockTemplateResponse> - { - Response = result.ResultAs<GetBlockTemplateResponse>(), - }; + return new RpcResponse<GetBlockTemplateResponse>(result.ResultAs<GetBlockTemplateResponse>()); } private async Task ShowDaemonSyncProgressAsync(CancellationToken ct) { - var infos = await daemon.ExecuteCmdAllAsync<GetInfoResponse>(logger, CryptonoteCommands.GetInfo, ct); - var firstValidResponse = infos.FirstOrDefault(x => x.Error == null && x.Response != null)?.Response; + var response = await rpcClient.ExecuteAsync<GetInfoResponse>(logger, CryptonoteCommands.GetInfo, ct); + var info = response.Response; - if(firstValidResponse != null) + if(info != null) { - var lowestHeight = infos.Where(x => x.Error == null && x.Response != null) - .Min(x => x.Response.Height); + var lowestHeight = info.Height; - var totalBlocks = firstValidResponse.TargetHeight; + var totalBlocks = info.TargetHeight; var percent = (double) lowestHeight / totalBlocks * 100; - logger.Info(() => $"Daemons have downloaded {percent:0.00}% of blockchain from {firstValidResponse.OutgoingConnectionsCount} peers"); + logger.Info(() => $"Daemons have downloaded {percent:0.00}% of blockchain from {info.OutgoingConnectionsCount} peers"); } } @@ -196,14 +191,14 @@ private async Task UpdateNetworkStatsAsync(CancellationToken ct) try { - var infoResponse = await daemon.ExecuteCmdAnyAsync(logger, CryptonoteCommands.GetInfo, ct); + var response = await rpcClient.ExecuteAsync(logger, CryptonoteCommands.GetInfo, ct); - if(infoResponse.Error != null) - logger.Warn(() => $"Error(s) refreshing network stats: {infoResponse.Error.Message} (Code {infoResponse.Error.Code})"); + if(response.Error != null) + logger.Warn(() => $"Error(s) refreshing network stats: {response.Error.Message} (Code {response.Error.Code})"); - if(infoResponse.Response != null) + if(response.Response != null) { - var info = infoResponse.Response.ToObject<GetInfoResponse>(); + var info = response.Response.ToObject<GetInfoResponse>(); BlockchainStats.NetworkHashrate = info.Target > 0 ? (double) info.Difficulty / info.Target : 0; BlockchainStats.ConnectedPeers = info.OutgoingConnectionsCount + info.IncomingConnectionsCount; @@ -218,7 +213,7 @@ private async Task UpdateNetworkStatsAsync(CancellationToken ct) private async Task<bool> SubmitBlockAsync(Share share, string blobHex, string blobHash) { - var response = await daemon.ExecuteCmdAnyAsync<SubmitResponse>(logger, CryptonoteCommands.SubmitBlock, CancellationToken.None, new[] { blobHex }); + var response = await rpcClient.ExecuteAsync<SubmitResponse>(logger, CryptonoteCommands.SubmitBlock, CancellationToken.None, new[] { blobHex }); if(response.Error != null || response?.Response?.Status != "OK") { @@ -439,41 +434,29 @@ protected override void ConfigureDaemons() { var jsonSerializerSettings = ctx.Resolve<JsonSerializerSettings>(); - daemon = new DaemonClient(jsonSerializerSettings, messageBus, clusterConfig.ClusterName ?? poolConfig.PoolName, poolConfig.Id); - daemon.Configure(daemonEndpoints); + rpcClient = new RpcClient(daemonEndpoints.First(), jsonSerializerSettings, messageBus, poolConfig.Id); if(clusterConfig.PaymentProcessing?.Enabled == true && poolConfig.PaymentProcessing?.Enabled == true) { // also setup wallet daemon - walletDaemon = new DaemonClient(jsonSerializerSettings, messageBus, clusterConfig.ClusterName ?? poolConfig.PoolName, poolConfig.Id); - walletDaemon.Configure(walletDaemonEndpoints); + rpcClientWallet = new RpcClient(walletDaemonEndpoints.First(), jsonSerializerSettings, messageBus, poolConfig.Id); } } protected override async Task<bool> AreDaemonsHealthyAsync(CancellationToken ct) { // test daemons - var responses = await daemon.ExecuteCmdAllAsync<GetInfoResponse>(logger, CryptonoteCommands.GetInfo, ct); + var response = await rpcClient.ExecuteAsync<GetInfoResponse>(logger, CryptonoteCommands.GetInfo, ct); - if(responses.Where(x => x.Error?.InnerException?.GetType() == typeof(DaemonClientException)) - .Select(x => (DaemonClientException) x.Error.InnerException) - .Any(x => x.Code == HttpStatusCode.Unauthorized)) - logger.ThrowLogPoolStartupException("Daemon reports invalid credentials"); - - if(responses.Any(x => x.Error != null)) + if(response.Error != null) return false; if(clusterConfig.PaymentProcessing?.Enabled == true && poolConfig.PaymentProcessing?.Enabled == true) { // test wallet daemons - var responses2 = await walletDaemon.ExecuteCmdAllAsync<object>(logger, CryptonoteWalletCommands.GetAddress, ct); - - if(responses2.Where(x => x.Error?.InnerException?.GetType() == typeof(DaemonClientException)) - .Select(x => (DaemonClientException) x.Error.InnerException) - .Any(x => x.Code == HttpStatusCode.Unauthorized)) - logger.ThrowLogPoolStartupException("Wallet-Daemon reports invalid credentials"); + var responses2 = await rpcClientWallet.ExecuteAsync<object>(logger, CryptonoteWalletCommands.GetAddress, ct); - return responses2.All(x => x.Error == null); + return responses2.Error == null; } return true; @@ -481,7 +464,7 @@ protected override async Task<bool> AreDaemonsHealthyAsync(CancellationToken ct) protected override async Task<bool> AreDaemonsConnectedAsync(CancellationToken ct) { - var response = await daemon.ExecuteCmdAnyAsync<GetInfoResponse>(logger, CryptonoteCommands.GetInfo, ct); + var response = await rpcClient.ExecuteAsync<GetInfoResponse>(logger, CryptonoteCommands.GetInfo, ct); return response.Error == null && response.Response != null && (response.Response.OutgoingConnectionsCount + response.Response.IncomingConnectionsCount) > 0; @@ -499,10 +482,10 @@ protected override async Task EnsureDaemonsSynchedAsync(CancellationToken ct) ReserveSize = CryptonoteConstants.ReserveSize }; - var responses = await daemon.ExecuteCmdAllAsync<GetBlockTemplateResponse>(logger, + var response = await rpcClient.ExecuteAsync<GetBlockTemplateResponse>(logger, CryptonoteCommands.GetBlockTemplate, ct, request); - var isSynched = responses.All(x => x.Error == null || x.Error.Code != -9); + var isSynched = response.Error is not {Code: -9}; if(isSynched) { @@ -529,14 +512,14 @@ protected override async Task PostStartInitAsync(CancellationToken ct) // coin config var coin = poolConfig.Template.As<CryptonoteCoinTemplate>(); - var infoResponse = await daemon.ExecuteCmdAnyAsync(logger, CryptonoteCommands.GetInfo, ct); + var infoResponse = await rpcClient.ExecuteAsync(logger, CryptonoteCommands.GetInfo, ct); if(infoResponse.Error != null) logger.ThrowLogPoolStartupException($"Init RPC failed: {infoResponse.Error.Message} (Code {infoResponse.Error.Code})"); if(clusterConfig.PaymentProcessing?.Enabled == true && poolConfig.PaymentProcessing?.Enabled == true) { - var addressResponse = await walletDaemon.ExecuteCmdAnyAsync<GetAddressResponse>(logger, ct, CryptonoteWalletCommands.GetAddress); + var addressResponse = await rpcClientWallet.ExecuteAsync<GetAddressResponse>(logger, CryptonoteWalletCommands.GetAddress, ct); // ensure pool owns wallet if(clusterConfig.PaymentProcessing?.Enabled == true && addressResponse.Response?.Address != poolConfig.Address) @@ -669,7 +652,7 @@ protected virtual void SetupJobUpdates(CancellationToken ct) { logger.Info(() => $"Subscribing to ZMQ push-updates from {string.Join(", ", zmq.Values)}"); - var blockNotify = daemon.ZmqSubscribe(logger, ct, zmq) + var blockNotify = rpcClient.ZmqSubscribe(logger, ct, zmq) .Where(msg => { bool result = false; diff --git a/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs b/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs index a3259059e..cf9a0c120 100644 --- a/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs +++ b/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs @@ -10,8 +10,8 @@ using Miningcore.Blockchain.Cryptonote.DaemonRequests; using Miningcore.Blockchain.Cryptonote.DaemonResponses; using Miningcore.Configuration; -using Miningcore.DaemonInterface; using Miningcore.Extensions; +using Miningcore.JsonRpc; using Miningcore.Messaging; using Miningcore.Mining; using Miningcore.Native; @@ -52,15 +52,15 @@ public CryptonotePayoutHandler( } private readonly IComponentContext ctx; - private DaemonClient daemon; - private DaemonClient walletDaemon; + private RpcClient rpcClient; + private RpcClient rpcClientWallet; private CryptonoteNetworkType? networkType; private CryptonotePoolPaymentProcessingConfigExtra extraConfig; private bool walletSupportsTransferSplit; protected override string LogCategory => "Cryptonote Payout Handler"; - private async Task<bool> HandleTransferResponseAsync(DaemonResponse<TransferResponse> response, params Balance[] balances) + private async Task<bool> HandleTransferResponseAsync(RpcResponse<TransferResponse> response, params Balance[] balances) { var coin = poolConfig.Template.As<CryptonoteCoinTemplate>(); @@ -85,7 +85,7 @@ private async Task<bool> HandleTransferResponseAsync(DaemonResponse<TransferResp } } - private async Task<bool> HandleTransferResponseAsync(DaemonResponse<TransferSplitResponse> response, params Balance[] balances) + private async Task<bool> HandleTransferResponseAsync(RpcResponse<TransferSplitResponse> response, params Balance[] balances) { var coin = poolConfig.Template.As<CryptonoteCoinTemplate>(); @@ -114,7 +114,7 @@ private async Task UpdateNetworkTypeAsync(CancellationToken ct) { if(!networkType.HasValue) { - var infoResponse = await daemon.ExecuteCmdAnyAsync(logger, CNC.GetInfo, ct, true); + var infoResponse = await rpcClient.ExecuteAsync(logger, CNC.GetInfo, ct, true); var info = infoResponse.Response.ToObject<GetInfoResponse>(); // chain detection @@ -144,7 +144,7 @@ private async Task UpdateNetworkTypeAsync(CancellationToken ct) private async Task<bool> EnsureBalance(decimal requiredAmount, CryptonoteCoinTemplate coin, CancellationToken ct) { - var response = await walletDaemon.ExecuteCmdSingleAsync<GetBalanceResponse>(logger, CryptonoteWalletCommands.GetBalance, ct); + var response = await rpcClientWallet.ExecuteAsync<GetBalanceResponse>(logger, CryptonoteWalletCommands.GetBalance, ct); if(response.Error != null) { @@ -198,7 +198,7 @@ private async Task<bool> PayoutBatch(Balance[] balances, CancellationToken ct) logger.Info(() => $"[{LogCategory}] Paying {FormatAmount(balances.Sum(x => x.Amount))} to {balances.Length} addresses:\n{string.Join("\n", balances.OrderByDescending(x => x.Amount).Select(x => $"{FormatAmount(x.Amount)} to {x.Address}"))}"); // send command - var transferResponse = await walletDaemon.ExecuteCmdSingleAsync<TransferResponse>(logger, CryptonoteWalletCommands.Transfer, ct, request); + var transferResponse = await rpcClientWallet.ExecuteAsync<TransferResponse>(logger, CryptonoteWalletCommands.Transfer, ct, request); // gracefully handle error -4 (transaction would be too large. try /transfer_split) if(transferResponse.Error?.Code == -4) @@ -208,7 +208,7 @@ private async Task<bool> PayoutBatch(Balance[] balances, CancellationToken ct) logger.Error(() => $"[{LogCategory}] Daemon command '{CryptonoteWalletCommands.Transfer}' returned error: {transferResponse.Error.Message} code {transferResponse.Error.Code}"); logger.Info(() => $"[{LogCategory}] Retrying transfer using {CryptonoteWalletCommands.TransferSplit}"); - var transferSplitResponse = await walletDaemon.ExecuteCmdSingleAsync<TransferSplitResponse>(logger, CryptonoteWalletCommands.TransferSplit, ct, request); + var transferSplitResponse = await rpcClientWallet.ExecuteAsync<TransferSplitResponse>(logger, CryptonoteWalletCommands.TransferSplit, ct, request); return await HandleTransferResponseAsync(transferSplitResponse, balances); } @@ -275,7 +275,7 @@ private async Task<bool> PayoutToPaymentId(Balance balance, CancellationToken ct logger.Info(() => $"[{LogCategory}] Paying {FormatAmount(balance.Amount)} to integrated address {balance.Address}"); // send command - var result = await walletDaemon.ExecuteCmdSingleAsync<TransferResponse>(logger, CryptonoteWalletCommands.Transfer, ct, request); + var result = await rpcClientWallet.ExecuteAsync<TransferResponse>(logger, CryptonoteWalletCommands.Transfer, ct, request); if(walletSupportsTransferSplit) { @@ -284,7 +284,7 @@ private async Task<bool> PayoutToPaymentId(Balance balance, CancellationToken ct { logger.Info(() => $"[{LogCategory}] Retrying transfer using {CryptonoteWalletCommands.TransferSplit}"); - result = await walletDaemon.ExecuteCmdSingleAsync<TransferResponse>(logger, CryptonoteWalletCommands.TransferSplit, ct, request); + result = await rpcClientWallet.ExecuteAsync<TransferResponse>(logger, CryptonoteWalletCommands.TransferSplit, ct, request); } } @@ -317,8 +317,7 @@ public async Task ConfigureAsync(ClusterConfig clusterConfig, PoolConfig poolCon }) .ToArray(); - daemon = new DaemonClient(jsonSerializerSettings, messageBus, clusterConfig.ClusterName ?? poolConfig.PoolName, poolConfig.Id); - daemon.Configure(daemonEndpoints); + rpcClient = new RpcClient(daemonEndpoints.First(), jsonSerializerSettings, messageBus, poolConfig.Id); // configure wallet daemon var walletDaemonEndpoints = poolConfig.Daemons @@ -332,14 +331,13 @@ public async Task ConfigureAsync(ClusterConfig clusterConfig, PoolConfig poolCon }) .ToArray(); - walletDaemon = new DaemonClient(jsonSerializerSettings, messageBus, clusterConfig.ClusterName ?? poolConfig.PoolName, poolConfig.Id); - walletDaemon.Configure(walletDaemonEndpoints); + rpcClientWallet = new RpcClient(walletDaemonEndpoints.First(), jsonSerializerSettings, messageBus, poolConfig.Id); // detect network await UpdateNetworkTypeAsync(ct); // detect transfer_split support - var response = await walletDaemon.ExecuteCmdSingleAsync<TransferResponse>(logger, CryptonoteWalletCommands.TransferSplit, ct); + var response = await rpcClientWallet.ExecuteAsync<TransferResponse>(logger, CryptonoteWalletCommands.TransferSplit, ct); walletSupportsTransferSplit = response.Error.Code != CryptonoteConstants.MoneroRpcMethodNotFound; } @@ -366,7 +364,7 @@ public async Task<Block[]> ClassifyBlocksAsync(IMiningPool pool, Block[] blocks, { var block = page[j]; - var rpcResult = await daemon.ExecuteCmdAnyAsync<GetBlockHeaderResponse>(logger, + var rpcResult = await rpcClient.ExecuteAsync<GetBlockHeaderResponse>(logger, CNC.GetBlockHeaderByHeight, ct, new GetBlockHeaderByHeightRequest { @@ -444,7 +442,7 @@ public async Task PayoutAsync(IMiningPool pool, Balance[] balances, Cancellation var coin = poolConfig.Template.As<CryptonoteCoinTemplate>(); #if !DEBUG // ensure we have peers - var infoResponse = await daemon.ExecuteCmdAnyAsync<GetInfoResponse>(logger, CNC.GetInfo, ct); + var infoResponse = await daemon.ExecuteCmdAsync<GetInfoResponse>(logger, CNC.GetInfo, ct); if (infoResponse.Error != null || infoResponse.Response == null || infoResponse.Response.IncomingConnectionsCount + infoResponse.Response.OutgoingConnectionsCount < 3) { @@ -467,7 +465,7 @@ public async Task PayoutAsync(IMiningPool pool, Balance[] balances, Cancellation if(addressPrefix != coin.AddressPrefix && addressIntegratedPrefix != coin.AddressPrefixIntegrated) { - logger.Warn(() => $"[{LogCategory}] Excluding payment to invalid address {x.Address}"); + logger.Warn(() => $"[{LogCategory}] Excluding payment to invalid address: {x.Address}"); return false; } @@ -477,7 +475,7 @@ public async Task PayoutAsync(IMiningPool pool, Balance[] balances, Cancellation if(addressPrefix != coin.AddressPrefixStagenet && addressIntegratedPrefix != coin.AddressPrefixIntegratedStagenet) { - logger.Warn(() => $"[{LogCategory}] Excluding payment to invalid address {x.Address}"); + logger.Warn(() => $"[{LogCategory}] Excluding payment to invalid address: {x.Address}"); return false; } @@ -487,7 +485,7 @@ public async Task PayoutAsync(IMiningPool pool, Balance[] balances, Cancellation if(addressPrefix != coin.AddressPrefixTestnet && addressIntegratedPrefix != coin.AddressPrefixIntegratedTestnet) { - logger.Warn(() => $"[{LogCategory}] Excluding payment to invalid address {x.Address}"); + logger.Warn(() => $"[{LogCategory}] Excluding payment to invalid address: {x.Address}"); return false; } @@ -561,7 +559,7 @@ public async Task PayoutAsync(IMiningPool pool, Balance[] balances, Cancellation } // save wallet - await walletDaemon.ExecuteCmdSingleAsync<JToken>(logger, CryptonoteWalletCommands.Store, ct); + await rpcClientWallet.ExecuteAsync<JToken>(logger, CryptonoteWalletCommands.Store, ct); } #endregion // IPayoutHandler diff --git a/src/Miningcore/Blockchain/Equihash/DaemonResponses/ZCashAsyncOperationStatus.cs b/src/Miningcore/Blockchain/Equihash/DaemonResponses/ZCashAsyncOperationStatus.cs index 428c8ca6f..a0d35dcef 100644 --- a/src/Miningcore/Blockchain/Equihash/DaemonResponses/ZCashAsyncOperationStatus.cs +++ b/src/Miningcore/Blockchain/Equihash/DaemonResponses/ZCashAsyncOperationStatus.cs @@ -14,6 +14,6 @@ public class ZCashAsyncOperationStatus public string Status { get; set; } public JToken Result { get; set; } - public JsonRpcException Error { get; set; } + public JsonRpcError Error { get; set; } } } diff --git a/src/Miningcore/Blockchain/Equihash/EquihashJobManager.cs b/src/Miningcore/Blockchain/Equihash/EquihashJobManager.cs index 17501457c..3bc331dc5 100644 --- a/src/Miningcore/Blockchain/Equihash/EquihashJobManager.cs +++ b/src/Miningcore/Blockchain/Equihash/EquihashJobManager.cs @@ -12,7 +12,6 @@ using Miningcore.Configuration; using Miningcore.Contracts; using Miningcore.Crypto.Hashing.Equihash; -using Miningcore.DaemonInterface; using Miningcore.Extensions; using Miningcore.JsonRpc; using Miningcore.Messaging; @@ -36,9 +35,10 @@ public EquihashJobManager( } private EquihashCoinTemplate coin; - public EquihashCoinTemplate.EquihashNetworkParams ChainConfig { get; private set; } private EquihashSolver solver; + public EquihashCoinTemplate.EquihashNetworkParams ChainConfig { get; private set; } + protected override void PostChainIdentifyConfigure() { ChainConfig = coin.GetNetwork(network.ChainName); @@ -47,33 +47,30 @@ protected override void PostChainIdentifyConfigure() base.PostChainIdentifyConfigure(); } - private async Task<DaemonResponse<EquihashBlockTemplate>> GetBlockTemplateAsync(CancellationToken ct) + private async Task<RpcResponse<EquihashBlockTemplate>> GetBlockTemplateAsync(CancellationToken ct) { logger.LogInvoke(); - var subsidyResponse = await daemon.ExecuteCmdAnyAsync<ZCashBlockSubsidy>(logger, BitcoinCommands.GetBlockSubsidy, ct); + var subsidyResponse = await rpcClient.ExecuteAsync<ZCashBlockSubsidy>(logger, BitcoinCommands.GetBlockSubsidy, ct); - var result = await daemon.ExecuteCmdAnyAsync<EquihashBlockTemplate>(logger, + var result = await rpcClient.ExecuteAsync<EquihashBlockTemplate>(logger, BitcoinCommands.GetBlockTemplate, ct, extraPoolConfig?.GBTArgs ?? (object) GetBlockTemplateParams()); if(subsidyResponse.Error == null && result.Error == null && result.Response != null) result.Response.Subsidy = subsidyResponse.Response; else if(subsidyResponse.Error?.Code != (int) BitcoinRPCErrorCode.RPC_METHOD_NOT_FOUND) - result.Error = new JsonRpcException(-1, $"{BitcoinCommands.GetBlockSubsidy} failed", null); + result = new RpcResponse<EquihashBlockTemplate>(null, new JsonRpcError(-1, $"{BitcoinCommands.GetBlockSubsidy} failed", null)); return result; } - private DaemonResponse<EquihashBlockTemplate> GetBlockTemplateFromJson(string json) + private RpcResponse<EquihashBlockTemplate> GetBlockTemplateFromJson(string json) { logger.LogInvoke(); var result = JsonConvert.DeserializeObject<JsonRpcResponse>(json); - return new DaemonResponse<EquihashBlockTemplate> - { - Response = result.ResultAs<EquihashBlockTemplate>(), - }; + return new RpcResponse<EquihashBlockTemplate>(result.ResultAs<EquihashBlockTemplate>()); } protected override IDestination AddressToDestination(string address, BitcoinAddressType? addressType) @@ -210,8 +207,8 @@ public override async Task<bool> ValidateAddressAsync(string address, Cancellati return true; // handle z-addr - var result = await daemon.ExecuteCmdAnyAsync<ValidateAddressResponse>(logger, ct, - EquihashCommands.ZValidateAddress, new[] { address }); + var result = await rpcClient.ExecuteAsync<ValidateAddressResponse>(logger, + EquihashCommands.ZValidateAddress, ct, new[] { address }); return result.Response is {IsValid: true}; } diff --git a/src/Miningcore/Blockchain/Equihash/EquihashPayoutHandler.cs b/src/Miningcore/Blockchain/Equihash/EquihashPayoutHandler.cs index 3c914c4d2..38d30b286 100644 --- a/src/Miningcore/Blockchain/Equihash/EquihashPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Equihash/EquihashPayoutHandler.cs @@ -58,14 +58,14 @@ public override async Task ConfigureAsync(ClusterConfig clusterConfig, PoolConfi poolExtraConfig = poolConfig.Extra.SafeExtensionDataAs<EquihashPoolConfigExtra>(); // detect network - var blockchainInfoResponse = await daemon.ExecuteCmdSingleAsync<BlockchainInfo>(logger, BitcoinCommands.GetBlockchainInfo, ct); + var blockchainInfoResponse = await rpcClient.ExecuteAsync<BlockchainInfo>(logger, BitcoinCommands.GetBlockchainInfo, ct); network = Network.GetNetwork(blockchainInfoResponse.Response.Chain.ToLower()); chainConfig = poolConfig.Template.As<EquihashCoinTemplate>().GetNetwork(network.ChainName); // detect z_shieldcoinbase support - var response = await daemon.ExecuteCmdSingleAsync<JObject>(logger, EquihashCommands.ZShieldCoinbase, ct); + var response = await rpcClient.ExecuteAsync<JObject>(logger, EquihashCommands.ZShieldCoinbase, ct); supportsNativeShielding = response.Error.Code != (int) BitcoinRPCErrorCode.RPC_METHOD_NOT_FOUND; } @@ -107,13 +107,13 @@ public override async Task PayoutAsync(IMiningPool pool, Balance[] balances, Can var pageAmount = amounts.Sum(x => x.Amount); // check shielded balance - var balanceResult = await daemon.ExecuteCmdSingleAsync<object>(logger, EquihashCommands.ZGetBalance, ct, new object[] + var balanceResponse = await rpcClient.ExecuteAsync<object>(logger, EquihashCommands.ZGetBalance, ct, new object[] { poolExtraConfig.ZAddress, // default account ZMinConfirmations, // only spend funds covered by this many confirmations }); - if(balanceResult.Error != null || (decimal) (double) balanceResult.Response - TransferFee < pageAmount) + if(balanceResponse.Error != null || (decimal) (double) balanceResponse.Response - TransferFee < pageAmount) { logger.Info(() => $"[{LogCategory}] Insufficient shielded balance for payment of {FormatAmount(pageAmount)}"); return; @@ -131,11 +131,11 @@ public override async Task PayoutAsync(IMiningPool pool, Balance[] balances, Can // send command tryTransfer: - var result = await daemon.ExecuteCmdSingleAsync<string>(logger, EquihashCommands.ZSendMany, ct, args); + var response = await rpcClient.ExecuteAsync<string>(logger, EquihashCommands.ZSendMany, ct, args); - if(result.Error == null) + if(response.Error == null) { - var operationId = result.Response; + var operationId = response.Response; // check result if(string.IsNullOrEmpty(operationId)) @@ -148,7 +148,7 @@ public override async Task PayoutAsync(IMiningPool pool, Balance[] balances, Can while(continueWaiting) { - var operationResultResponse = await daemon.ExecuteCmdSingleAsync<ZCashAsyncOperationStatus[]>(logger, + var operationResultResponse = await rpcClient.ExecuteAsync<ZCashAsyncOperationStatus[]>(logger, EquihashCommands.ZGetOperationResult, ct, new object[] { new object[] { operationId } }); if(operationResultResponse.Error == null && @@ -185,26 +185,26 @@ public override async Task PayoutAsync(IMiningPool pool, Balance[] balances, Can } logger.Info(() => $"[{LogCategory}] Waiting for completion: {operationId}"); - await Task.Delay(TimeSpan.FromSeconds(10)); + await Task.Delay(TimeSpan.FromSeconds(10), ct); } } } else { - if(result.Error.Code == (int) BitcoinRPCErrorCode.RPC_WALLET_UNLOCK_NEEDED && !didUnlockWallet) + if(response.Error.Code == (int) BitcoinRPCErrorCode.RPC_WALLET_UNLOCK_NEEDED && !didUnlockWallet) { if(!string.IsNullOrEmpty(extraPoolPaymentProcessingConfig?.WalletPassword)) { logger.Info(() => $"[{LogCategory}] Unlocking wallet"); - var unlockResult = await daemon.ExecuteCmdSingleAsync<JToken>(logger, BitcoinCommands.WalletPassphrase, ct, new[] + var unlockResponse = await rpcClient.ExecuteAsync<JToken>(logger, BitcoinCommands.WalletPassphrase, ct, new[] { (object) extraPoolPaymentProcessingConfig.WalletPassword, (object) 5 // unlock for N seconds }); - if(unlockResult.Error == null) + if(unlockResponse.Error == null) { didUnlockWallet = true; goto tryTransfer; @@ -212,8 +212,8 @@ public override async Task PayoutAsync(IMiningPool pool, Balance[] balances, Can else { - logger.Error(() => $"[{LogCategory}] {BitcoinCommands.WalletPassphrase} returned error: {result.Error.Message} code {result.Error.Code}"); - NotifyPayoutFailure(poolConfig.Id, page, $"{BitcoinCommands.WalletPassphrase} returned error: {result.Error.Message} code {result.Error.Code}", null); + logger.Error(() => $"[{LogCategory}] {BitcoinCommands.WalletPassphrase} returned error: {response.Error.Message} code {response.Error.Code}"); + NotifyPayoutFailure(poolConfig.Id, page, $"{BitcoinCommands.WalletPassphrase} returned error: {response.Error.Message} code {response.Error.Code}", null); break; } } @@ -228,16 +228,16 @@ public override async Task PayoutAsync(IMiningPool pool, Balance[] balances, Can else { - logger.Error(() => $"[{LogCategory}] {EquihashCommands.ZSendMany} returned error: {result.Error.Message} code {result.Error.Code}"); + logger.Error(() => $"[{LogCategory}] {EquihashCommands.ZSendMany} returned error: {response.Error.Message} code {response.Error.Code}"); - NotifyPayoutFailure(poolConfig.Id, page, $"{EquihashCommands.ZSendMany} returned error: {result.Error.Message} code {result.Error.Code}", null); + NotifyPayoutFailure(poolConfig.Id, page, $"{EquihashCommands.ZSendMany} returned error: {response.Error.Message} code {response.Error.Code}", null); } } } // lock wallet logger.Info(() => $"[{LogCategory}] Locking wallet"); - await daemon.ExecuteCmdSingleAsync<JToken>(logger, BitcoinCommands.WalletLock, ct); + await rpcClient.ExecuteAsync<JToken>(logger, BitcoinCommands.WalletLock, ct); } #endregion // IPayoutHandler @@ -257,19 +257,19 @@ private async Task ShieldCoinbaseAsync(CancellationToken ct) poolExtraConfig.ZAddress, // dest: pool's z-addr }; - var result = await daemon.ExecuteCmdSingleAsync<ZCashShieldingResponse>(logger, EquihashCommands.ZShieldCoinbase, ct, args); + var response = await rpcClient.ExecuteAsync<ZCashShieldingResponse>(logger, EquihashCommands.ZShieldCoinbase, ct, args); - if(result.Error != null) + if(response.Error != null) { - if(result.Error.Code == -6) + if(response.Error.Code == -6) logger.Info(() => $"[{LogCategory}] No funds to shield"); else - logger.Error(() => $"[{LogCategory}] {EquihashCommands.ZShieldCoinbase} returned error: {result.Error.Message} code {result.Error.Code}"); + logger.Error(() => $"[{LogCategory}] {EquihashCommands.ZShieldCoinbase} returned error: {response.Error.Message} code {response.Error.Code}"); return; } - var operationId = result.Response.OperationId; + var operationId = response.Response.OperationId; logger.Info(() => $"[{LogCategory}] {EquihashCommands.ZShieldCoinbase} operation id: {operationId}"); @@ -277,7 +277,7 @@ private async Task ShieldCoinbaseAsync(CancellationToken ct) while(continueWaiting) { - var operationResultResponse = await daemon.ExecuteCmdSingleAsync<ZCashAsyncOperationStatus[]>(logger, + var operationResultResponse = await rpcClient.ExecuteAsync<ZCashAsyncOperationStatus[]>(logger, EquihashCommands.ZGetOperationResult, ct, new object[] { new object[] { operationId } }); if(operationResultResponse.Error == null && @@ -309,7 +309,7 @@ private async Task ShieldCoinbaseAsync(CancellationToken ct) } logger.Info(() => $"[{LogCategory}] Waiting for shielding operation completion: {operationId}"); - await Task.Delay(TimeSpan.FromSeconds(10)); + await Task.Delay(TimeSpan.FromSeconds(10), ct); } } @@ -318,15 +318,15 @@ private async Task ShieldCoinbaseEmulatedAsync(CancellationToken ct) logger.Info(() => $"[{LogCategory}] Shielding ZCash Coinbase funds (emulated)"); // get t-addr unspent balance for just the coinbase address (pool wallet) - var unspentResult = await daemon.ExecuteCmdSingleAsync<Utxo[]>(logger, BitcoinCommands.ListUnspent, ct); + var unspentResponse = await rpcClient.ExecuteAsync<Utxo[]>(logger, BitcoinCommands.ListUnspent, ct); - if(unspentResult.Error != null) + if(unspentResponse.Error != null) { - logger.Error(() => $"[{LogCategory}] {BitcoinCommands.ListUnspent} returned error: {unspentResult.Error.Message} code {unspentResult.Error.Code}"); + logger.Error(() => $"[{LogCategory}] {BitcoinCommands.ListUnspent} returned error: {unspentResponse.Error.Message} code {unspentResponse.Error.Code}"); return; } - var balance = unspentResult.Response + var balance = unspentResponse.Response .Where(x => x.Spendable && x.Address == poolConfig.Address) .Sum(x => x.Amount); @@ -358,15 +358,15 @@ private async Task ShieldCoinbaseEmulatedAsync(CancellationToken ct) }; // send command - var sendResult = await daemon.ExecuteCmdSingleAsync<string>(logger, EquihashCommands.ZSendMany, ct, args); + var sendResponse = await rpcClient.ExecuteAsync<string>(logger, EquihashCommands.ZSendMany, ct, args); - if(sendResult.Error != null) + if(sendResponse.Error != null) { - logger.Error(() => $"[{LogCategory}] {EquihashCommands.ZSendMany} returned error: {unspentResult.Error.Message} code {unspentResult.Error.Code}"); + logger.Error(() => $"[{LogCategory}] {EquihashCommands.ZSendMany} returned error: {unspentResponse.Error.Message} code {unspentResponse.Error.Code}"); return; } - var operationId = sendResult.Response; + var operationId = sendResponse.Response; logger.Info(() => $"[{LogCategory}] {EquihashCommands.ZSendMany} operation id: {operationId}"); @@ -374,7 +374,7 @@ private async Task ShieldCoinbaseEmulatedAsync(CancellationToken ct) while(continueWaiting) { - var operationResultResponse = await daemon.ExecuteCmdSingleAsync<ZCashAsyncOperationStatus[]>(logger, + var operationResultResponse = await rpcClient.ExecuteAsync<ZCashAsyncOperationStatus[]>(logger, EquihashCommands.ZGetOperationResult, ct, new object[] { new object[] { operationId } }); if(operationResultResponse.Error == null && @@ -407,7 +407,7 @@ private async Task ShieldCoinbaseEmulatedAsync(CancellationToken ct) } logger.Info(() => $"[{LogCategory}] Waiting for shielding transfer completion: {operationId}"); - await Task.Delay(TimeSpan.FromSeconds(10)); + await Task.Delay(TimeSpan.FromSeconds(10), ct); } } } diff --git a/src/Miningcore/Blockchain/Ethereum/EthereumJobManager.cs b/src/Miningcore/Blockchain/Ethereum/EthereumJobManager.cs index d54e3f6b9..a430c3fb5 100644 --- a/src/Miningcore/Blockchain/Ethereum/EthereumJobManager.cs +++ b/src/Miningcore/Blockchain/Ethereum/EthereumJobManager.cs @@ -3,20 +3,15 @@ using System.Globalization; using System.IO; using System.Linq; -using System.Net; using System.Numerics; using System.Reactive.Linq; -using System.Reactive.Threading.Tasks; -using System.Text; using System.Threading; using System.Threading.Tasks; using Autofac; -using Miningcore.Blockchain.Bitcoin; using Miningcore.Blockchain.Ethereum.Configuration; using Miningcore.Blockchain.Ethereum.DaemonResponses; using Miningcore.Configuration; using Miningcore.Crypto.Hashing.Ethash; -using Miningcore.DaemonInterface; using Miningcore.Extensions; using Miningcore.JsonRpc; using Miningcore.Messaging; @@ -25,7 +20,6 @@ using Miningcore.Time; using Miningcore.Util; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using NLog; using Block = Miningcore.Blockchain.Ethereum.DaemonResponses.Block; using Contract = Miningcore.Contracts.Contract; @@ -59,7 +53,7 @@ public EthereumJobManager( } private DaemonEndpointConfig[] daemonEndpoints; - private DaemonClient daemon; + private RpcClient rpcClient; private EthereumNetworkType networkType; private GethChainType chainType; private EthashFull ethash; @@ -150,23 +144,23 @@ private async Task<EthereumBlockTemplate> GetBlockTemplateAsync(CancellationToke { logger.LogInvoke(); - var commands = new[] + var requests = new[] { - new DaemonCmd(EC.GetWork), - new DaemonCmd(EC.GetBlockByNumber, new[] { (object) "latest", true }) + new RpcRequest(EC.GetWork), + new RpcRequest(EC.GetBlockByNumber, new[] { (object) "latest", true }) }; - var results = await daemon.ExecuteBatchAnyAsync(logger, ct, commands); + var responses = await rpcClient.ExecuteBatchAsync(logger, ct, requests); - if(results.Any(x => x.Error != null)) + if(responses.Any(x => x.Error != null)) { - logger.Warn(() => $"Error(s) refreshing blocktemplate: {results.First(x => x.Error != null).Error.Message}"); + logger.Warn(() => $"Error(s) refreshing blocktemplate: {responses.First(x => x.Error != null).Error.Message}"); return null; } // extract results - var work = results[0].Response.ToObject<string[]>(); - var block = results[1].Response.ToObject<Block>(); + var work = responses[0].Response.ToObject<string[]>(); + var block = responses[1].Response.ToObject<Block>(); // append blockheight (Recent versions of geth return this as the 4th element in the getWork response, older geth does not) if(work.Length < 4) @@ -194,39 +188,33 @@ private async Task<EthereumBlockTemplate> GetBlockTemplateAsync(CancellationToke private async Task ShowDaemonSyncProgressAsync(CancellationToken ct) { - var responses = await daemon.ExecuteCmdAllAsync<object>(logger, EC.GetSyncState, ct); - var firstValidResponse = responses.FirstOrDefault(x => x.Error == null && x.Response != null)?.Response; + var syncStateResponse = await rpcClient.ExecuteAsync<object>(logger, EC.GetSyncState, ct); - if(firstValidResponse != null) + if(syncStateResponse.Error == null) { // eth_syncing returns false if not synching - if(firstValidResponse is bool) + if(syncStateResponse.Response is false) return; - var syncStates = responses.Where(x => x.Error == null && x.Response != null && firstValidResponse is JObject) - .Select(x => ((JObject) x.Response).ToObject<SyncState>()) - .ToArray(); - - if(syncStates.Any()) + if(syncStateResponse.Response is SyncState syncState) { // get peer count - var response = await daemon.ExecuteCmdAllAsync<string>(logger, EC.GetPeerCount, ct); - var validResponses = response.Where(x => x.Error == null && x.Response != null).ToArray(); - var peerCount = validResponses.Any() ? validResponses.Max(x => x.Response.IntegralFromHex<uint>()) : 0; + var getPeerCountResponse = await rpcClient.ExecuteAsync<string>(logger, EC.GetPeerCount, ct); + var peerCount = getPeerCountResponse.Response.IntegralFromHex<uint>(); - if(syncStates.Any(x => x.WarpChunksAmount != 0)) + if(syncState.WarpChunksAmount != 0) { - var warpChunkAmount = syncStates.Min(x => x.WarpChunksAmount); - var warpChunkProcessed = syncStates.Max(x => x.WarpChunksProcessed); + var warpChunkAmount = syncState.WarpChunksAmount; + var warpChunkProcessed = syncState.WarpChunksProcessed; var percent = (double) warpChunkProcessed / warpChunkAmount * 100; logger.Info(() => $"Daemons have downloaded {percent:0.00}% of warp-chunks from {peerCount} peers"); } - else if(syncStates.Any(x => x.HighestBlock != 0)) + else if(syncState.HighestBlock != 0) { - var lowestHeight = syncStates.Min(x => x.CurrentBlock); - var totalBlocks = syncStates.Max(x => x.HighestBlock); + var lowestHeight = syncState.CurrentBlock; + var totalBlocks = syncState.HighestBlock; var percent = (double) lowestHeight / totalBlocks * 100; logger.Info(() => $"Daemons have downloaded {percent:0.00}% of blockchain from {peerCount} peers"); @@ -241,17 +229,17 @@ private async Task UpdateNetworkStatsAsync(CancellationToken ct) try { - var commands = new[] + var requests = new[] { - new DaemonCmd(EC.GetPeerCount), - new DaemonCmd(EC.GetBlockByNumber, new[] { (object) "latest", true }) + new RpcRequest(EC.GetPeerCount), + new RpcRequest(EC.GetBlockByNumber, new[] { (object) "latest", true }) }; - var results = await daemon.ExecuteBatchAnyAsync(logger, ct, commands); + var responses = await rpcClient.ExecuteBatchAsync(logger, ct, requests); - if(results.Any(x => x.Error != null)) + if(responses.Any(x => x.Error != null)) { - var errors = results.Where(x => x.Error != null) + var errors = responses.Where(x => x.Error != null) .ToArray(); if(errors.Any()) @@ -259,8 +247,8 @@ private async Task UpdateNetworkStatsAsync(CancellationToken ct) } // extract results - var peerCount = results[0].Response.ToObject<string>().IntegralFromHex<int>(); - var latestBlockInfo = results[1].Response.ToObject<Block>(); + var peerCount = responses[0].Response.ToObject<string>().IntegralFromHex<int>(); + var latestBlockInfo = responses[1].Response.ToObject<Block>(); var latestBlockHeight = latestBlockInfo.Height.Value; var latestBlockTimestamp = latestBlockInfo.Timestamp; @@ -268,8 +256,8 @@ private async Task UpdateNetworkStatsAsync(CancellationToken ct) var sampleSize = (ulong) 300; var sampleBlockNumber = latestBlockHeight - sampleSize; - var sampleBlockResults = await daemon.ExecuteCmdAllAsync<Block>(logger, EC.GetBlockByNumber, ct, new[] { (object) sampleBlockNumber.ToStringHexWithPrefix(), true }); - var sampleBlockTimestamp = sampleBlockResults.First(x => x.Error == null && x.Response?.Height != null).Response.Timestamp; + var sampleBlockResults = await rpcClient.ExecuteAsync<Block>(logger, EC.GetBlockByNumber, ct, new[] { (object) sampleBlockNumber.ToStringHexWithPrefix(), true }); + var sampleBlockTimestamp = sampleBlockResults.Response.Timestamp; var blockTime = (double) (latestBlockTimestamp - sampleBlockTimestamp) / sampleSize; var networkHashrate = (double) (latestBlockDifficulty / blockTime); @@ -287,7 +275,7 @@ private async Task UpdateNetworkStatsAsync(CancellationToken ct) private async Task<bool> SubmitBlockAsync(Share share, string fullNonceHex, string headerHash, string mixHash) { // submit work - var response = await daemon.ExecuteCmdAnyAsync<object>(logger, EC.SubmitWork, CancellationToken.None, new[] + var response = await rpcClient.ExecuteAsync<object>(logger, EC.SubmitWork, CancellationToken.None, new[] { fullNonceHex, headerHash, @@ -325,20 +313,6 @@ private object[] GetJobParamsForStratum(bool isNew) return new object[0]; } - private JsonRpcRequest DeserializeRequest(byte[] data) - { - using(var stream = new MemoryStream(data)) - { - using(var reader = new StreamReader(stream, Encoding.UTF8)) - { - using(var jreader = new JsonTextReader(reader)) - { - return serializer.Deserialize<JsonRpcRequest>(jreader); - } - } - } - } - #region API-Surface public IObservable<object> Jobs { get; private set; } @@ -442,25 +416,19 @@ protected override void ConfigureDaemons() { var jsonSerializerSettings = ctx.Resolve<JsonSerializerSettings>(); - daemon = new DaemonClient(jsonSerializerSettings, messageBus, clusterConfig.ClusterName ?? poolConfig.PoolName, poolConfig.Id); - daemon.Configure(daemonEndpoints); + rpcClient = new RpcClient(daemonEndpoints.First(), jsonSerializerSettings, messageBus, poolConfig.Id); } protected override async Task<bool> AreDaemonsHealthyAsync(CancellationToken ct) { - var responses = await daemon.ExecuteCmdAllAsync<Block>(logger, EC.GetBlockByNumber, ct, new[] { (object) "latest", true }); - - if(responses.Where(x => x.Error?.InnerException?.GetType() == typeof(DaemonClientException)) - .Select(x => (DaemonClientException) x.Error.InnerException) - .Any(x => x.Code == HttpStatusCode.Unauthorized)) - logger.ThrowLogPoolStartupException("Daemon reports invalid credentials"); + var response = await rpcClient.ExecuteAsync<Block>(logger, EC.GetBlockByNumber, ct, new[] { (object) "latest", true }); - return responses.All(x => x.Error == null); + return response.Error == null; } protected override async Task<bool> AreDaemonsConnectedAsync(CancellationToken ct) { - var response = await daemon.ExecuteCmdAnyAsync<string>(logger, EC.GetPeerCount, ct); + var response = await rpcClient.ExecuteAsync<string>(logger, EC.GetPeerCount, ct); return response.Error == null && response.Response.IntegralFromHex<uint>() > 0; } @@ -471,10 +439,9 @@ protected override async Task EnsureDaemonsSynchedAsync(CancellationToken ct) while(true) { - var responses = await daemon.ExecuteCmdAllAsync<object>(logger, EC.GetSyncState, ct); + var responses = await rpcClient.ExecuteAsync<object>(logger, EC.GetSyncState, ct); - var isSynched = responses.All(x => x.Error == null && - x.Response is false); + var isSynched = responses.Response is false; if(isSynched) { @@ -499,12 +466,12 @@ protected override async Task PostStartInitAsync(CancellationToken ct) { var commands = new[] { - new DaemonCmd(EC.GetNetVersion), - new DaemonCmd(EC.GetAccounts), - new DaemonCmd(EC.GetCoinbase), + new RpcRequest(EC.GetNetVersion), + new RpcRequest(EC.GetAccounts), + new RpcRequest(EC.GetCoinbase), }; - var results = await daemon.ExecuteBatchAnyAsync(logger, ct, commands); + var results = await rpcClient.ExecuteBatchAsync(logger, ct, commands); if(results.Any(x => x.Error != null)) { @@ -559,6 +526,8 @@ protected override async Task PostStartInitAsync(CancellationToken ct) } } + ConfigureRewards(); + SetupJobUpdates(ct); } diff --git a/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs b/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs index 7c3a807c3..9ffaae492 100644 --- a/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs @@ -11,8 +11,8 @@ using Miningcore.Blockchain.Ethereum.DaemonRequests; using Miningcore.Blockchain.Ethereum.DaemonResponses; using Miningcore.Configuration; -using Miningcore.DaemonInterface; using Miningcore.Extensions; +using Miningcore.JsonRpc; using Miningcore.Messaging; using Miningcore.Mining; using Miningcore.Payments; @@ -52,7 +52,7 @@ public EthereumPayoutHandler( } private readonly IComponentContext ctx; - private DaemonClient daemon; + private RpcClient rpcClient; private EthereumNetworkType networkType; private GethChainType chainType; private const int BlockSearchOffset = 50; @@ -75,12 +75,7 @@ public async Task ConfigureAsync(ClusterConfig clusterConfig, PoolConfig poolCon // configure standard daemon var jsonSerializerSettings = ctx.Resolve<JsonSerializerSettings>(); - var daemonEndpoints = poolConfig.Daemons - .Where(x => string.IsNullOrEmpty(x.Category)) - .ToArray(); - - daemon = new DaemonClient(jsonSerializerSettings, messageBus, clusterConfig.ClusterName ?? poolConfig.PoolName, poolConfig.Id); - daemon.Configure(daemonEndpoints); + rpcClient = new RpcClient(poolConfig.Daemons.First(x => string.IsNullOrEmpty(x.Category)), jsonSerializerSettings, messageBus, poolConfig.Id); await DetectChainAsync(ct); } @@ -105,8 +100,8 @@ public async Task<Block[]> ClassifyBlocksAsync(IMiningPool pool, Block[] blocks, .ToArray(); // get latest block - var latestBlockResponses = await daemon.ExecuteCmdAllAsync<DaemonResponses.Block>(logger, EC.GetBlockByNumber, ct, new[] { (object) "latest", true }); - var latestBlockHeight = latestBlockResponses.First(x => x.Error == null && x.Response?.Height != null).Response.Height.Value; + var latestBlockResponse = await rpcClient.ExecuteAsync<DaemonResponses.Block>(logger, EC.GetBlockByNumber, ct, new[] { (object) "latest", true }); + var latestBlockHeight = latestBlockResponse.Response.Height.Value; // execute batch var blockInfos = await FetchBlocks(blockCache, ct, page.Select(block => (long) block.BlockHeight).ToArray()); @@ -128,9 +123,9 @@ public async Task<Block[]> ClassifyBlocksAsync(IMiningPool pool, Block[] blocks, // mature? if(latestBlockHeight - block.BlockHeight >= EthereumConstants.MinConfimations) { - var blockHashResponses = await daemon.ExecuteCmdAllAsync<DaemonResponses.Block>(logger, EC.GetBlockByNumber, ct, + var blockHashResponse = await rpcClient.ExecuteAsync<DaemonResponses.Block>(logger, EC.GetBlockByNumber, ct, new[] { (object) block.BlockHeight.ToStringHexWithPrefix(), true }); - var blockHash = blockHashResponses.First(x => x.Error == null && x.Response?.Hash != null).Response.Hash; + var blockHash = blockHashResponse.Response.Hash; block.Hash = blockHash; block.Status = BlockStatus.Confirmed; @@ -170,13 +165,13 @@ public async Task<Block[]> ClassifyBlocksAsync(IMiningPool pool, Block[] blocks, if(blockInfo2.Uncles.Length > 0) { // fetch all uncles in a single RPC batch request - var uncleBatch = blockInfo2.Uncles.Select((x, index) => new DaemonCmd(EC.GetUncleByBlockNumberAndIndex, + var uncleBatch = blockInfo2.Uncles.Select((x, index) => new RpcRequest(EC.GetUncleByBlockNumberAndIndex, new[] { blockInfo2.Height.Value.ToStringHexWithPrefix(), index.ToStringHexWithPrefix() })) .ToArray(); logger.Info(() => $"[{LogCategory}] Fetching {blockInfo2.Uncles.Length} uncles for block {blockInfo2.Height}"); - var uncleResponses = await daemon.ExecuteBatchAnyAsync(logger, ct, uncleBatch); + var uncleResponses = await rpcClient.ExecuteBatchAsync(logger, ct, uncleBatch); logger.Info(() => $"[{LogCategory}] Fetched {uncleResponses.Count(x => x.Error == null && x.Response != null)} uncles for block {blockInfo2.Height}"); @@ -189,9 +184,9 @@ public async Task<Block[]> ClassifyBlocksAsync(IMiningPool pool, Block[] blocks, // mature? if(latestBlockHeight - uncle.Height.Value >= EthereumConstants.MinConfimations) { - var blockHashUncleResponses = await daemon.ExecuteCmdAllAsync<DaemonResponses.Block>(logger, EC.GetBlockByNumber, ct, + var blockHashUncleResponse = await rpcClient.ExecuteAsync<DaemonResponses.Block>(logger, EC.GetBlockByNumber, ct, new[] { (object) uncle.Height.Value.ToStringHexWithPrefix(), true }); - var blockHashUncle = blockHashUncleResponses.First(x => x.Error == null && x.Response?.Hash != null).Response.Hash; + var blockHashUncle = blockHashUncleResponse.Response.Hash; block.Hash = blockHashUncle; block.Status = BlockStatus.Confirmed; @@ -248,7 +243,7 @@ public override async Task<decimal> UpdateBlockRewardBalancesAsync(IDbConnection public async Task PayoutAsync(IMiningPool pool, Balance[] balances, CancellationToken ct) { // ensure we have peers - var infoResponse = await daemon.ExecuteCmdSingleAsync<string>(logger, EC.GetPeerCount, ct); + var infoResponse = await rpcClient.ExecuteAsync<string>(logger, EC.GetPeerCount, ct); if(networkType == EthereumNetworkType.Mainnet && (infoResponse.Error != null || string.IsNullOrEmpty(infoResponse.Response) || @@ -288,14 +283,14 @@ public async Task PayoutAsync(IMiningPool pool, Balance[] balances, Cancellation if(cacheMisses.Any()) { - var blockBatch = cacheMisses.Select(height => new DaemonCmd(EC.GetBlockByNumber, + var blockBatch = cacheMisses.Select(height => new RpcRequest(EC.GetBlockByNumber, new[] { (object) height.ToStringHexWithPrefix(), true })).ToArray(); - var tmp = await daemon.ExecuteBatchAnyAsync(logger, ct, blockBatch); + var tmp = await rpcClient.ExecuteBatchAsync(logger, ct, blockBatch); var transformed = tmp .Where(x => x.Error == null && x.Response != null) @@ -334,10 +329,10 @@ internal static decimal GetBaseBlockReward(GethChainType chainType, ulong height private async Task<decimal> GetTxRewardAsync(DaemonResponses.Block blockInfo, CancellationToken ct) { // fetch all tx receipts in a single RPC batch request - var batch = blockInfo.Transactions.Select(tx => new DaemonCmd(EC.GetTxReceipt, new[] { tx.Hash })) + var batch = blockInfo.Transactions.Select(tx => new RpcRequest(EC.GetTxReceipt, new[] { tx.Hash })) .ToArray(); - var results = await daemon.ExecuteBatchAnyAsync(logger, ct, batch); + var results = await rpcClient.ExecuteBatchAsync(logger, ct, batch); if(results.Any(x => x.Error != null)) throw new Exception($"Error fetching tx receipts: {string.Join(", ", results.Where(x => x.Error != null).Select(y => y.Error.Message))}"); @@ -366,10 +361,10 @@ private async Task DetectChainAsync(CancellationToken ct) { var commands = new[] { - new DaemonCmd(EC.GetNetVersion), + new RpcRequest(EC.GetNetVersion), }; - var results = await daemon.ExecuteBatchAnyAsync(logger, ct, commands); + var results = await rpcClient.ExecuteBatchAsync(logger, ct, commands); if(results.Any(x => x.Error != null)) { @@ -398,10 +393,10 @@ private async Task<string> PayoutAsync(Balance balance, CancellationToken ct) { From = poolConfig.Address, To = balance.Address, - Value = writeHex(amount), + Value = amount.ToString("x").TrimStart('0'), }; - var response = await daemon.ExecuteCmdSingleAsync<string>(logger, EC.SendTx, ct, new[] { request }); + var response = await rpcClient.ExecuteAsync<string>(logger, EC.SendTx, ct, new[] { request }); if(response.Error != null) throw new Exception($"{EC.SendTx} returned error: {response.Error.Message} code {response.Error.Code}"); @@ -418,10 +413,5 @@ private async Task<string> PayoutAsync(Balance balance, CancellationToken ct) // done return txHash; } - - private static string writeHex(BigInteger value) - { - return (value.ToString("x").TrimStart('0')); - } } } diff --git a/src/Miningcore/DaemonInterface/DaemonClientException.cs b/src/Miningcore/DaemonInterface/DaemonClientException.cs deleted file mode 100644 index 017e63548..000000000 --- a/src/Miningcore/DaemonInterface/DaemonClientException.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Net; - -namespace Miningcore.DaemonInterface -{ - public class DaemonClientException : Exception - { - public DaemonClientException(string msg) : base(msg) - { - } - - public DaemonClientException(HttpStatusCode code, string msg) : base(msg) - { - Code = code; - } - - public HttpStatusCode Code { get; set; } - } -} diff --git a/src/Miningcore/DaemonInterface/DaemonCmd.cs b/src/Miningcore/DaemonInterface/DaemonCmd.cs deleted file mode 100644 index 55347629b..000000000 --- a/src/Miningcore/DaemonInterface/DaemonCmd.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Miningcore.DaemonInterface -{ - public class DaemonCmd - { - public DaemonCmd() - { - } - - public DaemonCmd(string method) - { - Method = method; - } - - public DaemonCmd(string method, object payload) - { - Method = method; - Payload = payload; - } - - public string Method { get; set; } - public object Payload { get; set; } - } -} diff --git a/src/Miningcore/DaemonInterface/DaemonResponse.cs b/src/Miningcore/DaemonInterface/DaemonResponse.cs deleted file mode 100644 index 7824e26c8..000000000 --- a/src/Miningcore/DaemonInterface/DaemonResponse.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Miningcore.Configuration; -using Miningcore.JsonRpc; - -namespace Miningcore.DaemonInterface -{ - public class DaemonResponse<T> - { - public JsonRpcException Error { get; set; } - public T Response { get; set; } - public AuthenticatedNetworkEndpointConfig Instance { get; set; } - } -} diff --git a/src/Miningcore/JsonRpc/JsonRpcException.cs b/src/Miningcore/JsonRpc/JsonRpcError.cs similarity index 87% rename from src/Miningcore/JsonRpc/JsonRpcException.cs rename to src/Miningcore/JsonRpc/JsonRpcError.cs index 658a25b2e..0101c31e3 100644 --- a/src/Miningcore/JsonRpc/JsonRpcException.cs +++ b/src/Miningcore/JsonRpc/JsonRpcError.cs @@ -27,9 +27,9 @@ namespace Miningcore.JsonRpc /// The remainder of the space is available for application defined errors. /// </summary> [JsonObject(MemberSerialization.OptIn)] - public class JsonRpcException + public record JsonRpcError { - public JsonRpcException(int code, string message, object data, Exception inner = null) + public JsonRpcError(int code, string message, object data, Exception inner = null) { Code = code; Message = message; @@ -38,15 +38,15 @@ public JsonRpcException(int code, string message, object data, Exception inner = } [JsonProperty(PropertyName = "code")] - public int Code { get; set; } + public int Code { get; } [JsonProperty(PropertyName = "message")] - public string Message { get; set; } + public string Message { get; } [JsonProperty(PropertyName = "data")] - public object Data { get; set; } + public object Data { get; } [JsonIgnore] - public Exception InnerException { get; set; } + public Exception InnerException { get; } } } diff --git a/src/Miningcore/JsonRpc/JsonRpcResponse.cs b/src/Miningcore/JsonRpc/JsonRpcResponse.cs index 7609a57b0..14c40e16d 100644 --- a/src/Miningcore/JsonRpc/JsonRpcResponse.cs +++ b/src/Miningcore/JsonRpc/JsonRpcResponse.cs @@ -14,7 +14,7 @@ public JsonRpcResponse(object result, object id = null) : base(result, id) { } - public JsonRpcResponse(JsonRpcException ex, object id = null, object result = null) : base(ex, id, result) + public JsonRpcResponse(JsonRpcError ex, object id = null, object result = null) : base(ex, id, result) { } } @@ -35,7 +35,7 @@ public JsonRpcResponse(T result, object id = null) Id = id; } - public JsonRpcResponse(JsonRpcException ex, object id, object result) + public JsonRpcResponse(JsonRpcError ex, object id, object result) { Error = ex; Id = id; @@ -51,7 +51,7 @@ public JsonRpcResponse(JsonRpcException ex, object id, object result) public object Result { get; set; } [JsonProperty(PropertyName = "error")] - public JsonRpcException Error { get; set; } + public JsonRpcError Error { get; set; } [JsonProperty(PropertyName = "id", NullValueHandling = NullValueHandling.Ignore)] public object Id { get; set; } diff --git a/src/Miningcore/DaemonInterface/DaemonClient.cs b/src/Miningcore/JsonRpc/RpcClient.cs similarity index 60% rename from src/Miningcore/DaemonInterface/DaemonClient.cs rename to src/Miningcore/JsonRpc/RpcClient.cs index 6e39947ff..7c7591a18 100644 --- a/src/Miningcore/DaemonInterface/DaemonClient.cs +++ b/src/Miningcore/JsonRpc/RpcClient.cs @@ -14,7 +14,6 @@ using System.Threading.Tasks; using Miningcore.Configuration; using Miningcore.Extensions; -using Miningcore.JsonRpc; using Miningcore.Messaging; using Miningcore.Notifications.Messages; using Miningcore.Util; @@ -24,19 +23,20 @@ using ZeroMQ; using Contract = Miningcore.Contracts.Contract; -namespace Miningcore.DaemonInterface +namespace Miningcore.JsonRpc { /// <summary> - /// Provides JsonRpc based interface to a cluster of blockchain daemons for improved fault tolerance + /// JsonRpc interface to blockchain node /// </summary> - public class DaemonClient + public class RpcClient { - public DaemonClient(JsonSerializerSettings serializerSettings, IMessageBus messageBus, string server, string poolId) + public RpcClient(DaemonEndpointConfig endPoint, JsonSerializerSettings serializerSettings, IMessageBus messageBus, string poolId) { Contract.RequiresNonNull(serializerSettings, nameof(serializerSettings)); Contract.RequiresNonNull(messageBus, nameof(messageBus)); Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(poolId), $"{nameof(poolId)} must not be empty"); + this.endPoint = endPoint; this.serializerSettings = serializerSettings; this.messageBus = messageBus; this.poolId = poolId; @@ -49,7 +49,7 @@ public DaemonClient(JsonSerializerSettings serializerSettings, IMessageBus messa private readonly JsonSerializerSettings serializerSettings; - protected DaemonEndpointConfig[] endPoints; + protected readonly DaemonEndpointConfig endPoint; private readonly JsonSerializer serializer; private static readonly HttpClient httpClient = new(new HttpClientHandler @@ -71,153 +71,62 @@ protected void PublishTelemetry(TelemetryCategory cat, TimeSpan elapsed, string #region API-Surface - public void Configure(DaemonEndpointConfig[] endPoints) - { - Contract.RequiresNonNull(endPoints, nameof(endPoints)); - Contract.Requires<ArgumentException>(endPoints.Length > 0, $"{nameof(endPoints)} must not be empty"); - - this.endPoints = endPoints; - } - - /// <summary> - /// Executes the request against all configured demons and returns their responses as an array - /// </summary> - public Task<DaemonResponse<JToken>[]> ExecuteCmdAllAsync(ILogger logger, string method, CancellationToken ct) - { - return ExecuteCmdAllAsync<JToken>(logger, method, ct); - } - - /// <summary> - /// Executes the request against all configured demons and returns their responses as an array - /// </summary> - public async Task<DaemonResponse<TResponse>[]> ExecuteCmdAllAsync<TResponse>(ILogger logger, string method, CancellationToken ct, - object payload = null, JsonSerializerSettings payloadJsonSerializerSettings = null) + public async Task<RpcResponse<TResponse>> ExecuteAsync<TResponse>(ILogger logger, string method, CancellationToken ct, + object payload = null, bool throwOnError = false) where TResponse : class { Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(method), $"{nameof(method)} must not be empty"); - logger.LogInvoke(new object[] { "\"" + method + "\"" }); - - var tasks = endPoints.Select(endPoint => BuildRequestTask(logger, endPoint, method, payload, CancellationToken.None, payloadJsonSerializerSettings)).ToArray(); + logger.LogInvoke(new object[] { method }); try { - await Task.WhenAll(tasks); + var response = await RequestAsync(logger, ct, endPoint, method, payload); + + if(response.Result is JToken token) + return new RpcResponse<TResponse>(token.ToObject<TResponse>(serializer), response.Error); + + return new RpcResponse<TResponse>((TResponse) response.Result, response.Error); } - catch(Exception) + catch(TaskCanceledException) { - // ignored + return new RpcResponse<TResponse>(null, new JsonRpcError(-500, "Cancelled", null)); } - var results = tasks.Select((x, i) => MapDaemonResponse<TResponse>(i, x)) - .ToArray(); - - return results; - } - - /// <summary> - /// Executes the request against all configured demons and returns the first successful response - /// </summary> - public Task<DaemonResponse<JToken>> ExecuteCmdAnyAsync(ILogger logger, string method, CancellationToken ct, bool throwOnError = false) - { - return ExecuteCmdAnyAsync<JToken>(logger, method, ct, null, null, throwOnError); - } - - /// <summary> - /// Executes the request against all configured demons and returns the first successful response - /// </summary> - public async Task<DaemonResponse<TResponse>> ExecuteCmdAnyAsync<TResponse>(ILogger logger, string method, CancellationToken ct, object payload = null, - JsonSerializerSettings payloadJsonSerializerSettings = null, bool throwOnError = false) - where TResponse : class - { - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(method), $"{nameof(method)} must not be empty"); - - logger.LogInvoke(new object[] { "\"" + method + "\"" }); - - var tasks = endPoints.Select(endPoint => BuildRequestTask(logger, endPoint, method, payload, CancellationToken.None, payloadJsonSerializerSettings)).ToArray(); - - var taskFirstCompleted = await Task.WhenAny(tasks); - var result = MapDaemonResponse<TResponse>(0, taskFirstCompleted, throwOnError); - return result; - } - - /// <summary> - /// Executes the request against all configured demons and returns the first successful response - /// </summary> - public async Task<DaemonResponse<TResponse>> ExecuteCmdAnyAsync<TResponse>(ILogger logger, CancellationToken ct, string method, object payload = null, - JsonSerializerSettings payloadJsonSerializerSettings = null, bool throwOnError = false) - where TResponse : class - { - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(method), $"{nameof(method)} must not be empty"); - - logger.LogInvoke(new object[] { "\"" + method + "\"" }); - - var tasks = endPoints.Select(endPoint => BuildRequestTask(logger, endPoint, method, payload, ct, payloadJsonSerializerSettings)).ToArray(); + catch(Exception ex) + { + if(throwOnError) + throw; - var taskFirstCompleted = await Task.WhenAny(tasks); - var result = MapDaemonResponse<TResponse>(0, taskFirstCompleted, throwOnError); - return result; + return new RpcResponse<TResponse>(null, new JsonRpcError(-500, ex.Message, null, ex)); + } } - /// <summary> - /// Executes the request against all configured demons and returns the first successful response - /// </summary> - public Task<DaemonResponse<JToken>> ExecuteCmdSingleAsync(ILogger logger, string method, CancellationToken ct) + public Task<RpcResponse<JToken>> ExecuteAsync(ILogger logger, string method, CancellationToken ct, bool throwOnError = false) { - return ExecuteCmdAnyAsync<JToken>(logger, method, ct); + return ExecuteAsync<JToken>(logger, method, ct, null, throwOnError); } - /// <summary> - /// Executes the request against all configured demons and returns the first successful response - /// </summary> - public async Task<DaemonResponse<TResponse>> ExecuteCmdSingleAsync<TResponse>(ILogger logger, string method, CancellationToken ct, object payload = null, - JsonSerializerSettings payloadJsonSerializerSettings = null) - where TResponse : class - { - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(method), $"{nameof(method)} must not be empty"); - - logger.LogInvoke(new object[] { "\"" + method + "\"" }); - - var task = BuildRequestTask(logger, endPoints.First(), method, payload, CancellationToken.None, payloadJsonSerializerSettings); - await Task.WhenAny(task); - - var result = MapDaemonResponse<TResponse>(0, task); - return result; - } - - /// <summary> - /// Executes the request against all configured demons and returns the first successful response - /// </summary> - public async Task<DaemonResponse<TResponse>> ExecuteCmdSingleAsync<TResponse>(ILogger logger, CancellationToken ct, string method, object payload = null, - JsonSerializerSettings payloadJsonSerializerSettings = null) - where TResponse : class - { - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(method), $"{nameof(method)} must not be empty"); - - logger.LogInvoke(new object[] { "\"" + method + "\"" }); - - var task = BuildRequestTask(logger, endPoints.First(), method, payload, ct, payloadJsonSerializerSettings); - await Task.WhenAny(task); - - var result = MapDaemonResponse<TResponse>(0, task); - return result; - } - - /// <summary> - /// Executes the requests against all configured demons and returns the first successful response array - /// </summary> - public async Task<DaemonResponse<JToken>[]> ExecuteBatchAnyAsync(ILogger logger, CancellationToken ct, params DaemonCmd[] batch) + public async Task<RpcResponse<JToken>[]> ExecuteBatchAsync(ILogger logger, CancellationToken ct, params RpcRequest[] batch) { Contract.RequiresNonNull(batch, nameof(batch)); - logger.LogInvoke(batch.Select(x => "\"" + x.Method + "\"").ToArray()); + logger.LogInvoke(string.Join(", ", batch.Select(x=> x.Method))); + + try + { + var response = await BatchRequestAsync(logger, ct, endPoint, batch); - var tasks = endPoints.Select(endPoint => BuildBatchRequestTask(logger, ct, endPoint, batch)).ToArray(); + return response + .Select(x => new RpcResponse<JToken>(x.Result != null ? JToken.FromObject(x.Result) : null, x.Error)) + .ToArray(); + } - var taskFirstCompleted = await Task.WhenAny(tasks); - var result = MapDaemonBatchResponse(0, taskFirstCompleted); - return result; + catch(Exception ex) + { + return Enumerable.Repeat(new RpcResponse<JToken>(null, new JsonRpcError(-500, ex.Message, null, ex)), batch.Length).ToArray(); + } } public IObservable<byte[]> WebsocketSubscribe(ILogger logger, CancellationToken ct, Dictionary<DaemonEndpointConfig, @@ -246,8 +155,7 @@ public IObservable<ZMessage> ZmqSubscribe(ILogger logger, CancellationToken ct, #endregion // API-Surface - private async Task<JsonRpcResponse> BuildRequestTask(ILogger logger, DaemonEndpointConfig endPoint, string method, object payload, - CancellationToken ct, JsonSerializerSettings payloadJsonSerializerSettings = null) + private async Task<JsonRpcResponse> RequestAsync(ILogger logger, CancellationToken ct, DaemonEndpointConfig endPoint, string method, object payload) { var rpcRequestId = GetRequestId(); @@ -257,22 +165,22 @@ private async Task<JsonRpcResponse> BuildRequestTask(ILogger logger, DaemonEndpo // build rpc request var rpcRequest = new JsonRpcRequest<object>(method, payload, rpcRequestId); - // build request url - var protocol = (endPoint.Ssl || endPoint.Http2) ? "https" : "http"; + // build url + var protocol = (endPoint.Ssl || endPoint.Http2) ? Uri.UriSchemeHttps : Uri.UriSchemeHttp; var requestUrl = $"{protocol}://{endPoint.Host}:{endPoint.Port}"; if(!string.IsNullOrEmpty(endPoint.HttpPath)) requestUrl += $"{(endPoint.HttpPath.StartsWith("/") ? string.Empty : "/")}{endPoint.HttpPath}"; - // build http request + // build request using(var request = new HttpRequestMessage(HttpMethod.Post, requestUrl)) { - request.Headers.ConnectionClose = false; // enable keep-alive - if(endPoint.Http2) request.Version = new Version(2, 0); + else + request.Headers.ConnectionClose = false; // enable keep-alive - // build request content - var json = JsonConvert.SerializeObject(rpcRequest, payloadJsonSerializerSettings ?? serializerSettings); + // build content + var json = JsonConvert.SerializeObject(rpcRequest, serializerSettings); request.Content = new StringContent(json, Encoding.UTF8, "application/json"); // build auth header @@ -306,7 +214,7 @@ private async Task<JsonRpcResponse> BuildRequestTask(ILogger logger, DaemonEndpo } } - private async Task<JsonRpcResponse<JToken>[]> BuildBatchRequestTask(ILogger logger, CancellationToken ct, DaemonEndpointConfig endPoint, DaemonCmd[] batch) + private async Task<JsonRpcResponse<JToken>[]> BatchRequestAsync(ILogger logger, CancellationToken ct, DaemonEndpointConfig endPoint, RpcRequest[] batch) { // telemetry var sw = Stopwatch.StartNew(); @@ -314,19 +222,19 @@ private async Task<JsonRpcResponse<JToken>[]> BuildBatchRequestTask(ILogger logg // build rpc request var rpcRequests = batch.Select(x => new JsonRpcRequest<object>(x.Method, x.Payload, GetRequestId())); - // build request url - var protocol = (endPoint.Ssl || endPoint.Http2) ? "https" : "http"; + // build url + var protocol = (endPoint.Ssl || endPoint.Http2) ? Uri.UriSchemeHttps : Uri.UriSchemeHttp; var requestUrl = $"{protocol}://{endPoint.Host}:{endPoint.Port}"; if(!string.IsNullOrEmpty(endPoint.HttpPath)) requestUrl += $"{(endPoint.HttpPath.StartsWith("/") ? string.Empty : "/")}{endPoint.HttpPath}"; - // build http request + // build request using(var request = new HttpRequestMessage(HttpMethod.Post, requestUrl)) { - request.Headers.ConnectionClose = false; // enable keep-alive - if(endPoint.Http2) request.Version = new Version(2, 0); + else + request.Headers.ConnectionClose = false; // enable keep-alive // build request content var json = JsonConvert.SerializeObject(rpcRequests, serializerSettings); @@ -368,68 +276,6 @@ protected string GetRequestId() return rpcRequestId; } - private DaemonResponse<TResponse> MapDaemonResponse<TResponse>(int i, Task<JsonRpcResponse> x, bool throwOnError = false) - where TResponse : class - { - var resp = new DaemonResponse<TResponse> - { - Instance = endPoints[i] - }; - - if(x.IsFaulted) - { - Exception inner; - - if(x.Exception.InnerExceptions.Count == 1) - inner = x.Exception.InnerException; - else - inner = x.Exception; - - if(throwOnError) - throw inner; - - resp.Error = new JsonRpcException(-500, x.Exception.Message, null, inner); - } - - else if(x.IsCanceled) - { - resp.Error = new JsonRpcException(-500, "Cancelled", null); - } - - else - { - Debug.Assert(x.IsCompletedSuccessfully); - - if(x.Result?.Result is JToken token) - resp.Response = token?.ToObject<TResponse>(serializer); - else - resp.Response = (TResponse) x.Result?.Result; - - resp.Error = x.Result?.Error; - } - - return resp; - } - - private DaemonResponse<JToken>[] MapDaemonBatchResponse(int i, Task<JsonRpcResponse<JToken>[]> x) - { - if(x.IsFaulted) - return x.Result?.Select(y => new DaemonResponse<JToken> - { - Instance = endPoints[i], - Error = new JsonRpcException(-500, x.Exception.Message, null) - }).ToArray(); - - Debug.Assert(x.IsCompletedSuccessfully); - - return x.Result?.Select(y => new DaemonResponse<JToken> - { - Instance = endPoints[i], - Response = y.Result != null ? JToken.FromObject(y.Result) : null, - Error = y.Error - }).ToArray(); - } - private IObservable<byte[]> WebsocketSubscribeEndpoint(ILogger logger, CancellationToken ct, NetworkEndpointConfig endPoint, (int Port, string HttpPath, bool Ssl) conf, string method, object payload = null, JsonSerializerSettings payloadJsonSerializerSettings = null) diff --git a/src/Miningcore/JsonRpc/RpcRequest.cs b/src/Miningcore/JsonRpc/RpcRequest.cs new file mode 100644 index 000000000..e1b15d13c --- /dev/null +++ b/src/Miningcore/JsonRpc/RpcRequest.cs @@ -0,0 +1,19 @@ +namespace Miningcore.JsonRpc +{ + public record RpcRequest + { + public RpcRequest(string method) + { + Method = method; + } + + public RpcRequest(string method, object payload) + { + Method = method; + Payload = payload; + } + + public string Method { get; } + public object Payload { get; } + } +} diff --git a/src/Miningcore/JsonRpc/RpcResponse.cs b/src/Miningcore/JsonRpc/RpcResponse.cs new file mode 100644 index 000000000..c5559bdaa --- /dev/null +++ b/src/Miningcore/JsonRpc/RpcResponse.cs @@ -0,0 +1,6 @@ +using Miningcore.Configuration; + +namespace Miningcore.JsonRpc +{ + public record RpcResponse<T>(T Response, JsonRpcError Error = null); +} diff --git a/src/Miningcore/Payments/PaymentSchemes/PPLNSPaymentScheme.cs b/src/Miningcore/Payments/PaymentSchemes/PPLNSPaymentScheme.cs index f8c7d44d9..00b12ce96 100644 --- a/src/Miningcore/Payments/PaymentSchemes/PPLNSPaymentScheme.cs +++ b/src/Miningcore/Payments/PaymentSchemes/PPLNSPaymentScheme.cs @@ -156,7 +156,7 @@ private async Task LogDiscardedSharesAsync(PoolConfig poolConfig, Block block, D #endregion // IPayoutScheme - private async Task<DateTime?> CalculateRewardsAsync(IMiningPool pool, IPayoutHandler payoutHandler, decimal window, Block block, decimal blockReward, + private async Task<DateTime?> CalculateRewardsAsync(IMiningPool pool, IPayoutHandler payoutHandler,decimal window, Block block, decimal blockReward, Dictionary<string, double> shares, Dictionary<string, decimal> rewards, CancellationToken ct) { var poolConfig = pool.Config; @@ -169,7 +169,7 @@ private async Task LogDiscardedSharesAsync(PoolConfig poolConfig, Block block, D var blockRewardRemaining = blockReward; DateTime? shareCutOffDate = null; - while(!done && ct.IsCancellationRequested) + while(!done && !ct.IsCancellationRequested) { logger.Info(() => $"Fetching page {currentPage} of shares for pool {poolConfig.Id}, block {block.BlockHeight}"); diff --git a/src/Miningcore/Stratum/StratumConnection.cs b/src/Miningcore/Stratum/StratumConnection.cs index 1e3378b09..b73976b8e 100644 --- a/src/Miningcore/Stratum/StratumConnection.cs +++ b/src/Miningcore/Stratum/StratumConnection.cs @@ -188,7 +188,7 @@ public ValueTask RespondAsync<T>(T payload, object id) public ValueTask RespondErrorAsync(StratumError code, string message, object id, object result = null, object data = null) { - return RespondAsync(new JsonRpcResponse(new JsonRpcException((int) code, message, null), id, result)); + return RespondAsync(new JsonRpcResponse(new JsonRpcError((int) code, message, null), id, result)); } public ValueTask RespondAsync<T>(JsonRpcResponse<T> response) From eedd29dc9e1228b50736d07ad86f16f26e3f33c5 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Sun, 18 Jul 2021 14:54:41 +0200 Subject: [PATCH 089/145] Refactor --- .../Blockchain/Ergo/ErgoJobManager.cs | 25 ++++++++-------- .../Blockchain/Ergo/ErgoPayoutHandler.cs | 30 ++++++++----------- src/Miningcore/Blockchain/Ergo/ErgoPool.cs | 14 ++++----- 3 files changed, 32 insertions(+), 37 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs index fd3fcafdc..d1b4bc54e 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs @@ -15,7 +15,6 @@ using Miningcore.Time; using Miningcore.Util; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using Contract = Miningcore.Contracts.Contract; using static Miningcore.Util.ActionUtils; @@ -40,7 +39,7 @@ public ErgoJobManager( } private ErgoCoinTemplate coin; - private ErgoClient daemon; + private ErgoClient ergoClient; private string network; private readonly List<ErgoJob> validJobs = new(); private int maxActiveJobs = 4; @@ -179,7 +178,7 @@ private async Task<WorkMessage> GetBlockTemplateAsync() { logger.LogInvoke(); - var work = await daemon.MiningRequestBlockCandidateAsync(CancellationToken.None); + var work = await ergoClient.MiningRequestBlockCandidateAsync(CancellationToken.None); return work; } @@ -193,7 +192,7 @@ private WorkMessage GetBlockTemplateFromJson(string json) private async Task ShowDaemonSyncProgressAsync() { - var info = await Guard(() => daemon.GetNodeInfoAsync(), + var info = await Guard(() => ergoClient.GetNodeInfoAsync(), ex => logger.Debug(ex)); if(info?.FullHeight.HasValue == true && info.HeadersHeight.HasValue) @@ -229,7 +228,7 @@ private async Task<bool> SubmitBlockAsync(Share share, ErgoJob job, string nonce { try { - await daemon.MiningSubmitSolutionAsync(new PowSolutions + await ergoClient.MiningSubmitSolutionAsync(new PowSolutions { N = nonce, }); @@ -363,7 +362,7 @@ public async Task<bool> ValidateAddress(string address, CancellationToken ct) if(string.IsNullOrEmpty(address)) return false; - var validity = await Guard(() => daemon.CheckAddressValidityAsync(address, ct), + var validity = await Guard(() => ergoClient.CheckAddressValidityAsync(address, ct), ex => logger.Debug(ex)); return validity?.IsValid == true; @@ -379,13 +378,13 @@ protected override async Task PostStartInitAsync(CancellationToken ct) if(string.IsNullOrEmpty(poolConfig.Address)) logger.ThrowLogPoolStartupException($"Pool address is not configured"); - var validity = await Guard(() => daemon.CheckAddressValidityAsync(poolConfig.Address, ct), + var validity = await Guard(() => ergoClient.CheckAddressValidityAsync(poolConfig.Address, ct), ex=> logger.ThrowLogPoolStartupException($"Error validating pool address: {ex}")); if(!validity.IsValid) logger.ThrowLogPoolStartupException($"Daemon reports pool address {poolConfig.Address} as invalid: {validity.Error}"); - var info = await Guard(() => daemon.GetNodeInfoAsync(ct), + var info = await Guard(() => ergoClient.GetNodeInfoAsync(ct), ex=> logger.ThrowLogPoolStartupException($"Daemon reports: {ex.Message}")); blockVersion = info.Parameters.BlockVersion; @@ -401,7 +400,7 @@ protected override async Task PostStartInitAsync(CancellationToken ct) if(clusterConfig.PaymentProcessing?.Enabled == true && poolConfig.PaymentProcessing?.Enabled == true) { // check configured address belongs to wallet - var walletAddresses = await daemon.WalletAddressesAsync(ct); + var walletAddresses = await ergoClient.WalletAddressesAsync(ct); if(!walletAddresses.Contains(poolConfig.Address)) logger.ThrowLogPoolStartupException($"Pool address {poolConfig.Address} is not controlled by wallet"); @@ -430,12 +429,12 @@ public override void Configure(PoolConfig poolConfig, ClusterConfig clusterConfi protected override void ConfigureDaemons() { - daemon = ErgoClientFactory.CreateClient(poolConfig, clusterConfig, logger); + ergoClient = ErgoClientFactory.CreateClient(poolConfig, clusterConfig, logger); } protected override async Task<bool> AreDaemonsHealthyAsync(CancellationToken ct) { - var info = await Guard(() => daemon.GetNodeInfoAsync(ct), + var info = await Guard(() => ergoClient.GetNodeInfoAsync(ct), ex=> logger.ThrowLogPoolStartupException($"Daemon reports: {ex.Message}")); if(info?.IsMining != true) @@ -446,7 +445,7 @@ protected override async Task<bool> AreDaemonsHealthyAsync(CancellationToken ct) protected override async Task<bool> AreDaemonsConnectedAsync(CancellationToken ct) { - var info = await Guard(() => daemon.GetNodeInfoAsync(ct), + var info = await Guard(() => ergoClient.GetNodeInfoAsync(ct), ex=> logger.Debug(ex)); return info?.PeersCount > 0; @@ -458,7 +457,7 @@ protected override async Task EnsureDaemonsSynchedAsync(CancellationToken ct) while(true) { - var info = await Guard(() => daemon.GetNodeInfoAsync(ct), + var info = await Guard(() => ergoClient.GetNodeInfoAsync(ct), ex=> logger.Debug(ex)); var isSynched = info?.FullHeight.HasValue == true && info?.HeadersHeight.HasValue == true && diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index 605453628..9ebaf59f9 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -35,7 +35,6 @@ public ErgoPayoutHandler( IBlockRepository blockRepo, IBalanceRepository balanceRepo, IPaymentRepository paymentRepo, - IHttpClientFactory httpClientFactory, IMasterClock clock, IMessageBus messageBus) : base(cf, mapper, shareRepo, blockRepo, balanceRepo, paymentRepo, clock, messageBus) @@ -43,16 +42,13 @@ public ErgoPayoutHandler( Contract.RequiresNonNull(ctx, nameof(ctx)); Contract.RequiresNonNull(balanceRepo, nameof(balanceRepo)); Contract.RequiresNonNull(paymentRepo, nameof(paymentRepo)); - Contract.RequiresNonNull(httpClientFactory, nameof(httpClientFactory)); this.ctx = ctx; - this.httpClientFactory = httpClientFactory; } protected readonly IComponentContext ctx; - protected ErgoClient daemon; + protected ErgoClient ergoClient; private ErgoPoolConfigExtra extraPoolConfig; - private readonly IHttpClientFactory httpClientFactory; private string network; private ErgoPaymentProcessingConfigExtra extraPoolPaymentProcessingConfig; @@ -84,7 +80,7 @@ private async Task UnlockWallet(CancellationToken ct) var walletPassword = extraPoolPaymentProcessingConfig.WalletPassword ?? string.Empty; - await Guard(() => daemon.WalletUnlockAsync(new Body4 {Pass = walletPassword}, ct), ex => + await Guard(() => ergoClient.WalletUnlockAsync(new Body4 {Pass = walletPassword}, ct), ex => { if (ex is ApiException<ApiError> apiException) { @@ -105,7 +101,7 @@ private async Task LockWallet(CancellationToken ct) { logger.Info(() => $"[{LogCategory}] Locking wallet"); - await Guard(() => daemon.WalletLockAsync(ct), + await Guard(() => ergoClient.WalletLockAsync(ct), ex => ReportAndRethrowApiError("Failed to lock wallet", ex)); logger.Info(() => $"[{LogCategory}] Wallet locked"); @@ -125,10 +121,10 @@ public virtual async Task ConfigureAsync(ClusterConfig clusterConfig, PoolConfig extraPoolConfig = poolConfig.Extra.SafeExtensionDataAs<ErgoPoolConfigExtra>(); extraPoolPaymentProcessingConfig = poolConfig.PaymentProcessing.Extra.SafeExtensionDataAs<ErgoPaymentProcessingConfigExtra>(); - daemon = ErgoClientFactory.CreateClient(poolConfig, clusterConfig, null); + ergoClient = ErgoClientFactory.CreateClient(poolConfig, clusterConfig, null); // detect chain - var info = await daemon.GetNodeInfoAsync(ct); + var info = await ergoClient.GetNodeInfoAsync(ct); network = ErgoConstants.RegexChain.Match(info.Name).Groups[1].Value.ToLower(); } @@ -145,8 +141,8 @@ public virtual async Task<Block[]> ClassifyBlocksAsync(IMiningPool pool, Block[] var pageCount = (int) Math.Ceiling(blocks.Length / (double) pageSize); var result = new List<Block>(); var minConfirmations = extraPoolConfig?.MinimumConfirmations ?? (network == "mainnet" ? 720 : 72); - var minerRewardsPubKey = await daemon.MiningReadMinerRewardPubkeyAsync(ct); - var minerRewardsAddress = await daemon.MiningReadMinerRewardAddressAsync(ct); + var minerRewardsPubKey = await ergoClient.MiningReadMinerRewardPubkeyAsync(ct); + var minerRewardsAddress = await ergoClient.MiningReadMinerRewardAddressAsync(ct); for(var i = 0; i < pageCount; i++) { @@ -157,7 +153,7 @@ public virtual async Task<Block[]> ClassifyBlocksAsync(IMiningPool pool, Block[] .ToArray(); // fetch header ids for blocks in page - var headerBatch = page.Select(block => daemon.GetFullBlockAtAsync((int) block.BlockHeight, ct)).ToArray(); + var headerBatch = page.Select(block => ergoClient.GetFullBlockAtAsync((int) block.BlockHeight, ct)).ToArray(); await Guard(()=> Task.WhenAll(headerBatch), ex=> logger.Debug(ex)); @@ -180,7 +176,7 @@ await Guard(()=> Task.WhenAll(headerBatch), var headerIds = headerTask.Result; // fetch blocks - var blockBatch = headerIds.Select(x=> daemon.GetFullBlockByIdAsync(x, ct)).ToArray(); + var blockBatch = headerIds.Select(x=> ergoClient.GetFullBlockByIdAsync(x, ct)).ToArray(); await Guard(()=> Task.WhenAll(blockBatch), ex=> logger.Debug(ex)); @@ -218,7 +214,7 @@ await Guard(()=> Task.WhenAll(blockBatch), foreach(var blockTx in fullBlock.BlockTransactions.Transactions) { - var walletTx = await Guard(()=> daemon.WalletGetTransactionAsync(blockTx.Id, ct)); + var walletTx = await Guard(()=> ergoClient.WalletGetTransactionAsync(blockTx.Id, ct)); var coinbaseOutput = walletTx?.Outputs?.FirstOrDefault(x => x.Address == minerRewardsAddress.RewardAddress); if(coinbaseOutput != null) @@ -315,7 +311,7 @@ public virtual async Task PayoutAsync(IMiningPool pool, Balance[] balances, Canc logger.Info(() => $"[{LogCategory}] Paying {FormatAmount(balances.Sum(x => x.Amount))} to {balances.Length} addresses"); // get wallet status - var status = await daemon.GetWalletStatusAsync(ct); + var status = await ergoClient.GetWalletStatusAsync(ct); if(!status.IsInitialized) throw new PaymentException($"Wallet is not initialized"); @@ -324,7 +320,7 @@ public virtual async Task PayoutAsync(IMiningPool pool, Balance[] balances, Canc await UnlockWallet(ct); // get balance - var walletBalances = await daemon.WalletBalancesAsync(ct); + var walletBalances = await ergoClient.WalletBalancesAsync(ct); logger.Info(() => $"[{LogCategory}] Current wallet balance is {FormatAmount(walletBalances.Balance / ErgoConstants.SmallestUnit)}"); // Create request batch @@ -334,7 +330,7 @@ public virtual async Task PayoutAsync(IMiningPool pool, Balance[] balances, Canc Value = (long) (x.Value * ErgoConstants.SmallestUnit), }).ToArray(); - var txId = await Guard(()=> daemon.WalletPaymentTransactionGenerateAndSendAsync(requests, ct), ex => + var txId = await Guard(()=> ergoClient.WalletPaymentTransactionGenerateAndSendAsync(requests, ct), ex => { if(ex is ApiException<ApiError> apiException) { diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs index 4b44903c5..ee66db550 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs @@ -58,15 +58,15 @@ protected virtual async Task OnSubscribeAsync(StratumConnection connection, Time var requestParams = request.ParamsAs<string[]>(); var data = new object[] + { + new object[] { - new object[] - { - new object[] { BitcoinStratumMethods.SetDifficulty, connection.ConnectionId }, - new object[] { BitcoinStratumMethods.MiningNotify, connection.ConnectionId } - } + new object[] { BitcoinStratumMethods.SetDifficulty, connection.ConnectionId }, + new object[] { BitcoinStratumMethods.MiningNotify, connection.ConnectionId } } - .Concat(manager.GetSubscriberData(connection)) - .ToArray(); + } + .Concat(manager.GetSubscriberData(connection)) + .ToArray(); await connection.RespondAsync(data, request.Id); From 3148ab107fba982250bf50b95d2b2560d565ff48 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Sun, 18 Jul 2021 14:58:25 +0200 Subject: [PATCH 090/145] Fix release build --- src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs b/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs index cf9a0c120..8576a14e8 100644 --- a/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs +++ b/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs @@ -442,7 +442,7 @@ public async Task PayoutAsync(IMiningPool pool, Balance[] balances, Cancellation var coin = poolConfig.Template.As<CryptonoteCoinTemplate>(); #if !DEBUG // ensure we have peers - var infoResponse = await daemon.ExecuteCmdAsync<GetInfoResponse>(logger, CNC.GetInfo, ct); + var infoResponse = await rpcClient.ExecuteAsync<GetInfoResponse>(logger, CNC.GetInfo, ct); if (infoResponse.Error != null || infoResponse.Response == null || infoResponse.Response.IncomingConnectionsCount + infoResponse.Response.OutgoingConnectionsCount < 3) { From ed63588cdb3064ea33b9f96132c31aa8cd6248a8 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Sun, 18 Jul 2021 14:58:25 +0200 Subject: [PATCH 091/145] Fix release build --- src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs b/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs index cf9a0c120..8576a14e8 100644 --- a/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs +++ b/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs @@ -442,7 +442,7 @@ public async Task PayoutAsync(IMiningPool pool, Balance[] balances, Cancellation var coin = poolConfig.Template.As<CryptonoteCoinTemplate>(); #if !DEBUG // ensure we have peers - var infoResponse = await daemon.ExecuteCmdAsync<GetInfoResponse>(logger, CNC.GetInfo, ct); + var infoResponse = await rpcClient.ExecuteAsync<GetInfoResponse>(logger, CNC.GetInfo, ct); if (infoResponse.Error != null || infoResponse.Response == null || infoResponse.Response.IncomingConnectionsCount + infoResponse.Response.OutgoingConnectionsCount < 3) { From 8c0137140f61ec0fd81a81bff03ea41edd0ef811 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Sun, 18 Jul 2021 15:08:22 +0200 Subject: [PATCH 092/145] Format --- .../Blockchain/Cryptonote/CryptonotePayoutHandler.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs b/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs index 8576a14e8..73b2fcbf3 100644 --- a/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs +++ b/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs @@ -425,7 +425,8 @@ public Task CalculateBlockEffortAsync(IMiningPool pool, Block block, double accu return Task.FromResult(true); } - public override async Task<decimal> UpdateBlockRewardBalancesAsync(IDbConnection con, IDbTransaction tx, IMiningPool pool, Block block, CancellationToken ct) + public override async Task<decimal> UpdateBlockRewardBalancesAsync(IDbConnection con, IDbTransaction tx, + IMiningPool pool, Block block, CancellationToken ct) { var blockRewardRemaining = await base.UpdateBlockRewardBalancesAsync(con, tx, pool, block, ct); From 1150bfff3c8b9496cd6e5690427be9842c7d98a2 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Sun, 18 Jul 2021 15:13:13 +0200 Subject: [PATCH 093/145] Refactor --- .../Bitcoin/BitcoinJobManagerBase.cs | 20 +++++++++---------- .../Cryptonote/CryptonoteJobManager.cs | 1 - .../Equihash/EquihashPayoutHandler.cs | 7 ++++--- .../Blockchain/Ethereum/EthereumJobManager.cs | 14 ++++++------- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs index c5df3e35c..8a9262c35 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs @@ -442,14 +442,14 @@ protected override async Task PostStartInitAsync(CancellationToken ct) new RpcRequest(BitcoinCommands.GetAddressInfo, new[] { poolConfig.Address }), }; - var results = await rpcClient.ExecuteBatchAsync(logger, ct, requests); + var responses = await rpcClient.ExecuteBatchAsync(logger, ct, requests); - if(results.Any(x => x.Error != null)) + if(responses.Any(x => x.Error != null)) { - var resultList = results.ToList(); + var resultList = responses.ToList(); // filter out optional RPCs - var errors = results + var errors = responses .Where((x, i) => x.Error != null && requests[i].Method != BitcoinCommands.SubmitBlock && requests[i].Method != BitcoinCommands.GetAddressInfo) @@ -460,12 +460,12 @@ protected override async Task PostStartInitAsync(CancellationToken ct) } // extract results - var validateAddressResponse = results[0].Error == null ? results[0].Response.ToObject<ValidateAddressResponse>() : null; - var submitBlockResponse = results[1]; - var blockchainInfoResponse = !hasLegacyDaemon ? results[2].Response.ToObject<BlockchainInfo>() : null; - var daemonInfoResponse = hasLegacyDaemon ? results[2].Response.ToObject<DaemonInfo>() : null; - var difficultyResponse = results[3].Response.ToObject<JToken>(); - var addressInfoResponse = results[4].Error == null ? results[4].Response.ToObject<AddressInfo>() : null; + var validateAddressResponse = responses[0].Error == null ? responses[0].Response.ToObject<ValidateAddressResponse>() : null; + var submitBlockResponse = responses[1]; + var blockchainInfoResponse = !hasLegacyDaemon ? responses[2].Response.ToObject<BlockchainInfo>() : null; + var daemonInfoResponse = hasLegacyDaemon ? responses[2].Response.ToObject<DaemonInfo>() : null; + var difficultyResponse = responses[3].Response.ToObject<JToken>(); + var addressInfoResponse = responses[4].Error == null ? responses[4].Response.ToObject<AddressInfo>() : null; // chain detection if(!hasLegacyDaemon) diff --git a/src/Miningcore/Blockchain/Cryptonote/CryptonoteJobManager.cs b/src/Miningcore/Blockchain/Cryptonote/CryptonoteJobManager.cs index 53e986455..e2f50a754 100644 --- a/src/Miningcore/Blockchain/Cryptonote/CryptonoteJobManager.cs +++ b/src/Miningcore/Blockchain/Cryptonote/CryptonoteJobManager.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Net; using System.Reactive; using System.Reactive.Linq; using System.Security.Cryptography; diff --git a/src/Miningcore/Blockchain/Equihash/EquihashPayoutHandler.cs b/src/Miningcore/Blockchain/Equihash/EquihashPayoutHandler.cs index 38d30b286..2a48674bc 100644 --- a/src/Miningcore/Blockchain/Equihash/EquihashPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Equihash/EquihashPayoutHandler.cs @@ -79,15 +79,13 @@ public override async Task PayoutAsync(IMiningPool pool, Balance[] balances, Can else await ShieldCoinbaseEmulatedAsync(ct); - var didUnlockWallet = false; - // send in batches with no more than 50 recipients to avoid running into tx size limits var pageSize = 50; var pageCount = (int) Math.Ceiling(balances.Length / (double) pageSize); for(var i = 0; i < pageCount; i++) { - didUnlockWallet = false; + var didUnlockWallet = false; // get a page full of balances var page = balances @@ -237,6 +235,7 @@ public override async Task PayoutAsync(IMiningPool pool, Balance[] balances, Can // lock wallet logger.Info(() => $"[{LogCategory}] Locking wallet"); + await rpcClient.ExecuteAsync<JToken>(logger, BitcoinCommands.WalletLock, ct); } @@ -309,6 +308,7 @@ private async Task ShieldCoinbaseAsync(CancellationToken ct) } logger.Info(() => $"[{LogCategory}] Waiting for shielding operation completion: {operationId}"); + await Task.Delay(TimeSpan.FromSeconds(10), ct); } } @@ -407,6 +407,7 @@ private async Task ShieldCoinbaseEmulatedAsync(CancellationToken ct) } logger.Info(() => $"[{LogCategory}] Waiting for shielding transfer completion: {operationId}"); + await Task.Delay(TimeSpan.FromSeconds(10), ct); } } diff --git a/src/Miningcore/Blockchain/Ethereum/EthereumJobManager.cs b/src/Miningcore/Blockchain/Ethereum/EthereumJobManager.cs index a430c3fb5..ae983373c 100644 --- a/src/Miningcore/Blockchain/Ethereum/EthereumJobManager.cs +++ b/src/Miningcore/Blockchain/Ethereum/EthereumJobManager.cs @@ -464,18 +464,18 @@ protected override async Task EnsureDaemonsSynchedAsync(CancellationToken ct) protected override async Task PostStartInitAsync(CancellationToken ct) { - var commands = new[] + var requests = new[] { new RpcRequest(EC.GetNetVersion), new RpcRequest(EC.GetAccounts), new RpcRequest(EC.GetCoinbase), }; - var results = await rpcClient.ExecuteBatchAsync(logger, ct, commands); + var responses = await rpcClient.ExecuteBatchAsync(logger, ct, requests); - if(results.Any(x => x.Error != null)) + if(responses.Any(x => x.Error != null)) { - var errors = results.Take(3).Where(x => x.Error != null) + var errors = responses.Take(3).Where(x => x.Error != null) .ToArray(); if(errors.Any()) @@ -483,9 +483,9 @@ protected override async Task PostStartInitAsync(CancellationToken ct) } // extract results - var netVersion = results[0].Response.ToObject<string>(); - var accounts = results[1].Response.ToObject<string[]>(); - var coinbase = results[2].Response.ToObject<string>(); + var netVersion = responses[0].Response.ToObject<string>(); + var accounts = responses[1].Response.ToObject<string[]>(); + var coinbase = responses[2].Response.ToObject<string>(); var gethChain = extraPoolConfig?.ChainTypeOverride ?? "Ethereum"; EthereumUtils.DetectNetworkAndChain(netVersion, gethChain, out networkType, out chainType); From 199e4a85372d9f688d38f04f64b4e13f0b55f8e2 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Sun, 18 Jul 2021 15:20:01 +0200 Subject: [PATCH 094/145] ref --- src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs index 8a9262c35..6961bb8f1 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs @@ -446,8 +446,6 @@ protected override async Task PostStartInitAsync(CancellationToken ct) if(responses.Any(x => x.Error != null)) { - var resultList = responses.ToList(); - // filter out optional RPCs var errors = responses .Where((x, i) => x.Error != null && @@ -497,8 +495,8 @@ protected override async Task PostStartInitAsync(CancellationToken ct) if(clusterConfig.PaymentProcessing?.Enabled == true && poolConfig.PaymentProcessing?.Enabled == true) { // ensure pool owns wallet - //if (!validateAddressResponse.IsMine) - // logger.ThrowLogPoolStartupException($"Daemon does not own pool-address '{poolConfig.Address}'"); + if(validateAddressResponse is {IsMine: false} || addressInfoResponse is {IsMine: false}) + logger.Warn(()=> $"Node does not own pool-address '{poolConfig.Address}'"); ConfigureRewards(); } From f72cd54678b432a420cd350ab665a20ebd5e35c6 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Sun, 18 Jul 2021 15:21:06 +0200 Subject: [PATCH 095/145] WIP --- src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs index 6961bb8f1..1bafbd8e1 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs @@ -496,7 +496,7 @@ protected override async Task PostStartInitAsync(CancellationToken ct) { // ensure pool owns wallet if(validateAddressResponse is {IsMine: false} || addressInfoResponse is {IsMine: false}) - logger.Warn(()=> $"Node does not own pool-address '{poolConfig.Address}'"); + logger.Warn(()=> $"Daemon does not own pool-address '{poolConfig.Address}'"); ConfigureRewards(); } From 5a8382310da4155cbb04d8d87f2467b0dc425bea Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Sun, 18 Jul 2021 16:19:41 +0200 Subject: [PATCH 096/145] WIP --- .../Blockchain/Ergo/ErgoConstants.cs | 16 +- src/Miningcore/Blockchain/Ergo/ErgoJob.cs | 194 +++++++----------- 2 files changed, 72 insertions(+), 138 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs b/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs index ab314a914..a39d553ca 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs @@ -23,20 +23,8 @@ public static class ErgoConstants public static Regex RegexChain = new("ergo-([^-]+)-.+", RegexOptions.Compiled); - public static readonly byte[] M = Enumerable.Range(0, 1024) - .SelectMany(x => - { - const double max = 4294967296d; + public static BigInteger BigMaxValue = BigInteger.Pow(2,256); - var top = (uint) Math.Floor(x / max); - var rem = (uint) (x - top * max); - - var result = BitConverter.GetBytes(rem) - .Concat(BitConverter.GetBytes(top)) - .Reverse() - .ToArray(); - - return result; - }).ToArray(); + public static byte[] M = (new byte[1024]).Select((x, y) => BitConverter.GetBytes((ulong) y).Reverse()).SelectMany(byteArr => byteArr).ToArray(); } } diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs index 12a0daf47..92d929555 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Concurrent; using System.Globalization; -using System.IO; using System.Linq; using System.Text; using Miningcore.Contracts; @@ -27,31 +26,28 @@ public class ErgoJob private int extraNonceSize; private static readonly uint nBase = (uint) Math.Pow(2, 26); + private BigInteger N; private Target bTarget; private BigInteger b; private const uint IncreaseStart = 600 * 1024; private const uint IncreasePeriodForN = 50 * 1024; private const uint NIncreasementHeightMax = 9216000; - public static uint CalcN(uint height) + private static BigInteger GetN(ulong height) { - height = Math.Min(NIncreasementHeightMax, height); - - switch (height) + height = Math.Min(4198400, height); + if(height < 600 * 1024) { - case < IncreaseStart: - return nBase; - case >= NIncreasementHeightMax: - return 2147387550; + return BigInteger.Pow(2, 26); + } + else + { + var res = BigInteger.Pow(2, 26); + var iterationsNumber = (height - 600 * 1024) / 50 * 1024 + 1; + for(var i = 0ul; i < iterationsNumber; i++) + res = res / new BigInteger(100) * new BigInteger(105); + return res; } - - var step = nBase; - var iterationsNumber = (height - IncreaseStart) / IncreasePeriodForN + 1; - - for(var i = 0; i < iterationsNumber; i++) - step = step / 100 * 105; - - return step; } protected bool RegisterSubmit(string nTime, string nonce) @@ -64,111 +60,92 @@ protected bool RegisterSubmit(string nTime, string nonce) return submissions.TryAdd(key, true); } - protected virtual byte[] SerializeCoinbase(string msg, string nonce) + public object[] GetJobParams(bool isNew) { - using(var stream = new MemoryStream()) - { - stream.Write(msg.HexToByteArray()); - stream.Write(nonce.HexToByteArray()); - - return stream.ToArray(); - } + jobParams[^1] = isNew; + return jobParams; } - private BigInteger[] GenIndexes(byte[] seed, uint height) + public virtual Share ProcessShare(StratumConnection worker, string extraNonce2, string nTime, string nonce) { - // hash seed - Span<byte> hash = stackalloc byte[32]; - hasher.Digest(seed, hash); + Contract.RequiresNonNull(worker, nameof(worker)); + Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(extraNonce2), $"{nameof(extraNonce2)} must not be empty"); + Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(nTime), $"{nameof(nTime)} must not be empty"); + Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(nonce), $"{nameof(nonce)} must not be empty"); - // duplicate - Span<byte> extendedHash = stackalloc byte[64]; - hash.CopyTo(extendedHash); - hash.CopyTo(extendedHash.Slice(32, 32)); + var context = worker.ContextAs<ErgoWorkerContext>(); - // map indexes - var result = new BigInteger[32]; + // validate nonce + if(nonce.Length != context.ExtraNonce1.Length + extraNonceSize * 2) + throw new StratumException(StratumError.Other, "incorrect size of nonce"); - for(var i = 0; i < 32; i++) - { - var x = BitConverter.ToUInt32(extendedHash.Slice(i, 4)).ToBigEndian(); - var y = CalcN(height); - result[i] = x % y; - } + if(!nonce.StartsWith(context.ExtraNonce1)) + throw new StratumException(StratumError.Other, $"incorrect extraNonce2 in nonce (expected {context.ExtraNonce1}, got {nonce.Substring(0, Math.Min(nonce.Length, context.ExtraNonce1.Length))})"); - return result; - } + // currently unused + if(nTime == "undefined") + nTime = string.Empty; - protected virtual Share ProcessShareInternal(StratumConnection worker, string nonce) - { - var context = worker.ContextAs<ErgoWorkerContext>(); + // dupe check + if(!RegisterSubmit(nTime, nonce)) + throw new StratumException(StratumError.DuplicateShare, $"duplicate share"); - // hash coinbase - var coinbase = SerializeCoinbase(BlockTemplate.Msg, nonce); - Span<byte> hashResult = stackalloc byte[32]; - hasher.Digest(coinbase, hashResult); - - // calculate i - var slice = hashResult.Slice(24, 8); - var tmp2 = new BigInteger(slice, true, true) % CalcN(Height); - var i = tmp2.ToByteArray(false, true).PadFront(0, 4); - - // calculate e - var h = new BigInteger(Height).ToByteArray(true, true).PadFront(0, 4); - var ihM = i.Concat(h).Concat(ErgoConstants.M).ToArray(); - hasher.Digest(ihM, hashResult); - var e = hashResult[1..].ToArray(); - - // calculate j - var eCoinbase = e.Concat(coinbase).ToArray(); - var jTmp = GenIndexes(eCoinbase, Height); - var j = jTmp.Select(x => x.ToByteArray(true, true).PadFront(0, 4)).ToArray(); - - // calculate f - var f = j.Select(x => - { - var buf = x.Concat(h).Concat(ErgoConstants.M).ToArray(); + var h = BitConverter.GetBytes(Height).Reverse().Skip(4); + var coinbaseBuffer = (BlockTemplate.Msg + nonce).HexToByteArray(); + + var firstB = new byte[32]; + hasher.Digest(coinbaseBuffer, firstB, 32); + var formula = new BigInteger(firstB.Skip(24).ToArray(), true, true) % N; + var i = new byte[4]; + Array.Copy(formula.ToByteArray(), i, formula.ToByteArray().Length); + i.ReverseInPlace(); - // hash it - Span<byte> hash = stackalloc byte[32]; - hasher.Digest(buf, hash); + var e = new byte[32]; + hasher.Digest(i.Concat(h).Concat(ErgoConstants.M).ToArray(), e, 32); - // extract 31 bytes at end - return new BigInteger(hash[1..], true, true); + var hash = new byte[32]; + hasher.Digest(e.Skip(1).Concat(coinbaseBuffer).ToArray(), hash, 32); + var extendedHash = hash.Concat(hash); + + var f = new object[32].Select((x, y) => (new BigInteger(extendedHash.Skip(y).Take(4).ToArray(), true, true) % N).ToByteArray()).Select(x => + { + var buf = new byte[32]; + var item = new byte[4]; + Array.Copy(x, item, x.Length); + hasher.Digest(item.Reverse().Concat(h).Concat(ErgoConstants.M).ToArray(), buf, 32); + return new BigInteger(buf.Skip(1).ToArray(), true, true); }).Aggregate((x, y) => x + y); - // calculate fH - var blockHash = f.ToByteArray(true, true).PadFront(0, 32); - hasher.Digest(blockHash, hashResult); - var fh = new BigInteger(hashResult, true, true); - var fhTarget = new Target(fh); + var buf = new byte[32]; + Array.Copy(f.ToByteArray(), buf, f.ToByteArray().Length); + hasher.Digest(buf.ReverseInPlace(), buf); + + var fh = new BigInteger(buf, true, true); - // diff check + var isBlockCandidate = BigInteger.Compare(b, fh) >= 0; + var shareDifficulty = (double) (ErgoConstants.BigMaxValue / fh); var stratumDifficulty = context.Difficulty; - var ratio = fhTarget.Difficulty / stratumDifficulty; + var ratio = shareDifficulty / stratumDifficulty; // test if share meets at least workers current difficulty - if(ratio < 0.99) + if(!isBlockCandidate && ratio < 0.99) { // check if share matched the previous difficulty from before a vardiff retarget if(context.VarDiff?.LastUpdate != null && context.PreviousDifficulty.HasValue) { - ratio = fhTarget.Difficulty / context.PreviousDifficulty.Value; + ratio = shareDifficulty / context.PreviousDifficulty.Value; if(ratio < 0.99) - throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({fhTarget.Difficulty})"); + throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDifficulty})"); // use previous difficulty stratumDifficulty = context.PreviousDifficulty.Value; } else - throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({fhTarget.Difficulty})"); + throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDifficulty})"); } - // check if the share meets the much harder block difficulty (block candidate) - var isBlockCandidate = fh < b; - var result = new Share { BlockHeight = Height, @@ -180,45 +157,12 @@ protected virtual Share ProcessShareInternal(StratumConnection worker, string no { result.IsBlockCandidate = true; - result.BlockHash = blockHash.ToHexString(); + result.BlockHash = buf.ToHexString(); } return result; } - public object[] GetJobParams(bool isNew) - { - jobParams[^1] = isNew; - return jobParams; - } - - public virtual Share ProcessShare(StratumConnection worker, string extraNonce2, string nTime, string nonce) - { - Contract.RequiresNonNull(worker, nameof(worker)); - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(extraNonce2), $"{nameof(extraNonce2)} must not be empty"); - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(nTime), $"{nameof(nTime)} must not be empty"); - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(nonce), $"{nameof(nonce)} must not be empty"); - - var context = worker.ContextAs<ErgoWorkerContext>(); - - // validate nonce - if(nonce.Length != context.ExtraNonce1.Length + extraNonceSize * 2) - throw new StratumException(StratumError.Other, "incorrect size of nonce"); - - if(!nonce.StartsWith(context.ExtraNonce1)) - throw new StratumException(StratumError.Other, $"incorrect extraNonce2 in nonce (expected {context.ExtraNonce1}, got {nonce.Substring(0, Math.Min(nonce.Length, context.ExtraNonce1.Length))})"); - - // currently unused - if(nTime == "undefined") - nTime = string.Empty; - - // dupe check - if(!RegisterSubmit(nTime, nonce)) - throw new StratumException(StratumError.DuplicateShare, $"duplicate share"); - - return ProcessShareInternal(worker, nonce); - } - public void Init(WorkMessage blockTemplate, int blockVersion, int extraNonceSize, string jobId) { this.extraNonceSize = extraNonceSize; @@ -229,6 +173,8 @@ public void Init(WorkMessage blockTemplate, int blockVersion, int extraNonceSize b = BigInteger.Parse(BlockTemplate.B, NumberStyles.Integer); bTarget = new Target(b); + N = GetN(Height); //Som + jobParams = new object[] { JobId, From 863858ede3066051b30c817ce0317911050f176d Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Sun, 18 Jul 2021 22:21:27 +0200 Subject: [PATCH 097/145] Revert "WIP" This reverts commit 5a8382310da4155cbb04d8d87f2467b0dc425bea. --- .../Blockchain/Ergo/ErgoConstants.cs | 16 +- src/Miningcore/Blockchain/Ergo/ErgoJob.cs | 194 +++++++++++------- 2 files changed, 138 insertions(+), 72 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs b/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs index a39d553ca..ab314a914 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs @@ -23,8 +23,20 @@ public static class ErgoConstants public static Regex RegexChain = new("ergo-([^-]+)-.+", RegexOptions.Compiled); - public static BigInteger BigMaxValue = BigInteger.Pow(2,256); + public static readonly byte[] M = Enumerable.Range(0, 1024) + .SelectMany(x => + { + const double max = 4294967296d; - public static byte[] M = (new byte[1024]).Select((x, y) => BitConverter.GetBytes((ulong) y).Reverse()).SelectMany(byteArr => byteArr).ToArray(); + var top = (uint) Math.Floor(x / max); + var rem = (uint) (x - top * max); + + var result = BitConverter.GetBytes(rem) + .Concat(BitConverter.GetBytes(top)) + .Reverse() + .ToArray(); + + return result; + }).ToArray(); } } diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs index 92d929555..12a0daf47 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Globalization; +using System.IO; using System.Linq; using System.Text; using Miningcore.Contracts; @@ -26,28 +27,31 @@ public class ErgoJob private int extraNonceSize; private static readonly uint nBase = (uint) Math.Pow(2, 26); - private BigInteger N; private Target bTarget; private BigInteger b; private const uint IncreaseStart = 600 * 1024; private const uint IncreasePeriodForN = 50 * 1024; private const uint NIncreasementHeightMax = 9216000; - private static BigInteger GetN(ulong height) + public static uint CalcN(uint height) { - height = Math.Min(4198400, height); - if(height < 600 * 1024) - { - return BigInteger.Pow(2, 26); - } - else + height = Math.Min(NIncreasementHeightMax, height); + + switch (height) { - var res = BigInteger.Pow(2, 26); - var iterationsNumber = (height - 600 * 1024) / 50 * 1024 + 1; - for(var i = 0ul; i < iterationsNumber; i++) - res = res / new BigInteger(100) * new BigInteger(105); - return res; + case < IncreaseStart: + return nBase; + case >= NIncreasementHeightMax: + return 2147387550; } + + var step = nBase; + var iterationsNumber = (height - IncreaseStart) / IncreasePeriodForN + 1; + + for(var i = 0; i < iterationsNumber; i++) + step = step / 100 * 105; + + return step; } protected bool RegisterSubmit(string nTime, string nonce) @@ -60,92 +64,111 @@ protected bool RegisterSubmit(string nTime, string nonce) return submissions.TryAdd(key, true); } - public object[] GetJobParams(bool isNew) + protected virtual byte[] SerializeCoinbase(string msg, string nonce) { - jobParams[^1] = isNew; - return jobParams; + using(var stream = new MemoryStream()) + { + stream.Write(msg.HexToByteArray()); + stream.Write(nonce.HexToByteArray()); + + return stream.ToArray(); + } } - public virtual Share ProcessShare(StratumConnection worker, string extraNonce2, string nTime, string nonce) + private BigInteger[] GenIndexes(byte[] seed, uint height) { - Contract.RequiresNonNull(worker, nameof(worker)); - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(extraNonce2), $"{nameof(extraNonce2)} must not be empty"); - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(nTime), $"{nameof(nTime)} must not be empty"); - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(nonce), $"{nameof(nonce)} must not be empty"); + // hash seed + Span<byte> hash = stackalloc byte[32]; + hasher.Digest(seed, hash); - var context = worker.ContextAs<ErgoWorkerContext>(); + // duplicate + Span<byte> extendedHash = stackalloc byte[64]; + hash.CopyTo(extendedHash); + hash.CopyTo(extendedHash.Slice(32, 32)); - // validate nonce - if(nonce.Length != context.ExtraNonce1.Length + extraNonceSize * 2) - throw new StratumException(StratumError.Other, "incorrect size of nonce"); - - if(!nonce.StartsWith(context.ExtraNonce1)) - throw new StratumException(StratumError.Other, $"incorrect extraNonce2 in nonce (expected {context.ExtraNonce1}, got {nonce.Substring(0, Math.Min(nonce.Length, context.ExtraNonce1.Length))})"); - - // currently unused - if(nTime == "undefined") - nTime = string.Empty; + // map indexes + var result = new BigInteger[32]; - // dupe check - if(!RegisterSubmit(nTime, nonce)) - throw new StratumException(StratumError.DuplicateShare, $"duplicate share"); + for(var i = 0; i < 32; i++) + { + var x = BitConverter.ToUInt32(extendedHash.Slice(i, 4)).ToBigEndian(); + var y = CalcN(height); + result[i] = x % y; + } - var h = BitConverter.GetBytes(Height).Reverse().Skip(4); - var coinbaseBuffer = (BlockTemplate.Msg + nonce).HexToByteArray(); + return result; + } - var firstB = new byte[32]; - hasher.Digest(coinbaseBuffer, firstB, 32); - var formula = new BigInteger(firstB.Skip(24).ToArray(), true, true) % N; - var i = new byte[4]; - Array.Copy(formula.ToByteArray(), i, formula.ToByteArray().Length); - i.ReverseInPlace(); + protected virtual Share ProcessShareInternal(StratumConnection worker, string nonce) + { + var context = worker.ContextAs<ErgoWorkerContext>(); - var e = new byte[32]; - hasher.Digest(i.Concat(h).Concat(ErgoConstants.M).ToArray(), e, 32); + // hash coinbase + var coinbase = SerializeCoinbase(BlockTemplate.Msg, nonce); + Span<byte> hashResult = stackalloc byte[32]; + hasher.Digest(coinbase, hashResult); + + // calculate i + var slice = hashResult.Slice(24, 8); + var tmp2 = new BigInteger(slice, true, true) % CalcN(Height); + var i = tmp2.ToByteArray(false, true).PadFront(0, 4); + + // calculate e + var h = new BigInteger(Height).ToByteArray(true, true).PadFront(0, 4); + var ihM = i.Concat(h).Concat(ErgoConstants.M).ToArray(); + hasher.Digest(ihM, hashResult); + var e = hashResult[1..].ToArray(); + + // calculate j + var eCoinbase = e.Concat(coinbase).ToArray(); + var jTmp = GenIndexes(eCoinbase, Height); + var j = jTmp.Select(x => x.ToByteArray(true, true).PadFront(0, 4)).ToArray(); + + // calculate f + var f = j.Select(x => + { + var buf = x.Concat(h).Concat(ErgoConstants.M).ToArray(); - var hash = new byte[32]; - hasher.Digest(e.Skip(1).Concat(coinbaseBuffer).ToArray(), hash, 32); - var extendedHash = hash.Concat(hash); + // hash it + Span<byte> hash = stackalloc byte[32]; + hasher.Digest(buf, hash); - var f = new object[32].Select((x, y) => (new BigInteger(extendedHash.Skip(y).Take(4).ToArray(), true, true) % N).ToByteArray()).Select(x => - { - var buf = new byte[32]; - var item = new byte[4]; - Array.Copy(x, item, x.Length); - hasher.Digest(item.Reverse().Concat(h).Concat(ErgoConstants.M).ToArray(), buf, 32); - return new BigInteger(buf.Skip(1).ToArray(), true, true); + // extract 31 bytes at end + return new BigInteger(hash[1..], true, true); }).Aggregate((x, y) => x + y); - var buf = new byte[32]; - Array.Copy(f.ToByteArray(), buf, f.ToByteArray().Length); - hasher.Digest(buf.ReverseInPlace(), buf); - - var fh = new BigInteger(buf, true, true); + // calculate fH + var blockHash = f.ToByteArray(true, true).PadFront(0, 32); + hasher.Digest(blockHash, hashResult); + var fh = new BigInteger(hashResult, true, true); + var fhTarget = new Target(fh); - var isBlockCandidate = BigInteger.Compare(b, fh) >= 0; - var shareDifficulty = (double) (ErgoConstants.BigMaxValue / fh); + // diff check var stratumDifficulty = context.Difficulty; - var ratio = shareDifficulty / stratumDifficulty; + var ratio = fhTarget.Difficulty / stratumDifficulty; // test if share meets at least workers current difficulty - if(!isBlockCandidate && ratio < 0.99) + if(ratio < 0.99) { // check if share matched the previous difficulty from before a vardiff retarget if(context.VarDiff?.LastUpdate != null && context.PreviousDifficulty.HasValue) { - ratio = shareDifficulty / context.PreviousDifficulty.Value; + ratio = fhTarget.Difficulty / context.PreviousDifficulty.Value; if(ratio < 0.99) - throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDifficulty})"); + throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({fhTarget.Difficulty})"); // use previous difficulty stratumDifficulty = context.PreviousDifficulty.Value; } else - throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDifficulty})"); + throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({fhTarget.Difficulty})"); } + // check if the share meets the much harder block difficulty (block candidate) + var isBlockCandidate = fh < b; + var result = new Share { BlockHeight = Height, @@ -157,12 +180,45 @@ public virtual Share ProcessShare(StratumConnection worker, string extraNonce2, { result.IsBlockCandidate = true; - result.BlockHash = buf.ToHexString(); + result.BlockHash = blockHash.ToHexString(); } return result; } + public object[] GetJobParams(bool isNew) + { + jobParams[^1] = isNew; + return jobParams; + } + + public virtual Share ProcessShare(StratumConnection worker, string extraNonce2, string nTime, string nonce) + { + Contract.RequiresNonNull(worker, nameof(worker)); + Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(extraNonce2), $"{nameof(extraNonce2)} must not be empty"); + Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(nTime), $"{nameof(nTime)} must not be empty"); + Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(nonce), $"{nameof(nonce)} must not be empty"); + + var context = worker.ContextAs<ErgoWorkerContext>(); + + // validate nonce + if(nonce.Length != context.ExtraNonce1.Length + extraNonceSize * 2) + throw new StratumException(StratumError.Other, "incorrect size of nonce"); + + if(!nonce.StartsWith(context.ExtraNonce1)) + throw new StratumException(StratumError.Other, $"incorrect extraNonce2 in nonce (expected {context.ExtraNonce1}, got {nonce.Substring(0, Math.Min(nonce.Length, context.ExtraNonce1.Length))})"); + + // currently unused + if(nTime == "undefined") + nTime = string.Empty; + + // dupe check + if(!RegisterSubmit(nTime, nonce)) + throw new StratumException(StratumError.DuplicateShare, $"duplicate share"); + + return ProcessShareInternal(worker, nonce); + } + public void Init(WorkMessage blockTemplate, int blockVersion, int extraNonceSize, string jobId) { this.extraNonceSize = extraNonceSize; @@ -173,8 +229,6 @@ public void Init(WorkMessage blockTemplate, int blockVersion, int extraNonceSize b = BigInteger.Parse(BlockTemplate.B, NumberStyles.Integer); bTarget = new Target(b); - N = GetN(Height); //Som - jobParams = new object[] { JobId, From 16e91f7693bb6bdbf0b2aba356f7c5e297962977 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Sun, 18 Jul 2021 23:01:39 +0200 Subject: [PATCH 098/145] WIP --- .../Blockchain/Ergo/ErgoConstants.cs | 30 +++---------------- src/Miningcore/Blockchain/Ergo/ErgoJob.cs | 16 ++++------ .../Blockchain/Ergo/RPC/ErgoClient.cs | 3 +- 3 files changed, 12 insertions(+), 37 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs b/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs index ab314a914..4d7535a11 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoConstants.cs @@ -1,12 +1,6 @@ using System; -using System.Collections.Generic; -using System.Diagnostics; using System.Linq; -using System.Numerics; -using System.Text; using System.Text.RegularExpressions; -using System.Threading.Tasks; -using NBitcoin; // ReSharper disable InconsistentNaming @@ -14,29 +8,13 @@ namespace Miningcore.Blockchain.Ergo { public static class ErgoConstants { - public const double ArtificialDiffCeiling = 0x10000; - public const uint ShareMultiplier = 256; - public static double Pow2x26 = Math.Pow(2, 26); - public const decimal SmallestUnit = 1000000000; - public static Regex RegexChain = new("ergo-([^-]+)-.+", RegexOptions.Compiled); - public static readonly byte[] M = Enumerable.Range(0, 1024) - .SelectMany(x => - { - const double max = 4294967296d; - - var top = (uint) Math.Floor(x / max); - var rem = (uint) (x - top * max); - - var result = BitConverter.GetBytes(rem) - .Concat(BitConverter.GetBytes(top)) - .Reverse() - .ToArray(); - - return result; - }).ToArray(); + public static byte[] M = Enumerable.Range(0, 1024) + .Select(x => BitConverter.GetBytes((ulong) x).Reverse()) + .SelectMany(byteArr => byteArr) + .ToArray(); } } diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs index 12a0daf47..6c07ca70c 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs @@ -17,7 +17,7 @@ namespace Miningcore.Blockchain.Ergo public class ErgoJob { public WorkMessage BlockTemplate { get; private set; } - public double Difficulty => bTarget.Difficulty; + public double Difficulty { get; private set; } public uint Height => BlockTemplate.Height; public string JobId { get; protected set; } @@ -27,8 +27,6 @@ public class ErgoJob private int extraNonceSize; private static readonly uint nBase = (uint) Math.Pow(2, 26); - private Target bTarget; - private BigInteger b; private const uint IncreaseStart = 600 * 1024; private const uint IncreasePeriodForN = 50 * 1024; private const uint NIncreasementHeightMax = 9216000; @@ -147,8 +145,11 @@ protected virtual Share ProcessShareInternal(StratumConnection worker, string no var stratumDifficulty = context.Difficulty; var ratio = fhTarget.Difficulty / stratumDifficulty; + // check if the share meets the much harder block difficulty (block candidate) + var isBlockCandidate = fh < BlockTemplate.B; + // test if share meets at least workers current difficulty - if(ratio < 0.99) + if(!isBlockCandidate && ratio < 0.99) { // check if share matched the previous difficulty from before a vardiff retarget if(context.VarDiff?.LastUpdate != null && context.PreviousDifficulty.HasValue) @@ -166,9 +167,6 @@ protected virtual Share ProcessShareInternal(StratumConnection worker, string no throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({fhTarget.Difficulty})"); } - // check if the share meets the much harder block difficulty (block candidate) - var isBlockCandidate = fh < b; - var result = new Share { BlockHeight = Height, @@ -225,9 +223,7 @@ public void Init(WorkMessage blockTemplate, int blockVersion, int extraNonceSize BlockTemplate = blockTemplate; JobId = jobId; - - b = BigInteger.Parse(BlockTemplate.B, NumberStyles.Integer); - bTarget = new Target(b); + Difficulty = new Target(BlockTemplate.B).Difficulty; jobParams = new object[] { diff --git a/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs b/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs index b9ac5fd6b..464dc862c 100644 --- a/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs +++ b/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Numerics; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -9262,7 +9263,7 @@ public partial class WorkMessage /// <summary>Work target value</summary> [Newtonsoft.Json.JsonProperty("b", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public string B { get; set; }= default!; + public BigInteger B { get; set; }= default!; /// <summary>Block height</summary> [Newtonsoft.Json.JsonProperty("h", Required = Newtonsoft.Json.Required.Always)] From 300cf14d16f875b65b49ab7b83ee278e53981a7e Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Mon, 19 Jul 2021 00:22:15 +0200 Subject: [PATCH 099/145] Donation address --- src/Miningcore/Blockchain/CoinMetaData.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Miningcore/Blockchain/CoinMetaData.cs b/src/Miningcore/Blockchain/CoinMetaData.cs index 0dcac4ded..f3265d2cb 100644 --- a/src/Miningcore/Blockchain/CoinMetaData.cs +++ b/src/Miningcore/Blockchain/CoinMetaData.cs @@ -24,6 +24,7 @@ public static class DevDonation { "ZEC", "t1YEgm6ovXFseeFxXgFY2zXxwsScD4BbfhT" }, { "BTG", "GRao6KHQ8a4GUjAZRVbeCLfRbSkJQQaeMg" }, { "XVG", "D5xPoHLM6HPkwWSqAweECTSQirJBmRjS8i" }, + { "ERG", "9foYU8JkoqWBSDA3ba8VHfduPXV2NaVNPPAFkdYoR9t9cPQGMv4 "}, { "XMR", "46S2AEwYmD9fnmZkxCpXf1T3U3DyEq3Ekb8Lg9kgUMGABn9Fp9q5nE2fBcXebrjrXfZHy5uC5HfLE6X4WLtSm35wUr9Mh46" }, { "RVN", "RF8wbxb3jeAcrH2z71NxccmyZmufk1D5m5" }, { "TUBE", "bxdAFKYA5sJYKM3zcn3SLaLRjsFF582VE1Uv5NChrVLm6o6UF4SdbZBZLrTBD6yEFZDzuTQGBCa8FLpX8charjxH2G3iMRX6R" }, From b09160855d71008c2763cd6de48fd593f416153c Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Mon, 19 Jul 2021 00:23:38 +0200 Subject: [PATCH 100/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoClientFactory.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoClientFactory.cs b/src/Miningcore/Blockchain/Ergo/ErgoClientFactory.cs index f0c08264f..40a9149ac 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoClientFactory.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoClientFactory.cs @@ -43,7 +43,6 @@ public static ErgoClient CreateClient(PoolConfig poolConfig, ClusterConfig clust result.RequestHeaders["Authorization"] = new AuthenticationHeaderValue("Basic", base64).ToString(); } - #if DEBUG result.ReadResponseAsString = true; #endif From da44d845673ada2d34601e6a0fa3b8974cb7f040 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Mon, 19 Jul 2021 00:45:08 +0200 Subject: [PATCH 101/145] Remove unused virtuals --- src/Miningcore/Blockchain/Bitcoin/BitcoinJobManager.cs | 4 ++-- src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs | 6 ------ src/Miningcore/Blockchain/Equihash/EquihashJobManager.cs | 4 ++-- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManager.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManager.cs index 9b48813a2..5e854b0d6 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManager.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManager.cs @@ -190,7 +190,7 @@ public override void Configure(PoolConfig poolConfig, ClusterConfig clusterConfi base.Configure(poolConfig, clusterConfig); } - public override object[] GetSubscriberData(StratumConnection worker) + public virtual object[] GetSubscriberData(StratumConnection worker) { Contract.RequiresNonNull(worker, nameof(worker)); @@ -209,7 +209,7 @@ public override object[] GetSubscriberData(StratumConnection worker) return responseData; } - public override async ValueTask<Share> SubmitShareAsync(StratumConnection worker, object submission, + public virtual async ValueTask<Share> SubmitShareAsync(StratumConnection worker, object submission, double stratumDifficultyBase, CancellationToken ct) { Contract.RequiresNonNull(worker, nameof(worker)); diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs index 1bafbd8e1..b12a369b3 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs @@ -16,7 +16,6 @@ using Miningcore.JsonRpc; using Miningcore.Messaging; using Miningcore.Notifications.Messages; -using Miningcore.Stratum; using Miningcore.Time; using Miningcore.Util; using NBitcoin; @@ -603,11 +602,6 @@ public virtual async Task<bool> ValidateAddressAsync(string address, Cancellatio return result.Response is {IsValid: true}; } - public abstract object[] GetSubscriberData(StratumConnection worker); - - public abstract ValueTask<Share> SubmitShareAsync(StratumConnection worker, object submission, - double stratumDifficultyBase, CancellationToken ct); - #endregion // API-Surface } } diff --git a/src/Miningcore/Blockchain/Equihash/EquihashJobManager.cs b/src/Miningcore/Blockchain/Equihash/EquihashJobManager.cs index 3bc331dc5..38dfabedc 100644 --- a/src/Miningcore/Blockchain/Equihash/EquihashJobManager.cs +++ b/src/Miningcore/Blockchain/Equihash/EquihashJobManager.cs @@ -213,7 +213,7 @@ public override async Task<bool> ValidateAddressAsync(string address, Cancellati return result.Response is {IsValid: true}; } - public override object[] GetSubscriberData(StratumConnection worker) + public object[] GetSubscriberData(StratumConnection worker) { Contract.RequiresNonNull(worker, nameof(worker)); @@ -231,7 +231,7 @@ public override object[] GetSubscriberData(StratumConnection worker) return responseData; } - public override async ValueTask<Share> SubmitShareAsync(StratumConnection worker, object submission, + public async ValueTask<Share> SubmitShareAsync(StratumConnection worker, object submission, double stratumDifficultyBase, CancellationToken ct) { Contract.RequiresNonNull(worker, nameof(worker)); From 8017b31fa48118b71eedfa8d84f88c65a4638598 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Mon, 19 Jul 2021 00:52:14 +0200 Subject: [PATCH 102/145] Consolidate helpers --- src/Miningcore/JsonRpc/RpcClient.cs | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/src/Miningcore/JsonRpc/RpcClient.cs b/src/Miningcore/JsonRpc/RpcClient.cs index 7c7591a18..8d75be70f 100644 --- a/src/Miningcore/JsonRpc/RpcClient.cs +++ b/src/Miningcore/JsonRpc/RpcClient.cs @@ -48,9 +48,10 @@ public RpcClient(DaemonEndpointConfig endPoint, JsonSerializerSettings serialize } private readonly JsonSerializerSettings serializerSettings; - protected readonly DaemonEndpointConfig endPoint; private readonly JsonSerializer serializer; + private readonly IMessageBus messageBus; + private readonly string poolId; private static readonly HttpClient httpClient = new(new HttpClientHandler { @@ -59,16 +60,6 @@ public RpcClient(DaemonEndpointConfig endPoint, JsonSerializerSettings serialize ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true, }); - // Telemetry - private readonly IMessageBus messageBus; - - private readonly string poolId; - - protected void PublishTelemetry(TelemetryCategory cat, TimeSpan elapsed, string info, bool? success = null, string error = null) - { - messageBus.SendMessage(new TelemetryEvent(poolId, cat, info, elapsed, success)); - } - #region API-Surface public async Task<RpcResponse<TResponse>> ExecuteAsync<TResponse>(ILogger logger, string method, CancellationToken ct, @@ -157,13 +148,10 @@ public IObservable<ZMessage> ZmqSubscribe(ILogger logger, CancellationToken ct, private async Task<JsonRpcResponse> RequestAsync(ILogger logger, CancellationToken ct, DaemonEndpointConfig endPoint, string method, object payload) { - var rpcRequestId = GetRequestId(); - - // telemetry var sw = Stopwatch.StartNew(); // build rpc request - var rpcRequest = new JsonRpcRequest<object>(method, payload, rpcRequestId); + var rpcRequest = new JsonRpcRequest<object>(method, payload, GetRequestId()); // build url var protocol = (endPoint.Ssl || endPoint.Http2) ? Uri.UriSchemeHttps : Uri.UriSchemeHttp; @@ -204,9 +192,7 @@ private async Task<JsonRpcResponse> RequestAsync(ILogger logger, CancellationTok { var result = serializer.Deserialize<JsonRpcResponse>(jreader); - // telemetry - sw.Stop(); - PublishTelemetry(TelemetryCategory.RpcRequest, sw.Elapsed, method, response.IsSuccessStatusCode); + messageBus.SendTelemetry(poolId, TelemetryCategory.RpcRequest, method, sw.Elapsed, response.IsSuccessStatusCode); return result; } @@ -216,7 +202,6 @@ private async Task<JsonRpcResponse> RequestAsync(ILogger logger, CancellationTok private async Task<JsonRpcResponse<JToken>[]> BatchRequestAsync(ILogger logger, CancellationToken ct, DaemonEndpointConfig endPoint, RpcRequest[] batch) { - // telemetry var sw = Stopwatch.StartNew(); // build rpc request @@ -260,9 +245,8 @@ private async Task<JsonRpcResponse<JToken>[]> BatchRequestAsync(ILogger logger, { var result = serializer.Deserialize<JsonRpcResponse<JToken>[]>(jreader); - // telemetry - sw.Stop(); - PublishTelemetry(TelemetryCategory.RpcRequest, sw.Elapsed, string.Join(", ", batch.Select(x => x.Method)), true); + messageBus.SendTelemetry(poolId, TelemetryCategory.RpcRequest, string.Join(", ", batch.Select(x => x.Method)), + sw.Elapsed, response.IsSuccessStatusCode); return result; } From 042479b1dcdf21aa8a92d41215df25e624a07bba Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Mon, 19 Jul 2021 00:57:04 +0200 Subject: [PATCH 103/145] Cleanup --- src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs | 1 - src/Miningcore/Blockchain/Equihash/EquihashJobManager.cs | 1 - src/Miningcore/JsonRpc/RpcClient.cs | 6 +++--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs index b12a369b3..12aaa2d95 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Net; using System.Reactive; using System.Reactive.Linq; using System.Threading; diff --git a/src/Miningcore/Blockchain/Equihash/EquihashJobManager.cs b/src/Miningcore/Blockchain/Equihash/EquihashJobManager.cs index 38dfabedc..e4043c62d 100644 --- a/src/Miningcore/Blockchain/Equihash/EquihashJobManager.cs +++ b/src/Miningcore/Blockchain/Equihash/EquihashJobManager.cs @@ -1,6 +1,5 @@ using System; using System.Linq; -using System.Reactive; using System.Threading; using System.Threading.Tasks; using Autofac; diff --git a/src/Miningcore/JsonRpc/RpcClient.cs b/src/Miningcore/JsonRpc/RpcClient.cs index 8d75be70f..5a136f2d3 100644 --- a/src/Miningcore/JsonRpc/RpcClient.cs +++ b/src/Miningcore/JsonRpc/RpcClient.cs @@ -139,7 +139,7 @@ public IObservable<ZMessage> ZmqSubscribe(ILogger logger, CancellationToken ct, logger.LogInvoke(); return Observable.Merge(portMap.Keys - .Select(endPoint => ZmqSubscribeEndpoint(logger, ct, endPoint, portMap[endPoint].Socket, portMap[endPoint].Topic))) + .Select(endPoint => ZmqSubscribeEndpoint(logger, ct, portMap[endPoint].Socket, portMap[endPoint].Topic))) .Publish() .RefCount(); } @@ -154,7 +154,7 @@ private async Task<JsonRpcResponse> RequestAsync(ILogger logger, CancellationTok var rpcRequest = new JsonRpcRequest<object>(method, payload, GetRequestId()); // build url - var protocol = (endPoint.Ssl || endPoint.Http2) ? Uri.UriSchemeHttps : Uri.UriSchemeHttp; + var protocol = endPoint.Ssl || endPoint.Http2 ? Uri.UriSchemeHttps : Uri.UriSchemeHttp; var requestUrl = $"{protocol}://{endPoint.Host}:{endPoint.Port}"; if(!string.IsNullOrEmpty(endPoint.HttpPath)) requestUrl += $"{(endPoint.HttpPath.StartsWith("/") ? string.Empty : "/")}{endPoint.HttpPath}"; @@ -348,7 +348,7 @@ private IObservable<byte[]> WebsocketSubscribeEndpoint(ILogger logger, Cancellat })); } - private static IObservable<ZMessage> ZmqSubscribeEndpoint(ILogger logger, CancellationToken ct, DaemonEndpointConfig endPoint, string url, string topic) + private static IObservable<ZMessage> ZmqSubscribeEndpoint(ILogger logger, CancellationToken ct, string url, string topic) { return Observable.Defer(() => Observable.Create<ZMessage>(obs => { From df3e1b8d6c1ee103c4b5e389322251e689373173 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Mon, 19 Jul 2021 01:00:15 +0200 Subject: [PATCH 104/145] Pass ct --- src/Miningcore/Mining/ShareRelay.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/Miningcore/Mining/ShareRelay.cs b/src/Miningcore/Mining/ShareRelay.cs index 9576ac1dd..a4181cb5b 100644 --- a/src/Miningcore/Mining/ShareRelay.cs +++ b/src/Miningcore/Mining/ShareRelay.cs @@ -23,14 +23,11 @@ public class ShareRelay : IHostedService { public ShareRelay( ClusterConfig clusterConfig, - JsonSerializerSettings serializerSettings, IMessageBus messageBus) { - Contract.RequiresNonNull(serializerSettings, nameof(serializerSettings)); Contract.RequiresNonNull(messageBus, nameof(messageBus)); this.clusterConfig = clusterConfig; - this.serializerSettings = serializerSettings; this.messageBus = messageBus; } @@ -41,7 +38,6 @@ public ShareRelay( private readonly int QueueSizeWarningThreshold = 1024; private bool hasWarnedAboutBacklogSize; private ZSocket pubSocket; - private readonly JsonSerializerSettings serializerSettings; private static readonly ILogger logger = LogManager.GetCurrentClassLogger(); @@ -66,7 +62,7 @@ private void InitializeQueue() try { - var flags = (int) WireFormat.ProtocolBuffers; + const int flags = (int) WireFormat.ProtocolBuffers; using(var msg = new ZMessage()) { @@ -111,9 +107,9 @@ private void CheckQueueBacklog() } } - public Task StartAsync(CancellationToken cancellationToken) + public Task StartAsync(CancellationToken ct) { - messageBus.Listen<StratumShare>().Subscribe(x => queue.Add(x.Share)); + messageBus.Listen<StratumShare>().Subscribe(x => queue.Add(x.Share, ct)); pubSocket = new ZSocket(ZSocketType.PUB); @@ -135,6 +131,7 @@ public Task StartAsync(CancellationToken cancellationToken) logger.ThrowLogPoolStartupException("ZeroMQ Curve is not supported in ShareRelay Connect-Mode"); pubSocket.Connect(clusterConfig.ShareRelay.PublishUrl); + logger.Info(() => $"Connected to {clusterConfig.ShareRelay.PublishUrl}"); } @@ -145,7 +142,7 @@ public Task StartAsync(CancellationToken cancellationToken) return Task.CompletedTask; } - public Task StopAsync(CancellationToken cancellationToken) + public Task StopAsync(CancellationToken ct) { pubSocket.Dispose(); From 89effc64a8c39a0170b4654b4e5b2f61e7175529 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Mon, 19 Jul 2021 01:06:14 +0200 Subject: [PATCH 105/145] Introduce KeyData --- src/Miningcore/Extensions/ZmqExtensions.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Miningcore/Extensions/ZmqExtensions.cs b/src/Miningcore/Extensions/ZmqExtensions.cs index 8f945f25f..3925637e9 100644 --- a/src/Miningcore/Extensions/ZmqExtensions.cs +++ b/src/Miningcore/Extensions/ZmqExtensions.cs @@ -15,24 +15,26 @@ namespace Miningcore.Extensions { public static class ZmqExtensions { - private static readonly ConcurrentDictionary<string, (byte[] PubKey, byte[] SecretKey)> knownKeys = - new(); + private record KeyData(byte[] PubKey, byte[] SecretKey); - private static readonly Lazy<(byte[] PubKey, byte[] SecretKey)> ownKey = new(() => + private static readonly ConcurrentDictionary<string, KeyData> knownKeys = new(); + + private static readonly Lazy<KeyData> ownKey = new(() => { if(!ZContext.Has("curve")) throw new NotSupportedException("ZMQ library does not support curve"); Z85.CurveKeypair(out var pubKey, out var secretKey); - return (pubKey, secretKey); + return new KeyData(pubKey, secretKey); }); const int PasswordIterations = 5000; - private static readonly byte[] NoSalt = new byte[32]; + + private static readonly byte[] noSalt = new byte[32]; private static byte[] DeriveKey(string password, int length = 32) { - using(var kbd = new Rfc2898DeriveBytes(Encoding.UTF8.GetBytes(password), NoSalt, PasswordIterations)) + using(var kbd = new Rfc2898DeriveBytes(Encoding.UTF8.GetBytes(password), noSalt, PasswordIterations)) { var block = kbd.GetBytes(length); return block; @@ -98,7 +100,7 @@ public static void SetupCurveTlsServer(this ZSocket socket, string keyPlain, ILo // Derive server's public-key from shared secret Z85.CurvePublic(out serverPubKey, keyBytes.ToZ85Encoded()); - knownKeys[keyPlain] = (serverPubKey, keyBytes); + knownKeys[keyPlain] = new KeyData(serverPubKey, keyBytes); } else @@ -135,7 +137,7 @@ public static void SetupCurveTlsClient(this ZSocket socket, string keyPlain, ILo // Derive server's public-key from shared secret Z85.CurvePublic(out serverPubKey, keyBytes.ToZ85Encoded()); - knownKeys[keyPlain] = (serverPubKey, keyBytes); + knownKeys[keyPlain] = new KeyData(serverPubKey, keyBytes); } else From 94eb70337ecaf182e7b664e137675dc6e6d4261f Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Mon, 19 Jul 2021 17:21:30 +0200 Subject: [PATCH 106/145] Verify --- src/Miningcore/Blockchain/Ergo/ErgoJob.cs | 95 ++++++++++++++++++----- 1 file changed, 77 insertions(+), 18 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs index 6c07ca70c..d56380d2a 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs @@ -22,6 +22,7 @@ public class ErgoJob public string JobId { get; protected set; } private object[] jobParams; + private BigInteger n; private readonly ConcurrentDictionary<string, bool> submissions = new(StringComparer.OrdinalIgnoreCase); private static readonly IHashAlgorithm hasher = new Blake2b(); private int extraNonceSize; @@ -73,7 +74,7 @@ protected virtual byte[] SerializeCoinbase(string msg, string nonce) } } - private BigInteger[] GenIndexes(byte[] seed, uint height) + private BigInteger[] GenIndexes(byte[] seed) { // hash seed Span<byte> hash = stackalloc byte[32]; @@ -90,8 +91,7 @@ private BigInteger[] GenIndexes(byte[] seed, uint height) for(var i = 0; i < 32; i++) { var x = BitConverter.ToUInt32(extendedHash.Slice(i, 4)).ToBigEndian(); - var y = CalcN(height); - result[i] = x % y; + result[i] = x % n; } return result; @@ -103,44 +103,85 @@ protected virtual Share ProcessShareInternal(StratumConnection worker, string no // hash coinbase var coinbase = SerializeCoinbase(BlockTemplate.Msg, nonce); - Span<byte> hashResult = stackalloc byte[32]; - hasher.Digest(coinbase, hashResult); + Span<byte> hash = stackalloc byte[32]; + hasher.Digest(coinbase, hash); // calculate i - var slice = hashResult.Slice(24, 8); - var tmp2 = new BigInteger(slice, true, true) % CalcN(Height); + var slice = hash.Slice(24, 8); + var tmp2 = new BigInteger(slice, true, true) % n; var i = tmp2.ToByteArray(false, true).PadFront(0, 4); // calculate e var h = new BigInteger(Height).ToByteArray(true, true).PadFront(0, 4); var ihM = i.Concat(h).Concat(ErgoConstants.M).ToArray(); - hasher.Digest(ihM, hashResult); - var e = hashResult[1..].ToArray(); + hasher.Digest(ihM, hash); + var e = hash[1..].ToArray(); // calculate j var eCoinbase = e.Concat(coinbase).ToArray(); - var jTmp = GenIndexes(eCoinbase, Height); + var jTmp = GenIndexes(eCoinbase); var j = jTmp.Select(x => x.ToByteArray(true, true).PadFront(0, 4)).ToArray(); // calculate f var f = j.Select(x => { - var buf = x.Concat(h).Concat(ErgoConstants.M).ToArray(); + var buf2 = x.Concat(h).Concat(ErgoConstants.M).ToArray(); // hash it - Span<byte> hash = stackalloc byte[32]; - hasher.Digest(buf, hash); + Span<byte> tmp = stackalloc byte[32]; + hasher.Digest(buf2, tmp); // extract 31 bytes at end - return new BigInteger(hash[1..], true, true); + return new BigInteger(tmp[1..], true, true); }).Aggregate((x, y) => x + y); // calculate fH - var blockHash = f.ToByteArray(true, true).PadFront(0, 32); - hasher.Digest(blockHash, hashResult); - var fh = new BigInteger(hashResult, true, true); + var fBytes = f.ToByteArray(true, true).PadFront(0, 32); + hasher.Digest(fBytes, hash); + var blockHash = hash.ToHexString(); + var fh = new BigInteger(hash, true, true); var fhTarget = new Target(fh); + + + + var _h = BitConverter.GetBytes((ulong) Height).Reverse().Skip(4).ToArray(); + var coinbaseBuffer = (BlockTemplate.Msg + nonce).HexToByteArray(); + var _n = GetN(Height); + + var firstB = new byte[32]; + hasher.Digest(coinbaseBuffer, firstB, 32); + var formula = new BigInteger(firstB.Skip(24).ToArray(), true, true) % _n; + var _i = new byte[4]; + Array.Copy(formula.ToByteArray(), _i, formula.ToByteArray().Length); + _i.ReverseInPlace(); + + var _e = new byte[32]; + hasher.Digest(_i.Concat(_h).Concat(ErgoConstants.M).ToArray(), _e, 32); + + var _hash = new byte[32]; + hasher.Digest(_e.Skip(1).Concat(coinbaseBuffer).ToArray(), _hash, 32); + var _extendedHash = _hash.Concat(_hash); + + var _f = new object[32].Select((x, y) => (new BigInteger(_extendedHash.Skip(y).Take(4).ToArray(), true, true) % _n).ToByteArray()).Select(x => + { + var buf2 = new byte[32]; + var item = new byte[4]; + Array.Copy(x, item, x.Length); + hasher.Digest(item.Reverse().Concat(_h).Concat(ErgoConstants.M).ToArray(), buf2, 32); + return new BigInteger(buf2.Skip(1).ToArray(), true, true); + }).Aggregate((x, y) => x + y); + + var _buf = new byte[32]; + Array.Copy(_f.ToByteArray(), _buf, _f.ToByteArray().Length); + hasher.Digest(_buf.ReverseInPlace(), _buf); + var _fh = new BigInteger(_buf, true, true); + + + if(fh != _fh) + ; + + // diff check var stratumDifficulty = context.Difficulty; var ratio = fhTarget.Difficulty / stratumDifficulty; @@ -178,12 +219,29 @@ protected virtual Share ProcessShareInternal(StratumConnection worker, string no { result.IsBlockCandidate = true; - result.BlockHash = blockHash.ToHexString(); + result.BlockHash = blockHash; } return result; } + private static BigInteger GetN(ulong height) + { + height = Math.Min(4198400, height); + if(height < 600 * 1024) + { + return BigInteger.Pow(2, 26); + } + else + { + var res = BigInteger.Pow(2, 26); + var iterationsNumber = (height - 600 * 1024) / 50 * 1024 + 1; + for(var i = 0ul; i < iterationsNumber; i++) + res = res / new BigInteger(100) * new BigInteger(105); + return res; + } + } + public object[] GetJobParams(bool isNew) { jobParams[^1] = isNew; @@ -224,6 +282,7 @@ public void Init(WorkMessage blockTemplate, int blockVersion, int extraNonceSize BlockTemplate = blockTemplate; JobId = jobId; Difficulty = new Target(BlockTemplate.B).Difficulty; + n = CalcN(Height); jobParams = new object[] { From bb61d30bf8b181e95c91bc6d1f69f2e7b8b505c3 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Mon, 19 Jul 2021 17:23:12 +0200 Subject: [PATCH 107/145] cleanup --- src/Miningcore/Blockchain/Ergo/ErgoJob.cs | 40 ----------------------- 1 file changed, 40 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs index d56380d2a..8ee00c820 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs @@ -142,46 +142,6 @@ protected virtual Share ProcessShareInternal(StratumConnection worker, string no var fh = new BigInteger(hash, true, true); var fhTarget = new Target(fh); - - - - var _h = BitConverter.GetBytes((ulong) Height).Reverse().Skip(4).ToArray(); - var coinbaseBuffer = (BlockTemplate.Msg + nonce).HexToByteArray(); - var _n = GetN(Height); - - var firstB = new byte[32]; - hasher.Digest(coinbaseBuffer, firstB, 32); - var formula = new BigInteger(firstB.Skip(24).ToArray(), true, true) % _n; - var _i = new byte[4]; - Array.Copy(formula.ToByteArray(), _i, formula.ToByteArray().Length); - _i.ReverseInPlace(); - - var _e = new byte[32]; - hasher.Digest(_i.Concat(_h).Concat(ErgoConstants.M).ToArray(), _e, 32); - - var _hash = new byte[32]; - hasher.Digest(_e.Skip(1).Concat(coinbaseBuffer).ToArray(), _hash, 32); - var _extendedHash = _hash.Concat(_hash); - - var _f = new object[32].Select((x, y) => (new BigInteger(_extendedHash.Skip(y).Take(4).ToArray(), true, true) % _n).ToByteArray()).Select(x => - { - var buf2 = new byte[32]; - var item = new byte[4]; - Array.Copy(x, item, x.Length); - hasher.Digest(item.Reverse().Concat(_h).Concat(ErgoConstants.M).ToArray(), buf2, 32); - return new BigInteger(buf2.Skip(1).ToArray(), true, true); - }).Aggregate((x, y) => x + y); - - var _buf = new byte[32]; - Array.Copy(_f.ToByteArray(), _buf, _f.ToByteArray().Length); - hasher.Digest(_buf.ReverseInPlace(), _buf); - var _fh = new BigInteger(_buf, true, true); - - - if(fh != _fh) - ; - - // diff check var stratumDifficulty = context.Difficulty; var ratio = fhTarget.Difficulty / stratumDifficulty; From cb192ba2ad02bab91dbd187be2b54be09b224a92 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Mon, 19 Jul 2021 17:41:59 +0200 Subject: [PATCH 108/145] WIP --- .../Configuration/ErgoPaymentProcessingConfigExtra.cs | 8 ++++++++ .../Blockchain/Ergo/Configuration/ErgoPoolConfigExtra.cs | 2 -- src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/Configuration/ErgoPaymentProcessingConfigExtra.cs b/src/Miningcore/Blockchain/Ergo/Configuration/ErgoPaymentProcessingConfigExtra.cs index 486cdf268..87c393707 100644 --- a/src/Miningcore/Blockchain/Ergo/Configuration/ErgoPaymentProcessingConfigExtra.cs +++ b/src/Miningcore/Blockchain/Ergo/Configuration/ErgoPaymentProcessingConfigExtra.cs @@ -2,6 +2,14 @@ namespace Miningcore.Blockchain.Ergo.Configuration { public class ErgoPaymentProcessingConfigExtra { + /// <summary> + /// Password for unlocking wallet + /// </summary> public string WalletPassword { get; set; } + + /// <summary> + /// Minimum block confirmations + /// </summary> + public int? MinimumConfirmations { get; set; } } } diff --git a/src/Miningcore/Blockchain/Ergo/Configuration/ErgoPoolConfigExtra.cs b/src/Miningcore/Blockchain/Ergo/Configuration/ErgoPoolConfigExtra.cs index 81673584a..8a40cf920 100644 --- a/src/Miningcore/Blockchain/Ergo/Configuration/ErgoPoolConfigExtra.cs +++ b/src/Miningcore/Blockchain/Ergo/Configuration/ErgoPoolConfigExtra.cs @@ -16,7 +16,5 @@ public class ErgoPoolConfigExtra public ZmqPubSubEndpointConfig BtStream { get; set; } public int? ExtraNonce1Size { get; set; } - - public int? MinimumConfirmations { get; set; } } } diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index 9ebaf59f9..7c768629a 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -140,7 +140,7 @@ public virtual async Task<Block[]> ClassifyBlocksAsync(IMiningPool pool, Block[] var pageSize = 100; var pageCount = (int) Math.Ceiling(blocks.Length / (double) pageSize); var result = new List<Block>(); - var minConfirmations = extraPoolConfig?.MinimumConfirmations ?? (network == "mainnet" ? 720 : 72); + var minConfirmations = extraPoolPaymentProcessingConfig?.MinimumConfirmations ?? (network == "mainnet" ? 720 : 72); var minerRewardsPubKey = await ergoClient.MiningReadMinerRewardPubkeyAsync(ct); var minerRewardsAddress = await ergoClient.MiningReadMinerRewardAddressAsync(ct); From bfd05e4d025bea252772ce991ce871fa23764933 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Mon, 19 Jul 2021 19:10:29 +0200 Subject: [PATCH 109/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index 7c768629a..a2b22e79a 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -219,6 +219,8 @@ await Guard(()=> Task.WhenAll(blockBatch), if(coinbaseOutput != null) { +logger.Info(() => $"[{LogCategory}] *** coinbase {coinbaseOutput.TransactionId} worth {coinbaseOutput.Value}"); + coinbaseWalletTxFound = true; // enough confirmations? From e2f47502e8fbcf56b1188538932a9c420ce800a1 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Mon, 19 Jul 2021 19:19:02 +0200 Subject: [PATCH 110/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index a2b22e79a..3daec94a5 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -219,7 +219,7 @@ await Guard(()=> Task.WhenAll(blockBatch), if(coinbaseOutput != null) { -logger.Info(() => $"[{LogCategory}] *** coinbase {coinbaseOutput.TransactionId} worth {coinbaseOutput.Value}"); +logger.Info(() => $"[{LogCategory}] *** coinbase {fullBlock.Header.Height} {fullBlock.Header.Id} {blockTx.Id} worth {coinbaseOutput.Value}"); coinbaseWalletTxFound = true; From dd5cf063acd231c99d41bca0631901a4d2b5d022 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Mon, 19 Jul 2021 19:37:20 +0200 Subject: [PATCH 111/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index 3daec94a5..bc69de558 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -214,13 +214,14 @@ await Guard(()=> Task.WhenAll(blockBatch), foreach(var blockTx in fullBlock.BlockTransactions.Transactions) { + if(blockHandled) + break; + var walletTx = await Guard(()=> ergoClient.WalletGetTransactionAsync(blockTx.Id, ct)); var coinbaseOutput = walletTx?.Outputs?.FirstOrDefault(x => x.Address == minerRewardsAddress.RewardAddress); if(coinbaseOutput != null) { -logger.Info(() => $"[{LogCategory}] *** coinbase {fullBlock.Header.Height} {fullBlock.Header.Id} {blockTx.Id} worth {coinbaseOutput.Value}"); - coinbaseWalletTxFound = true; // enough confirmations? @@ -230,6 +231,7 @@ await Guard(()=> Task.WhenAll(blockBatch), block.Status = BlockStatus.Confirmed; block.ConfirmationProgress = 1; block.Reward = (decimal) (coinbaseOutput.Value / ErgoConstants.SmallestUnit); + block.Hash = fullBlock.Header.Id; result.Add(block); logger.Info(() => $"[{LogCategory}] Unlocked block {block.BlockHeight} worth {FormatAmount(block.Reward)}"); @@ -245,6 +247,7 @@ await Guard(()=> Task.WhenAll(blockBatch), // update progress block.ConfirmationProgress = Math.Min(1.0d, (double) walletTx.NumConfirmations / minConfirmations); block.Reward = (decimal) (coinbaseOutput.Value / ErgoConstants.SmallestUnit); + block.Hash = fullBlock.Header.Id; result.Add(block); messageBus.NotifyBlockConfirmationProgress(poolConfig.Id, block, coin); From 55a372f5a3c73eaba975c64937c281b6a0f596a3 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Mon, 19 Jul 2021 19:49:30 +0200 Subject: [PATCH 112/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index bc69de558..4436cfda9 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -214,9 +214,6 @@ await Guard(()=> Task.WhenAll(blockBatch), foreach(var blockTx in fullBlock.BlockTransactions.Transactions) { - if(blockHandled) - break; - var walletTx = await Guard(()=> ergoClient.WalletGetTransactionAsync(blockTx.Id, ct)); var coinbaseOutput = walletTx?.Outputs?.FirstOrDefault(x => x.Address == minerRewardsAddress.RewardAddress); @@ -237,7 +234,6 @@ await Guard(()=> Task.WhenAll(blockBatch), logger.Info(() => $"[{LogCategory}] Unlocked block {block.BlockHeight} worth {FormatAmount(block.Reward)}"); messageBus.NotifyBlockUnlocked(poolConfig.Id, block, coin); - blockHandled = true; break; } @@ -251,6 +247,8 @@ await Guard(()=> Task.WhenAll(blockBatch), result.Add(block); messageBus.NotifyBlockConfirmationProgress(poolConfig.Id, block, coin); + blockHandled = true; + break; } } } From 0965bafe2c7e1b090206ba914c46ec1fc89846a7 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Mon, 19 Jul 2021 19:56:25 +0200 Subject: [PATCH 113/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index 4436cfda9..5c0fbc178 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -217,6 +217,8 @@ await Guard(()=> Task.WhenAll(blockBatch), var walletTx = await Guard(()=> ergoClient.WalletGetTransactionAsync(blockTx.Id, ct)); var coinbaseOutput = walletTx?.Outputs?.FirstOrDefault(x => x.Address == minerRewardsAddress.RewardAddress); +logger.Info(() => $"[{LogCategory}] *** coinbase {fullBlock.Header.Height} {fullBlock.Header.Id} {blockTx.Id} worth {coinbaseOutput.Value}"); + if(coinbaseOutput != null) { coinbaseWalletTxFound = true; @@ -245,6 +247,7 @@ await Guard(()=> Task.WhenAll(blockBatch), block.Reward = (decimal) (coinbaseOutput.Value / ErgoConstants.SmallestUnit); block.Hash = fullBlock.Header.Id; result.Add(block); +logger.Info(() => $"[{LogCategory}] *** coinbase 2 {coinbaseOutput.Value}"); messageBus.NotifyBlockConfirmationProgress(poolConfig.Id, block, coin); blockHandled = true; From c53d14df052280753f4a819035a9845bf9dde4cf Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Mon, 19 Jul 2021 20:03:22 +0200 Subject: [PATCH 114/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index 5c0fbc178..4436cfda9 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -217,8 +217,6 @@ await Guard(()=> Task.WhenAll(blockBatch), var walletTx = await Guard(()=> ergoClient.WalletGetTransactionAsync(blockTx.Id, ct)); var coinbaseOutput = walletTx?.Outputs?.FirstOrDefault(x => x.Address == minerRewardsAddress.RewardAddress); -logger.Info(() => $"[{LogCategory}] *** coinbase {fullBlock.Header.Height} {fullBlock.Header.Id} {blockTx.Id} worth {coinbaseOutput.Value}"); - if(coinbaseOutput != null) { coinbaseWalletTxFound = true; @@ -247,7 +245,6 @@ await Guard(()=> Task.WhenAll(blockBatch), block.Reward = (decimal) (coinbaseOutput.Value / ErgoConstants.SmallestUnit); block.Hash = fullBlock.Header.Id; result.Add(block); -logger.Info(() => $"[{LogCategory}] *** coinbase 2 {coinbaseOutput.Value}"); messageBus.NotifyBlockConfirmationProgress(poolConfig.Id, block, coin); blockHandled = true; From 360454fb9735473dd9261b1c5c7a6073300a3912 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Mon, 19 Jul 2021 20:05:12 +0200 Subject: [PATCH 115/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index 4436cfda9..3a37ed98a 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -48,7 +48,6 @@ public ErgoPayoutHandler( protected readonly IComponentContext ctx; protected ErgoClient ergoClient; - private ErgoPoolConfigExtra extraPoolConfig; private string network; private ErgoPaymentProcessingConfigExtra extraPoolPaymentProcessingConfig; @@ -118,7 +117,6 @@ public virtual async Task ConfigureAsync(ClusterConfig clusterConfig, PoolConfig this.poolConfig = poolConfig; this.clusterConfig = clusterConfig; - extraPoolConfig = poolConfig.Extra.SafeExtensionDataAs<ErgoPoolConfigExtra>(); extraPoolPaymentProcessingConfig = poolConfig.PaymentProcessing.Extra.SafeExtensionDataAs<ErgoPaymentProcessingConfigExtra>(); ergoClient = ErgoClientFactory.CreateClient(poolConfig, clusterConfig, null); From 31965ab20f094864c545d19c7e970f595f0f5a16 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Mon, 19 Jul 2021 20:20:40 +0200 Subject: [PATCH 116/145] WIP --- .../Blockchain/Ergo/ErgoPayoutHandler.cs | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index 3a37ed98a..19e7f41c0 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -210,6 +210,9 @@ await Guard(()=> Task.WhenAll(blockBatch), var coinbaseWalletTxFound = false; + // reset block reward + block.Reward = 0; + foreach(var blockTx in fullBlock.BlockTransactions.Transactions) { var walletTx = await Guard(()=> ergoClient.WalletGetTransactionAsync(blockTx.Id, ct)); @@ -225,13 +228,9 @@ await Guard(()=> Task.WhenAll(blockBatch), // matured and spendable coinbase transaction block.Status = BlockStatus.Confirmed; block.ConfirmationProgress = 1; - block.Reward = (decimal) (coinbaseOutput.Value / ErgoConstants.SmallestUnit); + block.Reward += coinbaseOutput.Value / ErgoConstants.SmallestUnit; block.Hash = fullBlock.Header.Id; - result.Add(block); - - logger.Info(() => $"[{LogCategory}] Unlocked block {block.BlockHeight} worth {FormatAmount(block.Reward)}"); - messageBus.NotifyBlockUnlocked(poolConfig.Id, block, coin); blockHandled = true; break; } @@ -240,17 +239,30 @@ await Guard(()=> Task.WhenAll(blockBatch), { // update progress block.ConfirmationProgress = Math.Min(1.0d, (double) walletTx.NumConfirmations / minConfirmations); - block.Reward = (decimal) (coinbaseOutput.Value / ErgoConstants.SmallestUnit); + block.Reward += coinbaseOutput.Value / ErgoConstants.SmallestUnit; block.Hash = fullBlock.Header.Id; - result.Add(block); - messageBus.NotifyBlockConfirmationProgress(poolConfig.Id, block, coin); blockHandled = true; break; } } } + if(blockHandled) + { + result.Add(block); + + if(block.Status == BlockStatus.Confirmed) + { + logger.Info(() => $"[{LogCategory}] Unlocked block {block.BlockHeight} worth {FormatAmount(block.Reward)}"); + + messageBus.NotifyBlockUnlocked(poolConfig.Id, block, coin); + } + + else + messageBus.NotifyBlockConfirmationProgress(poolConfig.Id, block, coin); + } + if(!blockHandled && !coinbaseWalletTxFound) coinbaseNonWalletTxCount++; } From b72baaf6a4bca3b404f7375f59e5b39a7e87485d Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Mon, 19 Jul 2021 20:28:42 +0200 Subject: [PATCH 117/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index 19e7f41c0..b61008a1f 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -230,9 +230,6 @@ await Guard(()=> Task.WhenAll(blockBatch), block.ConfirmationProgress = 1; block.Reward += coinbaseOutput.Value / ErgoConstants.SmallestUnit; block.Hash = fullBlock.Header.Id; - - blockHandled = true; - break; } else @@ -241,13 +238,12 @@ await Guard(()=> Task.WhenAll(blockBatch), block.ConfirmationProgress = Math.Min(1.0d, (double) walletTx.NumConfirmations / minConfirmations); block.Reward += coinbaseOutput.Value / ErgoConstants.SmallestUnit; block.Hash = fullBlock.Header.Id; - - blockHandled = true; - break; } } } + blockHandled = coinbaseWalletTxFound; + if(blockHandled) { result.Add(block); @@ -263,7 +259,7 @@ await Guard(()=> Task.WhenAll(blockBatch), messageBus.NotifyBlockConfirmationProgress(poolConfig.Id, block, coin); } - if(!blockHandled && !coinbaseWalletTxFound) + else coinbaseNonWalletTxCount++; } From 8f6a39da34018756cbadc644d9a999586fdaa2d0 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Mon, 19 Jul 2021 20:36:35 +0200 Subject: [PATCH 118/145] WIP --- .../Blockchain/Ergo/ErgoPayoutHandler.cs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index b61008a1f..0e8946fcc 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -222,22 +222,15 @@ await Guard(()=> Task.WhenAll(blockBatch), { coinbaseWalletTxFound = true; - // enough confirmations? + block.ConfirmationProgress = Math.Min(1.0d, (double) walletTx.NumConfirmations / minConfirmations); + block.Reward += coinbaseOutput.Value / ErgoConstants.SmallestUnit; + block.Hash = fullBlock.Header.Id; + if(walletTx.NumConfirmations >= minConfirmations) { // matured and spendable coinbase transaction block.Status = BlockStatus.Confirmed; block.ConfirmationProgress = 1; - block.Reward += coinbaseOutput.Value / ErgoConstants.SmallestUnit; - block.Hash = fullBlock.Header.Id; - } - - else - { - // update progress - block.ConfirmationProgress = Math.Min(1.0d, (double) walletTx.NumConfirmations / minConfirmations); - block.Reward += coinbaseOutput.Value / ErgoConstants.SmallestUnit; - block.Hash = fullBlock.Header.Id; } } } From ece2d9fd04f1f32c25dc67229d412576ae8a7f59 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Mon, 19 Jul 2021 21:15:34 +0200 Subject: [PATCH 119/145] Always use adjusted share diff using payments --- .../Payments/PaymentSchemes/PPLNSPaymentScheme.cs | 9 ++++----- .../Payments/PaymentSchemes/PROPPaymentScheme.cs | 7 ++++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Miningcore/Payments/PaymentSchemes/PPLNSPaymentScheme.cs b/src/Miningcore/Payments/PaymentSchemes/PPLNSPaymentScheme.cs index 00b12ce96..3499e0f50 100644 --- a/src/Miningcore/Payments/PaymentSchemes/PPLNSPaymentScheme.cs +++ b/src/Miningcore/Payments/PaymentSchemes/PPLNSPaymentScheme.cs @@ -92,7 +92,6 @@ public async Task UpdateBalancesAsync(IDbConnection con, IDbTransaction tx, IMin if(cutOffCount > 0) { await LogDiscardedSharesAsync(poolConfig, block, shareCutOffDate.Value); - #if !DEBUG logger.Info(() => $"Deleting {cutOffCount} discarded shares before {shareCutOffDate.Value:O}"); await shareRepo.DeleteSharesBeforeCreatedAsync(con, tx, poolConfig.Id, shareCutOffDate.Value); @@ -183,16 +182,16 @@ private async Task LogDiscardedSharesAsync(PoolConfig poolConfig, Block block, D { var share = page[i]; - // build address var address = share.Miner; + var shareDiffAdjusted = payoutHandler.AdjustShareDifficulty(share.Difficulty); // record attributed shares for diagnostic purposes if(!shares.ContainsKey(address)) - shares[address] = share.Difficulty; + shares[address] = shareDiffAdjusted; else - shares[address] += share.Difficulty; + shares[address] += shareDiffAdjusted; - var score = (decimal) (payoutHandler.AdjustShareDifficulty(share.Difficulty) / share.NetworkDifficulty); + var score = (decimal) (shareDiffAdjusted / share.NetworkDifficulty); // if accumulated score would cross threshold, cap it to the remaining value if(accumulatedScore + score >= window) diff --git a/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs b/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs index 89dd9796e..f3b73f72d 100644 --- a/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs +++ b/src/Miningcore/Payments/PaymentSchemes/PROPPaymentScheme.cs @@ -176,14 +176,15 @@ private async Task LogDiscardedSharesAsync(PoolConfig poolConfig, Block block, D { var share = page[i]; var address = share.Miner; + var shareDiffAdjusted = payoutHandler.AdjustShareDifficulty(share.Difficulty); // record attributed shares for diagnostic purposes if(!shares.ContainsKey(address)) - shares[address] = share.Difficulty; + shares[address] = shareDiffAdjusted; else - shares[address] += share.Difficulty; + shares[address] += shareDiffAdjusted; - var score = (decimal) (payoutHandler.AdjustShareDifficulty(share.Difficulty) / share.NetworkDifficulty); + var score = (decimal) (shareDiffAdjusted / share.NetworkDifficulty); if(!scores.ContainsKey(address)) scores[address] = score; From dc830c14cfc0bac4b1dbb578cb9f43a5a4baece9 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Mon, 19 Jul 2021 23:05:18 +0200 Subject: [PATCH 120/145] Cleanup --- src/Miningcore/Blockchain/Ergo/ErgoJob.cs | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs index 8ee00c820..8b6d1449d 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJob.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJob.cs @@ -138,7 +138,6 @@ protected virtual Share ProcessShareInternal(StratumConnection worker, string no // calculate fH var fBytes = f.ToByteArray(true, true).PadFront(0, 32); hasher.Digest(fBytes, hash); - var blockHash = hash.ToHexString(); var fh = new BigInteger(hash, true, true); var fhTarget = new Target(fh); @@ -178,30 +177,11 @@ protected virtual Share ProcessShareInternal(StratumConnection worker, string no if(isBlockCandidate) { result.IsBlockCandidate = true; - - result.BlockHash = blockHash; } return result; } - private static BigInteger GetN(ulong height) - { - height = Math.Min(4198400, height); - if(height < 600 * 1024) - { - return BigInteger.Pow(2, 26); - } - else - { - var res = BigInteger.Pow(2, 26); - var iterationsNumber = (height - 600 * 1024) / 50 * 1024 + 1; - for(var i = 0ul; i < iterationsNumber; i++) - res = res / new BigInteger(100) * new BigInteger(105); - return res; - } - } - public object[] GetJobParams(bool isNew) { jobParams[^1] = isNew; From 56fb1b15c2d7633952cb5d87cfa62f65120a218b Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Wed, 21 Jul 2021 00:27:33 +0200 Subject: [PATCH 121/145] Refactor --- src/Miningcore/Stratum/StratumServer.cs | 76 +++++++------------------ 1 file changed, 21 insertions(+), 55 deletions(-) diff --git a/src/Miningcore/Stratum/StratumServer.cs b/src/Miningcore/Stratum/StratumServer.cs index bc779252b..36314a8a0 100644 --- a/src/Miningcore/Stratum/StratumServer.cs +++ b/src/Miningcore/Stratum/StratumServer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Net; @@ -71,10 +72,12 @@ static StratumServer() } } - protected readonly Dictionary<string, StratumConnection> connections = new(); - protected static readonly Dictionary<string, X509Certificate2> certs = new(); + protected readonly ConcurrentDictionary<string, StratumConnection> connections = new(); + protected static readonly ConcurrentDictionary<string, X509Certificate2> certs = new(); protected static readonly HashSet<int> ignoredSocketErrors; - protected static readonly MethodBase streamWriterCtor = typeof(StreamWriter).GetConstructor(new[] { typeof(Stream), typeof(Encoding), typeof(int), typeof(bool) }); + + protected static readonly MethodBase streamWriterCtor = typeof(StreamWriter).GetConstructor( + new[] { typeof(Stream), typeof(Encoding), typeof(int), typeof(bool) }); protected readonly IComponentContext ctx; protected readonly IMessageBus messageBus; @@ -97,14 +100,16 @@ public Task RunAsync(CancellationToken ct, params StratumEndpoint[] endpoints) server.Bind(port.IPEndPoint); server.Listen(); - return Listen(server, port, GetTlsCert(port), ct); + return Listen(server, port, ct); }).ToArray(); return Task.WhenAll(tasks); } - private async Task Listen(Socket server, StratumEndpoint port, X509Certificate2 cert, CancellationToken ct) + private async Task Listen(Socket server, StratumEndpoint port, CancellationToken ct) { + var cert = GetTlsCert(port); + while(!ct.IsCancellationRequested) { try @@ -137,43 +142,31 @@ private void AcceptConnection(Socket socket, StratumEndpoint port, X509Certifica if (DisconnectIfBanned(socket, remoteEndpoint)) return; - var connectionId = CorrelationIdGenerator.GetNextId(); - logger.Info(() => $"[{connectionId}] Accepting connection from {remoteEndpoint.Address}:{remoteEndpoint.Port} ..."); - // init connection - var connection = new StratumConnection(logger, clock, connectionId); + var connection = new StratumConnection(logger, clock, CorrelationIdGenerator.GetNextId()); + + logger.Info(() => $"[{connection.ConnectionId}] Accepting connection from {remoteEndpoint.Address}:{remoteEndpoint.Port} ..."); - RegisterConnection(connection, connectionId); + RegisterConnection(connection); OnConnect(connection, port.IPEndPoint); connection.DispatchAsync(socket, ct, port, remoteEndpoint, cert, OnRequestAsync, OnConnectionComplete, OnConnectionError); }, ex=> logger.Error(ex)), ct); } - protected virtual void RegisterConnection(StratumConnection connection, string connectionId) + protected virtual void RegisterConnection(StratumConnection connection) { - lock(connections) - { - connections[connectionId] = connection; - } + var result = connections.TryAdd(connection.ConnectionId, connection); + Debug.Assert(result); - // ReSharper disable once InconsistentlySynchronizedField PublishTelemetry(TelemetryCategory.Connections, TimeSpan.Zero, true, connections.Count); } protected virtual void UnregisterConnection(StratumConnection connection) { - var subscriptionId = connection.ConnectionId; - - if(!string.IsNullOrEmpty(subscriptionId)) - { - lock(connections) - { - connections.Remove(subscriptionId); - } - } + var result = connections.TryRemove(connection.ConnectionId, out _); + Debug.Assert(result); - // ReSharper disable once InconsistentlySynchronizedField PublishTelemetry(TelemetryCategory.Connections, TimeSpan.Zero, true, connections.Count); } @@ -278,6 +271,7 @@ protected virtual void CloseConnection(StratumConnection connection) Contract.RequiresNonNull(connection, nameof(connection)); connection.Disconnect(); + UnregisterConnection(connection); } @@ -316,37 +310,9 @@ private bool DisconnectIfBanned(Socket socket, IPEndPoint remoteEndpoint) return false; } - protected void ForEachConnection(Action<StratumConnection> action) - { - StratumConnection[] tmp; - - lock(connections) - { - tmp = connections.Values.ToArray(); - } - - foreach(var client in tmp) - { - try - { - action(client); - } - - catch(Exception ex) - { - logger.Error(ex); - } - } - } - protected IEnumerable<Task> ForEachConnection(Func<StratumConnection, Task> func) { - StratumConnection[] tmp; - - lock(connections) - { - tmp = connections.Values.ToArray(); - } + var tmp = connections.Values.ToArray(); return tmp.Select(func); } From 7b61e5a83469d4500960803ff0cfd93790a67fa0 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Wed, 21 Jul 2021 11:10:33 +0200 Subject: [PATCH 122/145] concurrent listen --- src/Miningcore/Stratum/StratumServer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Miningcore/Stratum/StratumServer.cs b/src/Miningcore/Stratum/StratumServer.cs index 36314a8a0..bf46418bd 100644 --- a/src/Miningcore/Stratum/StratumServer.cs +++ b/src/Miningcore/Stratum/StratumServer.cs @@ -100,7 +100,7 @@ public Task RunAsync(CancellationToken ct, params StratumEndpoint[] endpoints) server.Bind(port.IPEndPoint); server.Listen(); - return Listen(server, port, ct); + return Task.Run(()=> Listen(server, port, ct), ct); }).ToArray(); return Task.WhenAll(tasks); From db5cba95b2db46144603b13bc0611846312bed9f Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Wed, 21 Jul 2021 11:19:46 +0200 Subject: [PATCH 123/145] Notification fix --- src/Miningcore/Notifications/NotificationService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Miningcore/Notifications/NotificationService.cs b/src/Miningcore/Notifications/NotificationService.cs index 35269aa99..a7620ff88 100644 --- a/src/Miningcore/Notifications/NotificationService.cs +++ b/src/Miningcore/Notifications/NotificationService.cs @@ -83,13 +83,13 @@ private async Task OnPaymentNotificationAsync(PaymentNotification notification, var coin = poolConfigs[notification.PoolId].Template; // prepare tx links - string[] txLinks = null; + var txLinks = new string[0]; if(!string.IsNullOrEmpty(coin.ExplorerTxLink)) txLinks = notification.TxIds.Select(txHash => string.Format(coin.ExplorerTxLink, txHash)).ToArray(); const string subject = "Payout Success Notification"; - var message = $"Paid {FormatAmount(notification.Amount, notification.PoolId)} from pool {notification.PoolId} to {notification.RecpientsCount} recipients in Transaction(s) {txLinks}."; + var message = $"Paid {FormatAmount(notification.Amount, notification.PoolId)} from pool {notification.PoolId} to {notification.RecpientsCount} recipients in transaction(s) {(string.Join(", ", txLinks))}"; if(clusterConfig.Notifications?.Admin?.NotifyPaymentSuccess == true) { From 97cbe67025e5cfd806d42b8f652a4c56e1941ad7 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Thu, 22 Jul 2021 07:42:08 +0200 Subject: [PATCH 124/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index 0e8946fcc..c6c0d88a8 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -308,6 +308,8 @@ public virtual async Task PayoutAsync(IMiningPool pool, Balance[] balances, Canc if(amounts.Count == 0) return; + var balancesTotal = amounts.Sum(x => x.Value); + try { logger.Info(() => $"[{LogCategory}] Paying {FormatAmount(balances.Sum(x => x.Amount))} to {balances.Length} addresses"); @@ -323,7 +325,15 @@ public virtual async Task PayoutAsync(IMiningPool pool, Balance[] balances, Canc // get balance var walletBalances = await ergoClient.WalletBalancesAsync(ct); - logger.Info(() => $"[{LogCategory}] Current wallet balance is {FormatAmount(walletBalances.Balance / ErgoConstants.SmallestUnit)}"); + var walletTotal = walletBalances.Balance / ErgoConstants.SmallestUnit; + logger.Info(() => $"[{LogCategory}] Current wallet balance is {FormatAmount(walletTotal)}"); + + // bail if balance does not satisfy payments + if(walletTotal < balancesTotal) + { + logger.Warn(() => $"[{LogCategory}] Wallet balance currently short of {FormatAmount(balancesTotal - walletTotal)}. Will try again."); + return; + } // Create request batch var requests = amounts.Select(x => new PaymentRequest From 8301dd54d46233f6099a7043bf6717169fdbd3db Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Thu, 22 Jul 2021 12:51:09 +0200 Subject: [PATCH 125/145] Do not allow individual payment thresholds below pool threshold --- src/Miningcore/Api/Controllers/AdminApiController.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Miningcore/Api/Controllers/AdminApiController.cs b/src/Miningcore/Api/Controllers/AdminApiController.cs index aa80cf6c4..9d2afe10d 100644 --- a/src/Miningcore/Api/Controllers/AdminApiController.cs +++ b/src/Miningcore/Api/Controllers/AdminApiController.cs @@ -87,6 +87,10 @@ public async Task<decimal> GetMinerBalanceAsync(string poolId, string address) // map settings var mapped = mapper.Map<Persistence.Model.MinerSettings>(settings); + + if(pool.PaymentProcessing != null) + mapped.PaymentThreshold = Math.Max(mapped.PaymentThreshold, pool.PaymentProcessing.MinimumPayment); + mapped.PoolId = pool.Id; mapped.Address = address; From ebcffebf7b254fd680f9a76d135dccb8fe4ae9dc Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Thu, 22 Jul 2021 16:45:22 +0200 Subject: [PATCH 126/145] libkawpow --- src/Native/libkawpow/Makefile | 16 + src/Native/libkawpow/attributes.h | 33 + src/Native/libkawpow/dllmain.cpp | 19 + src/Native/libkawpow/ethash/CMakeLists.txt | 37 + .../libkawpow/ethash/bit_manipulation.h | 81 +++ src/Native/libkawpow/ethash/builtins.h | 43 ++ src/Native/libkawpow/ethash/endianness.hpp | 97 +++ .../libkawpow/ethash/ethash-internal.hpp | 68 ++ src/Native/libkawpow/ethash/ethash.cpp | 444 ++++++++++++ src/Native/libkawpow/ethash/ethash.h | 145 ++++ src/Native/libkawpow/ethash/ethash.hpp | 172 +++++ src/Native/libkawpow/ethash/hash_types.h | 50 ++ src/Native/libkawpow/ethash/hash_types.hpp | 15 + src/Native/libkawpow/ethash/keccak.h | 49 ++ src/Native/libkawpow/ethash/keccak.hpp | 35 + src/Native/libkawpow/ethash/kiss99.hpp | 64 ++ src/Native/libkawpow/ethash/managed.cpp | 99 +++ src/Native/libkawpow/ethash/primes.c | 43 ++ src/Native/libkawpow/ethash/primes.h | 25 + src/Native/libkawpow/ethash/progpow.cpp | 658 ++++++++++++++++++ src/Native/libkawpow/ethash/progpow.hpp | 68 ++ src/Native/libkawpow/ethash/version.h | 18 + src/Native/libkawpow/exports.cpp | 16 + src/Native/libkawpow/keccak/CMakeLists.txt | 26 + src/Native/libkawpow/keccak/keccak.c | 123 ++++ src/Native/libkawpow/keccak/keccakf1600.c | 255 +++++++ src/Native/libkawpow/keccak/keccakf800.c | 253 +++++++ src/Native/libkawpow/libkawpow.sln | 31 + src/Native/libkawpow/liblibkawpow.vcxproj | 206 ++++++ src/Native/libkawpow/stdafx.cpp | 8 + src/Native/libkawpow/stdafx.h | 16 + src/Native/libkawpow/support/attributes.h | 33 + src/Native/libkawpow/targetver.h | 8 + 33 files changed, 3254 insertions(+) create mode 100644 src/Native/libkawpow/Makefile create mode 100644 src/Native/libkawpow/attributes.h create mode 100644 src/Native/libkawpow/dllmain.cpp create mode 100644 src/Native/libkawpow/ethash/CMakeLists.txt create mode 100644 src/Native/libkawpow/ethash/bit_manipulation.h create mode 100644 src/Native/libkawpow/ethash/builtins.h create mode 100644 src/Native/libkawpow/ethash/endianness.hpp create mode 100644 src/Native/libkawpow/ethash/ethash-internal.hpp create mode 100644 src/Native/libkawpow/ethash/ethash.cpp create mode 100644 src/Native/libkawpow/ethash/ethash.h create mode 100644 src/Native/libkawpow/ethash/ethash.hpp create mode 100644 src/Native/libkawpow/ethash/hash_types.h create mode 100644 src/Native/libkawpow/ethash/hash_types.hpp create mode 100644 src/Native/libkawpow/ethash/keccak.h create mode 100644 src/Native/libkawpow/ethash/keccak.hpp create mode 100644 src/Native/libkawpow/ethash/kiss99.hpp create mode 100644 src/Native/libkawpow/ethash/managed.cpp create mode 100644 src/Native/libkawpow/ethash/primes.c create mode 100644 src/Native/libkawpow/ethash/primes.h create mode 100644 src/Native/libkawpow/ethash/progpow.cpp create mode 100644 src/Native/libkawpow/ethash/progpow.hpp create mode 100644 src/Native/libkawpow/ethash/version.h create mode 100644 src/Native/libkawpow/exports.cpp create mode 100644 src/Native/libkawpow/keccak/CMakeLists.txt create mode 100644 src/Native/libkawpow/keccak/keccak.c create mode 100644 src/Native/libkawpow/keccak/keccakf1600.c create mode 100644 src/Native/libkawpow/keccak/keccakf800.c create mode 100644 src/Native/libkawpow/libkawpow.sln create mode 100644 src/Native/libkawpow/liblibkawpow.vcxproj create mode 100644 src/Native/libkawpow/stdafx.cpp create mode 100644 src/Native/libkawpow/stdafx.h create mode 100644 src/Native/libkawpow/support/attributes.h create mode 100644 src/Native/libkawpow/targetver.h diff --git a/src/Native/libkawpow/Makefile b/src/Native/libkawpow/Makefile new file mode 100644 index 000000000..75e475704 --- /dev/null +++ b/src/Native/libkawpow/Makefile @@ -0,0 +1,16 @@ +CFLAGS += -g -Wall -c -fPIC -O2 -Wno-pointer-sign -Wno-char-subscripts -Wno-unused-variable -Wno-unused-function -Wno-strict-aliasing -Wno-discarded-qualifiers -Wno-unused-const-variable +CXXFLAGS += -g -Wall -fPIC -fpermissive -O2 -Wno-char-subscripts -Wno-unused-variable -Wno-unused-function -Wno-strict-aliasing -Wno-sign-compare -std=c++11 +LDFLAGS += -shared +TARGET = libkawpow.so + +OBJECTS = ethash/ethash.o keccak/keccak.o keccak/keccakf800.o keccak/keccakf1600.o ethash/managed.o ethash/primes.o ethash/progpow.o + +all: $(TARGET) + +$(TARGET): $(OBJECTS) + $(CXX) $(LDFLAGS) -o $@ $^ $(LDLIBS) + +.PHONY: clean + +clean: + $(RM) $(TARGET) $(OBJECTS) diff --git a/src/Native/libkawpow/attributes.h b/src/Native/libkawpow/attributes.h new file mode 100644 index 000000000..83be231f0 --- /dev/null +++ b/src/Native/libkawpow/attributes.h @@ -0,0 +1,33 @@ +/* ethash: C/C++ implementation of Ethash, the Ethereum Proof of Work algorithm. + * Copyright 2018-2019 Pawel Bylica. + * Licensed under the Apache License, Version 2.0. + */ + +#pragma once + +/** inline */ +#if _MSC_VER || __STDC_VERSION__ +#define INLINE inline +#else +#define INLINE +#endif + +/** [[always_inline]] */ +#if _MSC_VER +#define ALWAYS_INLINE __forceinline +#elif defined(__has_attribute) && __STDC_VERSION__ +#if __has_attribute(always_inline) +#define ALWAYS_INLINE __attribute__((always_inline)) +#endif +#endif +#if !defined(ALWAYS_INLINE) +#define ALWAYS_INLINE +#endif + +/** [[no_sanitize()]] */ +#if __clang__ +#define NO_SANITIZE(sanitizer) \ + __attribute__((no_sanitize(sanitizer))) +#else +#define NO_SANITIZE(sanitizer) +#endif diff --git a/src/Native/libkawpow/dllmain.cpp b/src/Native/libkawpow/dllmain.cpp new file mode 100644 index 000000000..69b58914b --- /dev/null +++ b/src/Native/libkawpow/dllmain.cpp @@ -0,0 +1,19 @@ +// dllmain.cpp : Defines the entry point for the DLL application. +#include "stdafx.h" + +BOOL APIENTRY DllMain( HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + diff --git a/src/Native/libkawpow/ethash/CMakeLists.txt b/src/Native/libkawpow/ethash/CMakeLists.txt new file mode 100644 index 000000000..1eefc3f24 --- /dev/null +++ b/src/Native/libkawpow/ethash/CMakeLists.txt @@ -0,0 +1,37 @@ +# ethash: C/C++ implementation of Ethash, the Ethereum Proof of Work algorithm. +# Copyright 2018-2019 Pawel Bylica. +# Licensed under the Apache License, Version 2.0. + +include(GNUInstallDirs) + +add_library( + ethash SHARED + bit_manipulation.h + builtins.h + endianness.hpp + ${include_dir}/ethash/ethash.h + ${include_dir}/ethash/ethash.hpp + ethash-internal.hpp + ethash.cpp + ${include_dir}/ethash/hash_types.h + managed.cpp + kiss99.hpp + primes.h + primes.c + ${include_dir}/ethash/progpow.hpp + progpow.cpp +) +set_property(TARGET ethash PROPERTY POSITION_INDEPENDENT_CODE ON) + +target_link_libraries(ethash PRIVATE keccak) +target_include_directories(ethash PUBLIC $<BUILD_INTERFACE:${include_dir}>$<INSTALL_INTERFACE:include>) +if(CABLE_COMPILER_GNULIKE AND NOT SANITIZE MATCHES undefined) + target_compile_options(ethash PRIVATE $<$<COMPILE_LANGUAGE:CXX>:-fno-rtti>) +endif() + +install( + TARGETS ethash + EXPORT ethashTargets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) diff --git a/src/Native/libkawpow/ethash/bit_manipulation.h b/src/Native/libkawpow/ethash/bit_manipulation.h new file mode 100644 index 000000000..b88bfdaab --- /dev/null +++ b/src/Native/libkawpow/ethash/bit_manipulation.h @@ -0,0 +1,81 @@ +/* ethash: C/C++ implementation of Ethash, the Ethereum Proof of Work algorithm. + * Copyright 2018-2019 Pawel Bylica. + * Licensed under the Apache License, Version 2.0. + */ + +#pragma once + +#include "builtins.h" +#include "../support/attributes.h" + +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +static inline uint32_t rotl32(uint32_t n, unsigned int c) +{ + const unsigned int mask = 31; + + c &= mask; + unsigned int neg_c = (unsigned int)(-(int)c); + return (n << c) | (n >> (neg_c & mask)); +} + +static inline uint32_t rotr32(uint32_t n, unsigned int c) +{ + const unsigned int mask = 31; + + c &= mask; + unsigned int neg_c = (unsigned int)(-(int)c); + return (n >> c) | (n << (neg_c & mask)); +} + +static inline uint32_t clz32(uint32_t x) +{ + return x ? (uint32_t)__builtin_clz(x) : 32; +} + +static inline uint32_t popcount32(uint32_t x) +{ + return (uint32_t)__builtin_popcount(x); +} + +static inline uint32_t mul_hi32(uint32_t x, uint32_t y) +{ + return (uint32_t)(((uint64_t)x * (uint64_t)y) >> 32); +} + + +/** FNV 32-bit prime. */ +static const uint32_t fnv_prime = 0x01000193; + +/** FNV 32-bit offset basis. */ +static const uint32_t fnv_offset_basis = 0x811c9dc5; + +/** + * The implementation of FNV-1 hash. + * + * See https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1_hash. + */ +NO_SANITIZE("unsigned-integer-overflow") +static inline uint32_t fnv1(uint32_t u, uint32_t v) noexcept +{ + return (u * fnv_prime) ^ v; +} + +/** + * The implementation of FNV-1a hash. + * + * See https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1a_hash. + */ +NO_SANITIZE("unsigned-integer-overflow") +static inline uint32_t fnv1a(uint32_t u, uint32_t v) noexcept +{ + return (u ^ v) * fnv_prime; +} + +#ifdef __cplusplus +} +#endif diff --git a/src/Native/libkawpow/ethash/builtins.h b/src/Native/libkawpow/ethash/builtins.h new file mode 100644 index 000000000..0c43188ad --- /dev/null +++ b/src/Native/libkawpow/ethash/builtins.h @@ -0,0 +1,43 @@ +/* ethash: C/C++ implementation of Ethash, the Ethereum Proof of Work algorithm. + * Copyright 2018-2019 Pawel Bylica. + * Licensed under the Apache License, Version 2.0. + */ + +/** + * @file + * Implementation of GCC/clang builtins for MSVC compiler. + */ + +#pragma once + +#ifdef _MSC_VER +#include <intrin.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Returns the number of leading 0-bits in `x`, starting at the most significant bit position. + * If `x` is 0, the result is undefined. + */ +static inline int __builtin_clz(unsigned int x) +{ + unsigned long most_significant_bit; + _BitScanReverse(&most_significant_bit, x); + return 31 - (int)most_significant_bit; +} + +/** + * Returns the number of 1-bits in `x`. + */ +static inline int __builtin_popcount(unsigned int x) +{ + return (int)__popcnt(x); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/Native/libkawpow/ethash/endianness.hpp b/src/Native/libkawpow/ethash/endianness.hpp new file mode 100644 index 000000000..3426f4689 --- /dev/null +++ b/src/Native/libkawpow/ethash/endianness.hpp @@ -0,0 +1,97 @@ +// ethash: C/C++ implementation of Ethash, the Ethereum Proof of Work algorithm. +// Copyright 2018-2019 Pawel Bylica. +// Licensed under the Apache License, Version 2.0. + +/// @file +/// This file contains helper functions to handle big-endian architectures. +/// The Ethash algorithm is naturally defined for little-endian architectures +/// so for those the helpers are just no-op empty functions. +/// For big-endian architectures we need 32-bit and 64-bit byte swapping in +/// some places. + +#pragma once + +#if _WIN32 + +#include <stdlib.h> + +#define bswap32 _byteswap_ulong +#define bswap64 _byteswap_uint64 + +// On Windows assume little endian. +#define __LITTLE_ENDIAN 1234 +#define __BIG_ENDIAN 4321 +#define __BYTE_ORDER __LITTLE_ENDIAN + +#elif __APPLE__ + +#include <machine/endian.h> + +#define bswap32 __builtin_bswap32 +#define bswap64 __builtin_bswap64 + +#else + +#include <endian.h> + +#define bswap32 __builtin_bswap32 +#define bswap64 __builtin_bswap64 + +#endif + +namespace ethash +{ +#if __BYTE_ORDER == __LITTLE_ENDIAN + +struct le +{ + static uint32_t uint32(uint32_t x) noexcept { return x; } + static uint64_t uint64(uint64_t x) noexcept { return x; } + + static const hash1024& uint32s(const hash1024& h) noexcept { return h; } + static const hash512& uint32s(const hash512& h) noexcept { return h; } + static const hash256& uint32s(const hash256& h) noexcept { return h; } +}; + +struct be +{ + static uint64_t uint64(uint64_t x) noexcept { return bswap64(x); } +}; + + +#elif __BYTE_ORDER == __BIG_ENDIAN + +struct le +{ + static uint32_t uint32(uint32_t x) noexcept { return bswap32(x); } + static uint64_t uint64(uint64_t x) noexcept { return bswap64(x); } + + static hash1024 uint32s(hash1024 h) noexcept + { + for (auto& w : h.word32s) + w = uint32(w); + return h; + } + + static hash512 uint32s(hash512 h) noexcept + { + for (auto& w : h.word32s) + w = uint32(w); + return h; + } + + static hash256 uint32s(hash256 h) noexcept + { + for (auto& w : h.word32s) + w = uint32(w); + return h; + } +}; + +struct be +{ + static uint64_t uint64(uint64_t x) noexcept { return x; } +}; + +#endif +} // namespace ethash diff --git a/src/Native/libkawpow/ethash/ethash-internal.hpp b/src/Native/libkawpow/ethash/ethash-internal.hpp new file mode 100644 index 000000000..1d4ccd979 --- /dev/null +++ b/src/Native/libkawpow/ethash/ethash-internal.hpp @@ -0,0 +1,68 @@ +// ethash: C/C++ implementation of Ethash, the Ethereum Proof of Work algorithm. +// Copyright 2018-2019 Pawel Bylica. +// Licensed under the Apache License, Version 2.0. + +/// @file +/// Contains declarations of internal ethash functions to allow them to be +/// unit-tested. + +#pragma once + +#include "ethash.hpp" + +#include "endianness.hpp" + +#include <memory> +#include <vector> + +extern "C" struct ethash_epoch_context_full : ethash_epoch_context +{ + ethash_hash1024* full_dataset; + + constexpr ethash_epoch_context_full(int epoch, int light_num_items, + const ethash_hash512* light, const uint32_t* l1, int dataset_num_items, + ethash_hash1024* dataset) noexcept + : ethash_epoch_context{epoch, light_num_items, light, l1, dataset_num_items}, + full_dataset{dataset} + {} +}; + +namespace ethash +{ +inline bool is_less_or_equal(const hash256& a, const hash256& b) noexcept +{ + for (size_t i = 0; i < (sizeof(a) / sizeof(a.word64s[0])); ++i) + { + if (be::uint64(a.word64s[i]) > be::uint64(b.word64s[i])) + return false; + if (be::uint64(a.word64s[i]) < be::uint64(b.word64s[i])) + return true; + } + return true; +} + +inline bool is_equal(const hash256& a, const hash256& b) noexcept +{ + return std::memcmp(a.bytes, b.bytes, sizeof(a)) == 0; +} + +void build_light_cache(hash512 cache[], int num_items, const hash256& seed) noexcept; + +hash512 calculate_dataset_item_512(const epoch_context& context, int64_t index) noexcept; +hash1024 calculate_dataset_item_1024(const epoch_context& context, uint32_t index) noexcept; +hash2048 calculate_dataset_item_2048(const epoch_context& context, uint32_t index) noexcept; + +namespace generic +{ +using hash_fn_512 = hash512 (*)(const uint8_t* data, size_t size); +using build_light_cache_fn = void (*)(hash512 cache[], int num_items, const hash256& seed); + +void build_light_cache( + hash_fn_512 hash_fn, hash512 cache[], int num_items, const hash256& seed) noexcept; + +epoch_context_full* create_epoch_context( + build_light_cache_fn build_fn, int epoch_number, bool full) noexcept; + +} // namespace generic + +} // namespace ethash diff --git a/src/Native/libkawpow/ethash/ethash.cpp b/src/Native/libkawpow/ethash/ethash.cpp new file mode 100644 index 000000000..436ded06b --- /dev/null +++ b/src/Native/libkawpow/ethash/ethash.cpp @@ -0,0 +1,444 @@ +// ethash: C/C++ implementation of Ethash, the Ethereum Proof of Work algorithm. +// Copyright 2018-2019 Pawel Bylica. +// Licensed under the Apache License, Version 2.0. + +#include "hash_types.hpp" + +#include "../attributes.h" +#include "bit_manipulation.h" +#include "endianness.hpp" +#include "ethash-internal.hpp" +#include "primes.h" + +#include "keccak.hpp" +#include "progpow.hpp" + +#include <cassert> +#include <cstdlib> +#include <cstring> +#include <limits> + +namespace ethash +{ +// Internal constants: +constexpr static int light_cache_init_size = 1 << 24; +constexpr static int light_cache_growth = 1 << 17; +constexpr static int light_cache_rounds = 3; +constexpr static int full_dataset_init_size = 1 << 30; +constexpr static int full_dataset_growth = 1 << 23; +constexpr static int full_dataset_item_parents = 512; + +// Verify constants: +static_assert(sizeof(hash512) == ETHASH_LIGHT_CACHE_ITEM_SIZE, ""); +static_assert(sizeof(hash1024) == ETHASH_FULL_DATASET_ITEM_SIZE, ""); +static_assert(light_cache_item_size == ETHASH_LIGHT_CACHE_ITEM_SIZE, ""); +static_assert(full_dataset_item_size == ETHASH_FULL_DATASET_ITEM_SIZE, ""); + + +namespace +{ +using ::fnv1; + +inline hash512 fnv1(const hash512& u, const hash512& v) noexcept +{ + hash512 r; + for (size_t i = 0; i < sizeof(r) / sizeof(r.word32s[0]); ++i) + r.word32s[i] = fnv1(u.word32s[i], v.word32s[i]); + return r; +} + +inline hash512 bitwise_xor(const hash512& x, const hash512& y) noexcept +{ + hash512 z; + for (size_t i = 0; i < sizeof(z) / sizeof(z.word64s[0]); ++i) + z.word64s[i] = x.word64s[i] ^ y.word64s[i]; + return z; +} +} // namespace + +int find_epoch_number(const hash256& seed) noexcept +{ + static constexpr int num_tries = 30000; // Divisible by 16. + + // Thread-local cache of the last search. + static thread_local int cached_epoch_number = 0; + static thread_local hash256 cached_seed = {}; + + // Load from memory once (memory will be clobbered by keccak256()). + const uint32_t seed_part = seed.word32s[0]; + const int e = cached_epoch_number; + hash256 s = cached_seed; + + if (s.word32s[0] == seed_part) + return e; + + // Try the next seed, will match for sequential epoch access. + s = keccak256(s); + if (s.word32s[0] == seed_part) + { + cached_seed = s; + cached_epoch_number = e + 1; + return e + 1; + } + + // Search for matching seed starting from epoch 0. + s = {}; + for (int i = 0; i < num_tries; ++i) + { + if (s.word32s[0] == seed_part) + { + cached_seed = s; + cached_epoch_number = i; + return i; + } + + s = keccak256(s); + } + + return -1; +} + +namespace generic +{ +void build_light_cache( + hash_fn_512 hash_fn, hash512 cache[], int num_items, const hash256& seed) noexcept +{ + hash512 item = hash_fn(seed.bytes, sizeof(seed)); + cache[0] = item; + for (int i = 1; i < num_items; ++i) + { + item = hash_fn(item.bytes, sizeof(item)); + cache[i] = item; + } + + for (int q = 0; q < light_cache_rounds; ++q) + { + for (int i = 0; i < num_items; ++i) + { + const uint32_t index_limit = static_cast<uint32_t>(num_items); + + // Fist index: 4 first bytes of the item as little-endian integer. + const uint32_t t = le::uint32(cache[i].word32s[0]); + const uint32_t v = t % index_limit; + + // Second index. + const uint32_t w = static_cast<uint32_t>(num_items + (i - 1)) % index_limit; + + const hash512 x = bitwise_xor(cache[v], cache[w]); + cache[i] = hash_fn(x.bytes, sizeof(x)); + } + } +} + +epoch_context_full* create_epoch_context( + build_light_cache_fn build_fn, int epoch_number, bool full) noexcept +{ + static_assert(sizeof(epoch_context_full) < sizeof(hash512), "epoch_context too big"); + static constexpr size_t context_alloc_size = sizeof(hash512); + + const int light_cache_num_items = calculate_light_cache_num_items(epoch_number); + const int full_dataset_num_items = calculate_full_dataset_num_items(epoch_number); + const size_t light_cache_size = get_light_cache_size(light_cache_num_items); + const size_t full_dataset_size = + full ? static_cast<size_t>(full_dataset_num_items) * sizeof(hash1024) : + progpow::l1_cache_size; + + const size_t alloc_size = context_alloc_size + light_cache_size + full_dataset_size; + + char* const alloc_data = static_cast<char*>(std::calloc(1, alloc_size)); + if (!alloc_data) + return nullptr; // Signal out-of-memory by returning null pointer. + + hash512* const light_cache = reinterpret_cast<hash512*>(alloc_data + context_alloc_size); + const hash256 epoch_seed = calculate_epoch_seed(epoch_number); + build_fn(light_cache, light_cache_num_items, epoch_seed); + + uint32_t* const l1_cache = + reinterpret_cast<uint32_t*>(alloc_data + context_alloc_size + light_cache_size); + + hash1024* full_dataset = full ? reinterpret_cast<hash1024*>(l1_cache) : nullptr; + + epoch_context_full* const context = new (alloc_data) epoch_context_full{ + epoch_number, + light_cache_num_items, + light_cache, + l1_cache, + full_dataset_num_items, + full_dataset, + }; + + auto* full_dataset_2048 = reinterpret_cast<hash2048*>(l1_cache); + for (uint32_t i = 0; i < progpow::l1_cache_size / sizeof(full_dataset_2048[0]); ++i) + full_dataset_2048[i] = calculate_dataset_item_2048(*context, i); + return context; +} +} // namespace generic + +void build_light_cache(hash512 cache[], int num_items, const hash256& seed) noexcept +{ + return generic::build_light_cache(keccak512, cache, num_items, seed); +} + +struct item_state +{ + const hash512* const cache; + const int64_t num_cache_items; + const uint32_t seed; + + hash512 mix; + + ALWAYS_INLINE item_state(const epoch_context& context, int64_t index) noexcept + : cache{context.light_cache}, + num_cache_items{context.light_cache_num_items}, + seed{static_cast<uint32_t>(index)} + { + mix = cache[index % num_cache_items]; + mix.word32s[0] ^= le::uint32(seed); + mix = le::uint32s(keccak512(mix)); + } + + ALWAYS_INLINE void update(uint32_t round) noexcept + { + static constexpr size_t num_words = sizeof(mix) / sizeof(uint32_t); + const uint32_t t = fnv1(seed ^ round, mix.word32s[round % num_words]); + const int64_t parent_index = t % num_cache_items; + mix = fnv1(mix, le::uint32s(cache[parent_index])); + } + + ALWAYS_INLINE hash512 final() noexcept { return keccak512(le::uint32s(mix)); } +}; + +hash512 calculate_dataset_item_512(const epoch_context& context, int64_t index) noexcept +{ + item_state item0{context, index}; + for (uint32_t j = 0; j < full_dataset_item_parents; ++j) + item0.update(j); + return item0.final(); +} + +/// Calculates a full dataset item +/// +/// This consist of two 512-bit items produced by calculate_dataset_item_partial(). +/// Here the computation is done interleaved for better performance. +hash1024 calculate_dataset_item_1024(const epoch_context& context, uint32_t index) noexcept +{ + item_state item0{context, int64_t(index) * 2}; + item_state item1{context, int64_t(index) * 2 + 1}; + + for (uint32_t j = 0; j < full_dataset_item_parents; ++j) + { + item0.update(j); + item1.update(j); + } + + return hash1024{{item0.final(), item1.final()}}; +} + +hash2048 calculate_dataset_item_2048(const epoch_context& context, uint32_t index) noexcept +{ + item_state item0{context, int64_t(index) * 4}; + item_state item1{context, int64_t(index) * 4 + 1}; + item_state item2{context, int64_t(index) * 4 + 2}; + item_state item3{context, int64_t(index) * 4 + 3}; + + for (uint32_t j = 0; j < full_dataset_item_parents; ++j) + { + item0.update(j); + item1.update(j); + item2.update(j); + item3.update(j); + } + + return hash2048{{item0.final(), item1.final(), item2.final(), item3.final()}}; +} + +namespace +{ +using lookup_fn = hash1024 (*)(const epoch_context&, uint32_t); + +inline hash512 hash_seed(const hash256& header_hash, uint64_t nonce) noexcept +{ + nonce = le::uint64(nonce); + uint8_t init_data[sizeof(header_hash) + sizeof(nonce)]; + std::memcpy(&init_data[0], &header_hash, sizeof(header_hash)); + std::memcpy(&init_data[sizeof(header_hash)], &nonce, sizeof(nonce)); + + return keccak512(init_data, sizeof(init_data)); +} + +inline hash256 hash_final(const hash512& seed, const hash256& mix_hash) +{ + uint8_t final_data[sizeof(seed) + sizeof(mix_hash)]; + std::memcpy(&final_data[0], seed.bytes, sizeof(seed)); + std::memcpy(&final_data[sizeof(seed)], mix_hash.bytes, sizeof(mix_hash)); + return keccak256(final_data, sizeof(final_data)); +} + +inline hash256 hash_kernel( + const epoch_context& context, const hash512& seed, lookup_fn lookup) noexcept +{ + static constexpr size_t num_words = sizeof(hash1024) / sizeof(uint32_t); + const uint32_t index_limit = static_cast<uint32_t>(context.full_dataset_num_items); + const uint32_t seed_init = le::uint32(seed.word32s[0]); + + hash1024 mix{{le::uint32s(seed), le::uint32s(seed)}}; + + for (uint32_t i = 0; i < num_dataset_accesses; ++i) + { + const uint32_t p = fnv1(i ^ seed_init, mix.word32s[i % num_words]) % index_limit; + const hash1024 newdata = le::uint32s(lookup(context, p)); + + for (size_t j = 0; j < num_words; ++j) + mix.word32s[j] = fnv1(mix.word32s[j], newdata.word32s[j]); + } + + hash256 mix_hash; + for (size_t i = 0; i < num_words; i += 4) + { + const uint32_t h1 = fnv1(mix.word32s[i], mix.word32s[i + 1]); + const uint32_t h2 = fnv1(h1, mix.word32s[i + 2]); + const uint32_t h3 = fnv1(h2, mix.word32s[i + 3]); + mix_hash.word32s[i / 4] = h3; + } + + return le::uint32s(mix_hash); +} +} // namespace + +result hash(const epoch_context_full& context, const hash256& header_hash, uint64_t nonce) noexcept +{ + static const auto lazy_lookup = [](const epoch_context& ctx, uint32_t index) noexcept { + auto full_dataset = static_cast<const epoch_context_full&>(ctx).full_dataset; + hash1024& item = full_dataset[index]; + if (item.word64s[0] == 0) + { + // TODO: Copy elision here makes it thread-safe? + item = calculate_dataset_item_1024(ctx, index); + } + + return item; + }; + + const hash512 seed = hash_seed(header_hash, nonce); + const hash256 mix_hash = hash_kernel(context, seed, lazy_lookup); + return {hash_final(seed, mix_hash), mix_hash}; +} + +search_result search_light(const epoch_context& context, const hash256& header_hash, + const hash256& boundary, uint64_t start_nonce, size_t iterations) noexcept +{ + const uint64_t end_nonce = start_nonce + iterations; + for (uint64_t nonce = start_nonce; nonce < end_nonce; ++nonce) + { + result r = hash(context, header_hash, nonce); + if (is_less_or_equal(r.final_hash, boundary)) + return {r, nonce}; + } + return {}; +} + +search_result search(const epoch_context_full& context, const hash256& header_hash, + const hash256& boundary, uint64_t start_nonce, size_t iterations) noexcept +{ + const uint64_t end_nonce = start_nonce + iterations; + for (uint64_t nonce = start_nonce; nonce < end_nonce; ++nonce) + { + result r = hash(context, header_hash, nonce); + if (is_less_or_equal(r.final_hash, boundary)) + return {r, nonce}; + } + return {}; +} +} // namespace ethash + +using namespace ethash; + +extern "C" { + +ethash_hash256 ethash_calculate_epoch_seed(int epoch_number) noexcept +{ + ethash_hash256 epoch_seed = {}; + for (int i = 0; i < epoch_number; ++i) + epoch_seed = ethash_keccak256_32(epoch_seed.bytes); + return epoch_seed; +} + +int ethash_calculate_light_cache_num_items(int epoch_number) noexcept +{ + static constexpr int item_size = sizeof(hash512); + static constexpr int num_items_init = light_cache_init_size / item_size; + static constexpr int num_items_growth = light_cache_growth / item_size; + static_assert( + light_cache_init_size % item_size == 0, "light_cache_init_size not multiple of item size"); + static_assert( + light_cache_growth % item_size == 0, "light_cache_growth not multiple of item size"); + + int num_items_upper_bound = num_items_init + epoch_number * num_items_growth; + int num_items = ethash_find_largest_prime(num_items_upper_bound); + return num_items; +} + +int ethash_calculate_full_dataset_num_items(int epoch_number) noexcept +{ + static constexpr int item_size = sizeof(hash1024); + static constexpr int num_items_init = full_dataset_init_size / item_size; + static constexpr int num_items_growth = full_dataset_growth / item_size; + static_assert(full_dataset_init_size % item_size == 0, + "full_dataset_init_size not multiple of item size"); + static_assert( + full_dataset_growth % item_size == 0, "full_dataset_growth not multiple of item size"); + + int num_items_upper_bound = num_items_init + epoch_number * num_items_growth; + int num_items = ethash_find_largest_prime(num_items_upper_bound); + return num_items; +} + +epoch_context* ethash_create_epoch_context(int epoch_number) noexcept +{ + return generic::create_epoch_context(build_light_cache, epoch_number, false); +} + +epoch_context_full* ethash_create_epoch_context_full(int epoch_number) noexcept +{ + return generic::create_epoch_context(build_light_cache, epoch_number, true); +} + +void ethash_destroy_epoch_context_full(epoch_context_full* context) noexcept +{ + ethash_destroy_epoch_context(context); +} + +void ethash_destroy_epoch_context(epoch_context* context) noexcept +{ + context->~epoch_context(); + std::free(context); +} + + +ethash_result ethash_hash( + const epoch_context* context, const hash256* header_hash, uint64_t nonce) noexcept +{ + const hash512 seed = hash_seed(*header_hash, nonce); + const hash256 mix_hash = hash_kernel(*context, seed, calculate_dataset_item_1024); + return {hash_final(seed, mix_hash), mix_hash}; +} + +bool ethash_verify_final_hash(const hash256* header_hash, const hash256* mix_hash, uint64_t nonce, + const hash256* boundary) noexcept +{ + const hash512 seed = hash_seed(*header_hash, nonce); + return is_less_or_equal(hash_final(seed, *mix_hash), *boundary); +} + +bool ethash_verify(const epoch_context* context, const hash256* header_hash, + const hash256* mix_hash, uint64_t nonce, const hash256* boundary) noexcept +{ + const hash512 seed = hash_seed(*header_hash, nonce); + if (!is_less_or_equal(hash_final(seed, *mix_hash), *boundary)) + return false; + + const hash256 expected_mix_hash = hash_kernel(*context, seed, calculate_dataset_item_1024); + return is_equal(expected_mix_hash, *mix_hash); +} + +} // extern "C" diff --git a/src/Native/libkawpow/ethash/ethash.h b/src/Native/libkawpow/ethash/ethash.h new file mode 100644 index 000000000..5d4230f6e --- /dev/null +++ b/src/Native/libkawpow/ethash/ethash.h @@ -0,0 +1,145 @@ +/* ethash: C/C++ implementation of Ethash, the Ethereum Proof of Work algorithm. + * Copyright 2018-2019 Pawel Bylica. + * Licensed under the Apache License, Version 2.0. + */ + +#pragma once + +#include "hash_types.h" + +#include <stdbool.h> +#include <stdint.h> + +#if defined(_MSC_VER) +// Microsoft +#define EXPORT __declspec(dllexport) +#define IMPORT __declspec(dllimport) +#elif defined(__GNUC__) +// GCC +#define EXPORT __attribute__((visibility("default"))) +#define IMPORT +#else +// do nothing and hope for the best? +#define EXPORT +#define IMPORT +#pragma warning Unknown dynamic link import / export semantics. +#endif + +#ifdef __cplusplus +#define NOEXCEPT noexcept +#else +#define NOEXCEPT +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * The Ethash algorithm revision implemented as specified in the Ethash spec + * https://github.com/ethereum/wiki/wiki/Ethash. + */ +#define ETHASH_REVISION "23" + +#define ETHASH_EPOCH_LENGTH 7500 +#define ETHASH_LIGHT_CACHE_ITEM_SIZE 64 +#define ETHASH_FULL_DATASET_ITEM_SIZE 128 +#define ETHASH_NUM_DATASET_ACCESSES 64 + + +struct ethash_epoch_context +{ + const int epoch_number; + const int light_cache_num_items; + const union ethash_hash512* const light_cache; + const uint32_t* const l1_cache; + const int full_dataset_num_items; +}; + + +struct ethash_epoch_context_full; + + +struct ethash_result +{ + union ethash_hash256 final_hash; + union ethash_hash256 mix_hash; +}; + + +/** + * Calculates the number of items in the light cache for given epoch. + * + * This function will search for a prime number matching the criteria given + * by the Ethash so the execution time is not constant. It takes ~ 0.01 ms. + * + * @param epoch_number The epoch number. + * @return The number items in the light cache. + */ +int ethash_calculate_light_cache_num_items(int epoch_number) NOEXCEPT; + + +/** + * Calculates the number of items in the full dataset for given epoch. + * + * This function will search for a prime number matching the criteria given + * by the Ethash so the execution time is not constant. It takes ~ 0.05 ms. + * + * @param epoch_number The epoch number. + * @return The number items in the full dataset. + */ +int ethash_calculate_full_dataset_num_items(int epoch_number) NOEXCEPT; + +/** + * Calculates the epoch seed hash. + * @param epoch_number The epoch number. + * @return The epoch seed hash. + */ +union ethash_hash256 ethash_calculate_epoch_seed(int epoch_number) NOEXCEPT; + + +EXPORT struct ethash_epoch_context* ethash_create_epoch_context(int epoch_number) NOEXCEPT; + +/** + * Creates the epoch context with the full dataset initialized. + * + * The memory for the full dataset is only allocated and marked as "not-generated". + * The items of the full dataset are generated on the fly when hit for the first time. + * + * The memory allocated in the context MUST be freed with ethash_destroy_epoch_context_full(). + * + * @param epoch_number The epoch number. + * @return Pointer to the context or null in case of memory allocation failure. + */ +EXPORT struct ethash_epoch_context_full* ethash_create_epoch_context_full(int epoch_number) NOEXCEPT; + +EXPORT void ethash_destroy_epoch_context(struct ethash_epoch_context* context) NOEXCEPT; + +EXPORT void ethash_destroy_epoch_context_full(struct ethash_epoch_context_full* context) NOEXCEPT; + + +/** + * Get global shared epoch context. + */ +const struct ethash_epoch_context* ethash_get_global_epoch_context(int epoch_number) NOEXCEPT; + +/** + * Get global shared epoch context with full dataset initialized. + */ +const struct ethash_epoch_context_full* ethash_get_global_epoch_context_full( + int epoch_number) NOEXCEPT; + + +struct ethash_result ethash_hash(const struct ethash_epoch_context* context, + const union ethash_hash256* header_hash, uint64_t nonce) NOEXCEPT; + +bool ethash_verify(const struct ethash_epoch_context* context, + const union ethash_hash256* header_hash, const union ethash_hash256* mix_hash, uint64_t nonce, + const union ethash_hash256* boundary) NOEXCEPT; +bool ethash_verify_final_hash(const union ethash_hash256* header_hash, + const union ethash_hash256* mix_hash, uint64_t nonce, + const union ethash_hash256* boundary) NOEXCEPT; + +#ifdef __cplusplus +} +#endif diff --git a/src/Native/libkawpow/ethash/ethash.hpp b/src/Native/libkawpow/ethash/ethash.hpp new file mode 100644 index 000000000..835a3b731 --- /dev/null +++ b/src/Native/libkawpow/ethash/ethash.hpp @@ -0,0 +1,172 @@ +// ethash: C/C++ implementation of Ethash, the Ethereum Proof of Work algorithm. +// Copyright 2018-2019 Pawel Bylica. +// Licensed under the Apache License, Version 2.0. + +/// @file +/// +/// API design decisions: +/// +/// 1. Signed integer type is used whenever the size of the type is not +/// restricted by the Ethash specification. +/// See http://www.aristeia.com/Papers/C++ReportColumns/sep95.pdf. +/// See https://stackoverflow.com/questions/10168079/why-is-size-t-unsigned/. +/// See https://github.com/Microsoft/GSL/issues/171. + +#pragma once + +#include "ethash.h" +#include "hash_types.hpp" + +#include <cstdint> +#include <cstring> +#include <memory> + +namespace ethash +{ +constexpr auto revision = ETHASH_REVISION; + +static constexpr int epoch_length = ETHASH_EPOCH_LENGTH; +static constexpr int light_cache_item_size = ETHASH_LIGHT_CACHE_ITEM_SIZE; +static constexpr int full_dataset_item_size = ETHASH_FULL_DATASET_ITEM_SIZE; +static constexpr int num_dataset_accesses = ETHASH_NUM_DATASET_ACCESSES; + +using epoch_context = ethash_epoch_context; +using epoch_context_full = ethash_epoch_context_full; + +using result = ethash_result; + +/// Constructs a 256-bit hash from an array of bytes. +/// +/// @param bytes A pointer to array of at least 32 bytes. +/// @return The constructed hash. +inline hash256 hash256_from_bytes(const uint8_t bytes[32]) noexcept +{ + hash256 h; + std::memcpy(&h, bytes, sizeof(h)); + return h; +} + +struct search_result +{ + bool solution_found = false; + uint64_t nonce = 0; + hash256 final_hash = {}; + hash256 mix_hash = {}; + + search_result() noexcept = default; + + search_result(result res, uint64_t n) noexcept + : solution_found(true), nonce(n), final_hash(res.final_hash), mix_hash(res.mix_hash) + {} +}; + + +/// Alias for ethash_calculate_light_cache_num_items(). +static constexpr auto calculate_light_cache_num_items = ethash_calculate_light_cache_num_items; + +/// Alias for ethash_calculate_full_dataset_num_items(). +static constexpr auto calculate_full_dataset_num_items = ethash_calculate_full_dataset_num_items; + +/// Alias for ethash_calculate_epoch_seed(). +static constexpr auto calculate_epoch_seed = ethash_calculate_epoch_seed; + + +/// Calculates the epoch number out of the block number. +inline constexpr int get_epoch_number(int block_number) noexcept +{ + return block_number / epoch_length; +} + +/** + * Coverts the number of items of a light cache to size in bytes. + * + * @param num_items The number of items in the light cache. + * @return The size of the light cache in bytes. + */ +inline constexpr size_t get_light_cache_size(int num_items) noexcept +{ + return static_cast<size_t>(num_items) * light_cache_item_size; +} + +/** + * Coverts the number of items of a full dataset to size in bytes. + * + * @param num_items The number of items in the full dataset. + * @return The size of the full dataset in bytes. + */ +inline constexpr uint64_t get_full_dataset_size(int num_items) noexcept +{ + return static_cast<uint64_t>(num_items) * full_dataset_item_size; +} + +/// Owned unique pointer to an epoch context. +using epoch_context_ptr = std::unique_ptr<epoch_context, decltype(&ethash_destroy_epoch_context)>; + +using epoch_context_full_ptr = + std::unique_ptr<epoch_context_full, decltype(&ethash_destroy_epoch_context_full)>; + +/// Creates Ethash epoch context. +/// +/// This is a wrapper for ethash_create_epoch_number C function that returns +/// the context as a smart pointer which handles the destruction of the context. +inline epoch_context_ptr create_epoch_context(int epoch_number) noexcept +{ + return {ethash_create_epoch_context(epoch_number), ethash_destroy_epoch_context}; +} + +inline epoch_context_full_ptr create_epoch_context_full(int epoch_number) noexcept +{ + return {ethash_create_epoch_context_full(epoch_number), ethash_destroy_epoch_context_full}; +} + + +inline result hash( + const epoch_context& context, const hash256& header_hash, uint64_t nonce) noexcept +{ + return ethash_hash(&context, &header_hash, nonce); +} + +result hash(const epoch_context_full& context, const hash256& header_hash, uint64_t nonce) noexcept; + +inline bool verify_final_hash(const hash256& header_hash, const hash256& mix_hash, uint64_t nonce, + const hash256& boundary) noexcept +{ + return ethash_verify_final_hash(&header_hash, &mix_hash, nonce, &boundary); +} + +inline bool verify(const epoch_context& context, const hash256& header_hash, const hash256& mix_hash, + uint64_t nonce, const hash256& boundary) noexcept +{ + return ethash_verify(&context, &header_hash, &mix_hash, nonce, &boundary); +} + +search_result search_light(const epoch_context& context, const hash256& header_hash, + const hash256& boundary, uint64_t start_nonce, size_t iterations) noexcept; + +search_result search(const epoch_context_full& context, const hash256& header_hash, + const hash256& boundary, uint64_t start_nonce, size_t iterations) noexcept; + + +/// Tries to find the epoch number matching the given seed hash. +/// +/// Mining pool protocols (many variants of stratum and "getwork") send out +/// seed hash instead of epoch number to workers. This function tries to recover +/// the epoch number from this seed hash. +/// +/// @param seed Ethash seed hash. +/// @return The epoch number or -1 if not found. +int find_epoch_number(const hash256& seed) noexcept; + + +/// Get global shared epoch context. +inline const epoch_context& get_global_epoch_context(int epoch_number) noexcept +{ + return *ethash_get_global_epoch_context(epoch_number); +} + +/// Get global shared epoch context with full dataset initialized. +inline const epoch_context_full& get_global_epoch_context_full(int epoch_number) noexcept +{ + return *ethash_get_global_epoch_context_full(epoch_number); +} +} // namespace ethash diff --git a/src/Native/libkawpow/ethash/hash_types.h b/src/Native/libkawpow/ethash/hash_types.h new file mode 100644 index 000000000..bd9343686 --- /dev/null +++ b/src/Native/libkawpow/ethash/hash_types.h @@ -0,0 +1,50 @@ +/* ethash: C/C++ implementation of Ethash, the Ethereum Proof of Work algorithm. + * Copyright 2018-2019 Pawel Bylica. + * Licensed under the Apache License, Version 2.0. + */ + +#pragma once + +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +union ethash_hash256 +{ + uint64_t word64s[4]; + uint32_t word32s[8]; + uint8_t bytes[32]; + char str[32]; +}; + +union ethash_hash512 +{ + uint64_t word64s[8]; + uint32_t word32s[16]; + uint8_t bytes[64]; + char str[64]; +}; + +union ethash_hash1024 +{ + union ethash_hash512 hash512s[2]; + uint64_t word64s[16]; + uint32_t word32s[32]; + uint8_t bytes[128]; + char str[128]; +}; + +union ethash_hash2048 +{ + union ethash_hash512 hash512s[4]; + uint64_t word64s[32]; + uint32_t word32s[64]; + uint8_t bytes[256]; + char str[256]; +}; + +#ifdef __cplusplus +} +#endif diff --git a/src/Native/libkawpow/ethash/hash_types.hpp b/src/Native/libkawpow/ethash/hash_types.hpp new file mode 100644 index 000000000..a31cff49f --- /dev/null +++ b/src/Native/libkawpow/ethash/hash_types.hpp @@ -0,0 +1,15 @@ +// ethash: C/C++ implementation of Ethash, the Ethereum Proof of Work algorithm. +// Copyright 2018-2019 Pawel Bylica. +// Licensed under the Apache License, Version 2.0. + +#pragma once + +#include "hash_types.h" + +namespace ethash +{ +using hash256 = ethash_hash256; +using hash512 = ethash_hash512; +using hash1024 = ethash_hash1024; +using hash2048 = ethash_hash2048; +} // namespace ethash diff --git a/src/Native/libkawpow/ethash/keccak.h b/src/Native/libkawpow/ethash/keccak.h new file mode 100644 index 000000000..79ef7c5cc --- /dev/null +++ b/src/Native/libkawpow/ethash/keccak.h @@ -0,0 +1,49 @@ +/* ethash: C/C++ implementation of Ethash, the Ethereum Proof of Work algorithm. + * Copyright 2018-2019 Pawel Bylica. + * Licensed under the Apache License, Version 2.0. + */ + +#pragma once + +#include "hash_types.h" + +#include <stddef.h> + +#ifdef __cplusplus +#define NOEXCEPT noexcept +#else +#define NOEXCEPT +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * The Keccak-f[1600] function. + * + * The implementation of the Keccak-f function with 1600-bit width of the permutation (b). + * The size of the state is also 1600 bit what gives 25 64-bit words. + * + * @param state The state of 25 64-bit words on which the permutation is to be performed. + */ +void ethash_keccakf1600(uint64_t state[25]) NOEXCEPT; + +/** + * The Keccak-f[800] function. + * + * The implementation of the Keccak-f function with 800-bit width of the permutation (b). + * The size of the state is also 800 bit what gives 25 32-bit words. + * + * @param state The state of 25 32-bit words on which the permutation is to be performed. + */ +void ethash_keccakf800(uint32_t state[25]) NOEXCEPT; + +union ethash_hash256 ethash_keccak256(const uint8_t* data, size_t size) NOEXCEPT; +union ethash_hash256 ethash_keccak256_32(const uint8_t data[32]) NOEXCEPT; +union ethash_hash512 ethash_keccak512(const uint8_t* data, size_t size) NOEXCEPT; +union ethash_hash512 ethash_keccak512_64(const uint8_t data[64]) NOEXCEPT; + +#ifdef __cplusplus +} +#endif diff --git a/src/Native/libkawpow/ethash/keccak.hpp b/src/Native/libkawpow/ethash/keccak.hpp new file mode 100644 index 000000000..974067922 --- /dev/null +++ b/src/Native/libkawpow/ethash/keccak.hpp @@ -0,0 +1,35 @@ +// ethash: C/C++ implementation of Ethash, the Ethereum Proof of Work algorithm. +// Copyright 2018-2019 Pawel Bylica. +// Licensed under the Apache License, Version 2.0. + +#pragma once + +#include "keccak.h" +#include "hash_types.hpp" + +namespace ethash +{ +inline hash256 keccak256(const uint8_t* data, size_t size) noexcept +{ + return ethash_keccak256(data, size); +} + +inline hash256 keccak256(const hash256& input) noexcept +{ + return ethash_keccak256_32(input.bytes); +} + +inline hash512 keccak512(const uint8_t* data, size_t size) noexcept +{ + return ethash_keccak512(data, size); +} + +inline hash512 keccak512(const hash512& input) noexcept +{ + return ethash_keccak512_64(input.bytes); +} + +static constexpr auto keccak256_32 = ethash_keccak256_32; +static constexpr auto keccak512_64 = ethash_keccak512_64; + +} // namespace ethash diff --git a/src/Native/libkawpow/ethash/kiss99.hpp b/src/Native/libkawpow/ethash/kiss99.hpp new file mode 100644 index 000000000..8332a7ce9 --- /dev/null +++ b/src/Native/libkawpow/ethash/kiss99.hpp @@ -0,0 +1,64 @@ +/* ethash: C/C++ implementation of Ethash, the Ethereum Proof of Work algorithm. + * Copyright 2018-2019 Pawel Bylica. + * Licensed under the Apache License, Version 2.0. + */ + +#pragma once + +#include "../support/attributes.h" +#include <stdint.h> + +/** + * KISS PRNG by the spec from 1999. + * + * The implementation of KISS pseudo-random number generator + * by the specification published on 21 Jan 1999 in + * http://www.cse.yorku.ca/~oz/marsaglia-rng.html. + * The KISS is not versioned so here we are using `kiss99` prefix to indicate + * the version from 1999. + * + * The specification uses `unsigned long` type with the intention for 32-bit + * values. Because in GCC/clang for 64-bit architectures `unsigned long` is + * 64-bit size type, here the explicit `uint32_t` type is used. + * + * @defgroup kiss99 KISS99 + * @{ + */ + +/** + * The KISS generator. + */ +class kiss99 +{ + uint32_t z = 362436069; + uint32_t w = 521288629; + uint32_t jsr = 123456789; + uint32_t jcong = 380116160; + +public: + /** Creates KISS generator state with default values provided by the specification. */ + kiss99() noexcept = default; + + /** Creates KISS generator state with provided init values.*/ + kiss99(uint32_t _z, uint32_t _w, uint32_t _jsr, uint32_t _jcong) noexcept + : z{_z}, w{_w}, jsr{_jsr}, jcong{_jcong} + {} + + /** Generates next number from the KISS generator. */ + NO_SANITIZE("unsigned-integer-overflow") + uint32_t operator()() noexcept + { + z = 36969 * (z & 0xffff) + (z >> 16); + w = 18000 * (w & 0xffff) + (w >> 16); + + jcong = 69069 * jcong + 1234567; + + jsr ^= (jsr << 17); + jsr ^= (jsr >> 13); + jsr ^= (jsr << 5); + + return (((z << 16) + w) ^ jcong) + jsr; + } +}; + +/** @} */ diff --git a/src/Native/libkawpow/ethash/managed.cpp b/src/Native/libkawpow/ethash/managed.cpp new file mode 100644 index 000000000..47dd49b8e --- /dev/null +++ b/src/Native/libkawpow/ethash/managed.cpp @@ -0,0 +1,99 @@ +// ethash: C/C++ implementation of Ethash, the Ethereum Proof of Work algorithm. +// Copyright 2018-2019 Pawel Bylica. +// Licensed under the Apache License, Version 2.0. + +#include "ethash-internal.hpp" + +#include <memory> +#include <mutex> + +#if !defined(__has_cpp_attribute) +#define __has_cpp_attribute(x) 0 +#endif + +#if __has_cpp_attribute(gnu::noinline) +#define ATTRIBUTE_NOINLINE [[gnu::noinline]] +#elif _MSC_VER +#define ATTRIBUTE_NOINLINE __declspec(noinline) +#else +#define ATTRIBUTE_NOINLINE +#endif + +using namespace ethash; + +namespace +{ +std::mutex shared_context_mutex; +std::shared_ptr<epoch_context> shared_context; +thread_local std::shared_ptr<epoch_context> thread_local_context; + +std::mutex shared_context_full_mutex; +std::shared_ptr<epoch_context_full> shared_context_full; +thread_local std::shared_ptr<epoch_context_full> thread_local_context_full; + +/// Update thread local epoch context. +/// +/// This function is on the slow path. It's separated to allow inlining the fast +/// path. +/// +/// @todo: Redesign to guarantee deallocation before new allocation. +ATTRIBUTE_NOINLINE +void update_local_context(int epoch_number) +{ + // Release the shared pointer of the obsoleted context. + thread_local_context.reset(); + + // Local context invalid, check the shared context. + std::lock_guard<std::mutex> lock{shared_context_mutex}; + + if (!shared_context || shared_context->epoch_number != epoch_number) + { + // Release the shared pointer of the obsoleted context. + shared_context.reset(); + + // Build new context. + shared_context = create_epoch_context(epoch_number); + } + + thread_local_context = shared_context; +} + +ATTRIBUTE_NOINLINE +void update_local_context_full(int epoch_number) +{ + // Release the shared pointer of the obsoleted context. + thread_local_context_full.reset(); + + // Local context invalid, check the shared context. + std::lock_guard<std::mutex> lock{shared_context_full_mutex}; + + if (!shared_context_full || shared_context_full->epoch_number != epoch_number) + { + // Release the shared pointer of the obsoleted context. + shared_context_full.reset(); + + // Build new context. + shared_context_full = create_epoch_context_full(epoch_number); + } + + thread_local_context_full = shared_context_full; +} +} // namespace + +const ethash_epoch_context* ethash_get_global_epoch_context(int epoch_number) noexcept +{ + // Check if local context matches epoch number. + if (!thread_local_context || thread_local_context->epoch_number != epoch_number) + update_local_context(epoch_number); + + return thread_local_context.get(); +} + +const ethash_epoch_context_full* ethash_get_global_epoch_context_full(int epoch_number) noexcept +{ + // Check if local context matches epoch number. + if (!thread_local_context_full || thread_local_context_full->epoch_number != epoch_number) + update_local_context_full(epoch_number); + + return thread_local_context_full.get(); +} diff --git a/src/Native/libkawpow/ethash/primes.c b/src/Native/libkawpow/ethash/primes.c new file mode 100644 index 000000000..e27a535e3 --- /dev/null +++ b/src/Native/libkawpow/ethash/primes.c @@ -0,0 +1,43 @@ +/* ethash: C/C++ implementation of Ethash, the Ethereum Proof of Work algorithm. + * Copyright 2018-2019 Pawel Bylica. + * Licensed under the Apache License, Version 2.0. + */ + +#include "primes.h" + +/** Checks if the number is prime. Requires the number to be > 2 and odd. */ +static int is_odd_prime(int number) +{ + int d; + + /* Check factors up to sqrt(number). + To avoid computing sqrt, compare d*d <= number with 64-bit precision. */ + for (d = 3; (int64_t)d * (int64_t)d <= (int64_t)number; d += 2) + { + if (number % d == 0) + return 0; + } + + return 1; +} + +int ethash_find_largest_prime(int upper_bound) +{ + int n = upper_bound; + + if (n < 2) + return 0; + + if (n == 2) + return 2; + + /* If even number, skip it. */ + if (n % 2 == 0) + --n; + + /* Test descending odd numbers. */ + while (!is_odd_prime(n)) + n -= 2; + + return n; +} diff --git a/src/Native/libkawpow/ethash/primes.h b/src/Native/libkawpow/ethash/primes.h new file mode 100644 index 000000000..e37c532f2 --- /dev/null +++ b/src/Native/libkawpow/ethash/primes.h @@ -0,0 +1,25 @@ +/* ethash: C/C++ implementation of Ethash, the Ethereum Proof of Work algorithm. + * Copyright 2018-2019 Pawel Bylica. + * Licensed under the Apache License, Version 2.0. + */ + +#pragma once + +#include "ethash.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Finds the largest prime number not greater than the provided upper bound. + * + * @param upper_bound The upper bound. SHOULD be greater than 1. + * @return The largest prime number `p` such `p <= upper_bound`. + * In case `upper_bound <= 1`, returns 0. + */ +int ethash_find_largest_prime(int upper_bound) NOEXCEPT; + +#ifdef __cplusplus +} +#endif diff --git a/src/Native/libkawpow/ethash/progpow.cpp b/src/Native/libkawpow/ethash/progpow.cpp new file mode 100644 index 000000000..70041bbe2 --- /dev/null +++ b/src/Native/libkawpow/ethash/progpow.cpp @@ -0,0 +1,658 @@ +// ethash: C/C++ implementation of Ethash, the Ethereum Proof of Work algorithm. +// Copyright 2018-2019 Pawel Bylica. +// Licensed under the Apache License, Version 2.0. + +#include "progpow.hpp" + +#include "bit_manipulation.h" +#include "endianness.hpp" +#include "ethash-internal.hpp" +#include "kiss99.hpp" +#include "keccak.hpp" + +#include <array> + +namespace progpow +{ +namespace +{ +/// A variant of Keccak hash function for ProgPoW. +/// +/// This Keccak hash function uses 800-bit permutation (Keccak-f[800]) with 576 bitrate. +/// It take exactly 576 bits of input (split across 3 arguments) and adds no padding. +/// +/// @param header_hash The 256-bit header hash. +/// @param nonce The 64-bit nonce. +/// @param mix_hash Additional 256-bits of data. +/// @return The 256-bit output of the hash function. +void keccak_progpow_256(uint32_t* st) noexcept +{ + ethash_keccakf800(st); +} + +/// The same as keccak_progpow_256() but uses null mix +/// and returns top 64 bits of the output being a big-endian prefix of the 256-bit hash. + inline void keccak_progpow_64(uint32_t* st) noexcept +{ + keccak_progpow_256(st); +} + + +/// ProgPoW mix RNG state. +/// +/// Encapsulates the state of the random number generator used in computing ProgPoW mix. +/// This includes the state of the KISS99 RNG and the precomputed random permutation of the +/// sequence of mix item indexes. +class mix_rng_state +{ +public: + inline explicit mix_rng_state(uint32_t* seed) noexcept; + + uint32_t next_dst() noexcept { return dst_seq[(dst_counter++) % num_regs]; } + uint32_t next_src() noexcept { return src_seq[(src_counter++) % num_regs]; } + + kiss99 rng; + +private: + size_t dst_counter = 0; + std::array<uint32_t, num_regs> dst_seq; + size_t src_counter = 0; + std::array<uint32_t, num_regs> src_seq; +}; + +mix_rng_state::mix_rng_state(uint32_t* hash_seed) noexcept +{ + const auto seed_lo = static_cast<uint32_t>(hash_seed[0]); + const auto seed_hi = static_cast<uint32_t>(hash_seed[1]); + + const auto z = fnv1a(fnv_offset_basis, seed_lo); + const auto w = fnv1a(z, seed_hi); + const auto jsr = fnv1a(w, seed_lo); + const auto jcong = fnv1a(jsr, seed_hi); + + rng = kiss99{z, w, jsr, jcong}; + + // Create random permutations of mix destinations / sources. + // Uses Fisher-Yates shuffle. + for (uint32_t i = 0; i < num_regs; ++i) + { + dst_seq[i] = i; + src_seq[i] = i; + } + + for (uint32_t i = num_regs; i > 1; --i) + { + std::swap(dst_seq[i - 1], dst_seq[rng() % i]); + std::swap(src_seq[i - 1], src_seq[rng() % i]); + } +} + + +NO_SANITIZE("unsigned-integer-overflow") +inline uint32_t random_math(uint32_t a, uint32_t b, uint32_t selector) noexcept +{ + switch (selector % 11) + { + default: + case 0: + return a + b; + case 1: + return a * b; + case 2: + return mul_hi32(a, b); + case 3: + return std::min(a, b); + case 4: + return rotl32(a, b); + case 5: + return rotr32(a, b); + case 6: + return a & b; + case 7: + return a | b; + case 8: + return a ^ b; + case 9: + return clz32(a) + clz32(b); + case 10: + return popcount32(a) + popcount32(b); + } +} + +/// Merge data from `b` and `a`. +/// Assuming `a` has high entropy, only do ops that retain entropy even if `b` +/// has low entropy (i.e. do not do `a & b`). +NO_SANITIZE("unsigned-integer-overflow") +inline void random_merge(uint32_t& a, uint32_t b, uint32_t selector) noexcept +{ + const auto x = (selector >> 16) % 31 + 1; // Additional non-zero selector from higher bits. + switch (selector % 4) + { + case 0: + a = (a * 33) + b; + break; + case 1: + a = (a ^ b) * 33; + break; + case 2: + a = rotl32(a, x) ^ b; + break; + case 3: + a = rotr32(a, x) ^ b; + break; + } +} + +static const uint32_t ravencoin_kawpow[15] = { + 0x00000072, //R + 0x00000041, //A + 0x00000056, //V + 0x00000045, //E + 0x0000004E, //N + 0x00000043, //C + 0x0000004F, //O + 0x00000049, //I + 0x0000004E, //N + 0x0000004B, //K + 0x00000041, //A + 0x00000057, //W + 0x00000050, //P + 0x0000004F, //O + 0x00000057, //W +}; + +using lookup_fn = hash2048 (*)(const epoch_context&, uint32_t); + +using mix_array = std::array<std::array<uint32_t, num_regs>, num_lanes>; + +void round( + const epoch_context& context, uint32_t r, mix_array& mix, mix_rng_state state, lookup_fn lookup) +{ + const uint32_t num_items = static_cast<uint32_t>(context.full_dataset_num_items / 2); + const uint32_t item_index = mix[r % num_lanes][0] % num_items; + const hash2048 item = lookup(context, item_index); + + constexpr size_t num_words_per_lane = sizeof(item) / (sizeof(uint32_t) * num_lanes); + constexpr int max_operations = + num_cache_accesses > num_math_operations ? num_cache_accesses : num_math_operations; + + // Process lanes. + for (int i = 0; i < max_operations; ++i) + { + if (i < num_cache_accesses) // Random access to cached memory. + { + const auto src = state.next_src(); + const auto dst = state.next_dst(); + const auto sel = state.rng(); + + for (size_t l = 0; l < num_lanes; ++l) + { + const size_t offset = mix[l][src] % l1_cache_num_items; + random_merge(mix[l][dst], le::uint32(context.l1_cache[offset]), sel); + } + } + if (i < num_math_operations) // Random math. + { + // Generate 2 unique source indexes. + const auto src_rnd = state.rng() % (num_regs * (num_regs - 1)); + const auto src1 = src_rnd % num_regs; // O <= src1 < num_regs + auto src2 = src_rnd / num_regs; // 0 <= src2 < num_regs - 1 + if (src2 >= src1) + ++src2; + + const auto sel1 = state.rng(); + const auto dst = state.next_dst(); + const auto sel2 = state.rng(); + + for (size_t l = 0; l < num_lanes; ++l) + { + const uint32_t data = random_math(mix[l][src1], mix[l][src2], sel1); + random_merge(mix[l][dst], data, sel2); + } + } + } + + // DAG access pattern. + uint32_t dsts[num_words_per_lane]; + uint32_t sels[num_words_per_lane]; + for (size_t i = 0; i < num_words_per_lane; ++i) + { + dsts[i] = i == 0 ? 0 : state.next_dst(); + sels[i] = state.rng(); + } + + // DAG access. + for (size_t l = 0; l < num_lanes; ++l) + { + const auto offset = ((l ^ r) % num_lanes) * num_words_per_lane; + for (size_t i = 0; i < num_words_per_lane; ++i) + { + const auto word = le::uint32(item.word32s[offset + i]); + random_merge(mix[l][dsts[i]], word, sels[i]); + } + } +} + +mix_array init_mix(uint32_t* hash_seed) +{ + const uint32_t z = fnv1a(fnv_offset_basis, static_cast<uint32_t>(hash_seed[0])); + const uint32_t w = fnv1a(z, static_cast<uint32_t>(hash_seed[1])); + + mix_array mix; + for (uint32_t l = 0; l < mix.size(); ++l) + { + const uint32_t jsr = fnv1a(w, l); + const uint32_t jcong = fnv1a(jsr, l); + kiss99 rng{z, w, jsr, jcong}; + + for (auto& row : mix[l]) + row = rng(); + } + return mix; +} + +hash256 hash_mix( + const epoch_context& context, int block_number, uint32_t * seed, lookup_fn lookup) noexcept +{ + auto mix = init_mix(seed); + auto number = uint64_t(block_number / period_length); + uint32_t new_state[2]; + new_state[0] = (uint32_t)number; + new_state[1] = (uint32_t)(number >> 32); + mix_rng_state state{new_state}; + + for (uint32_t i = 0; i < 64; ++i) + round(context, i, mix, state, lookup); + + // Reduce mix data to a single per-lane result. + uint32_t lane_hash[num_lanes]; + for (size_t l = 0; l < num_lanes; ++l) + { + lane_hash[l] = fnv_offset_basis; + for (uint32_t i = 0; i < num_regs; ++i) + lane_hash[l] = fnv1a(lane_hash[l], mix[l][i]); + } + + // Reduce all lanes to a single 256-bit result. + static constexpr size_t num_words = sizeof(hash256) / sizeof(uint32_t); + hash256 mix_hash; + for (uint32_t& w : mix_hash.word32s) + w = fnv_offset_basis; + for (size_t l = 0; l < num_lanes; ++l) + mix_hash.word32s[l % num_words] = fnv1a(mix_hash.word32s[l % num_words], lane_hash[l]); + return le::uint32s(mix_hash); +} +} // namespace + +result hashext(const epoch_context& context, int block_number, const hash256& header_hash, + uint64_t nonce, const hash256& mix_hash, const hash256& boundary1, const hash256& boundary2, int* retcode) noexcept +{ + uint32_t hash_seed[2]; // KISS99 initiator + + uint32_t state2[8]; + + { + uint32_t state[25] = {0x0}; // Keccak's state + + // Absorb phase for initial round of keccak + // 1st fill with header data (8 words) + for (int i = 0; i < 8; i++) + state[i] = header_hash.word32s[i]; + + // 2nd fill with nonce (2 words) + state[8] = (uint32_t)nonce; + state[9] = (uint32_t)(nonce >> 32); + + // 3rd apply ravencoin input constraints + for (int i = 10; i < 25; i++) + state[i] = ravencoin_kawpow[i-10]; + + keccak_progpow_64(state); + + for (int i = 0; i < 8; i++) + state2[i] = state[i]; + } + + hash_seed[0] = state2[0]; + hash_seed[1] = state2[1]; + //mix hash was here + + uint32_t state[25] = {0x0}; // Keccak's state + + // Absorb phase for last round of keccak (256 bits) + // 1st initial 8 words of state are kept as carry-over from initial keccak + for (int i = 0; i < 8; i++) + state[i] = state2[i]; + + // 2nd subsequent 8 words are carried from digest/mix + for (int i = 8; i < 16; i++) + state[i] = mix_hash.word32s[i-8]; + + // 3rd apply ravencoin input constraints + for (int i = 16; i < 25; i++) + state[i] = ravencoin_kawpow[i - 16]; + + // Run keccak loop + keccak_progpow_256(state); + + /* mod start */ + hash256 output; + for (int i = 0; i < 8; ++i) + output.word32s[i] = le::uint32(state[i]); + + + if (!is_less_or_equal(output, boundary1)) { + //if(boundary1 == boundary2) { + if(is_equal(boundary1, boundary2)) { + *retcode = 1; + return {output, mix_hash}; + } + else { + if (!is_less_or_equal(output, boundary2)) { + *retcode = 1; + return {output, mix_hash}; + } + } + } + + const hash256 computed_mix_hash = hash_mix(context, block_number, hash_seed, calculate_dataset_item_2048); + if(!is_equal(computed_mix_hash, mix_hash)) { + *retcode = 2; + return {output, mix_hash}; + } + /* mod end */ + *retcode = 0; + return {output, computed_mix_hash}; +} + +result hash(const epoch_context& context, int block_number, const hash256& header_hash, + uint64_t nonce) noexcept +{ + uint32_t hash_seed[2]; // KISS99 initiator + + uint32_t state2[8]; + + { + uint32_t state[25] = {0x0}; // Keccak's state + + // Absorb phase for initial round of keccak + // 1st fill with header data (8 words) + for (int i = 0; i < 8; i++) + state[i] = header_hash.word32s[i]; + + // 2nd fill with nonce (2 words) + state[8] = (uint32_t)nonce; + state[9] = (uint32_t)(nonce >> 32); + + // 3rd apply ravencoin input constraints + for (int i = 10; i < 25; i++) + state[i] = ravencoin_kawpow[i-10]; + + keccak_progpow_64(state); + + for (int i = 0; i < 8; i++) + state2[i] = state[i]; + } + + hash_seed[0] = state2[0]; + hash_seed[1] = state2[1]; + const hash256 mix_hash = hash_mix(context, block_number, hash_seed, calculate_dataset_item_2048); + + uint32_t state[25] = {0x0}; // Keccak's state + + // Absorb phase for last round of keccak (256 bits) + // 1st initial 8 words of state are kept as carry-over from initial keccak + for (int i = 0; i < 8; i++) + state[i] = state2[i]; + + // 2nd subsequent 8 words are carried from digest/mix + for (int i = 8; i < 16; i++) + state[i] = mix_hash.word32s[i-8]; + + // 3rd apply ravencoin input constraints + for (int i = 16; i < 25; i++) + state[i] = ravencoin_kawpow[i - 16]; + + // Run keccak loop + keccak_progpow_256(state); + + hash256 output; + for (int i = 0; i < 8; ++i) + output.word32s[i] = le::uint32(state[i]); + + return {output, mix_hash}; +} + +result hash(const epoch_context_full& context, int block_number, const hash256& header_hash, + uint64_t nonce) noexcept +{ + static const auto lazy_lookup = [](const epoch_context& ctx, uint32_t index) noexcept + { + auto* full_dataset_1024 = static_cast<const epoch_context_full&>(ctx).full_dataset; + auto* full_dataset_2048 = reinterpret_cast<hash2048*>(full_dataset_1024); + hash2048& item = full_dataset_2048[index]; + if (item.word64s[0] == 0) + { + // TODO: Copy elision here makes it thread-safe? + item = calculate_dataset_item_2048(ctx, index); + } + + return item; + }; + + uint32_t hash_seed[2]; // KISS99 initiator + + uint32_t state2[8]; + + { + uint32_t state[25] = {0x0}; // Keccak's state + + // Absorb phase for initial round of keccak + // 1st fill with header data (8 words) + for (int i = 0; i < 8; i++) + state[i] = header_hash.word32s[i]; + + // 2nd fill with nonce (2 words) + state[8] = (uint32_t)nonce; + state[9] = (uint32_t)(nonce >> 32); + + // 3rd apply ravencoin input constraints + for (int i = 10; i < 25; i++) + state[i] = ravencoin_kawpow[i-10]; + + keccak_progpow_64(state); + + for (int i = 0; i < 8; i++) + state2[i] = state[i]; + } + + hash_seed[0] = state2[0]; + hash_seed[1] = state2[1]; + + const hash256 mix_hash = hash_mix(context, block_number, hash_seed, lazy_lookup); + + uint32_t state[25] = {0x0}; // Keccak's state + + + // Absorb phase for last round of keccak (256 bits) + // 1st initial 8 words of state are kept as carry-over from initial keccak + for (int i = 0; i < 8; i++) + state[i] = state2[i]; + + // 2nd subsequent 8 words are carried from digest/mix + for (int i = 8; i < 16; i++) + state[i] = mix_hash.word32s[i-8]; + + // 3rd apply ravencoin input constraints + for (int i = 16; i < 25; i++) + state[i] = ravencoin_kawpow[i - 16]; + + + + // Run keccak loop + keccak_progpow_256(state); + + + hash256 output; + for (int i = 0; i < 8; ++i) + output.word32s[i] = le::uint32(state[i]); + + + + return {output, mix_hash}; +} + +bool verify(const epoch_context& context, int block_number, const hash256& header_hash, + const hash256& mix_hash, uint64_t nonce, const hash256& boundary) noexcept +{ + uint32_t hash_seed[2]; // KISS99 initiator + uint32_t state2[8]; + + { + // Absorb phase for initial round of keccak + // 1st fill with header data (8 words) + uint32_t state[25] = {0x0}; // Keccak's state + for (int i = 0; i < 8; i++) + state[i] = header_hash.word32s[i]; + + // 2nd fill with nonce (2 words) + state[8] = (uint32_t)nonce; + state[9] = (uint32_t)(nonce >> 32); + + // 3rd apply ravencoin input constraints + for (int i = 10; i < 25; i++) + state[i] = ravencoin_kawpow[i-10]; + + keccak_progpow_64(state); + + for (int i = 0; i < 8; i++) + state2[i] = state[i]; + } + + hash_seed[0] = state2[0]; + hash_seed[1] = state2[1]; + + uint32_t state[25] = {0x0}; // Keccak's state + + // Absorb phase for last round of keccak (256 bits) + // 1st initial 8 words of state are kept as carry-over from initial keccak + for (int i = 0; i < 8; i++) + state[i] = state2[i]; + + + // 2nd subsequent 8 words are carried from digest/mix + for (int i = 8; i < 16; i++) + state[i] = mix_hash.word32s[i-8]; + + // 3rd apply ravencoin input constraints + for (int i = 16; i < 25; i++) + state[i] = ravencoin_kawpow[i - 16]; + + // Run keccak loop + keccak_progpow_256(state); + + hash256 output; + for (int i = 0; i < 8; ++i) + output.word32s[i] = le::uint32(state[i]); + + + if (!is_less_or_equal(output, boundary)) { + return false; + } + + const hash256 expected_mix_hash = + hash_mix(context, block_number, hash_seed, calculate_dataset_item_2048); + + return is_equal(expected_mix_hash, mix_hash); +} + +//bool light_verify(const char* str_header_hash, +// const char* str_mix_hash, const char* str_nonce, const char* str_boundary, char* str_final) noexcept +//{ +// +// hash256 header_hash = to_hash256(str_header_hash); +// hash256 mix_hash = to_hash256(str_mix_hash); +// hash256 boundary = to_hash256(str_boundary); +// +// uint64_t nonce = std::stoull(str_nonce, nullptr, 16); +// +// uint32_t state2[8]; +// +// { +// // Absorb phase for initial round of keccak +// // 1st fill with header data (8 words) +// uint32_t state[25]; // Keccak's state +// for (int i = 0; i < 8; i++) +// state[i] = header_hash.word32s[i]; +// // 2nd fill with nonce (2 words) +// state[8] = (uint32_t)nonce; +// state[9] = (uint32_t)(nonce >> 32); +// // 3rd all remaining elements to zero +// for (int i = 10; i < 25; i++) +// state[i] = 0; +// +// keccak_progpow_64(state); +// +// for (int i = 0; i < 8; i++) +// state2[i] = state[i]; +// } +// +// uint32_t state[25]; // Keccak's state +// for (int i = 0; i < 8; i++) +// state[i] = state2[i]; +// +// // Absorb phase for last round of keccak (256 bits) +// // 1st initial 8 words of state are kept as carry-over from initial keccak +// // 2nd subsequent 8 words are carried from digest/mix +// for (int i = 8; i < 16; i++) +// state[i] = mix_hash.word32s[i-8]; +// +// // 3rd all other elements to zero +// for (int i = 16; i < 25; i++) +// state[i] = 0; +// +// // Run keccak loop +// keccak_progpow_256(state); +// +// hash256 output; +// for (int i = 0; i < 8; ++i) +// output.word32s[i] = le::uint32(state[i]); +// if (!is_less_or_equal(output, boundary)) +// return false; +// +// if (!is_less_or_equal(output, boundary)) +// return false; +// +// memcpy(str_final,&to_hex(output)[0],64); +// return true; +//} + +search_result search_light(const epoch_context& context, int block_number, + const hash256& header_hash, const hash256& boundary, uint64_t start_nonce, + size_t iterations) noexcept +{ + const uint64_t end_nonce = start_nonce + iterations; + for (uint64_t nonce = start_nonce; nonce < end_nonce; ++nonce) + { + result r = hash(context, block_number, header_hash, nonce); + if (is_less_or_equal(r.final_hash, boundary)) + return {r, nonce}; + } + return {}; +} + +search_result search(const epoch_context_full& context, int block_number, + const hash256& header_hash, const hash256& boundary, uint64_t start_nonce, + size_t iterations) noexcept +{ + const uint64_t end_nonce = start_nonce + iterations; + for (uint64_t nonce = start_nonce; nonce < end_nonce; ++nonce) + { + result r = hash(context, block_number, header_hash, nonce); + if (is_less_or_equal(r.final_hash, boundary)) + return {r, nonce}; + } + return {}; +} + +} // namespace progpow diff --git a/src/Native/libkawpow/ethash/progpow.hpp b/src/Native/libkawpow/ethash/progpow.hpp new file mode 100644 index 000000000..f1de8222e --- /dev/null +++ b/src/Native/libkawpow/ethash/progpow.hpp @@ -0,0 +1,68 @@ +// ethash: C/C++ implementation of Ethash, the Ethereum Proof of Work algorithm. +// Copyright 2018-2019 Pawel Bylica. +// Licensed under the Apache License, Version 2.0. + +/// @file +/// +/// ProgPoW API +/// +/// This file provides the public API for ProgPoW as the Ethash API extension. + +#include "ethash.hpp" + +#if defined(_MSC_VER) +// Microsoft +#define EXPORT __declspec(dllexport) +#define IMPORT __declspec(dllimport) +#elif defined(__GNUC__) +// GCC +#define EXPORT __attribute__((visibility("default"))) +#define IMPORT +#else +// do nothing and hope for the best? +#define EXPORT +#define IMPORT +#pragma warning Unknown dynamic link import / export semantics. +#endif + +namespace progpow +{ +using namespace ethash; // Include ethash namespace. + + +/// The ProgPoW algorithm revision implemented as specified in the spec +/// https://github.com/ifdefelse/ProgPOW#change-history. +constexpr auto revision = "0.9.4"; + +constexpr int period_length = 3; +constexpr uint32_t num_regs = 32; +constexpr size_t num_lanes = 16; +constexpr int num_cache_accesses = 11; +constexpr int num_math_operations = 18; +constexpr size_t l1_cache_size = 16 * 1024; +constexpr size_t l1_cache_num_items = l1_cache_size / sizeof(uint32_t); + +extern "C" EXPORT result hashext(const epoch_context& context, int block_number, const hash256& header_hash, + uint64_t nonce, const hash256& mix_hash, const hash256& boundary1, const hash256& boundary2, int* retcode) noexcept; + +result hash(const epoch_context& context, int block_number, const hash256& header_hash, + uint64_t nonce) noexcept; + +result hash(const epoch_context_full& context, int block_number, const hash256& header_hash, + uint64_t nonce) noexcept; + +extern "C" EXPORT bool verify(const epoch_context& context, int block_number, const hash256& header_hash, + const hash256& mix_hash, uint64_t nonce, const hash256& boundary) noexcept; + +//bool light_verify(const char* str_header_hash, +// const char* str_mix_hash, const char* str_nonce, const char* str_boundary, char* str_final) noexcept; + +search_result search_light(const epoch_context& context, int block_number, + const hash256& header_hash, const hash256& boundary, uint64_t start_nonce, + size_t iterations) noexcept; + +search_result search(const epoch_context_full& context, int block_number, + const hash256& header_hash, const hash256& boundary, uint64_t start_nonce, + size_t iterations) noexcept; + +} // namespace progpow diff --git a/src/Native/libkawpow/ethash/version.h b/src/Native/libkawpow/ethash/version.h new file mode 100644 index 000000000..f08900fc0 --- /dev/null +++ b/src/Native/libkawpow/ethash/version.h @@ -0,0 +1,18 @@ +/* ethash: C/C++ implementation of Ethash, the Ethereum Proof of Work algorithm. + * Copyright 2019 Pawel Bylica. + * Licensed under the Apache License, Version 2.0. + */ + +#pragma once + +/** The ethash library version. */ +#define ETHASH_VERSION "0.5.1-alpha.1" + +#ifdef __cplusplus +namespace ethash +{ +/// The ethash library version. +constexpr auto version = ETHASH_VERSION; + +} // namespace ethash +#endif diff --git a/src/Native/libkawpow/exports.cpp b/src/Native/libkawpow/exports.cpp new file mode 100644 index 000000000..2004b4f48 --- /dev/null +++ b/src/Native/libkawpow/exports.cpp @@ -0,0 +1,16 @@ +/* +Copyright 2017 Coin Foundry (coinfoundry.org) +Authors: Oliver Weichhold (oliver@weichhold.com) +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. +*/ diff --git a/src/Native/libkawpow/keccak/CMakeLists.txt b/src/Native/libkawpow/keccak/CMakeLists.txt new file mode 100644 index 000000000..60f960f35 --- /dev/null +++ b/src/Native/libkawpow/keccak/CMakeLists.txt @@ -0,0 +1,26 @@ +# ethash: C/C++ implementation of Ethash, the Ethereum Proof of Work algorithm. +# Copyright 2019 Pawel Bylica. +# Licensed under the Apache License, Version 2.0. + +include(CMakePackageConfigHelpers) +include(GNUInstallDirs) + +add_library( + keccak + ${include_dir}/ethash/keccak.h + ${include_dir}/ethash/keccak.hpp + keccak.c + keccakf800.c + keccakf1600.c +) +set_property(TARGET keccak PROPERTY POSITION_INDEPENDENT_CODE ON) + +target_include_directories(keccak PUBLIC $<BUILD_INTERFACE:${include_dir}>$<INSTALL_INTERFACE:include>) + +install( + TARGETS keccak + EXPORT ethashTargets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) diff --git a/src/Native/libkawpow/keccak/keccak.c b/src/Native/libkawpow/keccak/keccak.c new file mode 100644 index 000000000..3dfafdf2f --- /dev/null +++ b/src/Native/libkawpow/keccak/keccak.c @@ -0,0 +1,123 @@ +/* ethash: C/C++ implementation of Ethash, the Ethereum Proof of Work algorithm. + * Copyright 2018-2019 Pawel Bylica. + * Licensed under the Apache License, Version 2.0. + */ + +#include "../ethash/keccak.h" + +#include "../support/attributes.h" +#include <string.h> + +#if _WIN32 +/* On Windows assume little endian. */ +#define __LITTLE_ENDIAN 1234 +#define __BIG_ENDIAN 4321 +#define __BYTE_ORDER __LITTLE_ENDIAN +#elif __APPLE__ +#include <machine/endian.h> +#else +#include <endian.h> +#endif + +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define to_le64(X) X +#else +#define to_le64(X) __builtin_bswap64(X) +#endif + + +/** Loads 64-bit integer from given memory location as little-endian number. */ +static INLINE ALWAYS_INLINE uint64_t load_le(const uint8_t* data) +{ + /* memcpy is the best way of expressing the intention. Every compiler will + optimize is to single load instruction if the target architecture + supports unaligned memory access (GCC and clang even in O0). + This is great trick because we are violating C/C++ memory alignment + restrictions with no performance penalty. */ + uint64_t word; + memcpy(&word, data, sizeof(word)); + return to_le64(word); +} + +static INLINE ALWAYS_INLINE void keccak( + uint64_t* out, size_t bits, const uint8_t* data, size_t size) +{ + static const size_t word_size = sizeof(uint64_t); + const size_t hash_size = bits / 8; + const size_t block_size = (1600 - bits * 2) / 8; + + size_t i; + uint64_t* state_iter; + uint64_t last_word = 0; + uint8_t* last_word_iter = (uint8_t*)&last_word; + + uint64_t state[25] = {0}; + + while (size >= block_size) + { + for (i = 0; i < (block_size / word_size); ++i) + { + state[i] ^= load_le(data); + data += word_size; + } + + ethash_keccakf1600(state); + + size -= block_size; + } + + state_iter = state; + + while (size >= word_size) + { + *state_iter ^= load_le(data); + ++state_iter; + data += word_size; + size -= word_size; + } + + while (size > 0) + { + *last_word_iter = *data; + ++last_word_iter; + ++data; + --size; + } + *last_word_iter = 0x01; + *state_iter ^= to_le64(last_word); + + state[(block_size / word_size) - 1] ^= 0x8000000000000000; + + ethash_keccakf1600(state); + + for (i = 0; i < (hash_size / word_size); ++i) + out[i] = to_le64(state[i]); +} + +union ethash_hash256 ethash_keccak256(const uint8_t* data, size_t size) +{ + union ethash_hash256 hash; + keccak(hash.word64s, 256, data, size); + return hash; +} + +union ethash_hash256 ethash_keccak256_32(const uint8_t data[32]) +{ + union ethash_hash256 hash; + keccak(hash.word64s, 256, data, 32); + return hash; +} + +union ethash_hash512 ethash_keccak512(const uint8_t* data, size_t size) +{ + union ethash_hash512 hash; + keccak(hash.word64s, 512, data, size); + return hash; +} + +union ethash_hash512 ethash_keccak512_64(const uint8_t data[64]) +{ + union ethash_hash512 hash; + keccak(hash.word64s, 512, data, 64); + return hash; +} diff --git a/src/Native/libkawpow/keccak/keccakf1600.c b/src/Native/libkawpow/keccak/keccakf1600.c new file mode 100644 index 000000000..e12b268d1 --- /dev/null +++ b/src/Native/libkawpow/keccak/keccakf1600.c @@ -0,0 +1,255 @@ +/* ethash: C/C++ implementation of Ethash, the Ethereum Proof of Work algorithm. + * Copyright 2018-2019 Pawel Bylica. + * Licensed under the Apache License, Version 2.0. + */ + +#include <stdint.h> + +static uint64_t rol(uint64_t x, unsigned s) +{ + return (x << s) | (x >> (64 - s)); +} + +static const uint64_t round_constants[24] = { + 0x0000000000000001, + 0x0000000000008082, + 0x800000000000808a, + 0x8000000080008000, + 0x000000000000808b, + 0x0000000080000001, + 0x8000000080008081, + 0x8000000000008009, + 0x000000000000008a, + 0x0000000000000088, + 0x0000000080008009, + 0x000000008000000a, + 0x000000008000808b, + 0x800000000000008b, + 0x8000000000008089, + 0x8000000000008003, + 0x8000000000008002, + 0x8000000000000080, + 0x000000000000800a, + 0x800000008000000a, + 0x8000000080008081, + 0x8000000000008080, + 0x0000000080000001, + 0x8000000080008008, +}; + +void ethash_keccakf1600(uint64_t state[25]) +{ + /* The implementation based on the "simple" implementation by Ronny Van Keer. */ + + int round; + + uint64_t Aba, Abe, Abi, Abo, Abu; + uint64_t Aga, Age, Agi, Ago, Agu; + uint64_t Aka, Ake, Aki, Ako, Aku; + uint64_t Ama, Ame, Ami, Amo, Amu; + uint64_t Asa, Ase, Asi, Aso, Asu; + + uint64_t Eba, Ebe, Ebi, Ebo, Ebu; + uint64_t Ega, Ege, Egi, Ego, Egu; + uint64_t Eka, Eke, Eki, Eko, Eku; + uint64_t Ema, Eme, Emi, Emo, Emu; + uint64_t Esa, Ese, Esi, Eso, Esu; + + uint64_t Ba, Be, Bi, Bo, Bu; + + uint64_t Da, De, Di, Do, Du; + + Aba = state[0]; + Abe = state[1]; + Abi = state[2]; + Abo = state[3]; + Abu = state[4]; + Aga = state[5]; + Age = state[6]; + Agi = state[7]; + Ago = state[8]; + Agu = state[9]; + Aka = state[10]; + Ake = state[11]; + Aki = state[12]; + Ako = state[13]; + Aku = state[14]; + Ama = state[15]; + Ame = state[16]; + Ami = state[17]; + Amo = state[18]; + Amu = state[19]; + Asa = state[20]; + Ase = state[21]; + Asi = state[22]; + Aso = state[23]; + Asu = state[24]; + + for (round = 0; round < 24; round += 2) + { + /* Round (round + 0): Axx -> Exx */ + + Ba = Aba ^ Aga ^ Aka ^ Ama ^ Asa; + Be = Abe ^ Age ^ Ake ^ Ame ^ Ase; + Bi = Abi ^ Agi ^ Aki ^ Ami ^ Asi; + Bo = Abo ^ Ago ^ Ako ^ Amo ^ Aso; + Bu = Abu ^ Agu ^ Aku ^ Amu ^ Asu; + + Da = Bu ^ rol(Be, 1); + De = Ba ^ rol(Bi, 1); + Di = Be ^ rol(Bo, 1); + Do = Bi ^ rol(Bu, 1); + Du = Bo ^ rol(Ba, 1); + + Ba = Aba ^ Da; + Be = rol(Age ^ De, 44); + Bi = rol(Aki ^ Di, 43); + Bo = rol(Amo ^ Do, 21); + Bu = rol(Asu ^ Du, 14); + Eba = Ba ^ (~Be & Bi) ^ round_constants[round]; + Ebe = Be ^ (~Bi & Bo); + Ebi = Bi ^ (~Bo & Bu); + Ebo = Bo ^ (~Bu & Ba); + Ebu = Bu ^ (~Ba & Be); + + Ba = rol(Abo ^ Do, 28); + Be = rol(Agu ^ Du, 20); + Bi = rol(Aka ^ Da, 3); + Bo = rol(Ame ^ De, 45); + Bu = rol(Asi ^ Di, 61); + Ega = Ba ^ (~Be & Bi); + Ege = Be ^ (~Bi & Bo); + Egi = Bi ^ (~Bo & Bu); + Ego = Bo ^ (~Bu & Ba); + Egu = Bu ^ (~Ba & Be); + + Ba = rol(Abe ^ De, 1); + Be = rol(Agi ^ Di, 6); + Bi = rol(Ako ^ Do, 25); + Bo = rol(Amu ^ Du, 8); + Bu = rol(Asa ^ Da, 18); + Eka = Ba ^ (~Be & Bi); + Eke = Be ^ (~Bi & Bo); + Eki = Bi ^ (~Bo & Bu); + Eko = Bo ^ (~Bu & Ba); + Eku = Bu ^ (~Ba & Be); + + Ba = rol(Abu ^ Du, 27); + Be = rol(Aga ^ Da, 36); + Bi = rol(Ake ^ De, 10); + Bo = rol(Ami ^ Di, 15); + Bu = rol(Aso ^ Do, 56); + Ema = Ba ^ (~Be & Bi); + Eme = Be ^ (~Bi & Bo); + Emi = Bi ^ (~Bo & Bu); + Emo = Bo ^ (~Bu & Ba); + Emu = Bu ^ (~Ba & Be); + + Ba = rol(Abi ^ Di, 62); + Be = rol(Ago ^ Do, 55); + Bi = rol(Aku ^ Du, 39); + Bo = rol(Ama ^ Da, 41); + Bu = rol(Ase ^ De, 2); + Esa = Ba ^ (~Be & Bi); + Ese = Be ^ (~Bi & Bo); + Esi = Bi ^ (~Bo & Bu); + Eso = Bo ^ (~Bu & Ba); + Esu = Bu ^ (~Ba & Be); + + + /* Round (round + 1): Exx -> Axx */ + + Ba = Eba ^ Ega ^ Eka ^ Ema ^ Esa; + Be = Ebe ^ Ege ^ Eke ^ Eme ^ Ese; + Bi = Ebi ^ Egi ^ Eki ^ Emi ^ Esi; + Bo = Ebo ^ Ego ^ Eko ^ Emo ^ Eso; + Bu = Ebu ^ Egu ^ Eku ^ Emu ^ Esu; + + Da = Bu ^ rol(Be, 1); + De = Ba ^ rol(Bi, 1); + Di = Be ^ rol(Bo, 1); + Do = Bi ^ rol(Bu, 1); + Du = Bo ^ rol(Ba, 1); + + Ba = Eba ^ Da; + Be = rol(Ege ^ De, 44); + Bi = rol(Eki ^ Di, 43); + Bo = rol(Emo ^ Do, 21); + Bu = rol(Esu ^ Du, 14); + Aba = Ba ^ (~Be & Bi) ^ round_constants[round + 1]; + Abe = Be ^ (~Bi & Bo); + Abi = Bi ^ (~Bo & Bu); + Abo = Bo ^ (~Bu & Ba); + Abu = Bu ^ (~Ba & Be); + + Ba = rol(Ebo ^ Do, 28); + Be = rol(Egu ^ Du, 20); + Bi = rol(Eka ^ Da, 3); + Bo = rol(Eme ^ De, 45); + Bu = rol(Esi ^ Di, 61); + Aga = Ba ^ (~Be & Bi); + Age = Be ^ (~Bi & Bo); + Agi = Bi ^ (~Bo & Bu); + Ago = Bo ^ (~Bu & Ba); + Agu = Bu ^ (~Ba & Be); + + Ba = rol(Ebe ^ De, 1); + Be = rol(Egi ^ Di, 6); + Bi = rol(Eko ^ Do, 25); + Bo = rol(Emu ^ Du, 8); + Bu = rol(Esa ^ Da, 18); + Aka = Ba ^ (~Be & Bi); + Ake = Be ^ (~Bi & Bo); + Aki = Bi ^ (~Bo & Bu); + Ako = Bo ^ (~Bu & Ba); + Aku = Bu ^ (~Ba & Be); + + Ba = rol(Ebu ^ Du, 27); + Be = rol(Ega ^ Da, 36); + Bi = rol(Eke ^ De, 10); + Bo = rol(Emi ^ Di, 15); + Bu = rol(Eso ^ Do, 56); + Ama = Ba ^ (~Be & Bi); + Ame = Be ^ (~Bi & Bo); + Ami = Bi ^ (~Bo & Bu); + Amo = Bo ^ (~Bu & Ba); + Amu = Bu ^ (~Ba & Be); + + Ba = rol(Ebi ^ Di, 62); + Be = rol(Ego ^ Do, 55); + Bi = rol(Eku ^ Du, 39); + Bo = rol(Ema ^ Da, 41); + Bu = rol(Ese ^ De, 2); + Asa = Ba ^ (~Be & Bi); + Ase = Be ^ (~Bi & Bo); + Asi = Bi ^ (~Bo & Bu); + Aso = Bo ^ (~Bu & Ba); + Asu = Bu ^ (~Ba & Be); + } + + state[0] = Aba; + state[1] = Abe; + state[2] = Abi; + state[3] = Abo; + state[4] = Abu; + state[5] = Aga; + state[6] = Age; + state[7] = Agi; + state[8] = Ago; + state[9] = Agu; + state[10] = Aka; + state[11] = Ake; + state[12] = Aki; + state[13] = Ako; + state[14] = Aku; + state[15] = Ama; + state[16] = Ame; + state[17] = Ami; + state[18] = Amo; + state[19] = Amu; + state[20] = Asa; + state[21] = Ase; + state[22] = Asi; + state[23] = Aso; + state[24] = Asu; +} diff --git a/src/Native/libkawpow/keccak/keccakf800.c b/src/Native/libkawpow/keccak/keccakf800.c new file mode 100644 index 000000000..5b9a18025 --- /dev/null +++ b/src/Native/libkawpow/keccak/keccakf800.c @@ -0,0 +1,253 @@ +/* ethash: C/C++ implementation of Ethash, the Ethereum Proof of Work algorithm. + * Copyright 2018-2019 Pawel Bylica. + * Licensed under the Apache License, Version 2.0. + */ + +#include <stdint.h> + +static uint32_t rol(uint32_t x, unsigned s) +{ + return (x << s) | (x >> (32 - s)); +} + +static const uint32_t round_constants[22] = { + 0x00000001, + 0x00008082, + 0x0000808A, + 0x80008000, + 0x0000808B, + 0x80000001, + 0x80008081, + 0x00008009, + 0x0000008A, + 0x00000088, + 0x80008009, + 0x8000000A, + 0x8000808B, + 0x0000008B, + 0x00008089, + 0x00008003, + 0x00008002, + 0x00000080, + 0x0000800A, + 0x8000000A, + 0x80008081, + 0x00008080, +}; + +void ethash_keccakf800(uint32_t state[25]) +{ + /* The implementation directly translated from ethash_keccakf1600. */ + + int round; + + uint32_t Aba, Abe, Abi, Abo, Abu; + uint32_t Aga, Age, Agi, Ago, Agu; + uint32_t Aka, Ake, Aki, Ako, Aku; + uint32_t Ama, Ame, Ami, Amo, Amu; + uint32_t Asa, Ase, Asi, Aso, Asu; + + uint32_t Eba, Ebe, Ebi, Ebo, Ebu; + uint32_t Ega, Ege, Egi, Ego, Egu; + uint32_t Eka, Eke, Eki, Eko, Eku; + uint32_t Ema, Eme, Emi, Emo, Emu; + uint32_t Esa, Ese, Esi, Eso, Esu; + + uint32_t Ba, Be, Bi, Bo, Bu; + + uint32_t Da, De, Di, Do, Du; + + Aba = state[0]; + Abe = state[1]; + Abi = state[2]; + Abo = state[3]; + Abu = state[4]; + Aga = state[5]; + Age = state[6]; + Agi = state[7]; + Ago = state[8]; + Agu = state[9]; + Aka = state[10]; + Ake = state[11]; + Aki = state[12]; + Ako = state[13]; + Aku = state[14]; + Ama = state[15]; + Ame = state[16]; + Ami = state[17]; + Amo = state[18]; + Amu = state[19]; + Asa = state[20]; + Ase = state[21]; + Asi = state[22]; + Aso = state[23]; + Asu = state[24]; + + for (round = 0; round < 22; round += 2) + { + /* Round (round + 0): Axx -> Exx */ + + Ba = Aba ^ Aga ^ Aka ^ Ama ^ Asa; + Be = Abe ^ Age ^ Ake ^ Ame ^ Ase; + Bi = Abi ^ Agi ^ Aki ^ Ami ^ Asi; + Bo = Abo ^ Ago ^ Ako ^ Amo ^ Aso; + Bu = Abu ^ Agu ^ Aku ^ Amu ^ Asu; + + Da = Bu ^ rol(Be, 1); + De = Ba ^ rol(Bi, 1); + Di = Be ^ rol(Bo, 1); + Do = Bi ^ rol(Bu, 1); + Du = Bo ^ rol(Ba, 1); + + Ba = Aba ^ Da; + Be = rol(Age ^ De, 12); + Bi = rol(Aki ^ Di, 11); + Bo = rol(Amo ^ Do, 21); + Bu = rol(Asu ^ Du, 14); + Eba = Ba ^ (~Be & Bi) ^ round_constants[round]; + Ebe = Be ^ (~Bi & Bo); + Ebi = Bi ^ (~Bo & Bu); + Ebo = Bo ^ (~Bu & Ba); + Ebu = Bu ^ (~Ba & Be); + + Ba = rol(Abo ^ Do, 28); + Be = rol(Agu ^ Du, 20); + Bi = rol(Aka ^ Da, 3); + Bo = rol(Ame ^ De, 13); + Bu = rol(Asi ^ Di, 29); + Ega = Ba ^ (~Be & Bi); + Ege = Be ^ (~Bi & Bo); + Egi = Bi ^ (~Bo & Bu); + Ego = Bo ^ (~Bu & Ba); + Egu = Bu ^ (~Ba & Be); + + Ba = rol(Abe ^ De, 1); + Be = rol(Agi ^ Di, 6); + Bi = rol(Ako ^ Do, 25); + Bo = rol(Amu ^ Du, 8); + Bu = rol(Asa ^ Da, 18); + Eka = Ba ^ (~Be & Bi); + Eke = Be ^ (~Bi & Bo); + Eki = Bi ^ (~Bo & Bu); + Eko = Bo ^ (~Bu & Ba); + Eku = Bu ^ (~Ba & Be); + + Ba = rol(Abu ^ Du, 27); + Be = rol(Aga ^ Da, 4); + Bi = rol(Ake ^ De, 10); + Bo = rol(Ami ^ Di, 15); + Bu = rol(Aso ^ Do, 24); + Ema = Ba ^ (~Be & Bi); + Eme = Be ^ (~Bi & Bo); + Emi = Bi ^ (~Bo & Bu); + Emo = Bo ^ (~Bu & Ba); + Emu = Bu ^ (~Ba & Be); + + Ba = rol(Abi ^ Di, 30); + Be = rol(Ago ^ Do, 23); + Bi = rol(Aku ^ Du, 7); + Bo = rol(Ama ^ Da, 9); + Bu = rol(Ase ^ De, 2); + Esa = Ba ^ (~Be & Bi); + Ese = Be ^ (~Bi & Bo); + Esi = Bi ^ (~Bo & Bu); + Eso = Bo ^ (~Bu & Ba); + Esu = Bu ^ (~Ba & Be); + + + /* Round (round + 1): Exx -> Axx */ + + Ba = Eba ^ Ega ^ Eka ^ Ema ^ Esa; + Be = Ebe ^ Ege ^ Eke ^ Eme ^ Ese; + Bi = Ebi ^ Egi ^ Eki ^ Emi ^ Esi; + Bo = Ebo ^ Ego ^ Eko ^ Emo ^ Eso; + Bu = Ebu ^ Egu ^ Eku ^ Emu ^ Esu; + + Da = Bu ^ rol(Be, 1); + De = Ba ^ rol(Bi, 1); + Di = Be ^ rol(Bo, 1); + Do = Bi ^ rol(Bu, 1); + Du = Bo ^ rol(Ba, 1); + + Ba = Eba ^ Da; + Be = rol(Ege ^ De, 12); + Bi = rol(Eki ^ Di, 11); + Bo = rol(Emo ^ Do, 21); + Bu = rol(Esu ^ Du, 14); + Aba = Ba ^ (~Be & Bi) ^ round_constants[round + 1]; + Abe = Be ^ (~Bi & Bo); + Abi = Bi ^ (~Bo & Bu); + Abo = Bo ^ (~Bu & Ba); + Abu = Bu ^ (~Ba & Be); + + Ba = rol(Ebo ^ Do, 28); + Be = rol(Egu ^ Du, 20); + Bi = rol(Eka ^ Da, 3); + Bo = rol(Eme ^ De, 13); + Bu = rol(Esi ^ Di, 29); + Aga = Ba ^ (~Be & Bi); + Age = Be ^ (~Bi & Bo); + Agi = Bi ^ (~Bo & Bu); + Ago = Bo ^ (~Bu & Ba); + Agu = Bu ^ (~Ba & Be); + + Ba = rol(Ebe ^ De, 1); + Be = rol(Egi ^ Di, 6); + Bi = rol(Eko ^ Do, 25); + Bo = rol(Emu ^ Du, 8); + Bu = rol(Esa ^ Da, 18); + Aka = Ba ^ (~Be & Bi); + Ake = Be ^ (~Bi & Bo); + Aki = Bi ^ (~Bo & Bu); + Ako = Bo ^ (~Bu & Ba); + Aku = Bu ^ (~Ba & Be); + + Ba = rol(Ebu ^ Du, 27); + Be = rol(Ega ^ Da, 4); + Bi = rol(Eke ^ De, 10); + Bo = rol(Emi ^ Di, 15); + Bu = rol(Eso ^ Do, 24); + Ama = Ba ^ (~Be & Bi); + Ame = Be ^ (~Bi & Bo); + Ami = Bi ^ (~Bo & Bu); + Amo = Bo ^ (~Bu & Ba); + Amu = Bu ^ (~Ba & Be); + + Ba = rol(Ebi ^ Di, 30); + Be = rol(Ego ^ Do, 23); + Bi = rol(Eku ^ Du, 7); + Bo = rol(Ema ^ Da, 9); + Bu = rol(Ese ^ De, 2); + Asa = Ba ^ (~Be & Bi); + Ase = Be ^ (~Bi & Bo); + Asi = Bi ^ (~Bo & Bu); + Aso = Bo ^ (~Bu & Ba); + Asu = Bu ^ (~Ba & Be); + } + + state[0] = Aba; + state[1] = Abe; + state[2] = Abi; + state[3] = Abo; + state[4] = Abu; + state[5] = Aga; + state[6] = Age; + state[7] = Agi; + state[8] = Ago; + state[9] = Agu; + state[10] = Aka; + state[11] = Ake; + state[12] = Aki; + state[13] = Ako; + state[14] = Aku; + state[15] = Ama; + state[16] = Ame; + state[17] = Ami; + state[18] = Amo; + state[19] = Amu; + state[20] = Asa; + state[21] = Ase; + state[22] = Asi; + state[23] = Aso; + state[24] = Asu; +} diff --git a/src/Native/libkawpow/libkawpow.sln b/src/Native/libkawpow/libkawpow.sln new file mode 100644 index 000000000..1d1e6f955 --- /dev/null +++ b/src/Native/libkawpow/libkawpow.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31229.75 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libethhash", "liblibkawpow.vcxproj", "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}.Debug|x64.ActiveCfg = Debug|x64 + {8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}.Debug|x64.Build.0 = Debug|x64 + {8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}.Debug|x86.ActiveCfg = Debug|Win32 + {8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}.Debug|x86.Build.0 = Debug|Win32 + {8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}.Release|x64.ActiveCfg = Release|x64 + {8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}.Release|x64.Build.0 = Release|x64 + {8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}.Release|x86.ActiveCfg = Release|Win32 + {8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {DDE0FE54-030A-4DFD-98A1-952779FB461F} + EndGlobalSection +EndGlobal diff --git a/src/Native/libkawpow/liblibkawpow.vcxproj b/src/Native/libkawpow/liblibkawpow.vcxproj new file mode 100644 index 000000000..d8726098f --- /dev/null +++ b/src/Native/libkawpow/liblibkawpow.vcxproj @@ -0,0 +1,206 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|x64"> + <Configuration>Debug</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|x64"> + <Configuration>Release</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Globals"> + <VCProjectVersion>15.0</VCProjectVersion> + <ProjectGuid>{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}</ProjectGuid> + <Keyword>Win32Proj</Keyword> + <RootNamespace>netmultihashnative</RootNamespace> + <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion> + <ProjectName>libkawpow</ProjectName> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <UseDebugLibraries>true</UseDebugLibraries> + <PlatformToolset>v142</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <PlatformToolset>v142</PlatformToolset> + <WholeProgramOptimization>true</WholeProgramOptimization> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <UseDebugLibraries>true</UseDebugLibraries> + <PlatformToolset>v142</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <PlatformToolset>v142</PlatformToolset> + <WholeProgramOptimization>true</WholeProgramOptimization> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <ImportGroup Label="ExtensionSettings"> + </ImportGroup> + <ImportGroup Label="Shared"> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <LinkIncremental>true</LinkIncremental> + <IncludePath>$(SolutionDir)\..\..\..\..\boost_1_62_0;$(ProjectDir)windows\include\libsodium;$(IncludePath);$(ProjectDir)</IncludePath> + <LibraryPath>$(SolutionDir)\..\..\..\..\boost_1_62_0\lib32-msvc-14.0;$(LibraryPath)</LibraryPath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <LinkIncremental>true</LinkIncremental> + <IncludePath>$(SolutionDir)\..\..\..\..\boost_1_62_0;$(ProjectDir)windows\include\libsodium;$(IncludePath);$(ProjectDir)</IncludePath> + <LibraryPath>$(SolutionDir)\..\..\..\..\boost_1_62_0\lib64-msvc-14.0;$(ProjectDir)windows\lib\x64;$(LibraryPath)</LibraryPath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <LinkIncremental>false</LinkIncremental> + <IncludePath>$(SolutionDir)\..\..\..\..\boost_1_62_0;$(ProjectDir)windows\include\libsodium;$(IncludePath);$(ProjectDir)</IncludePath> + <LibraryPath>$(SolutionDir)\..\..\..\..\boost_1_62_0\lib32-msvc-14.0;$(LibraryPath)</LibraryPath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <LinkIncremental>false</LinkIncremental> + <IncludePath>$(SolutionDir)\..\..\..\..\boost_1_62_0;$(ProjectDir)windows\include\libsodium;$(ProjectDir)windows\include\libsodium;$(IncludePath);$(ProjectDir)</IncludePath> + <LibraryPath>$(SolutionDir)\..\..\..\..\boost_1_62_0\lib64-msvc-14.0;$(LibraryPath)</LibraryPath> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ClCompile> + <PrecompiledHeader> + </PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <Optimization>Disabled</Optimization> + <PreprocessorDefinitions>SODIUM_STATIC;_CRT_SECURE_NO_WARNINGS</PreprocessorDefinitions> + <SDLCheck>true</SDLCheck> + <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary> + <LanguageStandard>stdcpp14</LanguageStandard> + </ClCompile> + <Link> + <SubSystem>Windows</SubSystem> + <GenerateDebugInformation>true</GenerateDebugInformation> + <AdditionalDependencies>Ws2_32.lib;$(ProjectDir)windows\lib\x86\libsodium.lib;%(AdditionalDependencies)</AdditionalDependencies> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <ClCompile> + <PrecompiledHeader> + </PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <Optimization>Disabled</Optimization> + <PreprocessorDefinitions>SODIUM_STATIC;_CRT_SECURE_NO_WARNINGS</PreprocessorDefinitions> + <SDLCheck>true</SDLCheck> + <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary> + <LanguageStandard>stdcpp14</LanguageStandard> + </ClCompile> + <Link> + <SubSystem>Windows</SubSystem> + <GenerateDebugInformation>true</GenerateDebugInformation> + <AdditionalDependencies>Ws2_32.lib;$(ProjectDir)windows\lib\x64\libsodium.lib;%(AdditionalDependencies)</AdditionalDependencies> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ClCompile> + <PrecompiledHeader> + </PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <Optimization>MaxSpeed</Optimization> + <FunctionLevelLinking>true</FunctionLevelLinking> + <IntrinsicFunctions>true</IntrinsicFunctions> + <PreprocessorDefinitions>SODIUM_STATIC;_CRT_SECURE_NO_WARNINGS</PreprocessorDefinitions> + <SDLCheck>true</SDLCheck> + <RuntimeLibrary>MultiThreaded</RuntimeLibrary> + <LanguageStandard>stdcpp14</LanguageStandard> + </ClCompile> + <Link> + <SubSystem>Windows</SubSystem> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <OptimizeReferences>true</OptimizeReferences> + <GenerateDebugInformation>true</GenerateDebugInformation> + <AdditionalDependencies>Ws2_32.lib;$(ProjectDir)windows\lib\x86\libsodium.lib;%(AdditionalDependencies)</AdditionalDependencies> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <ClCompile> + <PrecompiledHeader> + </PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <Optimization>MaxSpeed</Optimization> + <FunctionLevelLinking>true</FunctionLevelLinking> + <IntrinsicFunctions>true</IntrinsicFunctions> + <PreprocessorDefinitions>SODIUM_STATIC;_CRT_SECURE_NO_WARNINGS</PreprocessorDefinitions> + <SDLCheck>true</SDLCheck> + <RuntimeLibrary>MultiThreaded</RuntimeLibrary> + <LanguageStandard>stdcpp14</LanguageStandard> + </ClCompile> + <Link> + <SubSystem>Windows</SubSystem> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <OptimizeReferences>true</OptimizeReferences> + <GenerateDebugInformation>true</GenerateDebugInformation> + <AdditionalDependencies>Ws2_32.lib;$(ProjectDir)windows\lib\x64\libsodium.lib;%(AdditionalDependencies)</AdditionalDependencies> + </Link> + </ItemDefinitionGroup> + <ItemGroup> + <ClInclude Include="ethash\bit_manipulation.h" /> + <ClInclude Include="ethash\builtins.h" /> + <ClInclude Include="ethash\endianness.hpp" /> + <ClInclude Include="ethash\ethash-internal.hpp" /> + <ClInclude Include="ethash\ethash.h" /> + <ClInclude Include="ethash\ethash.hpp" /> + <ClInclude Include="ethash\hash_types.h" /> + <ClInclude Include="ethash\hash_types.hpp" /> + <ClInclude Include="ethash\keccak.h" /> + <ClInclude Include="ethash\keccak.hpp" /> + <ClInclude Include="ethash\kiss99.hpp" /> + <ClInclude Include="ethash\primes.h" /> + <ClInclude Include="ethash\progpow.hpp" /> + <ClInclude Include="ethash\version.h" /> + <ClInclude Include="stdafx.h" /> + <ClInclude Include="stdint.h" /> + <ClInclude Include="support\attributes.h" /> + </ItemGroup> + <ItemGroup> + <ClCompile Include="dllmain.cpp" /> + <ClCompile Include="ethash\ethash.cpp" /> + <ClCompile Include="ethash\managed.cpp" /> + <ClCompile Include="ethash\primes.c" /> + <ClCompile Include="ethash\progpow.cpp" /> + <ClCompile Include="keccak\keccak.c" /> + <ClCompile Include="keccak\keccakf1600.c" /> + <ClCompile Include="keccak\keccakf800.c" /> + <ClCompile Include="stdafx.cpp" /> + </ItemGroup> + <ItemGroup> + <None Include="Makefile" /> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project> \ No newline at end of file diff --git a/src/Native/libkawpow/stdafx.cpp b/src/Native/libkawpow/stdafx.cpp new file mode 100644 index 000000000..bd27597c6 --- /dev/null +++ b/src/Native/libkawpow/stdafx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// $safeprojectname$.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/src/Native/libkawpow/stdafx.h b/src/Native/libkawpow/stdafx.h new file mode 100644 index 000000000..f3a07375c --- /dev/null +++ b/src/Native/libkawpow/stdafx.h @@ -0,0 +1,16 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#pragma once + +#include "targetver.h" + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files: +#include <windows.h> + + + +// TODO: reference additional headers your program requires here diff --git a/src/Native/libkawpow/support/attributes.h b/src/Native/libkawpow/support/attributes.h new file mode 100644 index 000000000..83be231f0 --- /dev/null +++ b/src/Native/libkawpow/support/attributes.h @@ -0,0 +1,33 @@ +/* ethash: C/C++ implementation of Ethash, the Ethereum Proof of Work algorithm. + * Copyright 2018-2019 Pawel Bylica. + * Licensed under the Apache License, Version 2.0. + */ + +#pragma once + +/** inline */ +#if _MSC_VER || __STDC_VERSION__ +#define INLINE inline +#else +#define INLINE +#endif + +/** [[always_inline]] */ +#if _MSC_VER +#define ALWAYS_INLINE __forceinline +#elif defined(__has_attribute) && __STDC_VERSION__ +#if __has_attribute(always_inline) +#define ALWAYS_INLINE __attribute__((always_inline)) +#endif +#endif +#if !defined(ALWAYS_INLINE) +#define ALWAYS_INLINE +#endif + +/** [[no_sanitize()]] */ +#if __clang__ +#define NO_SANITIZE(sanitizer) \ + __attribute__((no_sanitize(sanitizer))) +#else +#define NO_SANITIZE(sanitizer) +#endif diff --git a/src/Native/libkawpow/targetver.h b/src/Native/libkawpow/targetver.h new file mode 100644 index 000000000..87c0086de --- /dev/null +++ b/src/Native/libkawpow/targetver.h @@ -0,0 +1,8 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include <SDKDDKVer.h> From 3b11f4cf174acd1ca98a61e6c7350478f976cd8e Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Thu, 22 Jul 2021 16:51:12 +0200 Subject: [PATCH 127/145] LibKawpow --- libs/runtimes/win-x64/libkawpow.dll | Bin 0 -> 132096 bytes src/Miningcore/Native/LibKawpow.cs | 36 ++++++++++++++++++++++ src/Native/libkawpow/liblibkawpow.vcxproj | 8 ++--- 3 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 libs/runtimes/win-x64/libkawpow.dll create mode 100644 src/Miningcore/Native/LibKawpow.cs diff --git a/libs/runtimes/win-x64/libkawpow.dll b/libs/runtimes/win-x64/libkawpow.dll new file mode 100644 index 0000000000000000000000000000000000000000..31dc85d316da07f3c75a8ca478b613de0803169c GIT binary patch literal 132096 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%0AFg2=pddo0+>G&9hKA)s<)y$^%6m+G`D@rPr@=Qo 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 literal 0 HcmV?d00001 diff --git a/src/Miningcore/Native/LibKawpow.cs b/src/Miningcore/Native/LibKawpow.cs new file mode 100644 index 000000000..dba32c80b --- /dev/null +++ b/src/Miningcore/Native/LibKawpow.cs @@ -0,0 +1,36 @@ +using System; +using System.Runtime.InteropServices; + +// ReSharper disable FieldCanBeMadeReadOnly.Local +// ReSharper disable MemberCanBePrivate.Local +// ReSharper disable InconsistentNaming + +namespace Miningcore.Native +{ + public static unsafe class LibKawpow + { + [DllImport("libethash", EntryPoint = "ethash_create_epoch_context", CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr CreateContext(int epoch_number); + + [DllImport("libethash", EntryPoint = "ethash_destroy_epoch_context", CallingConvention = CallingConvention.Cdecl)] + private static extern void DestroyContext(IntPtr context); + + [DllImport("libethash", EntryPoint = "hashext", CallingConvention = CallingConvention.Cdecl)] + private static extern Ethash_result hashext(IntPtr context, int block_number, ref Ethash_hash256 header_hash, ulong nonce, ref Ethash_hash256 mix_hash, ref Ethash_hash256 boundary1, ref Ethash_hash256 boundary2, out int retcode); + + [StructLayout(LayoutKind.Explicit)] + private struct Ethash_hash256 + { + [FieldOffset(0)] + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] + public byte[] bytes;//x32 + } + + [StructLayout(LayoutKind.Sequential)] + private struct Ethash_result + { + public Ethash_hash256 final_hash;//32 + public Ethash_hash256 mix_hash;//32 + } + } +} diff --git a/src/Native/libkawpow/liblibkawpow.vcxproj b/src/Native/libkawpow/liblibkawpow.vcxproj index d8726098f..9d05a07b3 100644 --- a/src/Native/libkawpow/liblibkawpow.vcxproj +++ b/src/Native/libkawpow/liblibkawpow.vcxproj @@ -105,7 +105,7 @@ <Link> <SubSystem>Windows</SubSystem> <GenerateDebugInformation>true</GenerateDebugInformation> - <AdditionalDependencies>Ws2_32.lib;$(ProjectDir)windows\lib\x86\libsodium.lib;%(AdditionalDependencies)</AdditionalDependencies> + <AdditionalDependencies>Ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> @@ -122,7 +122,7 @@ <Link> <SubSystem>Windows</SubSystem> <GenerateDebugInformation>true</GenerateDebugInformation> - <AdditionalDependencies>Ws2_32.lib;$(ProjectDir)windows\lib\x64\libsodium.lib;%(AdditionalDependencies)</AdditionalDependencies> + <AdditionalDependencies>Ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> @@ -143,7 +143,7 @@ <EnableCOMDATFolding>true</EnableCOMDATFolding> <OptimizeReferences>true</OptimizeReferences> <GenerateDebugInformation>true</GenerateDebugInformation> - <AdditionalDependencies>Ws2_32.lib;$(ProjectDir)windows\lib\x86\libsodium.lib;%(AdditionalDependencies)</AdditionalDependencies> + <AdditionalDependencies>Ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> @@ -164,7 +164,7 @@ <EnableCOMDATFolding>true</EnableCOMDATFolding> <OptimizeReferences>true</OptimizeReferences> <GenerateDebugInformation>true</GenerateDebugInformation> - <AdditionalDependencies>Ws2_32.lib;$(ProjectDir)windows\lib\x64\libsodium.lib;%(AdditionalDependencies)</AdditionalDependencies> + <AdditionalDependencies>Ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup> <ItemGroup> From 15ef61c7709ec6bf0e2ed6fa610365eec6578cda Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Thu, 22 Jul 2021 20:57:17 +0200 Subject: [PATCH 128/145] IP pseudo-authenticated miner settings change API --- .../Api/Controllers/AdminApiController.cs | 1 + .../Api/Controllers/PoolApiController.cs | 78 +++++++++++++++++++ .../Requests/UpdateMinerSettingsRequest.cs | 10 +++ .../Extensions/IpAddressExtensions.cs | 24 ++++++ src/Miningcore/Miningcore.csproj | 3 - src/Miningcore/Payments/PayoutManager.cs | 1 + .../Postgres/Repositories/ShareRepository.cs | 12 +++ .../Repositories/IShareRepository.cs | 2 + 8 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 src/Miningcore/Api/Requests/UpdateMinerSettingsRequest.cs create mode 100644 src/Miningcore/Extensions/IpAddressExtensions.cs diff --git a/src/Miningcore/Api/Controllers/AdminApiController.cs b/src/Miningcore/Api/Controllers/AdminApiController.cs index 9d2afe10d..b69dad5c7 100644 --- a/src/Miningcore/Api/Controllers/AdminApiController.cs +++ b/src/Miningcore/Api/Controllers/AdminApiController.cs @@ -88,6 +88,7 @@ public async Task<decimal> GetMinerBalanceAsync(string poolId, string address) // map settings var mapped = mapper.Map<Persistence.Model.MinerSettings>(settings); + // clamp limit if(pool.PaymentProcessing != null) mapped.PaymentThreshold = Math.Max(mapped.PaymentThreshold, pool.PaymentProcessing.MinimumPayment); diff --git a/src/Miningcore/Api/Controllers/PoolApiController.cs b/src/Miningcore/Api/Controllers/PoolApiController.cs index 9a849f25a..85098c937 100644 --- a/src/Miningcore/Api/Controllers/PoolApiController.cs +++ b/src/Miningcore/Api/Controllers/PoolApiController.cs @@ -33,6 +33,8 @@ public PoolApiController(IComponentContext ctx, IActionDescriptorCollectionProvi { statsRepo = ctx.Resolve<IStatsRepository>(); blocksRepo = ctx.Resolve<IBlockRepository>(); + minerRepo = ctx.Resolve<IMinerRepository>(); + shareRepo = ctx.Resolve<IShareRepository>(); paymentsRepo = ctx.Resolve<IPaymentRepository>(); clock = ctx.Resolve<IMasterClock>(); pools = ctx.Resolve<ConcurrentDictionary<string, IMiningPool>>(); @@ -42,6 +44,8 @@ public PoolApiController(IComponentContext ctx, IActionDescriptorCollectionProvi private readonly IStatsRepository statsRepo; private readonly IBlockRepository blocksRepo; private readonly IPaymentRepository paymentsRepo; + private readonly IMinerRepository minerRepo; + private readonly IShareRepository shareRepo; private readonly IMasterClock clock; private readonly IActionDescriptorCollectionProvider adcp; private readonly ConcurrentDictionary<string, IMiningPool> pools; @@ -537,6 +541,80 @@ public async Task<PagedResultResponse<AmountByDate[]>> PageMinerEarningsByDayV2A return result; } + [HttpGet("{poolId}/miners/{address}/settings")] + public async Task<Responses.MinerSettings> GetMinerSettingsAsync(string poolId, string address) + { + var pool = GetPool(poolId); + + if(string.IsNullOrEmpty(address)) + throw new ApiException("Invalid or missing miner address", HttpStatusCode.NotFound); + + var result = await cf.Run(con=> minerRepo.GetSettings(con, null, pool.Id, address)); + + if(result == null) + throw new ApiException("No settings found", HttpStatusCode.NotFound); + + return mapper.Map<Responses.MinerSettings>(result); + } + + [HttpPost("{poolId}/miners/{address}/settings")] + public Task<Responses.MinerSettings> SetMinerSettingsAsync(string poolId, string address, + [FromBody] Requests.UpdateMinerSettingsRequest request) + { + var pool = GetPool(poolId); + + if(string.IsNullOrEmpty(address)) + throw new ApiException("Invalid or missing miner address", HttpStatusCode.NotFound); + + if(request?.Settings == null) + throw new ApiException("Invalid or missing settings", HttpStatusCode.BadRequest); + + if(!IPAddress.TryParse(request.IpAddress, out var requestIp)) + throw new ApiException("Invalid IP address", HttpStatusCode.BadRequest); + + return cf.RunTx(async (con, tx) => + { + // fetch recent IPs + var ips = await shareRepo.GetRecentyUsedIpAddresses(con, tx, poolId, address); + + // any known ips? + if(ips == null || ips.Length == 0) + throw new ApiException("No recent IP addresses found", HttpStatusCode.BadRequest); + + // check if at least one matches + var match = false; + + foreach(var ip in ips) + { + if(IPAddress.TryParse(ip, out var ipAddress)) + { + if(ipAddress.IsEqual(requestIp)) + { + match = true; + break; + } + } + } + + if(!match) + throw new ApiException("No recent IP addresses matches", HttpStatusCode.Forbidden); + + // map settings + var mapped = mapper.Map<Persistence.Model.MinerSettings>(request.Settings); + + // clamp limit + if(pool.PaymentProcessing != null) + mapped.PaymentThreshold = Math.Max(mapped.PaymentThreshold, pool.PaymentProcessing.MinimumPayment); + + mapped.PoolId = pool.Id; + mapped.Address = address; + + var result = await minerRepo.UpdateSettings(con, tx, mapped); + + return mapper.Map<Responses.MinerSettings>(result); + }); + } + #endregion // Actions private async Task<Responses.WorkerPerformanceStatsContainer[]> GetMinerPerformanceInternal( diff --git a/src/Miningcore/Api/Requests/UpdateMinerSettingsRequest.cs b/src/Miningcore/Api/Requests/UpdateMinerSettingsRequest.cs new file mode 100644 index 000000000..04ee3329b --- /dev/null +++ b/src/Miningcore/Api/Requests/UpdateMinerSettingsRequest.cs @@ -0,0 +1,10 @@ +using Miningcore.Api.Responses; + +namespace Miningcore.Api.Requests +{ + public class UpdateMinerSettingsRequest + { + public string IpAddress { get; set; } + public MinerSettings Settings { get; set; } + } +} diff --git a/src/Miningcore/Extensions/IpAddressExtensions.cs b/src/Miningcore/Extensions/IpAddressExtensions.cs new file mode 100644 index 000000000..f21f00cc1 --- /dev/null +++ b/src/Miningcore/Extensions/IpAddressExtensions.cs @@ -0,0 +1,24 @@ +using System.Net; + +namespace Miningcore.Extensions +{ + public static class IpAddressExtensions + { + public static bool IsEqual(this IPAddress address, IPAddress other) + { + if(address.Equals(other)) + return true; + + if(address.IsIPv4MappedToIPv6 && !other.IsIPv4MappedToIPv6 && address.MapToIPv4().Equals(other)) + return true; + + if(address.IsIPv4MappedToIPv6 && other.IsIPv4MappedToIPv6 && address.MapToIPv4().Equals(other.MapToIPv4())) + return true; + + if(!address.IsIPv4MappedToIPv6 && other.IsIPv4MappedToIPv6 && address.Equals(other.MapToIPv4())) + return true; + + return false; + } + } +} diff --git a/src/Miningcore/Miningcore.csproj b/src/Miningcore/Miningcore.csproj index be55c5ae3..89609a0f6 100644 --- a/src/Miningcore/Miningcore.csproj +++ b/src/Miningcore/Miningcore.csproj @@ -22,17 +22,14 @@ <ItemGroup> <Compile Remove="Api\Notifications\**" /> - <Compile Remove="Api\Requests\**" /> <Compile Remove="Blockchain\Bitcoin\Transactions\**" /> <Compile Remove="DataAccess\**" /> <Compile Remove="Maintenance\**" /> <EmbeddedResource Remove="Api\Notifications\**" /> - <EmbeddedResource Remove="Api\Requests\**" /> <EmbeddedResource Remove="Blockchain\Bitcoin\Transactions\**" /> <EmbeddedResource Remove="DataAccess\**" /> <EmbeddedResource Remove="Maintenance\**" /> <None Remove="Api\Notifications\**" /> - <None Remove="Api\Requests\**" /> <None Remove="Blockchain\Bitcoin\Transactions\**" /> <None Remove="DataAccess\**" /> <None Remove="Maintenance\**" /> diff --git a/src/Miningcore/Payments/PayoutManager.cs b/src/Miningcore/Payments/PayoutManager.cs index 43a011482..c9ddf2eeb 100644 --- a/src/Miningcore/Payments/PayoutManager.cs +++ b/src/Miningcore/Payments/PayoutManager.cs @@ -20,6 +20,7 @@ using Miningcore.Persistence.Model; using Miningcore.Persistence.Repositories; using NLog; +using Org.BouncyCastle.Utilities.Net; using Contract = Miningcore.Contracts.Contract; namespace Miningcore.Payments diff --git a/src/Miningcore/Persistence/Postgres/Repositories/ShareRepository.cs b/src/Miningcore/Persistence/Postgres/Repositories/ShareRepository.cs index 7b5496839..b0b23a1c4 100644 --- a/src/Miningcore/Persistence/Postgres/Repositories/ShareRepository.cs +++ b/src/Miningcore/Persistence/Postgres/Repositories/ShareRepository.cs @@ -193,5 +193,17 @@ public async Task<MinerWorkerHashes[]> GetHashAccumulationBetweenCreatedAsync(ID return (await con.QueryAsync<MinerWorkerHashes>(query, new { poolId, start, end })) .ToArray(); } + + public async Task<string[]> GetRecentyUsedIpAddresses(IDbConnection con, IDbTransaction tx, string poolId, string miner) + { + logger.LogInvoke(new object[] { poolId }); + + const string query = "SELECT DISTINCT s.ipaddress FROM (SELECT * FROM shares " + + "WHERE poolid = @poolId and miner = @miner ORDER BY CREATED DESC LIMIT 100) s"; + + return (await con.QueryAsync<string>(query, new { poolId, miner }, tx)) + .ToArray(); + } } } + diff --git a/src/Miningcore/Persistence/Repositories/IShareRepository.cs b/src/Miningcore/Persistence/Repositories/IShareRepository.cs index 9c2bb53e9..970942a76 100644 --- a/src/Miningcore/Persistence/Repositories/IShareRepository.cs +++ b/src/Miningcore/Persistence/Repositories/IShareRepository.cs @@ -25,5 +25,7 @@ public interface IShareRepository Task<double?> GetAccumulatedShareDifficultyBetweenCreatedAsync(IDbConnection con, string poolId, DateTime start, DateTime end); Task<MinerWorkerHashes[]> GetAccumulatedShareDifficultyTotalAsync(IDbConnection con, string poolId); Task<MinerWorkerHashes[]> GetHashAccumulationBetweenCreatedAsync(IDbConnection con, string poolId, DateTime start, DateTime end); + + Task<string[]> GetRecentyUsedIpAddresses(IDbConnection con, IDbTransaction tx, string poolId, string miner); } } From 1faa4dbefcb295aa1cc5d9b4364c538a2be1d21e Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Fri, 23 Jul 2021 00:25:54 +0200 Subject: [PATCH 129/145] Logging --- src/Miningcore/Api/Controllers/PoolApiController.cs | 9 ++++++++- .../Persistence/Postgres/Repositories/MinerRepository.cs | 6 ++---- .../Persistence/Repositories/IMinerRepository.cs | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Miningcore/Api/Controllers/PoolApiController.cs b/src/Miningcore/Api/Controllers/PoolApiController.cs index 85098c937..adb332fa3 100644 --- a/src/Miningcore/Api/Controllers/PoolApiController.cs +++ b/src/Miningcore/Api/Controllers/PoolApiController.cs @@ -22,6 +22,8 @@ using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.ActionConstraints; +using NLog; +using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Miningcore.Api.Controllers { @@ -50,6 +52,8 @@ public PoolApiController(IComponentContext ctx, IActionDescriptorCollectionProvi private readonly IActionDescriptorCollectionProvider adcp; private readonly ConcurrentDictionary<string, IMiningPool> pools; + private static readonly NLog.ILogger logger = LogManager.GetCurrentClassLogger(); + #region Actions [HttpGet] @@ -609,8 +613,11 @@ public async Task<PagedResultResponse<AmountByDate[]>> PageMinerEarningsByDayV2A mapped.PoolId = pool.Id; mapped.Address = address; - var result = await minerRepo.UpdateSettings(con, tx, mapped); + await minerRepo.UpdateSettings(con, tx, mapped); + + logger.Info(()=> $"Updated settings for pool {pool.Id}, miner {address}"); + var result = await minerRepo.GetSettings(con, tx, mapped.PoolId, mapped.Address); return mapper.Map<Responses.MinerSettings>(result); }); } diff --git a/src/Miningcore/Persistence/Postgres/Repositories/MinerRepository.cs b/src/Miningcore/Persistence/Postgres/Repositories/MinerRepository.cs index 98a764020..9ea49466e 100644 --- a/src/Miningcore/Persistence/Postgres/Repositories/MinerRepository.cs +++ b/src/Miningcore/Persistence/Postgres/Repositories/MinerRepository.cs @@ -30,7 +30,7 @@ public async Task<MinerSettings> GetSettings(IDbConnection con, IDbTransaction t return mapper.Map<MinerSettings>(entity); } - public async Task<MinerSettings> UpdateSettings(IDbConnection con, IDbTransaction tx, MinerSettings settings) + public Task UpdateSettings(IDbConnection con, IDbTransaction tx, MinerSettings settings) { const string query = "INSERT INTO miner_settings(poolid, address, paymentthreshold, created, updated) " + "VALUES(@poolid, @address, @paymentthreshold, now(), now()) " + @@ -38,9 +38,7 @@ public async Task<MinerSettings> UpdateSettings(IDbConnection con, IDbTransactio "SET paymentthreshold = @paymentthreshold, updated = now() " + "WHERE miner_settings.poolid = @poolid AND miner_settings.address = @address"; - await con.ExecuteAsync(query, settings, tx); - - return await GetSettings(con, tx, settings.PoolId, settings.Address); + return con.ExecuteAsync(query, settings, tx); } } } diff --git a/src/Miningcore/Persistence/Repositories/IMinerRepository.cs b/src/Miningcore/Persistence/Repositories/IMinerRepository.cs index 2a3ff1269..f80478420 100644 --- a/src/Miningcore/Persistence/Repositories/IMinerRepository.cs +++ b/src/Miningcore/Persistence/Repositories/IMinerRepository.cs @@ -7,6 +7,6 @@ namespace Miningcore.Persistence.Repositories public interface IMinerRepository { Task<MinerSettings> GetSettings(IDbConnection con, IDbTransaction tx, string poolId, string address); - Task<MinerSettings> UpdateSettings(IDbConnection con, IDbTransaction tx, MinerSettings settings); + Task UpdateSettings(IDbConnection con, IDbTransaction tx, MinerSettings settings); } } From 74d8d53eb14e5a4d7e4cf2854abe699af6ac44d0 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Fri, 23 Jul 2021 00:27:58 +0200 Subject: [PATCH 130/145] Logging --- src/Miningcore/Api/Controllers/AdminApiController.cs | 12 +++++++++++- src/Miningcore/Api/Controllers/PoolApiController.cs | 4 ---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Miningcore/Api/Controllers/AdminApiController.cs b/src/Miningcore/Api/Controllers/AdminApiController.cs index b69dad5c7..d0519081d 100644 --- a/src/Miningcore/Api/Controllers/AdminApiController.cs +++ b/src/Miningcore/Api/Controllers/AdminApiController.cs @@ -8,6 +8,7 @@ using System.Collections.Concurrent; using System.Net; using System.Threading.Tasks; +using NLog; namespace Miningcore.Api.Controllers { @@ -31,6 +32,8 @@ public AdminApiController(IComponentContext ctx) : base(ctx) private readonly Responses.AdminGcStats gcStats; + private static readonly ILogger logger = LogManager.GetCurrentClassLogger(); + #region Actions [HttpGet("stats/gc")] @@ -95,7 +98,14 @@ public async Task<decimal> GetMinerBalanceAsync(string poolId, string address) mapped.PoolId = pool.Id; mapped.Address = address; - var result = await cf.RunTx((con, tx) => minerRepo.UpdateSettings(con, tx, mapped)); + var result = await cf.RunTx(async (con, tx) => + { + await minerRepo.UpdateSettings(con, tx, mapped); + + return await minerRepo.GetSettings(con, tx, mapped.PoolId, mapped.Address); + }); + + logger.Info(()=> $"Updated settings for pool {pool.Id}, miner {address}"); return mapper.Map<Responses.MinerSettings>(result); } diff --git a/src/Miningcore/Api/Controllers/PoolApiController.cs b/src/Miningcore/Api/Controllers/PoolApiController.cs index adb332fa3..313f02d19 100644 --- a/src/Miningcore/Api/Controllers/PoolApiController.cs +++ b/src/Miningcore/Api/Controllers/PoolApiController.cs @@ -1,5 +1,4 @@ using Autofac; -using AutoMapper; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Infrastructure; using Miningcore.Api.Extensions; @@ -8,14 +7,12 @@ using Miningcore.Configuration; using Miningcore.Extensions; using Miningcore.Mining; -using Miningcore.Persistence; using Miningcore.Persistence.Model; using Miningcore.Persistence.Model.Projections; using Miningcore.Persistence.Repositories; using Miningcore.Time; using System; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Data; using System.Globalization; using System.Linq; @@ -23,7 +20,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.ActionConstraints; using NLog; -using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Miningcore.Api.Controllers { From 460c71019137ca4a7f0d56f4a45b5efdd64256ff Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Fri, 23 Jul 2021 00:59:40 +0200 Subject: [PATCH 131/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs index c6c0d88a8..1ba622169 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPayoutHandler.cs @@ -326,6 +326,7 @@ public virtual async Task PayoutAsync(IMiningPool pool, Balance[] balances, Canc // get balance var walletBalances = await ergoClient.WalletBalancesAsync(ct); var walletTotal = walletBalances.Balance / ErgoConstants.SmallestUnit; + logger.Info(() => $"[{LogCategory}] Current wallet balance is {FormatAmount(walletTotal)}"); // bail if balance does not satisfy payments @@ -335,6 +336,17 @@ public virtual async Task PayoutAsync(IMiningPool pool, Balance[] balances, Canc return; } + // validate addresses + logger.Info("Validating addresses ..."); + + foreach(var pair in amounts) + { + var validity = await Guard(() => ergoClient.CheckAddressValidityAsync(pair.Key, ct)); + + if(validity == null || !validity.IsValid) + logger.Warn(()=> $"Address {pair.Key} is not valid!"); + } + // Create request batch var requests = amounts.Select(x => new PaymentRequest { From 8ad29de2e8cd7f1a7b574c82fe1604f7ba833ff3 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Fri, 23 Jul 2021 02:00:38 +0200 Subject: [PATCH 132/145] Always use miner/worker from context instead from share submission data --- .../Blockchain/Bitcoin/BitcoinJobManager.cs | 11 +++-------- .../Blockchain/Equihash/EquihashJobManager.cs | 11 +++-------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManager.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManager.cs index 5e854b0d6..d41b255e6 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManager.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManager.cs @@ -243,19 +243,14 @@ public virtual async ValueTask<Share> SubmitShareAsync(StratumConnection worker, if(job == null) throw new StratumException(StratumError.JobNotFound, "job not found"); - // extract worker/miner/payoutid - var split = workerValue.Split('.'); - var minerName = split[0]; - var workerName = split.Length > 1 ? split[1] : null; - // validate & process var (share, blockHex) = job.ProcessShare(worker, extraNonce2, nTime, nonce, versionBits); // enrich share with common data share.PoolId = poolConfig.Id; share.IpAddress = worker.RemoteEndpoint.Address.ToString(); - share.Miner = minerName; - share.Worker = workerName; + share.Miner = context.Miner; + share.Worker = context.Worker; share.UserAgent = context.UserAgent; share.Source = clusterConfig.ClusterName; share.Created = clock.Now; @@ -272,7 +267,7 @@ public virtual async ValueTask<Share> SubmitShareAsync(StratumConnection worker, if(share.IsBlockCandidate) { - logger.Info(() => $"Daemon accepted block {share.BlockHeight} [{share.BlockHash}] submitted by {minerName}"); + logger.Info(() => $"Daemon accepted block {share.BlockHeight} [{share.BlockHash}] submitted by {context.Miner}"); OnBlockFound(); diff --git a/src/Miningcore/Blockchain/Equihash/EquihashJobManager.cs b/src/Miningcore/Blockchain/Equihash/EquihashJobManager.cs index e4043c62d..4ccf0025e 100644 --- a/src/Miningcore/Blockchain/Equihash/EquihashJobManager.cs +++ b/src/Miningcore/Blockchain/Equihash/EquihashJobManager.cs @@ -266,11 +266,6 @@ public async ValueTask<Share> SubmitShareAsync(StratumConnection worker, object if(job == null) throw new StratumException(StratumError.JobNotFound, "job not found"); - // extract worker/miner/payoutid - var split = workerValue.Split('.'); - var minerName = split[0]; - var workerName = split.Length > 1 ? split[1] : null; - // validate & process var (share, blockHex) = job.ProcessShare(worker, extraNonce2, nTime, solution); @@ -286,7 +281,7 @@ public async ValueTask<Share> SubmitShareAsync(StratumConnection worker, object if(share.IsBlockCandidate) { - logger.Info(() => $"Daemon accepted block {share.BlockHeight} [{share.BlockHash}] submitted by {minerName}"); + logger.Info(() => $"Daemon accepted block {share.BlockHeight} [{share.BlockHash}] submitted by {context.Miner}"); OnBlockFound(); @@ -305,8 +300,8 @@ public async ValueTask<Share> SubmitShareAsync(StratumConnection worker, object // enrich share with common data share.PoolId = poolConfig.Id; share.IpAddress = worker.RemoteEndpoint.Address.ToString(); - share.Miner = minerName; - share.Worker = workerName; + share.Miner = context.Miner; + share.Worker = context.Worker; share.UserAgent = context.UserAgent; share.Source = clusterConfig.ClusterName; share.NetworkDifficulty = job.Difficulty; From f2262449362b83c4650ac16e6be9a88c6dcabe13 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Fri, 23 Jul 2021 02:01:42 +0200 Subject: [PATCH 133/145] Use miner worker from context --- src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs index d1b4bc54e..d6971e665 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs @@ -310,19 +310,14 @@ public async ValueTask<Share> SubmitShareAsync(StratumConnection worker, object if(job == null) throw new StratumException(StratumError.JobNotFound, "job not found"); - // extract worker/miner/payoutid - var split = workerValue.Split('.'); - var minerName = split[0]; - var workerName = split.Length > 1 ? split[1] : null; - // validate & process var share = job.ProcessShare(worker, extraNonce2, nTime, nonce); // enrich share with common data share.PoolId = poolConfig.Id; share.IpAddress = worker.RemoteEndpoint.Address.ToString(); - share.Miner = minerName; - share.Worker = workerName; + share.Miner = context.Miner; + share.Worker = context.Worker; share.UserAgent = context.UserAgent; share.Source = clusterConfig.ClusterName; share.Created = clock.Now; From eb1e1132c0b8859d67b7c1cbb3bcb4cce07e6f6c Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Fri, 23 Jul 2021 11:11:49 +0200 Subject: [PATCH 134/145] WIP --- src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs index d6971e665..bbbdbab3d 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs @@ -334,7 +334,7 @@ public async ValueTask<Share> SubmitShareAsync(StratumConnection worker, object if(share.IsBlockCandidate) { - logger.Info(() => $"Daemon accepted block {share.BlockHeight} [{share.BlockHash}] submitted by {minerName}"); + logger.Info(() => $"Daemon accepted block {share.BlockHeight} [{share.BlockHash}] submitted by {context.Miner}"); OnBlockFound(); From f303b1eaefe6d75411d85b7e2bf3ea4507501afe Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Fri, 23 Jul 2021 11:56:56 +0200 Subject: [PATCH 135/145] WIP --- src/Miningcore/Api/Controllers/PoolApiController.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Miningcore/Api/Controllers/PoolApiController.cs b/src/Miningcore/Api/Controllers/PoolApiController.cs index 313f02d19..f4ace50c7 100644 --- a/src/Miningcore/Api/Controllers/PoolApiController.cs +++ b/src/Miningcore/Api/Controllers/PoolApiController.cs @@ -579,20 +579,17 @@ public async Task<PagedResultResponse<AmountByDate[]>> PageMinerEarningsByDayV2A // any known ips? if(ips == null || ips.Length == 0) - throw new ApiException("No recent IP addresses found", HttpStatusCode.BadRequest); + throw new ApiException("No recent IP addresses found", HttpStatusCode.NotFound); // check if at least one matches var match = false; foreach(var ip in ips) { - if(IPAddress.TryParse(ip, out var ipAddress)) + if(IPAddress.TryParse(ip, out var ipAddress) && ipAddress.IsEqual(requestIp)) { - if(ipAddress.IsEqual(requestIp)) - { - match = true; - break; - } + match = true; + break; } } From 69bec78ba73ed366b1ea90aa52ee4cc690c3469b Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Fri, 23 Jul 2021 12:02:04 +0200 Subject: [PATCH 136/145] WIP --- .../Api/Controllers/PoolApiController.cs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/Miningcore/Api/Controllers/PoolApiController.cs b/src/Miningcore/Api/Controllers/PoolApiController.cs index f4ace50c7..9e34933d2 100644 --- a/src/Miningcore/Api/Controllers/PoolApiController.cs +++ b/src/Miningcore/Api/Controllers/PoolApiController.cs @@ -581,19 +581,8 @@ public async Task<PagedResultResponse<AmountByDate[]>> PageMinerEarningsByDayV2A if(ips == null || ips.Length == 0) throw new ApiException("No recent IP addresses found", HttpStatusCode.NotFound); - // check if at least one matches - var match = false; - - foreach(var ip in ips) - { - if(IPAddress.TryParse(ip, out var ipAddress) && ipAddress.IsEqual(requestIp)) - { - match = true; - break; - } - } - - if(!match) + // match? + if(!ips.Any(x=> IPAddress.TryParse(x, out var ipAddress) && ipAddress.IsEqual(requestIp))) throw new ApiException("No recent IP addresses matches", HttpStatusCode.Forbidden); // map settings From 8ed66b91fef5b5fd126f581d259e33dcc0ebacda Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Fri, 23 Jul 2021 12:05:58 +0200 Subject: [PATCH 137/145] Refactor --- .../Api/Controllers/PoolApiController.cs | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Miningcore/Api/Controllers/PoolApiController.cs b/src/Miningcore/Api/Controllers/PoolApiController.cs index 9e34933d2..d0efb7780 100644 --- a/src/Miningcore/Api/Controllers/PoolApiController.cs +++ b/src/Miningcore/Api/Controllers/PoolApiController.cs @@ -558,7 +558,7 @@ public async Task<PagedResultResponse<AmountByDate[]>> PageMinerEarningsByDayV2A } [HttpPost("{poolId}/miners/{address}/settings")] - public Task<Responses.MinerSettings> SetMinerSettingsAsync(string poolId, string address, + public async Task<Responses.MinerSettings> SetMinerSettingsAsync(string poolId, string address, [FromBody] Requests.UpdateMinerSettingsRequest request) { var pool = GetPool(poolId); @@ -572,29 +572,29 @@ public async Task<PagedResultResponse<AmountByDate[]>> PageMinerEarningsByDayV2A if(!IPAddress.TryParse(request.IpAddress, out var requestIp)) throw new ApiException("Invalid IP address", HttpStatusCode.BadRequest); - return cf.RunTx(async (con, tx) => - { - // fetch recent IPs - var ips = await shareRepo.GetRecentyUsedIpAddresses(con, tx, poolId, address); + // fetch recent IPs + var ips = await cf.Run(con=> shareRepo.GetRecentyUsedIpAddresses(con, null, poolId, address)); - // any known ips? - if(ips == null || ips.Length == 0) - throw new ApiException("No recent IP addresses found", HttpStatusCode.NotFound); + // any known ips? + if(ips == null || ips.Length == 0) + throw new ApiException("No recent IP addresses found", HttpStatusCode.NotFound); - // match? - if(!ips.Any(x=> IPAddress.TryParse(x, out var ipAddress) && ipAddress.IsEqual(requestIp))) - throw new ApiException("No recent IP addresses matches", HttpStatusCode.Forbidden); + // match? + if(!ips.Any(x=> IPAddress.TryParse(x, out var ipAddress) && ipAddress.IsEqual(requestIp))) + throw new ApiException("No recent IP addresses matches", HttpStatusCode.Forbidden); - // map settings - var mapped = mapper.Map<Persistence.Model.MinerSettings>(request.Settings); + // map settings + var mapped = mapper.Map<Persistence.Model.MinerSettings>(request.Settings); - // clamp limit - if(pool.PaymentProcessing != null) - mapped.PaymentThreshold = Math.Max(mapped.PaymentThreshold, pool.PaymentProcessing.MinimumPayment); + // clamp limit + if(pool.PaymentProcessing != null) + mapped.PaymentThreshold = Math.Max(mapped.PaymentThreshold, pool.PaymentProcessing.MinimumPayment); - mapped.PoolId = pool.Id; - mapped.Address = address; + mapped.PoolId = pool.Id; + mapped.Address = address; + return await cf.RunTx(async (con, tx) => + { await minerRepo.UpdateSettings(con, tx, mapped); logger.Info(()=> $"Updated settings for pool {pool.Id}, miner {address}"); From 07cf10f1a8b7d5a60032e8336d685230c22b1653 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Fri, 23 Jul 2021 12:10:44 +0200 Subject: [PATCH 138/145] Typo and wording --- src/Miningcore/Api/Controllers/PoolApiController.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Miningcore/Api/Controllers/PoolApiController.cs b/src/Miningcore/Api/Controllers/PoolApiController.cs index d0efb7780..80c2d1f0f 100644 --- a/src/Miningcore/Api/Controllers/PoolApiController.cs +++ b/src/Miningcore/Api/Controllers/PoolApiController.cs @@ -577,11 +577,11 @@ public async Task<PagedResultResponse<AmountByDate[]>> PageMinerEarningsByDayV2A // any known ips? if(ips == null || ips.Length == 0) - throw new ApiException("No recent IP addresses found", HttpStatusCode.NotFound); + throw new ApiException("Address not recently used for mining", HttpStatusCode.NotFound); // match? if(!ips.Any(x=> IPAddress.TryParse(x, out var ipAddress) && ipAddress.IsEqual(requestIp))) - throw new ApiException("No recent IP addresses matches", HttpStatusCode.Forbidden); + throw new ApiException("None of the recently used IP addresses matches the request", HttpStatusCode.Forbidden); // map settings var mapped = mapper.Map<Persistence.Model.MinerSettings>(request.Settings); @@ -593,6 +593,7 @@ public async Task<PagedResultResponse<AmountByDate[]>> PageMinerEarningsByDayV2A mapped.PoolId = pool.Id; mapped.Address = address; + // finally update the settings return await cf.RunTx(async (con, tx) => { await minerRepo.UpdateSettings(con, tx, mapped); From 7e528a04614c5951a7938791ee0a0384d290699d Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Mon, 2 Aug 2021 01:12:45 +0200 Subject: [PATCH 139/145] WIIP --- src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs index bbbdbab3d..e8b7c8ced 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoJobManager.cs @@ -111,7 +111,7 @@ await GetBlockTemplateAsync() : var isNew = job == null || (blockTemplate != null && (job.BlockTemplate?.Msg != blockTemplate.Msg || - blockTemplate?.Height > job.BlockTemplate.Height)); + blockTemplate.Height > job.BlockTemplate.Height)); if(isNew) messageBus.NotifyChainHeight(poolConfig.Id, blockTemplate.Height, poolConfig.Template); From d5884fbabfc60e5198762c87b1b4c2e1cb0f3a30 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Mon, 2 Aug 2021 01:14:45 +0200 Subject: [PATCH 140/145] Add cancellation loop check for the sake of completeness --- src/Miningcore/Stratum/StratumConnection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Miningcore/Stratum/StratumConnection.cs b/src/Miningcore/Stratum/StratumConnection.cs index b73976b8e..a0a6fdefe 100644 --- a/src/Miningcore/Stratum/StratumConnection.cs +++ b/src/Miningcore/Stratum/StratumConnection.cs @@ -299,7 +299,7 @@ private async Task ProcessReceivePipeAsync(CancellationToken ct, private async Task ProcessSendQueueAsync(CancellationToken ct) { - while(true) + while(!ct.IsCancellationRequested) { var msg = await sendQueue.ReceiveAsync(ct); From 8d9e4f338cf53d48ea964a3336f27f9488cb9014 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold <oliver@weichhold.com> Date: Mon, 2 Aug 2021 02:50:55 +0200 Subject: [PATCH 141/145] WIP --- src/Miningcore/Stratum/StratumConnection.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Miningcore/Stratum/StratumConnection.cs b/src/Miningcore/Stratum/StratumConnection.cs index a0a6fdefe..3e92f0c30 100644 --- a/src/Miningcore/Stratum/StratumConnection.cs +++ b/src/Miningcore/Stratum/StratumConnection.cs @@ -231,7 +231,7 @@ private async ValueTask SendAsync<T>(T payload) private async Task FillReceivePipeAsync(CancellationToken ct) { - while(true) + while(!ct.IsCancellationRequested) { logger.Debug(() => $"[{ConnectionId}] [NET] Waiting for data ..."); @@ -259,7 +259,7 @@ private async Task ProcessReceivePipeAsync(CancellationToken ct, TcpProxyProtocolConfig proxyProtocol, Func<StratumConnection, JsonRpcRequest, CancellationToken, Task> onRequestAsync) { - while(true) + while(!ct.IsCancellationRequested) { logger.Debug(() => $"[{ConnectionId}] [PIPE] Waiting for data ..."); From 7a10969a5ee749c66a05b9542db597e47fd4846a Mon Sep 17 00:00:00 2001 From: Konstantin35 <molepool@outlook.com> Date: Tue, 24 Aug 2021 01:27:08 +0300 Subject: [PATCH 142/145] Added changes related to London hard-fork, also correct calculation of the reward minus Burnt Fees, additional eth_sendTransaction fields and settings: "gas": 21000, "maxFeePerGas": 50000000000, --- examples/ethereum_pool.json | 18 ++++++++++-------- ...EthereumPoolPaymentProcessingConfigExtra.cs | 10 ++++++++++ .../DaemonRequests/SendTransactionRequest.cs | 12 ++++++++++++ .../DaemonResponses/GetBlockResponse.cs | 6 ++++++ .../Blockchain/Ethereum/EthereumConstants.cs | 1 + .../Ethereum/EthereumPayoutHandler.cs | 16 +++++++++++++++- 6 files changed, 54 insertions(+), 9 deletions(-) diff --git a/examples/ethereum_pool.json b/examples/ethereum_pool.json index 2949a351c..730816cf5 100644 --- a/examples/ethereum_pool.json +++ b/examples/ethereum_pool.json @@ -109,14 +109,16 @@ } ], "paymentProcessing": { - "enabled": true, - "minimumPayment": 1, - "payoutScheme": "PPLNS", - "payoutSchemeConfig": { - "factor": 0.5 - }, - "keepUncles": false, - "keepTransactionFees": false + "enabled": true, + "minimumPayment": 1, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + }, + "gas": 21000, + "maxFeePerGas": 50000000000, + "keepUncles": false, + "keepTransactionFees": false } } ] diff --git a/src/Miningcore/Blockchain/Ethereum/Configuration/EthereumPoolPaymentProcessingConfigExtra.cs b/src/Miningcore/Blockchain/Ethereum/Configuration/EthereumPoolPaymentProcessingConfigExtra.cs index 77065ccfa..d99e32725 100644 --- a/src/Miningcore/Blockchain/Ethereum/Configuration/EthereumPoolPaymentProcessingConfigExtra.cs +++ b/src/Miningcore/Blockchain/Ethereum/Configuration/EthereumPoolPaymentProcessingConfigExtra.cs @@ -11,5 +11,15 @@ public class EthereumPoolPaymentProcessingConfigExtra /// True to exempt uncle rewards from miner rewards /// </summary> public bool KeepUncles { get; set; } + + /// <summary> + /// Gas amount for payout tx (advanced users only) + /// </summary> + public ulong Gas { get; set; } + + /// <summary> + /// maximum amount you’re willing to pay + /// </summary> + public ulong MaxFeePerGas { get; set; } } } diff --git a/src/Miningcore/Blockchain/Ethereum/DaemonRequests/SendTransactionRequest.cs b/src/Miningcore/Blockchain/Ethereum/DaemonRequests/SendTransactionRequest.cs index fcaa1968b..22d3eb87c 100644 --- a/src/Miningcore/Blockchain/Ethereum/DaemonRequests/SendTransactionRequest.cs +++ b/src/Miningcore/Blockchain/Ethereum/DaemonRequests/SendTransactionRequest.cs @@ -41,5 +41,17 @@ public class SendTransactionRequest /// </summary> [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public string Data { get; set; } + + /// <summary> + /// Maximum fee per gas the sender is willing to pay to miners in wei. + /// </summary> + [JsonConverter(typeof(HexToIntegralTypeJsonConverter<ulong>))] + public ulong MaxPriorityFeePerGas { get; set; } + + /// <summary> + /// The maximum total fee per gas the sender is willing to pay(includes the network / base fee and miner / priority fee) in wei + /// </summary> + [JsonConverter(typeof(HexToIntegralTypeJsonConverter<ulong>))] + public ulong MaxFeePerGas { get; set; } } } diff --git a/src/Miningcore/Blockchain/Ethereum/DaemonResponses/GetBlockResponse.cs b/src/Miningcore/Blockchain/Ethereum/DaemonResponses/GetBlockResponse.cs index fd5ce61a9..49985ba85 100644 --- a/src/Miningcore/Blockchain/Ethereum/DaemonResponses/GetBlockResponse.cs +++ b/src/Miningcore/Blockchain/Ethereum/DaemonResponses/GetBlockResponse.cs @@ -177,5 +177,11 @@ public class Block /// Array of uncle hashes. /// </summary> public string[] Uncles { get; set; } + + /// <summary> + /// Base fee per gas. + /// </summary> + [JsonConverter(typeof(HexToIntegralTypeJsonConverter<ulong>))] + public ulong BaseFeePerGas { get; set; } } } diff --git a/src/Miningcore/Blockchain/Ethereum/EthereumConstants.cs b/src/Miningcore/Blockchain/Ethereum/EthereumConstants.cs index 39bcc9e0b..07aed05e7 100644 --- a/src/Miningcore/Blockchain/Ethereum/EthereumConstants.cs +++ b/src/Miningcore/Blockchain/Ethereum/EthereumConstants.cs @@ -85,5 +85,6 @@ public static class EthCommands public const string SendTx = "eth_sendTransaction"; public const string UnlockAccount = "personal_unlockAccount"; public const string Subscribe = "eth_subscribe"; + public const string MaxPriorityFeePerGas = "eth_maxPriorityFeePerGas"; } } diff --git a/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs b/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs index 9ffaae492..8dca14dce 100644 --- a/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs @@ -126,6 +126,12 @@ public async Task<Block[]> ClassifyBlocksAsync(IMiningPool pool, Block[] blocks, var blockHashResponse = await rpcClient.ExecuteAsync<DaemonResponses.Block>(logger, EC.GetBlockByNumber, ct, new[] { (object) block.BlockHeight.ToStringHexWithPrefix(), true }); var blockHash = blockHashResponse.Response.Hash; + var baseGas = blockHashResponse.Response.BaseFeePerGas; + var gasUsed = blockHashResponse.Response.GasUsed; + + var burnedFee = (decimal) 0; + if(extraPoolConfig?.ChainTypeOverride == "Ethereum") + burnedFee = (baseGas * gasUsed / EthereumConstants.Wei); block.Hash = blockHash; block.Status = BlockStatus.Confirmed; @@ -138,7 +144,7 @@ public async Task<Block[]> ClassifyBlocksAsync(IMiningPool pool, Block[] blocks, block.Reward += blockInfo.Uncles.Length * (block.Reward / 32); // uncle rewards if(extraConfig?.KeepTransactionFees == false && blockInfo.Transactions?.Length > 0) - block.Reward += await GetTxRewardAsync(blockInfo, ct); // tx fees + block.Reward += await GetTxRewardAsync(blockInfo, ct) - burnedFee; logger.Info(() => $"[{LogCategory}] Unlocked block {block.BlockHeight} worth {FormatAmount(block.Reward)}"); @@ -396,6 +402,14 @@ private async Task<string> PayoutAsync(Balance balance, CancellationToken ct) Value = amount.ToString("x").TrimStart('0'), }; + if(extraPoolConfig?.ChainTypeOverride == "Ethereum") + { + var maxPriorityFeePerGas = await rpcClient.ExecuteAsync<string>(logger, EC.MaxPriorityFeePerGas, ct); + request.Gas = extraConfig.Gas; + request.MaxPriorityFeePerGas = maxPriorityFeePerGas.Response.IntegralFromHex<ulong>(); + request.MaxFeePerGas = extraConfig.MaxFeePerGas; + } + var response = await rpcClient.ExecuteAsync<string>(logger, EC.SendTx, ct, new[] { request }); if(response.Error != null) From 8bceefd63dd46a78d2d739c36a33a920261a810e Mon Sep 17 00:00:00 2001 From: Calvin Tam <calvintam236@users.noreply.github.com> Date: Tue, 20 Jul 2021 17:47:42 -0700 Subject: [PATCH 143/145] Remove deprecated issue template --- ISSUE_TEMPLATE.md | 35 ----------------------------------- 1 file changed, 35 deletions(-) delete mode 100644 ISSUE_TEMPLATE.md diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md deleted file mode 100644 index 1544b9961..000000000 --- a/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,35 +0,0 @@ -## Note -Please use the following format to file an issue. Issues that fail to comply will delay our responses. -Please remove this section when you submit the issue. Thank you for your cooperation. - -### Description - -[Description of the issue] - -### Steps to Reproduce - -1. [First Step] -2. [Second Step] -3. [and so on...] - -### Expected behavior - -[What you expect to happen] - -### Actual behavior - -[What actually happens] - -### Reproduces how often - -[What percentage of the time does it reproduce?] - -### Version - -If you are using released version, please state your version. -If you are using clone from `dev` branch, please state commit hash of your copy. -Also, please include the OS name and version you're currently running. - -### Additional Information - -Any additional information, log, configuration or data that might be necessary to reproduce the issue. From 91997e2c982fb868052232048b97b64c44297c6a Mon Sep 17 00:00:00 2001 From: ahmedbodi <ahmed_bodi@msn.com> Date: Tue, 27 Jul 2021 19:48:59 +0100 Subject: [PATCH 144/145] [Examples] Add randomxRealm config option to the example --- examples/monero_pool.json | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/monero_pool.json b/examples/monero_pool.json index c5238f808..6e15ff346 100644 --- a/examples/monero_pool.json +++ b/examples/monero_pool.json @@ -46,6 +46,7 @@ "id": "xmr1", "enabled": true, "coin": "monero", + "randomXRealm": "xmr1", "address": "43g9avHw8WYHnq749LU1Nw1BZ8FCwS2B7GLoW4vif7cPjJB7e9f6WgU8ptDFM7kyNS9kz1zy334dAYFKgP2KJU8vMoZ3hYD", "rewardRecipients": [ { From 89da4264fa13f8d057c5ac6112b2ba69bf8a3b4e Mon Sep 17 00:00:00 2001 From: Bravo <djdram@ya.ru> Date: Thu, 9 Sep 2021 12:15:26 +0300 Subject: [PATCH 145/145] Update GitVersion.MsBuild 5.7.0 Update MsBuild --- src/Miningcore/Miningcore.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Miningcore/Miningcore.csproj b/src/Miningcore/Miningcore.csproj index 89609a0f6..9d53307b0 100644 --- a/src/Miningcore/Miningcore.csproj +++ b/src/Miningcore/Miningcore.csproj @@ -44,7 +44,7 @@ <PackageReference Include="AspNetCoreRateLimit" Version="4.0.1" /> <PackageReference Include="Autofac" Version="6.2.0" /> <PackageReference Include="Autofac.Extensions.DependencyInjection" Version="7.1.0" /> - <PackageReference Include="GitVersion.MsBuild" Version="5.6.10"> + <PackageReference Include="GitVersion.MsBuild" Version="5.7.0"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference>