From a74348e1155e6c18b0748795848f56669011cd9b Mon Sep 17 00:00:00 2001 From: Howard Chu Date: Wed, 16 Mar 2016 10:24:48 +0000 Subject: [PATCH 01/47] Add destructor for readtxns Only if we created the readtxn. Was missing cleanups from exceptions before. --- src/blockchain_db/lmdb/db_lmdb.cpp | 27 ++++++++++++++++++--------- src/blockchain_db/lmdb/db_lmdb.h | 4 +++- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index d2939bf9..9ae7b404 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -228,17 +228,24 @@ mdb_threadinfo::~mdb_threadinfo() mdb_txn_abort(m_ti_rtxn); } -mdb_txn_safe::mdb_txn_safe() : m_txn(NULL) +mdb_txn_safe::mdb_txn_safe(const bool check) : m_txn(NULL), m_tinfo(NULL), m_check(check) { - while (creation_gate.test_and_set()); - num_active_txns++; - creation_gate.clear(); + if (check) + { + while (creation_gate.test_and_set()); + num_active_txns++; + creation_gate.clear(); + } } mdb_txn_safe::~mdb_txn_safe() { LOG_PRINT_L3("mdb_txn_safe: destructor"); - if (m_txn != nullptr) + if (m_tinfo != nullptr) + { + mdb_txn_reset(m_tinfo->m_ti_rtxn); + memset(&m_tinfo->m_ti_rflags, 0, sizeof(m_tinfo->m_ti_rflags)); + } else if (m_txn != nullptr) { if (m_batch_txn) // this is a batch txn and should have been handled before this point for safety { @@ -256,7 +263,8 @@ mdb_txn_safe::~mdb_txn_safe() } mdb_txn_abort(m_txn); } - num_active_txns--; + if (m_check) + num_active_txns--; } void mdb_txn_safe::commit(std::string message) @@ -1303,9 +1311,10 @@ void BlockchainLMDB::unlock() #define TXN_PREFIX_RDONLY() \ MDB_txn *m_txn; \ mdb_txn_cursors *m_cursors; \ - bool my_rtxn = block_rtxn_start(&m_txn, &m_cursors); -#define TXN_POSTFIX_RDONLY() \ - if (my_rtxn) block_rtxn_stop() + bool my_rtxn = block_rtxn_start(&m_txn, &m_cursors); \ + mdb_txn_safe auto_txn(my_rtxn); \ + if (my_rtxn) auto_txn.m_tinfo = m_tinfo.get() +#define TXN_POSTFIX_RDONLY() #define TXN_POSTFIX_SUCCESS() \ do { \ diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h index 718ee105..a3f32ffa 100644 --- a/src/blockchain_db/lmdb/db_lmdb.h +++ b/src/blockchain_db/lmdb/db_lmdb.h @@ -115,7 +115,7 @@ typedef struct mdb_threadinfo struct mdb_txn_safe { - mdb_txn_safe(); + mdb_txn_safe(const bool check=true); ~mdb_txn_safe(); void commit(std::string message = ""); @@ -142,8 +142,10 @@ struct mdb_txn_safe static void wait_no_active_txns(); static void allow_new_txns(); + mdb_threadinfo* m_tinfo; MDB_txn* m_txn; bool m_batch_txn = false; + bool m_check; static std::atomic num_active_txns; // could use a mutex here, but this should be sufficient. From e4c2e9e5e0d5169c44ae7ac7f1f1fd8ec3676523 Mon Sep 17 00:00:00 2001 From: Riccardo Spagni Date: Wed, 16 Mar 2016 21:59:21 +0200 Subject: [PATCH 02/47] baked-in block headers now go all the way up to 1 million. 1 MILLION --- src/blocks/checkpoints.dat | Bin 31824036 -> 32000004 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/blocks/checkpoints.dat b/src/blocks/checkpoints.dat index a9085c2bc10ef0d5c95d77c5ec027b178443ec77..8a6bed31a901b41317c9ab3e08848a789caee348 100644 GIT binary patch delta 179053 zcmWjE^*f&p0|oH8HB&P&rVZoK-QC@7m^RJS_}aKlcT9IrH`5){-Q7%gy!ZRV=bV4w zT-V91Ou!0=%EAIf#J&R{00#n4!h-+;2q1z05(prJ01618f&dx_po73`5WoNdOc1~V z0c;S!0f9FlfC~a|K>!Z~@Iin81PDRk9S9JC05J%>2LTcgAO!(35FiHu3J{F1_2!q&;K)@CRzJP!o2-t&w0|+>RfD;HfgMbSNxPpKi2z&(r zcM$LZ0Z$O{0s(Ij_yz(#Am9rEFc9zqf$t#j0|fj*AOHkF5C{Z;pCAwf0>K~<0s^5R z5C#I_AP@lpksuHS0?{B40|K!i5C;PBAdmn8i6HO`1d>1?83a;5AQc4CKp-6iGC&{` z1hPON8w7GdAQuGkKp-Ck3P7L`1d2eQ7z9c{;5P`Af4!0fAl+=mUX%5EuY~ zK@b=MfngB%3j!k`FbV==ATSOB6Cf}N0#hI`4FWSDFbe{6ATSRC3m~uv0!tvU3<4`4 zunGceAg~Ss8z8U=0$U)k4FWqLunPivAg~Vt2Ow|=0!JWl3<4)0@DBt|LEsDo&OzV; z1TI0~3Iwh};06S4LEsJq?m^%I1Rg=)2?U-&;3XJ<3kTpJ|8IB*0t69)1VM(NKu{rQ z5Om0E2nGZbf(5~b;6UC$a3OCYco2LD0fZ3p4nhPWhP;Q6Ku95E5ON3wgc3ppp@w{b z&_HM*bdZk_dI$sLKL{g)3Bn9vfv`f@AnXth2q%OK!VTep@Iv??{15?%AVdfv3=x5d zLc}2A5DADRL<%Aek%7oUmqNDHJD(gtaVbU->GU65`_52P2;2kD0lKn5X0 zkYUJQ$OvQk1bs_C>Hke5}9b2l8R36~Xzd3?;vV;2O*Bsx}SxB&Pnn^kIEZy8t;X zf()1K$C7&5=|p10p5#`AE`vvcTzKj7duDX+s8rdCJ^hYFltPD^*%i4C6FfI5A%7EW z(dNwVgXHzYt>-c25CtU{z0jR0A-HlF{~2Y=#BejU!*tZCv{o`YDc$fsD}9miW%t|> zgVm)JrpdGDy}oG%nr}YEbXtkVvX;k4CfU{*GtFPsKZeH7kwPomRa%@nPEx2uj&Z3m zg&ns`^g?MAQ&WPdL;f3X$rjwi_-e?i!-R88d&wY213HgtFKRGtMJM_Xi@i~pf{i#ZqmZvS(% z5nbm>mt^Pn3M0+;Z<#pzn;1<#1jI_ptx>lJQsMMPC!tTSgj8?L*)iY6!p?s+sZU;F15r~7WG&9&?tEiYlpv^B|t8@$#-6l0X|C? z!wiisg^(qIElp7kXi&R0g7qp$5Bf^nt!i6ud&XNoxm&2hkw^N(JDv>u(uj*2y~22- z6^qHSr7@6-RhuJ#9&g}9%tPR*u)v3gSUn*9ZGP%7MndzVl!mMue9M{DMROF zvETh0Tm`5t;d;S}7V1GkheW5h^-x&GRwS~cq5p>}&u1v%ZdSlf1V%8@#uiV<`bzq_ zBGs@<=ltJ$#A=#5zpF6P1c6ZO=u$-bpN=n8w&i!-H43FpSr>&Tu_$pS4e4XMx_nE<~UKQ+nJRvnD=)m+%IvU;=>r*BWw zrP|1jovjQx{P<(|*J#}`2J4TuOZe?Su{5Wdq|uuRYtK~bF{*z*&2GM-_oVv+e-ng& zVJ;p{7SXKtD{BbNb*qFbr!fK%d2u$`W!pb<(&TxaIiL3llPUZA{7Cco} z`yelw_*%9D<&QvEsVL68o!!I5v-Hmv7Msk_^b#^YyyCC}BtIqY(A@J%Hs0EvU8f}7 zse^)$b0gn`_cQ$}a6()aId{oFMOIcky536^3hL>kF5F!cJICNhZq2pdpet3P~`?S^KGI5I_l50RAJ6tY zN9MY3o;RgXU-4QH7*0t|vwH5xr-*%vRH7dr4*f01Q_1J+1KeaS4F;O83+s~atP{_; zS?{%cn3 zwFBSZ{;A-`o#+sF;VN+v5SFN=6aPNf9Xa$0ZL^ufF$#u0<1D4QEyk{K{o@<%8{uXz zVySzcx?U5JiM?9fj8>EL^e!PA)h4;h=b6Fnt8i+0-=o2cSQ+ncL{Bp(x~8d9uncT^ z5AS&jF=eKV@!Ra!BOl?YTLm2y0aH~v-_lPz<0;7!c-x+G|D5jan&hOd22%s#l-kCk zv_8*q46DQX(@0Oh{SlN3;T)-`$5WW`^PoS?d-_{^je?M{bcstep#M@Ad|Q1Wm=^IJ8#;J&#Mz1BpKc zHtk8n1PX=K@2BYdxTvD7j~d^`&F8#grYPItSbcy==lJN$%2-t9L`*hp+5E!u?UH?- zs+Yr#hR4OI#*HV3mwskFKe<&EDpOZxBW;9-WhIeU&nx_rVP(OJ!!wnAjI3qONl6sa zq*^{`s>bhilL@*tL*J%MP?a=(7=0TU^SkpPx>d!tHz}xj_o5-D6wy~MYPnluUS)huJleNe% z;L0SXC=~kGZ$mvOX@*7cD!wb?rCk}%ZLz9zxv*(q;+Jg?{(ja1I{H6jT!xZ5BEtSN zmd{OTKXlfXOs6)w+g8I4ikwyu>>DwzZoy>Oflkx%-w2V4yq5YaM34n;ZcX*SptTYP z{T?hNlRx44B;ezUor&W~q8;a|#@-fP6YU80snRRy)kGRjnZX`>5GN+S+NBHn#xXIztSP3|mPx>7dXjyrrHA^~UE!w~V>tl~k$#*6+sC z(J#t-@_?zoW}BG3*k4fgM^X0&=swCu}-m~1p0mpD%*A6&HBez>}8c@ zuNCW2t8S?nS`AslEecL4WaysdwE{?q6*@|`u?+UqCKFFT9Xjh}3)}_Ao5U3E!rVzd zZI6^bs7EMRc^PU{@t&B|8C?<~rCJUcuKY%gs*pE0?X$)sjsD2c`bsux#aN#$;O+_z z>G^UjWyr#@!GUc@5h2&V_@}5yDJ4U9>p8ZFQ4>)Y+lG|EW@mi@P1yrO142Tu4tJxu zqIPGemYn5i-H*wT@#RKu@(Jw?wRt@FcrPAx9Wl_ z)Z`1uSLWa0kZw7!-IerljH4=q$+~Y9b;kSzsM&D&@I4oflq^YSnIG|Xk{TZJgS^+R zw`&z=pu-T-H|JRMONdK)VBNa zx45}GYi7ka!=na+U9Z0T3)qpeOOTnz=(MK*T1LN9fL;JkS-9^j`(92qUyGojF~Rju z@V>{2`^iDB~V6`Kzs0lP5;KLsF{3kq7*mq%7Ei^(};{=1w5J-5QJCYG_+%!>CvRsY$Hsrx+r zhi;j)K+*adZJ%Y^k}w5Ug*z7@%@wO-q+`;G#p$*bSi0U{{K4?pf;17N>&c6Ks41{OBXNIp6OD?X1GfkJGMWWXBMccw_d2>lUu#I@&W1(F`8d)l5yr z^lujKZgt|u_zG zG#j&LzSAe>drv^Vp0>9&@31{BmNe*)7y3AlW%7EV}C{)s-D0J~+e<<5iY-+0~p_FYE2;n(5f2B0Tt^OBPB` z78r=yEWF~MI&GGQ=uRFq-?OJQ?AcKL$=?5D8nVAl&Xr8+YncDlzL3HYpBBDcr692& zshUeR<*vRhsF%pNT|x4S{uz&-H*0cHMacUms_~Wy^Bs({_p^%+?Q|9`GbL?uD^8gB zftVn+*LSfVBfC$MJN1sb@T@&z0;Uyc>tv3z`0tEdH^>v%VGKS~z6oi)AgaVnJX$KI z&{GYnp+-+x&(T65V+Z?@D`|<^U9Z*=a`ZdRcLhdcB`fbwrhmP`Rg%YU_aFWtQ&*b7 z`K06Uc@Tun;rlc9S!8Rde!MY>M66q8mX&$wm;VCT9S|$<;u;b5tsSsjNo%nbyT)0e zXdm%}6!|jl9&p>4A6GH1fFYCgW_6DJChH=jxY^W9_!L2O;d)r{8br{O#i zr1TpUW!qX+^!+<0^ny71hLTv?FRu3@9>14dE=6Dm{qFA%LmOr>Sg}KPFL;_qM1K7F zBA@oT+J0=+gG|buI6yC0Y3Re92AO?yl+kgrseVyI)6a+KI~;j;9vS$yGn=#3^dCwT zDEEM;9u3PZhc{NL@s!bf)6bDAa{QS>C89L0k*WirU=)9hZr|-NV-wdad#|eax)dZ? z1>9*^Y4LEDbaS`1%*wQmP9{&|rI^`$h9)Om8VyNC5U~ubXORGtoRfp)d4cG^-(*df zPq+e-%eEuPX|!PMBu}<~>G!g0m#VbZ4~L>TXRmZ!(_46(C^fVMbIm`q3OpJeI^F5lT(YWXg?K99E@DdD9aD}I6CX1- z=OB&Y_F3`w%<-=#ZX$#(41NnBsA8A3_vEULpz)yknt)qQaqoJ<1a{## z>{xt(VAD#w~i-=G7j1y7gYnCxc$UNOf#C$#Va)lq`i8ZzW%69C9pK0hjQL zBz~q1BMjJMTb2rt;|DOve5=K3L{?4>fOT1-ekc8MFn6r>ZsW1UKf!d&d3=YN7rzjN z?gGO|WvroX;aw{~IWcv#mmAeB@&~4?f_$x!+M$V=stq0D!7O%)Wl;Q<;&{&W+nHbx z_<21w!nuvJ|CxbNqn2Oo4AVWnY@X$N42Rb(M9?&zZ7p!$(~Hq1zd zsoy?hG_A1?k0@4c$04^i?N*JaBF-mZ^NV=`aZSYem3y0{t0{?fk@4bXF2^FJ|J?w( zPDk!uQe=>Jm{B1W%etL={Pq@BG7^bg)5v0Orj7$F`?ubI4QYLHTrDdE8uuH?%+tk5 zaeuYvWj_s#VypCJj=67)(RZ_tPQw1Xj|uve8J>jQ{#bS1CS+vXZp0U;IN}=?$9s;C zu##ztm6EQ=b$_{8q__y(JUQ(i+Sf3y=Z+P| z{iTXWLy5cHNopcfrTmEZoZ}dp-$qm$JtjqB9M1S%QHbPn2A$%);2G>X|74q?1UO)F zPe0%Dc{7iUewlfXi@iJ5av080Do46>;l-pH-=bx6eavL=Me_^G{ghjRUv4qt@WC~u zx`c}M0{^dQ6S-7#w6U*Yc2lM>#>riuR{K|_JXre%N2PDP10*M}^X_mh%`QG`xBp4E z8m0eWU9jM#_gzca5>qRgJ={6yX)1#~eS-rbTc-&9ai_gmvH#eTJ>Vk9wyI#mqS6KSv$a;$K+gPHQY@mNss<4k%Vr4bL*45G)PHU=_ElZ0-1} zDLZZ%g5C6u_5tti;a2R(tmP&7>ZGFCzDvaowujZcLJ0EVIWqbjQYG`b>XsF57sFnH zw-N4b;qm3;BkDX?XiZQ;d2Od|nDc*w7eOdWS2P&Tho3(NeYQR=J<8j!oTbHBpZzL$ zs*uxBpZM$B^jXobrn*$c!xvZ|+#WAGs?-nkLw*g^uy8?Zo}8o9M3W1Qe(5rIxW~7e zDWNgasLnUd%aw;`KI=T`6Jgs&f(n(y?9NBG6jD|tQks8}sFhIXZEU*skSWX(KAsX5 zzhmll+vYi2M92zwEmQL;%Xsx)DtT192W2_>mDY;QW%~v@?xu- zb*X)2UYL0XcV=}@a%rMdOHaI}EeMU$NQhIBhBOBRs&}Pw4%zvZkQ{BF-*QdIkG?U( zEoz(A(zx2!UMl82KgBjyBVTFx5saS4`Cu5Hnfvys@mZ>vtLBQN6SiQ(En9=?xP0Eq zZRNxf^`-KhmJ^SL@+89P&&$7OPaP7xp4D+z^(%4AJVhcYXY2M@XOs*0+fkEy_YGX~ z1Yfe&RR3_J`P$JWMq<>u6T>u={@HM0yrt{Ux0mQ#fcFiNXup?F`w8M@x1=4zdgO{v ziItjice65eGnQWaTUgnalD1F22eDVW_8NOvLIDkD6vgp+J4bFOi$GLhA65faMVZK& z0r;Vu2)KoI%syq=EhUEV9QZs+jXryrM6r z-qbXc)iy}MX1?e3+j#3@iD^@}u(L4#cnuQ1sxL+as)cN*J($vBnw$PpaK>(guQgFe z7mhSa$lV_a6kNh6zFn01vI?YZG9|_M*t$#eM#rkdtDIz6)X4t#<5Pk)VVc9?L1M4O z`reVfot1o+<)P@^=uOiNr}=M-kp0He1oFoQ0_$A*A#t{wJC2#0qq+&ka6| zp=&JZ*DTqb4Mjl0pYKAyy)IU*8YAJm`b08*8|D^^X{fMIp~aR^86My#RA3URtefJi zs?h~Lc!|^L&n=&)jx~v`|4pW!pIe;QJ=A2G%f_|HJRrP>Ng*yS1wLHVdhj$#_?Tx< z%JT6&W}6ubh4p$fd}(oEwFv4`5GxoKwK3TBjV2AaRvCWZKXO;MLg4H2%&dJJ<@hbP z)nkjqa`rR|W3y8pd2(Vt`9G5&i_eahc>I}7uRsd}V8UA!c07_37cPpHFx^3-nJ*0*n$gEq)g z%F?=ZgL}L`xCLQ*0~_=Ct2dg3k85MCp;>vOld`38@1~QZ_?ORgKo$L0_h+3)PXE~a z8w3P%U}c1N+{v}0*v$KRPdTa{T#Wof*qP-|89M(|aM-G8H2;iOdgqDf;-}AGZkf`g z|03E+zC*0Wc!S3?;CHE^IkAYe5xYaIBsov?FK$YIfcERmMA)8Zaoq>qv{xA^+c75n zxU=}E5*MwC(pXGu1^MWjWZeyX^2}Cqk7gQ1fDquwy~}S z{9wc8eC}>!U;pw2+N=M&#rk(dRaxt#-NsOt5L|ALAEhEzq1kg@S$_swn*66~89C+r?=){N zUSPCBZF>*-jMIixiG8K{@$p`wO3ua!<^wQY3Uc_kcwmu2ad=CQrHPc1=|m+J(;UnB zA2YK$7Pm*Bl?lEj{O2Hc4Fo-VuLGv7lF!Qp^1sgqzcOeV$gCAn%kGf>cM9OjSl{tA zbh$HQ_R^CV+^CYNad{av@G2?wwarHxrO?Or(cv~F$|AfE{?|#ofiX_z-CD!@CE!2U zL=NIR%}XzSRU9ApBQr8Tm0v=OUTaPA#Q0xm?g54EY*jjJm9}a0Zwe#WS@{s!3 zd5!9{^0~s+)w}ZJTJQTq{t0KqExErgFiaN_*$c1dsCz!^c%QGthisA6gl(Cu_kPWL zNSW>2>T>KevOc5Phq@fkD!8@?U2Gw0FkQ)9DS^Mt!DU%WR5PYU4LKe+m9!~v!eZ+L8JkhVrqp-13t|G`ofWfOWFa%57W6Ma_qi2 z>WjnIsGY9JY*G?PES;!d^<+NnPO=_WD*a_lPZuV9Mjkm={hlesD}gkhGlOBs8QiM= z$8TD!t2o#uBF2}0?)_vq_YB#C?>cP#hyJ6=i*oCaj;IZ7!iBa?#F^=Lh+aosIB3sl z9}8KlZ4!T<6B`y|)e^ILR>w{^XH!P(o-GuV$hAJ)y~}FVU-$XxW2L;zQ-4-On*aD; zJifg7Z5m3TPtd>n-q2Z+G15t#>EExLuF`*f6z#7T>SA4y?LD%kvkM@sXX|8zc~w~q zHJ!^YQPNNkjhXeNZ^|MXpYgu2JVM;r>5M>nEnwtV>PQK5v2hUMHp{o{I!zwojr>}U zmwk>p>=a$vx4&>KqD2+76JfqRwfoM#f923q3d^&BkCyGP#;n-e98@7;M^d3>Rz3xO-xVQieg{!-LO*yy*x;ZGSMO1C7^Pe$qjj9*`4+=_D7G6uZ= zcvi+>`$Eulf3_=#Fw)CQ^ANPf|7Lt_b?z)svZOozKb25LrQ8QbznK}6axT0c+C<~q zwmeo{?-pZ0B-;+kbI*_Eo4AP_?$)-!73l_@NNn0|j#ZVgLAdJr1+`l9JiDtUq9nM3fY-ATWaaY#oUS^lF>i4>vc z8|hd#`bgnD*L&F0ScF5U~=a2J#;uBHk48Nb|`(o~Hhtfd-uRz!sv^r{A zwwZYU$JbV)-amv(2cL}_)>C;A5l6FCZ`W6+*Vp=OcCEC|pg&F**}Ura;OEC6zYOCKQPA)f*EZzM=vt!*Et7|ofAX6+11zic8IF5HX2STnIfJ@WJcaTJ{FrJu89t|{r8)gt>`sY} zDY?DWXr1Rtr=Xbo&=|ZYi3OLz@D)2YGk-*@O>E0iL@PXW?jf?gX#nZ*jlA&l-c2)A zhsb%RKD92jhsl-oFW8OwAD%FBE5-l>og8ULjU#ux0ypoK$!~}rI7f}9fmmys%7lUa zowM$?OLaL-kJa3ia&SD}(Z`eDaTpHNPRWy0^$F}~4d4hziK<fC5{nyP9o4(oPJdjFE`Om(r9kRubPrJH{<9ocu$zsUuRTMD&jA|FFHqoB~({tmJ zj&r5OI+5&LY~wme-t8h_mq|47yLKQm%Xf#xwKVBkicHW>%$=d_3C*OzfHU=tbRXKL zJ{i3ucgQ?3Ms(HP2G9udf2MoP%*PFd{JP9DmoyU!a(tJgyZigUbd! z{CCP!4SlA016nW~k~(H1`6!qIMV_{=Q&Kud2c@fVpf8t+|bvC zrN~o5)g9rpLKmwy~0A4C05$KN&fOW#ff zro?Il4wTDG0;C+y)9o}Lva z^n@zIu^EsV()jg1k2KE%A+Jm|ZzinRP4@Uc*k3g6fm|!N%nWl58N1(RKgk;EiEr#y z1@@lblvzk@YEM?zRIDBJo*7qOLWhc85FwM0c+K>F%cHP>nwtIPS!-AwI^ ze^1v(mS5q2Cl2)L6&I?>vlAVBT@&)M*3#QEZM|mjSIWeX<~VijYc&}Qst%X0OY)>y zO<)KMI)7anh~}u?YhEWc$3Dfm8)EBEPCYtZ4%!FYsK~Zx;~>4b30&vjaQVH<^q)rI zZ-dk3%fXn#^b@dVRT_?5x1YE@FgG1cDq0W{Mvpn?FHy~(nb6F$bI$H8U&yDs!^*Oo z%xj0+OK&2Cx3PHB{b!<~;HTn@45{Bs93P_CeV;}AUocmqwmI- z9UPyW3e-JC<>=+#5S#woE}N}$cYmXD|2kh*`tq?25iu6=?c8nM%V}bb`K82l%mIV9RA6Vq8gue zSo}J3jSVemJqu?rO&@Sp6z`$a}itp*~Ru&XKNFG4@HeKMx)c@Pl9tUVM8;k{YjJ5PYz~mBT=uir8hr9zi`IK8g58VCoP94t zeF**WUgcxVx|d|1TocugDMhLywnBYHRN*U&o(rzck?!RDt?oHO)>0E?Rhj^+3c&)b zmp|ht9~1>QsejXu;KF_AB*L$yQy)zHG|q+FTSy#!K_Ds_W!xt8i1qOdn*(M{?|-w2pq$mT@5g~6G1 zEtjtQmNtp$EM zWP&U4Rn?d=$M+;=dy(_zIAx0^UL~<3ew1I=_ys~@j;`^>*KpB9+Z#;e`Q3&VLtwbU z8)AMjUWIrT^l2>wuAH#+()M?O+$1{@!qv8!7c=ZsuG_BKaR#^3hNp6oU#f_fZnEB_ z#N}6T^mT5CrxkRqsGSkw{dsO}8I)cEp zQLu>QYG6N9)|&hUOC#z!#qvqeDo{6W>V=I{Rvpg1lksQS9TgGmwWZm<_=tKu?wy(x zI&10TpQu|Aj8$LDpmQe6t>N;9@sC%pzFD~DE4LqS-tm7Agl{+x$mGzjKzN{>sI-> zf#$@__I@{S#b;+>$mN}<5t>6t=gIW7&pZ6)Ubml`*yXHTunMN&z%4Yc-=F(QNj=#t z_4gkf5Djza%>ISH%F-IU!{1r&-8SIbt+Oe-%FlcI-b2|kx#a;pywK-uH;Kna(i~$B znpwLG!koD9gmRvxZ6X%2NZ=pfEJ%jR711p(+j7U?dMRioc1pccnQ|3wWU5^nH$ZtH zkt}4(-JfQo9QJNuFylM8;BM5s9Mu=S`pO}KrIll|M3$5FNrsaI%=yEi-Z0(NJ2LNU zdD{nmkNeC$x0`{gTa)wD;^l#o|Gw9bGo$70#Y{(pAWDL7$TzhA)-8uQ&xdAKh&Pns zTO4=E`j%{46S*V*)w9mSwd{{+XFoVMdt-Z>AQ60+&V%XgFbrlj%}>!VZb=L&@6MwQ z@FVS$w$up_5cLYhNID`i4@y=|E#c;2Fn4|qdh_#*S11vjvp;5#z(mF09babz34=Lh z%EcK%W$W!&>suLMx@Rp8AibZ(-?HI_GM~=)r zO=MnKVG6^}Bd*aup6r?YsJIk|e{`6ocev!J)=_9z`oip@2~;G7U!bBF-@fn;{>nZ_ zutMbC*~ji!FQ&u)5T;La^J8|kHp47=UH^5#e-6Z|*#Y-@mW+8q76ji3SK(>rm50ih zM+MTeP=&JxkX2>tFL?HN17*!~Fv5Q~ety!dG2K*x>0D@MYvl|r&etT)yXc*7iNgN* z<}G0>*ld;bT5~R*>hj-Lt1H{}7oFnbJt7BtWF&lV{!;@+uL}NMRv~7i_p^C+TR25P z84l;b`>7Drk}iOwrE#`SfTSFNJ;k9KeFYG$QEy*a{e6G#{mGjjF`)dM@UJPy1-!Jq z{RG$#^I4zLOdj9kv~0KzRr$LdPacVlNcA7Ep*%OS)DgQ)g7hOXFFDB|Bn%Gj#ZIJflS9| zmT!5E7-V~95^t$ny$KZ~bQ#`ypGx4D^u?0*zzlcx`%3sQ2zlX=_tzYTZ{;(k6fOoX zxKRZ(PD!wB2gsz*qUKCR^uqPmIKBsMV@WTFut}WDel*)8<1~GP%>G&71jMr_T6O-( zW+>2&5mUB*DS$nyM5IYK^9}9?EnAc9JY%-gWkqQF>pWlh=flP=n_iwL#v}Eblj}50 z4Oo(1e^66~Y}x705p+P+MsK}3d#5ytD8o*~-|RbHC!^>_06wD@^KTNPP?O;8C|91U zOhVK8AjZ*VT@;JRjSCAgZIg#_)dm=uT>sypVvDJM?M+_eNcP3ynl$QSn69ZFA44U##BZcdiKLx z-aZ$qPE3Ef5;|wJ$v;Z}_tTt|evt*P;DlT5aI5O&D>r$to}Hr_xY0gCP)J+jvf~6J zhX41y%w6Jam88RT`zHLudey$FeQ}SLr}i7hPsfx5U;i57t7k~czfM6%nx}U)X{fzh z%|tqXzn$vKqwdIMMjyE}{>NI>U$|XG?4T}rc?Dgo|8>7DDe>bS*w?SGE56a3Huv!_ zr;fb4lf(ffOL$WOaU`jSJ3Xyl=xai1*ukfXtj1Q}@BT%r{in4x{9#5MiIt&>UVAT! z`@K_pI>X1ewEwNK$rmC(txjGuVBNyGOvF#6>ELmYr)SqaX zu&@j+F^vj*rk}NpQa{6rIur-wQili$dlcd zNWfHN;P^972)ckG%}C(C_-$c5hPMmo(ed&LQR|`{U9O<|Pq9toPqT;41mA5?c48K# z21lRt<7*i75}O095NNv8{>t9=?7)hx_m}9+xyE5SLeEYVk;c`5fj^{3PmPgLc)fLM z?L^`E-x8e>X^XexWUNTg(;zxI48LO$HOFQ&Xv^OLX4OO}ZI zuLXTsgFXn}dbw8V&*aJo6?UE9sff^maavp1*!XVnYki?mb`w1A3*EU+R3Vs)24Rz! z6$%!8q?uPJ?vvK;gA^Xp($2?MEH{Tym;jinsC?FD}gpXd~M zU4H%7#As)3D*XZ~)tdhb%)CI_on#i0j^m!?Q7w^{J(btyn08P1WOPYFvMm*zO>kOM zeON#LfE8MYXPwITx}E3~T1zX;z({pM`p~!G56k(h_Sl}~1Vjqehpr{1#sAc9{hf!_ zz8zS--q;Qvxfhgaf)SGI{QgZs7BYo2(?O=m5s{X18r=FuN2xQj3eBm{o~D2Gi~`9H zYbf9}M|O*9P|Et7U0yBR5{lY7&q(!D;nWKgb|BW@-R#`!b=DS#Z7)tX7+sTa4>H3W zB0~?SI(S>_;bTH({5NT`BY&@tHf#^0Ouv{b$c>PQJm;y^eoq)>BFU-oSJ?`8#h$Qa zCp~t(BA#Gp-Y}UzFfyqG%$4vzz$8k|mkb%Xt7crff>qozPu>qzMWP-{f>axo-;=x(Nnljg{uU?BorbMVL*R)d zK@if+wKmIOSooOiBpI)((o=+msMbQ9b`ToXN=l_T$@p>`z?q8Ia#>8)Y@G@}ZH~R+ z;`m<`8-JBni@9xMCv2nLK;N}~*fK9AVi_5`=bQ_j_Dj4(#mthlCye$mR8z0v&Dpm& z@2K66Z-2Da;~Cuj%S`xl_kK7IJGk5{^oPd1ccjc)C9!cPx)IWwfv)inc3-m)RBe^4 zc8+6^%!I=bM)Yp&$C}-^HT>6JJxzMUM25*cN<{4~j2d1mxux@f4meUc7`8Q9gnM+y zEJ3l4m~t!^hSwE*zXM=PNa|^Y>ZMKtIeC9YdQ55sXcLCURqG~yz>KGit4Yf0l)MWH z=;7{uze4VmKU=d#?q+c=FVsVA3l(4SlEJE1ddlQXJ;Sw@II6#F`0+{cxG{sV#>&QJ zf%ed1Q!9Sm2*tYumGBETWxFBV_o&b|8MnV@iTb6Lkuv^(FJb?g!&JTve;QU9yB)_UqNZh9#M^4$^xYk_+HqRu5%1{oko;8@OY-Po zhPlJrvN)k{;;YrBE_>+hq$$B?M*Go~#+U9B@KGgviQr}gll)c82ej^DemAd#XDT-e z!G@UXk|V+J#kAkZhxfjFDrEd7d2*x|$A(1|5o@;(t6N|C;b4vm70*k$`8?*q_7AJ~ zs1K*A^1s%b7LRkJJ)_t$4QOIICUL%?Wxuz_eRS}ztnI=hrP2FG%{@hydO3RamRa?E zDW#A1F}9V*RRgtCpU>oK^{QW9eM9&uo&LK6A7MrwZJ%7eY|Qph1JF&vhR1=Z#CP?N zf5FTC!HbOM1r2OXqM-k$-G4)w=|u%DYb?&)Z#*QY((nBl9Fh3Dy0m{sOKW8Z+9ERk z;MHuoO5Ym|A_!n(yewEc-`6)sB-KtG(?(sE{xq|Vz&LF#yp`!S-N{`h3aK z85*4l#S;O?(F)q5zkKnPY0)$CFCib3@Tfw(|7aEP*m9(DEJdcfIISytrTKz$Z_~<) z7iB%Hl&V9P1iE)nG8V_29IsUCbtpKc-#VJpY*9zT0_h`|rY1j1xKSFQqsy~6VrpWe zz!{L_e2PXkjpK(w07D8&?u{L%i4pJSY_rHHDjS09L> zqH6b_=qJC}>E1`HdprYY6HGNy|9=X&QgA3|>&3OXo|QiW88^ ze(+I%J?}~$j@Gk2s>X+$EV2zW$M(y$Mxe!5qoo8VlTZu!7^|%cnn`7}k$c3vZLJdipm~b*O_+dZ~2(dKm2! zJXY|ehu&D=KQ>nNg!k`%>O2NcJP}Ew6|27&9_C~4*};E+S%Ez?-R@AD)xftIXJ$%I zqnk`)ws$_Y)1ONO<@N2QrF`^}OOm9^Z=1PmnEjzIftF_H$Z}^IAeDO_4o>!^h>0+P z%e8fHH)7Df?_S33R!}~LFoC^VSakX)c7y!9==vDMktq;Uv_GLF@pM(u-hDwZ2%=4V z1$Y)2T~vS6|MUR^6Y-yyPZa1+tZ?ICuGa4AXp2BGYAq{cq+~32C!xA0N8bd`Ep$wa zP;Ae34TN(%UBIIw+}9-j%$XfHunh2=d1Y#-8Hj{1LJZh3c8%v^A15)02=Ryn~*5PSNl z@ZcU$18Hc7W6KW}f#JVEB_ali6~HD+E#i{}PSW$q@K;Ut)}_Y4czrn)3vxr`XlSX- z2sogqVAMO5Yx_V&ITOk7V7e>n#l`Tr%72e9a;l6No*7ASE0<)>08Mn&~Ir>@-u5_XDCqTabMrsqB z|4?_c-@ZBRZxarf=RJac4;`VniiDckc)@gErh#Aj%$Wm#-Gql0?m<-rvRL#nZ1+^1 zjZjZ^ovrzax#&rK7h1f4T0?7n?UPZUvnd01{7ab+5I5KhP;)oIyFF}RX)9Ee6Zn6x z2vuV*2aIdl`Wf14B;S*8#W3!s_HYz+{z`^%`tEBj{`mChwb5T$h_;evl6e(TWFjRF9T*#2>JCj7ZKG16iAMEPYAf~ z;8p`s*w|7u!EpERGc=g z!gq}Vi&G0w=`lA#A3I$`Rm^`w!xEBuGC&J@Nie;^^(+(qn?-$GfuSBW1Td9m&iY38`-eQ3 zUS)(bQ=&6q@%Ebw6cJ;yaS7@0miEXykpb?F0||UqR3j;T*I@o#G!lQejaH`(oo3b* zwO|-;{#g5JODkCHzpOS)TcnC&4%+6Zdq#b445$Gb)p&6xhjMTBzl8S^O6i+VpLnJF zX(MioZRfUG;7{%FmI{C?#tsd{qo>)$Z#fmno^2Ef2D%+hJqH}%YXd_i|rR!lZ)b<{9&%f4I z*b0yt(ChS1SjS6ew4VCo)IY8wX>|I&{3IdB2&A2}Vh9_+2b6p7QXrY`WWkklnX7VT zZc)kqUS+7E4O)K=mX3H(M6*s`&$e}78k}kDBQdpA?j79Qpk9B*Z7V%l29%X0v@FD4 z0~g0|GTncEPO>}QyzVuHD_Gi9^76Dcwaa_~U_G={F>O2ZZQw(`j<#=lU_=PbByDui zkvuyZ?8t=-)+VmzEvoR%qc-85G5Os8*Fkp3Rem?*#t5=RsQw*ZXdua^B%8bgD6E~l z2^PZsRbef)aut6uSb;_<+cOfWM~qZr=gtqP)S6@xOouE8uYFUqvVulFf%+{mNXZm- zZP3#g{qDI^$I`kHtT2A8JtuLVbH=JDNy8`(fP>~Wf+(j3Q>y303g!2VNnrkKF1IUy zxp0ymRs>TpNw}l+*rD$g)m1+^L@ztK#T%9Ra>fMho8o_mXSYpSROvC`O4pZQ3Tw5- zZBmHP+79ipFPX-r2aS1Ub^LiS=b+=sEQ2d^V#7@15PfQW--<`@_IFo(UfDjQC*L&* zjK}4o3O0UaJWCD__KHoQ^PT#6f90B#V!lqo^&=aW-i%+F0(1M*S?xCbjf@D8K+%>5 zf2EbO)Xskso(wzLvfsgGGSp9fveu;^Em=mKBq9M-bFe-W2VXK%C-qj<M%m|FB@BQ)4x`ZM~(m#%>_%r>ufwEUg=I{wU=tz!u+WuDA)w zb)>Xas=V+LEnAnA;cYc{8ksP;Lo61e<--MqZyrmybmZ4FdzOoCMNR6Xr zO~%rPD4lG7XqH4mS`L8oSZ)tIzZmajrs5sj)Z;ONev@HpS zRv;^!4s1RV>6YCZO87l^DS|Jrc#lFDXsmw{07@%eJS}oXb6FULIFOs*4?>R035W;O zNYG>@J$jN`4;DK*03|}=TD6fq^`O?2Fz4NJxhOZ_<$bI&osPxF0JvjvhRB*8!hGHR zaBOJhU|njLdpkY(?qAbv1(jNPkr@b1kLGfQx+t zv?$aP7*RQX1akWM72gKGNID`xt~h^GdqVIA(3Rrg-0zJ9PG$dssIINF^L$ji+B}-s zS6A9iq%=Ep)cGmH$b<5a?h0kIRQGOWvQA2smZl+P`d7`vwn$qLk**hF-XEv1~ABBqcWBV{;@s#k7ARmCr63 zXf6k1vvqY)5>WYi61&j&yop@bo2Y%e{R7TFhw~`p?t2OwiA(M1!i{(0bLGH-GKon3 zeK-N)m+u3|2?}LNycdylRrIu3OFU?w{%UA?I~x}HP>8@Ou?%_LAoz7amiK6u&}=3q zn$9mUB9gd$=s4h_KyEdO^lZ?VjLo!0G4kGhV=(0o%O36iK|2>GPz5B(3=C}F;OrbzQD&y$ta zalatsnJxHd5kWu@^Mg(?%IblsNfVkN74k;2RUzj-?k86{OoB^kZsBS zPfIXnbMRTJq%AHdQvH9yv#Fx%Sc|6&6`yw1ga4$=vucw{5=F*zw2-w?pBxpLdTg@f zSc3F`IeQ+_sYePaWaf&gMnwH6jM7u9Qm@TD^|acy){bdK#f+#Ma?E~p@inPU%ptCT znUt!BM_e2-jChEZGmZEddC&eZSkGy>Z2cO$wkJ7t1qDKMhxvbkoDGM}eL0Mr4`Yjm z3JDg8lr@=x@)C6w@utG2I)<$!D%L6{P)G0>Vy|a>eb~eq_LKp1J9Tj|@Tw3EvsPYW z@!525<%q4kN9~BoXw8|2=^c$SugoHyU$@P=>?ndi z(Mwp#aZU9vIof|{llM!Q0q71s24=}Gs09|A$<~}7-Or|TD>bu@?8>&&qG-aHi%Bj1 z_*I8RrwEA$*+0yiE;QP)5YF3zcqKpyA8~yKgk#GoFjuC@U9bu5$02#H;9jYu^rQxL zkj!j^U7Z+V;5^;homrD96Hw*-YgXqUlwf=N~5c5I`qYXA>HO1rOvHn zgN;6g7q@?tz1+lBV|mv;V6mK~`6+$IJHSUD$Gcf2BH$g9N=dVfBLLSOlh{ zj@vZ21TEUpS^D~)nBf9n(*N>x0NY^Ftd3F2F0g;E!Q-$Zpiv7Ogaw`yGFaahR8-YoTL6EkHY^I5}3^CTg!d&;+ zvowEOKl9*&@EJ4clfXSiJHyqd!pXg2g|AAUZVv{dFn1noD1s`FnT`!x!XASUC?V+d zISAT<_kGn^Xd=>0^)&nx1Bx`1%)!QKQ)9qv`$r;1w9uhbtPS&`qF2E=#|;wlKKEp% zXXFb^dt;}cU|m-bEM#2SAu>QsfTm*hr!#-{W|tp3qn3hMw1T6$yX*jyWg0uR$k+hy zLhTAM|4>tSJgt!iIc1H;AGz{DHwnJ0*CQaSIQMVhHi3W}`CKuT0J1sKNG&4gQ$V$_ z`2c$8u?0>9+53$NAZX3uvm@zEe;g@+gs1JafPqof>w34;1B$p)r=eWxeZbtf_-TJZ zg8~zfDzd$dj{?J4*eGyJ@n^Gg$`-c-yh&Xy6ibp?KFkQ9GLeNJHNH8GFBtOkzdK0*}bH-}}h4KA<)C6tb# z9Z=EEgdD?gnBZGA7oY^Q%>{?Bcl}nKS~Rh@m%&2GDXUS;ggk-IcOB7g08%CH4B#Ut zFP96Duyb)L!k|)5g$sJB(Oo6xmg(-kx*w#}h{CkW^c#RfbtoNb-L){%D#= zAeKs`syakm=2nhN_Mq*bvn`53|7&QlTS3rJPdouB)5TjwW1UQrswOEE<(273W(AOa zYT*EehD`Gk@wUGmTHFCOlo07={3|ctkD$x%N(IY<7n+dWGX@>dfyAz$q?((B>YQhJ zrLd^O#Rpeh37@{DBrdbxVE}(P8xOpU=05BPXB;N(*w#m-j@GfoK7c2c0lfW2EYLdG-ciEjcb+J701uRsaIciDKH#_dYL$~q z!U&BLMR#rfb&PD19-y#!ukao1)+TFT#MgK-Uy4U8O%$r%exR%g^NN3m{SGE!z&u_c#aN4j&6DdeazJoXyyMn z-{G)MQFJH<>jDXJ68SJ&B6?C^>7ioM1o7LtcPhKVusqp7aOTZJXyhQyAg<3o%t?Mpm!6y!-w&~IkzeAUg;EG>b+>;EK0sv$#G1@W5J-ll8^M}4 zz|q?Z6DvPtk~=!eMN5b~LtpbNbzzEverz3?>N0w$>%u2PGDkUZ+7o+ueln8};s9v0f;VJAjQk$o#FCrMh(yKOyL z^GBAK!hL_=6c=tsaza)4YXRGv8izYR-|?H~wSA^K9GLa(vTg3RC5CCw`iZSN9C;8l zu)H%+fvgg=+S=2zLD&!)04*Jjk77fVF?}iH4TbRdbmMopc#-DtaR%m@d4($~n``!L zZ8LinE7WyYR89lRfN*!$CjO~@fru=(<&sdC#fg8nbKrr}$-lh2w0YPpQlY6C)GUPB z4QL}@4&aQhh0;D+cF~~dQDd(vqNKOy=*EaQ(3s|#LS5->%P>n;TeEUmxAmk06v~s) zO zbJ)B7Ws?G;B^zEZ(E6a0$=@WVRSS^a ztJAzbO_R7V)^>k#iTl3`08h!J-xzsosYyK;27$AHIf)~VTF4Xw$9UQMw`2DV}?ZAF< zv=4w9M$d$sQe^9URW(gMGDzMgM70yMOtL8?B_tS8XFp+Qh%mzdP} zHP{=}x|p+w=zcs$`05=h(EA9aYwSjvy-&Mh!b{g=e=lKi^f3+Ir*03i&r*q&r~Dqi zZXy(KJ7NC&qe0FU#<$sf3@FH^_E*cbUL-_c$DNFk5|C-JID>bSf~mh)D7k+D*5*qc zd7Zvqz~FW;lO7k7%pY96{bZA#-r#;#k)kNc8haz48W)u2iJBw`XeTf24u)m}wB4@t zH4pV>(rDfJLN@2Ku1u`4=lQ?8a`~{t*1hkfo784Wy{{poU>(fw-&n?69mRWLg_7eI zj&Cd2!;vlRzaUzs4G{7d4N89w|F%Qvlsr!4^4%`UtZO8;&9VV?*>6LCZI%u(u!LP0 z`$!$a)ODwAetR7}TtZdmj-a*ICj({iPAJNN;-)k|2t|9<4sa`8Uht#?n^==nFMKp) z{;7?a^vQ7jR+liC+0-9i*Wb;5V>_odHt0M$zmf)`7DObw}WrkrYSn8Ct1e4m3%g z3a3$OQ3nTVu*ubv>05tBq~2B(uNprix8;{%cafZAv)7NncDGEbA?ZEAlnr$fWb+Cx)XrP;?G z1m+lPFsBTSm)Wf}AXbd+L_~|B_BJgdduAWyhk-hUqi$9@z0ffLHMdG!Zw}(_$taHB zx3Y`s(V#OkfFkwk8PI_4jlYuiugDCrL24iZ++R0hXs%A1`)XlgS5GUpxIdfRulG2G zH|lCp5ogO2)0%%b^s|zNO;($}E7wGi){WA2(O~+hoj>ziwwP>yi^vWb^!()of_s}X z9(-31JkV$QG}pQ{4ph{v-`1PR79#9YUr=R~F=?9FHmrgyNROXYLr_vwevPOsh*K|# zGB~0_Up;nIdJtO~5;6DRm*c3+%@-Hb{~7Xz5S#DdGp>J{Zo^X{8RE`>q{syUmUcXJ zMqHA&2U-04!mh-G{!EiQAO6y`LPpS zj2MPhGZ%h~vwvI*{Y)?InN3{I|lU@%y&XL0~E&w(=n=5*LYsfR3|v2fRHf2E&QeumkDpanE(& zuxNiR;4xbMze%+IjVylAgcx$fbsLsND+gt4Qo}j`EclvEv%veg%c4RVVwUsAVSyJ#wjr9z-lx56=G*cE@QYRp2^+~Gv4Hyt6?VvMG+#Hvy!N?NyF z86?lQ#F)9*>q=+s@)AgqZC2m>j^2|EC^9eHdhTWLa6_o&1LV7nDOEGev1 zxS$P{nw_~ndS{u}^u{U<4C1YHIe-Udh%+lxVDo3=ZD(G;oMb}<3YKIG8 za2!Jm`#uTxr{n;GWp~P?Y2sY{|Kk>=ce(tFbEVFIJaz+TKLl&=yW}FsM{}M;9pCcG z)9!*%8HRs2=2DCS3ZfHTlYO2k!eLDJsp#KKmiBHDs%+M3D{8R-RFO)|nGSy`gArrE zFc-O?YHM;y$IJrl)UFd4`N|s_Y7|%?-7x)EvSLpw?jpPV0CxXSrk_8;KtH!a8^(*~ z)a;Oxs#4ABbC}?yz7RucgE%I=5450}s~tTJ1dX>0<2&;aJn+HvzPc*|aQXJkVc&wz z5PUor3I4YIH*F*KyQmNSnqSZ9yM4F3@FZpJB)*l z)T>WN}zSW~@p!Wes+jA0aZD`ho&M-b!z|IJ%)^%#Gd-s={|WI<#)Lw`Sd% zCyu`+0t>%SodSlFI|g6Ys>8vddJQxFS7b7MUEfWEx{f6U{TVVXXfA($`<<_Z3E1!h zoy6$OC!iid>CGq;-hc^p7P4c-v2?5EHyN_NUKAPSPUVbYTis)j!~M+=LeP&G9BY0V zBV(aNPdPXJfcoB3n`+t+O2P4~L;z6cI^xd1DStMPK@-45_Gn*Ho3f<8S(z;h1~;JA ze^QLF^p3(py2r>|=^uY@!R~#Rqs?t*L#MQAsG~%0NhC{5o)8knsfn4c?PC{0?{xuf zkXb&bpAugbq-_sYQKpahZv`R`MUd6Z2~?^Zf8z=nwfw8oM$-s-=PX4HV4&_dzd}kW zJjNdkC3hGD2^zvj09~%JF@3csUHun`eXuh6IV*V-GjGO)(x8se!kt8BQUP+ zz9k@7?n8@Pv1ru~Mv#0K;6sZGHI-_4?Yyvb97H{H#8Yd{)wmK9N2nQ**s2Owud`PO zO>olVl@}mid)fJMgZ)f`JSMSwMIoYy zGsQJ9Bn69NACw*8e3ZvpzH(KC=RPtMJaUF3Pn*KxA!-}DQvWYiv7m5{#JWMKOHFUD ztuybHdfn0L)Leajf9WCK`Le|0&fQSd_-~NTz1C9AU|4@IJrXOve-#9tXqyoCBe9u4 zi27N)Tuv4nKWpsE59ZT$e9rNlu#4tv!k+EoUUgFAi;vf~o^U;?0ULj@r4#u%rgjgV z+VGPA2Ctc+CvJ2_5^dM&W-!C<0Rv|#!&UiJSwL3SG8-b{JaETvo8C)urwK71)?@$u zw{7Ws2OWP1U*&f~2xCjJgG-7ulbtjpQQD?5(0+&n7;&nSp2%NEGbN}F05IGfgQyK#MVopb=qGRwRfOu zwVe3hQNk(?KXyI1D?4fx78}jh=C*wzESfd?F_wSN-ErIk((8`1O+!*HUvntrXOByC zsf;t8@!OL^$c3)jZ^>KNAxPy`atbN={bP^jb9lP#2Ma|BKE;()Sw^{ZW4Dclu7-{D zjBh7|#;?g^rc`W|hDUgxZU?Bgoc>~I2A35*VlF@((dww>RQcKVatZfqz|R?fCsj0J zE!TfBtYJEf7+8ahbXDG$3!-m!pS`r?gPjN2Z2J6*)rNVCZkaeOO>i}aNq$cptOJd<2ND2?&dUejy}6vW7ykA$KxR4QOjjU zr)=Lw*2S9W$*v@Mi*ZM7l7XqvI%Gv~6(&5py3IAgHvStcU@9I-Q1_<@&JIP$bBI+J zIR0dATP&10HHfkx-{XVb;iOA#H-jK5Iqz}+M4@-3EgH@qT~t)1#k5X5?`UaVFKBe^0RTsWS7rie?)V2T zGMg%FyCZRaG@kKF*;*BTHed&n0a_sc_VtWk1V0$1LFlfeP;Ms984_Yu2h`hKPQbwe zhH;5x4|_Ue-@4=RFH-L4Bp2s`&?SE~2US9m>(-r15@H5l;~;czbq2>yIrB_94DZt} zhbvT?1ZRV`8++#-E9xkX;IX4Rc{oHEOmC{2v-#^;KOLGhw7`f8BYbPk$qRk*K>&Zt zroEefYWs$r{GQIuYL@FZgte`vWHdJGObAzZK1Qe!Oj+$A4>|pJRDf1+$uWPltCy)O zj!|Y#olbPJuI1Wu3PgE%o1C5*UMOHa2uLb1Iig5Y9ZamtTRUVM4Ng#rT?M0hmEF8x zX!5PHqsj~D)zquM!rv=qwC7FfMg4 z!##k6Hl5_k3H|XBIBqm%jJ|(_2EJ13BboM=XH?T9!~M^hSz_wn3YZ-N1)N{uj&uu< z&*IxHY-5DGW6&;J%Irk0TL60+4psAOV;dR>riZnJOjtk-RvuVY2d0ss*u8Y5W=MR~ z3I&wG1vv8T!3?EcdGaZ@MNA5Gm1(kbhBf(V5p`>r#U6hyJVv|dBS?R74J`fFVWIA3 z&_3kHjf*w&ejE|ax~)~vKl%RFtfY;Vznv%{7x?m=Ry)=tumxTwHtj2MLOx>q-smKs z-G17Ez*dff8@fM;JgbW|Qg$-|7nc{fSL$PT1Mo0;TzlRaCP99#$I_ubMsz}hO9?kM z=vBJ;95?f0?-@cqfrfwanz)=x)q@{I4SHi`#_LQu!)>kNuRTffu3!Yr!X(LUY5smX}Ul5%mIeBu@arkF{LNvZGoZFd| zmNaErWj;KL%7~_5za@qLyxTPrd4X?2n)o1nZ6Uz`n+Bp*2Y`R+<)V$`zK&7^jkRIF zEQ}UaLG~?J|HPmH-UsWva z5*}s{B(z?IN1RrmgsEoR ze6r#h(*|r+SNx(*h)wl79`tySX5d@4wj8w#<78Dc(7M(3hD&bh@f7lc2)ctZeLUy6 z6XLd z$O=ecwnVx)>IgFsnW4K3b6g?CAX+0p4WWByjB#;UD8H`E-4dDuFD5xb6C@oBO-xv&Z-ZrlZ{)j?(9q+ktJKP|N3RQh zFou5;nN?Vv)7E;~Ko?|-NBkPC+k+F#Y^8|B>*j5>;|nfu2eLmO+t&QQ(*<6!tgpbl zlI=Wmep+x( zwD`12vmQ`!~FKfVR`xrs|I>=@@9KxG|mv`(OY+Z z+nBU?RoEw0&bKyVh=8WbWdazSh~xSXSl1)rT>G4_fai@&(@G^7!jS2Uf*A$ms3BtF{sh`p(X6+znGYaQM3ZTB!I{6ojc5;3kM@5{ z(3Z*l3oe0WdA<^P(2FF5WH4SyE}hF!mU|7qK>K_I@S*11F?>Jq(|CoDFVJGZ9X~)j zm)vaiGZw$=%gQQS3!SZrmsjYd)LZ}<>a){Ic)?9a7L?Z*dxCS;K)@n7%qF5&8_^%; zXlQxCa)-XU@bVVAx>mAqvs7K7JDq=U=B}!}9A=ngB|GS>+|d~Uk?2&OgZu}c1A+)& z7MDY03@XURQkGMmZAN8P*l>g>Mdk{`D>?E)E-~4TYanuKnqdK zwbyy%o|Wn@QZc$zYKt^nYdJ9773)1oICU*g2iwQCcsNC#?+9MiFDu=*ZQOsG1<$_- z-n?`8mKDDsqznJ(0_tqNC~P=fvTP&W!Sb~3Sje1}xL!&e-OC2c)4 zeE(Deu|KjSz0tCcoz82|&9r|o4Ac64aCx!SEX|fA<-FDwro73FAF^tZv}?EAjIP?6 z+fe;fD`a@Zcx1cDW=;yPSdT;{ddgrfoh6m<~og|(HOENFM3 zV;WwU>zEN@?B)bG%zhkd9TVkx02l5IMCqZNIw8>AmOpju=W24mvpatp)gvuQ+#0dfB zL}wV$Vf1`ySqZNYx3zNxcWUz3P$Zw6H5uIz`$T9~>w)s7yLAP_!z;=7LUD9{22BuI z2lbZbY@FQ<0`dt&_-}s~Z}lExaPhvxWcQK*uQ4~#$Xd}Fj4VfCI?-7g^IL7jv%(G| zJoZO~-uk@zS#o^4h?k?`TT`V%h66Y5R-x&ZhdludG!HM|=Ot<>C7plGnl49W1$#cu zjt7}!ny3Spn3*v1y)!j-`4&j&Bjk^(UwsCFFQR=l;pM@ct%ZM^kvN+K0bIh(t5A7a zB9_)!+oTu67CH?acn5sXtF7M5G~bWu*cn5-u%9)`Ef(6Zv#gAIYPnus^kJXE;h$#M3krT-5QCax2 z4GeU1?okl?$IXB9j!|r)HXnK)UMFQ`bswk^?A0?PQX*10%2fuw&i=0GG-NfFb=_~iu?A{C|W<0FF9vx`R=>w|Qgg$Ken8A=70O*1W zD2o8}>6~=^-wN2l4azLR?~Y?HJ>VOnx>{cSdj ztObje3hsl0-g0kA^h1e68qA(_DpHV$#Q&im)S~Y73uPG?Mb(N-UE5 z(X)Trwk*y1EIDSY5K$de9sW+xCp!_iE@=6wYGa_C%jX04<)~)yb~Gd!`BiIMpP{Li zT<)9r)*QIf$zyz+f+-3!I@VcTZjoOH7sln+XF+jO8F$3}K8_V@mzs|S|K?>lEO;K{ z;v31ogCLeG2Fn4^8KcVHY-wuikem<|zzBbF7Lx`#R{Wa+q+cmo_vRrO$1@~?V+y)j zZ4}?8)UH%pME&8H`_D6M+bNTiWS0YM3^|SBH^Q!iqo$Hz-CH)|j`^0&sx(&ZhBrfVZ$TEwN&wqbF|2A7ZemQ`{Lp&+9f5>b3vwW8T_f-j?4gWurlxC zd8K5+vO}yfx`fh?>k$4sN;R`gR^d6@oA6!NmKo3d|s}Vo~SVSkWXi2koi2 zp%-QG`~^nPqU1+qSU+&~w(M5vV2*%HC^YDLmq|-kmV3hi`?2Y~2C=!lI1$RGAywf7 z(HZ8O>uj?ygxNac8;0S=Jj%o{{Xc)v2T9bPOdy$%nEPbbDE=<`ZXhEqj zyx=hKlWc3pVf^Fy+(evTES1p7@SE_Zd}H!EGX{G-0jMM>EDT|z5{EMr&OT&UTNH*c z-I(lK_N~7w7W!&-wJ9D6L*Ai9DtPV;g%gc&dGxvA9-Sw5ostLc9NQ=3r{0M*Xbup$ z260HJy&BKwOB5dCE%Mrh&t`v5+bLE^x0`!UghH2NBp*JJF{5KcrqV`%#PdodVrWd~ zuu+SgJt;f!&kwtvj?fYYIutnSrg%L7*BnC()bn&Cr_QqA-{{i7;ABzuQE~^$4AMA~ ztTb$!<4k-NqR}^S&VdM(eoI3b#jc>C+*|*OG14>3cNK~Kz^oC8A>4oS{c(k)32!6b z_b#oXER)5oS~2?;;w-EuOg;OrT5O(Y(ogm3AS{0m`roWR$hZDk%YB}h4uV}L4E%hm zJD{l@}}70`u`eyH%^T984f)@HXR_^h0K5?!(3-md>`8JX zR7^HPt0s>jw<2A>J2b}3FCh7)>9!vJRb+$t^yAumEXc`zp>7#UKlE09M-FzpbU(SZ ziLk@P?rz>xB`f%Kv0!dW1$gABhEeM&58?z?8%QBQ{bPR~WAj}1?xIX6429VZ*$`?I z6}D2-qag(V2Gh{*2uWIFX&|X@zdua+9Nf%CiiRpQOWDz8xfu7#2=YBY$C=zw-O+nc zM^kl34h-s-tI)1SJ*9BWt89Q=Fw<*_bkF9a@4=bZ@}Ps71eh#z8<8e$d7G21P88k& z33pt8A0bO<~&mR3WKnB5u!Ev>dnX08OB&T)_k- z==9YLxd6*?+-@yI?3%F*VSEXxNJ;{0Mv%1E?VAlUzgOmCg^IUwd@a@EmLgsIf4bmr zz1kx5s_ypF{Ef&ms(P?ToW(!e#Fg#@a7d{iM=XE!iqo&1#QAgirDLt%G%kCa`lm3P zWMj|WE{1=8$Ty4H$lnzzzIS8q+E20rduIJ-G^5z9b9!}b% zIyhOBfXi?p!=LKp27K=-N&w+nQ11Iz5wsmyIyCiA^q&QZ|>>-xKTOw;u{aWZA$k{X&tbZmvYNM z7<$i-$ZG(<*Sv#9*DPEBoV2iNJ&Thn6loUDkL;r6KdsGV^$?jc90hq#YlVKiewnDAZyIPzw`_5Q0jlv zpykXR?Hlif>9KAApI-laW3hc=fGg3k0E9JHjK7RJBfh1yA^$k~Ew~l`1%W@ieIM^S zh8o{7?9&s7iqdGE1oCPmnB8w@DU#_H-vRGv44dxgM~f{ zZI6`k1BS9{9EU}&#T3HMJ`XE1#SE+-@aLuZQgLIdksab)_Ac=A7U%e5|h0mGa z^6z}`JN(o6$7v;=#wKp6GZOuKc7{6Ns#>CDMsGAh`rAR$f*9OTefNJOi;{px6Tgwk z&-A_Fi?eL=24+1y?ugcrTyDNT=6Bn}m*@)2Qz*%?Z z;&38WAbCtyz$=J+J9h@TfpEWUr!RS5wqtU9xoL%?IC69hDKG>~YAdWdl5;8J)) zEN2JLU1J&|^4*w~rV@YToFiK#0(BU3ps#uCm_DS`nD#y$uQ}QN`olbo7{b73qc-^( zv*5=4yA#%kNWrQ5-OJz9tRPtUHzHDVk-4oF58KPG2?E&!Dq`9k%zkZRCm|$13M=RK zu$-YuOc2ps6mB;7>sSeW(NhJF00R7hIhSfkeGCA8OgPqWqq%=w)-#!B9CZq2;5J2k zuh7Atz7+=2(>=i+5O}*Tr@6QF<{exY>8`VC`>_+~ZbxDwolcW|tU*aL4k~%)q@G8S znx@W;fkVyZ5R%-hnjcQB*v>OaT%*2Ie!*yGSTi>^>4pX`TwMgwWgNY^LAd{qJwW=i z+Zh?$nl4@Z4rG789m~6`m$nEMughTUD1d4|_9hiwaaSOH%h%oAb?9F|L7i1uH@cy7 zL$}rwk_oKQ!DcBIoH=kPua@AvZrd=XBzi2qu5AXb6fKJW{kU(A8!KMEk1#$qye~cp z42^*H$@)l$^-=dP;Q4?|r8 zj-OZ+-qOI3LS5ahOZ&o-8J#Eo`DK3P$m6J!&Rt2!X2mel^^Pn4;n_%Z#<3~u8F$2o zW@CFlBPP^jAuh7=Zj`8GGlbrp`Mm5t=`bi5043ZiFyGIUR0jL-VTeYF6nYlWHjoLV z&1ko4NOOOx!T>-5jAB&{IAY4((zWxfD$3a^^&#7by`Yc|Ehoj`cd)dCs|=ojRk*sF#I$ij{+lFh%aiztCUkNM=twb z$hxqx%KE^Mbs-rURZifpCiDitQIesDPJw;#Xn@9Tnk5Yx51##o7LeAuA2$9_G51d> zRpGLO@1a(oYFm2S%}y;r3iIXM08HO~;d9c=mf}WP6_HEB^k<2%DD2b^v^AO)Id;{Cewq8=%!FhYOkonBMA$It8tu-)@NuSxO)CmF44 zn~K8viR@};C$fSkw~#fNWwED&L;d&?-_iwVBAi-*o9@u1R<0$&iAZCe#TsXHGc9TC zCx;YFv%{)E*4yP8kR{hNE=;-Ak|CAehoo73Ba@Oj%b_9p-q&U0UCIC=6)ud#pmKkJ z*i)O?n4vIl+JBs1iD}9WBFqe7G_7v7Ej-e;dhNRX9!MMTpQvC=iLbttf5tC{Lx;hkrI!ny z_D~7b&q(kwyA5P%`7COBH5#W;6IOp0hZH(A*tv}@_m@d(o)|zlV0W6HTl|)hjz~mg zL~OKvb)uLm&UrK+iM$B5|MAK=dO0 zYC-VIVbeOFuHun!Neb~zb%8TDG50$)6^{fe`g-cE14J{A4PXA{y=zQD{_Z)}F?ItY!@SktoHL4Gle#fk`7p;P76bzc zyRsL_$oh;|-Skd%$xX8{Ir^iq`~gU^I&tsMEQQf?8fp_DKydWgoW8`?cxRrVQ{Rh_ zDorXF{QFYxU~a7EEhTf*A*_ES5+-prJ(Va6R3}?`UK{OhJ*8}7v42s$faIw}a`eK1 zVmAM+G88L-(?~|O#o%V50N8|`zDNqOw)&zObS)JWO-{jfpe!qrw8tVBd4 zCztpjL9 z@6FT@@*#Do z>)#?IbEQP0s20CPH4Bu6VixqhkHKxlJxhHfiC;&Y` z!oSD;swT7IZ+So*eqEt{^W;I&>o~UFOM(vf*C$}p6q(pf|{9ARXkb>48CukjVam`D2R=%I5t&X@ZyzAa*>YfHzUS z&GXNrCS!tpV?m+C=UGO|fYjIb;({WyZAajd?UT?2-q5Xx| zcAZ5DXo)o1j|gUg@X{V&JjsTt6;N{5A5(TP--UEgAy$$ntY`v_5gMbLZ9dj^2k4;w z5OIK-c1Ennh^Je^X1D6_$#aAi1F9bf-qC11Yomj`1Me({KYyamt=PNbab%0seqNs8 z5Waj4nLvS!!Sw-uC=Trl?u0{{_bTs$yU#=PA%5KLL==#}j%4-!Zw8f5kEAIetMa_u zMY(d~6j|!3SW~J0&Zo~34yrOluU6i%y0xjeeB2-|7u?gjaQ}~Punhnlp8Ji(P7JYK z?UZqb2__TP@MqE}NZFgi1gPRHLt^ZXBd3=iIob-YqDZ%Y_UIL7vez09H~PPuQMN!W zazeild@~Y_2NS9|m16G#Rfm7$%;z=Pg*7)4FhxgYKR9)BdoNfVb6$Wb)*|mffZ4lF z$g@QtRr`rSs0@G07%4f+)1TP>GlcO%M(i3ty2?9p$!zhMhh18*qu8}ab{`ex;eCOZ zy&B_HycH7;5k~baCU6SwZ8Uxk5V0|w|4(04^ zExCLcUZC&J%iyf{5d5xEg2q^N+D@k!<}F``{zOBbHW8!;4`k*4b48lnzfrF_HE;mv zYtvc&ehuDI$8(RXE1jwuAU4cEJD#*AcWC>f zz~boBXl4ka8ap|gW#2dsWBN_*?x2_1iS!kDiSpz~NxnxbNky>EU!6M!Nm62?53 z3z$>f)gf~p7L?^3X*>rN$?$!Z?FZ-fqx;~gJ-F=%P}-cMWeS{dig@=715VRk(}7D%-=9d&g%ky zlEBjJ$h}5kqW<)U)&IPNpYh|G_pTDCwDk{`lLr?*KAJXvf0F%MoQYk0>iui<;>fuNl6ArS+PT~H1 zGnoE2?E&JSgC}Wrb8+0IF@$L7(!BQuGRx~aIBV3X2IaeqHjfxlp{WDjoakL?lziew zFl@|~qlKQ>Z?8~d?_KUI(>(1zM4U}&?pvq=uCXA0RngTgSKJGbTMRl-oaCN>!eUidY=Es=G^F9PSAZPTWLNHGXy`)5 zlOA~J@Pi00YeUyL7X8!xyU3}TJHMxfy4f_VbBAhyC@G<16WGSQSDSg82TBXbBkA3U zkPJWpm}Y4L6soY~4`psuU`|?pV3uU%H~k|ogBtTnrAd0u^G=f+u^^*Xt;Ng7G+xse z?drAe_@`=@zT8!#k&8-YPNuDP)p_5NW1>D04pSePt2b1c9bjBTrGNBb%w4gLv{=9c zL<6T!4=O~ZXBvwp4`;{m2 z8J5WMa%q&Zi%xgmkZW3}iUSz-ocD6jW9G9I($btTj`2_=EEs#>)9Adt7W3S1If%DU zhgEoYD^;P=zyUsbVze!K;++L4@OiM~n38*-`)4iyYstA4{9p8ny^dAcJeODCCyl)vIxZBL=nUBc}GZ}G6p*K|@&vj#e26Gr73SuQFeNQtS zWYvS+VEay&0AlGH2BiE{gAZ>N4nfiNngo`DbLa|1vki?bOMlGU`txc#AC@X9bK-C9 z&zTA2PP-o78xl4%y&pkhI)imXMFS?*=>$zx!cPNg+s%&{{hDmhic$}~O)i`*DkAGP z*tZOy**0>u>tY&YVH912%)5F?u5`C7TDQ+ zivk%UuVb+n|4QpRdQ<=T<( zdk>Lc?J)mI*Eg}>S4B1;Onn#}54N8g`oZ>Cnvz~$n#1HeL*zsZIF)sl2uxRQs1dZ= zqM;!?1?tMHG9IAFG4XKL9I#01Y%$`kTJ(F$5X+=E(PHB&0ZL3#@7Crw_@24c&W3LZ}cOSL*7`)*VM@ zcn+;%k92&&=b;f<8*bdj7r)?%qc{P!t)}DaBz`I5af;cEDw%8C=AW0?e_!WJCGKAt zT@&1?lp9r^twD6vnUJ_=_>4&=SdVnm`M4=IL3g)*S~X`?ecM(d0Mu+HvI*uZ!R@qa z-{RN=?NJ973q>r+bFF>;(cN*wem_QTg(ffQFLRTcNqxrBYxJ?NmSJ*AfdDU;;h2C; zwP-27t6fOEBsVSoV#RZpj)KzVmL+o+t`d+k-$3cU%71;^gc*f~Li7gXKONlAdz=0BgD8Vj8J*t=>auE=JquRm@;QTyzhS4&YQl0@|L^Pxi|09O7 zw0{S#$&v71JR4AH1yp9m&ka}F)JSM^{u=Sfqk5|Z(R4zh3+uM3E^8{do~ZS$!VKVE z(}Bm+*{Vt4OmU%xlq1cA6b$zumyelWpf0MMB}0mW!@azf1HbsoR^krAto;0eZoeOY z&?za|6fgjC9s7%i!T&a0@Yw%w2|^F&i8O|91qc?hsz&uT?!-TTo2SX_3Co8E1t=q6KPzxBRws4 zF97UAR5vvky*RPpA6+utGycSLUl2oo-j?x-A62pKkI`~6B6&cc1TC2Da8 znY0Ry2j#K^Z0NC!tQ@<#nT-{bS9=rM^LmMM$i-s%3II|}KPB?T8Db^J4Ddq+&IXgd z@9B}k*rE{W=gp!Q7GLl9@^XHE?m+;5VGMls9!uv>@$=$7*`YFHGuJNURW!9Xox<21$ zD0qHiaBk7cG_?(>(uKzo$0Yv2iDu7WpyK|M_!Aijkui$7<5ZjMjL(tsY|~wu@hKuk zVPdFrC#`GbO64Ev-Y0tgpQNtDMBs1C2xNw;2nQPy4B1m_&lqz~RpG7)c!`6nxNc^G z7x}<-DuuQw>q0gn&oa<|zj);w8=5ro9zAe`UUY;VRc%kO6rn1Y0cNTcl?CJ{2bP0$ z?Qgokp0{jL6Mg?ow{N<^K??%^=Dg69VUVoDYq)nmw+W;NrlkU3qPd2cia8}WkJ$HJ zW;ZliApn35v!5Sc(USVw41f-h@^jipS9ol2P32Q45zD&Pjc$ps$3T z<0W2m$b;ct?g#khKbic8_So#C2^CC@1A^<{FzF-B@KAh#0lGp$63(KHv`d%1j+cc~ zAXH%VtWx$ZH#1Uyegngd9MvoPhlU4Tn9JbVyM}8h0lpa5tQrx_t%!!oi0M-LuwE$@ zhIBiL=DnPIDRsxd+PSSuUANx7cVFk&3wk70@&R*{iL}bMg8j99myYbjGW2#DEpftS zsy4D+kq<+%2x@AgDE6@g5@b%zxb4^FbBW^IBctEX=TqZhID70dw~~UsGAAzgjkAs`ZX^y&mqye*E8Ja5 zYoYLif!tjCj{<|#=rj#$*bXZAKgl9qimdY3H6G11cjh-nGfIanZw1C)2YMY`-Ka}& zKkYoLY3HMR|8D@y=HkAbV=x!~V?T-Lp1wj_WY z2T%r%HQ<(fYkKs;Wug|skGEaKJ^k||>l-#al-%j((DnO9XLR0oln6HnBUI%Tztqyf z*Zm*9VW6d$NLM(#MT_mL}%p2LR>Y{9mPL7f!!;Z%*Z+&fO_REavh-eAEnVimeR3 z7HWSp89AP&IF477AVwZvtOvf<=VASAQa<0H9l;7NDAl^!PhZS8 zMMMqNLCSIYJ%A}^tK~fy58*#iFkZN5;BMZ3i6&}N{!qmOTF`bFx9!DD%gG(uY5fO> ziw#7wIB?j6l{DZSS?nfi3$JmWV}XgKPc15c z$B6pqB>=Gq(^vONq*@?+83msq`QhNJ$!kg@5GZ62uQ(q#5Ftd1r=W+~m8Eq2IMRK~ zwlW7g;;;F7$Munf97apWHin))nRrmcWF&2Xw&&rIPbS+jI5KSs;%n7`ka9EW z<*RAf%+NNK8-12U1^L29cwA=I%mWl+R23 zIDOG?X{-|m<&beRegFfBe{&pTFmmSLkvlJp$gge946e!DNhMr0OvqspsO!A1xh2EJ zEsXkqo3(D((5rnS<7@DBJN(SI61Och+ZjydW4b6`7z1EIzl#d|wnBv!Sxr@cML1S# zXI%ENloz(oIUQ~1_DM(qkE}_$;Z%9#*m0JCNJeF7OMya&Z4+or)!)eO@IzD9JP5n2 zb^E9^>yTBxpe8tsQId}mGpgy%YR2uxO@o_rJOC$fm2@eFQG3e;aKp5=e(fJkv;_lJ zDy_^9b*$_>NG0{Uqdd|Q0Eer8`{X&)^aLwld74eE6CAhGQ^3XsMLfeGe?ZATm6_}C zSRQk3N&owVjYktp(oy^ zCRfqH)DD87N-0@`{6Cm~*1qJQiitIk*GVgv zIrj7Ap&$- zE?nJ;Z+OQ@Q@gB_DkABlc4^Dfpw~tI0V!m7vbg;r+csH{Y~1cFvy+Z}_L1@e>!@WU z_?JvWNO;mRmj_LM0cFzw@`O%{IUCLv!+?$d_bfgL*f~dYa!u+neF56?+PKUb({an8&z(|671XIKh50bZP_qTRVekw(ILv6D$u~Z z;PE3bM z7d*yE=&i#q`bCSbQOCRYrz-)EV8;k{6KAI@w(zTSUp)qPN?rFZFI)wH~QCoUSf3VO2d z5@4ILWW>vbx>-`U)$nRG4<^U10$)juKSIGEb&u3u__k;*4ZrA>_rX#S!1Vf?$muqy zdcd^Cqs1A@VK}BGpvG!8iug`+Qf{8*FL#+2))F#*|IYspQM!hnrs=wrdZ|s34{qpPev0{-Sni>?tJsG z6N67Z^er!ZF9YU~;y^*Z$}9;5o^@n%L3F+?{Un}zY4))&je)xzE(`5%IPC9SeOFCE2yP>Z+1Qn&CE=&g)9eL^9u@1L|P{F>|K z7Lp&bx3_WiXC1?>h$k!HW>G3v5Q@V->nVtgwPc3lqxWwMWKJ`J6yQwSIUDQAtS&v9 z3#g2tpN04AqvLEs#>H`Q_s?swzMho!Ab%P{lPnTH8WkKi3uUK*Kb{Z;d=Ex{H&&X! z1$E02Yu5m#a5(#q)1I~n2lY}~hh5jS<4fAaxd16~Beu=cirgE4xg0fJ({BR`Gbyt} zFnmbqid3KpY7>uH<5J?Ehl213VKq7Hf$#&db~A#Rh}{}x&ZcvmIGfk*S>B%sxZs)PPT=7F;+d7n+Cgr?V3uk+TwzsLdv=mbCL zjliQ#WXf%MMVm+W6gqt^$h{|A8(FHlNx@e6o!aA4$n4g zim=QW`ARBu&x8rI)F`!o(vL@(MhUBHTl@%H@t~%3tDQ!ve5OLsFtmVtOQCNME>Cfo zI=l$TlSd|80|N0(FG%G`7=wPrda07Uw`7EWp8uiZyl458<&V6b3A(ruMse|W#ZXmZ zc!@m$84BZq0hN%ojk5#oP_2g#4ZSF0p1WWqR8{tf15X7=B$ZF$jK$WQCaOoH%)!2P3_>Wn8A&>>6Wah*_RU=GZIc+oL-@nQn;wRam6|_u$A1||o{lBelDb-%{QI_(^ zP3Ttl<_T5AybHkC8Q?z$2&QHmEiS04Lj|9r0T=CK6(PAbeox6gf(*q{H^d8Su(Csr z^+c<#`DY(dmq^fp9)#FapG#Q5k8GoHF-I5R8*}@XzzyotKnQTEb|IKah9@-$1MYK% zOuySDl}r48NSE(d{%b@WopFB@hUDcG#LRov*G_G{7gVfX$Tk@%FQcAGt73|~L!S;P z51U3>wq)aLgEI?lwN*WV9WdBnKxYZnfR=Z2kO4@%y&QhfVw*q+nI(0};u6yOCTh&1 zut4LI#wT7DNrzOOLj$y#FTqHWLZ8a$>{-0;-7SqN-*1~=uToMQc%m>2 zMejrJ(5UD7jG5E}LG_%|YRgM=KxBva%E*a{1sI6I!dP)a9z{?ZwaxCo_<4f-;YB3u z-Nh9_F(DGdVa#06VbtJIkckPerr4yqEOWP@_IHE_izKD?@Ctu2xn7b^uu6^1w|46J z6>w93%LJ9CumEk)L-T5zSN!hOxz&;k>s$UQ$ITmGeH(Yo9W@b9N8M^q&Mx4(vT}6S z3{lT=VTr}!x$Qy4C<(KZOT`!BNa5u1LPL34>CA82V6+Zp2 z@^kX)9-gx`o}xHJg=6Ly6UZ>lejocfKmrMIe_9Gvi-p>D;M|}Yug4P5!Lhfw<2`DJ z!Ds&=JY2r6mf|L}=wll`OjuYa&a_Y+L318;t$||CfEqTPSVWeuz-GHGP9o&0^V`#Z zx1kR!+@ofCB)@XyVYp{dCVr#tz!HJY*R>)Q;mpdW5l^ZxXzBqbna#PAr_%W98qgJY zPKxNehP`TDA-2yX2D>b6e)cyZEu7da(kvS|zaYz93uuNzT=70n+y|Pbu9Z*@RfCpG zj40`S!Kp-(WAB-?JreW;h2`%L8-(?Ly1sx)*q%B+a>BuQQuYxNFe3hwV9y_XLI*2<#gbJ^ zkpESvTUZBHj?+iNZ8(XfF4zXzn(X%JXKVJhe~-^<08%u~7xTCzOsl(EtetX%1Jf|FszxOo)G+>*lwXy{f2jrgFNuZ)LZwlj(vkiJ!=SubzKBArvOK!K>v8eXVHMLC3&c-aP<#=T}E0$1e=w zW~zYqml9bsK=9AS-|s-iL5BR|-bD4FHX<;7(2iun+!E*77LNN<$H% z*6dfuzTNKq#^A1Kjivz5c{F%>wejpRioyHW;FcfnO7hSqPYRNMlOfZ8$BoJctWtra zIR+o1M(B2#!iMpCaW+rtTxOY8HfE76b6Tf0mIOjzr>AL?txw`Zbx~_y98J`gH#nb> z$t&!UxsP4?yq@~?w79d0pnHZB;WN0*crH)G)l)D$7Lcpk9=UK99lyC9 z#=4blmgQXIMj+fqItx~q7weB1#r@7XTz?u{CjC>E4lVISN3*b8e5K+;QX>ph_9Ec1 zII|b$d{<*E^JOM=clbzwj(YPXX~in|P?CtZgDHtQ_wyTnXS>)LE&~U6O67J%6&Lx4A%!+X+!bv0JK3rbB^=G~!F%7~l*4Jfi6nmILhw!^H;om;kUYhE$ zY99>Zy0f29?F+TIfut??ntQ)u3{C%gIXK>_KK8D8vc}KW^OAY%|7JTRUkFy zeH*2J<7kn??(*ns?5czCMxSV4q{`D(Ckbr`Hd zqdxtgUeFboAm&i@6RXv!(VrZ*U2WO7?6a+Z_7gG4zpXpEae1uNFxjV0VElCdVhas( zwG32woidjF=PVfq}Sa~GU@jBR~yoH>J#QBAS5;FEwmf^-`%jj8>^ zyMcw?Kd8c&m?JUG7?JG(=q?&$-pGEJE!T&I38*&5c4<9Ohd|?o`ZLygFEqUWPuq`w zUVth9@0GE)_4Xv_eH8o%y<=6I`pYKo_L`}l->n+{x&jgtox19oy7z`@MpZSjV;0v0 zcsVp+n3sp2wE(SKr|P%FpA~qW#}U6VGL!|#OSgGUL(zFWHsnh zK#dngQ38QHGSpM2Jba3G@jts(v&Uk8d{CR!1jX*^d@_JA1ok#yQX1pS?1yaougdfv z+sftsD-9xm-NGCA9om~75FujHIEOkjmmId*rLS8-fqnnm%Y6<+0OJa`D#<~oJ4?gJ zssMjwVlTu2_qg-Q+2g2%xfGYaLOp0V3u2dvD3K84$5$S6B>KasN-d@Dz7p+!<}z@) zuz&i)$_2xgm($-_+P_d7qD%6%KX^n)loCI-o2m<9_QUO3nHtW~-lBl@zfu+j0Ej$? zWHl8J@Ul~nslH;DZdhk7xn27hgbjcYyB49?43V&EG6PB7Em5IScszLUUz}acHv1y` z150Lt7d?D~HlP~JR!0FUFY=&&NqjQ+PgcR-Reil2MfRMoy zjM7U4Fos}^`(Zjby-Z^;0 zxvQDohZEXMD#iXF)*1X6=fWJjH5f4?6RLp&wOPd1LKhv>JOEN`8~*I3=gWhS>R66C zvXvwQ^8ZqSTcig&_kZw~YopZ@CsU8~G7V{&uh|yImd*R%UIQKZzpSp1<#0}o1)tt7 z_xAT%b#H*Zpaw@@ASg^PLJaMNGt%p&Bd?)mxEOWiO98dU-x>AZg=f`w^&4Jy!*sZ zZ2g%?-#ckdv#B?YoY;#U{|syoRCT?!j?#%M8PRDM_iWtrlCy3M)9I zN`FmKV_DoU>pYzPkO#9$5zRl^#KJJ1)1!6T%_BgYO5RiGojjNkMb8dCz7tIdBJzaO zx1A*-=O#mdFE6R0>INx0h!h`3LB;#q;BchY$C^PIevtca8CB$r>p%HY3G1&oV)G_u zP-uWK@L3DSUU~t4RWrC4a??<_l|y zv?N+O)Hxh&5pQt`N!HSljko2JV_hYK=VscbOx%ZEboGQ*(<~6l-?G*Zcn1}_wy#;X zIfM{c`_872WX>02|Gh`yk)1mZP>7-6;Px*S= zG$hO@LOgcI;XHZV(4L0GANj617j^&)06C4T-x_~Z(8qS*$e!xaxh~+PeOw|#(Dv0t z9#eysU<#UH|~PphZs{*zdVgHbftS?p;Vpe(+`cI(=Us&OQ>lIaW(7 z!XsoNV(gWF>BXEZmq^$K(lLR)a~g)DgkQ-Uu+oFuWVu90V45y?&{hJ{+kTTEj`v9D zbMSp8Y_zsfm=51q76gz$&?_(mL|X!HqKYM@F3I=Xv@AMpP=7^b?y9XTOtw!mnQRvA zjbU|hd|YhSBVb3n+1|=XhvF+-ZvPTRj<5Y{fjLrtm9PjP20KSi)|Au!M5UFhUJ4i_ zH}jJVdq2QBO*6RYG@~{|O#z3@PAft>i*QA7JVmpHNaxC{TUAtA*zWw2+^-`4+ph8ji1Ci<2D?y-ZYqTE&!AoI` zT&P!n0fwV~G$V<)UDcNdVMlKvky_owLKGxS$cEzC%??5-c+W1E8{q>JXd;S zkB?4K2Eos)rneJP#=wB6255tZRfHlMc4e>4P6YGac*uloWmW+%ICkKF^1)tdUR(^7 zg*pccRONl@OYM5KgNUdsqYQ)q^PD4zBg7Mbe|5mUjlUxrmAOXco_KplEe0liYglv0 zA=y61>D;;tx0T+moX&Y4RsbqF(mSiqE~5VMu9+gq~;&gF0&vXo(jLpg+=CU@LkA(`>Ta=z%zY z(^pB@>TJ+2kY8szu7<=?4deqG6#&hqXp`er8P6m;Lki6C_;x$pcK9jhpJ?w@acgf$ z+P}#ReM0<=I+uMrXTVJFxSSgj+Z{KNJ^!Cnf0vXuMb^_lq^7I_H{qlY29`zH%lKJ4 zAPn@WNo^jB05Z(ff+j*EpA25|ifey=4;@!iCzmZCgO0W@sebtAtEIZ$!=E`v>sTfj zO>?mg6-nPBgDq;_d-OeRQ-p0BIDqVjQti6GRcFmI44+TR^uapF+ynwx#+MN(7jxSs zs|BZ9R7|Y=49LLLyRFU^PS&4So?c}%TXqg`&>g|S21Coj6pp3de#2!0D4Ril)R4u+ zs>`x6FcqBfIhYO{cYGH=d)$Aww>l986^X7gjj#RcvgMJoy@-C_$rvoZ|mN6eDj<+vbOFFYFjj{Sh~gdUHV z71n*t&1diARPN6Lo643a!esD&N*AE2scJbXLVl>`KBRJ&+BBghK5`xrXVPmMXd~r< zwrp@F?A$f1bbWg#KzT;Yjyn%$Qj1KR1>7m~4#b!3#gDQczaXm9#yf!ca^_!5Kzqt& zoYu9Z&$69C6@JQ;$JaHpIw%DQyag#@)|fNfl|?rp`-xGYy%ILR8>TUTtm)s)I6m&6 zWMPVGMfh;TKtJ0FJFgqwk=5ak`fS@Px+AE)Az@x@@*mCvfb_vA>bvqW<0@<9Y?;0R z=(MK>@+zQ2ouma+gwVvl@9B7YolQzz;M*#Z>NK+ zomZH6ZhE<`rw1fz({u!XjDeYSjElI#cL1|&-6`4rSo=I6sQaJ&F0{@9f@7wj7xsES zd6>P__i-Y?20O1!b8IVHKTr_oqHf<3rHyY7~TYmr4% zp4t5AyN_HxH>^fZS6B99jGk4?Xv5zuBko!5{i2w1w2>1n%<8g#C^)UkIIw{SM@`F} zBaULjCZM(LCB}=X3i`r&0@^(L9vL^+r5gjCCCaPqAs>c4VazvhzbJ{FD9&~>=&7~= z-3Mu=^zZ{#2nJe#{6P6@A^0>8JrGQJpKFC!R_Iwx^j?Kikn#o-Oxz+!#5z& z)VNJ+>;t_z?udUt4>i4PLD=2{o>gWpAELhZN2XIYCgHY!O=Sd?uAiEOB%QxTljyk> zT~y~0TN)f3HLvXciz+5iR2ZA)4YoBCxXNZhgQAEnT?Ba^q)3Jy}M3U4ggj`W<>9< zejt+9IJY`~n>)+DoxMh3LJ|j;(GJgm+O?x{9s|sQwEx6&A0L9tD@SLEcJu^kp!xJf4~=Hj=p`l8=T z#<2~(R5++&|1j&U+9jR<{v|ul`+^C^rz{d<>BF*rFs>f`i3rBd!xS9H6)%c3)xUw@ zhDZMQcwRm9TL4-;6;n0glB_a|0V#Xn&-@)oPh@_iFw4GYiB(|c{Qd7K)n-l>I}HZW zLi?E3G7ZJ$Joe*@!i2xpi{+@i^69nM=1D2o$_$8HwE*UkKV7rS-I_;g#q_Mmz``^` zYT;;qvs}!^aBG%Lc?()FTD}?Sc|!FnY-dS-@UVX6)Te;3$hHw>HeOC@y4Pyj)f9dN zqA&(4SqL~p5K{UprF{=KpS7Z{f>$+Zw4kSfi`-jpqJDzr2%=8 z&zaQGz?CbbY633CmCsAeAKqcIC~`ySrIFizz&ki@25*0(rSaU0=B|y}DlD;8M743y zT(It;nOyQuLuyNnN(N5+0PEN0B@AD@-_5-3ua@5<8+V|o6)-$}FbOP$v~|`N^UzAr_tPR}Emns~Au-Uc@xup{11uUu|0mwM z4%g3|@}jMax%@iuMkACYMY4fj;$lC4%cgMD*3^TDIchCT6xb5kGU;b!nPx?T0kekY zKJul8R+RBX?mk%(&gM_E4(0Pe5i7}~-~u0e2c*ZLUhhu|BJ6F)-5)h&M$MrRLLWbd zhvx=nW{cpj=HO?&SNHEfU}96Ye@<_Tg>M%mb`q_k;JdKQsG5&0C8{txXwzALVaWZP zc3C6a_IB&3z;eif9qg_>+0X1Cjgi}nI@{q0#+tC-)#JB`p5^cB`Js}*m1K^d!=hJvx;#Isrs{b}6HucIPWHwputT{29q zTl$`l#742d1;~M*qKKAF{jDy49uadWLa8MsG<=v4rcVZ_If*HPJ{l`#&~5Yh|NWY2 zb?6FIh+S0j;?`($UAXZHGoM|YH|vfjSb&R@aIL;x4_2n%D_ylWrzwwVuj51-IRe^J zrCvHl=YC2$`KQl~I@ueItP~#A@Ak3SxSRQ*2YFSniZz+cG$qOiL33b#YvR=HigX*l zo3Wyf)b0&9aXnZ2A{6~>no3Ex+%#G!7dfb*A%!x|n9Y+C zu4j4)vz%#`#`#FhPC5tg}sKpQF{!r#G<=I{?SIfxF$7k_L>t5*9?v69b$9w%BmH^b^v z2gKFofUw>loCzg~?3ZWc(2b=?y+T`jMd>NZ91vurA*!UB#uj9Ij3&%oY^;x8`$cwU za6|^%1YNf=zMcq(GEu0bpYipb;)*Rc!$+K_E>QkiI7 zn7Ez{eecX2r5s}$DONxj3`5b2%=p4!(glZBKD}*M6CdDEm;duObZG*2A9QwOG&F906e;iNEDU}oUcOgv zzE)dYik}ICKVfL$MrYKJaTbt8;TT|+aD{R~)SSW_a!Lvf74rjIXt`|yA>N~Y7bDa@ zI^pTi3juH)z`AL`K;aY2vexr&_(Vsu?L`~5gebUwk;>fm=HjPmUMFm_KKp|WtYfY< z8IL=6)>)T~wY80XIg`UYw4{Zoz4;Th4%jm#M-tqBUl6ji71B74zRNCe<4>D`kMDV= z;)Zb9vJNR{nm5>fD!*g)z^#vLe*H?>%kf_*XxK!ALNSJ6ezHr*24H9yeYz z%(QoZ+HY@K`{6Z5sY{gxcV6}f$jpgCiT%RGy3qg70AfsvQw2gTO54NnTLn3g{Z(8h zcE3(!BNd1$L(POu8`Qg!g9=4~RSqr|AE=!RYlk+VCd{B(%B%_lboLy=Y5~S6b9KIX zd;)e;h??yd95Qr7IEQLnMA^paLMzC??pqXpqni8G2L(Q3()+Sxw`Vk#gJ^s18byyn zedUyr`Nn=Jjx*r$7Sj`P|H5DFF92?zA3k20?>{Z$Slp^qv0HV`*1~K2K&=F4UEF{F zkZ5B~Oe!aJZ$%0Nn7k>$f)w1~>e`Y`^-mE&M~e}`b9i0@MfD~H`*d}Lc0vqo#Kbd^WAZ`1#*a0w~JjWLTf z-X>nwF_`s_#30_&a7AxQtUa+%l2ikyzlPTA$I&oyXmSO^S(;b|$Zp7YbC$jBxEtEC zQ#`(4oDlS4hOTO5cR^69YbOc>$v@+N1HT0CYty^L5P&4jaT(~Kkz{ZH6(pWWjFF%Ij0hq2pR z2Ie*uG-~Dz78BB|o7i&(gvlKl;?F?8A_^tdY5?wiq;)9TSGQerdujE%ULS@Ol+8Aj z;9`-M6M5WLp%lw2_US8I@qHu~D=>7Ll1_SqMf*O+qZEa_N$*aaJjhi_X z>;e>+nJH}d@5mqE@@S>Rfs^bhdV1{k+}hBdvPpqOCg!CahZ1d~{=lAo77e6poQM@^ z=t~|57d;&YET@Zt=FBrlDd%&947Neh-K2v3T;h4|k3)<~EBC_mq;}8=!*b*aXw7lg7gV*{}GuKvjL@7SM=UV+PJixD{t1+S&5-NZqX$hu`dy9; z-_Z;Id5^+|9KXbDS@Y^1*W$1-hRu0u<)AiU;F z;QiTeL5mOEgHPakN~--1uCf4ItRoy2D2?@vR`SKuYVMPA7`@Tc{_tb+aJM+cl>HWS zwe}_@e$e_T5&JEF+hat}LU~O!AYYVAjIio03%DZsJN?lB2Q-XeP85j`2y~gI1`war zF1=`-?t@k-gb=WfKM)4a^L_IQH-GMpFrxD1B$&}(^cNG{jEZ=wX}q!rkTfnUug^0P zV39`GZBT;yDTRbTbGB;%)@BOde+!tp7I_M#%FxSoqq;_a2x~NLO<6HdMqTIoFH8GFA29 zpcU>}J^o}1gGm9-5g5mE88%k68m3heP&qSZfth{f;0;_4v(|{_cVvEg5+HzKA*@^V zuHDlNN_C@u_DF;X{vm!w(})7otoOP18HPL;y^G>uKh3R(uHH`>&nu%DYsPQ|aV}2) z0YaVaTqbEL=;hic&ZlBDy(g!ehbPTFyAVRai4Xh@wR))B-s`#eClQ@xwQ?}sNMeJ^p00j@?!ssRpn!xtWl zX=3MpQ2~c-H{Dgu%0Prl60GxgVXKxy-1b`N=mo^bu6I((g;~yi^!j6!2i7Okg;LtO7&WM3@ak4t19Ozmlwr@A<>t*i2F6{zK~9` z^`rHa^jxexh~C{R$#6yle{`(TbHTtfUbxDCZDKj@Gh+R>FS{ft z2;|{^ZX+IpPyhQpj^P`;lBe<{z(nu-$V0*I(+0Fx=kswys({n@k%OBPlYtKr5#8i} z38jE~wPbhYd?Mv~aHp8tKAT1UXBjfaeF2K-I|N9aPe&r5IVz+www9_Ex#BqmDl5z% zB%K^G`lx7Sx&J748(A9OcDFx7sl*$1TX! zt#%Cx&V8g+kNPwKVgiv&>(<-Vl0LTh+3i&Vo4J#Q=4wzf=?u(g1yJ5^kR6%j6x5ZE zs-sopd+f*cP&USeGFRWx487qTeghz8#tt;mo@z*|1`bJ$aSg2mW(}LmvTHp2R3+u0#}) z&j7(F%uo~6Y1oP+78!E$d`UzNLdcUQ+s1C3Ldc;82+*YMh`$u2{ACT@wwhim!7`(1 zQaOi93V3=8e2LAD-&@qdX#DpE*0$F{e{%DD5r`n5*cjl)ho6XZt>~wfl!sRxn?bzt zBf;(+FXAs(@Y|U>(Q;nhZJ5zsnkm@J&OxW~s;ROyc4fL=Fq{adH3iSrRsmKyeUm46 z459m*UnKcVQ0-v>$5?WN8fxsA)p!#yttYU&g-|Jb7+9CB)V1Z|&GIr=NFVFce>G^S zf#pTb*#Vf*_9wHDBeaVO651-oc;PXg`n`0gF8BuGJi;vs%~>SC6UEIz;!v}q%Q>Gm zZ}^DpV@HcvCh>>2m90V$7vS3XVv-h!YU3QBTjG#GD2eH*ZYDCP$krIL?zfd&V>v!| zLsbzx363z`ajV=X5PcawGQvrTe~2z}$!AKeoueRiW>30e`>Oicj^-*WxF*=LZAk{6 z8lS>UX?kX5%j2dOyM=wRkB^KYyOdF}G;r3xS2y?Rk??);T**iz zm{)2LCE9@3nFQ5pwn3=&w;!h))H)!f>o4dMx4hq@2ImjGrzHj@n*hlbKw@@@kySlcjA@aB$ z*;bVkyPr}H<+5tQ)E{I2e@4+QoC1UDCk}=4M%%1~ zSE5F2#GjCH@3OFe;a1UbDl^1=01fN!>)MBnGUr#x2vIroIw!45SwLp# zwP+*31%jkS;4YJ84ed-{YSGe5P$t_hb$U~gta#B|hb%6MXSJvce}j7sxa)o|O%o>V zqOZJYd;j<4HH^F$mwchvcrnLcll#shgeEGG9j+WUgjzOxL98E!I@;it%O4uWjE=_+ zfr^6ysVR;$5yp9yjcd{MLvH9TC+IvqR3PdyzO34i^fBwxNqYavycn7d&7xUidhp{l z8mw3vC*Ml-rZ(}Ge)I%V)t@@}$56_VQg|*ry$+w>w<0#Af7Y?fZCi;c@4Z!#B_$?- zPx179=GYvUj}lX>Pv(c5LnV3sa!dX8P6q=|X^M>Z4B8m!7aDrwHye`2MY?w0G<~w+ zN{2gF0I2TB9B*dzZ6;l!sh?kQRZuUzVBxQjC4l9QZ%+Wy6I(lSfzn+#=V!f~2a=pu zE7tiFTuWNCe=(aenX;iN$U4CZ>$b`Q+raw1)9H`_!JAv`rZH!}jO#Ap8W`vKl5IMl z?rfjPSSA&#LNnqq5wnZZA$02o9=aDH&!EQ!2O9>Mbf51eZYL7SDO0FCKu^1=X|LC$ zig)DIkSyrI@cCv5TkXS%I&-!|2L#)$;$A2R8vllhf2B5b;AiJ9zqfp25n3?_j?cjW zg6x|N_*G*#+n9w5Rvc8OfrIHH$F9J!3#s}&m1?oCmfRi@KMM#98annFJqYk)VMHe2 z$}&EW$o#0HN)%)H8z1ni%KVg;z}l5bGK!2s(_YAtmihi}Ect_LCgXnNZK;Agnr3>o zFyGLye^Oe=(PSFkEGZzFA#x<2o4A`VG^WyvB4v_oqbEt25+)Bx&4WKpDM59R&E0zT z{GaOHR1BY{TFC=L`hP`d$42ax}X*!Tzy&zWL8TRxjxXbD`=mm|;wUY-$KL-KD8Gh(TCVX&+ia0&?Iz&P|YC9GbSqW=_c;jCsX@NMY zRK!u{IN10R!bR)R2R{+9S~QNW+$`!-auY&NABp_sJoo$xcA{lng=fvyZaCYSQ4oIA za4~Wme=Z`22CndZMu1z-9`31ReSux${6N4s=7JBHEPax);+X zBeyv^&iO;_^-knc@=35eLeRu0P1Uo7bo3;$(`*LIlu12lb}UXLU|H9LjE(vaso&o| z@l^Id&t@4_7aG6QU)7dv>L2{zf1%qd_By%vS_9x#Wb5bBiI`=8L4&C0O;UlX!IVVT zV!Sj{;FWOlZ{>t)F)>Y&d4lq(vGLGZqyW{@x{7QRk7vTTGbwNYls&4k-=97)bS`#q zAYH*em}ATY^1v5ID5Yjn8~!2G|F5}Ak3CN=qxccu$ljL7m-lf8#?T*3BdjgrUjDPxkcW+N_H{Ak#1@Tm`v4!A`F(c;dKk z6={E<;zmX@vDN_{Jndl?hkz2Fgl<(-tHGu>nM)X*i()hhM|rz;vR{=qG>mHYr1lH@ zU%?nn^t#GBPTU9qOe0h=5AncxMnN8tINZ|l{OhVO_woX?2YubXe~3lHVtQ&WM(StE za`|29f)q4G?a%;SvdS>_#D4S;6H|?UE4=LVcs4dh(OJ6z(!<5micu~2hv}U3Op!Qtmwj{#45mG#k_$GVO8p8*m0Oo+Lh`HF-5}=Kzc7AwTE^9nwDS3^C|88cidf= zLt8k-5?fFv)91OYfBw{DVlgT24MLdp;9hhl<gO+m>n(tI>*^!MBcRUy_3Pqmkg zlvwh@Y6v_(9i6f6Ty=+RiYi9;FHt<3WnWi@Z2I`OX}$X@tPoo>V{~kbH2oe9*>7z> zDQVetpNAF*n#*CHxY-b{)VGsIk!2(4lij_AC=XE02ErzYe@HK~`_6l*n3|~>foUdB zU#Py}16M*zCJlkr)2$5}p)#M=t+eWD@bIXAAK2Pq6fTIk+x4HZI;{$HV2Hv>Sb8l= zix4YT#yO_y_Z!72++7{*FbK5Z31O)qCp<2)mb)Aqdn)OAGMUR}(Sav7CVt?aI(~jj zN)G$1!LhZ(f3K>4)1@bt+BZryO9sl*bh8lMA zV7^84;Y(X%%6YjT-g_bDxOoxM2a!N+7^jt{<|pBF%|4uwg@LMOmB@v(f8}Xru756h zc5%HQNkVpw@V6l{g}~whaCwmJl;P-3Ju;!I-sQmwG1T2cMsKxXu7;a37UK#Tu60*3D#ik-j!ECW8mWAZ z4#6eJ(FkquR+pOPKil>XfG-AU77W%!39D)XjYvf;Pyh3$9FOwajl6$hClegzj#@k0kpXx{gHEc0ekhS}aA z{ifiy*-mHK@<=ZV+CPyF!aH}#20zc$l&iiYT(gUPopc}* z@B*ToDZKgq&QbgQ-@=NsHycLcw2cn_79)UKYuCw+cum8~aSbF>7C1m~%Yu%*J;m1J=KBWFVgY*e6e;ma* zTG`QxhV;IEm7#4pAA%Q`Tw{1$Ly~Cd=Y7vfgArOwgXuFpuER+0yv7Aa$t0CoLe<3g z6(qWE{Q3>!otmr9^^OVUEW(LQE*m~<6uK-UUUaAz34&U^PoLIGog0Gxiz)$MZ96&? zQpn+&Nc&QLIYBP8eB4>?6RmA2e=~1?&*c`PuO{M*sL^jL?k6B+YFp7l=ITd9lh=s5 zbK@PnUFX>Kvi8^tsqjvUT=IplSez{)R>{{=TE>@}EbV_29H#pZ_~w!J2YWR4Ak0@h zBa6>N`kEUYMbnpE2G>C%?#dO0iYxkqcAhe-M}zM{-i5 zpVh+cf_!%lBSMGBvzNKLq*YBLOnF%pkbhylhUb&L=aZKcbA>@4pxQ~YoYbhS7U20- z?_|bt0&S88;?yU^=daq!^I1?Bi`Aj&)H)(_xUY&w?6y=dPS~xn0ex6k9=s zy$pYnWY044q0XtR)|u7ne@@1l^Dkp;Np2v|_eBy`)#UMCIki5b4u%475TPWvoHCH* z4WV5-jmNDvoD~0){Sg!jwvZf0XY)$h86S-)!|ro8%|dug!2$5X+baV@ ztGWN-4C&f*%D5@^kXQaZM2i(Twy8-VJCN%owV7gD+YPM;vbcpjkzD`E)(_p$=r1F? zWG4Ba_;(QT@qjlFb7Uf$0jWcEukrp3CB&fYsTpOY_Jl=#e=Ngs$o!&suTMf{qsE70 zL^M?NY2E$I5B)e#=F>eg>JE+ZRSIU9Osrb?75XhbkYNj=cW zql-4ke1Y3Q`48_e_;>?|H}lY>!Wjs}SeN}f9%G={rI*Ny*{kG6qI{iUc?F)g=7*w^ z;tQ`2@^a7}f0mX{3&tv7#=!XhOZ6^OPr;OCDX_w23K%=f%hP0&aW}}=ux(_olOvvB z`6P79#kr_OW``4;NUtihz8;t2AFXPev%SeAgDH~7y|>SZ-T-uUZdT`fH4t*z>EG;9 zncx}Kx#P1Vdy~wtWh|wS3lHRCZ9z|?>sFPGbHK_Fe~?@Fc1_$F{P%DZ=p<KaB?lJ3aCcj}L9a3?OVyQ3#TMskt~sm^{-#`DBQAAYvu*O#?juQx zGnj#WI$F<-Cl z%^;6Eqow@Y2mYGOx(P{JqJ;2n3&C6|7eQdo5sP`!{%erk>s$i(7Txg|JUut4IuH>! ze^4)Qdc;RBSW|(T3%A|ZNqAEo4XOEvY#tvP6r&lA+#C^RTXR1jA{%6v-V2nAOG1yE zY2{MQ+(Hl_FndI1>)!Ws3HSN+P&@1jI>N`%4@jHs3jr0CYUM(hsm0|ct-0vM?!ZK+ z3i&|I?-M7Y`Q5Xs|vFXqw_K^|?0Mh6Yd4|31OIhi>{P_Pt3^Dy}EU;aR z-Om~0fdVhAs~V^{wav_D#M?10@q%;f=!orD){6e}C+) zBxiiT&g+Nrwbrnabw(*#mN+6KWcO;dorj0rb-*$LzlTd|sNBf;*WbHpU0*iPKg4P~ zW(9W+np7JNN(y{KgG;f9lMHi5TNi9n`3_&@WVEL<338)7N4~;N?J0SeF1xFxvmgW~ z#$l^QF)&E;)=d21+ZRy?rl%(Ce|Zi}X9I%+QDL1i0Kh|L z47l(xM5zR@!}!YnaGaKp3_C8$SilZ@1&Yiy4Cpt^db!Y`L8ksXADLoeI4*&>st6~ow~G~_ zECnh1-;`oOcX>sS&$l=^r|uh&Z;@U=tgctY4)!g$e_R3~i@+44&Bw$Vc67=EyGkzs zOYeMf1e&+bO+gX4fQ8V zM2i_6Q9f!+EdeoM_W(iT|Mn?+m&XVkLkDvjSKc1ov;B*hlVo6}ONF@jZ)RjQT{BV- z{Z<^g#g-~cdVJBv=2dcEkGHpYwv(h9QcXgcce@MfxNAF2!>V0?xB6p)$q)Pc~Oe@Tb_AqAmcjA)EeV4ge-x7s& z;T`)P`lET^9IkLLZ-Kyjrj0xP`q7_5AG*wUXVKrc>s1Sf3aMjCjT)kuNjd4=GAAFSm#i4^LjZ zF_+y?EY~QKf^ZOG*HE``jp3I(J=%#@$}B5Ig=oGje>V4y2e@(d5=3ZqAQ4hEgz85-;i)yRgyR%aM$h^TA*N1khaE5i94msLmKC{;Tep&?UN{H+N34lKV@^fYvtlr^07x>YchWe{f|d zcpoYye>NRFQ|`hAls4Km_`6w08eJ1>;V3W~RJDg1{3f-Et)NTpJ8N?Gsw7{uQy+U> z#wUTVn1f!p(8ZM@zJN>hHWcw3y+B8TNBaI=8)d*1jQYkQqyr-#K7?9Ge{c4RHG$Gi z10yyX;!o;afb4ZaUa%2_(%k`n@Of4@Be_f2e^j~YqfR^Dw7Vz*q4SlySu5gB*p2cX zmmdUfTU#mST1OzyT$s(T!U!A`9X6?2KC#r%H=%w-GDjuj(8jY;y{NihF1yJq_51~r z7HpRyg7Tz7-N<2tlNnVTYAe|;wSi+f`>ayFp}(*gMY8m|s78lj1)A;$^!bQ-Pe>`7p~r;fx{o5Pes#}cRL(P0y}b1tZ--%Dvu=KBvHO{LF>c|p)M zPcd{}jKb3mxO^r9Cw-~=XS_9ud+z*5xJ*Xk6% zQ%YMd+OD?K<(f5tmG4w1jSvmq9IC3{INRX&vB)}t;Ikin2+9!6)ydV!{oMrS(N2Ht zi71Qv_w{%fwCmFS{xM&pX#K+!fAI75bKS7hKVH?~w2ahQ`WtX5+jQqtC z{~#7D>!f&*FE-hgXl{%uAj%$JoHqeZjM+x3Cl(kU(gi!25oXmU@8)&nVri)>t!0^! z<%@q;Xte6r^L5;8b<|N!mq02GNy((!`A&i$^s+V@{0sHVm2nLsC-HNAe?d(8rHY>i zv^#=QFwbF-kk{Et>+$4=h`(C{=d`c79W7nF3h9SLUQu{r9*%Sjxxa*8(r)BXCw?M< zJo`iJ&}e;)AiiBf4oLZv+a19oP;m4Ty-d$tSjXbyR;RkH`@vLu=hcS2rBtDGxH{R6 zoKMXU@D!mh4jvsQQmq~7e~B!%;p`JSqVCyu&i7(_k%v#yC30QitvBEaS@V?=Or-h- zs8H9pQKU}(iZp8+TF;E+L zXu7o&md@Ok$eLF-HlT7nDO39+@tqRR`hGqB1vI%z|7yRL@RO62f0YQj4TRfaFx4NJ z1GWR?)lL1-)}H318X@vtlFdBS=?)9(w}tqo990FKo%%1?4-TE?G{UggmT^Bi;bc5p zE;8_c$0y{XhJ`gJ8N0_0?zl?)zKi5^#Y_xif9>?_6UYw~$HbUq%rIKb$pjxgHl|cO zW)pqUP6_YkyZiTYf2|eZDSISTz0+**HD1;5zRwbfM*_{pvk9dSh#>B1fAJyJIW}RJs_2uqmiBAo zE+w8BIiv2$4FT_3GslLwTT($`$9nm)T?J}N%SjzVB~7ryTbH-k--0?a&(UE6o=)!Y z*`9`oj4yeMaYcU=BmV~JT^#hfb7d;V>A&tY4Wq_y?5)_)L)OlnoK{P-s)oJkD400V zucbL8SOdCsf103j__P9V_KuB%f*K6F3v{}-br;w#ap)RW$1h^mBsJ0CrAudtB;kPy zatysl$4jv8Va#21yf^7W_p3P85R0R4-E`X_oty2649-5N{XnMok&;wn6YTRv-8!GT zmNwi~E8NQ&91ADypGMW?v(e?={)f0Md0o}{CW`AhM{1u`+C z`i9MssoHXu@z~tSSLTQ392wG|PhPy}5%$$R!NJhok%K`$bkD?Af_&Z4G z(|)#?e~hP|0cIB~@V1Ja?2IzBk#@*x<0fcQ+-?UfAe!ln8wMy#fLm3QMLPCQ`l191 z4Qt@B6u@NP9Yr3%cuqZ_=w~e}e-fov0to@iIBLoh4Q47QoRCo<0%o zXEPBf?VeLa(VG`cDa0l*xpOw@KFh_zFZG#S@A1N+@#2d`yY#~FTPJQ|xPGGQ$>@#E z5U7djkR&0XKfqIT>HkJkQS9MWO#0}{)Iu62Zn;#4A331l7w?Wu#R>U6FrY|JQhm-v ze_qL2uY*u8-s;*epWMN6nkM%PshOaydqKwzrcQbyywd(3foZ(>G`fW5<^H^jkG5pS z#;A$=Z26<|?qCR?)Dj^|LLij6)yI%tpRh&NP&j4!r!SHUPZUQ;nh>Y;WwH$+s0@hi zxx_=s7%bub`n{1hMUb}DJweQb(?8oKe+1$WT6m*avJqJ4(J&bZn&%_L_PVzw&k8sx z1!xRNQXIc6r)q>?dW-(trSY-v4h!@5&Qi3+<{wObes7O+ZPn^If*22u6e{2_we@?EG zC7J0KyKLMrJHGK}OgEW%hrqiQgSf)Fm!?qjvB#&n)+7P=2z>#2No2%$=W+%RgFE`E zywmeCLU+PRM|Z3HPyh=bfVnS<%A7im8Dc(E%qNZ(=V=JC}DFxBE)_D!)Jd@bC`t~)i z#ZjUPG=kH6og8#h2DnqXJ#pFTXRqgeL5i+!@1Zbg30Ih1IHs?z*ctWyokkVwsIK39 zcG#mo`O*#f4~EWL2c|)Xf1Rargj$N3VvRByBuV#{RNEKmqC@isu~$EmZG(K)JrYw~ z%-uCEvkdw+*MH-QA+OLhaw=3O0ZuhtaA%+$@JT$XD?LMzAs+dTl*hzx*#KR;5pywQ zAEzMGM`tm})S*fq+sFNJ-7-}4qQ)h{NYO*uaOeSGw@fTc=AIX%fBx_C2wb;%tX*av z>vfq+?NQtK`TG^sXWX?8K0k1)i zwJuokjby?b>l#Q7_-SVtv6W2g>&uJohdk*GWYVE?kPfREADP%*I4BJ05LB3w3($Af882j770JVw>C z_i4wfGM+z*ywr>RR+mj&hCQK-SBd_(HgD~uGpo`<79S4kZrkDs9H&?+hcv0^CQ2{w zLOt8P&!28j{DBh(_kY>2&%|-GMh+Sw@qG1-F5;-IFxkJI~ zZWlJL%KoQn@(vvl*$xj{rg~vQ>79B>jv2U2dNd;if8Eshl!l_5js3tRVN24R9z>i-R7J7#>cgzqn zWzjx=t*s>aB8g9|D~KTopAndYXXDwW$NyHA-?Pli2S+EEvq_3NOX?k$F1Lm9e%z1( zaj(mrfA?<{M87x#4}?&3{olIBb^sld+LEbOXe|}i;z{mhIKSCsT8^iYtn2IT>ES*W zz31=n19GkzTdg#)2knNm@|$Q6Ru6D(imj57&WfwPcYg)Z>P*~I^DvWV$G2tf5?T^2 z`zO)3se!;3>XVVfvq9yO><+dKsal?}I$cv4fAj7b5AJ%ge|mR*e6~d5NZLYCFiagx z14#Ersk~=0i?@21$}3Lt1+LW69Kaq!!j34dRVtWHczc-kL)aKmnM-I{I{18`4s4zN ztoo?XRrHsFJ76GB@ZBIsWn?YxN64r*y3+&SP2ivFYV ze~Pj&#MOQtTr0bxG@5dU`uI}k;pNvif7ZGXtGqc*%{^Z59C!Dd7Q2tBClmafT8gKIE9L7 zw{B&~F2Lt~98u%rzNgZc1X4>2D;TO_o_-)CfU{dLj91ck4;|vB$sSR%Mjn*bte~5T z2Am8w-0RfkX;yt8Ygq>>9Jjm^e`kGw*`QJ|OT2RzURu~VdQ*LjI28wqWJ=PWS-nw@%z1iCDBflQj-+?7dr(q<~Rx4o9aE&cIa{4Wtx6Qom0C zL##*99BZW+<{e%t5Zw#ge@j~DScIaYQgl$lyut7fU4P*wZb&1=d?+=_!QytUR-f2U zsDZ8rsD%&NN+q@`2p+k#8&4#?!=nZ`K@7Gcp66zhKjy!RD;azlF%c;%E~!_qt+QJN z3yP7k6ZKgXAJ_W@wog{q5iQn%eQu3NC|1-wYd~+o2Bip)EDM})f3(7nyr2zid2eST zDQ5m0%9}x?8Lp`aigm#B$urXatzaS}bAOM(mfL*r%cW?HeOQ;Zhq0s#PA&#|`Noqh zywzZ)m8c%Z*QOggN#~;p%o9M94AO{*+6A`fW9dlOLFBQZ(-5s2rV9eUkpx%st50l# zSP)9ZVkzbloMWeYe~&P#r@uau*dCk`OO0=pST7?SbGlNDh6gFu`}9eraH6u+TfrZR zczTtfIrO&tg_35K#_w}*GcUmnc=SS^&fqnuTR!9ad7KR4q!~|K9I3gPM^i$`L(0Hu z?$+uW@}oI)xrD(vidq32A$0<&`V0NzjRjXkwe5D`R@tOQn(y(HTFYT(KZy$&}k!R^>v^Q>#K+@xZ zykMNtk-Uv%M0XO5_RnRxX~%EcEZu}UTcl4!gO6q01HB2+hIKZ>6lpTIcQRcUn}5ye zE3Ex0y`JM7e<&z3-ag&;gSLr_uuex`EPta5@a2CZn}&k51;$cT#ExD62m^nQW9Qv! zzLgInPIo7lvAwIkG60E#M%W6M|EOAjXOOIL7ep8MA>yzFR*=%W+0rkg*~cgNu*UU- z(wEtq42FCue4^=8d02b>&-&{xj)y}-$b+XP$>$a=f9H^2h`69+X+;J9>_P34rg25$ z1&>)Me|Qeu6yQkH;5fa-YXp69I!2B!^u#p6`y?w9TnhOY+G5>3Wo~Xbdzw zE+SOR(5{Waayv-dol^iZ*fZq*UB5~-EE1%X)5Y|zaR7@ullm$`YMY?U z%M*x{f6tIo$-Y@!yi(2^J;}}u^Exnlnhc!eEyYnh#up-cxS`8RXnUVrG$VcUSKieE zfT>&8GY4MwihNHZcpN3=Gxc!baSzqKVOJ6~=24RePId*Us}yOcJN@cs7ATxr!+i`U zdE78wV515Ig)}FN&d|XB-}>>bNybyFYJzjIf5`rITTlRS08SHj1JPJQmKn?hadS+$ zzq14zP+a4VfpMB!Kxr2fWmPhsi~VZV5#gyA`Pxm@PIFX~bJ_|kdLSl5Ei|yv3W0>I zIpIZ#M|aw>RuzOyrf2|1+*h~Pjd(ztz#?Z}GTl0-B?3S~pTMFnE^@s{XI*Dbs-C&? ze-wVzQTA|rNM?3RYroq=c$3wnUFp_7(-KHrXPCWZ=No5}ocU-CH{wXWind1kJ+Psp z2N4p;e3pm_XE`N`lCFt#1y6YpV)X3g{3~9y_2{-gnWyh~Ey7ofq%ycvSyB3rBarNv z9s<)+HY>AD?H@At;@&o>vtE#xlBBO_e`80b1E7m?hmsx`6)&X>6K4f1=a&z{GqU40 zC|Zg`U*K()Kc!19NT^vDe$8ml*;k)Nx^T3&lNut_i23gYzP_ajty_mfhmj`98_GZ6)*8C z@;4;YZ7$8OuoggJ4!Z@b9tB!fe~5+&vs=~mSHjBDtc+-Mmo{(xS8lC}2k!#lN~*LE z%q$fy!ok2fnl+Ozu_+Q1nzcZ<6p zH!g9t>i19Zu$PJ(9$7(lG~CCn{Khq)Z?9SjA3%Ft0|iD=Z*4jke}l6ye^*%*oH6`A ze<H?J_Ke^~jD)3u0Tr-llw8f@-8qvHA?gF&%egYJXr#=TUH8r*-m zQdYdo8v6v4R39Wg4Yw!d8wxN8W3kb6N z*`_`zirxnR;q%rHe^fc)R8a-T#f(z~+hqwTfdyA%C65OcEjbABksbsHK75(0>rjmW z&iPc+WGLUIX}|S#I}$0J+6B0iJr_tLAH+kv0spu~xSPe@)w^55CQW~d-N zl~za9(>fTS);X%L0IqrBz+c0@h7@bdd*9|;2$Zrldrt<`e>C=W4~m{h6B6^%s$a2p z)NlTmc$cfU0TEU0iqv5!E8b;UIyw0dw1OT756FT_Iqllm)_`b>$~3fP5aq4Uh8!uC z4H?418k>&H0@Snw>k0r?5vAw<1nv|LbG@0x?RzS8wOn|Y8uf$Ah!ukQ=Ep=(QE_%+ zN2I8E3Bo(Ce>T&IYNNXeEA%%NOzfW`0w?8w8k_621^aMepWE0{Wv_u`a&L{qUv&R8 z6aKLg_>2^un{j^iRUD{pS+;VB6c$1sTGl9%0$H@ z=L>-;_j=wM1n!6aH6%;(XMwYr6dxF{w9kjB+1_E#kt!Rx)myx7QCkm0O>Jec{U@IK znTiY(f8An#WD;RqU;0!h|K6!;6qa{N0^**h#hFN=W(_I>+?QB^u7tDTH}YNlPIE<@HC!ZUapqwz{k#A}^%3i%5&C z+f<-Sbg&egWNj4QnPqP`6gD@D#A^{le2o){npbF2fCC9kj+X@j*mRAAaWmey4Cl{Z ze+i~bIo9-=Y0IV6mdzPMGNThrMT3m4jWdA>8WF%S<-Fco#bth3QltQja;}IhV#4@@ zt5Y7{r|aFvypUUKXUo1+#Z?+}7)vL(Pnme&LXzoW=7 zFpg-wT_vfwi>sY4m$YlsJVtxyQ~+~Wf1;F%iE2UqF}39rS7bxzYNo0TV1IhSwd@1j za>{S#V4k%wesy>xVU#uWvx)QgQ6`PkN;Et3{z6PISNrmMr@&V3Q=X;zEr4|{5^~(b zPGPVn2dz|Z1`H)#jYv-e%^R)%f0rpvn#otphLx<_SMy;21G+C*CyXsVg?p`R(?U!WC zw&uBY_I))QE~B&fzF-v=bOx)Me?n^1+`}VT;_8hAh~w8h#04)?zrh$9n+? z8%mRh+now}9*jm4{wfANZuoD=X9CA(U66pK8fjNUMI(3=I&l9q00@o}e`vT~Y!|k2 zHy3)9gLTf8I(>0HfF`XT&M}|OS_Si2P0VOD=>JoBq`*p0n5qjmc?koIg3-R+w#dCg zuA%(osG42eJ8KZZm||uByG*RT*rXRtj)e6TDiT3Hy=YkK>`c3+gvJHFRpcyTWMyImHH7MQBLRH^X7u;*zE& zLJ~;$k*kz8okFQZD}7gac5^&-ac=vVdJVjc!9MQ-qO6h`=voh6fA8t2)Tn*Jn-6S| zq6t@U1SB?nWq!dfwfOz8Ej$5*8=xXMz9nH3ef0$qmD zUZc9V!<`FL=1v%~f8>I_SKdunY?Nm+W}wOr^u3v0juwIsl`HTmM``nd(VRjE6fY>i zQEk~*MC#{Kl#bS7C@`{Cq;JNBahfO#1MgCmq1f3R6~Y9cab6gH@fznn&I(`nd3@FM z$v659r&{spXds*&lJGs2)0KI$7q7Q7Gk^y6xy@St@PUL8f9NzVDQ)Zb$R!&BEfQb+ zBVFzLQEd!srg$wW*g2-ydR%k)xj^KT@h<~+J#V$i;K#8ok#B^+rK9Fy% z?oZDOG1sBph{%>#>obPI%?z=)2N~{{M;<)?i`Kz7R@5E%SzT}mamBy*&*E9GBhW%S zi+>*}dKb(ee@F;;z&zWukt6kAShxM=?w6?V->Bu~DO4T#0=u9{{SimE?k4Uz(sjvE z>7UO4FteP7X#Jf9L}uqAMH{Xb9V+x1#kjO7J(gVd+KFuD)HbPmy!POL4 z)JjKY+02g@Epe@H>brTEa%jmJUobxQRYWmf!+a~f_RQPbLEj)@Xh8dFlguoOucLq= zEhLHZ!U2aix1a~pt4D3AN*dSgUP_qjZof4wPIBQgm1a8TIhnAiArXhe@y$>w6xTWOHSOm^@7@>l$)pxq$yPWVe3Y7OfJCk8i5?cK49;A%m5^;Qhd+q$GyRc zrE3&O!YJaA9n9Us5agXm2A4D?X!<`N;v1cxjW}DDPBz1fXZne1_;gW`_}`dCHVd);xtc%Q>6flN|e@kknBOSctQdDJoM^? zYK;qs0i@<&m9CitZwDAxGj1QeQMopKe~8a7vY*l!<(X2(?DY0!e)Z&frDsOr+Ktm| z9SiB2v5a47O;1A9cKlirG(kp5`R5rBUEK~+JsBG^aplcr-x)l_wL5CB>4{d7L-c|5 z!E*I?DSFupnc3+W7=EHyqOzPt19c&n69-x^?2$pwYBIXKB{8U>fv}M3{&=>ie+Hqt zo5UtP@-9*e7Y$H+@xxAqTY$3T1^s8m@4Z_uvD?GITv4D5+}+k7V~a42LkwD70Q#o9 zt3-Ol4r=-YuxiK+SqTenZh2jGZ)FC-1(+^V>Vr&u0g3S7U{J55JHoAb){I3%NDAow z3q|rUO%a1C6#wue5e}SKsNhq?0kM&sonDm+gMos+sgW-NkE`BVNuPXUt8ccPgEUA+Q!~&&)v_BJTC9drtOAs`e^KwxxqX&; z-rEfLn{58;T^(}mW&w<;2m#Uw8w&YR=>qW^hh|f4Wvy=lyScX0RHzg_e=(oKTR?Cx z%Nsp86Gn#I$t49yWWV+?Vj^Q1RWGQqb$_j_v-=he!E z8xvd97Ed^RT~%9oPi@IKE3|!RSjo@nDmeVv=sh4OYE9(`2NUUFRgZ z)Koy<5)f2M1HcT2kL*hPu&vxG<{}JxzMo?Gc`*lIPao5FCJb^Te<@CFyQbx4iLz-M z;I4WBW>&IMySAwG5)pq%GU?U27GM+N7zLAql;tzQm39b5%6M-h!Hp0>k`=tx@={Rl zQ1Y6bNlNC3)wlK%YObU5=wIT$ky2$646UOWH&%%FT@N*AZerrYnzoNXJr9d3au)5_ z7N&?FBkS*MVf15~f7bAguD$MA?Foz`%o+JRno~%oNkj&v;!HZkjOB0A1Ze9tblmIp zMOckg)4;6gulF|rfGHQ!A7pu_Gpbh49TAl?0EqMj>T1*d1_2CmO6E&r-9}Y=`5+hw zQ>~{NkEZfEO~^&hUTsS`Rk!RBB+J2KSn{$LC`O%ou`D_@e-!lDnbCzc$AiNB4^5^z z5`F|h{GY2t*i{F$Bi$*hwY4rm^9~UWxU@3rYQ8dLp zfNqIZ4i4wifB&m!P=ocfzky(Uv*21ZJ$G>X-G`KjzzM{%=h#rpDIMJ;GIg>ZyLXqJ zZTA5FWtA~blw(P`SHY|z`Q&=lnd8V@a1uGWL=$t29?8v@vQG%+wJMj}4*%wy*{pw& zvzY%ZtYRIZ6r5Rvj2?;_Yei~H@iMA*3=Pmq}EkIlAMIcZ)vR17O;^2bo z9ATS&f5!#IAuqt?HkHtrAr?MbcpDuz!Q?suH-()DTt4f&e8sQhB?nJTEdn_0X?W0+ z!X1|D9G;k&1*U3Buw>>_hbfyKrVhFkL_78XR6wi0<|w0aYVOIx5NsXY_^k>1w~Gc) zJ`q_AF<#Z2&zkM^tE37W&hhsTyj?^ta5#b%d%Obja^Drkjk!j@3G7pjGte>JM#F(GvVorqsvOy z3Q}3L-G550DsFq`*OFE`v1;AB7P5Z4ao>KaS@?L}#}OqV-KwAw-Q;j2Nnbg%)Qn3| z>4a}hE!`ZrW(34{*e9+8TT{R=lp!FwOOtknOdauJ5TX%YR#Y30bspth9{@z;bt7o3 zc53u6<9soZT`s(#%iyesonAXMv2kb4ajXJr&wtsxDE-8pXaZ(lUE&kVsYHA*OxL+q z*c2YtD!Zo&7c-(TS7A<-!+d$h$jEn!hL3@3O0WP3^e(Afu#I0`9Hrubvj}CTU)qwb z@{!}7s$cN0Ud}RdbDdVq??S&d9_rAqR*~i0df2XOl1E-I?_GZif?|E_(n)@YvarZO ze1A9^#A4Cl&d4%HfUuy0|Bv!5>!7RXT}jWqaud-PJbxL43pv3(1p*{xQ!5ObftX_^ zml-dso^XT4=>`6N`SgvECO&=DY?oYGvf`mf^(~JDTvY5GaPXh;9xG<(M1az*6Hf=h z-_NnF%VZRoNt@C1p%P4fpZ=IHT}PzQIDf{M!R3)ytZt_Rkk0qVO8>j8UqRp#%NS>S z8@<7CifqBTJ3?YSLF~jJGp%IHK;of+_hx=rs8Y*??zV%pQP1jOFb8Sv5>K>E%3TI2i347gF;YNEC~>7y;E894K~o1L?OPS34fkU zSs+as)n+YPf|(co8{O3oO|kOzOYcg|;K;lntw^*vjFHR#5vV(8a5s>NYHeG#oe`N; zAgn}fLaZ+1o0Opm41cAFmXfFhNn*?<-o%wX9 zWj;d(vz5#A&)j!}M9$7}^aQuPZGY4L8t)+*ABS?xp-t)2%Vp>f-qo1`#N}0dusb=c z@_bchwBs_@rXY_x-G4bU^R-|nIgK%{Ty#_J%FCJ_}%363etij3UA zx`WF02mYUzc9F!md31zl;D2Qle1Rg#<5oMZfpm8p3o6*hD?eJ=Mk#u9`nUY@R!;m2 z?!yCyCS*_xt(Tti=KWA5w2Nw#NZnUCS{)s%BWO1jPg2Xz0+*K|CkcC4gWHZtFwbnM zVmzv~5npoAM+eAcD!FlcFbt{86=5$5DRM4Pa(vXkb!oT;Pq-C!eSg6n_6uw8itXf> zXk1WrmqMYG3bUg*oRXSZV4Tmxt>|wSn^M@%j61(GJ9t>j(k9apy?vby6l%s)>bUf| zz47y6sNIDh)$HC%YnsPBpr_n?8sb(Ry>|3`>wRIlg4{wR-IEc?2lK=XX8MqQ3_fN8oPIDC%Z`MihM?S*kE~P0d8*{L{=2Ai7d%`53mz(a<^8r z>LEpoIDuC8FT!y^2Tn_3%?{QS97Og7fVwGAt*$;^*xUmCv?6hS9P0yP&(ZXPgW&Z_ONbDr~W1Kv8&m_oX)PFWV&vxdj^z@~Ra%j0M z?=@s*etD=ni1LdyC|s#C-wr>=@Cn>TChc|{BNCcz(f2-z=7w#lLI@B;v>XX$FSoOy zDBqjv5EurBAs8 zmU;9wv^})%a(`)zq1;FLQ%1nXM$BI{s{giZ1S07T1{H9+hnISFHyYW|uBnvW*?WqS z;;N+kMlbq4i^lhOm5rTTN1Y)Ut4;yxLY`0>Q}_oB`JjB31EPed-k(GtZKDV&{Ls(JrgjgAUk8h_xbz8E&1DB%@%K9vj1D*EAD zIWrKr<5*KTyHXx8TMH0SO>0s$8M%wUXaqih$QRg zTROtNuzwSEn}F*QHMbFjTag<>bxoU&sApFQ=c1TH3UYj4lKA81b4gL?PKDe{nDA5thjF@g7YC%g zvnV%P^G_J3|I@md?6tTcyZ?M z!G8g-TglkVDRqvz7XorEc0^2i=a`e$h@0mNsgekeK*~z4=agw!vVw$Bqh-P}WYrYc zRi7>N-hRJU9031r#@iL|9?QgCirfs}D0@eM#gy5c2UXG>UKZ#~(QWpp1Si|7uUY}Z z?042Q$Y`0R?b`AeheJT?)uY??X|6$FKYs`^^=ZeRVQx8nC#sZ3%L;srEcz^=6ZAKf zHF;vk6Aqn#8|Y_-=xN*;lT=dX2ltdAV@$9QWbNLm6vX&T5(j^-<6dD>figJFIG#!W`mlP1l;KoncLOM*m4o?hP85@E^Hk8Cx*6JQTh6 z9TvrNFp^>g_cdGZ9q`xDBySo|5r4LUsOR19CMLK*RUTK#j-V6n&N0}8WYU-}B&$P< zVP!JumDGDVAAYYkkX!P5d&7Z9JkXm4c|I(aAJq5peB*qy;=lwIJHpMf|KE#XBjN_m z47-`R)GKBLdJaW)mTej)T6}%UK5g^l&LAkgXUkM%7pX#TXW@f4W9i?BIRT?gpaBJUP_;~ zn%kQumGFM1vvAYChF;E1$QP(nGY`Z#}GIYcT=xdGLUr91YiQ zoJ(wN>q|Uy3*3L?8-IJ8B~^YVQ4BSXXY~7<;}G2*VL*y~tRukH)qgKy^ON|_qr@Uqv2&yT;zurANfLpb59T=Hc1MLB2 z7eL^X?%PdwQ7BNZJkEUA$X~#xFp|y%0mx3EuW!VbkHl{~cL7SPcOKwb;SNmg(q095 zROL4TF>YJQ27eO22(Wn-$g{oi%Sgl|5NT3wd0=ji4=}T!gAC+8)8+fF!#mp7KAY5D zXaFwIup;2t0bom#N{421d$)-CIJyn4Vz}J;>Tn}`6lD_tLsHy6%VvhH3~e!(+r-hL zJ_roy*vU8_^<7433-|E!0h^lCd_+vyA)%5P;NSCLr+?ZCjIe~4qm%VlrD$lu@(HU! z*#N;qq!D2u9`|mzpTP)viH(?0wmm_&MhhiLAYvG!e-0b!Y}@#@5W4Pow512?cscub zx`-&!&nH=SPKBuR*5ju!oIFXz$3v@}u%bN?GNsWS8xL~TsDq16wtF&wMd`yPuo8+~ z*k676dVkjwGh@AwaB@rR?=2{SsZ>xN=58zU0ca^1ld7i9sK#!}flGPH`*?(ZA=x0ZnYU!5r9Ock};ie!gfuPC7 zeou-oEPM>WMKA7YCmBgi$&%kj?|JHv13rIDe^``ui(L6tnGuo~VOgn@CX7#66EIv1 zD<^sq9BS4XF?AZLf@yAug<%7|Cv3y4ZZAcYlrYy<)IU5+8z;t7dLFcRZHjUknZ_ba z^naZA*((gt)?75oViQuh*NEP^;3A%PDtk`V)*9L7dIl1#l=t`>@|-z?Lt@9qOeU*v zU49n7^6nsm2HPB-yhb@q4sr=fQAg_9N!0R?BaPLrGYm+uW~hqFI}($hxk>b})Ns%N zSds=qfkih4lB`&a5df(&OKSp2E0!0%Re!xKc7HT40#84;mH=8vlC5Kma2_iGOqq75 zVFCM8Zm9F54F41cAnO1@-p+B+tzW#egv&xC(8oqt-JjSqLHsiY}j!=Kg%244p3i_cO48Xl(ZG34fBqd>I z`Ue46|B|i`BEJo-YnLb1cJT%t==faa#ElawX%iytg|`(B+d9J0a=In#(i*ZfZ{SkL ziY#fi2eE+xj~;c4906FZ{MR98uYVzgMIPJxCSPC`MgO}}l5xY%&$f&8IZ&cbb#9{J->@CKld(+<}ZN;eVc2!WTbU zSmw;+JEQ^PkklZ7_>1|& zu;@gEEa-3a?IXu;NB1bwJb#UEuxqC>P=1%jlV*mPPo_~1GFMtcq+zcnfPYX^BWl(m zN{K4N;mI*p)%lm>-c;z~SswXeu3k;n0ucJWzKxaNAU2q}#o%sLcel|kMj`E(p&1=6 zwxe(Z2A=>Kg@3m1I+CYg8`D;|ygm|!k~9PqcRImh6pFau!c0oH~gr* z0~@S3=mfSZqzHOozHqzYRh%G(NP^n-s}jVdxSN%}Pc;YNzsG^Go>3r#!O>vF!xoer zGWiHrKNSmQEyFk0i+|3fbpW(iIFPp>Dw?V4o>kp-~nx%8O(sd+;z4Z>~zx;gu#AWr@6v zL9647<(Ug9rH-FN6R{52A3mx<0jv_1XP6@~cuJn$00xq#rnroNZ3HX)HX>zkqn8=6 zcJ+>E}t`@w#sdSlh`<^k(+1|gzkrB!mbAm!v`uP zM5{N%&gY&;TXy!=Nwk>;+Y;9-oZ-f{rP<5@LNVhK=c-p=((auUh* z1!F#)KEbj{los_-BTzn{#1ixTB;#gg>#d<@GJiz(?iSi9i#TffLr!VKopj0#Lx^Oi zw{3GIt-S(9=FN4~U&i4CN2|KHdp+dfBu^kJQd9uENH}glV(hhrl^7z&ehJrsC~+c* zyBAX{s%lYak9G?Lpx>-Qfxp7hQZ$0TnM(~_hLmUzImg3LLe;!hfd#^hS$mmXD? zdVe#De+F2=F#8rR-K$;^*s1!BV7<@W%R2DU3bpU92q&p7^{1CR2wm|A*vd4Qqp0bs zn^&*iZ8yRvXg(3li=DK8XetY|mQh$pSvz#Iki*nj{bnm=T2EYYIKW+3!>nTZ+>7W* zuyVC0kZKK&#$YVVgvA>=lG66_3Twu;!++>l?i|hVamxK&oUC`016)1Z6WbFX!^aXM z{7|CMI9N$nME>VJQkAT4|;ya6o?74dTIAoxm+WPutI^~N6CN>x7Lk|BVT!k&^^ zfzCBCucL`DC-1NjW1MsLa@5WJ+8w&B$aueI|I79C~7+MzgQ4B(l@pqJOVD zyEpC)F)8ywI9nDP5(gJVe=QN53UTk(HzsS7O z4O+T@(uY4#sB}aj$gRcZ9VuM_7=O~Eu(h28$+`$}FfPCN;{x5Y{zKEeey)~f)n}fy zBi}{MjnDYzcriTxZ_bxffW8}V5;hrvRajDZ;UGzb!@2ho1!*e`7=kS5pyq=4aYQg4 ze91a2&kZFz)LU{G>i4DeRHtMIDdl72kqU9Tx9>mjA7=AO;|C*qwW2jiD}Sa`7x?)G z!!se6;aIpu->ICx=_Gk8V{9CaXe*eMmn0EbCR7v0l0C;#+jut*y@DJVOB7eUx?WXc zwDQVm>Q|C8T+SFrJ66IcM8!C=*ggA044q;Ah8?8T0+LKIsOWV^LJ@cyb zIFVc-;zE*BZWMB-+yA5pWq*)Ok?IQXn8c34V*~ghkKj(0hIZfzcgtESa8{@X3y)&T z)%qcgF8eXob-V+=f?$!L??R2Oe&?Vy`b$eN$1_#Ar1z7!5x-we7z<8twJd(x*d2K# zTnNS-a7Hu${Y++)}*6qh2Z3lddsvCL$KaQfB`Ima0A1>CKSX_fs|AiT}1@^>v1I}PlQ z-S!8HiHJu}TwwyR8>dWot~6>+y4+C91S0FD52fUux`AF3;_zL;WHo7(K(`AcrfjDB zJ8qLSg<>7Q6g}v(nt!WfKC1y4OY?;_E6}a9P*M6!qnP`siXXEXRb0E#?&Yks!2T2% z(9d(QoUu9@7J$|^#)0A?{@sO!xRPSF!Do>X=teMY#swJMRdr8ow z>Z`+i3z{{EPex;tM_uxvi3wkd3q1(X!+6-kJw9vpEwwCFuRtVm4aY1|o*AG~Z1Mt! zEx%rV4ZeV7Oz!K7)Ay;YFT0orbrkD4WMAoF99Q^&r@;Fe2&$=ozZ=f44!+O~P@)=x zX4hMCFIP|YCx7;*aRY6NJFyTn&kl3XNjdQisDQZ(Q{JQy!R`O0!W_Ed1rm#g5kYH{ z#Cby5>;OJ{DEmwF8Xq5QSMn@c;wD!Xtz4Et$&CD%>ki1UynjTH;st$1?&rFcFhbG` zkRVb)^_-4Nmyg$4@4h|fp!tjc2GfXahd&waK_iL&P=DvXz6Vn8Fy!bQ@OewK{jS+! z2=<+#!UosTDsF-S`l(cJqsUZDmCnlp=KdXJ04@Fe&RYNcB_W;!Iwv81(lAZBf0iyN zQ~w*2$^@d!$Fpl8$&B4N2XGBgyJs6&$^R)M3rk)oy#Om_{YWX8oZ5xm5Hphr`CgN)j*& ztA9A9S?WAt5s)PH_* zx!Hz$Qb%X0=Dm?zHVN2T1vqUpeC!?q&Jss3;Ye;UeAsCzBm^gxA2DJQOKO=UsDc)i z`~-@9Nkqyn8^xOgv#ts;y&O!&azj2j;$T(LBQmPSvuh^Mf$U;6MmA?LolwOsR4 zU~50CBU(n_sQrzYpXklv%V0h0q|zR9unlxx`=59?J5qu|{KC7>SYNQpr5r=oYU}XSuQj!P{y|G5KN<-eQ4l>V;wHEN z&Nz*hqlTw6rgCaOX@!ieSj56M-EcFFo1u(n(e|>HqB-k<)8X7gGakCmX4=PSRe_{h zDZN3B(exHLgwiJ0y!i6CZ#O~QzkgLRe18TCJOG+5coNl=@9frkv*{A#TlGQF$p;tw zV3?)`sAr}jeD|@q`ts&}<@`V(F`{+MaiEtK9155hG1dv-u8kbZD|MxAPt>56E@qUf zN_@YF4aSGK{XJZVZYOcAbX-;7(inV?nX&#A^sZ4qrFRsi@hAJ^U37?GDu2;0vMaLa zMtVn&U;~T*{Z6wK7>9uAuh@{!iM^vZkanE{{~IOu7*WPdwP+#F)&dD*DRZN$LMnzbm7jO4BnQdj)u@|47MJhZa^0!HdEmaS1+Nt{NmMb zxcHQwL04T7wS3ns&P&auDt}Oo5Tf=%Ke4$qSSo-AZvGf5!gfml7Bx8U8i+lFDZRo5 zWpc>5TOR&eG;BiWdS?I$w({qRr2^!LTy2oK%F04N6B^tV99?`z@=F?T?x}*Vk407| zUZv2qZ1keDo3!84P$`kqOD&OZFvcbL-w%gZwwB%2<3rF%pEt&qvVTbgM&vajos4T` zEk5RWu$y{2@b8!`;x<~Iw>pv{_x0Q{*J|6S`$0Baj-fW6q%=(?cN9f{usoqJ*FzdSP3^SsP_h=Xg=DV2o+R!Hs+zIt0oCq> zof8gU&D91jG=F0LBUMa0OBg&@|L}J+*#3x1wV}|;c|;7Rc6`!jGGqV;mdQdB^^%xD z8{jq3m@KDHD-qY!5OLVAD=rje4R0gvg5jzYC#OBT-%BnGJ|+L^n7rjO$kyhCO4QSp z!b+PZbD}&pIE)j7NM}LPiP~ki1SihTk__X7a=zj1>6JAoI zyNklZMGnV!YTmg!9!Oj5xjQr@>BRg)1*-H=Ceg8en$4=#3w&yZBHY;(EXg`Op4I%F zyqbUCN#_rb>yuM6<1WRi*FmC#slNsvZGXEHsKQt*UekP42kUg0-&WsDmypUO?ocZJ zy?<1%!3^EbrV=lEV$Gnk!+#js8-WG$!v1#vse(-@bU$AA5LL^uYPz2~I1wz?fg*Gv zh15ydSRiYSV3Z4lZ`+q7i&M3tQt`k<2?}^%R@77Gd$Og4Y4<`zCx_tMw8iG8R3s4{ z3BQC~j|66Q+c|-JJ2LCi!l7aK6UBP2EPoi7CZ(sH;sn2NIv$b0LXep-?qBMemnsS# zdXPqQsjFEYST7mtvdYpm#~+IoB`r1hhUWc4)%)OGND5mq>^(1J-N{sX9k)~0)k8G+ zn`UioSs7iQbudit&V>Y<@}a4Py~;$sgiJuD0}og$_5hUUQCiR5=;_Rgq9Ab(;_ z`rHpej4T-MYU8luumuIAy|KGq&|vv<@^^Hi6YV5}Oe<@kD~P9)7ztjjA&YZ0QENWp zb`5tK{Tmh7t=2sSV%-Kndd^pW0IJgmi2}mdk5_fUR1&e=;(}kPGBcN|tGn|J6Myz1 zgcndA@=m2pPi4&w^Fp{SpcXGGQh&)U5vBSn3D7|YNgnIsOm9+uxCs-Ymg^DsZOPIu zf}H@(EjCZGo;nY>)O<+gTU?z;E<%=YWaXe@Lnsc3AFO0r=F|$4qwN+#A%qRz#A_(0 zxdt&v>2^#?Lm{Ib;%&0F|Hb!|s&JcG7(NEP-+!vt<)L@J z!6xPYDyS`2+kGd321z==5LS+?1WX3gL3hu5i$L+%=(dtZ++$!GKFGJZYOES!q#A#|yp9Q5 zLDm*6MhJ`pv<Vt79DspwcxPk=PonJJoh6aOtE<5PvgPSQsiFp{&+q!KwZ~IW`yKdQR}ejqiKW5zrhXs4*cyr0oS7 zQ98mZ?v@jWjI4bgx_R2PhO$r%&E;{>P|N94RBD(1?f7@bN0l>2A=@nfe%EF*yLy<{ za#b#u@4c_OOv-yDL%8=KGN(!eIutqo=i9K|JR`fnLVs2U$$#|qW4p5b7^~1xb+PKG z6AoNZ^5h8Ah?$~jB{s$jwm_8-=VRa==zkYocsiB0j|`Hpc2xH1a&GE3eM+dbabt5D ziUOC)T*_+57cdV+c9sHNlVV;FF#Yim#*`iWC_UIBGuVNWA>kCobWLDHx~kGw!WBsQ zuK0lKaaUli=zj;t`H3U2>bVu9Q8%hSJA)9lBgGAO+;1}-6f;Yaf#pP?b zE?w79gnzKZrn`_>EajZk?nktQc|H6vO2i!R`>^mMV7XB*(-isLJ+v<59@ZBB3t6W+ z=gKV#0YC<%6l&=(pPzFjy7$BRwbwpe@}18eddI&wYD!y9K`DOQy)l{B3UBcUJFOk0 zdiyLoaE~6Rj6U7)X%tHQ-|iV(npuQ0kW@e!J%3`GLzX!}t-FQ@*3vPsTlj%Rk^hE6 zwv&}RR(*j-`ABJUcjTLl7L9Csa#0i;up(*8AF?+Xr6T9)a6*rdmh@0xK8~Zk7T9RK z6|B}A08^a15ULAz#xAY}o%!K#R_?u|NxP0273GXYxB~e%%FJ>#z>-E@ zK@!9Gdp8YjLbl#6e`&`J&pI!!br>m99Dj@poX8C$p4Rf|pSmad&((TTcwD~n#?Pj% zfKZf69&1P$OKf_4qLhzB>MtR%@F(z@Jv#D6YI!hRM;v>GeeijvJth8@Q9;8AC1F}C4i!y{~^ z?9LB+)5n$z7@sq!ja5T76)O%8JFit2ROB}`w_^JM__6pE!p{BsZe^+`aG){*~ykg@o9k9|ANkYukEO3M`w4ScS!_{XTmsD@xfX`$5FWNNvtvvfCBjq8(A z1E+dwW()2E0hQj^SsrxUAEcFF1?#)vj57L@Mspl&ahLw*TcbvdHH49E>Cl)k7{)IF zEsCgpef^70?(@sjGY>$z<$qU2+`aTKqq6stpvo589(%iml7B>F3={~A>BAj4)q0J1 z97n~KURfwa^rCt;GJMQ+<2CIwh#EBA!dm*y)+fMd=r2fcYPJ#k zSR)=|#8R(brAi=z3ELvwJk6{{U0+Fi^G1<1^O@NbyPuw9s~MW1FMn0L9M}3d+Cm21 zR2bh1jJEWrY^Q&cO!e@khVLJcIYCZPi9FSYd1}6IH0c4s24e=Zt6==xry`m`1|wk6LBeS7zd&dh1ZIcm7A;qvk^?vW zwKPPFs780|GzB%pm4A6fcx%;z66a=BGSK(!S}g^~zEH+C2-xET%8kO-^ovy9x zK=<|_mn^UF%d>5_$6ez*ZPabgTp7o??K)V8z`x7{>PDA>h6qiA{jdo1!CC!Yf)dW| zUa}}Nj)CYrY@+kLm%-l`2+nON64O+u9Vx!9ANe;~Li;Q`SARAj;$FEx&&hFlA~2l& zJW;irUM}(jDj95m2ivE*0GzrBj-E5vX-T;KpC2WW`5gpfmqUfTw($b_lX2=86#Ik1 z*e#RDW$m$N5Tbhqgi3Xse-F9rK7gD5RZ!Gt_u`jH*m8Wf^^fcT(Za!_?^gvKjyeItg#fAL2$ zyn=wWTAf&k;8r)IWInQ4DH@LJ{^ld;aV`kc)zoq~g0Nk=Ml?1p0=uOY1(}1kXzo5g zhlFY*tgra^LT`AsDWyI5PH)k98H}&N6I{Hasi*f3WPiE0TsPaRke;Yhq?y$7>ucoj zaST{!Px+s!ErjXQ)U?WZLlg#goSb>(5wB=cu!t$wSKxE6hjDTv!n!w*sxGa{Bd{AmLp{7v=~YnE(d{rXiouG#WFj`ey6) zD(b^UpG|(Wdvdk4|KV4U&)tj`qU&bJ{c*^Y3l*xCexF9lVvz$znwvJFX;>RS?~j8LBq!BWOVGT#TR$%K!u$Jmi|;<#c$PG z9GLUe8NTRFx^nlZf%5N#(7*NBwrDUnHUN6@!@`nIX*+FN1oi+O(cuNvkUI-V!N0se zKYsvA2Q)Y5t?Or4k)sn0{b(Ijysn_OS0?R)6m&q6Jg6s7dwUEjiU-F;-ap@1Adm6H zTMOR&a%SuEM8{P~?V5gOie=y$S?(vzt>cRJlVJzH^s_3$EIAiSLOCuj z@y^>?n#h0t@Os-s1ocONDUctIE7XvH4#y_gT<; zZ(G0&uuivtSGVv-_^1|4C?`49lksNq7P#cMGef`%GB~Pdvpj8I+j1uI@ky(I?SB+o z8BO*FqmCu5$gPpLHSd+=Z+yVcNkcy^?|ywl#auJzFd zEa8j9PL9;?LbZJ}w%EIq`8IaS5482Lal8zFgSA=+N-2=Mp&GH zfy@GS&ACWPZ9Bj@HW;i-m<*cPLdEa|ZN3w@clWaJUC7$3BD^}4g)Cvrmq+nv(V*ie z&2VyEicNxZa-5aTZ_=q_?@n_D{8sDxX%Y&co92h#3lEiKgY4DAQ67cg*)iLfe8&rz=5K$7?POT_2rb3@5yr?xzxZ{g9P?atXt@jpHfX*#$!i)8_|B`!n znS3l9Df3&*;y_Sq;HVkvnt#iLJM~yM!Y!ET#zPQTN3UAEXV-QMxH}7X?XzX3^;<@E z3NNR25wgwdVRc%exT8Jag>8uS!40oQdS`=Qn(i(g49=|ukXYxe2SXt_M6ao|lrNUP z)R;li9N%Z$N~fSkm2CO?KxEEY4))q9iNh6_Us`c!WLyy*CADe4>3@bZXoXS2=J#ht z>pc8_&4A~gf^%KDrs6DU$d9#^oWrd*IALCyiE(uG_nTz{)P{G1&wyd;GJDc4~2b~6km9PHe2DUi9b5RWKw7&T(9GLAEM zCeVX|^|rUh0lY4<@0LV0{j5G0|M~gbnXb?0&Z0N|Gy=PYXRov(G#w5TR2H(;vUL0V zfkAy_(0@ocgX(C;HS0|pj+g%f&6=73wrg?FBtEg(zbI;&uz&GX_XoULP(NQe!f-av zq4`lRkx+zsvq>2&1zs;7`_Z}cC7GPhl9#r=9#AmGF7nD4j7{HEn$**c|EGNnQLsav z;?Gk~@{U@c z)P@dc9%z6OrQ|C>dvlQ^95+bV$KGd*T>p>a=@sD+0)PB8Kk~5l-mhc$5_c&nILxyX z`W>d!M#|iG;7wqIFA0$fZJ_dg`_LV3v`!p~=U}(OR=XU=l4C9pZWBD8^&iqvg?n&- z>9<&GNZQ8#&eoS=JSs%qr5nj|P=}j$yO@CY+G+XQDLrKB!3>5-;}lBV~h4_%znN=Dqa$k5PBGQMI4H}Qz#{jol0RQe)4fXK5?O7tN@u} zEKZ50*FC<7D5|rhSScgO#AZ|8viocgxyHGh9mBxWmj_ATz zszf{&3bF8+bvnSLlqO)4^Pg*)XaAr?!}+#IgSDOG_$Sx35ewH|GG?D-OO2J4%NI$)-}E}L4|7 zbE&H}v}73-u`!)KK6Gm#_c`g8&nP>_gt05eQ0Yk&Zwb3_M zW!DrJ44Mv#=nJnwaDaL7)uZ-_Wz@Kx8-Ky@VPjE?=(f4)yV?}chR%&PIjZ1Lh?H90s~$^TkyV&i3mYbaE|?D z9cXpzLz&Dw%YPT|i9h#62QuBv6+Lx%MoPL=Q~5Sy@EENefBlfB5)P0jkej zj2>=SVG=Xbn7XY`brVjO-2h8R_-H`t3LDik(idVgN?{rtONoI{L|rw@0?pE~>(4i7 z{PHPv9eU0YYVbBJb$Oioav9GhxPK^KFoFDP?a>rp%k_FP-flYyO4)+gmdP7KC&6_t z=fqQ+FH6+Q=PA#ywYOLNe>eG5vEM6Do?bq|NEyoyy|DkLW(dhAI6mEPh5+N;4O`U% z!t%W>(SGV`omvh}c_=3INx>a_) zZprFO2Kj)t)<@|V$haMkw|`N51sG;yM#ej2i!C8gYvoTknSdYvuAFpl$z!Lpj&E(>sL^wXNl#}(nYfQtbq#0D!_}}*S|K|$63KBnSbv3I?qM5kbj5T> zXKIYDje(cumD%$4*3e^umjW!jTEU9?!xs}Y0}KZdsrrh`&}CZf!%7p+(oyL#kyDE< zyW&!-QgAL+EqRqATWyOqYWd70%@(6+hl?BSWb!SR#_&`AY|L6l!SyCXR*$hVsfRxf z&7m~^<}x;N`$WkLxqlxT68#q?=9;w5Mkdoe)TVAxL&rws-DG~$b(^mK`>&pf-!u%3 zjd|E*8lle{Pl$SOw4h~+j01EYKEG*MN6ev;tASapYO3}U2ZralSCm-uymYJYQ?1o|NTO}BzP09!0@_Vs^Cf;lPQ|JrKn7}yg_I^vvL3C>{n&I&)R6WfuL_C z+Qddr1b-uPk}4a-Ai-7~(Ndn%^k;SB0>beH&VC(KT^)(q=eNm228eZ67HoZ#Z+e9J`U*>G4tyC;eWS`U4KD2tCaI){8}Mq130pwnwle{%FlPS z|9NpkI9wz?=Dg6T-ZIbEj_qwEd0N`qI(9t zb7-&3)wfvza9sv%in%MAJr!99$7+t|aestfR92stZ31-54g8iXe{2oRAm_?rj+n(4 z_&!EXos2I8zxp-8Ff#(z+|RTL7hNC!+GU)|{xJ6(rv`53F@UjE&ZTh4KW{7`qn7N| zEr>`m9M@QsHHmrK@HG3-+o&InbDqP4Lu?X_-?FQXx9Ch=(Zx4W2tO4z8cZAx$bViL z0k&lky;OyfP4?KJ+U82=-fVLT$TNVRJ;@8$$vPVz%o&nPJsE6K1IhN_=+md%3`x~r zvB17`^q*x=P-NRK?C=Ft<lU%aJg6bWe z^=1`D8_Ft(nh0XH`d$kpB8*aX&=mP}LNLhdN1=8RDu=V$gT07sg7{PA6e1G0%wsrB^dSP@Gcv|!O@hOUCkD>$gFFRluyPcdl_vi{A{HA_W5+~b`Q z@xx@al?pYwu~2B{z4q=gL%G={7XjS#<3}gC3Ic;>Na0Qp=C1Mh)qnI>GPc{GNm6OP zkqGpa^kleRy)A{Bap6iSY!)7zPY|LJ$k-D@kh|Kux5sWc$P9kr2cj*9+e(q-%C_Y% zQLZlJAU^>+bHCv}XI61328R533kwvHh(*$@dwDnOl5$I?lzl6S`C(g6dckGmBeclu35|pM{aR z93tatPMsLj9&8}F=B@Kj!fM$zo-&_Wixj!SW!1J(*ZzVS@PBvvX3bL@DCp2>_Dp=;EuH%suy zKmsUQeUjJphw|)jg`oV+qeC!A>ru8V6HeM>rVvz4k?y>qVJFHXR3wvW{Q($cH$3bK z3}o2$SIzcZCnutBL0( zLGC_Z?N6TYqcc~>B{aA;u4rKFY!x8B3i1K<0NEmC>c&vgbs`gvj7qQgNcS$MNOe$sQ`{qg`S{+4R(wr%hG>ck9TiUadKMH*lG~4ca2xx zr~IAGax8T2r=HhcgX6R;)&2p5E$|U`$iN$8jncLLZL_2GM}QSSlr(D_5#?A4lL_cN z1?bGh$_6y`^Xgnm!AvCL0hZSa#9QHm=QcF0BisTtm(A0yj=i5621Y+`%;oLFdm%3Q zAQl~*S6_cw$JjW5Nw2d;w2MKZq=_RKxh*$5tChu+)SWYM_gcs(#>r~l1662#7UD@} zwn%iv5uuWoKXQuk$tAN2;kIN#nf=fzFFkq?LGI^={KQILj2+a`5GY!bps|M(l zbozfKvBPUWF>&OR*9(Z~oZ$?{T8b8F=r_aDvt%#vtkN|4m+I1wf7GgRj##V94EV$| zTgB{T(J!xR^r$egKru*^y#pt+%Hw(1<6WxCTh+LD`&!DEsy^uG;j|b+nu=G0xkyvleM2|n*|B_- zuadL@kU%>*&B<7o&x1h$v_4kIF$;J8e$wn++8cDO{6?wnF&sz67GYM=~TU%9hNE^)OJPv=c z*bqpVbgd}Ngh<4%>>=vfTHPAZQ6-=n0vjI>QP(U&fff5KOCY8Oy=y2q`Y(O#1Qt)C zt6*hHJv4-(u-(*2^v_W*&`nE;!ut!C1iaBKljlt_ zn+0Dh`&T-N?A9{Uu7G9Q)j~F27C3+2K}B@v*g*Cho}msH^WqJM=5xDr^KM5VNz+?3 zzIoY-H$-zzBobP({yOChF?EW8xGJqKkHtO;{?@CCUTyc6%R5Q@PR-_=XVLRbX$KGP zdOC=v<&pv7O4aCxLuxfDNYEhYI9a-366d^^%%7bUukHg77lTxJ#F_G>m(_pgq2muC z`~Wja-MYSt$+#GN!7dI3Sv0Gz^Foarhwx;kh_*HdHXelx>!AQ*T6yuu{v6uyP;^sFCT?Ga&{HS4BF3ic&|C$!Saqv|sQzn@D*P)7j!c+{m zl(OwHUL{+?vM%lb#d1Rw+7Ew{S1pK+M8Y^7p#u@ox#8S%0lP9EA)Ln}o3k>w8TQv8TOLC9Q3m`(R6k>7**yYh^2?*D6ICPcnA}N0XCzdrXhxCRx z^bs*@y)3q(|2*TD!Jm^ljw7$`Q(s%uPoc?7G}GR-f!|#jQAEJ&8X%08i`QyPRL}{i zEotIs&^>x2#k@B$xY`6^VYw)~!Z^TQeJKft?LIj5xAc_s?o(z^5XOr>*#Y(=dODGr z505itVpl?Uu4gc^r%`{dwDYytFgNDp8jT5Z&4VWgc9IN;pz0B@(1)vg>4onY@AB30 z3lfBgyXNk@pHI1^wsug}poQoDR7RSegXXrGy~uktx`5oOY$Ljrfk$c4hmd;_>6{vo z1FR{kZPJK>{`uT+Ns_+yaZ-2b)2@GN1KLW~4>wDU+oMWrMd*L18Gy#AJmzfk77aP_ z{C4|pB!Eu0DOrLR+#SbhrJQeV_%Bn{52Fe;o!*@|!AbYhS{%u}hJ~ElVASKh-~dNy zUVBCtsqlJKN1*(b%{Yc-7{tJ(WUD`)csavo;a6@&Vph>s`%e z;xs4z^L!%$7{-5&m|2V1hi;3&yMyp5x-FrF>6s*A`Yb@AUOVg4ZDR}W9MfHuvY}v3 z2nVNh)S;)&=I3R7duXWyreJL?)Y%ePs*)La+L7O>zo(CQLUs3X zMZ$;5HSw1d_KZv<@Yfm#Y8yFo$Q@5`MW;Bz@!i2cV)So-0_+9oG)QthXye%8YCvXs zr}Y|-1!{lB1}P=%^rjiVWUh!!6n8)W#br#(mNA4UCuZf~EV7U2i3S9^R2Z7jfw5iQ zxtI!-=IxZ1^T1w|qj2p{x!#4ZUkz3Aw(vwT(R?~BaFxB3icYAfD${h`VkMFXXXmj> z4=vmJCEjZTOEIr7oJtLxbFx7wx zipi-mAmGGipbS$pHG+KNbz;-#JPb$d$iGf8c*JC&S`nK+dg>L4Me!Ts9nEs_{K+AX zy6Atn3Zw4hy+2^-zfBMY1htDeF@f<5hoXAg0eJwv*Bm5!yi z-Ou5zg3(_RMK_i|_{g@~32Hoa$&G)amUDmMF}bZ5NmBSlR|-4WX)XDI`Z!%?y6gZ~Qp>9XX9Fr@N4_ScWw5K)Eihax>+}L5|lu;Zd{JuhxRhO!lnZ5H=-b8vv*(;D?%VMqbP-780&%yl!1#?=SnBfOKS!`XH2243j&waTW^0qDQQ#a?xunv>mM3?e<66f!XF0`GBY`v7@zhd z!mj$B18U2AHIndVF_fqf?|+c*5rDd$CQteIClum|67!VDPJMl-1M=Vx$tO<1?#RfH z`O=Q_3-}y6%(ecO#~i&A*W{0LOF|HMQSXsa*HOR?3ajS;S~f!5`ylV&+=_p&=s4d8 zb}n_@CB&w~*AOv@k#w*&8Z@Djw5%B5I01h4(v@gbArgfAyqkkND!0T3hUk;P$LK4m?E5Xqu8h6}}I%tIF-T zhRB_AtO*tdM-M$pU180ri5)cvk_?}LvP@v{%bLV^oU}zG4VE!(NxA)@Pn7Xl7Udmk!<{P|*(CJAU+mXmk?S@DEo{n3@y@_R8LrKs zINYh`sAg?WT@mK0=-+?d_n^?O^7p|+Q88!$Y#=O)_Sm5MMN@!q1HrUyny`LsocaNh z^Ph}e{qs^nvRiI#TO27JFCAUjsB1lwyp~o;*M)mIcq%}xa`%<xCNr`<1f+}x)urWr8^#s1ZcQqcFe8irx3_MuPe(@=(ZlQ(xu$G zmH`kIdQy(5xyOI4(`eG^VNZmpslk9)#Qi{jWu;M9yWst3Wagn8Vx^pDB!I3?{)vFx z^JHCgvB52Nx1$t2uG7q47=$w&)IvHuw%AYXjqkneza!4-LSdt2n3gvKV_PLhap2#dv+;Na5uotZ9K@s5S24j$E0}1+-ltBN`8&~8}yHY~#`lMT_Vi36AQ@f=#WP&yMZRN6>?_Ma^B zqxSDtuczx9_Tvw9qhE&+7>gL({trlcbd=(vz4#Mn(D*0@^7R@+HxsPg;1#RDUS1rp ztN4GF{leNUM3P9kz*6Y2hWIH`H~T!Non8BIdT-nqeV5q-zDgQAGNT^Jhik{yS*U^a zM!AF)WiU2IbW^0H4ySZj+ngG=WT%j>S(HU?ncj>e-C$}Re`6$&a4%DZ?WAyH9 zRoN+daGnV>L_}mc3_d80`ebg%;=I)+wn%?liOH^h3d4=BK7+pQ;Vibm1&e>o+Mh*h z_*4NV{)6801^9Y;v><0oRL09KVAwo%YW_w=X*C9@%>kR~%?yGB&juTnDnJtgkhX?2 z#tpm25$LDeDxO;{8R3HS+;v)Y5$JWJmNi2Z0pW@>j)59nr&wxK1R2B1M!bxl{#<{# zM?_lv`D7{(a})6u=t`&df!Z^G#xy#0ycRFfyj^9&ic9E&1I#Fd!qDxK<;0Y-Q+Pu-w%ASgj0JYiyz=h!$c`V@jrp7mr0c!P>H#lN!cZYBt)F`A}`5VPxYPoeM_& zILk+N=e##Um%Z5o?BIz)`nk3O+n@G}xWcpY<2Qms)@RJS*6d zA%Esb5{=RMQl{vc9apx~zDmO-yiB34Q$ze^9G^`=dvw*42fW5`JB0oS9=!HgJ{0DB zEr@fCApG66mu0$UQ7pbHOlLEVAGC1=^O9o_Q6_s9-4pB4bQTdJut@l54EUPpD{Uri zxdh-()^su!KLNB%sCR448Ht;`%I`{(XNo2yYfQU41>< z)stt5uTO6A@(Ed7?!AkvA7u38fl8iHq(COS=~&tzeDiOl$4b)+7I$lfDJ$Hf7h51? zcUJZ*1A7%m%R$B!r~(6H_BaHsUYbH6o2-z3ct2x0wGjfcu$9v#;(-Q>(jp5KD`XL1 zf`YjzmY&=re`-VR(sh5iGn(WV2;@07G4@c&n7?E-R3+HSr^v4OE|x<-KiBu2rJEBw zDKXw%8&~Na0?^qy+8LMZm*9XKeJe9^N~aRWVsq#scPPs*-%I1xeuwyc%dWv_ubJMr zU$W!gb|h%xG~1K+16;G&$1wLXeZk(^9vyBkrzYMYm?9KN&&7X|LBY$2bxVS434%ip zNe{DHY{KWFUmd`M>68Yx1A6=8Ls|%RlKU&HQR2v=$ccQrNqcJ_mk)}w z%xl6<_#=j>;A?+`C!l#mR2;*r;yGI17={-6=Z=WM#Yb#Uvsb2TrCXkp8huaUC7DqO z7@%X}uAt;1FXaDgjqUB>4O;iWZseIBwngE)ZFX-s{OylbNcPFu`U1@edMNCwr zz-MzYdaDERv4QAN-srFJJR&T z5tC1mp@fV#mU{b@JmV79C{g@EAA`z_^wR6#TvhkV4OgG5e7MFidu&Y8=n)vC|0Ibg z0Yf#EaL<234JAp5LcZeEh_3={zWd8J(<;Qgj;!7qSPm^}Ytf%w7g01`dRzn`+~CT4 zz$C+m8w4OCE`)37{4V8y#GviJ%lZJH`0dhUxj0bFF!Hfm20x}E9vDCMm|}ui$%4;A z04cp%Q3}5y4ZD*XT(H%q#9IvZKsiJk02aBB$V7h%teyELkpULDCk>tfZOm}%bh(l; zL#Ptv`KAu}T#DTaWVySbuNnYQmTY>kfM?8Rqo>H{KdJOMc~_MOPxqvh2Q(nn|3O}K zUX?n^IV;9#Ax($C_tC-JYh-Na#t#||!Y1#3Ze=F0qINe#XoviOD*5xqJ~;&y$|c6p zTw{MwUA4q}audD)>hf3aa)Zr=4``xk)8ZPK;XDvUBn{B7#7#7dsaDvDEV3E|EF1SY zJ8Oa|?sxF-g~x&^FLPF;zSBh!e&nlS}I%tWSp_NaSxqWKg30k z7Y`2o7Xb@LksHtt4|s3$3FrYS5;zV~mGpnL;kCasDvMPKjwK&M)dbech3Eh6HdgqY&c#54z2}Tjl)T!E z9v{PMM|A%kj*F&fG*Z>uw`(h zD|`FR2mPn(Rr=&+mLiRk~<@YCjd&`o00xg=4oh%Llc`Y{my#@}wjtDMZru~L@ zi82It@keVPaPmOZxE#II{Y>Tx0S*P>V7(bqu&98%Uf|!!f2z@i((c5&bCKiuuM%IA z*MUPJDA8&XVQc@7UV}fLO&@<4oGI&)hMMAW*0_in{U&ZD9#S7So&EUfd?=&v6r-U8-5_5epgAI9Nh17At>xOwdaPXw1QH zb`OT@3Cy12;96hwGdfzQS#+d6z>m8ij_(oYh0n#?kG%Kg>5#!!SMhVILpdhhw{#wA5r z&?s`98+r!5goFxpjF^8-KGy(9p@%6*&I?2TU_CY5o}euqd*0J<{vX%(?`x5^Na6F; zRgg30vhG|a?P=~#{qOEUKgYiKGUtTINf=y#gkUK0Uynx+OF|zx)06Z=-|hk3M5nC$ z;O`V~bMlJRBS{_G+1O!3LiL*Jj;?VTRc84R_{v#ye_urpXrzCjGvSgplrCv)UsewT z`{!gaeMdtZ+rR9ntR{n*>(vxd_ixF_k_-)`AZzuH+^r5+aA9RIF86w7+ZWfu>PIG# ze7K{LVer^LMhYSztQoG)q!rVPhl{)JAZyTCKtm8dHF`~aEJBj{R1SZIn)^INalCazuf zUjd=|_;eWKtg%{@+U_K*+;j`5)bz+4Dl$A^Y2A~-o{kGu27n1Uofe~TM)t6i99D2%Kl#12 zhD6n%6M^nYdW`;&D0=VJrA$o(W2lUXbUxYd^;mnI58DhK@}N;Y2BldwUkz4YGJtcd zXncjeKBa#;dv+C5Yx>}!f{UQDNyZssT@Kb#%c?~o7LEW{YB(Vr<(vmlG2tof&4v^! zmW#o)eE1QJn!m!(Kix&){%X*Xe4JHV@c&1AO2k!N+wQ^r;Q+lQfl85}BLsjylM*Vz z=P*i#?IFR8D6bXLTi;!;I$Ewup<+pD0xFAJ?5lsOt9B-$)+@`>v1S^~5g1f%6_`P3 zic2WxA9%2H41G%zq21rwxQKAJ`&ao%m%oudoG+nufaGm>)Tg#x`?5o-=9NATI>#iT zI*3N9GOrA#t*5hYcXMM#k%f=dE_kUS>?p1%RWPS{P|>EeZ}^cyR)*wlW!D99VJ6E8 zxE_Drp|*dv`3twc-zD1z-L}DL3B!N{VH!9A;F4Y?00{2oZ%4BgB~&v!$!y>!d(p&c z_-j9iqI&?i-x!jsVgg)-sY$)znZ1zYFNA{ z){rl$AP;IdNKRt8Gt8bBjwG6ZDLsFbXNz)9z11=^;kAJ7gas4sJ?*Pfil^6vU-&6J zN-GI~v)G@&5$|(kwtA&gSP>DbWu-BStxFD_B9j8hFfuskoc3{2wgD8hshQaH^+3ez zGz5k*_=fdQ})@T$gt$#kpijlT_th`yme&dpgt5f; z$>t}P>1~d53Vyuwqx59KNmv4CpYyaT+Geh770-a^>0k};i zxSW@8M+5%%oiDe^Rxgbx?~e{fCjQgGq$ZH=v2*%zO@*v?QN5Rv9FV=MW5qR0j-Oy4 zmLawXSd3A>#%372IA)&gMOad=Z7fv-RUeioPi){)H-Xa4OT64L`0js>g)AiVSm(aX zpIOG7ne1QlsLae5sC?GUIJZ%l;e^3**+ofme7Md%)2}{+5*n|d0h4xpDShw)d;j*| zLALso^Z6Q|Anz3GY=qC&IJTp=Y zX3Q65l@nT4IXR?9uP=&8#8GY9^|44k_eh_>MpmzQcbKy2ne|7(3|wVy=ZA~QAMMnH z0gfenz1Yz%0m073T`iY4?!=q_pdD~e5gTadm{~zQ1(wyVp}>D8o}Mu9#e6N~y$Cqm z0O=w91KLvt9gsuHsZtx6SOMZ}wB5N|X4Q6v`(mmFbJiTSV3l=A2= zSaW8KtBnn%OZ9*E+PuI3fZ|Y>g#U52*%SP)=L`i0xjPo9_%N5#D=bRnoce-x;aB=p z+eV9TsoS}lK0+*-{i<*@LJeyX7@3kN=RSMCL)WKEZH;e5Ch$M$YTg~(O6CH7e%*fO zi)722z>lqVoiD4|;_D1cT7=fUTnr#t8W0Q|ZwTgj%g29ascZ?f@*s}9-KB^gQJ0!Eeu+RH;vq_7_GG(=;hmxM!FR?T_A2C#}+STND*bRLWQ%q?I;t zTBsy!HC(&-7}@p|A)nCz;xS-s|9d`=b2hmgau{h}XDT8!f6rll%Wt}LR%ym+$-XrS zsQ=a7Us!+sg&$G5oD#ayk{iyay2x+YvVKH{tg^S4OsWnlRme#Rf~JKwwe>S69idtqTDG1IA`fU_VEl5_1M z!dMMZZAt#$$$)~q-Z|yVkR5@*n0<4=!Ymj~uZVwcPHpgSA)|Sc^=Lj(@iYj@>e6u1 zVL-bN;~;Pm%5}83bx4V)jvFP)@*_8NAWA=7Ml%^TZkk0FrGO-vNXH0%k1wo zP!50je(}9d>K7w@q}YxFdMAKHlAnJ{P}e8@$^!*e_>PpfuEK$6d?V!&O=On!q zj2MKBSM@d?pRHA*1u?D?hmgC2uz8rX&Q5UOejvdw_Yq8}nVCuEF;HUH1AepKzkl=Y$Ck*pMkM+NKUG}aYZP>%=fyzpB3uQN1jyU~cHClO@Fh|XytR}{jW zlvYhBNtNb*0ACuo1BIZvzmw2g$J>9BZrWc(s-sR(sR0~JnKGdgCXHHSk||93`1ZWc zAEes@LYDxm5*U7}!~j7U3NQ$fyhx-;mS2177_Wcq#@Xsz z3-mV0?%1vhgLNjD;t{Qc@Baj}Qd3y{Br%*D@wTO&3~Mi%c00`zn@GocnUqm^W)H5@ z>irCKRKoRnV3_R*JsUmEdAOkP`ZK<0y#U@I3!)*sKSMmx>i}Jw;*5mcdhC6cVtby{ zTfe|tbW?O8~@8 z(7xrYZgM7&t0x2$E0CmUtJH^8bsbzj1%Y&@7aY_Ygg!1P0JruB$H9M-U3f@5^L^vkL|=%3Q9-<{gg<}OA{wDV7_-77({6ZhPy+}eQ&~54`faMAs7C^nfO5x4v^uTD zg8R8^byhb`m0;UrZJ`ODD@{VbFCD|e-F`j?bF1m*%mV+EffI-|CEC3ix4$CIp%o`y zUL7WEQ!3it>?1HN`>NrWZ%a3=JaG+yg5}5imPDmfP_JypsilAEX_%z7R@frT5`ySj zkReMv<)fUS;HB3k|Ao!J4|%r4>FE_`{v5GPr++O8IF%8nWpv+|nM;31Pbh7VdYrP}IlhUBf$uL92XBYapY^`g59n94NMHfDh2nVE^U5?A z4QXA93uNXd^p~k>Z#xo0^O$w8MtYeROZ%^04Eq zTT^CQONcgJrofK|JUHpDk$o;P>A0jHV5S3J;n8G+{-qTH#Tod`1#hkW>jc=f*bzPh zdn1&<-Ozvk+ps-*Szj;8OqT+;x(VKc_4A0In{J?Qx%UI21JB#4>j+|swo(9K)_q?r zw6=ZMKe;j+wXMjTK_z1-g$0^B*Upb=|3os3?-<1vo$+&Yt(A=T3P{THswY%z$ZoMH ztc-zG#cpjHFT}$QHgi#(2M!xZ6ZC*w1Dx6q@f?3dav!t-1>H%@W30sTa}ZSnAa665 z#G3Ir%D}(^?FY-%X}c9`Q4rs<}nPlXQa&q$?k! zi2&&7I#+ES8_i2|uEuS4?pFnraa`NKQX+pE*QRg>|DyaNmG-sm6Po7CfaVGJ3J137 zy>x%#PhA9sJ8ESnj#gh`L*s$(V6X1w{?C&*Jh*;*N4NR?sb@AM>AOcuZyLVeN{H!i zR6EJ6E@R0}>nKE-vtP#8bd{yL5DSL1d6PdW-psTtZjDTpag?v|g$fmh;doGnHx+QK zmtcfWBpy&G4h?#SibNW!PNqVSI8Pbn#shz)ke3~#AjkcG2of!=(kzUy7aKmZsVq`| zDMsyvctM ze~zvYKWhqT#a2_@HQ!%X#~og|GahVqU%VhE&u{qYIbz`f7ivvhFM4VA)F>Ny$<@`O zdc$QSZgN1J+gwgN^>E)K2o-@By(ymWbJ-LWH zjK-;HK0vCA>cv6}@?l^IIxQBjCZfF-MYEy3i~-$6rr?}A6HlB4|RW#3y@ zdU@0MzB#&l-I(t60F0A?%z3Mg@AM*(vElAcLwdmW|FLM7`=cZo-CW z1Asz*&SPwhacQV+N6cR84?oKbLym|g<^<`qA7t9cBWal2OBhe}=hc4-W$WER2wR$$ zpoo6qEa@eYLZ+zH+UH`=ig|2}IQuq&piPQB>hHM6M#w1Z)hQD<60U)&4RhB#j|o@dx*xer zzvEbcDll|)uMNPIhF%%+@*`we!)ufUOseX`7i2|}^kN`nd6Iv-PBg5C4Bu$@iJfBQ zg+kcQ(b@#P`Y6luhGRXJ+|7A7hVp)^Ms>x`n}>N76RU`@-gy%asr$&;$I-cKYR4F| z5#_*k#lzy%1vxx2GxRGJa$T33DVXyVUY&FH~RDA9Gk|P`my&V{cZv#2dmLr zy6SO@DoXj{h}3@vg=|c*Uy)GRZ?V&Vxa-z#4A4XNAuy|m8R+(OOw(>o&Bon`HKbyS_6Tl~4$lj0=Yh22qblB! zPQE*?Xb68nMv%^(rl=Uiy;>e4@G)Ld+I>q>5!NZDWeDtAw}Q#&mT>@c8hu#JBg>jCSiuHOYOpkKJ3FX3@m!J=l>7)f7hg1= zUfzxAmb!>G znHWH$CxSr{%RZf*2>z>Gf(B_@qCmgQsH=ZzHA12uP*6+H`+mEGQI)_)27{{gL^*T6 zEai3?NA$KTr%!zaM`uPH;k76^dUh=7&4n3OS^fo^m1K(Sw?zU zx;5Q<<$>uxoL7mKF8{9(-^qhiuuiA7@Wr+(X;}(vcZorEzTbAq z`@zh)1v?j_!pEqi6lv6t#3eSdIjnY(Dkh}ddc&HI1laFIBZ>S;0Pg$y?+~uOSNRoZ zH?ImMRa?U0tw2uOK28R#?4g17I_Q5XoQ)c7LB8?4h$3h zeryd0c(>8jk$)cT7hZ_%H;wQ{sYz)c1L0xqz%eP_*Yaw~=~w%Y)#b@lO)i!kcquF85==x5CAkO%EGpId5Zh0aW4p5^`AV*?$RsGnw;~G(o0@FO0kD?0<~+ zBDUv-R_ktoj6?QWoTCr)3u6@_p*8!CYoOBtpxv%gc= z+_8o#$h%ydBfwO>$=VhCN`NcK#S)KEA=}Yb+HNb2HvW&-1Q4;4gr9#UqR_z)DdJ8q zKJEK(-Qd)TU%P0`&|=bPLl8Dp0;e0`=#mr}j6=w{1l1dAj$j!|c34U!8ZPBsKa8{| z65LX%p=W+Fq=Fs_1P&RU#iIpTxCk)qiCqWG7e^bevU z?U;OK8DGy+uEgY_wT%kd?V9rpQhyw#IA=;~13A?qZ@^?{dTO!1yq6X z&YOfa7+X-z2!2be74q0L2U&jqiQl-ayC)Jym<;=Zgo%d^P->WsH58_AU~zUHRElOh zo!PCcF-vZIkDUE|z(*18zLKob3Uugd2>OMfJtC0aT(*B%!ZMvdROw4~yr^Bz#Dbsm z|Kx{#`(r&}7Z-9g_HhY*7kvihTwesC|MG?{cj#zAqF!(=i4Ie=?uo^5g<3g#%Jgbl zGUejDOYDamuweYTw@0i9xm_L1h$7F%Sire7#Al*D>_x}p2 z`$hf~ap!--x^=8$GnHQk#oMIx-;Cs-$yy{5h3{Q7_JZY&>O+NM870TnHYHCe&6TaZgw zA(??!PQI-Eq}59GqHXyA4sc~+r}5Jv)IK9PlWF~Pozbqx`s+aA*xAhRI@+ieULB@m zf^5gk1w7;j3SxG$I4}u_@dxdk<*+uPPlioNI_Wn6PU44 z#-^X&!!PH;HEYxZLTpmnXh7&(jv<;g{CQeoviDCRMDG09H{IXQ3MfRNaf0>vAzi!{ zyq$5mHmk?ZtRczeeQv-6{uezQZ?BGh#b&1lZ0_>G8e=f-y@l+=22)M2B zi9`>oWA|EKPOnxBlYj~XjUv)^oL8b&c56z^W)1Y1Y;(ox_~l-U0U4Iv9Wrrla79o1 znh%bQBRKL#eZ~;v3qXWo{r!Js(He0Jx<+HGW)##_o=u`Jx!PIsDHrgRnC5?;Ip94U z&CKP4=_9C?&#DcU6JnOGG=l6SiIGC6-ap%J|q<~Abk zi%@${+l2^N(S>XIq!qh2HO2`lC_YTAK8l2HxoU~^k-z1ROnLX8Peu%4k0VuVBw}k& zFpZp5g=*SKL(IZEmQ_0|W7w>sGZ~-OxM#zSzmidGo8GF4R+RthL8UdY<}c^(RcphB z-^-7Hwe-X#DJk~Q`#XPa^u-I=AZ5+*<)GJP6339OZxBN%-~QS3RiTi49E4;TpPI`0 zHeA2PE*NTa-N@-g5^YX^8UReBPy+LWPhuUvVN9BdYRmx5AK?Lec9B$gRsVtS#-(hHsTXHe-eBI4MG>x=4$`^|#?9g3QxMRA4r7MoK_AN|d`eXE!_4 z<>QDIm;s*zbYXwzwh4pdzx3)DFuYu=Hm51|jhQ%#{_hx32-uA^TB`vZV@@TfWnK7H zEeLi(huTt&?U&DH8`x$e^9;a2Wn`3`a#K%MCwsu2B@T}hG-stMwDg)Be`f(Ph<+~q z4$Z2Q%dX~^zHJ}IV%K3uqD;+7$^3gY*Bs*{?4uTJW)XjS@HFPGNf@BNSk^~ovcu;M z+Z55#otIq~55)`_3c7USvZBZuj*KTa>+CsvQwvXj*Bz<-J_EKKSnKz*dRe0M`$!-h z9G)aY_*!9&#)zljrm?BZYuOdv9r&|G5fMO_@^rt4C#%2&`>-8|8FO_2ahz)}1*~E| z!|K0e1V?{=m7PbIv=og`Pbq`Ac8KSz$bz0J!G-5qu`LXB(9z%!3hGN+25;9Q=Cf?F-_)e1B+YoJ}^N#q}tdz?qS%M`1P8=Kr3@a-9S z2Y&of7hIY)K1NsL9VR+&^&bK}t**QSJESaWYsI`Psl@9h$gxVL#=NPEE|_T^=pXiw zavB~768h&@rm$24-$VB*2w7+lTCz)EGtI~PZS$U%F0Bs@0&J>PGUqa266wVo0ct(I za{YfQMZ8*y8_#)q*L0@tEw3!f6m;gP&;1T(%0zlCqhO2HR-!4&+8MN_SR7XwsIsvH z%>72d#BjC;Uwg{EVR_=X;CT|JbC!A^Ew0szYvt5#)vV*7b(ijcdM%p6sU4E?j!~VX zCY+3~;~BU{mai$TOP1ZA1x&=zdp6Hitb2dt8sh9=kVK91Zo9>Li_hRLj}`8?(n2=k z;Dvadt(N4;q~fHM`ukfC2xPQMcSUwM-}U;54xx0`R&5C{@!6Oy@jb1>7c>QURM^7h zBv^rRHUGq3t2(kBh%QgUn&7Rx@wr0RuPFR*>&}&Y7!Fz*8fmTJEk313(iKCz`*wdB zSI7GDN>3Zr-Zq4vYR^q8RF>Ph3rR9cBkw8-^(;Xb+w0}-X-ybIY419wcBRBtKCoMY zJ1heaD1#GA7l6$;UjAJ%46iDs>0EBX(YuD*4|RF*nYPiko)g4ja}CmUw?w0ndz7>) z)>?EUiIUnsUgIU9a}!-xH5K#mO3a*8_2?4`XcD62i;1Vk0N# z`V7-P`;gtc7F3GaFu)$}Yh3}u8A@eK`-gO@q)So$IOn(?#6Eb*sgXc-7`(O#I>A~7 zwSt`$yN0m#oA3`&dQE>cI@R-Xh23+}cx9X_zH!k#u^b~!vC8P$r+z1k+w6bp{N>u| zTlG6tGXk5DnaS*x{~;!9$FTjEO=2?tZBqn*foN*z{z*_M?wdVBM9W7*IuS-kA}xw~ zbK})4o*hFli#B|m*?eDVMOLh)#aP`7G^7AIK*qoREP39p6=z?9f3$Uhb}E6Lq-;3R z9Y6}>r!Nqlo)>)i$u$47v6W>)3u$L{9S8JZeh+rOxJDvM zn6#c=PoLm$f=u7IdGP4F;-ZQV$!a0OSVT1=*~-#|lb${f&j)UYpe)U-kVJfcBNa$X z>moBA4Can26sDK1S6=>wrI6A8J!l`Bk0I`PgwTU5tLpli7uFkJx-xWS+x5#390Tz| zF8b~-b@X-46$2Pq?og*l7|oTbC-F%8a=aTM|E#NV2|oAZt`dh}EtS+9Id|Hnvaq6gH77S`%w{0R}4E6`6Ui`mN>V#MDX!r+Z)ucUd>v}{VO zX2)i8cg)>)%)babHAO^!dx5w{i0Tt zZj#Q_t^2nWxjWkoz9flxNpz^FSw>a^eyZ=km-0t#>6f+@f9$%-?sL2~;Q=Va-BeFx zFLp&z6F&kb_aZ8SPXU*S-Z_6X!*FSqfAm;~K9lkQ|1Wk1nInpSD=F$@zcuBMGzUH> z;<*NJC9SyE=K?R>foPtobuLj{Y~r*^239N;)37m7*Y$Ykwbf@fU^<0CjAhaMO6pE$jO{5HL}N6Nff#iGuo_DVStF zLgkNE0{qv;&#MhchizCxnLN*-OWUX*5w4_?NzF$2rEQH=9qwzNu7>BdSF6GNAxi{| z!L#<0!}4)=3_4s;EUTz}#W4eSz~=aRT@`}m!gNXQ1l}Eg_S%cds1hLn7gb!DO#XyO zlXcA14+XI`j2f7j@1>}8!>%0Uy3OlwD^8nvpe$6}tJzzXOp{BYegp@3!PYWB^W?Y4ZhktUKff6_=^*exCv%b(04 z!0ns3S4NlOL+6I51{~*^+ycH(os+<4qLK67z3^XL`Yq|UdO~#sT2W%+YD$vH$!!C% z8!8^jT}6$**I^e)wo>&<2vHGI!?t0NDiF9kiqt57Wd(FYIiPIS#{aLJ8}cb0S$HU^ zIn&~ki?g*p8b2kaf~D<682JH2Lq1NlzuK`MV&reQdWNCLGo;X8a~+38;bDy#3+xdr zNwrE4(oNS*Y8@8Kb(6CJBgnrquHShojGTd~m2(YfhGXL(bITcpkFziYN|@YV=ND}> z$oznRys)&~ob3^kR$CPUgpc@Xe+m=R4p_(}82DJYOq9i%J4N!qZUnT`lABE?>E#*! z&+DG!ZUx!MN7cyYCUwmYQ-Rez08RUt+Z~AVw$OTTert2o_8K&VD*e+gddi(jiozS@ z*k5?k1WumImQ~{iKIP$Nn~L!NGwXmW5?JGZ2{sGV0a@y!piK}3dYYPxKwM~g4yj2@ zqi~(CoPmTzFgk}r8isq5n!L2lrwQTJ<29=nRuzfH7`n*2YSkD)p83FJZ{5g=F!1l` zi(_6aWjR1Jgy4sM4vNR8OI7_!M3cJQU=DP zTo9}90n(z&a^vpJB;)@JPGUwaC2*X7dKtyoq$Ye07(Mq5UW;92t<@Ja_;>%RQOHj7 zcm$>|%?YtrE51ux@&o^QeGc+7s`pqJWZBIu@%s0oK)W#kAgM`J7{br_!;b^({*b87 zs{w%prnX(=CF^*E+UlJM9JJtzOXHkmWUe=Z-Vv2ycfA8k6kN&73>Qg;F5jac^-cw(4-dDfRQXe z`l<wn%yy6pq#XR)jA6%iUrzXK0OT>Sk1-=kd9-{_+jKJ#@%|YWYr2L4BkP2tV zNEr*`&boz}t*w~SBMQANWL<~(s$_4K;I{=QdtpDmeovUXtKanMoq6k7iW~f7_%f&j z_m!oO9|SJ}1U{O^DWZIT462x9`g--nc%V{j2X^h5q zRBua0zX&_{Puglfcq^tsxewU6mEzD$6S8t5oRFqF--q-w-t=?^5y%jH$(rN_F zY=3MO1kh{-3}=T1OVOqDWoy-9FkgYDiKW+!3%k%18Wa7`7c^_w)YZ!AwDgm|(-EYS zI?o8mVnAWdEfQaeS5SfPWChu~3cYNG#`&6%j<4`OeDy^G}6q4WZy42O4FM* z!h98}eKapky5w*OZgEZl+(Q#M8)K-8g$J4Z9~&F8#?~4*v`gM?VR`p-tvmOu;{ca( z#@%gKdaT`jLj`iaiHGaI&efaOS!RpllWU)`M`am9y#6<5i-mPk==B6;U^N>EJJyM zccjuFl~wV`tuLnJo0jQ5u4+?Iv9rkb9n(!_OFEA-3glXarq+p&U@7F@+;J#-DLIu} zo#ILYHtmWJuou%v!z)4x1p>{aIBS=uC>Y_8#mT&XJ#l?)p;R^67Jq-B#3WX0SuS!C z3am%K4>$((ypK^!bNrw@_qiJhn+rL4Jna+TLXDXoD9AE(ou%I2=d1;J9FQ3gK+qjo zkaW==jKI3G4JdO9+qFeDXv^LVXDHg`g#fgN;r;l zoUz7#=fEHy&wj)xlwZou)&JC?Fu*FDpqq2RsU@W1sPLjMvXb_cDI1O`y2T*DM@B~S z$jnS_u6RG&&xf=6w*}%&f;QGwJylqT>y=)mwT31Z>M}lpHqe57YU@_5l^}C@sPh(H+7A56@BdnDZ=X7Dy#s3DfEu55f16w!H zCP#tF;Zdk5gxX3_v$PKAlU?PE6$v1gQRBoi`d^40{~3N;gfSLn1_*_Dzj!5n zalkC#lbvsB3pHo^IK=PL3|mIPR;kYs)^f(11Q2*BDB;ig$LD&2skT6Pve&R^+XTW?7reFeJmX$fFIKa)4es+l4^hUw|)8>rsCb$Zr1aGE+K%86q1Ej2y6K}_kt&#Vz*3QRqr1AQm@Xc= z!BMaCVt;PnDGa+D9xx{tOqD^RL=HyEUf(2LdN?oK&`GFK0~F3zIZ<37=`w{f?9)Ce zUmOsSzVzXU+l43zP(-LWz~~kg%}KYz&1yHAn}zsfGr)!?>TZ&l%k1_;K+Eb;_}X!2($fj(iUV+ zPKzmASuV}Yth5g1Dy1F|78)wGkB)pM$};!gk*@+sK!yP4swKI1J;?~3Rn|gkHD>g1 zv{eIK3o6>sFky^;X!9M|D&qwyBsUdZsZ9*14#j@7K*Z4dzp|{h?gjF%$ErPNtb_&C zN=?55AGKUN7`^#lT6ihM|28lCN1~k&1QzpNtGx(5^1`FQIe<*j z|1F-%$2u8Bxb`&=N66+Dy?LnMR%(J0TTR7(nA2Nx(Jdf1cq`%++~W@y ztpIe{gE?5%ZsavX-*~AkpnvQE(Nl|~R^Aup44(#II4lCoaW-G;lmd2_4L2LsQxWn^ zPt#zsV<^vI65dmzlS#xQkY5sjx^)s2ar^||{B3;IgW#c|SOG9&=3H(y#X^YPg6d?& zQ9-fmN@N&+;U^X5eGSvIa!c)#ok)I8xP?4n-BN)>_dhh!wB&*?^e1_m=yL1a>eS|_ z`5zL%%uDVT!09kv8gJ!5r-K)Y*hospDl;5DbxYT- zL|Rx-(uZbqGnAElEUN+^B5B{{C(@bJTq>Wd%0u6ORK-tF!J8OgTQ&Mm{6bO0ch=Vu zD$Eq=U2Rxh%aO5Lo+N=xRmM<;)&_jHke_>B*se zQJUO;Y!~f#LZp+;fZSfD_|y${F+*wQfNfVCZ=L3dpn=t_-Cg@MvnilOU#wAW;dn*w zbOaO?hB(jk{-}VMsit6x?j)Xkvj1)!ByzCmg*2UH3CQ2>ZHFOr5a|rrtDYE9563!= z1^#CKl5-xuW1%m09!%|-EkV{U4oGJ6?*Z3;b$e}ipg~McP^eY%>jqcVzqfRw-y^5^ z3Tc<|%)n;$eK_q6W!V>CBmNIER@xYH&b+)cBnzDM>Hvt@o%QWrmH?0OD2_uY#DFTP z2t-O2b|~y-BB7xKe?n6!#T{b!UGjqB&@FglOJwv0oQRE`kdQ-1chy{-^w_y>NPM7w z@GNBQRbib%QWAxxu0AXJnop3n6Jw~%x9^uy?rHamxGW_#Uc?|;hUGs}6+Ims3g z{=S)0^Vx2L!C~PoLb^WtzZ0J zyS@qZGmEe_iEC&cPo@(|k@uetfya}7FJ)*pAo+!KHAqLIpF_91+VWvlbBU)ajNQs(y ze3`>7+k}6#>tw@*wu1I93L&aim~{bB=dn#C?=;|9OWidua5YT=2HV)ahc9LQB{>M^!QL%OCz#~ z?$G|aShbZp9%bsTC{8A(6We@+`Xmzs?g@8RBo&#F4+$bPUKq_dlWBR?Ja6i zWcE-%4ck?8kz=qw8#-26nrKUKsnIFbVi=Cm?;`3F+nTew%K4aonYEQn8;@q*9Aei? zZWC6pR9rC%9Fo`VUw!lV{RzR}=5LYQ_@PJU5E!V-EEI~lD`woF`|rj2H;Zb_oMTzD z!(XDO9FicjBcZJ-xPTlCg{|fPc0O+m3UnGx^DRN=t&g<0{T5GFKM`6$%0NTgkFDhc->qXPga-YpU z5yrx0(oMyZl z{cuHGuaC$Gu!I>tr08TbB1Oh^87~egL$>$_@7xm1|iMj{O zC)9K<&5BRv4_Pji{MY_dKS3=^qQ|D}6{IWoQatZ(MHO$}(B}z#@`CoI1KS?i6IV3X zdN(sD4*?i|rviXpRTQ`8G&-2_C&ITD7v76$iPr0`u5|vzI@^VAcOYz!&Y+ht6Nel95Wulbqv_y0iX>ZN$#v*;Wa7w#}_symUX{ytAh07i46CeP+%d8u| zE4~b)oF-O@*Ho#O#w>hshaSr8`oI)Lt;E9`6rdG@x>mGk>zK=s>UYS290$fz+RA`Uw6dV># z?qnB#_-0MTo{JpytrfCyAPZoMg>$_*Ks{}JKGR+=?ZeEi^w7}{ZnaQ~gOpQK44fuBj%J08EaF|4_ECu=!q; zOOZlSMejf;#_-~8@_=^^(oWc{XH&V;)lT?-z3?4mHC*MH5Pr#mMlW%vzCTe8-FP99h!jBOUoD{&Jo7vOeCv+by4d01KH#%4n3 z`C%`yn%wc5s|L)bO4nms`&H!SA$sHvZFj^e6h^n)u76nmLl_0NUnJtwDj|}p^(;QF z=Yf(Z)|>tbR2TrL`e%4$7`s6K6x@YEM1Un~E#O%fD!1+-5k%&^&~K@G6>=DVVTs`l zm_B_-_Kmv^P)C0Fm?4Bl&&lx+?vCf}e$X|bTRDqVmhM*KWp0Q=RbXgq6u?IR5LeGf z=5IGQ7Df%xo4a1q+6FUdPR3+R{TU}O6LlGhcF<~*#pr+>kK~;3$z-wm9EB+^IJl7& zTe%p7*$0v_s0i=3LQ3VDgqB@@RVq()gOZ02rSf;d*Hn|```IA#8jLBDRh~mQ3deb< z@dU|*QtVO;IMhv_Xir!U)FBm3ID4QV8HH+ii-VqKC&9Aps$FtE)Iad{jZMbjaBo9i z7uT8|wER7?E>5T2{8#94k1JIa5#I*o*q=seiXQh`t(q~*?!dSCq`BFDoELf#uZcs? z>fRUsr_c(~VxK@1<@@z~(1ylIfSy&FrVrErZ65fe?f67db^#=u{H(r(>QQ7aPg2>epl_nf@ZBXtB&@AzCCAaouHuUt1wuHF5P!EF+tBd z5|^KVj5FXC&HUq(Tvxtaa!=}AVSJc^2|x-^atm`qmY>cf#eTWib! zW2`^&YK17^`2k_M8Z{`wA(z))<~?-sbH>?j!l|Q?Kv&0qp1@y@Kp~2vL#wyK)H67P zb8DTGOLera7obx>w*LZeuBCf&@d9E2?l_>{p!)Gx>y;$1o(Rm%%4@iv z#KXgK783R2u&e8OpFRF?^lq7Q2#{I}CZ0c{J`1ra3@gN^=Qx0gD&!U}o)Z%18cH5{ z@))>f5f+zb7@ni(G6$Y@uQvSw;9*%{4M zw0_~&@EvNk_AHsLK|4>_m&5fkqb-P^!1p8e-1ZIRE>6~6j;wV%sJ{CkTL_=Hs9HV)O|vv|IhbJHD@CM1?(wY46WiWM=}6CK{qtW~!qh%-nS+qW^-BfkSxs(>OY>^ulHSbU(AU z=Lo>*46s@0(rc9A#!j!J2nkS*y5jPGc|}aH8YC^Liy39*oxKJ)0fet-VW<4tR+m+N zl0SP?Dn53Hz)QJcB070?rz$iq85*Oxi~|nNR{~qQ2K6>2WGyyPIzwm8@htFsuXZzq zUg|}(eMo^#;b_CGnqJb-7#K;FWI&|b5GgX zak+%2ehv2D&od#}p{l9revk70dwJIoAaY;H4jZJSg=xgrTAIq_`^5?~t-##wumo|_ zOlVB%{KnDhxkZb4BIO$q!2l~F55^C~A%aXd6`tmAF#Ic+F4or;R2_i&Moo`lg;l^o zbYf@CPXTe!7D>crYL-PkS9H!sE|Zh(SwCwSk5<_+@hnq3{H8g!y(fNLv6GgJwn z3_J#@v(z>k#-8rX>rnH5or1H~mg}gMs-DNa*`}4HQmy4V{5UUlEPt4XK+02Lk0Bj| zAg6qYU(Zn@Ig_F0zcEYXh8xYao&Gn!CpX+@+xqeW8%xNE|Jn(#P3jm7lcfTBi2Y~| z%-8f`pF?(%PC$Fl!p|UeRXx@Vp4c4h;ER5wwLl?vlFO*E zhN$~=glcS&E#~~KAVjFg@M^V9c3%AJ++}^aB`E@TGx+4ToJei%n=moCMqd{}4ZE*X z6-ZVLp^PS7RJHvj!7Zqj!kE^!1U~b3_?!gz%eLYkPz-E670PlOvp#3L*!l!KP$!Qp zJurMqs#nN4*vMCZR@mJC5MdvPiJ6ae6N>)*b(m_+fa=U5&&lMM{66yO{Lv*sb8p$+ zD(Byf3eWB=yOp>&RfpnX92YN#oSD^;pJ|dEKf>Iv8NruRK@&RApQ!WUkf>45rFyw9 z;-AqjplHKn*wt0x7OCLB(;p6G`jsU?4~hn(;#kL=gl95;Q|3w*n6j_zUqGZxinwal z#-6pF%=mdI14I-dSlOJxFKF*vz3+d@Us&>$;o$vhI)&G!Qg#Z%RXMCy-olTrD1E~(@6v%20>yRgz!dXH5lLO%-3I}a@?vkq7Wi-Nl7j~ z&~G{PpIGRBq(Q8g)Bh)w)=XFRXutS38?tGmfE2|s?;`^YhE$Em+aT!_Jl;aO6DGHc zq8qz>gorTk7Q(ct8y1cAWAAXf2sfa`z(A>7eEJD<(=r1LX<3tEI}fn+mu`-AK}Zw1G`w7ifq}}F zz?uWoCeBi~tI93$iRf8)6DB;p5;;7pfA2Z*Qs`p)-D@RQ-AMvwOK&@;fnAhXZBR@ZY2R6@ul?1Ob>b*oX#9^R~ z;TXVzQ8GE_jRSU8i&0TESn2jv!=o~uQW!24j&mRH4GMOXxvQ_3rorCik0k=Qp6MXko%-QWQ7wf8D ze<3w{IjO#Z6)hV3SxPbic}gb+pAzSBz|fdi)M@s_hC=y%S;k2Zqqq1H|GMW+{tU01 zCOrv=cZ-wKk&2j#KBU38niBTM}9`*H$8;Jy2QP4k#>v(uv!~3t9*eS zE4eOu_!78-${hTPxjzS`I@4*w0a_aP!d8DLV&(vp>zI?(4%)!tqhS0>ieZjV@9 z8R5ad_t1t%;{HBq0}X=(&D|}3(_(3Oh(&^If1HAhL| z>5^)9x*jWFEk?VvRxivOe^PC!oJ%_NajLP}LQ=vlgNJ0h4ZKyk4O3Od1FV-;99eaI zOQnpJvPcqm(m4?Gr(~vk^aoyw*95*|K-5K}v)m)>?yCV{7SohjDS4=W+gxeo09ra) z+#dP{CIxVBZ!V)gW_m2~36}HnfQq0OL1OK`G)v~#LlmbS=t5mz9eRZ`=;%|xkQc4G z;oHsT2-X8;h+OEG5X_M9-{y=M*m=DjTlhS}J|!`Z3BMT0oJlDVo|4rnWI5Z8;F9dx z`c9oO7s~Rop6G<9Xq7I1)Ul^t0(~gfRK3~_7Vs`~V(Lqe-7BMwfoa4z9dwar*{F0W zDEKNuj*VhoUNk`S*GhOOgUYzS5nRU>pQLt^zc1&8&T13<{a#>tfGEGC(b3r(-cbLy}qF43reRdRvVV@>>vce!W@ z2*IYFq9<4(RLbyFA-lg(8Pfo-`rO}U?SJ?wqTW&G#kx6#T?ZeHhFJ{Hk~m71oq4;b z;({LP4(T~~9`;fU47~jBh*Z?jtsl(y1@Qfx21yvl3#-e#+H6jy%6R-HR#8{m&!}7M zoAPzDy@AaDj;f?9|L`~&~)~AwvK^A zR9H(NqF?*9HWSk$dUcZzfdZy3Hi%>#$7F(qqMI{JQvu4qqv1kWY*U>2uAmMDy1U*- zMnAL73e81-As$B%Y)nuZ=$OQ*yZGbZ1|2M)80fM!6$(>xvMfWnNuNGnp+B^n-0*1j4U)05>Xv$PNl|~03ytod; zjMNI%R;ANYRP022q%_*DNEo!Veoc-aZX2c;U~EqYq-q1aB(nn<;en_Bw+SKV(JWUJ zA3mCW^{PLIw8(sS{ocCT*-B#A8_Jy6ee|0!qo|{-l&i5nwHo%rnGx2W!_i*!Er&j> z)O>t@YCHiT7BP4l?!_Nx_c;J@O!MegEy`ugUU}iDjMHbGg21I93%1~#m$(uGF&O1HQUyI68^{D0kQB9XryPe)oj;fJ$m&IkaV)R z&A(AXEbV?K_hA{3MWm}{^HUdzKi=ysvOpGpeq;J@6ei71wAnGs`djJ&tAOb&NK~Ez z;mbE+)18Mv9mcA|%0lMSkMDD~aRSNg1!Xe0!pSR>zQQ zExV^vtnEB$%c^iTBrj$hKp^R9?6Q|9jV93n)(w+FhtpukQ$Gm41rr{rH zGC`&O&?05b6e9Z&KdroC@_`1$T&GWeQP#v*{E~6<@ycPkbsYyLUK<#;eQ4mIy)M&4 z$lJR;ivlR38{GKZwjgx25E7UcVbukHnOf3l23)m5PW1Zn;}Ux-R0S_yZZKj=-&ajB z9HaTDqVBCKmf!>r{b@^tdqVf|@5h}K4I@E-XEsV|t{YhKloFk}eSkQk6PsawXL3=m zy$>5G@ilh{DY`j~pLvUwx&M?tcMg-KLd1tVllL@n>1b}o{lmpusnQ$@c8{8k49owm zVjP>Tuz2M{Uh*%!Z9a@&2|Q;AI0-@ww8=1QOqXG?gdBpRxLiH(NZYh2`77N{P8~~u zgp2K&AlSpBSfsyE@Vb;4^w8CR^iT$iu_Cl{vs!XJm#IUCj&A8Jlww}Vd2HA47svWlox2wLDJO)KmKdYa6b zs*j-IOD3@kdQ>PI^XQ;|=|5=`I9PuN+>+Xq#n@oYEoYN0Tv5h0xCH(l-v%|Ckhtk# zgji{<8U&HjY``b?xR55E_DroD)ClhY#Qnutj#4ztQ?jG(r7F#vOD(@Sr>@7Mj_`8g z+%*A%0q7jw$mgk8eQ!?HWr&%hmq;@A>7p!^K6tc2eaSo^KXQDu>-I(eUpixMRzq*blqT1pjCa)TqL6cG=Kx2bwZl$Uw^h7^EQ&nAC!u+_?zAC|5XU29pEf4HR`2vC^qZmm959k?ZVhFBF<=Jj&}bN6#l(;f+cBhC z6g;*d|BJCWG|EQ_d9xeOF%@1h1135rDr025xlg#IDJ;%t`q>7E4r&pmrh1O@q`@*p zDRFG*^i6-7<7i5!Ia(<-rnQChJu=27r2EY=9UyVMHNJ-dm()aO%fgkUF`7=Cx%Y0y zDu=4#$}(Sn3KhF=%|A8ggc1e4Z-x)y`?`*;?no`k$ZJ7YDF_DlseJMS)gR`nBo~{A zdnDFsQJRl+CJ#mgx-pBl5hVo1Z0JgMoEwIcFj;g4-{s&Plb;ZVR5r(}tCSa#8W{OH zrp73EPbGTO$62m|HP!!Z)E5~#$Hj%(F<B(62}C)hR!qB^fk;!%9;uO~Mazs9hZXDe)rTgH@)yE+&K zXA0n0sKOYdH1}lfudK0h)(Z-1=n1#-pDFH8iw6q(JK>f0kV^m|)7_Kg$D?K>(|(QU z)U)!8+f(F{&OqcdGcU!|$1Y`qHbxWiUAww}Bd*Fx2J27LThVa-6!}q#BwyW}K}Gc5 zNS4B(8fknRAzc>!yG<4&lz-fl)LpcB19s+)Pz9y|o>Q6Bo^{~+~ z-JdXz7u}R+C}ihEeKzcav0}0^U04u*!2*#M!4E;%=`%gK`QOfGAhLgYHW-4~y^N;z zWc=<>tjlP!gg;YD;=?j zvBDKNeFqjNqZ?#F3rApxy>!tZtOwM85VQS%UhzEaXrgTRfYZ4G5mEzxnbMNJ zwf=-44Q0`8JjpH*WrXPg-9-;5#e~$$>|DD@AffKo=PgawzJPf7*TVET$TuTGjMR5vU)}b&MLtiD1C{1~m!#R;?Xrsw45M}5MGY6zDfZUmufTz8dPd$O2~ zH7p%#iLc3iY(!Vt7skg5les^(k6;n)aU>$qn@6@=9N9lfBU0~#tz*itQs==y908)3 zH<)F}91*(~W|G5M62Po^-tk%l?M6o^Fn{p^hX;$!$L?td;f#pqi5u5{(2?8;4jVO1 zi15a}Kc~a|SIyx(#o*A>JNnRQ5k6?;*$DyZU@o9cI~9q88yuCdvsYxcl^n~FZvMS? zX_*Ip;!R~>8s%dikJ@0l(wjsP4o{Oqn83$;r>V7?ASfibSkPhT7*_U*To za)_OR(YMmt%aArA{wg`3wlklnOpK52IP_k4wPx>j$vMZ8D<=5is$ymWNHof+TK-(P zSMb}N8+h>_GD0{1dRZx`lLZujdc6o@b~QPZUgG4N015Si@BqGlrpsZh{85mzB9>ub zkM`w)rQFk3RADdO;9XL-_##4|PB@y|biLeVKU3{1=M&`Y$aInP^n}zs;JqH3dd9aO zF0zgzBG*s@x7y}fE7=_l0Coac+g~WM?i3Q!CV67WrtM)4#P{ek(|I3B(VxtZF5#D$ z6NxD=@-uRI^n9vhUPvGdxnIhz!d;p?5ZlJvyTMIr9F{_hu# zus}|#hY&y=Ru>)iw<8LAhl+ywS=SYJ4xmn4d)z=7&SuS7b((j4M;Ie3J|ZHd3l{V3 ztilRRK#OvJP%QPP)5jorqMuJ#KbGTJcyB34ea--kjjq8`R4O03r z8>ek=7C}ayO(F3?KodEFVltXTQP?F=A7>0~$D^5ZDTCQpkVRnDF!lgGqP!zH0`aIIP41@TO%<2*sp*)!%>z&G^-psmOtJ)@te zPWeF(@LQWWB-!e-a?Th^vGJTxm_)08F^t;eGcYcHkJ?AdnbbruDcX~bO8>(e`WqcF zW0!w%@8_slt@(_%pq=%gbIU~xHi{b}Luvka5+8y3f7H&WyqPR2_Cv?3qYZ3H7piP% zE`tZvc%OS-r&vz<=$Ah%w4U`|DH^1~E$+B4wj(`qY3Yx$)2z2j^h(jv|1J7|!%DMm zj~lx)~4N2`Ozyp)9^_3e1}Cr>s1Heyasw`lZ}?8fVcaxqppQ z2>#_FPH*U-|2Xw-Kf54~E@tDIK#35m--&r`5zEmg{Z_$PW~z71Sw+bUs`F{wF=31i z>bQ1L7|xw{Do;ib!YV{V127tY6hH zt-7ne>(BgD+R9k!EmX{Gi#avaZQ{p3-Xmt_skIDJ7jy{f=DK=x991lTcGofQ`mMwG zUL71ImZU0e_~b?lkeWsy`4O&R)_8H0PzV{4WF@sNZ1v_%4}%hKYO2=IaLk z=FxN7eN3X9Sz{F_Sc7JN5RNe%utj}Q20~7fLxpw-^^M2~0R*N)oLNq+@{;-@gkpR+ ze>3V#SXzw!0*@3czT|DtR4Qy&nT-VGwUC*y#LW>Q^7q$>Sn@I(O7c#;4O(ZHzc5|h zzj*E+uwOLfJnvZliNUbttS1_*8kbWwyP@_V%7Tf}V$cX<^IqDk3CvJQP z6xmmKhiGdw9f?23hPJmlhnQFOv!8qcp$3B?T@dthB7j?c`D1Km>+uT>8YbOT+#G9- zqqk2E#n+R{LW(z*_j?dAy>wfiJlnF|*`t*(wFi9}1yZaxLqXeCR6}SDEc4RZjMO^KsJqr{|(VL>;rw4iVX`kyjLhIfPi&Z zyVev8=~zHZiAqC!i27TjJO)Y?fHHY03h%s55&tHCwEWOSB`H9E4K`=v&A1nARFz2u zPaonD@zu>J{txd~3?(!Lnbuef3Nyjvi^R~Sk$*Ah$-4>X)0T*674%S55P1&Ql1z8K zr$rUhi-C@{rJEV-(>z3MBwc)n4VNO@Pml)5CYjLq!T8sj;lpW)oTQmljzRYGij z9#Oh<{+L~kwS~hF&P`y&-)f`GkSO0L3AC-csBS_P1x#BmOa~LXS;MvXg3lVEDDZ$G z0%HD+ix-famAgII8Gl%=6I?C63Rk+Aa6z|!qB&aEevi16k5Zv@07*c$zj{65$%%_7 z3kp5p!I2x6n^s*|rCq-_n%MBf7-aoVl@z3F6(5dW%9+&8frTahkm(gRGu45|Qf1@>HPi%58UO9$ZxnDnDHY`brKMGFGl zXOCY+e`}#I)(A=TDmGu7>8j~fBLLCj;G4)9s9`)6=yj~Q&I-xabc3(uiCo9a$_WXWAZivB9OaR=gu-F|`Gg5(`cGe?t)^=WwK0rbZAXOn0A#VJ+4v`n&g^ z@ywM3z4uakDyDbJ$k6mdW4*_m+#VE}dE{n@w|Yut&)IBol>Y#4xta4F(s&%>P!iST zHN~4^0#}{|U||w!-R@EwohDlP>z|M^ve4VBwb?-0y+cvzY>ZHq@WM$g4pdaY(c@K1 zf3fTf3ZD05))T`V&DElYLWC5UgjP>I^dp@0%53Un@(LuAqTG-uhNKCmY9BGra zYH3&adz%ZJQgBWpe4R-S2=(&cJsUoTf1p5ztS1f+ijwOCwrbaVVGcS5c5M0!^v)SH z@Av`jM>dqkcRS0*WoaBH551uQnUGX(4#IdfK~<*U`KXO^He4<7WDuwBc}6?ccpDg! zsG-JRdrG{U{s@OgsgHNPMPq2tz|Il$pd=V0!lrzGC z!Pxdu=ug-0`vPE1iIguV`zf2Dyg_#zjbLxP`z#s|iMsofEr8BW9C9oaZO9%8kRqF; z(`q0w@P;EZC>U^GHUoSxhi-tQe{VNYJhq5Zz001XUIKkiN!ZANo=JYK(IX7Ns4U81 z7jWt6<=s6$dYQ^<+sHmR)u3AbO<>J`-(Q_fv4N{+_jM#~RA}FRh6Ye$#)flEcG7$v zh^~2uL5jy_T-Sw~!CM0S$^)@H${5+m%MU*LHgo1@^DyGe2Jf>?0Vy#Ne+&Vhfu%Us z(JSzsc4*Wb!7etlP>TF4B4oTw)k$c7o}O<3Q@tsyBp!WAV?>%7$VfJOX9XNxel!!;obh@_$*5u6?>7#jDTnw0lK7LYfPAw0)ae?kaZW}C9m9Mx4@ zn188c(!DiBXnUk;JRF#cdr;NZ329$Fc4J^pXTg8ldW!@%3R&>u)JXpk&FGY=ripoI zC$iwE8Xc(9O+t;#8NH=bUIZ!I<#C zQRcjJLhDpknN~rGk+B!k zR~AP`&DXtia5@kZ7RCP(ZWXrnIk7LRNfSXho6atomen|t`{lnRd)PqJs?Y8Nzf~n5 zRg;wmcLtJWn)3fpw1V*C^XQg?ed_$<#jpokxOu;%gKJiAtrwXI?X0%M6Qd-_` z$pOJThmwbpf9z80CCO4Lx-FxPh~CGhM6P>7+@MhVj1_5r3{c2hb9q4fw8!EWt0^bs zuN)QiJ=mYbavLvnVUk`JNV5*v4A=dE>@0*N_YOC<1VJ3zK}4_DPS8(@abk$uz>Mw` z$>nQ^2Yo>%PETRJmX@0y?SL{*l`_hp>H}$rW5lLSf8K}EkP|`_=Q&6f-50(D!p$OZ z1V)MCp17B2D`6mnj0p_|W3v|PP_!dpNDW=P>HA3aAc8K)PIbS`&}8Jx7iVE=Z5C!r zTR-GbHo}$Mh?6U#wdF)>=IMYsLM~fjGDQS2$Au*7*JxK`;V<>tGLW`?JyvITP`WHV z+?ycEe^e~C6z%oa%(?~MFOCKPf0{)?@Tgcs!PaEHh+8a-j!gOuzML;AAFj&Hkzu9Y3MGg|-U9EOPvsd!~P8OnY$3 zJ%OzN>!8%Wyk%>%ZU%fpM~~@|&C5}eUK#SkYE z3-3s*)hb7B-#YE|9Kbtx$Ue@rcx*<3@_W`X@&Nvqlq2_sOZNh!1{MEWGQ;%vws&dg zu%B4cBbiV1N*r90ziYUK4OTo(oA+9;N@@N!z%r8}@$5 ze>F=7xj{pT;?!?f6=qr_&D_MNFpQPwuPW6KZSPs zLX_A7YgfV(pQ#Z1xD68fY*2fJ(KCd;wG|un(vx$1lK=&}IWCXNgQX8$O6Mw0D(M7x zy>tWa$JMF!BE}Vw=QisD6}}=7b~CfT5lPi`){ylwp~hPJjl7W;5Ll&dLb`Vjf1NIL zdBhYuT1DTNrI*cwSDh3LE2^$igSmee)-zY<_cNSCSBgj_kh`L$^V;~|mXU4aKZz|qmnUXP9VgJ}~euSWD8j@Q3e{%D8gq5xt zHY_k-J)9$~I-zA_jKSJRmc2GWoWML@g`}2FJVQg3fqYz3J-?QXfMg7yQOo3SD>7OK zg*}N)HD1i3A+we-A!NV?=Fn|#W9PW@>-gYhx*`J$8&!0LCE~jY3evcb5I5$R=?P$L zt&8DF0j#O87=f9Z2~Vw+e?7|qxA(L!&hTSe3(4B6z&5hy+sGj_AfHG~*GJTf7~{*E=8OOkovnY zN${fx?#(AefmhP@6Zj@WzRiC^6X2)c=aBVJu*{KCl2T%E(T_7^zu$YaST`QQ-!&x_ zB)@Et8JP_I=ox*fT5fkE08SYP@1jov10g1r#lgL|3W9#j1CI2Vse=8>})eW9a zT~g*gZCP1_!beNOe=&}zLJ!QjIr!|E+|n|)u5UL76k%`?_$()dx{paCe8C@ZTc{8_ zvOl<=AI46YLIxe6Skk;+t`0ZvA^d21n!0QWnqw)v_teX)$ANUXYmp3?N~@eK>L4v6 zzi!0W6M{kKitR~88&gC*a?&;$m0FY(`l{ju@bmVT zkI{v!YlvVfKc#$*I{9fu6;$x^?d0`UMf8=u<8?4vrLZ-)Mk||97`7?%fUZ(k=$7x! zmNV{B6{M}Dyx7QqG60H};E7Dbpda=Ag+>T*=qSMVf9p+|Bmathw|Y28q6{Dn8Z-9e z+aedxoh0$hj3Z4<%#mRGDGbKryxNIdjJ2iJd*`&rw-x+U>|#`F0s{6LMvirMrEQ1U zl9_z4c8u?Hr0m6*DW51`NQ?~s2&8N|}f*CiF zcYt)rf4cma?oUPHEVJ#S5mSZY7Rd6fxt%ByuN7m6+hT_9l6G~jgy8-oW`xZqsa#x> zL}nv;^<52geBMev)IPmEJZtvU_5o+^3x}Xkq^|qsM8qVlV-%DsmRznTh+Q=l8^oda zX#RZpF8(0Yiqnt1W)TA@{Rw@vA)pkA{3M2Ne+6P(3EEtf1+@wEL-)ijkHj<=Togu6 z%ly1qHwL?|>csj1`@{4}7NLk`YDJsI=;;Z9wT+gKZ=Ioh@ia(?^YwKFL1T~P#&lFF z%so?wa-*6Ex@ysJl+%2I{?_fzCsXh7zsD~#lKJiKUPN13jJ2f z7u??&t%Fpt*7PX)JN%8yyH#7Li*ibZ#ui&nR&B0jfee1c<+1UgS$#>zD`uS2 zg1KW`G*y!B&R@B^k=84ZPS0`IwL0+fQD~_-aOx$B@tODTXkxau4c_5Hv}!(Rf3Y6| zPTwJ^07!u1A9d9sF}4j;CzE}2vAyy}3GpgO&OE!IqD{c!oJzQgD*YG6`Ik;tEVhZ< zJ6ptItkD~sOQ~Vco6jE#%f57z6K*{*t-H=b14{rNA&&HE6OOpf6|#nH8&v1^ag(Ye zlG~dhe^3Ih^DTw|G^%~uD__1ye+qy67s@)2-Md(H$UV*}QO&EPIexf-HP%d)Wxv`4 z?N@z7T_quL55}1JZ)Y?gfPALFG!kV~(bvQ34;YS6dJ2Pt+3kO3v9%{5S75Gq89-zp zQU(Wf5}u>t$Ns-Zo^3U7X!iX5W zAo*WIjeW)_s(`-)0{5buf9f|HYga^|bG_TiG%yFzWU|3qjZxa+F2a1yrOM8w210O@ z>~I0W(Owr9D9!z2&BT!b33mVCcX@jufr)?gi{wyZ+SspdIro$S`&$7k9Jkv%R%HI` zlS*061X}xq`PIFhp>I*Euj#Q(%mRumVf4QyPFK!`7@bAmb z8&{g-2;g|I1eWM@-Y0t-$nuZ6I^A0x2l>`)fV;+2*{<~x`3ZrdEi20{`xRD7?aZaI z!u{f4HGd}^K%WoRf3?=-bF^@T^+2%(>U3rd4(y~}+lc}?CabXD#^xwpw^ztI7z87h zi&k#-jBBw*J#SNT@?rRbrKtT^&v96iw*tkLQyn6Fq}a%SxfPg+i?~BL=6;$T6g@_t~tXO$Gop@O29g3MS2gzn@6NRn`BM8I>ARCEqURLT1 zZJ^@$>`5SxeHk*ejzEx6xf=RDKbRNzqV9u3(l`UaL5tsGY;P}v6Pe@LbssCPTiU#X zoZTf5Q2HfY>@6-p#dLJ$@S~-U*q2zw69Vdi5P|f8C`1d>9M);A~0N@M7(IY*>#x!%5cwZ zYRnKrFMexHferPxf9yC-JW#bOtt=`Hn{|}6t~=;9#9or=O=^#!`FQyRDFuqs3atU; z&$#yMe{@dHjWem{Dyg(y&!RW<_;T!$ca2z6kCwT@4tAtt!6}TvANSp}=?mNT5qVNK zn5}Kspv-_gEQNn2Q|UKL2=Q4dXENTp|GK>Ku>ZMfoYO(%Gv^g@=GSamY?G6Od%u$U zg=;NUHIYt}R}>2lQMCu5#xcgyO{9CK40J+@Ow5x55z^r;~%u9^}vw>MspAbVyD zEKka z9#KVR`ftrOXmbo?`^^ORJ1k^EDCSjmYL@~}27itgq6-TmL&`#Kx-GlmC=8BmIaA>R ze|YVfRA$Om!35+Qx@$TsKd3C4&%YzHVdApmg5dI3!oQSU`{3 zmjqoj0oos^%9!2?Op)GeB#EX1<3N*ce;Nt}Jw(Vc?45{`AKGUvRO%aJ9$F^5C7CIEV7|jD8gvG2cEi3?5o-8e6231%q*Ff8X_9 zSzT60_&TAwcise$`1>OjX0%v^0OB_T(4!{EG76+3pjh}AwT2$b{p8|P5O;wkwd{4t zXA6I5J_pFvSyk3S{&+f;dw>s@=AtG8Bf`HqUJ9Kd?*8uq`RH1zPw3hM{TwNYM!v!? zHr7NdcHD0w-j6fD7nw8d?=Et|e;k(cE`l0@)l2qA^e!-QQPv>GE-n3aR0E;b%Bg&1 zPv`sblNV(_2TAw?K?${m!bHMDc<8&wYT z2t7$}+?DEH1_huq@Dz?T=Zgt1nt+U&;7&6oB}e8ANW#gNNpI>?{O*ns$>C~}assnuhQcAQvz zuDVU2aH~tG6k;XS6y}-xlsF2};2)-eQt0l}%8+l^L&yK9O3u%Hf2h%gMOLlA;p0AS zGqG^=E!k87l3o{@k@6i$*;$vHr(sP7G&Hx)MiM^ zs>pD2hcO2xAjFSRe+<3j1wXZR$JvE$F~&S?JRdRwE$*V?<&r2Q*^|ERl;9WZ+u4nP zbmuNhEW=?m{=nQmgcF6Nz`(}+wk~yvqMZsXsctWRjCaWSSY3s9Y6YXI5(^%m)-81X z;24W(Zdrx?KPlOpndTPCj};o?*EM@gfvIP_$&}3NwPP}nf4ji0ew{Fq{Av%e5e(@n z&c90#2pzkSnFfJ4)W1Wqz&M1+lgrQgQJh<}L3-qspo>z#<~PYe&G5y~=(%*MqV_r- zHVr@bljp^F2K2aJc^=TH)Og;FPa-hX_rkJ!h!8JsIm7~Q?BNqSSd&41T}t|1NZc6u zT>CnOt}DYBe;0lxgy_NdqZt2GQt^e$^YucW(x#b0-t3DnC0z}YE1epKYqRTl=Nzxg z393_9@@OM|=Nz{^VD-%VDKKyjhXR}lbDoUP&B!$g3vthtqq6OF( z`o1?!Ct1cfiYpOO8zbQ)#mGcFB^?omL_`o1e=#A!xDxRcfkNcX=yIEold6H@QSS{5 z?cpKFz|=*Hh?|n^^U}5gGD>`)tT=vYMgv&gTeo~|`WIBGlTgR4!Hee6LzQ1>9N4|W z2ECtEQ|d|ZAgrSEobTgu+h~yFgVWXH5d)j`m>WGJ6nV9G9uG!k%e3pt^7mj+S=2a4 ze{D_|BR>D(5ZkvS>N6IPE-aKv1Z7+|c;%oA?WfFqM}6zEYJ6H1T_<0;xq6XAUXTCsJw{?Xe*Nw{^<~JIAE>j=me{X`+ z)B1-~$C=+ED8?7#BIp$uok!hSEiL!+hF`(ovcO+S(Ny5!7?3_T#U>k>s#G(LeL0QB zI3}Sd=w}-7n&~lXq6U)coFkmPie+82f<3mdO9SD{`kLrvZKh}jEa1tJP%+bVLM$OB z{OaUjIYh?!G!t#tqrJ$8YWeB1e<;kAv=_f7PvsbodjtoinMaZ*SEF@Y5hlTnhjt|z zTtdz)`s``_)KuQA`&Sek9J^C@eG=8c!$Ns29VT!LmtHymSpWa8{@*n%6$P=P4K|un zM|4}7t(R@CgEvyTtIw_~;x>zcqbC@MwaEETUP_45XNOi&)))JsChStWe-HZCJh_=* z`NRO;V>XtD45&cOds^0%jiV2Atw?TaoPG>2?&H+f?2HXIW^J<~+2!+SdkMdx*sFIqr!@o%t_l}>`Q1V@lzn?T_`vlSY?u?j7w~u6jZ0v4&GYdLabLLIC?8MuT`vjm_lf?)Xrm7pqnXK zg{Yp~-7Oc~sR%N*aq^NNTU@*T=#)|TMEM~Z3Bv$(k`*KER|)?3e;q-z&~-(Bj8f5a zcSBnxqAg=B3fuXxH%K=we2uq8-9TY(0Q1;Ii#Srntfhrnu=><0Ok(=@L4 zP+vtv#WITN6D^}==hSP&*~!pcawAK*OZZFW*ES*%v*-h4 zI7h`4@KI;ny@hr;K+;yjd@-he@KVU>GQoF@CiTMO!}OQ zo?O=wruX;r%WvyAt#h6IYi}t8n4+-sM=fEBCXBla_|gq$IdBDDxRLT4>BlINsOoXxXx@JSMx0Wm(hHvk`k~A0=AXHt`~vLRy{_oc5t;$JXs8 zBt8FvaBW_Re~=#-`Gnf@f$lZlKzTZiF8w#;K~k^pgX1P0kwnG&p#PA{YcDRS|4#YL zpz!KNWJE2R$;um?-uW4(EY$>`KW`1hC`da$T>xz>k`O<6Fi*ytg9!wHx^^WGam8vzS;XxU?$QR|cB$Hu*{5UuK-41cS6 zN+bML#6_fw_4>re^Dx#;0!EPkNro9Lq4O8CP+RP@FF!3cLZ5H0uJ)d*7^BHm0f#sM z&L1dJk1ffNRrq%-0vSI)>Z6eNJc*!D4*zX2f9KF8AuX5RlU<8t+zsn@OcjSL%wYL3 zo`PoqP=O&=LtIVmEJ%m>8)s{THvD14K(bi=@q`$rJLldGV(Ms|>l!)ZIy6bb+ilP%`X!L30$MPD@fNekk%E ze?JGl$&`=jO|L~k2;^x_qLBLd2ZRAPFJ1yM@|N|nFie^s+4eW$%Y9)p@JIq>>;WpU zX`=hRg+ST$8jV;I^j05`{glGpU4J27=(V~hmFQEGU?t}zR2NdQxrUH+>CI&9xqM6{ z-?3?8kh(6cS4%%47Kpje@~GAn-$4xL;jjhm#o9jX{pSH0tCr;ptbWf zmg=03C!^VCunAyDS>X$D@2tn)>~hPeRtc~M-XimRgnN{$TR;uax3XVw_swp|Kp zfv|YZ1f|<4Jng$ND`VUM#dGAz06j(4(>mXxZQs~CUd~{G{wQ1kc`68BkzLW2f9*dj z6=GK+I^M-^N;`_2y&LvAQ`^4AWm4PDvxUFh0CvmDpAZ4(8jN~MDCMWi7O}+bCK~#- z)(f@v!E5y83e9J&7~PM)sNISAacm-4 zXdT8tBSz`~`}#(6vrCG@%`slIe*i8%2QM1#X?0U=QRk7twRFQzmRJ0qpIE5NGuN?( zQ2>V$gLaD^AkaD7DbFp&)v65$cK$iB=!3iSqF>ZDmr zXHzMe10YGO!y0U$qkV%5Uoja=`Ww*sp}@d$Sthuei!C&;7m-?xI!QG)4|2;}$^UYN z#s%uwU?#0!wR?_-=8yRvf24y5yI}hB?8D!{$_2k7tYIN21nTF8Dz3h?_eqVXZ{8F^ z9gBmPFHDzs6lc5(BQsaW=P3%_Ultb1+3pN8V*1+91GrLgFi4REKh%s)LNdplqBQwQ z>HR6oRcs^{UO2&m4+WWIk#hED4^lX;;5%};8eu#*>_v_1&Z(Ere~yAH_DXjyrwXOV zYTNe`&s>ZMqn9YQPZoU=D7~*0H4ch)Py~Uh6jlW^Z_A7L?6LA?F`RM&w0FVi9*0$; zDmi5ftJh9)`5OH6@CxhkUsE9>Nn<8ClzL&F&3WCc?9C_MY%dRv*_IMgf?G2KHP)J} z$hC@j>fm|EDF)q*e^Je7!@-(WUt1mPTRwG|u(&hX0*Oa+Hj{aNT;gR2~ z>=Vv`-NCC+&%l|hMm&pS45oy=ZnqBhmY5XIoc+&sW-1O-fW$+G zybuDac^#0TTPUeifJBj*un-1QmMNVZSUG&2@4X>cW7E#G2|{>2jqWY#=}Rxl=4Ls{ zp$xr-+P}v6e*~BHV9|-w#-cDETk7$yCHz988N0&Q_>)rum*|Q-OdwC*{z3)3gi=t5 z7Z_cC$ntWvq|Vnt$$|>om>Kz05<`q;3LJV}aLwV(h{tP_0j-??)4@|PdHUCp(QDK2 zA{$U_2I1NRKht$FSOq(cbcr`(ndp{fXsZU}={262e^Ktu3~~FxPRg?1JkQ4^k^c-u z#{08v^G>yKg~=97U{#R?B`c=(%V?lp3rlQzZczyrLf^C`TG)iZFwt@s(J4=2cc5O_ zQH06$yl29;K1KqlUi>`vUd~vEV=Jl=C`Scy;p{AWI1B$j0JOA38nckLF1W|7LiH59 zzl$BKe-gK09MdZSvXIB#e%wbO@wxlK_`GKS&U@-dQ$Gm9o%2^~yzgtO^MGJ1oBn8* z=Hkx~2DL+T0t5hX;?2tsSl&-2JlV}IYYA0KdQcki=z|Dhuc&KR83_sxr6*s_##EbO zPH3W&N}{Hjix7s1%^q$XqX;bQ4~y(H|nbwuzWF=01_Uu$yV~5z*_wg)ymR=!#H%=BXmJ zf6-cqJvV3g2{GY=+?8nWFkLqxS0lPeuZnL-x%hmSZ7dFcK0CSshhujEW-~ibl`_^u zIYpk8UB-h<^d#*0e*3p@M@tBM-66;}j%&458U*KFHP77--5q0Fh^!s5o@jnu%5gWn zv|F1TdbT7~ZhT%0?VIgVy9?~ogoS4=4iQ-|C!wL;*qkK>ScaYB-fq&Uj(;=f9jE} z6x;E3bsxS9TjV_s`b!7OGi}?ry=xD0&dFa$C8f-U#_=nBi=C7P2Es!2S0aZAEVytI zL?r-UjHMYI@}+9?5Npqj6A_EV(A%O!TbB#wpSygJzG;Egl}z6mOcsfEO$Y{LOJ|bO z6&P7g z54J%TIz{vo@q{=xs16Nj1DtM5{&duugSykVxM8n952kmn_ljya*)S1{e};0(&HBr1J=@hoDli-VD^?3iKfeRec(uQ zwT4r8e0>tSgPk6(w0l2Uf3}#wIk9o>&4kY98fU>JA!02Ifspr14b3BT!`=rEd~7M^ zOw~;t*(n!-v!A3|3L*k^W1nV^;lXD3 zG=O>zml%WaOiABp8 zl=FEp4=HdPTtDMne?(QTpSA@F(3t@9kF*4*PVeFOoJ(1`N%TX?5>@os4&w==tAobvkhyuQy{MZ0DuBFD#q~$~7b`zRba)3~W zy@3?`GdrDu6XDWg7Ujn`kaeBBMmHH>3ypNXo5z4Gf2<{!cJtu5d|@2jd{;d<8~La_ z$#4zO;_6Q~T&ghDqA9(icz5|F=)6sVSWmeW)&f)1(8P>j@db?9*b#@tFu+o8?HE@e zR}74qXVOA{0*+x{z_j_=S1itOY2D2n=0_v@XTIW3dWZ$CzbM?c{ift8 zZ8fowduVroM2FHhTM_^UsZvaoul~QG=S~I2tb^%I?w$Tf86j`>6GM$7+}=^mOmJ~9U)gE%NQjb zQp#~!*j%qK0K<5S?gO)o5RugUc3y-olVm95>OO@_4%NiJ%$^FX$-mHeC6LlTSvZv& zH}k`H9G=7wo4L5E-r98>pzb%tFR%O*9cMZ|22y%08A|I;mPyOz5L{n*u#yjYJ1GwC zf5`0_&$+^VA#NHZ^|~3Hoc_ue3srfPNZ7GnK35N%Bl7kDLgACv<80|0V+Ncepblgt zwq)c=uH=2Xm}3r6;O){m=iuFLbI3Jw_6jbvWU6urS83_N41Xu$u$xX$iKB|~a2HOq z7%vvItk7J`p-t(dw#@h_>(>UAohFj|e<4h`p(6|-F*9A+GfV(QX|?5K?z^dxn=*>9 zUna!6eJl(M|HNZ$Y?z|2sZqA806j&~ROh9waqvVax_NRdgeR*iy_;GHIX^3zTNBFfF944?#Mr|By2XZTzlQb0J6ZFxhWUBRsFH ztfmxxnsM-SLZJAXnT_iB@7rj_w@Nv<{d#{=Vmdad5lHEs4b?|Ph9%Eh+d#Ol8%yHb z4b)D`GWkTw&mC2HF3RXRrPmF}f3Rnxfv)ag`@W*4oRm7_fTN!kMO1YyKi$VYaM_v@ z-4K|-)JR&wzTcE!;$L!G`0tof@c*}ZMiw`!KR({NB#EKO2@mvc_<2W7ai&Aph0PG~ z-~JNkV8lLMplnau$He=M0a+?^9ZoI3`89up^Z0a)$>|tNo>0bHUKT%te+_S_(!uwF z;%iQ^Ufx4r1>^*Sv2-Lk{KsWA7h|f_r*CyXaEPAKYh#%tC)Os+z}Nc+YC1kbRW74n z&C0+Zxkk{srQ$`c;M7E;Pn@g1t>ZbT!KIWmrky{ArulQlokwaF&bDoKCR})!rVw+!=x`mA%R|8BEvF}^-0VC-f6r8?;4^$|P#vlD z3;SL0w7ToQVd?}&bkvftqR&k$7CB4xQHz((e#V}y&J!cHF$(HdUUW?mf@oTxQ8_@7 zu3B~~dFZS*99>b{o%tk`>`VEJy!in2i~B%W&5YG5(*pq^}+x1g;|05a)BCF4=TM+R9wvfIx8QUGclOMKE9VK6# ze2~-1F;MMT^6KN!)G{bR;1F}ymk2W~5=jjvhfj`L23FwB`|0YCt0gB`*QxsWwP`tW z9>#F&Ojt~>a9Yjz@5>IgD=ElfCNn^jMA9dLq0)U6!59#t^*q zbGsVezTqifmN|~bb|Ty2SL&S}#XAZvuFrIq>&j__t7qy>^oDS_h5ohoOg44N4KP)> zNAlgSL|ALBf2`I?fU6nS!c)F%1#|4fP$;&7&+ng%Fy;CI&w}yuUfMN0_?WF9@g~+{xD{c#x?$9|e(Jbm z3t$)!b+;NraPQzV2oWSBakq7*OR7*6BMiNBkc@5}e+$y`XPKlf{{MAHl=p(BG0;Dy zIixiH=-LO1g0ndv0QW?3Zbt5KyA2e zRf7#2GS%-iHzI#a5(&pj7uXJ$R(TRBk3IZN7!K_DIStk*D~x4Xhs4KH3^JY@N$cy2 z;3wk`VL|XFVL$=x<=S{A_Bgi zm6g0|cp)oTe(r)VDzOyNqf{;GFof@JuDVTq9 z*^m~aq@O;&44f0U_PLLByMps5gmhl1VRHcOe=%baOL=#tChGe0t^%$xrlDTt8;F?HI4aX8{w14JK@hoiqGH+;%RP21o2;dL{fSe_(|^w z=!a2_qg2g2{t`8XdY#_`icF+4wZ9Ev)r$abxzE?MV<$f|DbFVVqq5Xj`Efstw4Px} ze`oHLY0J1gQTxDZxO1A@*@CseGP55IB^kzgzw`1jwiG+ht4u{02GGc9h!=MQCxaf| zLMd>wZC>0_vwaQ1PepV5ij%_Gtsz0dSU_)28p;J^=0~tu5u&<7q*L@ZislZi2u*fM z*)I;dbB%tIe+tpouP&m~-cLQkw)CJ&f1@c+?di4)Xx5PAKMxOXGKuc1b@mk}lA8G6Rt^nMi8V3DmkD@i zfIhLr&bhS^X;E$itKr72^$j8xfAK+kz0EO}f(6zi>ly>5BBvx6^2}eVviEe)!73B` z=il9Vz8}}fI>;ggdZ1)Vy?HKeAo9o%cqUyxmJp+z__LUOh0)0Dv+~OFUHL-l(b%%l z5g7djod7uk`N)C_%&}Jr-oLhl`s32{|4 zRGT>WG_#*l-h9>=emjzPd$CwjVKsk9AC9i65s0VrtS~(fY#wa?@R5$|S_!6lV^a!^ z6j@Xz!CnatrP7Cw_hYNaf6&xfi?>#YWgCBAZ&_3BJ>9*m8SVA&62mJFBB= z>k7t18LzP)iIq7Yh!$rJMCX-Ycg(Mtbo&f#Up2O$ti~@B&<~R{D486O1hF1NV{)UM zapse?Re~Z`XKSRM}+n*_cYR--45!C8LM+BgLY;i}?u^NxN+MrFmgWF8q7vgA}5PG$yR#fDDiT9@9dXDy6Yzo|; zKfk<>3vz$6D`wtjW!h$k+yPAeBge*i;6#B)@(KperD3L`e_}5lwW-eAiq$d>J4Mv8 zl5Zq##Kqe}&5yRS;GE;mQIc9OE85zh~?v0_MfseYmm z69UMC{uzMN#UB16+5BiVmtD;dX|J@B_|HJl43V1J(Soc5!~-eKKS{uf^7x%ZR%kzs zKPc7!)Db6at~5@1x!u#oO}i$1EajI~0lLFNg`4$CKGLgAo8@;M7>jvBB3n+eMQC*mOtsZf8aS1ld7G6 zT4~%wRHL zf96av4P;Io&AbvGaw&Znyi5XvK!I+clVCw@4f>_FL!l? zT)?tnn9UticCeZha|}c5zJli^QeV7L0sBAdYhE*#tcD6cp$^wONk-KEs#)@V_4chM z#N8aIbX}Xi`}4&?p035Af2P zuRv)eHL^|-fH`pDme-}aAXfIPGu_W7C-+K_z+cyeCn;7u2=;)1Jb!AfTqd(pe;2@Q z+Itdk9uk=zWspPZ;`!-!y9uJQfe<9%NZ}MdAi%rDK`W&C1jBi8_b-N)>+kz9-cOs6 z$ciQ!Y_Y@;l7z%af%vjZc8Iv9XXnv3v*^Y`F{!i^xtv%tO_*qe7ZvJ3^2}hIQMq_5 z=6Fl!fhoFFO{OBMpGt?MvxiWee|08m_h9)`6#pd3=?nG;GOvr@xi(7_lt8E?zJ<=} zNMeNH!vNNRs!Q@!ExAsyvYNA>5L$M6DwT|{^3HtsSZgBzXxrEDY~AFe?c3fED=NkK zi|K!Q2`=)ZOsMt+7v6;urLVfreDDDjjQD8ANUcf30&u-;N%& zaYM8av~I?=p51ybI1*LYI>JM;2JYcB(p?iDG(q0!LM3ME1E`E`a4~x3Zgn$3Q*ggf zDc+n#>9B*ucx#OlwppCAmu8VfCdnc z{gaKmsmb};0_v4?o+#pg3e19!6Pr-8&G(B;_G?~T^wMgbNzd?l&?a3w;WY@Pv4p04 z^}#NQze;j73JmJ``?QV~0dH0>P%Ps>7epny?CwW4_b+W%W+*?le?ZSW#MiCKL@3I> z!!=y3^8%yXf>+sxXy`a!VWpdcOJdf=)4D*o@_pYcZtzpHb&JgOz+2+@p@Ey`W&X|2 zcRm|mFK*qQ=G05Gyq<6=e|@~{VcFCDfyvlIX?J~PaA82R zCBnf;-?^rr#3F=Ff3H%`xAfVO(G1cq8_~{zAPFJ90gd3XqlezjvqMo}|MaLdI0YJg z$Yp3zN|Ae~?ofIC%}8x+;r@qR=9G7XkBxN%Y0@oAKq5{y#k2pLGgC#8m(1Pfd@s2y z36jtCj?#IIX)=~e&5yBipKOXL#k?h`K3RK15Hp0pU*D?Wf7uPBtjlQ++O6>O!b2NY zCatuuDm=U>A{|v3%p~;dMvuw=MKtl;LXriir7diY24v_;gc5P|X0lzN$I@ti`s>w2 zcv+$RcROj_U$52?maooPfaZUjMYI1tkF7iUwi1- zbifVj7fTTgjen=BEHV=oIp6|9y+G{MXbYIDWZu>-G1h(@IPuIC8Wh#+8mU5RKIr=? zRMIp7@%qo&VJU`6sEc<}M&%RwS7K1H^C)ahH}(dig!3c z6A_=U)q2lu(77amOOdN9k;XAqK3@w4r9i6f#!a_aaj(mH-!QOZ!=D2atfmI3AVF2e zmQwULJ|ANh&Z$4`qc22{5X>Xg$C!LWFW>S17HH{iP%ZB2z3lxr3z2neIeS54Kjd}` zRK%2%f`8~Y&{#0{dzEJhW)2;&0|uS*yAG9Mu%>63WNIUt3p2mfRSs>M9av~fO8hu9`C`AnQsG6_?o5Jam0AwEYC(el%XNZ1 zhKs*Q80+n**AhqsXx{yojaCXJR_6qHc)g!KnI#rzx;i8&BE0JrG+&eehW5E^-__=| z-hU%=7%s9?7@p*+5t9L+t9twWGGTyY7eVc!jUdZfNX)6Q?PZ(+m&P_rMJ;jE+r)SR zM;7}!uOBI7D)6zb!-Py^!lc+z*jTN|p`$&q8*$jQXJ9<4LlZLw(Ab}eM$_y{%hL|N z@S@2-QI4OGNM`id-eS06+O|q-9+!fWw0|@G0^MRY5725+dtYDg5Ggbj)}s1Fm7E^%zmanZ+|l&ON)flJbU(DjPfyo zExff|6$pi)I$h!M)_%x}+0kXYd+G^54!M^fFdBa@#Hce(2Tc_I%$DSZ{9QCB&%yz6m;Qhq@rW0+3nDxKPlUAqDuSUpH` zJ^o7rHo-@^YrqxS3g;UFNc2^>C*;$1o|6`kK+}#n_%``ejPE<Kc5e748X*EBL_?HV)OnZkD&e8$hhouU5Qg|JeXb?SGs+B@#a) z?ywKN#QXe(w(q&BPHv{}g;lIRX7*veLQ~ouA8Me$rdlCf~SXpkK30bL`+IG^u zRSZ!x93@`XnU!XAF-8YMt5Gw{V4KYncCn1)vftk5hgKVApauaOTBD6PmK#)Q^G`}S z-zI!yxVqZcDke7f^9;}gmwzS4X`^+BL!em&a}*msTO1Zi%(y_=MR`@(2SHTOlY|jN zIjRs$zZ>cY-pP}lH2wZe!L;2?2rU(Fj#xJXDAWW3#;x;#+0{qIw6)lhgtY&}hEOvI zKbj;I(;jc1r->Oj3@eH=|xfk14q2s zzz{GbP1w)@3o+ukVyg8Ed~r^FfXX%){vi(Uu$%G7C#^IT5po@sl5oPcyoQE+r+}v) ztGd!Y`!reufH=c5OYB5>kCZTI*XI&9p{!Wyat*Z7%^>O2x3RmDZad|UwXEDl)<4zD z)*m~^@6uhs&>yw6VSlbXpk=1l*%gUaa!G)QaD=nQ=3{9f+;zF@aN7tRqap`TbcWuz zE9ELrI@O)To8?JgQ7eaT_JW{s_#;lKNb8k32{WW<-!l3lQEspqH zD3jzHK=cROqOmybD5sXiaXkgn=MXb~2(!Lnc0Ca7^BVmzH5~JRaxV{7_(Ie?sguz_ zH$1LA=1~A3<*L8syuHGlLAafT@ypg?nVFJ@h{oeI>ibEkNrmjNnL`F_IOQ6T_qe2;z>NUb)bGvld`*DDJGVG#B>`}5%!1yLVT5} z>6zcwq{s)sWI5Ans0~ECl$Lr)-)D}FdJxQrzs!yDbR_A=H&w-)5!sZ9A#r?5Mub@#q zF098H_pf>aV}iF3SMN`kZK%Tqb@#6u0S9T|aQyt0qYMP;{RIfVs?6h61GSaUNes`$ z@PE!r&uB8zk+SHgRccl5wriUy-&kzhUgr>qXLD<>>S}q6-zt^ny!q(?FJS;7ePf06 zOfaddGlW8?0q;$H6E)FZVWmL2g4e@Cx~B1EMJQsA3x8o-=0BImdwPV){3Vp|OSPgLM`@DX;BtUu zB6jt7kKQ7j_MRIGLa@E2v+~;1r~&j*PQv!>4S_pnfyuEQ(;)#au9iS2n%2nT;$Sq>f2QBY(<^?kFnvPrswE*t3_IBR2;Onl7HHe zk*(awU~3_g+X9Sx05?ACkQ}tDGkx$TQp70}hY`tMZEoQ#8FmY# zaa65gp}IXxaW#kXaT)bFFee=VoM7AlV|C53Iwu4>M|7R#FSE0=wRC=EpMIwp3`x!Y zeYF^MJ4O>WWxQ>jR?$Iyx*BY5hks_wmtD`R&k4xvvFDKPiXS?`;L$UZ;VT1M+4y%4 z)lsC-RT;pDPbN!&51O1i`b}E}e3`fLk9|j56t4kN0p?=J*7odN;H6H17uONT59T{) zCg|=PtG?)Xm;ue|V34o6$rSBH%`5vc6J+c|C*14?L#5i>pZEe&mFra;ynkKa)~vyI zt2icMfD$2FU=xpgO{NCqb$CTZYhqFxT)7OZ2kdLaHtNA^TJN6imtkg7dZ*)=y(%4^ zH=YA4EflIcP5-aSQ76W!w&q$$g)g_=y+9TT&7UoNb+J3=(w>eAxNRXwmc$HJS?t@m7s=6FCoGWAd9^5^Nq|L(F#1ff0{m?-TIuXuR(e5j z=o$%;qdzfJDsX)5jl6`DuBYO5-6>lvHD`-m%aTL&I8p_%&v0#RQdd2sF=R!9QGgSg z;}|#qzukEtS7hBcdw(%aB0o@&X2ZZ`_jUY%SS?qyaB+1R!JH(lFlm_GKFD(1(-f}+ z)l`!<3R1rX88#0+$e&eX;SmV?uhD^^M%z>4#4CYGvfFkvRO8J@Vh2Zi?i<~^1* z6MGto-xT$M%-JR1~grzN-`>C7MV1EPDPp7&_j=ph}&2*t@ zx!fWu$=DqM@n<#J*3pYVxth9X@Z{Mg{-j+>}+N z2XBMtX4`_Yiuv7VgJR0uxLxLD8$KrGzhs}L<>Wfn2>${*P0UUe@IX@`|JkE zux&Y@NK5tHV1I%(zMiPNHg-mrvrYm&E`jg3wqL*WQpmltWe!4`gN}GJjf#b08{A(r z&1yZMW1>e)Ux@!47G$g!tsD|% zZdjKuX9u&@79r?BQDY3CsAVK6!cf&}!oG=>k)Xge-G5o*kHR`)@r&$Yef~#;;sd#M zU7K(G72<5wFg;VlyD0A9y!?uO*58NeMgg#wSP@ajjpKLV!IWdp3D~;1oyl98&io~& zizJ-4wKhzwx`a5rUMW8ykG7F7pC$_PU~HYMRsKzA$@#1w64sCl2uz-9A6PwUiWwK*T_@yDSOAB`&W-w}c!cgA?F*njpij0U zs6Mpvp|cd^(@wJ`j!3Dwyw5fthbD(wgPK&peSaZc>&+y(!pSPBifTK7giGozcNV=` zn}IqjV%!{H!O$z`TOup#BLk;@Hy6Ww>3g3YR%PKE>Y1IVodGAB%UbDw`A#&`klGNe zl?C>c1dBd;Kn%q|(X5Su!jco**1vE_YfCq=okyt4mgjZ5*-F%cxb`vlfgO9*`65;G z0)J#yW_|D(Ihq|MHoIJ67VO?y5k`ulA_;E!I3n7$vEj|WC?f(DcR6O*@Y%MyjMkf>_j?_IY-V%kdJZ8N^Illpt1L7lR?oARn`hWf5 z)S(|)lDw7cQ-@SG7}3=J&HzAn+La6U5Ekd}rQ<@%*+OMwmcO^gz3WU&)rrRx?X&0x zpj1Xv9k^55St_dbY{`G>#fZ7pgGlottD(xtu&_^G~yq&2La=YK$UWd*gf)TY%4w&R}iA^B5|UMS51ymN4uB%%_( zT3rcG4#j>=TvWuii^F>WiIwfNl+|Kho3GbPq33vy*=OzSmB9*Z=U;@4bIErKlYOg^ z!k?C(!Lw4gE-~&#gX;MNCASvjf=PO!C<~x*oD+%mLk)yC2FkK~T6P3!{C~Xyz*&+? zgi|VT<&lW}UWc7>pv1pN8uY+GN^yeGEmEz#ndWcy!A)&34-A!iMw0&hIKqBNau_ha z@V!9W^KPu8S;!UhHrpzjvBBbLt2@myk8pa8i)}6V5#=@7@P4wuW5A=Zn6J z16co8l*Umo@bny070epofPZ3KD_YBP$p-;u+(RW;s|s*DX50eVG0vaam$AlA5RY^% zEHTB@J%#NwWE_d*?}P$IpMFV6F{@i>ZttR^A^zh^UGUfEY>2U*fvfZ)k||%}SRA@tRarP>t)P@Cg|CRe!cVNCW)1?G(6AcHbOSWrG+c;Lefbk%Yg$MzR*D6aAF9Ht zIgy-Ptov_dZveU#iaJ(grom1?{Q%z4ByN<^=iUlY5L9>%A3|totNSRL`LAp@Iu#nJ zEEg|rNuSkugV*(^D}PxPL}cu?l7jL%R-!qZh4shQ#O(LM5Y$yC)erh2srhqKKhEkd zT8e%^0+C{HO8}Zk)aBJOa)YFUxkNvBE~HzXX|y^kx-YV9!K7lr@breCr@nX+|A$#f zDg>C@f3)aR7>5uuRA%VftN68RBF8Mg53HO_1WTZ(-G;<5UVk&~-9HCdr|hsoWm%H1 zvj0O4_MdsqW|ptitDLT%5oZ~A@1CCNv(<7${y*|&%zc6DU@ol?VT+V?7_r4TV&pE` zu5q9R=d7Jej4R>k$iu)g*<(^g^u5YJw1Xb0mP1?koh!aWb-Dv;CRqbXEGE5=@VQvI z%x7t;+06|Z7=Mz53EmLxI%&9V3qq~O#(-^{7m77`b8gP~*OPIs*O``-ghTo#emZtD z*sVM*_eL!MZ;vDHNnI;Z;>D<5<^Z4qtr#v&6|vI_~g(Cs3I$n&Kvx9 zbpkK4#!ttGG2O5y7-hHz!_w1VZF`}Achr%GFK2r&9>LAi??XeTM z9Gy?;zgI#ADpJu-sft+V;;%4S$mfWa8!;^c!GUZOk8rYdI>MYAPuzWD9n6gAy_nwt zsXHuFdSbdf+)^8BQ;^Ep&Q&>!=fOtvY4Y(&*ni@g4*9tWYJ1m)@fD97nBpyJWX_@t z8qL^N9}lM5zTNg_l2kIoT6chUnFl0`7_%ONq*aL{*B)cDlDVhBSp@h(APUlvs9*^p z$9CR?A}qrEeRT?R1&^J^=LNLPNz0=XGkxk3BzJ(725E)3>- zImql(cd>Kzz1~Q_girHwwlJ2_h=+Bw;Ims=#NDmLEpcYQ*@cb?jT5N zRpoPjSOkt{bRRb4Q|Uwfga|S_tbuH32(jjbJV>(&MHL|}s7s^zvy8un3=7}PrR-$p zo`jKswn_-2LOzT`a4cx*HE;>#{N;C#MEPnt(|AlO->QqfF%SQSJ+wPh#Oz`2h=2Yh z{}#HMZ-WM8L|9~_Q`Wgf$(@rsi22z4K{|Y|g zt_15P6b-hJ-fY+_;Q{k&yRZaJA%BkS3vTa1fjn$+jI!b@;qw9ys<9uuFe;&W^~^!W zC;?<7QxceX&5Hm7xnnX(@gmd0;q_Go)u+ST)t)X`r7Y%6U#?fmmY-s&YRo%|i}7AOuxbYdU6_6q4RN9d zYelx}traAQ`g|ie^UlwY*Clf)=C-zW$A(VT@_Zc)@f^>7xHeXa*cCI*{0$2z(n~Ptxk~0 z0HDp5_|k*k0vopYToj`a5CfThr#E)=IoY1`K{YiKGg>T^T?(C`QSKh64LA zKq$2_;*s*mog|>dSQC-4=9~%LEEw723z6zmpVH!kDYfyfOgrPFM1Puw#FJCSsIS}4 z>~~h>j8lix7uzL>J&H+ZH{o6wD*!;_Y35E(+Tk{SamWnfY%Y`O&=ZYBeT>fcj=W0U zc7nQuWwrN=p0$-ZnhQTc&UTWaB5C8oaKeC;)>CH|(Lo)iQHW zpJ`RIL{NlF3FCF&FMpbH(B0gciz%R}6`1m5_Nl#Ue&5!!4OD)gD%)o`L>KMk&I4vI z3Rf#(!Q1?Y+<66k?i;DTFEJPNN!V61NV_OnbaBNqXz|2XWM8M@a_Y5iT5APcj5OdQRJBCTeE_j@F40o)M}%Z-L97xezaDLRpB8X+3ZKuB)=(|dLQ4e z1avJChnqL7q@ZZ<487X?+*8H|+`+R!1ZIT^-NQqzBa$=0r=|fOL)F3PVBTUNDA}8F$R{OizcUR^&7C z((X^fS`4&lY!&|mz>ZQV(ow2u(j1$xHoK8`=;J)zvJr`Y7Mmn!K>Syfarmp@;ACGP zU0MvKXn!FsTXr3@n^*U!U~M&Cu&>N*oD{Ak=3<(uB3vc>9)>wcbq9FRq-4d@ZR}iR zGh(0Ucosh_G(Il7PXB92(w@x3m2I}55)*I?Xs~~xedyv6FpuwL)e#+@+5G6_`dRiP zkvo5%Ij=yB36fNgOcZ_u?NyARE#ZKOkvFsDAq!c-Exd48~MfMkwl;4(Y z-k=NXbt${8f;onJbWu7C*8@tu(W2#?-czDX>l|l1Ed1uR)4_o@q+UtOd|y}+YqwY2 zbWwJ9!;M7b>n9TJgH=l{h_Fo0qpr2lydNB-a`fA2%KkT)5(q}O$0#wAT@Dh~2Ac3oYH#^#@mACspj3GOc0m4P( zc!6O)Hx8n86@*L6MsRaR6hTtUPCV95Cx5t7fw*x*VTG~5m7Vpt8MORZHvGj{;gXW} zbQP2w?bcqQSQ;9IC&06r3i;r{RZ74pLt29<1|Umec0%dYHMrf2YJ$vnO5%VFfz6d= zHkQlkuz97o!vOmQ@I%Dy>Mbi|WTEMGDZMwh&)GD8bxVqRpA@rGl0vM^suNBA1Ah}c z-#dlXdXkCkfEC5x>G98;=4$L9sEjMN_B7zS)G|~0Il-epSnZDtM4Thz%HZ$IuJb$5 zZVvW%QUvpmbh>uwYMon?F1>)vE91s~i*t)#5pe=PW%DAAv#Y#I9DnW0gb&;idH(lpv3ELsYzm5_tl4b}qvLgX^k2*R z*zcs&0L|>x@aBbLXw4$&4r0se?Ag4INFfkD2nMDI-!G+2g2 zs8&qRGs6NnCowxiLyt~4A&k{g3=rgU!Q2BgPg!ho^Bgr?eEXZ%ZxLy)*nj1k;EA!$ zlLG05*}v;cCc|^1fDma1m1OvPwnJ3?Db1wCbAbEGI28(NuG+M)?f^t7WeDA}0>wth zk@vv+>ZHrkk}SXZCVS)i?8jX+#b$@_iO$7Su86^LjdXO{ecb`nUH6z^o%`ru3)gAR z{jE=XECn$sg>t2wP>b(c?0@)GM`~+eKIi2+0{0q`19&jjuGDkrUTe#pD~+&kRVbt_ z5$dIo7Y<{Sqt)wktd`1zF%*SMvjm`}9e+VlDBI?|qd&yy9xA}vRI)?{>Z+C!xZ2!j zBm6K(JH*kXW*;Zadh3(MTzS8pEdC1~nYDf3L&~d8*s7m0siWt*nt#T#FB!_3H63di zxM4TD-HRP}P-tHvF3vW7m}Y+a+{*lIsDA?&Z`VHU$L-RAp|TXUJ42xcU(ZqQ=>GiX z#(=1DqU_eZ`)5qvxks4V6)7bq)diKSn;BS3IUm>pPKxnwSMI?NVWwVO$i`a7&EVf;+{CE*do5nJNUW^(dv<@cu-iNyR2WlhKf`9&jeVh9Kcj+y249r)9 ztp5}F4?k4+o+8iE0gkE&8>L3Ea{8ch?0vO9)9u&+6e#Il_ubPCTpnnC&NybZDgr3) z@zeE450Yv=!*A*Zfa~}ZDx{vdRVr4;8l3c6Lyi}BUna@;>NNe}eGzGsnXmfPYOiXQ zCql6bF<7eV_>QAca@KJ>g8KC6T@6)8RVD3Om)}I}LyW(Fk^~Z2b<%ph!aqcGB?)5p3J2 zwP!uXTP+|AJ0KZ0%1lmL5DA3N{7-QKvV{%)rDPB_I)BHlvR~5~aR_ck*q$Ohp$N)B zLw1`1ZB0?z8fvxnL2Dnu*r?0AYMAw8yq@E@&;Ezp9WJYJQn&HwXvd*lq+COW*Km#u z6zn)HdT=a2TL&1HN6+U(_izNQ@zB=F?%31o2=-(tRvD}Oa zg4o$Aa8x{|B1Aj3oFdB-D(~!2yahthX)Veh%g?cOg)1I%$bY+ifmM!|?iEZ%WQSWz z8efv^YK!7{uNq!3mRhCUVnMGh)v<~$(A|q(Eq@*l*&(h|2>f-`0~jh=^=#q8MSg5= z`2(vWr4=XzzBh(Q4FuNk#2FH&E9I6@L6bq=#3^tKKrqU_R`7uq@`V zpY$zLTrKIHcVNbm+5Q3h01kUjw9AVYDSsy#i`MlRut&pnr0j&`8=E!oxG6`#ANL~C zJOwR(tLTZ`KbT<{XGHA3R;|MRL}E3e4=XR?;x3{>lMhin16tc#Czcdd;QP`WseEto z0Cn*9P;7U^IucT+?o%#ri+v;z{-x~^;Ew7&xRbf|h9RLcEPA>zD$9PDCwVKz}m<^3!vg;hqSKc7xIC%`J^8iB;0QX|*h&nl8&n z)3r|tx&BDCHGnB>`Ah9V&i3;8-{SuTIm#rFwgE*J>a-SFuT=Jlqdc1Yk1b$1zM8@= zE3^J6XHgZ7LCs{sK}cNi0)OLG-JBY-uUtI9%1)4#$Z8yUnT8*C=O6oH)qi!(RC&uS zpo^)1D6zy`J!(U2zRC(kKY|uW2tMMCf*7nGMl2 zTcyclShVPjcx97gk5u;)Fhrqwpr@eq`H%Kzn0T5Fgujp!Ib;&XZTYnIX2uiZ(a%KmVR&xnLa?>-1Te84C_Zv~%3 z#|a`*1B>Gs)M5(h)8PWpP1CVATvlLqYBt1i3-^#>e-T``Eq^yi0F|pr`0zc`Bv`h} z+>A|XrmjP15>PsGeBMyRyz!DS&eN|vPlD=Uyf?s;2CL7+wIGOMk6_=QWFjE8;d2;U z)cjV+&D0tWkTRio9S_wZT!3RI2k}q8Y-4xungI|HOiGe8he|HcJ4iJ1v_7Wf)Efz< zDoL$l^0@yjy?-tT-YW*i<>Zacy2fJ7Z=4ynhccPay?lJW=`dzNHJQ~T&idXt{A?bz zR#eO8tQiMqsxuNKzbAB|36hRQZ)=$vz9>zwE21^DBseWnOIHg|DEWH);{=Z@!Q?*z z^IUX)U^6ROVli?P3WOXnDjS{ASq%If)g`nGVtK4sKz~NZo`6ZPUH)U4lV=Y`x3qpN zY3*mWb5Mau5oW*^j%c+TOnbxOA~u&iQ_?BGb435)b3$WUT5`=GsbH|>QzEfE$~kYu z4l0^Z%uCwQ@ynD6>PG2cBz>SL(E6;u|62RVM6Te>V}cI};(n1>7Ru?g(R6rVrKBX=cB`bn{FComee0Pcqhr@#mV0UVXGEK=ks zl)j1rwEMyvIfD2f($)vm8A^MOOkq07;bsWt3#5>M`r@UA*7Ucj*fs)dG}dn}bQ zVmhshpGx?aJaE(sKm*`d%4P{me5&^c%rVSsG0paMd`E`dABV!q&3&ENFu^ymu9hzU zK7TUB2U+@l8w)OEfAjTX00w#D@VhH!iz@}{(Y9IdwymVo9e%STFI~!z_{>U?Nn&MP zo!V|le$8TZmG!mJi)$d@`e%!qP^=tO?+|@T`PxLG!c?Bga5k%jpF8LO9tgsu235^% z#qv~xjDID>l^+)~%>_g2HK(~{Ro`$uS%0i8PZ&BEpnT}B)BgT~YxaOo^BgfHmS^A1 zy+@%Ksg&tEMSm2yBqzRCm_j91JLWlB06M%#nU}zEvPJ%CS-o2cgN;h8qJfL zVSX7>1YeN~R8$4-;Idy`4iKKDX3ON;`$%b&p1bXt*VRqn)zGLn>Umxkl}lMK`G0Z^ z@$LiN#Vqj&Br2=sL$ihq*in1vQUff-ej!M6FvoY`<*lqXJqo(>i236e)iG*y?)fM>jZnpsbe zY?S$Nx7vsM)vBaOd`6ejRDfh-zkd!#ZzYbdS`Z%Ugc;v3%VVb%>k|g~Bc5yo&m~Xn z7I*l(a4i3Qu->A<{NL6AB3^WSgcb46{|2I$D!NE{P~_XeMGcrBwM}8FENbz|3#g7X zV_XQHDUMwW?!yQtWdL!8suBr;ClLAGF98Ntt34V_Uv5Wo|A=+7`6n_7M1M2bJ|Kv8 z1N?AY_Fjwibsy=rh{Gu>a4aY_^^xmOks@o?*>NPdGGQy6K>E!N3xxLclzsd-Ia7Fo zc`W!`{Gr-AKb9$%AA0$F1*?K%OBA()z8wclgy`IyT-wggU_E%PNXQ)d3v^AW%y*+; zf5b480vVT_f4}LPwuZytsedsfYza=(8_QqREAtAm68~QwgmZA*yW3|p3$4|YZSG{XTjBQortIvoESbwckcU%-A{)Y%X zcB?_T6u`>EM~rP}p=bd~$e~LAt$5@Rx|%~jNVNaec33F;4|v)dO3dfqOr0)>C&@Em@376#XuxB!Ndk30|MPo(y5bU>0YL%}jY*-W zm#NzRbLuvSG455eh<~z@gKl#)!rvDq)D*bjVHhg#qI{ycAjWk{8I8IuZ#7KhgcEl9 zI62YUOAGe6DA2}7rOmWpDqxw_j5b5^`AvOV%EZ&}{6qBzDX|EHC!tyQKyP_tq#dPG za}T+N(khK^lYj~1-K^`6iuk~K63eh0hV2trRYY?NZGmq^aeqf($Dvc62l97h?TzNh z-OW!&F`@qH12$&(5YmZ(dgSlptCOl5sf8M;8DK+p$2*I&@jN>CUy0Feu?yq{#bSOK zw1ier%j9XJOUStdOCZv=gc#&j_cX#0pR^sqXITZ{th zv>ak#?pOugrY}jftGq`gwA$&zs^l%>ph=&e(<qG2)jidi7D;)9wwer;LMWOeU0s&j&vg|>9BVfke~Oz z+fZ}7L+ODH{>iGMvLDrTjPd)eXnsG}?cZk`1@S;?|13TeK@R;ml&P!4&i*Ct#@<+6m?mpNZC>!>C5a8I6!NhJTPEYiTZ>Jd9C^{S%HlT`cK186SAn zdgNNP{{gA~88R_bmQ=GPd4rF*q%N#bERUAl#A7>}F56{|oI8Y0sDp&v$vt5?hsqYd z@%L1MIYhZ(A5H4UvH3<}RsgP}K^iQFQ5D&+9$xgrGC;G?VQ(LRBd%q_t_eq9k2E-z z8Go3Il)>F~NZckCgZxG`om@6WV@{(P3tAVPMsbyc%4dK(4_Atr7{x;+On}i>obs&^ zK<1Pg#c|v83XrF#4NR9{LmhV@h*DhrGeqd~AHr$!D50gz-d)DbyjsL~1Wu>i5fik+ z_(y8?YMr7q#!^t8TWiwtkC70cQk%2Z*ncrcctBgS9M^B2RG}oqlK;Npk{V3k7s^ER zkFJ~WA7yyk8JLsgKJG@8^5Q9o7qND}t?{X)S1tY`x(SMOAFWR!VJ?WBWHxvcZQeqZ z(hVLB(rM8QO`ijvI6HjOQ#GRst|!yd%%Rq%V>A7FXyK-2YQ(25?L+Z21Xdds#(xy~ zNDX9bSN;#r|E4sE1hM6iv}Go2+LraHp+m?^j-)J6&lMH1bOAPeRm{tqext5w*K$?}C{6gDl;n(Xt?fSldF%V8TCIjIg@6>?Y%}+y9fEzLN z9msZvf$_~+$`$dgT}$AGq$?RSv&2!?bX>YyA8XIhkDb=+DxZeqG#-D15J+R40tRmvMRHrGSyMHRZqaT|Ju9e;Rwll*r6u^n`@G zx!JnbV-33#&zIu&vi+87Wf&jgjB2d3LPiq{Q5z{ov1poJ^2Chyou*AAd;f*v+Ws|( zQ*bqih30`7G6Pc@OiJJzXwJ17#EP?X9u!$|n0$lzh$F`^e5WvsVuMC^$mJQWxfXvL>ViPCBU_=UWzAL$LW$0 zA`|DKzw*`|kUC21=YNfnjGtJILLUkLm1>KeBl6RT28z(UYK-caM$@LHeD&(B{O0Hq zlyZh2H!xT~SOM{bE?wstrcBS)-sNLZKCJr`n$cY<~%AUr(^THSKm62POva zNimk?dyKPeJY~b(LtUGX#|4XtDZyjWn`;V} zPylb{-L;}W1%Fm3TV?&t8tio@gm)*gN-V@!GjYU(Dt{aVUhcJ(CAP-yn^vf~2 ze4Y!@rK4WadR_Q@@Nd7_qPPg`tuUHD)+M9MEqqz4LI`0@1q)%x{V)S>SJGrB9G^{B z7lXRo#zaut+tI#O&AnfV2@ggEmb^3dDv3Ab&|S2^D;tzq&>Zz$#Vj<4SxXL=TcH%jCZ1Bj0_lfbApFN94<&A7Og+^yN1kciN*VY)RTUUt{cx~ zq);Lj!ha95HN1j&@}3z<;jqq)z*Y^F5LFGWRfnC&=;Ws?A)e7MgV{)df=nT<%ala2 zr3ZuEbcOkORi(1LltxP~UtozZhp05a89^}5O|^?%xGf$}ATK23ms+(ut9~HlL}YlG zf+KcBIq25+;kvG`aM%HA60mklSQlr}%k5mIlYgryTfG`4=O8aEy5t#Lu(c{lo*qwABsZ{0e`9{IVQ{@MUwV<Ra2i0-K zK!4={0j6IJ@{$`U6%$bxfE@&CAFdTe!+3x1W$D!n6 zz85%onykQ2xO_35Sn>2bRh(s&tR=A%hJW`fEb8g{1-2S@=D4q-qW|CRu6V^O=TXr@ zvvqP~x-8}>6*GQeWZEck;vdkoUSNm~Hn0XRgYldJNOc`+oe+`T020r=snU-Lb89k` zzg(@QcI)7@< z(GGOu9E5lG2NcO}ZNAlo<7~Z3$=g_^Y)v~Ql^6uDQ=AtOXQ&70rin=99PeLbCx7|y zWu^|X*`qF$9rei3ENTLrI(dG2PE{*bFQ-An+LSOlvgfpY|GEwU+y>RjTHKufsYw1KX{Bqr* z5>j#K8C!f@1NmLUjfsep$Ib%dkA;Ki+54=euxFx(QKw&={e?WjVG;AeldD7zTP^{7 zSP@Yb^AeHazE-#5(}a8Y7zaEf&)V}5F5~SQ0Ma*dC*b{i58&3@-O+OegnzJfVx;g| zKigO3O{X_6a%opL{ruic=tAdvXP2=JZ7~9yL^4S&reO|c#0fI*yxdccZhS>GRv2;~ z3Fhy6+dSpw1`J<&jSu~rl1-pW8h|W<8C;p&H1L{yPA#DVC3uy4Ob0%egw z7tlt~g~Z_j5|1k1J(6Wuu%w2s1gj(#9oy)k(`$}ZTZ?Bq>VS{DV8FGu*rQ~1&3kQ# z(|*j$65n$j2rD;dIl?u2j`SW@D*$8C#J8(EP#yP-J80*1VDaUAH-Cg-to!t`1O4n6 zST)*!IPSesyQJnWnkE4` zZCRaq&m-=`yVi&wiCaJdm8gVL(Ux&HH(p76U(3!p@~TOFl)=s+sYj5L6R*SeUf=I!p&;5b|3E>i?nQIE1-@S>MU zq6QRQiFLmKK0m;H0aYF)Qg&2bEL@VtENmTDt)t7JIe)OkuvHwEct5n!>JgsSPz|~; zVd?Ulu;0BURTv_TZVTfxA^5|Lp_4kR{0$m4YA0&AcaPx!CV#x^8nZ~W>k4H?WPgCJ zJ;#3QNay7S1yrF@j@_u%WwL0yDf9hYS6+$u6TJjhQyJLmvZ0b@=bm@=oTU@#3;STL zc>K9)K92%80u_ccD1*P=gIcxq(RInAVhcOx0O4xU3^w{yaF_dk@Eo1Y)=n=xr6SxB zgL05LJ2D5!-G5NpY=xmJA6}JA!j3Q7N)zbrGRVH{5vMEFIQ01>v^}`_29^Q()G2*5 zC(jUSm-*ssYo(()f|F+_J4R@EN=W9r)3~Ef-Bpz<3)3V zvK}y;?tq}Uh}`9XqZX^%x&giAfv7YcfAHPB(x6B2@9rWL5G~9tRxo%fM666A5n8s6 z2M^yYyiHjsCV`!$`TM#)rv;*GI?%{n&5_ym2~aM16W(q`OYwxwKsKVm$;n@&j`IFv z4y|w61b@hN3$QCEA7G=%@4In1P;1VEWlzJ*1kB?Fuc|R_EFg1JxfY@~fiqn8nhU=W zU-bSy_`-(b{o6(UNamqiaqVQ{zm4oGGG2VaxcNc9Wu{e?`YMSs=&8v3=QQ*&K1mZlgYG_I03DiwLo zylE#Cy%3}rA~nhO+)_eaX-qpI=7T9IY8?E(r)K7RbS*nxrw8|C&0K5`-xl*+z>E}# zx^HO-A2f6wb zeoFYrJ+n;E-ppYfGbb#ea*C{Q9l>s$CvXkCxTl7up0h!46k{aibc%e|9xrAKDr5%O zoO~wLo2Si1PdD1MC>zVL*u-@?go&9@T-g~V2+mI9^gbkDEi8&VFeX;!ti zYri{T;T~OTwL`MTzsx&0RY)uGu?^i)Wg_qQY7ROVwDJy1;^Q`Om!uL!iu3h z2)B-49a+UQ@FA(G*JLiW5#_ASfPyJ*dtto9-aOC`t3yphNiqJQfN65G`=&uRcQK+3;QH6L9l%HT_9f~RM1dI031OwxnL zc6MB^Z1dIvPQ}U(qr*ec^=2<4Y=cBYCDIb9OWftonMYsJ_Ma!G%42AjmkNkIZL?{Z zWrBPP9<1e3b`$#dp4)Au`bi9ev8o=p$eVg?P5h1HgPjBS3-c?-74{Td3b21{ZweX3 z6&x;kWln9FKc#2h*Plo)gy64{av4?`#ed*oE!s3vm36JnFLkiKQsVVjhJXu(mD^rk zy40@%%+ClID?2&+XZil+X`-OCveb?-^z?qip<)#|@M)k_!iPS|;ldS5d|t-(n8jj~3wLwj!_ge48(BC`tiV+fWjQrxeh_BIS0FDQ zVfO5`gyTp}bAz;!-xinPSqnN~s%JP~$v7xVCeG$YW1W$TtC)k3yz+nh*$RbwOE39_ z>54Ei34L)YM`Y-AVk&G@0XG@>P1(*((omZX(wN^>xyxM#?|gE)X3ev;kNLLUUtYyX zQjqd{Y&E!4x7a5Rb=X#lO#iyvPn|rWyzS}O?FLzGD^ij8hQ)Ce6_?6=jk zXhqKFfi!Wn)bf|&ieG;RF=U9#?XHtfir8GEZiBH z^tyqOuY2qT{F;A&cxl^8uf8O4EuVioSFr9}$aoR~g{!KhgyszZa+Dj< z6s3`{BJy^^pl~o*dQGY$0RbYwyFYixc{DysI9q8K(E)$DH<^uOhVYGXKch?Xk(~Nj z_JI!>;!=t!B-+KqjgLkZa=rn{kS()p>%MzL?*&#^tCJCls|kuPif9tp1Ff0>fdXuw z77y({!24wBQrXGr_^1P+7u#n*UJTcCD0|g13^n)>jYTf!34J?*{0i(&HZ+T-o`#-6 z1~j07CF*}&QHqVBZaXE&_C1&w0&n{dyi%)%m>wu4Bx45p0 z1=hQ6PxNA=GMlapwJ%<|$^zkwg$+)B1^aPz=-#PPOd?Lz)tZE}tXaXe;5eK38X#Jzv+26Gis)l+T8u$eA@WMT$VuW2#l z+CtsXVqush5itCK^I zT$VHJ*yf`87Y;u_lvqr`N%A9*p7eg%+a{SwPDDR|g;e$@^^LyGcm`Q63SR@v;Rz!I zn1Fu>a`cY5dO-&<)2S`2j|66#kfXTSHc4`VaMAjp^%2Swk|e5^gYfQuTno^yi`a}r zI@L~<#=XKRn~Z^Jo%}ur7xT*t5>g1+CaEt)l6d#ot}6332k3bUpAljcMV3HgbEVL! z(Wgi{REaJ%0c=kngsRP%yD!KM1a*pmmSumfU`8cC6$bf&OQL^RO;!5ouP# z$l_;qR>3NRNt30)C55@@pp(>zP~bSB_bn39rAr03^HH*956Z>lhA|R?cu6*57tS#e zbZhme?{=ev&E&6FeYC##VzO7kI7|G&Jx?7S^-Yu0p!kB}QHYt!-4+bm`CY{%yy>*J~4o3*}JolVzE zkoKa~P2O}P=@dDlz65BSalxR9;^2M0P>&~0L~6LdfBfHZeS(@7sED8hg2zD1sfazD)cfJy@u&#iAqP0O*lD7JJg{r^Og zK0R$a4D(i9eC}7nZfe5H$LAejUtCecKWTK7ezT}qZc?>7HBfvF>g+${WlMp2t2-cB z%sAi*E;~ffXmOz9g%(in6aGkB-HcrjhM5M)&-?4VL$0{gf2bD9P=s~NIsAXNg>=jU zSzq8-5tZ}NM=*HY{A(;Bk|_%t~9% zNF7%_l87#xLO!(bF!4CftB1F*Zc&ky#bH5pmzJwqFS+Yfv$z|zX@=D=jwajLIFeCP zjE(qjn{z)pHJV6rtt^V4QaFFsfP=YBsKHN!Lt~A0ItsSwY}0IpB|+NFDr}4-W_D0~ z`cVm=(tCe08lcp3bABbRTz}Ta{AfNLj0ePtmm1AGAIM7^u8Gxf;MG7M=T3TB0RF1A z-&)lJYPwLJ@GqiVd~($0GH5$zr9`!O{v&EBsT`d3HAjm;!g#w|PayW0(LpM(m`_2L)X12RMoElXz*4&nj2MVLf>Jm{$8etC2J6 zkC;Dqa5c9;&zP8eFoVZ)q&$@x-TtS5c(=)gk?238_SDOJg83A#cs}sXj-bKnT6UDI zI+tOD!D^*?ZrX?4p5(A@qM3ahg}({W|U8Cbj!##C0Mz6r2n+zoC!ugy-%fVbi~9~7#) z*VYgQB}rg{`m%onN-WzXTfsl|laR_YLZ@Av$Z;SHbAPg^gb`mW2OnlEXKR9xePwsL z+L#jIiTF}|jt~<9ugj--uWE!S?gG-LhFoB+TLdg^sFBr-%y|?k77?xV3TjSkSDn+< z#r|kDIkz_^ts&4GsNMej$?JAz7It&wW+*kz6Q5)X4C#Lp1<1j`*Rf;_I5RMC*EzL% z3ux!y7B}0E?3p2iQ)5`!dfQF@M3Vt>Bs}I`J3n*sU_gu+sz2LVo1Z^5M05<#8nA4j zFSs(JdnCn&SdV&TaFJ2%3XPdx-6wdnIB9BGSAZGVdMxPKX4cRvO5|-b+tnxQ=B&%? zI2qqrBkB>H*BN7e&|Ixp89RwV2fr6?&W#MjjUL9CI?Jk$$InWR4l zyVwFM&%=+)Zz!MQy2VZp04UDt;Z_-z>r2Lu;*7IOd3K8WBZ~L{I>WH}uR}JmT{jnm z7pPHIpQ0=`Zh%CzLKy;n!_I5?QLcl}!*d&%9d2=w-ohh+c7cmk3Q3AA|f0cx6A2y^MQnJ*YnO0wpmUxg$ zCKqa1t+}N_XprY-l!CHp)0wd*mW4WWAtwRALnMLTLn}#;2m$SXCWjCmCVy%_Y}g{t ztcrg=*jR6h>V9}9&xaR*gkNR}JYx8>10eXtjm#pcif$bybhc=a=vG`WchDLwBd`zB zbsa=N5sQkEQiff${)7BGnfHmnC6Q=m_9UKG3=WaPz?@CJofr3x%LN@i^G}jtg=fXn zW(3?AEtf~Qv#eIS4dr~1HR&j`1csx*kcEFku^!H&pDn`WfBQ7N7xCRMKBK~70simq zuQI*l-;&ian7;hc^KJoA!gh?w*z@*u*n8Ptpx3q%Gb#L4NWT3IPu<>gqi#m*amC z3YFa6BbB)N$Z;lXg$@Utttkqwn>UQpI4;wlc+%#A*97tC4a zWAyMnJ)p?#UJWV4g8;Ya1ZduHO; z@8R@LmompJmFA!AfD~Zhz`(_yJ>hYegE0y@oZq(sGcrfldp7JM{XHj7Ha)A>VE1 zaC`t0V(0&c_9^uRk6!DzeI~{6q5~lJ(g2kD%I`xH7{Ua`Q zUj%idEdX05hMo}dMtjX@L9Kr!(}m`j8GdO}*tkNanF2mM*_9RxK6-Lcn6o{EEbB71 zD z?a3D*^Jh2)ubQRerxxW*8o26`j-rp%v3wpX2=wrmj%I9a>{6w!1tb>+Bl})r!KZl) zW50g7cV`>o18LsXy2g!hW$G1uUzxA&;)9r~v%0ZK=m}eb5nO)Z_BH-iS}YbM5(T_w zl6h^y+CpLS(i(10U%h{k4$6<>kp`bkDqvces}wer(I~o3QS@utF8 zsQW1E#dP_nw{+*#yr6fV5a=YH8j!r2Zee4QRRR8*VgsiM-Jn+K}CKri~nq^CP*S)$pLk>HNb;t_N)6)Z*6{Y<+Yj^@>B_8Hpn zZ#>~C{a!?V&{ls9U7SbtPy{}bXWM~-94!sF9AA1>5^zuuGQNxc-Cdr(Stp3SneWnn z%>$dyP^nx=@$LvjalQwvq#GLDEhd4K*X8^nf(YJEIC_N=7(Go+U&!Zl1I53c4aPFC z8cgF>pz*9E(1<`k&4U@|Y~A=iIGalp(TBsk>Cl~!nn{0Wq-T}bK5}Zd2zWv8tLh8T zhOPa$Tp@0b?xos^aYcoGXLy&u2_GW^YwDt< z8Ba~osjt=s;AE+bCMYCy2O{Io%fk`CMF_!fM_6ysc2;yT=Kwg{Mh(=fW6FQsNgnlZ zJrdBH#*KfP!Ov0@r4_d0vL!5Wg~-m@rWURlirtyCl;~YFpiO9-omE6WEwbSOBn6Ek zcw_W^kjxPjUIB1%Wa+U|K~DxUO6f#8K>U?bi|@Gxy?VthtIHs%CM`}=hAY+P$Gh@X z-{;KLxGF~Otmhx^*kyVA!|5V)tsFM8ExCs4xUqk!;8h74f?8O&rkSj_>vN_LO(gjT zd0iZsOl$T6?c zO!Uw#@^WoA!)JypESwNpjecb@nzQ#{ov#Yj8N{y9*?sxI8y?I*XPvD>R;4)yOkVSt z{VRX%D&{3*s}8Cj7qGI3^c8JNwOl>9h)paN_4IR3n)UHCRC&S*B%v9M{;$HC66)CM zz|qA1cKJwXBvrYMcCe?SrX@xd^*?Us?`Dl8klIDi(2|@nl!0<^2V8R2o(7VCs)@V* z1BmD_aGd0U?cM`k!vo)9^zqOcwzwn(I)Z;@wIC3d7M)q80T??1#q-VnmD@jC7sjCo z>b$D&KT*Y74U23?aGqd@>iX#)Vo&#awDac(sng(Ux+sKhSp>v=_b{Tt`EjSfq(S<7 z)UPffs`w-DuPb(461uj+MhqrE_8wR`Ugr?|+6ctn-JtmfX(@KoY|aOd-V}fA zCP%Gkgbp_dT#YX-Rvyth{xkVOkkUMe_VveW>8SgV_x69g>k!Y>Rd#ys`WxGw{t882Ro44NYsBvcHN+UpU9!Be&f9IHgIaxPEyncF;RjqwB1;xuFv8^<61k6b(wdQLal$VXLcy&uL*(; zn0gi=i(i0M2Uc9-QKxGWUWhk6T-E}_Yny|p$R=7zcTuZ29aHD@hwbrkh)L0aTVAy4 zIoes#wLHV-5(t&6vZ8@SlJHArm-t-86IIyx!IhuJ6Q0Q>CuQlXBaGaaZ6qt0B()Yn zWg60tkQ%mg3e;>c{`h}5sF9zQ6X48%9qOp|2sCF=U-}f`Gtw%V`?51$4pzCXJrg@M1>hw_pHEIZbW(ocvneh1^uT}PZczg4=23Tc>{0Cb zfLf#OFfRC2P<@161q`xbe3crRqI-3&gi96%YY}Md(;I{>6E&BJEkaJM?VeLhu;x`C z(Ws$eK%PwzrE)QX?Zpq4m6so|!{>#HlO1_=JY1$@2Ua-^pv+(4d1Y@cx$IQXk*0x(W zu?e;v1H_Fv|1A0**Uj;{2?Z;PBt8kmZ*1d?&IjcbwgPS_C}Yse2RG`&j4>oILM&Cq zk5c_6`G!cOwbn9Jfg60~kwG~Unt_aGZCyHz(@fL`Pgj3f-7K~qUO^HFYF)0A#&Hul z@6{&pB#Yq7%-8m2Y01EQ z87A+v8Hay<7{FjSmsO)Q;Yj{&82)KN+L){o=33KKU-6?4)|16LalIE9Q)NG*TOwNx zk)fk6-Hk({Q#JGPsn_!HM+20_(G@-0*iT+gPbD$}kMs7MX_elV)8^ebnKm8MO^U(!2q)C@% zCb?M5bEb@QH415@l6dn4=HYll)C~hyyxasR;|?_i@)<{GL2Vp=o^tXkZev$ zojrf98Q9&66c5(h%j*i$b@94AXSF&0jTqAtctteUY-nZB)}uE>psSQ?eT0)X zk1S6~U9gLc=|?HgjsCT=K+fh6)0TqlG0%{9Z0w@rb8&QAwVh|2vMS3=tp6RMj7HLZ4M`Q<6sRzD? z|6=>gM)@L1mb0S8>8Bdlo!yMd&ko6GTLn^94?LD{25kP$AnV3=nALTmysLlzs^<8C zoCb^sZAU2wuqIORmlW0Z^y}K37)uP!UH~^h6BVTmI??9Xw9KmYo=@T>0H59r*lT}9 zD_LpG@SBfrkk^}fX{k$S%OMCkOD#>c#H_-h-rafJ^s^XpbD{lK*<=K}Y6n1}w4yQL zV1Tof{XV4E$Po3L4Ehr!?o!JbIB`pic8?u;$_U*-?g|suUI0PnAq|74XyKN$(o0mv zwWrc==`Uti_><1uxZ!j~JTNq0ozZ`sLm6)2_*JDYU|py7c^y9n*&pYs2w zm~3Jb@1cya6B%buiTcJ|$vlf=)z4(qAV8G265)a03tu1F^beT2VB#m@^c zb`ehPK%rP`#9&1Eg|pru&;={KFugK!w!bl;9?RnC(k&EiY<~V`I1sKY&0&8cX}7E7 zO9mTQodQm2u77t&q8z?Y$HUZkS^y86px5NGtkv2jm*L?f7l zKYD**Vl<(JuWW!TEu_CJ6UXZ!f}p0aVqb&Y5*LJ>|KE3(gnQ-FBc!`IbO^G5$tC-@ zy1i4N=qB5ZWJ92(*tWU;v{XCXd`igw#2BT3-K_Nh#`LoeaPw__;0P` zQlW>v59`fCN`DD9L03{_k>??fu3@Zsm> zRU$XuGLWh1CZ$)6{?0&H>yI^`lImnMYVO|>val`a*}L)iam;_@A_vuUckauvH(B8s zfI(nEQR!q(?4&W3H{j^8y33#=CcCWJ4$Bw!TenhHXm}VcJn-NHHXNv;9wuX#Bg>p$ z1>c#ARuKOhwX*K>{}zi!*jWcCV!Om31(zHTf}*)#Yv*+WU#8iuiiIi)Ccb|S>6obpfv{7RG1^FzE7i%= zeCmHqYqOjPi+e~kLKaQcewcvPt$RSv$+h_xK~iH#N|Pknm)l+{qWXK#qQi$EnUnDqUlbN{f)AyF?o)s?1uCA30#oB>~Ks9pJ0#V9pg! zIsgXPI@C1?&8r{%p<}e?Eid8bvFd|@nO%3@l5>Cm#AJnMxmqy+9&zNVh_xON%P{Req{Yb>4hDTp7L6Yo?yl8FR#qCPxr!JnME%P#06+G zFH9^Y-^$0ZCM`WjI}E@8QBLr0uQ3A{rt{F3`(Dy)YHhDBb856h)$&7H>;@sb(plk3 zG?IUiq>YQjN6_0G_2X$PH0PxmUw~~>gZ!QoC(`0f!f@jgQi_hA~O;>2zy2#q<)v3$jgrR zO>9(t^eCw9IUOl2*)o)wd!Nb+fTE-qmCt{oF0>v%5=`A|IW`$LapkCk8R~qts6(NS z^84`!`RGTLY(4#-I059#nvP8z;rL|K$oF=PrTR9w;r-F=EV4SvG+> z)6TV27$^wqjoqwTK(HORe!vP~yO|&w@3fNithG8UGcZWEyy2pbfs9?<8sD;blV#NC zsJ_U0&pwVGdhxQ?knm#!k^o0gUpIfEOS5W)Fu1pIh|6<^&zJjmm`Rwh5ugBS+T^C& zz0OO8Qm7?=7>+)i-M1i3RkGU?&yCi25Zy& zY2zH@m!!C@{glR56VvL@KQ?(n_sj6lJ;bDj@EEfSR)2++W;wAUSGb(tUA9d*|4uVE zsfYa{i$BTP)9eXK!~5j;=#y;W_g1;v6V>)EPFqR@HF~iM-MCXHyYfyA#AMr4!OV5l zqMI%yibq)@Gd4XF+ytdBe}#WrDah#-%T8`={%B`K%rj`p`^9)J4QL~d)n;^!u~^_n zGQ^2KhsWmE)!haz4yq(mb-mwQ$^qM=Racn0z=+;9dwhW-cn|o3Ow|%6{Ho5*-H@4_ zLH~jLD}CUf`^-|)J;@+CG#h@WvI4{!&ZPw*9QAikN?Pkf_?WR+c2$45x8`u|^3cT> zL1Dp@wct2IqgoNh(_XHZ`)uw>=^@}YZj{J%qz*&sY3G!O0&Kd@FHZ7pvT?S$!Vs7% zXXeI3dL%4}-s}nUmQe(6Q1m#!=iVc;qdvBF#ZlMJ#&vLb-kq{70%yxu2PFYJ zp37mTLx~w2hk`AS?kc!@B`|)BRYTIVWj)!If*Z-(F zmI^>SI+bguF1RRB4#dpijga)u`+*A9>WAWM*)wR-jnD!GC8U3$hD%zuQdz!3g{J}) zd7l*QX6j^}dAYl`^dq)uAI`@>5#1WiTM0QCT9z)^^Uh2>2Q}5a{Cib~abXT93Ud2s zXp;g_H{H!?FVPpm{hKis!~l$+j@G4X`Soch!a;!aR}*?MRnYsq>#C%Wxt8>EU(Cbq zD%p!8=T9d#UBrK3+5v@~pjzMbM=$vfLon1&q;C9LTMMD8{x0npnKqM+GheP#JLIB= zk+i9w5AgW=Urm*u&#wx!tloALR@DirlkBC;Z1a0;c8-%!myG{IE=z5ZOS6XZ)wo`V z*|GC*lq(zL|N08}F^3fe!?{chvy`!2uG`++*jTpM`8R)CNFn$iz)7y!AwqJ@DH2T@ z7_e-5104lkWoWSz0WP<3{yQ-&^fw|2G3MFJL)Opq){c7s(J!oi;$5fGZfgCp7zefC>SZ-esTukb7(f^O1OT45$HvaU5ox%Qyh^tn zenl<}a`|F_PM@xp{0mLpokZVRw<|#E0HS$6cLsmGmtfjWBTaJ#l=J(*N#bWk$0fA; zQFe8C8rx274ZaV{7S|mgBmFZ}KUoJp3yVAHV}H9W-^iOtdZGaKkvKjOWS&4Oh;#%) zzhzEYTe@&zP@ZZ!fMPwR-XwU$sS8C#N`I(Kpx?6*hC1_`NuOY3fxy>};#rCSVXIYH zJqLe|Hqn1?FS4N8a3_&}7vyw#cr%CoMVdzIZPc(_0b#KUglW;Bxz^a&tu=hiwtpDU zN8v&E`^CitH!Q3$emO~1$V$Rz64%$S%$D5wWiI0x3&ct-cZ5yB`$cN+&JSwdcY7nG zV;8&jzH?HC8a&L-EvQ!K9)SXE>{lIbGq{(A!J06s@>$^jMOUUf zmT3(|aU}dYw@yZ3vYccz+xXQSZQX%=;04t#g}Hb>>5IB+TbFB6n` z&f+#Bvj}M?EaoT#959d)e*@Kl%B0vi0>atZD`p*WOGxI&mYvfyp}==988jLxGtGa>uFdR{b$I1{#Os)Kw%K|ZDM4*^;ySNY*AfN#J66R zoO75Ql9O|lQSG>%GILn3pzN)%>fnwJ5!-+c`vTTKNTz}J!t-nii<07md`Ew{CTcM# zcH-9v_S1=J!mY7mKfkhh%$4|LPUg0Kt1|x#%)x3Qvwa{5(GZDc^37px3?v9hy(6hK zw-l;oS9Iw&Kcg+>3R2*>_LT9gy}WjA_0t)%U7nR9SwWfl8mSpAZgwA%umAt2rKUZ% zn>i2o>eoU-g%J69N^OODGFgAAO8%yfgZ=72@h8a-(74^IB&2+W4Q{U!j>f2gsJ(8 z-jWfhAyw)&QAJvzDxlzv>eBm-ZbTqs%e4fj7FSP;4e;dCbZo1X>ZL=sqG6UP6i5Bh zqY!OvfVU}FSbi{n@2P+3^QEJjqrLa(oyVU*^ite>|tY~dVZ4s z%A=u5r}N1USR97Pb#j5k*Q@|)=%6j|rD=5bSs8!6a`H+tcre`%rK}uJXjLY@MSw@una;8n~&# zjBZe|W1)Ur6{jM(4=dIYqQ%feUK(g&qgV2#R4arFMzyHv-kCZFW~iBapJa5chVQk2 zmC=nr*~C;cUtS5Fd#B@%>&_-m&J&<*w#k9%JI)GkZNFwLgV}xV;V~AjEdQ%K3&Np? zU@~4dzywX5%&C7zhBsYT?ca7n{(hee6<+_#mvVu#Xu!vut05$My&JDvQ^R0uV>|A- zTCB;x1S(#3jT>^pqZ(8%ErZKnDlkPu$vr-ND)vCYJ?yX-+3_Fq6Nd$aYPwzqDKu^5 z`1qDF-$2dKx+90?IcY z>b^Il#3yPPHFn=0GHU7eRDyh+e2i4uFb9Rfo3+trRR!T|VGnEyPPkMP_Sdx?u+cG- zjkgDELGS3CgMYN|rP{HQA9A=Z2l_O51o}b^D;hG`?8yt^auL|n(6zwGW{GOVp^FQ( zc16|7arl3cGe{oBZwJHSWy;Pb6BCa3_N0LS8nK5B_q+<&DD|)ekI&Xgw8SZtEJZ=^ z9qa)`@c)tT$Rly6_iH*76CeZN1{$uh4^-0Ew3Jli{iEikI75H$*~W^EFGZUY3rIbr zjT>I*>??S*>PLTYRMfc?N-jv1ShZSV?B>AdtD}F9`nL2&aq|;Ad)>yJ;plv9UI`@v z{Ob>-nBJs;CzT|z^#5etsa42ZV#n2LhmN;&A<}wBX(Vm6#f0_G0z6_9BmlrvbFIn* z8%kE9^a}NfkJMJ%a3|~{fa*zC)h#uN0DHAUT0lDfHv=RraZ0#=v;r9Xssa|2OZgQ< zN`Md4Cf z3-c2exdu=W96@WFGav(TI9UW+)zl!Q^QF$R(Jx(#6!ci1{SuWEr=$($C=1#%LYFBl zsaiGkby*ST*$_|W*h+6f`1T!F=brfFtmwX1WJ$ZY{EIPWM6$u&`ol+jpsG<^wOW6V z&7I5i4utx;#bscn*>6TyM*WzgWR=EJn)gaRwgW^k#3&PpjFpmf0vKp0=Ly-!TXFn8 z%WWK~DVV*}0dWpXapWGt6p~fe4EoQUrf|Akb}aOJ{)~MhGzkZRgT64D*D1{@`8OE~o=h5hmkrooVn;~jeg4DRUn#$#1j zDFxJt-MlV07t4ZzQ8Ul_t^hyO@CwVD&IIoc9q_omT4AqJBPQ&wg2C5Tw ziYh3ztL}pnzO?cr;~fk>lqVvK@MM31cxs52 zOn;W@Zel#B_Mq=Oxn=^=xzkbPyvZ<0+JRdn(DQzg~f)*8!3SMUsQkV?Ub+qCys9j zS1LWVGx^haeZv?DFt`vuGd{EMJ~Z;o_l0UuM<>U}TWIj$nrz+rq2EEdmj0&wiDHTB zKqy))mc4Z53siC0*z=MF7r0;YP(;+;rb?t^@GmpLq#L}l{2xk<%TtC}S$Rv~)3J0y zF&M|?okrP3zi6AKSBifYFCvY$58~g56w+2xD);Z5HCq(;_W)39er+8=>8Cg7tE_cf za@^~u#{>!tN=jrS(3~91LJuKo^>ljA#0$x%4-0tZFzgIvigL-(`$$ z79A|=rUFqyS>gK>yKJotV&6sl%4ng`hs?kuwIzoWvf;dv&jf#((x2K@Uq?uR;jwr1 z2dV0O5!PIylN-(pygLk#TA188WN$jm@x_mGpvgTG#6xRd@htI5TM|Aje2e#66R0V) z3M=`?vMW_o?~_3*5K_h?r~Qt@&ZoH3akBQo!F4SlewEICiX08|_LhgW$`1Cz>o8N6 zlDJZfw@hFj?e>4T>ARRNyW{~|2cs@5Hq6K1i&gfU^Uoq6!FszgYc_V4&-1Azd?e84 zr7Kz|?8rZFG3meTe`}B83{2UF)d-Lj6@$#omCQOBgD&L8zlLKmU!|ajzYh~9(CF<4 zvxB6(&o?sq`%a+JGsc9?|K8s)KwV z#QMA&4Y-8HOibGg8CxwB8gU1hFoUiIa6L7xM1AXQFpR7Ma1qTbVGiU^8-fBqZDIWY zh{oz77PEg5kV|dBe-O*w_|n}!GPVJOHG$T>&}X<0Jo`HtE+eQkUlBz#>v;F9+im_} zV2Pcw*rtbp{WD5aR6MOx7^lmQ=lX0$HzDNze55Pe3Co1W>O9ZcS7u`cr5gl#-&7kz zG$G<^xtE62NjGQVuU@&*xZ7Jtt)-7w>kOXc4t0N2g1g%toc&YKXfg~MTyrKN7Fyef z*RK>$b9j=RB5?58gKCXIV)t^7+$Qq=*X-b3WwdPAiLQ^{mQcT7Bf_K(jXN#Lr&1*y#~-$NFyM3@x!&1_Qlz|Ze2f| zPIm#cCIeTe4K$Z;T{(1V4>*!+jccO8CBc7RIF~0FqT?g$DjD>m1#|~rv+rFtz*0j# zGv1ikeZ%p`ntj+&b8%|Y#NHyiZK~SC{j%;n=yjfgCteAL#}vz-A9{e}O(o_{aMD-A zq{8s0I1UqJaknUu-C(iIa!_N3B(UH9fdn9{x&e!Q0Itt`K6Qup8Fwz3=sxbH5>bC$ zz5eX@+A^Q>yAQBBv(Y12aO;*7ny#c&zJH}WMsv-o6r;q^=Fy3@-`pm4EefRco^(2( z5=Ao|3cKlT$jyu%cgOqi!tV6VoBHOD~UJ&@N-^NWUz4;X_Z3C`wKF3BNy!_N?T6klyU9DR_S1tN@8K;uN{#0}Hz8;O(hX zFv5nab$#YGV<^<^kKN1yGC_a)M{{%h?cb4q3VglH1g1iWm)wJ5H&;r@fy>8r$s81ZX+R!> zrG0mwrKB&;R=Lh6go*jPNZl1xum5L5ItOnS@XG+&MQOkT{kvKq`sjaHDyR@av}dZ6 zfZ$kW@jr@JNfhAyZokz+XPY6UGpIOOb6@TI2xE)aP2SR2F!SSn|N*9 zZpy=E<0MhEs~5&%VnUVeBXNQ@Ed0+MfD{mYK2=^ywlpHlfCm+^Gk-ezgvwR^2-r(b z6Xx%cL9#pV-g_=9#xGqs}djC97e7$+xmGyrez zi9(p1+Qbi_r^6oru+lmAnH~wV9vf6ZzF=7=HSOC+72r-+=(vCGlKZcJP%q65)$ha# zwLMZ^i4)k-^_SYwtQ3M}oErKh#CKX5QlbQ;tO{gKPzo^yhSTH+S}|pnU-M~Qr)a2$ z;U=-~nOJZ8WKT;JHfWq!{p7_pd@K>>p;$gqJ+g$c7sg)EWlqK@+>NRv>kX)n-80?T zHPo;1(5JD9rSX5$vgM}GVH>gRsJcu0`g=}6G&_FZJf@~}bMX=ETz#esUp zK>_Hg%RBI+&v0-*0?i#;h*6YyIgd|~AN9L}1?N1V;uYnCiKMP_sTswUA5@Nrl5K$% zQRVjMY#T#7VkntLdE(jx{R^;?rw<;Ur16LocK@_+L*RccyXbLDd%vDu&JN*C*Q{GZ z*eWmw+!yu-b%%rWP5q!S90Kow!O6pZ=)!R$j-tX%n(}}`Sj7#f1RzHO>AU*YPj=f1 zJjekL!ZPb}rL7%GRy(CIiO?)bZ2RHz_o#910DiiaTE?p%RNSuzoe{N@6@sVD{Gt(tazVQdDs_+U!zY8eLh{4%LCF>Idw^rF zeZUKq21ayhU*?v0Dg>SOwlsV?<{RLw*R3J@36=cSmx^h>N0<&^Xd)8|; z#RKcqE^sxoAgVp4iq>2N>;jkG)!=kIJotiJZRR?Fns;m_5Y1O7to!t@?s@m1L9NG< zR^2C3Ya-94p}6@~eLv)PD2sR4!HBG6zRZicvGAL^P^pKWU@us3KT*aO#nWGt5I%pH zFXcH<=%EV<8#qU@qoM zXh73*`axBEXX2p)CH$O9x-8Qu@!EgOCSwBW0Wd~>%_M492~}hag7t!ykTtez6$lo~ zlm*6&wiYI!M_GQp%KJf^Ugd&f2TwW;R36;nj$+`I7Jiu&dvlT>K|b7esii*ZPpy9+%+mh& zsWy+BkOnd;I1TnQjd2TWtlyeKg8Q84iwao95g?sM1bQwvYXzr(#3;S+BATlD4m=)T zx1YQ3>3K1&f@+}ng3U30N`ORGGA6b3|NLYX3NC3 z)b{(r09)7<$7*-N^__t^O<8|t>0px1$L8oV8>i-YJHSKeuuIQctH$*j{hJeF;b zXvg;vK-A;tR#+z@Kj2IYQ)N;$4UxMlOH7u#p{ReMCj7 zojgCpTP^?Jmn9Qp3 zY$MXbnfD&<^>U8YR&x?3ALOMak2 z0Nd=u(Nn2J^U;D{B2iuoLim(1pSL(rd!N&&T`R8(wvC(WHH?~EC@W30x4<6kpjyf) zp4-~FRQe^X?RQ*R;J(KB6H~dw?GC=L9aHR5047ItQba*j#hBhr=x^~h0X{9`_%^*a(}|5x~3F*%^qvJi#j%S>9A}z1O#4wNyl9>I5G=GH!lu~3j zeOrCDGQ+@*{96Xd^y$kNO=V&Gsb?c8MF$tce-`@mJ(^|_FS09&*}T&7dT>M@Hy`s7e>+xFaSl^j8pVEht2xeJug0@S2@?puQ z%{E(yTIPRm`e~z6X6r55b9MYK!EF>2(V-V8LofmeAP`!-S20dRh5xyboIIx1;1B*U z)kL$bHr4;JZVi%Pw7Ub|q@3T1IM%?`j&SHI5)oifAjUs$S0P4d8xM&IXaOcFufZjL zcvXD}$l^qTmS3LqA0se4_KlD!^HJyC`46K9#YlfrN2T0Bc}oSZIl?9{RU+tM^Kf9D zuA^k17rn*h$W&iDoqDkw+%P~_YLfIka@`mYS5Ho5Xn)jHibe;(D`>4mNQ6>+n(tSQi#@(G3Gtk`?nQ=zjo!+Z9FP=|+@w`DHTyL6?7a#3Ee#;%C< z--efeW7bhZqwczdF9^E4a#8j-owraz09;+Mj{oym^Kru7VSbm+A>r1anFtq;5@4b< z3)0sDYmk+%&S)xjV;s>VHgZ}G_3a}!Do8{JQlcKW>9Ro)>S1zT&k2NX5)BLMo(uUi zLuWDESQI{*!5zl`oS3?|}XYOe?8flrXDpm0}aW_Q|z8@-d;cyyv+4#k6es{#$I*{=d=s?BZInF4` znM7NdL=nW38$f?Az0=TK+P>~v1LA=5SiAlu#MNq|#*9jG($rlmqdpgncak?)IZc;; zQrdY0{;792#pWY|R_XY76arGpC$fw4$-zcVAV_0KrQZHIv{RoaJJs{3&x?ATNzDT^ z>6GghhTaXfr=I8A|98a4VMQ{<8>fVI^JGAS3TQ<;@}-OwdwnB86!_iiPFCa0;^fE# zLwT{XRa-+$Ro>oFH%_NoKQ6O>vWtx4FtHmS=?e=p|CTJMC9_ z?EAyyb>~o=Or$8fnvW3g`;@+yNoYnN{Gf~LY+FP|Bhqi)Md@=Ou4`vk^O9OK9(r*4 z7bxhGkBk?6hYO>+<4NGQfqhBIki86vJynSDPKAnFc z3!xB0^<4HFUWP@{GAJ_fyv(B@y6o)C79|cv3uuV?yF0D|1IxPmSpnt41n>kahS^9^xhhzt={-`zipFO(>k9H8O zO%WsiuA)@UX5z0FJuOfIKgw21tG}3mUyo4CX1oet=x#WnYe0h2T@(CAV0k&nm?Vw5 zO+w#79l|q^h+Ho?jPI>seQ5j0(F~Xz+UT;`EU0dRnaDV4^Jv9i(Z5lD0@PG%?#mL@ zo{#VFKZ>SX8vUdWotI3?7lO9Cym}Aep)Ut$?d;TrQbUilrnzLrmmMqp4Wt-7o>aCb zU)i>?pqe&f3^wuXAeZT|qG`Ne&_Cc;wJ9lJyv2JVegy*KBMW5Af}GPU+PN~r*&TKr zI#zv%<_inl+IMgBmb~?U#IpE+J4+Ay8xRJ3%-4y!tuVD{f*xm#wP9bxiA7Z82z!oX zF1N|oAD{%PBp zpro5Rvf9d!>Fua8=+z(lP5qAHlHkLWr>i3%MJkNT0>JQE8lMe+RdvCH<2pg49VIl+ zFORDfsssB%prN7BTc^_x_~jw#SX2TFI1q8b2v$NHdS7bXsOTKnI_*Bi61v7s6*|LI z_hsU4rSt(<2?CyybWa8>KFTUrO&Xz8RWgwy4OG%l_x1cKH>$R-3#rrBv|d2^#H1dv zCi3%RpVLS=m%_Dwx$J49Sc*;|8S-o870pbX@_Z9O6qOW5J&I(?+q~t4&Hyg_mp@l- z6(^SI;4j5L@Rq+-?*j4uj8QBnSaU=-Bh;W)FtrdAvrM$!+BUkRw|{`#+R4)|n6vlw z!4d{J@tyj0h@~S3Go|>Z4*)u9>XCtqeA2{HL5FYz=x<UR{)RoTWXdoW)mHXah0=h&L=P!gA42b>YO|eCH^uoUnhJ%rLMK!^ zp0u#JbUO_XV1iN(m{->062&q!1Fxcb%}(CJ&f2V|Hf~47h%8`{3RC2A&9NB6hu2b~87HLGC~vNFhu1QMWLUDP-I>NzAp80gU5bZ{5N1h=dZ%WcDVnRPZw*{1(OJqfOV7&kXJhiMzcAGuH+9fmVnI#Jo0hGqU& z^_Vrfp@&jjRAPDgnw@stvnJ(394tXY-1W-9xNeCc^|E69aj=~qNk@B?fFUwX1A29L)kA&S;bc7Nd zTafM++ubYWXMh9-*KigEkeC>im(&j#=7bps<~w*KzEn#g_RhT3zAxuDTX%yJpD3I8 zlY~p4pzo8e(pdtEh(`MNy!h@tiBP_yd!05+sQ z3{TCxcDn2UI4O+43Al820nLSf9h>8GB+yl-rOvJ!#Yq5l}tmO?|z8z9|2VXJmzb zQ#M15X}f;!N*t>f5(7~313gX$(5p)y3hzkDK^(HPJ-;}t=f+;>t!0PS`PC!y=Ls_RP%hBJ3_XELZz-T?{uqxzgfNAta{tWSKGK z)aX$c#jQ!dF=8ti8B@S!iZNT7saVcMC~h zvS!Ng5-|yn|Hl33y)qhhR5w&PL1CWx7t&NP?F7nEb0`t~_N_uaMS1S@C_RY1Ba@*m zg-UT{Q|P2CeKRWMxs?s6w+xs2DLkjZ=qv(+wE(MH!zp11&@;t%RNv(*%ZI1L)T#5c zh*n`6*PjOp#FzqqzXl4ml$L3&*xJC&Q8jEfs5BJ`;l$EJED*~r`9U&N7RkFh_0Wvi z`zoOcwBLdtHzsRPDzDvu3nFE5jnY#F2n)14D>RRBXGGGJQ6K54>QPN>(OX7EPm!%< zO~a1%1auvF0ck$x2QD5`H;n#qk?at?C+Xm|5MzD>D&|suYFSoC`gto3lP|JKw*CP0 zGsk*>SkJHNAG>Iik0*7c-=t^la)mckjN3ehQXFY^br4^u)%6mvTu0(i_Ev)>LG8&h zZiiTXdPTi;i|7RhQ4XyXW|hH;26sW+I0{);^jw-s@}8hEpfLArt6*Q@^zBz{Yyau--WUy+1~|5l6%>e}HbV78ls^={0A)F_5u4Q;fl&FWBs?=MY=ihh z=vm*qisMWYm=dF zyy;_0d(E|7hm8CYL}MemN8Unx3+N##^9ApJP`73BtSsWOp$~mS-DM8$7?d%3rim{x z^8V1X@$hv5Nebzm6>eR~BX<_QZ*H1B&t5+YCi=(NN2BjDsO>`lI`2MRI^MZ}UST&c zkV!T@?veV>=9{`9N%O7#o2=MYK|pUBhYamTRJa#UM$N5(6uC9KH{uF>APcS{iY4rS z>tyfm+5cgt)EqT=a@B=p!08ab>ItWu)uE+?RY~ZTCD!5cd z3|POE=+pc)Y&DBx?W$Eb`=`RR6J-W}OL=3@p=PJGq#Oog3JeJl*R!|52Wxq@>X%ey zPBXYPAg$hx4`jH2kjL6;!V-j`1U#|^xb_#30m6O#H6dcdQQ_ZbXMDq!ySov1Owyjz zUD1bqIdXb*rwknk0%sK=nfH>A_)9Oc-sm_=Y7Zq|kpJf`a}yDWgVQ1UM z`L%zCuc0*0w##fKdaf@Hhudm@#LAylCH5`c7+lpc5E|Vy+2_x>$pH{~cd93)x0%t* z*BKie>*YEW)}xEoy3bQd`7?DiB&wUvETzZ$RFY821D7 z)&8)+Y{F!FfOOfyjl)Bwe z`S9Gz1{~`6StoomX810D-QbdJb92Ko<|QUl3>T$h;|rFPf?V*D!9o31U!Z3GS)F}r z5G_=Oz_!QVm2#EVXf!7IhHWq6+x-%oE`%mTU^W!LV`e@&q)GJ@7H6Tptw~6gq*Vr{ z1$y_}q6-9t)GABx`I$VKhc>m6-YK4iJ=yIU`%cjjHyBz}j%Q|nsrTAk3BJ>8*j#C3 zPo(oAjnA2RsBHVT&2_hRSAVl6@m`|iK!~?K%z@wzG3BKM)R(!o_<}nLA8j^p!|tS` ztW2KFd&S-LFHfkf-{kBoClU$|Nh~IDx>ABmNs>@>i|)sIO?&8BgEP5Ky;a~aZXb`ER6M%Vq` zVYXc1*e+^=S&;B=p4RLMr=Az#^v{H4xY!};#00hw+2y2vGA8tu8``$nH~}gWA(s!U z86Kvb^W3ujm;tgEPxjzBD#+MO|FQI| z3LndCw#PtV`Ux`qdPu&HsS0H1(%q&UEAQE1@-FQCqX()gtn9R$>Fr;BpD!Nt`V^Va zq+^NJ?nR&rbO~c!U*)A=v4fGl&WkbQ)KiA<=|qBm(0}6gFGZT!_|e#UV~!;2dCU%} zJDb(j9<#t78m(!$xx+ny9tKe1L_Tx;K|+QaCY=qX91G+jU7B@61-G8YBH}gp!7Ati z3Al~@Tgb0g?8hFoe=-E_IM!B3%$pZ80nSa%SHK1S-yYJbZB)-3X8{ecaM!mzN|8D} ziS7b_B1(VAdcJ746G(X>OYCqDt@HQ_nqE>Uni2vqlE-H|^9$4*0>cIJU`*oJo@G}vz@YR5 zCQ78IX|Sx{GeR0yr2&h>=HwXy*`HbrfZxG??#*TYcxnPDHNIxHZ80@ak6%SzmGakG*;{N&o9u>?9n`5oM) z99C7l`cd$kKLYV|7bjSI>j*GqWMXB=O(Fc)hkUVX5@yB^=4;E$iq@=@Pp$ZID{4xA zwi`3DuS%xlQ435QpEq$pdilN%|NBa3x`$u%^)d(V z-o8d$4iBf%oL5!S7dcLka5WsI?r4Esk?Ujrt)j`wxp0aYKOAfMxY#+D3Rq=-=gDfD z(9@7ya_aKZD17MRe{za1=+subpocBgoCeUms<0Z8mkYwqXee8^&!!z|4S?TB5ml75 z7(}E6-1~xO@fg`l2?8`hCWN-OkSc(n6ZG<&V?9NxRTiK^YPba-cZ^>HdgzspmTGT& zx~$^O?wI1yz_Oo5#wp7;!B+8q#OQAa8~q*qH9LV`mVIM>UqboTeP+N0)&yOTM0tOgjD>jEs0x{DJK@{J{}K>paVS=fHCG)YntGA#2Fx>YZ`qk zU0l5RRG%wR5^-%3DRx9(buJwtdEL~5wIvAnV3LOUV;mEOJbWdKPYNT9j_wMXb4}*7I+lw))>;B@lj9t z()FEAmH(-qP!vULIdk%V?JO`C^XViSpKS-Sn5fqevJ2^v(}orhb8b0T8(|Ywfh&qoJ2We(!xc&@(%MmL4 z#MM(j)P5q_PIGnRL#Lg0c*cqN?}c2BASVYvsWX>^%*v?R0bqiEQ&Vx+-`>k2mvDuC z5JojrC#T!{!KTDn#XvenZ}L&8zV-v;m)3FYf=_yL-vYt13g)K0ga}QuoJ{~eet2?% zR$DWNelW)sP2zjt648N+WpnUqMFIz{!0WXSpcMwGUKTGqZZ4l`VwCh&P_|ukjP{Mx zl4mfkym({hxzSL6ft5AQnvWC6_cd#}!_eyYBUQ0&Z4?l`50mu*$np$lD!b2Uv%9Ee zV}j$ZYn7#G`Yr+7HRo}!;OAbQ4Uc_B(X4I}^~%B4uh3ufWdM}4nc12d-4o2TmS~8F z>dZJbNhYY5etk*Tnp7L<{TtJ#EH6(OQ<`>&gBYW{>?3u5-Mv4@xv0B@ehPg$6ZxTO zWNGX^fke9({*=j{rRG^O1VNDyScNN>#{k=+_E)&UD@O@KGy86=qUHaQ*M+H(72j{((!4x%m4u;vnbu}wfMEsVo-v-BOZatfswnN`WA~3^{UKB zEd%32Dl>x4UnLVORS+^$L&TQk_T-R|;vk`LSOth7wTaHmtS|UEF1QQ<8p&OHJc8y2 zVu*}?C)ouEA1ZYZrgV35u&_+r zbcM}+NlguawX^gW*2qY=hAC+iZXuQznGHQAfY`(Y5Je_iB{jtQ;=`<>mJOsu#QC3n zuM>l1gdD(6FHhy)&69?Nwwi1OuBMxh`B9pGjXvq3tqOox|-5V&oQl7kLJw@PBt@`<#AT{FvcYvZZ0P@$*ApanqLhIcb=g5J7 z!#4PC7vtnR-v)J&EGjgkNr6%rdAMw@*VyB{z-pR+wvF&ZD=!s8cPd7~_Z+;`%URCn zoQ1{a8ux>T3-qI5;Sn#!_rkgLe=R-K=WT&Dr+x|X*a^hM%vi5X?v~(!Nh~Wk~mBA)1{8YgH##_y))Y5cx&2P*g)D?s?azm6-43bfxx?L!;8AY`$KgxFubn&~P~4 zaxQntIYGIQ@F1_(1+)R*Z`yHxE-|56Xk4yb-c#D3#ySqrjMo-U)yIRXw!;kI#IZUW z0xUftX?h}G2PpqQTf~>(pu@yI@qXBC8x806G8g)N#Sbppqtep%9!EXwi>a=n<@59X z7W$T&j|t$oFGI$u$u{SD#v#cua+}+bOLYOxWG?*sXCZ@w`>sgw;iQm%3Ar)f>+uu@ zpB3&?%A>*uHC{8E9jInAP(7(n_toj7=QzIS9epM)YHU8_ zH60e)v0tsnxhM8Ddfkg3WKe_b!8j0@I6;mo%HT#ChJH8+znK(&#nyv?D_4fBB7Q6( ztNe;)Cxgy$2(4JgzA)9+b^q^N{KQ~ux**`T^Cl1rZ@ zzQ1%dYutm15VsA~8`Vm^>T0-&iZ~L+7H(it z_!g+&t=aV(TUZhtJ-W2a#&&KJH$YKL6WQ7DG8?nR0?TtsjJ98=tv_gHnjls7ezNkI1o7mS0Zn8QBY`R}M6)Qx?XoYzy=EIDi}fa)`EPL+Qjm zG+bK`t{=qHCgk%Hh_baLgu$&f2;wq@K_i~idU2$G=|=acvvFYz{)TU;JFgpmo!|6e z$+AoDrSXMxBZ_$~(XvfEQUpWvIfUef_D#AY-cBQ>MNrBoDD6Bhb0 zBKm=(xNzzT)E1cZuuw4`Uof0`i`2#f;BJq9Qk(=(-dTJ9w4UTue==h|oG$La0Vkkq z!&oXCHa^;^`E$Nza{G6&7!iw2NW=N5nZ;9<@MWQOVX1HZvk9p2%!jK8_8b6PotbCs zeQ(L^NyzdgVDZ)+sXLB}>PAu|8BF|PDgj!QJ!~y)Cy!wrH&p^{;A@}FGA|wS$x^O= zKqQrm0G-Vw8G(OrCozZaB~R3jsPb|RgX9A84{2UspDf)bML(%}tg5HFyukz9AUt+W z+96tp?1V&P_p24UG;uMc=$rVb&`Et1Vd7u2D$BV)5z_GNcx(;4TtQI64AW+0gC`w_ z>Y*p*-%nZ0r^vQC@o|2k~JrUgRyWJ`R>C#eY!J0)h{URmVYp5U_&9 z751j$dcxJHiqJDudn6ToRBoKD82pA|pvqNP!1pq$xQb8teA&FyD`qF`nhJB@bEiiH<911))19Gc(AzG!T2VGB4@DlX4@azz{5mg{4G8-&b*i)D>I6#g%NuTH zr(5h&V-{6{6Y(M8@2}Pwn0GpVa6Yh!ciRx60wW2fL{3nl)fI54U1~Cr^e@Qr)nQ|& ziCA3mR;(T@Lrc&+tdbHvcKAfDkFv&Tc^{k2mr(1QJ9_oO{zdvTC(^8mbMdlx>3uSr z1XlZ{@;rgVr2vYMVVz9N1Oil~k+ub?*PS-Tt{NzGBFtD}Nsnfd-zm9&iyBre71sUA zVtlKV_hiUtG z81cQ}eebjfd30sL|eYLl&yD%7 z(ja^}WNc~I`)rX*(Q<)**}IQjc=+a55@+KNin7Oua|Ued#;!H8!NhhP*x#y9-_PeU>(&F zRS&GlmM;O1uK%dNZU5I+SiV`H2gN7qn*bs8Z=2M33>V?J+t&zcY~}UIoj?^d0ZF&p zuC?!LWYiGbC?1o8dWVn`S048)pY0l!u800iR-P{>=MN}kqFDx8>XN*!-Us5GgJpUC zHlEDh!eaEmmI5<>)GXh<^Og^4%8@5@+uh_0s*4&0>a?4n&F@=yk!ZhQyvvpHvUm(~ zJ`V*|4EG>K5iAF7b6+cT3^w5J`&RRfLsIMMXIOM zW*yl|*lYaOb?N!U`VDd+LDmecC}LGxkW7h4VpUeznJVdCc+r zpyVLESye`Vwy&#KV=Iz_QxU~7$Z+Fi@9Y>cw_UkPrz`ro+>!8s*kh~#e+=}}-d@_Q6s4uMr-W4%R4iTA6?^a0+M|*N0 zRcvZN2F^R_$Wy7j|E<#U#0A{YUbGMDbT-QV5QhD75I~~Tf{{y~=H-}a!}5z-kBf~T ziIeLIadxrD`Ai{;@)O6^ud5VO6(z&?M~6uVOFJ0S8QX{B2V07;=Ple#BjRbiV4^6n z-Y+qK4oDdgYQJy%2dxmvYe?iD-y?@2fP}M!SlFkFZC6^6R-ZzruY#7_Ye+q#0Hp4u z`=8L!W%p`=bh-L|H}8W$bgor%T=WrXJWS~^DKz@{oprSgjVj@WkJd+b2vdA#5$J$h zS|w^(Rno;X@H{I=*R-N`^95^1V=6eB%|>~DYAv>yleh{c^Ek~u`ZjWRd(z_Eh;ZhO zb`QaeI9%}gx;K$ZW#1O)$#kd9Mp;rwvfSRH{ip8_17x{sagP_~d!Vd2Gt+MwRuYM7 zm^8;^sA(Xs@LVWNAqpX3!c4131XH@AxfFduUltBjidX22;}K z9TkkOw|>F;+f*orn=IDMAd{oO#0}f%A;Jh#Gq%F}gkh(FzW*+rZVV~Cq&PIh6Gz;^ zuyDmjBI8i2L!sPxC^Dor2zC^YET4LRvFZ4(&7l!%)Q9T=W=@3_D|f|0@Q?Q-dV6bu z?R9)!Lzwb-y*&aq#7ZXnbcgK9!;@Us^iVHAx{y;NgYyvVb&*U&i-_!HB`o7z#I#R= zf1ddia;paHxlha4?F2c7qU`UU`(#mZvx)B3tWSibdUcXqyat+}M-IT*79=}=_NuAd zxvaX@TwmAty0>q2qw@Kmx03xniG&o2w(|ML84Z^+n`ZyL)QuMSI38fr@@kol8cRQ{ zu3BQEjCsn5UxiOSJunlTvKF6e%$>6HADKRsIqwi9%{k32EUIq-5c!IIQRqD3x}D)F z7#aBPPb1@Z)7BP#Sa8i0qbw?aqV>hnSZGwz0^dEt`YX!5X5FA&UtSs5OjC1Ps)?L4 zaiM^T3OxpNpXVtY^imo6GS|P*2BVCVy!^h|vf_F5jKX-+Um2@s!CJA^s+y7*HFtBC zNHRUzy-?QskQ3J_@TnW-jE4iECpZ}-a_R|NHp_#A_kj(rA^rnPH#G` zR3NT2S_Qy*#I`14G|@x;G=B&tV5Ji+AoBuWYN~2fn=}-SdANJ2b9H3Nj zYS3!@o#5RTLL2`^Be*BRi7H|pFd&!KdK?W+XfeL=fe|40*!bPXV*y*@u4cnfv1!Ut zp8i7)H<>F$ZS%Kh&4|_7FsH`|Z}{X@>@opphEK7rFuDF}ss^Ti4j|U*ZkfkIOZWi_Bz;xM%(uypB)3l zQ4OHgPI#kE1jBKEVLtRGNbJl6XFDVRplf5}LnHTxakNt1qKM0Q`LBNd^N{W*5Fx4{ zvM3)4>w|DF#i3G1QJ4u34-y z;B?PKzqry2ki?c*UhR+_`EOsK01i}UL-ZH~WkR9LU_khP^27Y=es%4X&gh2Zbru-Y z;Yi`y17-P%{&bs+z>MR zlhUwe@zmo!R519RU!?>FH>^|u2TmWi*nZzJ<$0MnbFpq+;X}>(9?HN3N25eBtU?7EyRn5A4e0oU6a;>4_O$-gff+ zJ#6}6iOkjz54hW^)pw~~?eaFXiA&0VS@|s}TkL5CX>3uSdesYPO(ThT zL01D#C)wZ-w3pI-q505bG8b#tT-NCaz@agP*)Hv5TZ!oZG5}=j*x}Bb!%y>4RSUWIFyu-!F9;b(jD8}ZG(bu%$n`wKcaOR9!s$cn~6K1cR_mUCgH@78- zT5=NB)8NYm;jy*F1i&wH(R;HcBa@e}0Ij@mf5;QLYC~SnXBXys{r8qIF4_D%+%*du z>SFiPXcfj7Xb6eCWg0sOl8i?Enl8{=NAVCrUtE;ypm)n<(qYJNLZj`%sLHHK$Y5N5 zsNCBm&6S>MJdY3kD~czHC07*At2tZwV^&@Eg*K*%P3}bEIk+TJ?F_XzDu`Klck8S<#I{_oT_uiT1)7+BB6ai?3w5_iz&+u| z*B4xRkP8X4iY>JC4Lm@?v+#zFpG5nAwLP$@LNn{-Qad6ahD);W0^R0f^v=aO;RV0l zWi9U0luh99DlR=m7!0da>Cf6;^@-2TctY%+QQp*sj8LI{klW}~e>%?Y-}9&Fuaqww z0!*<~wYF6br5|F=*@71g<9m;5W3b#?Ag$!TA(CdvXZ=C#&Ka>JB!-}=Y#yM0zY}-+ z!2vgQZQvH(;9}Vf{18*)Hr0l)?h&H9VF_xEhFKW%534+QNRoE@2^%BX&D2GS8~7x! zN*|#=qZwL&0rO)mlWvxi6)5-Vfl>uY%1g}*TWHH>2Z!>}~u4RRPLhsrC{$0do zm5SekCWF$KgR^=%A_}j9tD*oCSJ>IGg{)C-@&q?EuIV_(!}q%;2b#Z|(v`}LF!X1& z@_ime$yRz-Zrz%a9`c4<7asnnOo;(74cHa3?*dU*^%N=0{ZdB3TQ&rS_me2sOZN8( zIM6?u7A`^Rd)-=LdtdT@)ziAmN+kST@JA^*;yhOi^crdVNX^TOGzGOm%^(0(A5!V* z9qsaUjkNncH9|_g>hz7X;pnw#I{-fQ3KJfW@~?&JuPovgX|bNZ&Oo zk9$MX5ife})E~}Z(w2^>ENBzZLnVTF_&5j60fB4C>-A0+HPr8a9{*b*rc*f8&R2#~ zM@eKbH8_xe=)Fh39SZWNXJ5-4ZH?vi3wR8pz%MR1Ih?t%1E-T{R1Fc{caRgo2aF-kz zpkmUX+jI@F#!U^2gV#IQK!)gIH2MQ=sbRUz0O{g~pm|!R-<%g;r!p4lhA;h|3FKx? ztx+eXXI@JPHQpblg7a?*Ws0NWqNK;X9;a_8cElLb>?t;Xe?$346zNHziSx!`e-l`O zau;hRa<`YW*Z+S_$83h~rPZV1%NN5A>t(EI-fd=%N_xb-x;l`Hw-1%9z$hRf@dP~LwHw#d zSGokwgG6{HNxHV!o=3qMq13pTVQpy1koa&<9= z!yxJK9VmU$Ba1)t1d7uA1ie4KZ~5rZoAUAu;(Ot`>7hc-`-6EcY8^2Xu-{G(zCUNd zf_4Oj+PO|o?kiN7|a&|c27lx>KX7VzBd0(?SZ+@*am zn}F9?9m;N}t2rrh_w}hLUTfgHInvhk6~*v>fr$AX8;o7+)tGQo&yAQOEzFPEXWIj) zu~7}|uo4fSQsaW~5l0JkvYgFXqRJ@U0L)(Z~&HM@aXx5u~rk4?M>Mqzy zyzI(Gv9jE7;bv)mWzf~CIp?bGmNFHcgZnI{OiJ#-B8TMExooy~29opaC%wqRgFlad zLL=o?2UHfTQnO)|GeB5Tu}15Y$nm~Ww(?%W`|qcew2dpRKO5l|=Jo>OslIumWZqi1 z?Qj@YuZ6+d5Kh1TZu~98)OQ@ptJ9g6YQt(30p=kOeLno*>(-e3!rUS`X%H#?9XQw# zKXGFqy4_uP+9+oFr*d%MXr?5h8x)tM;q)i3D}3uxSXI>J1VJN zH@#@lDD4;WxV~${N7m7`4a#`BC0~ybcG&$Wpr$ZrZHF)FOR&t?rzWg5pqj_k1IGp(pBkFj&}S_B zvvk0S09X0(j#nf`qu-{LKH$gQjHN^Hc}uVG4X}ADG8GYfhCM20C0t*2XH!UG_s{& Q{`b zoFb=6J*h9JNdsvpr%NL_LmJDO(nQXZv!$t=Bh93_w2+q4O3sz@8i=`KCw zHt8w1OE0-Y?v&orNA8lo(og!!0J&QR$~|(g43fbzM23noOzxB6GD7Z`2V|s-lF>3o z#>zMuFAvHDc}OP8!!k)G%OmorOp&Sbm`s!DGDBv{ESW8j%N&_2Pso!pPv*-4Stw7* z)3Qhw%QLb>o|Wh1d3ixzl$YdXc}14WGFdLK$_iO2ugNM|EoTi%g({Hp>>-D%)hc?2w(ZOLog1*()E&hw_o^ll}6sd?KI9XY#pxAz#W@ z^0j;;-^zFLz5F0Q%1`pM{35@~Z}PkRA%DtW^0)jW|Bj$w6b14B4~RlhI1Y@1;@~I} zMWa|0k3*tFl#D~8RFsax;_x^k%EXaTHp)f$I4X{gW1>PF8x^BcRE{c9HLAsNaeSN* z)#JoCDQZN`s1+wi?WhxVP7uHEgD3_I6WH08PPb-j3#kboE=T$oM;x!qeZlg zR&j2e7w1RoxF9Z!HqkaNii@LNToRYYWpR025$)s3xGJuWYobG38`nk0xIS)(PH|)0 z6gS5$acgvrF3~l*Mfd0tw?)smJ$l6*acA_7K5F*e4<_;@fT#6vML9*#*dIUb2eV@gbo$6{Jcj~OvDX2tAy zJm$pQcp{#Rc`-j0#KL$go{mMaIG%|m@oYR7&&Lb#V!RYD$1AZkmc{aTHCDvRcr8}N z>R1!6#~ZOW*2VgGGd9Fqu`xEq+wo4k8}G&Yu{pNH*4P%?V@K?aU9mg%#NPNIK8%lI OU+gbf#>WLSC;bOFp!DJZ From 435b6e4e32a46740270c67775a9c0c8fbcc1da1d Mon Sep 17 00:00:00 2001 From: Riccardo Spagni Date: Wed, 16 Mar 2016 22:00:50 +0200 Subject: [PATCH 03/47] bump the version --- src/version.h.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h.in b/src/version.h.in index 08e39c00..07312981 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -1,4 +1,4 @@ #define MONERO_VERSION_TAG "@VERSIONTAG@" -#define MONERO_VERSION "0.9.1.0" +#define MONERO_VERSION "0.9.2.0" #define MONERO_RELEASE_NAME "Hydrogen Helix" #define MONERO_VERSION_FULL MONERO_VERSION "-" MONERO_VERSION_TAG From 498ad737290ad844aca85c6971b14cf434c248d1 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Thu, 17 Mar 2016 22:14:08 +0000 Subject: [PATCH 04/47] tests: enable core tests again They should not have been disabled in the first place --- tests/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 71de09cf..d1be97af 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -54,7 +54,7 @@ else () endif() endif () -#add_subdirectory(core_tests) +add_subdirectory(core_tests) add_subdirectory(crypto) add_subdirectory(functional_tests) add_subdirectory(performance_tests) From 9b3e43c3270877f905efc375ddd6f2cf7f64f460 Mon Sep 17 00:00:00 2001 From: Howard Chu Date: Sat, 19 Mar 2016 12:57:47 +0000 Subject: [PATCH 05/47] Fix issue #706 --- src/cryptonote_core/blockchain.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index f540697d..8b7f298e 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -2798,6 +2798,7 @@ void Blockchain::check_against_checkpoints(const checkpoints& points, bool enfor { const auto& pts = points.get_points(); + CRITICAL_REGION_LOCAL(m_blockchain_lock); m_db->batch_start(); for (const auto& pt : pts) { From db1b2db4d5fd6b4d441faa4bc7c7381e3d88a268 Mon Sep 17 00:00:00 2001 From: Howard Chu Date: Sat, 19 Mar 2016 12:59:05 +0000 Subject: [PATCH 06/47] Reduce log noise --- src/blockchain_db/lmdb/db_lmdb.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index 9ae7b404..4e30548f 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -240,6 +240,8 @@ mdb_txn_safe::mdb_txn_safe(const bool check) : m_txn(NULL), m_tinfo(NULL), m_che mdb_txn_safe::~mdb_txn_safe() { + if (!m_check) + return; LOG_PRINT_L3("mdb_txn_safe: destructor"); if (m_tinfo != nullptr) { @@ -263,8 +265,7 @@ mdb_txn_safe::~mdb_txn_safe() } mdb_txn_abort(m_txn); } - if (m_check) - num_active_txns--; + num_active_txns--; } void mdb_txn_safe::commit(std::string message) From fff238ec94ac6d45fc18c315d7bc590ddfaad63d Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sat, 19 Mar 2016 21:48:36 +0000 Subject: [PATCH 07/47] Print stack trace upon exceptions Useful for debugging users' logs --- CMakeLists.txt | 12 +++ cmake/FindLibunwind.cmake | 25 ++++++ contrib/epee/include/misc_log_ex.h | 2 +- .../epee/include/net/abstract_tcp_server2.inl | 2 +- .../storages/portable_storage_from_bin.h | 4 +- src/blockchain_db/blockchain_db.h | 3 +- .../blockchain_export.cpp | 2 +- .../blockchain_import.cpp | 6 +- src/blockchain_utilities/bootstrap_file.cpp | 44 +++++----- src/blockchain_utilities/fake_core.h | 4 +- src/common/CMakeLists.txt | 3 + src/common/exception.cpp | 84 +++++++++++++++++++ src/common/exception.h | 53 ++++++++++++ src/cryptonote_core/blockchain_storage.h | 3 +- src/cryptonote_core/checkpoints_create.cpp | 1 + src/cryptonote_core/cryptonote_core.cpp | 2 +- src/cryptonote_core/cryptonote_core.h | 1 + src/cryptonote_core/miner.cpp | 1 + .../cryptonote_protocol_handler.h | 1 + src/daemon/command_parser_executor.cpp | 1 + src/daemon/command_server.cpp | 1 + src/daemon/daemon.cpp | 6 +- src/daemon/p2p.h | 2 +- src/daemon/protocol.h | 2 +- src/daemon/rpc.h | 4 +- src/daemon/rpc_command_executor.cpp | 3 +- src/daemonizer/posix_fork.cpp | 3 +- src/miner/simpleminer.cpp | 1 + src/p2p/data_logger.cpp | 7 +- src/simplewallet/simplewallet.cpp | 2 +- src/wallet/wallet2.cpp | 5 +- src/wallet/wallet2.h | 1 + src/wallet/wallet2_api.cpp | 1 + tests/core_proxy/core_proxy.h | 2 +- tests/core_tests/chaingen.cpp | 12 +-- tests/core_tests/chaingen.h | 6 +- tests/core_tests/tx_validation.cpp | 2 +- tests/net_load_tests/clt.cpp | 1 + tests/net_load_tests/srv.cpp | 1 + tests/unit_tests/ban.cpp | 2 +- tests/unit_tests/epee_boosted_tcp_server.cpp | 2 +- tests/unit_tests/mnemonics.cpp | 1 + tests/unit_tests/test_protocol_pack.cpp | 1 + 43 files changed, 259 insertions(+), 63 deletions(-) create mode 100644 cmake/FindLibunwind.cmake create mode 100644 src/common/exception.cpp create mode 100644 src/common/exception.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9674404b..ea1d5d7e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -243,6 +243,14 @@ endif() add_definitions("-DBLOCKCHAIN_DB=${BLOCKCHAIN_DB}") +find_package(Libunwind) +if(LIBUNWIND_FOUND) + message(STATUS "Using libunwind to provide stack traces") + add_definitions("-DHAVE_LIBUNWIND") +else() + message(STATUS "Stack traces disabled") +endif() + if (UNIX AND NOT APPLE) # Note that at the time of this writing the -Wstrict-prototypes flag added below will make this fail set(THREADS_PREFER_PTHREAD_FLAG ON) @@ -274,6 +282,10 @@ if (BERKELEY_DB) include_directories(${BDB_INCLUDE}) endif() +# Final setup for libunwind +include_directories(${LIBUNWIND_INCLUDE}) +link_directories(${LIBUNWIND_LIBRARY_DIRS}) + if(MSVC) add_definitions("/bigobj /MP /W3 /GS- /D_CRT_SECURE_NO_WARNINGS /wd4996 /wd4345 /D_WIN32_WINNT=0x0600 /DWIN32_LEAN_AND_MEAN /DGTEST_HAS_TR1_TUPLE=0 /FIinline_c.h /D__SSE4_1__") # set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /Dinline=__inline") diff --git a/cmake/FindLibunwind.cmake b/cmake/FindLibunwind.cmake new file mode 100644 index 00000000..9946e7cd --- /dev/null +++ b/cmake/FindLibunwind.cmake @@ -0,0 +1,25 @@ +# - Try to find libunwind +# Once done this will define +# +# LIBUNWIND_FOUND - system has libunwind +# LIBUNWIND_INCLUDE_DIR - the libunwind include directory +# LIBUNWIND_LIBRARIES - Link these to use libunwind +# LIBUNWIND_DEFINITIONS - Compiler switches required for using libunwind + +# Copyright (c) 2006, Alexander Dymo, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +find_path(LIBUNWIND_INCLUDE_DIR libunwind.h + /usr/include + /usr/local/include +) + +find_library(LIBUNWIND_LIBRARIES NAMES unwind ) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Libunwind "Could not find libunwind" LIBUNWIND_INCLUDE_DIR LIBUNWIND_LIBRARIES) +# show the LIBUNWIND_INCLUDE_DIR and LIBUNWIND_LIBRARIES variables only in the advanced view +mark_as_advanced(LIBUNWIND_INCLUDE_DIR LIBUNWIND_LIBRARIES ) + diff --git a/contrib/epee/include/misc_log_ex.h b/contrib/epee/include/misc_log_ex.h index d1451ff1..58d47c47 100644 --- a/contrib/epee/include/misc_log_ex.h +++ b/contrib/epee/include/misc_log_ex.h @@ -1426,7 +1426,7 @@ POP_WARNINGS #define CATCH_ENTRY_L4(lacation, return_val) CATCH_ENTRY(lacation, return_val) -#define ASSERT_MES_AND_THROW(message) {LOG_ERROR(message); std::stringstream ss; ss << message; throw std::runtime_error(ss.str());} +#define ASSERT_MES_AND_THROW(message) {LOG_ERROR(message); std::stringstream ss; ss << message; throw tools::runtime_error(ss.str());} #define CHECK_AND_ASSERT_THROW_MES(expr, message) {if(!(expr)) ASSERT_MES_AND_THROW(message);} diff --git a/contrib/epee/include/net/abstract_tcp_server2.inl b/contrib/epee/include/net/abstract_tcp_server2.inl index 698e1947..e4a46120 100644 --- a/contrib/epee/include/net/abstract_tcp_server2.inl +++ b/contrib/epee/include/net/abstract_tcp_server2.inl @@ -777,7 +777,7 @@ POP_WARNINGS { m_thread_name_prefix = prefix_name; auto it = server_type_map.find(m_thread_name_prefix); - if (it==server_type_map.end()) throw std::runtime_error("Unknown prefix/server type:" + std::string(prefix_name)); + if (it==server_type_map.end()) throw tools::runtime_error("Unknown prefix/server type:" + std::string(prefix_name)); auto connection_type = it->second; // the value of type _info_c("net/RPClog", "Set server type to: " << connection_type << " from name: " << m_thread_name_prefix); _info_c("net/RPClog", "prefix_name = " << prefix_name); diff --git a/contrib/epee/include/storages/portable_storage_from_bin.h b/contrib/epee/include/storages/portable_storage_from_bin.h index bc2fb146..4cb2003e 100644 --- a/contrib/epee/include/storages/portable_storage_from_bin.h +++ b/contrib/epee/include/storages/portable_storage_from_bin.h @@ -84,9 +84,9 @@ namespace epee inline throwable_buffer_reader::throwable_buffer_reader(const void* ptr, size_t sz) { if(!ptr) - throw std::runtime_error("throwable_buffer_reader: ptr==nullptr"); + throw tools::runtime_error("throwable_buffer_reader: ptr==nullptr"); if(!sz) - throw std::runtime_error("throwable_buffer_reader: sz==0"); + throw tools::runtime_error("throwable_buffer_reader: sz==0"); m_ptr = (uint8_t*)ptr; m_count = sz; m_recursion_count = 0; diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index 3396b8c2..b9db6d19 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -33,6 +33,7 @@ #include #include #include +#include "common/exception.h" #include "crypto/hash.h" #include "cryptonote_core/cryptonote_basic.h" #include "cryptonote_core/difficulty.h" @@ -151,7 +152,7 @@ struct output_data_t /*********************************** * Exception Definitions ***********************************/ -class DB_EXCEPTION : public std::exception +class DB_EXCEPTION : public tools::exception { private: std::string m; diff --git a/src/blockchain_utilities/blockchain_export.cpp b/src/blockchain_utilities/blockchain_export.cpp index 964c610c..4c5e5fd0 100644 --- a/src/blockchain_utilities/blockchain_export.cpp +++ b/src/blockchain_utilities/blockchain_export.cpp @@ -198,7 +198,7 @@ int main(int argc, char* argv[]) else { LOG_ERROR("Attempted to use non-existent database type: " << db_type); - throw std::runtime_error("Attempting to use non-existent database type"); + throw tools::runtime_error("Attempting to use non-existent database type"); } LOG_PRINT_L0("database: " << db_type); diff --git a/src/blockchain_utilities/blockchain_import.cpp b/src/blockchain_utilities/blockchain_import.cpp index 1aaf2bdd..43f6af6d 100644 --- a/src/blockchain_utilities/blockchain_import.cpp +++ b/src/blockchain_utilities/blockchain_import.cpp @@ -359,14 +359,14 @@ int import_from_file(FakeCore& simple_core, const std::string& import_file_path, str1.assign(buffer1, sizeof(chunk_size)); if (! ::serialization::parse_binary(str1, chunk_size)) { - throw std::runtime_error("Error in deserialization of chunk size"); + throw tools::runtime_error("Error in deserialization of chunk size"); } LOG_PRINT_L3("chunk_size: " << chunk_size); if (chunk_size > BUFFER_SIZE) { LOG_PRINT_L0("WARNING: chunk_size " << chunk_size << " > BUFFER_SIZE " << BUFFER_SIZE); - throw std::runtime_error("Aborting: chunk size exceeds buffer size"); + throw tools::runtime_error("Aborting: chunk size exceeds buffer size"); } if (chunk_size > 100000) { @@ -406,7 +406,7 @@ int import_from_file(FakeCore& simple_core, const std::string& import_file_path, str1.assign(buffer_block, chunk_size); bootstrap::block_package bp; if (! ::serialization::parse_binary(str1, bp)) - throw std::runtime_error("Error in deserialization of chunk"); + throw tools::runtime_error("Error in deserialization of chunk"); int display_interval = 1000; int progress_interval = 10; diff --git a/src/blockchain_utilities/bootstrap_file.cpp b/src/blockchain_utilities/bootstrap_file.cpp index da3b4459..247709f5 100644 --- a/src/blockchain_utilities/bootstrap_file.cpp +++ b/src/blockchain_utilities/bootstrap_file.cpp @@ -117,7 +117,7 @@ bool BootstrapFile::initialize_file() std::string blob; if (! ::serialization::dump_binary(file_magic, blob)) { - throw std::runtime_error("Error in serialization of file magic"); + throw tools::runtime_error("Error in serialization of file magic"); } *m_raw_data_file << blob; @@ -143,7 +143,7 @@ bool BootstrapFile::initialize_file() if (! ::serialization::dump_binary(bd_size, blob)) { - throw std::runtime_error("Error in serialization of bootstrap::file_info size"); + throw tools::runtime_error("Error in serialization of bootstrap::file_info size"); } *output_stream_header << blob; *output_stream_header << bd; @@ -154,7 +154,7 @@ bool BootstrapFile::initialize_file() if (! ::serialization::dump_binary(bd_size, blob)) { - throw std::runtime_error("Error in serialization of bootstrap::blocks_info size"); + throw tools::runtime_error("Error in serialization of bootstrap::blocks_info size"); } *output_stream_header << blob; *output_stream_header << bd; @@ -181,7 +181,7 @@ void BootstrapFile::flush_chunk() std::string blob; if (! ::serialization::dump_binary(chunk_size, blob)) { - throw std::runtime_error("Error in serialization of chunk size"); + throw tools::runtime_error("Error in serialization of chunk size"); } *m_raw_data_file << blob; @@ -197,7 +197,7 @@ void BootstrapFile::flush_chunk() if (static_cast(num_chars_written) != chunk_size) { LOG_PRINT_RED_L0("Error writing chunk: height: " << m_cur_height << " chunk_size: " << chunk_size << " num chars written: " << num_chars_written); - throw std::runtime_error("Error writing chunk"); + throw tools::runtime_error("Error writing chunk"); } m_buffer.clear(); @@ -221,7 +221,7 @@ void BootstrapFile::write_block(block& block) { if (tx_id == null_hash) { - throw std::runtime_error("Aborting: tx == null_hash"); + throw tools::runtime_error("Aborting: tx == null_hash"); } #if SOURCE_DB == DB_MEMORY const transaction* tx = m_blockchain_storage->get_tx(tx_id); @@ -233,14 +233,14 @@ void BootstrapFile::write_block(block& block) if(tx == NULL) { if (! m_tx_pool) - throw std::runtime_error("Aborting: tx == NULL, so memory pool required to get tx, but memory pool isn't enabled"); + throw tools::runtime_error("Aborting: tx == NULL, so memory pool required to get tx, but memory pool isn't enabled"); else { transaction tx; if(m_tx_pool->get_transaction(tx_id, tx)) txs.push_back(tx); else - throw std::runtime_error("Aborting: tx not found in pool"); + throw tools::runtime_error("Aborting: tx not found in pool"); } } else @@ -362,16 +362,16 @@ uint64_t BootstrapFile::seek_to_first_chunk(std::ifstream& import_file) char buf1[2048]; import_file.read(buf1, sizeof(file_magic)); if (! import_file) - throw std::runtime_error("Error reading expected number of bytes"); + throw tools::runtime_error("Error reading expected number of bytes"); str1.assign(buf1, sizeof(file_magic)); if (! ::serialization::parse_binary(str1, file_magic)) - throw std::runtime_error("Error in deserialization of file_magic"); + throw tools::runtime_error("Error in deserialization of file_magic"); if (file_magic != blockchain_raw_magic) { LOG_PRINT_RED_L0("bootstrap file not recognized"); - throw std::runtime_error("Aborting"); + throw tools::runtime_error("Aborting"); } else LOG_PRINT_L0("bootstrap file recognized"); @@ -381,20 +381,20 @@ uint64_t BootstrapFile::seek_to_first_chunk(std::ifstream& import_file) import_file.read(buf1, sizeof(buflen_file_info)); str1.assign(buf1, sizeof(buflen_file_info)); if (! import_file) - throw std::runtime_error("Error reading expected number of bytes"); + throw tools::runtime_error("Error reading expected number of bytes"); if (! ::serialization::parse_binary(str1, buflen_file_info)) - throw std::runtime_error("Error in deserialization of buflen_file_info"); + throw tools::runtime_error("Error in deserialization of buflen_file_info"); LOG_PRINT_L1("bootstrap::file_info size: " << buflen_file_info); if (buflen_file_info > sizeof(buf1)) - throw std::runtime_error("Error: bootstrap::file_info size exceeds buffer size"); + throw tools::runtime_error("Error: bootstrap::file_info size exceeds buffer size"); import_file.read(buf1, buflen_file_info); if (! import_file) - throw std::runtime_error("Error reading expected number of bytes"); + throw tools::runtime_error("Error reading expected number of bytes"); str1.assign(buf1, buflen_file_info); bootstrap::file_info bfi; if (! ::serialization::parse_binary(str1, bfi)) - throw std::runtime_error("Error in deserialization of bootstrap::file_info"); + throw tools::runtime_error("Error in deserialization of bootstrap::file_info"); LOG_PRINT_L0("bootstrap file v" << unsigned(bfi.major_version) << "." << unsigned(bfi.minor_version)); LOG_PRINT_L0("bootstrap magic size: " << sizeof(file_magic)); LOG_PRINT_L0("bootstrap header size: " << bfi.header_size); @@ -412,7 +412,7 @@ uint64_t BootstrapFile::count_blocks(const std::string& import_file_path) if (!boost::filesystem::exists(raw_file_path, ec)) { LOG_PRINT_L0("bootstrap file not found: " << raw_file_path); - throw std::runtime_error("Aborting"); + throw tools::runtime_error("Aborting"); } std::ifstream import_file; import_file.open(import_file_path, std::ios_base::binary | std::ifstream::in); @@ -421,7 +421,7 @@ uint64_t BootstrapFile::count_blocks(const std::string& import_file_path) if (import_file.fail()) { LOG_PRINT_L0("import_file.open() fail"); - throw std::runtime_error("Aborting"); + throw tools::runtime_error("Aborting"); } uint64_t full_header_size; // 4 byte magic + length of header structures @@ -456,7 +456,7 @@ uint64_t BootstrapFile::count_blocks(const std::string& import_file_path) str1.assign(buf1, sizeof(chunk_size)); if (! ::serialization::parse_binary(str1, chunk_size)) - throw std::runtime_error("Error in deserialization of chunk_size"); + throw tools::runtime_error("Error in deserialization of chunk_size"); LOG_PRINT_L3("chunk_size: " << chunk_size); if (chunk_size > BUFFER_SIZE) @@ -464,7 +464,7 @@ uint64_t BootstrapFile::count_blocks(const std::string& import_file_path) std::cout << refresh_string; LOG_PRINT_L0("WARNING: chunk_size " << chunk_size << " > BUFFER_SIZE " << BUFFER_SIZE << " height: " << h-1); - throw std::runtime_error("Aborting: chunk size exceeds buffer size"); + throw tools::runtime_error("Aborting: chunk size exceeds buffer size"); } if (chunk_size > 100000) { @@ -475,7 +475,7 @@ uint64_t BootstrapFile::count_blocks(const std::string& import_file_path) else if (chunk_size <= 0) { std::cout << refresh_string; LOG_PRINT_L0("ERROR: chunk_size " << chunk_size << " <= 0" << " height: " << h-1); - throw std::runtime_error("Aborting"); + throw tools::runtime_error("Aborting"); } // skip to next expected block size value import_file.seekg(chunk_size, std::ios_base::cur); @@ -483,7 +483,7 @@ uint64_t BootstrapFile::count_blocks(const std::string& import_file_path) std::cout << refresh_string; LOG_PRINT_L0("ERROR: unexpected end of file: bytes read before error: " << import_file.gcount() << " of chunk_size " << chunk_size); - throw std::runtime_error("Aborting"); + throw tools::runtime_error("Aborting"); } bytes_read += chunk_size; diff --git a/src/blockchain_utilities/fake_core.h b/src/blockchain_utilities/fake_core.h index 2fb5031d..3840a1c1 100644 --- a/src/blockchain_utilities/fake_core.h +++ b/src/blockchain_utilities/fake_core.h @@ -78,7 +78,7 @@ struct fake_core_db else { LOG_ERROR("Attempted to use non-existent database type: " << db_type); - throw std::runtime_error("Attempting to use non-existent database type"); + throw tools::runtime_error("Attempting to use non-existent database type"); } boost::filesystem::path folder(path); @@ -176,7 +176,7 @@ struct fake_core_memory { // TODO: // would need to refactor handle_block_to_main_chain() to have a direct add_block() method like Blockchain class - throw std::runtime_error("direct add_block() method not implemented for in-memory db"); + throw tools::runtime_error("direct add_block() method not implemented for in-memory db"); return 2; } diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 2289704e..5537900a 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -31,6 +31,7 @@ set(common_sources command_line.cpp dns_utils.cpp util.cpp + exception.cpp i18n.cpp) set(common_headers) @@ -48,6 +49,7 @@ set(common_private_headers unordered_containers_boost_serialization.h util.h varint.h + exception.h i18n.h) bitmonero_private_headers(common @@ -60,6 +62,7 @@ target_link_libraries(common LINK_PRIVATE crypto ${UNBOUND_LIBRARY} + ${LIBUNWIND_LIBRARIES} ${Boost_DATE_TIME_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} diff --git a/src/common/exception.cpp b/src/common/exception.cpp new file mode 100644 index 00000000..f8954329 --- /dev/null +++ b/src/common/exception.cpp @@ -0,0 +1,84 @@ +// Copyright (c) 2016, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "common/exception.h" +#include "misc_log_ex.h" +#define UNW_LOCAL_ONLY +#include +#include + +namespace tools +{ + +void log_stack_trace(const char *msg) +{ +#ifdef HAVE_LIBUNWIND + unw_context_t ctx; + unw_cursor_t cur; + unw_word_t ip, off; + unsigned level; + char sym[512], *dsym; + int status; + + if (msg) + LOG_PRINT_L0(msg); + LOG_PRINT_L0("Unwinded call stack:"); + if (unw_getcontext(&ctx) < 0) { + LOG_PRINT_L0("Failed to create unwind context"); + return; + } + if (unw_init_local(&cur, &ctx) < 0) { + LOG_PRINT_L0("Failed to find the first unwind frame"); + return; + } + for (level = 1; level < 999; ++level) { // 999 for safety + int ret = unw_step(&cur); + if (ret < 0) { + LOG_PRINT_L0("Failed to find the next frame"); + return; + } + if (ret == 0) + break; + if (unw_get_reg(&cur, UNW_REG_IP, &ip) < 0) { + LOG_PRINT_L0(" " << std::setw(4) << level); + continue; + } + if (unw_get_proc_name(&cur, sym, sizeof(sym), &off) < 0) { + LOG_PRINT_L0(" " << std::setw(4) << level << std::setbase(16) << std::setw(20) << "0x" << ip); + continue; + } + dsym = abi::__cxa_demangle(sym, NULL, NULL, &status); + LOG_PRINT_L0(" " << std::setw(4) << level << std::setbase(16) << std::setw(20) << "0x" << ip << " " << (!status && dsym ? dsym : sym) << " + " << "0x" << off); + free(dsym); + } +#else +#warning libunwind disabled, no stack traces +#endif +} + +} // namespace tools diff --git a/src/common/exception.h b/src/common/exception.h new file mode 100644 index 00000000..93e0d401 --- /dev/null +++ b/src/common/exception.h @@ -0,0 +1,53 @@ +// Copyright (c) 2016, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef MONERO_EXCEPTION_H +#define MONERO_EXCEPTION_H + +#include +#include + +namespace tools +{ + +void log_stack_trace(const char *msg); + +class exception: public std::exception { +public: + exception() { log_stack_trace(what()); } +}; + +class runtime_error: public std::runtime_error { +public: + explicit runtime_error(const std::string &s): std::runtime_error(s) { log_stack_trace(what()); } + explicit runtime_error(const char *s): std::runtime_error(s) { log_stack_trace(what()); } +}; + +} // namespace tools + +#endif diff --git a/src/cryptonote_core/blockchain_storage.h b/src/cryptonote_core/blockchain_storage.h index 878202cf..e25f8011 100644 --- a/src/cryptonote_core/blockchain_storage.h +++ b/src/cryptonote_core/blockchain_storage.h @@ -41,6 +41,7 @@ #include "syncobj.h" #include "string_tools.h" +#include "common/exception.h" #include "tx_pool.h" #include "cryptonote_basic.h" #include "common/util.h" @@ -314,7 +315,7 @@ namespace cryptonote "m_invalid_blocks: " << m_invalid_blocks.size() << ENDL << "m_current_block_cumul_sz_limit: " << m_current_block_cumul_sz_limit); - throw std::runtime_error("Blockchain data corruption"); + throw tools::runtime_error("Blockchain data corruption"); } } } diff --git a/src/cryptonote_core/checkpoints_create.cpp b/src/cryptonote_core/checkpoints_create.cpp index 41f2321d..41246e8d 100644 --- a/src/cryptonote_core/checkpoints_create.cpp +++ b/src/cryptonote_core/checkpoints_create.cpp @@ -28,6 +28,7 @@ // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers +#include "common/exception.h" #include "checkpoints_create.h" #include "common/dns_utils.h" #include "include_base_utils.h" diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 6f0fe88a..907f9d80 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -161,7 +161,7 @@ namespace cryptonote cryptonote::checkpoints checkpoints; if (!cryptonote::create_checkpoints(checkpoints)) { - throw std::runtime_error("Failed to initialize checkpoints"); + throw tools::runtime_error("Failed to initialize checkpoints"); } set_checkpoints(std::move(checkpoints)); diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 32f0b2ad..186c1c93 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -36,6 +36,7 @@ #include #include +#include #include "p2p/net_node_common.h" #include "cryptonote_protocol/cryptonote_protocol_handler_common.h" #include "storages/portable_storage_template_helper.h" diff --git a/src/cryptonote_core/miner.cpp b/src/cryptonote_core/miner.cpp index bad3f30b..7bc126cd 100644 --- a/src/cryptonote_core/miner.cpp +++ b/src/cryptonote_core/miner.cpp @@ -34,6 +34,7 @@ #include #include #include +#include "common/exception.h" #include "misc_language.h" #include "include_base_utils.h" #include "cryptonote_basic_impl.h" diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.h b/src/cryptonote_protocol/cryptonote_protocol_handler.h index 1c92958c..2112b508 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.h +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.h @@ -38,6 +38,7 @@ #include #include +#include "common/exception.h" #include "storages/levin_abstract_invoke2.h" #include "warnings.h" #include "cryptonote_protocol_defs.h" diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index 0b42257e..c48b836e 100644 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -26,6 +26,7 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include "common/exception.h" #include "cryptonote_core/cryptonote_basic_impl.h" #include "daemon/command_parser_executor.h" diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp index 206de9d4..7e513963 100644 --- a/src/daemon/command_server.cpp +++ b/src/daemon/command_server.cpp @@ -26,6 +26,7 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include "common/exception.h" #include "cryptonote_config.h" #include "version.h" #include "daemon/command_server.h" diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index e79823d0..d4453835 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -110,7 +110,7 @@ bool t_daemon::run(bool interactive) { if (nullptr == mp_internals) { - throw std::runtime_error{"Can't run stopped daemon"}; + throw tools::runtime_error{"Can't run stopped daemon"}; } tools::signal_handler::install(std::bind(&daemonize::t_daemon::stop_p2p, this)); @@ -155,7 +155,7 @@ void t_daemon::stop() { if (nullptr == mp_internals) { - throw std::runtime_error{"Can't stop stopped daemon"}; + throw tools::runtime_error{"Can't stop stopped daemon"}; } mp_internals->p2p.stop(); mp_internals->rpc.stop(); @@ -166,7 +166,7 @@ void t_daemon::stop_p2p() { if (nullptr == mp_internals) { - throw std::runtime_error{"Can't send stop signal to a stopped daemon"}; + throw tools::runtime_error{"Can't send stop signal to a stopped daemon"}; } mp_internals->p2p.get().send_stop_signal(); } diff --git a/src/daemon/p2p.h b/src/daemon/p2p.h index 3858989c..b74a5e72 100644 --- a/src/daemon/p2p.h +++ b/src/daemon/p2p.h @@ -60,7 +60,7 @@ public: LOG_PRINT_L0("Initializing p2p server..."); if (!m_server.init(vm)) { - throw std::runtime_error("Failed to initialize p2p server."); + throw tools::runtime_error("Failed to initialize p2p server."); } LOG_PRINT_L0("P2p server initialized OK"); } diff --git a/src/daemon/protocol.h b/src/daemon/protocol.h index 8e2add4a..5f6c317b 100644 --- a/src/daemon/protocol.h +++ b/src/daemon/protocol.h @@ -50,7 +50,7 @@ public: LOG_PRINT_L0("Initializing cryptonote protocol..."); if (!m_protocol.init(vm)) { - throw std::runtime_error("Failed to initialize cryptonote protocol."); + throw tools::runtime_error("Failed to initialize cryptonote protocol."); } LOG_PRINT_L0("Cryptonote protocol initialized OK"); } diff --git a/src/daemon/rpc.h b/src/daemon/rpc.h index bfd2afd8..caeae9a3 100644 --- a/src/daemon/rpc.h +++ b/src/daemon/rpc.h @@ -55,7 +55,7 @@ public: LOG_PRINT_L0("Initializing core rpc server..."); if (!m_server.init(vm)) { - throw std::runtime_error("Failed to initialize core rpc server."); + throw tools::runtime_error("Failed to initialize core rpc server."); } LOG_PRINT_GREEN("Core rpc server initialized OK on port: " << m_server.get_binded_port(), LOG_LEVEL_0); } @@ -65,7 +65,7 @@ public: LOG_PRINT_L0("Starting core rpc server..."); if (!m_server.run(2, false)) { - throw std::runtime_error("Failed to start core rpc server."); + throw tools::runtime_error("Failed to start core rpc server."); } LOG_PRINT_L0("Core rpc server started ok"); } diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index ffda7cde..13cc5fad 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -28,6 +28,7 @@ // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers +#include "common/exception.h" #include "string_tools.h" #include "common/scoped_message_writer.h" #include "daemon/rpc_command_executor.h" @@ -88,7 +89,7 @@ t_rpc_command_executor::t_rpc_command_executor( { if (rpc_server == NULL) { - throw std::runtime_error("If not calling commands via RPC, rpc_server pointer must be non-null"); + throw tools::runtime_error("If not calling commands via RPC, rpc_server pointer must be non-null"); } } diff --git a/src/daemonizer/posix_fork.cpp b/src/daemonizer/posix_fork.cpp index c068912e..84ff8886 100644 --- a/src/daemonizer/posix_fork.cpp +++ b/src/daemonizer/posix_fork.cpp @@ -4,6 +4,7 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // +#include "common/exception.h" #include "daemonizer/posix_fork.h" #include "misc_log_ex.h" @@ -20,7 +21,7 @@ namespace { void quit(std::string const & message) { LOG_ERROR(message); - throw std::runtime_error(message); + throw tools::runtime_error(message); } } diff --git a/src/miner/simpleminer.cpp b/src/miner/simpleminer.cpp index ba956d90..19db3f53 100644 --- a/src/miner/simpleminer.cpp +++ b/src/miner/simpleminer.cpp @@ -28,6 +28,7 @@ // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers +#include "common/exception.h" #include "common/command_line.h" #include "misc_log_ex.h" #include "simpleminer.h" diff --git a/src/p2p/data_logger.cpp b/src/p2p/data_logger.cpp index ca0726c5..d3ff0ca8 100644 --- a/src/p2p/data_logger.cpp +++ b/src/p2p/data_logger.cpp @@ -33,6 +33,7 @@ #include #include #include +#include "common/exception.h" #include "../../contrib/otshell_utils/utils.hpp" namespace epee @@ -43,7 +44,7 @@ namespace net_utils boost::call_once(m_singleton, [] { _info_c("dbg/data","Creating singleton of data_logger"); - if (m_state != data_logger_state::state_before_init) { _erro_c("dbg/data","Internal error in singleton"); throw std::runtime_error("data_logger singleton"); } + if (m_state != data_logger_state::state_before_init) { _erro_c("dbg/data","Internal error in singleton"); throw tools::runtime_error("data_logger singleton"); } m_state = data_logger_state::state_during_init; m_obj.reset(new data_logger()); m_state = data_logger_state::state_ready_to_use; @@ -52,7 +53,7 @@ namespace net_utils if (m_state != data_logger_state::state_ready_to_use) { _erro ("trying to use not working data_logger"); - throw std::runtime_error("data_logger ctor state"); + throw tools::runtime_error("data_logger ctor state"); } return * m_obj; @@ -60,7 +61,7 @@ namespace net_utils data_logger::data_logger() { _note_c("dbg/data","Starting data logger (for graphs data)"); - if (m_state != data_logger_state::state_during_init) { _erro_c("dbg/data","Singleton ctor state"); throw std::runtime_error("data_logger ctor state"); } + if (m_state != data_logger_state::state_during_init) { _erro_c("dbg/data","Singleton ctor state"); throw tools::runtime_error("data_logger ctor state"); } boost::lock_guard lock(mMutex); // lock // prepare all the files for given data channels: diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 73b56f2b..a6ef292e 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -561,7 +561,7 @@ bool simple_wallet::set_variable(const std::vector &args) { if (args.empty()) { - fail_msg_writer() << tr("set: needs an argument. available options: seed, always-confirm-transfers, default-mixin, auto-refresh, refresh-type"); + fail_msg_writer() << tr("set: needs an argument. available options: seed, always-confirm-transfers, store-tx-info, default-mixin, auto-refresh, refresh-type"); return true; } else diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 2afe08cb..ef17a770 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -32,6 +32,7 @@ #include #include +#include "common/exception.h" #include "include_base_utils.h" using namespace epee; @@ -803,7 +804,7 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re // handle error from async fetching thread if (error) { - throw std::runtime_error("proxy exception in refresh thread"); + throw tools::runtime_error("proxy exception in refresh thread"); } } catch (const std::exception&) @@ -2012,7 +2013,7 @@ std::vector wallet2::create_transactions(std::vector ptx_vector; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index f798f404..0961ad98 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -35,6 +35,7 @@ #include #include +#include "common/exception.h" #include "include_base_utils.h" #include "cryptonote_core/account.h" #include "cryptonote_core/account_boost_serialization.h" diff --git a/src/wallet/wallet2_api.cpp b/src/wallet/wallet2_api.cpp index 0644e369..0cb25339 100644 --- a/src/wallet/wallet2_api.cpp +++ b/src/wallet/wallet2_api.cpp @@ -28,6 +28,7 @@ // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers +#include "common/exception.h" #include "wallet2_api.h" #include "wallet2.h" #include "mnemonics/electrum-words.h" diff --git a/tests/core_proxy/core_proxy.h b/tests/core_proxy/core_proxy.h index 0315fc8c..22f4c042 100644 --- a/tests/core_proxy/core_proxy.h +++ b/tests/core_proxy/core_proxy.h @@ -82,7 +82,7 @@ namespace tests bool on_idle(){return true;} bool find_blockchain_supplement(const std::list& qblock_ids, cryptonote::NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp){return true;} bool handle_get_objects(cryptonote::NOTIFY_REQUEST_GET_OBJECTS::request& arg, cryptonote::NOTIFY_RESPONSE_GET_OBJECTS::request& rsp, cryptonote::cryptonote_connection_context& context){return true;} - cryptonote::blockchain_storage &get_blockchain_storage() { throw std::runtime_error("Called invalid member function: please never call get_blockchain_storage on the TESTING class proxy_core."); } + cryptonote::blockchain_storage &get_blockchain_storage() { throw tools::runtime_error("Called invalid member function: please never call get_blockchain_storage on the TESTING class proxy_core."); } bool get_test_drop_download() {return true;} bool get_test_drop_download_height() {return true;} bool prepare_handle_incoming_blocks(const std::list &blocks) { return true; } diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp index 4fdd0a60..b897d923 100644 --- a/tests/core_tests/chaingen.cpp +++ b/tests/core_tests/chaingen.cpp @@ -58,7 +58,7 @@ void test_generator::get_block_chain(std::vector& blockchain, const auto it = m_blocks_info.find(curr); if (m_blocks_info.end() == it) { - throw std::runtime_error("block hash wasn't found"); + throw tools::runtime_error("block hash wasn't found"); } blockchain.push_back(it->second); @@ -82,7 +82,7 @@ uint64_t test_generator::get_already_generated_coins(const crypto::hash& blk_id) { auto it = m_blocks_info.find(blk_id); if (it == m_blocks_info.end()) - throw std::runtime_error("block hash wasn't found"); + throw tools::runtime_error("block hash wasn't found"); return it->second.already_generated_coins; } @@ -323,7 +323,7 @@ bool init_output_indices(map_output_idx_t& outs, std::mapsecond); } @@ -486,11 +486,11 @@ void fill_tx_sources_and_destinations(const std::vector& event destinations.clear(); if (!fill_tx_sources(sources, events, blk_head, from, amount + fee, nmix)) - throw std::runtime_error("couldn't fill transaction sources"); + throw tools::runtime_error("couldn't fill transaction sources"); tx_destination_entry de; if (!fill_tx_destination(de, to, amount)) - throw std::runtime_error("couldn't fill transaction destination"); + throw tools::runtime_error("couldn't fill transaction destination"); destinations.push_back(de); tx_destination_entry de_change; @@ -498,7 +498,7 @@ void fill_tx_sources_and_destinations(const std::vector& event if (0 < cache_back) { if (!fill_tx_destination(de_change, from, cache_back)) - throw std::runtime_error("couldn't fill transaction cache back destination"); + throw tools::runtime_error("couldn't fill transaction cache back destination"); destinations.push_back(de_change); } } diff --git a/tests/core_tests/chaingen.h b/tests/core_tests/chaingen.h index d0d912cb..d51d9d32 100644 --- a/tests/core_tests/chaingen.h +++ b/tests/core_tests/chaingen.h @@ -284,7 +284,7 @@ bool do_check_tx_verification_context(const cryptonote::tx_verification_context& { // Default block verification context check if (tvc.m_verifivation_failed) - throw std::runtime_error("Transaction verification failed"); + throw tools::runtime_error("Transaction verification failed"); return true; } //-------------------------------------------------------------------------- @@ -307,7 +307,7 @@ bool do_check_block_verification_context(const cryptonote::block_verification_co { // Default block verification context check if (bvc.m_verifivation_failed) - throw std::runtime_error("Block verification failed"); + throw tools::runtime_error("Block verification failed"); return true; } //-------------------------------------------------------------------------- @@ -621,7 +621,7 @@ inline bool do_replay_file(const std::string& filename) if (!tools::serialize_obj_to_file(events, filename)) \ { \ std::cout << concolor::magenta << "Failed to serialize data to file: " << filename << concolor::normal << std::endl; \ - throw std::runtime_error("Failed to serialize data to file"); \ + throw tools::runtime_error("Failed to serialize data to file"); \ } \ } diff --git a/tests/core_tests/tx_validation.cpp b/tests/core_tests/tx_validation.cpp index f72c906e..41d5f1e2 100644 --- a/tests/core_tests/tx_validation.cpp +++ b/tests/core_tests/tx_validation.cpp @@ -156,7 +156,7 @@ namespace } } - throw std::runtime_error("invalid public key wasn't found"); + throw tools::runtime_error("invalid public key wasn't found"); return crypto::public_key(); } } diff --git a/tests/net_load_tests/clt.cpp b/tests/net_load_tests/clt.cpp index 56089a4d..36026167 100644 --- a/tests/net_load_tests/clt.cpp +++ b/tests/net_load_tests/clt.cpp @@ -37,6 +37,7 @@ #include "gtest/gtest.h" +#include "common/exception.h" #include "include_base_utils.h" #include "misc_language.h" #include "misc_log_ex.h" diff --git a/tests/net_load_tests/srv.cpp b/tests/net_load_tests/srv.cpp index d8d3eae2..13b31215 100644 --- a/tests/net_load_tests/srv.cpp +++ b/tests/net_load_tests/srv.cpp @@ -31,6 +31,7 @@ #include #include +#include "common/exception.h" #include "include_base_utils.h" #include "misc_log_ex.h" #include "storages/levin_abstract_invoke2.h" diff --git a/tests/unit_tests/ban.cpp b/tests/unit_tests/ban.cpp index a5ce244d..5d40e90c 100644 --- a/tests/unit_tests/ban.cpp +++ b/tests/unit_tests/ban.cpp @@ -56,7 +56,7 @@ public: bool on_idle(){return true;} bool find_blockchain_supplement(const std::list& qblock_ids, cryptonote::NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp){return true;} bool handle_get_objects(cryptonote::NOTIFY_REQUEST_GET_OBJECTS::request& arg, cryptonote::NOTIFY_RESPONSE_GET_OBJECTS::request& rsp, cryptonote::cryptonote_connection_context& context){return true;} - cryptonote::blockchain_storage &get_blockchain_storage() { throw std::runtime_error("Called invalid member function: please never call get_blockchain_storage on the TESTING class test_core."); } + cryptonote::blockchain_storage &get_blockchain_storage() { throw tools::runtime_error("Called invalid member function: please never call get_blockchain_storage on the TESTING class test_core."); } bool get_test_drop_download() const {return true;} bool get_test_drop_download_height() const {return true;} bool prepare_handle_incoming_blocks(const std::list &blocks) { return true; } diff --git a/tests/unit_tests/epee_boosted_tcp_server.cpp b/tests/unit_tests/epee_boosted_tcp_server.cpp index 946db292..b68ce77b 100644 --- a/tests/unit_tests/epee_boosted_tcp_server.cpp +++ b/tests/unit_tests/epee_boosted_tcp_server.cpp @@ -104,7 +104,7 @@ TEST(boosted_tcp_server, worker_threads_are_exception_resistant) // 2 theads, but 4 exceptions ASSERT_TRUE(srv.run_server(2, false)); - ASSERT_TRUE(srv.async_call([&counter_incrementer]() { counter_incrementer(); throw std::runtime_error("test 1"); })); + ASSERT_TRUE(srv.async_call([&counter_incrementer]() { counter_incrementer(); throw tools::runtime_error("test 1"); })); ASSERT_TRUE(srv.async_call([&counter_incrementer]() { counter_incrementer(); throw std::string("test 2"); })); ASSERT_TRUE(srv.async_call([&counter_incrementer]() { counter_incrementer(); throw "test 3"; })); ASSERT_TRUE(srv.async_call([&counter_incrementer]() { counter_incrementer(); throw 4; })); diff --git a/tests/unit_tests/mnemonics.cpp b/tests/unit_tests/mnemonics.cpp index 3dc5db7d..3bf1df7e 100644 --- a/tests/unit_tests/mnemonics.cpp +++ b/tests/unit_tests/mnemonics.cpp @@ -27,6 +27,7 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "gtest/gtest.h" +#include "common/exception.h" #include "mnemonics/electrum-words.h" #include "crypto/crypto.h" #include diff --git a/tests/unit_tests/test_protocol_pack.cpp b/tests/unit_tests/test_protocol_pack.cpp index a9a1a34a..1ff70b4d 100644 --- a/tests/unit_tests/test_protocol_pack.cpp +++ b/tests/unit_tests/test_protocol_pack.cpp @@ -30,6 +30,7 @@ #include "gtest/gtest.h" +#include "common/exception.h" #include "include_base_utils.h" #include "cryptonote_protocol/cryptonote_protocol_defs.h" #include "storages/portable_storage_template_helper.h" From b52545706147b31c373dbdb001b3504dbb35ead7 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sat, 19 Mar 2016 22:18:47 +0000 Subject: [PATCH 08/47] simplewallet: make --password-file work in RPC mode --- src/simplewallet/simplewallet.cpp | 96 +++++++++++++++++-------------- 1 file changed, 54 insertions(+), 42 deletions(-) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 73b56f2b..ea1993c8 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -760,6 +760,54 @@ void simple_wallet::print_seed(std::string seed) std::cout << seed << std::endl; } +//---------------------------------------------------------------------------------------------------- +static bool get_password(const boost::program_options::variables_map& vm, bool allow_entry, tools::password_container &pwd_container) +{ + if (has_arg(vm, arg_password) && has_arg(vm, arg_password_file)) + { + fail_msg_writer() << tr("can't specify more than one of --password and --password-file"); + return false; + } + + if (command_line::has_arg(vm, arg_password)) + { + pwd_container.password(command_line::get_arg(vm, arg_password)); + return true; + } + + if (command_line::has_arg(vm, arg_password_file)) + { + std::string password; + bool r = epee::file_io_utils::load_file_to_string(command_line::get_arg(vm, arg_password_file), + password); + if (!r) + { + fail_msg_writer() << tr("the password file specified could not be read"); + return false; + } + + // Remove line breaks the user might have inserted + password.erase(std::remove(password.begin() - 1, password.end(), '\n'), password.end()); + password.erase(std::remove(password.end() - 1, password.end(), '\r'), password.end()); + pwd_container.password(password.c_str()); + return true; + } + + if (allow_entry) + { + bool r = pwd_container.read_password(); + if (!r) + { + fail_msg_writer() << tr("failed to read wallet password"); + return false; + } + return true; + } + + fail_msg_writer() << tr("Wallet password not set."); + return false; +} + //---------------------------------------------------------------------------------------------------- bool simple_wallet::init(const boost::program_options::variables_map& vm) { @@ -795,42 +843,9 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) if (m_daemon_address.empty()) m_daemon_address = std::string("http://") + m_daemon_host + ":" + std::to_string(m_daemon_port); - if (has_arg(vm, arg_password) && has_arg(vm, arg_password_file)) - { - fail_msg_writer() << tr("can't specify more than one of --password and --password-file"); - return false; - } - tools::password_container pwd_container; - if (command_line::has_arg(vm, arg_password)) - { - pwd_container.password(command_line::get_arg(vm, arg_password)); - } - else if (command_line::has_arg(vm, arg_password_file)) - { - std::string password; - bool r = epee::file_io_utils::load_file_to_string(command_line::get_arg(vm, arg_password_file), - password); - if (!r) - { - fail_msg_writer() << tr("the password file specified could not be read"); - return false; - } - - // Remove line breaks the user might have inserted - password.erase(std::remove(password.begin() - 1, password.end(), '\n'), password.end()); - password.erase(std::remove(password.end() - 1, password.end(), '\r'), password.end()); - pwd_container.password(password.c_str()); - } - else - { - bool r = pwd_container.read_password(); - if (!r) - { - fail_msg_writer() << tr("failed to read wallet password"); - return false; - } - } + if (!get_password(vm, true, pwd_container)) + return false; if (!m_generate_new.empty() || m_restore_deterministic_wallet || !m_generate_from_view_key.empty() || !m_generate_from_keys.empty()) { @@ -2675,16 +2690,13 @@ int main(int argc, char* argv[]) LOG_ERROR(sw::tr("Daemon address not set.")); return 1; } - if(!command_line::has_arg(vm, arg_password) ) - { - LOG_ERROR(sw::tr("Wallet password not set.")); - return 1; - } bool testnet = command_line::get_arg(vm, arg_testnet); bool restricted = command_line::get_arg(vm, arg_restricted); std::string wallet_file = command_line::get_arg(vm, arg_wallet_file); - std::string wallet_password = command_line::get_arg(vm, arg_password); + tools::password_container pwd_container; + if (!get_password(vm, false, pwd_container)) + return 1; std::string daemon_address = command_line::get_arg(vm, arg_daemon_address); std::string daemon_host = command_line::get_arg(vm, arg_daemon_host); int daemon_port = command_line::get_arg(vm, arg_daemon_port); @@ -2699,7 +2711,7 @@ int main(int argc, char* argv[]) try { LOG_PRINT_L0(sw::tr("Loading wallet...")); - wal.load(wallet_file, wallet_password); + wal.load(wallet_file, pwd_container.password()); wal.init(daemon_address); wal.refresh(); LOG_PRINT_GREEN(sw::tr("Loaded ok"), LOG_LEVEL_0); From d2aa427c78f3aa742fc812b73de63abb8602707b Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sat, 19 Mar 2016 23:58:30 +0000 Subject: [PATCH 09/47] rpc: fix print_tx in command line mode It was only filling the input in non rpc mode --- src/daemon/rpc_command_executor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index ffda7cde..15ddc081 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -558,6 +558,7 @@ bool t_rpc_command_executor::print_transaction(crypto::hash transaction_hash) { std::string fail_message = "Problem fetching transaction"; + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(transaction_hash)); if (m_is_rpc) { if (!m_rpc_client->rpc_request(req, res, "/gettransactions", fail_message.c_str())) @@ -567,7 +568,6 @@ bool t_rpc_command_executor::print_transaction(crypto::hash transaction_hash) { } else { - req.txs_hashes.push_back(epee::string_tools::pod_to_hex(transaction_hash)); if (!m_rpc_server->on_get_transactions(req, res) || res.status != CORE_RPC_STATUS_OK) { tools::fail_msg_writer() << fail_message.c_str(); From 2b4cab30fc47539a87b4644c7cf4c657da391c0c Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sun, 20 Mar 2016 12:05:55 +0000 Subject: [PATCH 10/47] epee: fix potential hang on exit Also close sockets on failure, just in case --- contrib/epee/include/net/abstract_tcp_server2.inl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/contrib/epee/include/net/abstract_tcp_server2.inl b/contrib/epee/include/net/abstract_tcp_server2.inl index 698e1947..1c854dfb 100644 --- a/contrib/epee/include/net/abstract_tcp_server2.inl +++ b/contrib/epee/include/net/abstract_tcp_server2.inl @@ -1005,6 +1005,12 @@ POP_WARNINGS while(local_shared_context->ec == boost::asio::error::would_block) { bool r = local_shared_context->cond.timed_wait(lock, boost::get_system_time() + boost::posix_time::milliseconds(conn_timeout)); + if (m_stop_signal_sent) + { + if (sock_.is_open()) + sock_.close(); + return false; + } if(local_shared_context->ec == boost::asio::error::would_block && !r) { //timeout @@ -1018,6 +1024,8 @@ POP_WARNINGS if (ec || !sock_.is_open()) { _dbg3("Some problems at connect, message: " << ec.message()); + if (sock_.is_open()) + sock_.close(); return false; } From 1c3ed4c9da57b1ed1f912c14370249a9d02a3ea6 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sun, 20 Mar 2016 12:35:53 +0000 Subject: [PATCH 11/47] cryptonote_protocol: clarify height wording It's logging the blockchain height, not the top block height --- src/cryptonote_protocol/cryptonote_protocol_handler.inl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index f917f4c3..9f0ea0e8 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -281,7 +281,7 @@ namespace cryptonote << " [" << std::abs(diff) << " blocks (" << diff / (24 * 60 * 60 / DIFFICULTY_TARGET_V1) << " days) " << (0 <= diff ? std::string("behind") : std::string("ahead")) << "] " << ENDL << "SYNCHRONIZATION started", (is_inital ? LOG_LEVEL_0:LOG_LEVEL_1)); - LOG_PRINT_L1("Remote top block height: " << hshd.current_height << ", id: " << hshd.top_id); + LOG_PRINT_L1("Remote blockchain height: " << hshd.current_height << ", id: " << hshd.top_id); context.m_state = cryptonote_connection_context::state_synchronizing; context.m_remote_blockchain_height = hshd.current_height; //let the socket to send response to handshake, but request callback, to let send request data after response From 79117d4275c53ecd472f810e710c74be597758d5 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sun, 20 Mar 2016 18:06:04 +0000 Subject: [PATCH 12/47] db_lmdb: include the error codes from lmdb api in error logs --- src/blockchain_db/lmdb/db_lmdb.cpp | 87 ++++++++++++++++-------------- 1 file changed, 47 insertions(+), 40 deletions(-) diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index 4e30548f..e928ab80 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -545,11 +545,12 @@ void BlockchainLMDB::add_block(const block& blk, const size_t& block_size, const { MDB_val_copy parent_key(blk.prev_id); MDB_val parent_h; - if (mdb_cursor_get(m_cur_block_heights, &parent_key, &parent_h, MDB_SET)) + int result = mdb_cursor_get(m_cur_block_heights, &parent_key, &parent_h, MDB_SET); + if (result) { LOG_PRINT_L3("m_height: " << m_height); LOG_PRINT_L3("parent_key: " << blk.prev_id); - throw0(DB_ERROR("Failed to get top block hash to check for new block's parent")); + throw0(DB_ERROR(lmdb_error("Failed to get top block hash to check for new block's parent: ", result).c_str())); } uint64_t parent_height = *(const uint64_t *)parent_h.mv_data; if (parent_height != m_height - 1) @@ -606,6 +607,8 @@ void BlockchainLMDB::add_block(const block& blk, const size_t& block_size, const void BlockchainLMDB::remove_block() { + int result; + LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); @@ -614,29 +617,29 @@ void BlockchainLMDB::remove_block() MDB_val_copy k(m_height - 1); MDB_val h; - if (mdb_get(*m_write_txn, m_block_hashes, &k, &h)) - throw1(BLOCK_DNE("Attempting to remove block that's not in the db")); + if ((result = mdb_get(*m_write_txn, m_block_hashes, &k, &h))) + throw1(BLOCK_DNE(lmdb_error("Attempting to remove block that's not in the db: ", result).c_str())); - if (mdb_del(*m_write_txn, m_blocks, &k, NULL)) - throw1(DB_ERROR("Failed to add removal of block to db transaction")); + if ((result = mdb_del(*m_write_txn, m_blocks, &k, NULL))) + throw1(DB_ERROR(lmdb_error("Failed to add removal of block to db transaction: ", result).c_str())); - if (mdb_del(*m_write_txn, m_block_sizes, &k, NULL)) - throw1(DB_ERROR("Failed to add removal of block size to db transaction")); + if ((result = mdb_del(*m_write_txn, m_block_sizes, &k, NULL))) + throw1(DB_ERROR(lmdb_error("Failed to add removal of block size to db transaction: ", result).c_str())); - if (mdb_del(*m_write_txn, m_block_diffs, &k, NULL)) - throw1(DB_ERROR("Failed to add removal of block cumulative difficulty to db transaction")); + if ((result = mdb_del(*m_write_txn, m_block_diffs, &k, NULL))) + throw1(DB_ERROR(lmdb_error("Failed to add removal of block cumulative difficulty to db transaction: ", result).c_str())); - if (mdb_del(*m_write_txn, m_block_coins, &k, NULL)) - throw1(DB_ERROR("Failed to add removal of block total generated coins to db transaction")); + if ((result = mdb_del(*m_write_txn, m_block_coins, &k, NULL))) + throw1(DB_ERROR(lmdb_error("Failed to add removal of block total generated coins to db transaction: ", result).c_str())); - if (mdb_del(*m_write_txn, m_block_timestamps, &k, NULL)) - throw1(DB_ERROR("Failed to add removal of block timestamp to db transaction")); + if ((result = mdb_del(*m_write_txn, m_block_timestamps, &k, NULL))) + throw1(DB_ERROR(lmdb_error("Failed to add removal of block timestamp to db transaction: ", result).c_str())); - if (mdb_del(*m_write_txn, m_block_heights, &h, NULL)) - throw1(DB_ERROR("Failed to add removal of block height by hash to db transaction")); + if ((result = mdb_del(*m_write_txn, m_block_heights, &h, NULL))) + throw1(DB_ERROR(lmdb_error("Failed to add removal of block height by hash to db transaction: ", result).c_str())); - if (mdb_del(*m_write_txn, m_block_hashes, &k, NULL)) - throw1(DB_ERROR("Failed to add removal of block hash to db transaction")); + if ((result = mdb_del(*m_write_txn, m_block_hashes, &k, NULL))) + throw1(DB_ERROR(lmdb_error("Failed to add removal of block hash to db transaction: ", result).c_str())); } void BlockchainLMDB::add_transaction_data(const crypto::hash& blk_hash, const transaction& tx, const crypto::hash& tx_hash) @@ -674,6 +677,8 @@ void BlockchainLMDB::add_transaction_data(const crypto::hash& blk_hash, const tr void BlockchainLMDB::remove_transaction_data(const crypto::hash& tx_hash, const transaction& tx) { + int result; + LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); @@ -682,16 +687,16 @@ void BlockchainLMDB::remove_transaction_data(const crypto::hash& tx_hash, const if (mdb_get(*m_write_txn, m_txs, &val_h, &unused)) throw1(TX_DNE("Attempting to remove transaction that isn't in the db")); - if (mdb_del(*m_write_txn, m_txs, &val_h, NULL)) - throw1(DB_ERROR("Failed to add removal of tx to db transaction")); - if (mdb_del(*m_write_txn, m_tx_unlocks, &val_h, NULL)) - throw1(DB_ERROR("Failed to add removal of tx unlock time to db transaction")); - if (mdb_del(*m_write_txn, m_tx_heights, &val_h, NULL)) - throw1(DB_ERROR("Failed to add removal of tx block height to db transaction")); + if ((result = mdb_del(*m_write_txn, m_txs, &val_h, NULL))) + throw1(DB_ERROR(lmdb_error("Failed to add removal of tx to db transaction: ", result).c_str())); + if ((result = mdb_del(*m_write_txn, m_tx_unlocks, &val_h, NULL))) + throw1(DB_ERROR(lmdb_error("Failed to add removal of tx unlock time to db transaction: ", result).c_str())); + if ((result = mdb_del(*m_write_txn, m_tx_heights, &val_h, NULL))) + throw1(DB_ERROR(lmdb_error("Failed to add removal of tx block height to db transaction: ", result).c_str())); remove_tx_outputs(&val_h, tx); - auto result = mdb_del(*m_write_txn, m_tx_outputs, &val_h, NULL); + result = mdb_del(*m_write_txn, m_tx_outputs, &val_h, NULL); if (result == MDB_NOTFOUND) LOG_PRINT_L1("tx has no outputs to remove: " << tx_hash); else if (result) @@ -741,8 +746,8 @@ void BlockchainLMDB::add_output(const crypto::hash& tx_hash, const tx_out& tx_ou MDB_val_copy data(od); //MDB_val_copy val_pubkey(boost::get(tx_output.target).key); - if (mdb_cursor_put(m_cur_output_keys, &k, &data, MDB_APPEND)) - throw0(DB_ERROR("Failed to add output pubkey to db transaction")); + if ((result = mdb_cursor_put(m_cur_output_keys, &k, &data, MDB_APPEND))) + throw0(DB_ERROR(lmdb_error("Failed to add output pubkey to db transaction: ", result).c_str())); } else { @@ -767,7 +772,7 @@ void BlockchainLMDB::remove_tx_outputs(const MDB_val *tx_hash, const transaction } else if (result) { - throw0(DB_ERROR("DB error attempting to get an output")); + throw0(DB_ERROR(lmdb_error("DB error attempting to get an output", result).c_str())); } else { @@ -879,9 +884,9 @@ void BlockchainLMDB::remove_amount_output_index(const uint64_t amount, const MDB { // found the amount output index // now delete it - result = mdb_cursor_del(m_cur_output_amounts, 0); + int result = mdb_cursor_del(m_cur_output_amounts, 0); if (result) - throw0(DB_ERROR(std::string("Error deleting amount output index ").append(boost::lexical_cast(amount_output_index)).c_str())); + throw0(DB_ERROR(lmdb_error(std::string("Error deleting amount output index ").append(boost::lexical_cast(amount_output_index).append(": ")).c_str(), result).c_str())); } else { @@ -996,6 +1001,8 @@ BlockchainLMDB::BlockchainLMDB(bool batch_transactions) void BlockchainLMDB::open(const std::string& filename, const int mdb_flags) { + int result; + LOG_PRINT_L3("BlockchainLMDB::" << __func__); if (m_open) @@ -1025,10 +1032,10 @@ void BlockchainLMDB::open(const std::string& filename, const int mdb_flags) m_folder = filename; // set up lmdb environment - if (mdb_env_create(&m_env)) - throw0(DB_ERROR("Failed to create lmdb environment")); - if (mdb_env_set_maxdbs(m_env, 20)) - throw0(DB_ERROR("Failed to set max number of dbs")); + if ((result = mdb_env_create(&m_env))) + throw0(DB_ERROR(lmdb_error("Failed to create lmdb environment: ", result).c_str())); + if ((result = mdb_env_set_maxdbs(m_env, 20))) + throw0(DB_ERROR(lmdb_error("Failed to set max number of dbs: ", result).c_str())); size_t mapsize = DEFAULT_MAPSIZE; @@ -1104,14 +1111,14 @@ void BlockchainLMDB::open(const std::string& filename, const int mdb_flags) // get and keep current height MDB_stat db_stats; - if (mdb_stat(txn, m_blocks, &db_stats)) - throw0(DB_ERROR("Failed to query m_blocks")); + if ((result = mdb_stat(txn, m_blocks, &db_stats))) + throw0(DB_ERROR(lmdb_error("Failed to query m_blocks: ", result).c_str())); LOG_PRINT_L2("Setting m_height to: " << db_stats.ms_entries); m_height = db_stats.ms_entries; // get and keep current number of outputs - if (mdb_stat(txn, m_output_indices, &db_stats)) - throw0(DB_ERROR("Failed to query m_output_indices")); + if ((result = mdb_stat(txn, m_output_indices, &db_stats))) + throw0(DB_ERROR(lmdb_error("Failed to query m_output_indices: ", result).c_str())); m_num_outputs = db_stats.ms_entries; bool compatible = true; @@ -1232,8 +1239,8 @@ void BlockchainLMDB::reset() check_open(); mdb_txn_safe txn; - if (mdb_txn_begin(m_env, NULL, 0, txn)) - throw0(DB_ERROR("Failed to create a transaction for the db")); + if (auto result = mdb_txn_begin(m_env, NULL, 0, txn)) + throw0(DB_ERROR(lmdb_error("Failed to create a transaction for the db: ", result).c_str())); mdb_drop(txn, m_blocks, 0); mdb_drop(txn, m_block_timestamps, 0); mdb_drop(txn, m_block_heights, 0); From f7301c3563e7ae19b8d7c65d8bc88f3c42eefc09 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Mon, 21 Mar 2016 10:12:12 +0000 Subject: [PATCH 13/47] Revert "Print stack trace upon exceptions" Ain't nobody got time for link/cmake skullduggery. This reverts commit fff238ec94ac6d45fc18c315d7bc590ddfaad63d. --- CMakeLists.txt | 12 --- cmake/FindLibunwind.cmake | 25 ------ contrib/epee/include/misc_log_ex.h | 2 +- .../epee/include/net/abstract_tcp_server2.inl | 2 +- .../storages/portable_storage_from_bin.h | 4 +- src/blockchain_db/blockchain_db.h | 3 +- .../blockchain_export.cpp | 2 +- .../blockchain_import.cpp | 6 +- src/blockchain_utilities/bootstrap_file.cpp | 44 +++++----- src/blockchain_utilities/fake_core.h | 4 +- src/common/CMakeLists.txt | 3 - src/common/exception.cpp | 84 ------------------- src/common/exception.h | 53 ------------ src/cryptonote_core/blockchain_storage.h | 3 +- src/cryptonote_core/checkpoints_create.cpp | 1 - src/cryptonote_core/cryptonote_core.cpp | 2 +- src/cryptonote_core/cryptonote_core.h | 1 - src/cryptonote_core/miner.cpp | 1 - .../cryptonote_protocol_handler.h | 1 - src/daemon/command_parser_executor.cpp | 1 - src/daemon/command_server.cpp | 1 - src/daemon/daemon.cpp | 6 +- src/daemon/p2p.h | 2 +- src/daemon/protocol.h | 2 +- src/daemon/rpc.h | 4 +- src/daemon/rpc_command_executor.cpp | 3 +- src/daemonizer/posix_fork.cpp | 3 +- src/miner/simpleminer.cpp | 1 - src/p2p/data_logger.cpp | 7 +- src/simplewallet/simplewallet.cpp | 2 +- src/wallet/wallet2.cpp | 5 +- src/wallet/wallet2.h | 1 - src/wallet/wallet2_api.cpp | 1 - tests/core_proxy/core_proxy.h | 2 +- tests/core_tests/chaingen.cpp | 12 +-- tests/core_tests/chaingen.h | 6 +- tests/core_tests/tx_validation.cpp | 2 +- tests/net_load_tests/clt.cpp | 1 - tests/net_load_tests/srv.cpp | 1 - tests/unit_tests/ban.cpp | 2 +- tests/unit_tests/epee_boosted_tcp_server.cpp | 2 +- tests/unit_tests/mnemonics.cpp | 1 - tests/unit_tests/test_protocol_pack.cpp | 1 - 43 files changed, 63 insertions(+), 259 deletions(-) delete mode 100644 cmake/FindLibunwind.cmake delete mode 100644 src/common/exception.cpp delete mode 100644 src/common/exception.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ea1d5d7e..9674404b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -243,14 +243,6 @@ endif() add_definitions("-DBLOCKCHAIN_DB=${BLOCKCHAIN_DB}") -find_package(Libunwind) -if(LIBUNWIND_FOUND) - message(STATUS "Using libunwind to provide stack traces") - add_definitions("-DHAVE_LIBUNWIND") -else() - message(STATUS "Stack traces disabled") -endif() - if (UNIX AND NOT APPLE) # Note that at the time of this writing the -Wstrict-prototypes flag added below will make this fail set(THREADS_PREFER_PTHREAD_FLAG ON) @@ -282,10 +274,6 @@ if (BERKELEY_DB) include_directories(${BDB_INCLUDE}) endif() -# Final setup for libunwind -include_directories(${LIBUNWIND_INCLUDE}) -link_directories(${LIBUNWIND_LIBRARY_DIRS}) - if(MSVC) add_definitions("/bigobj /MP /W3 /GS- /D_CRT_SECURE_NO_WARNINGS /wd4996 /wd4345 /D_WIN32_WINNT=0x0600 /DWIN32_LEAN_AND_MEAN /DGTEST_HAS_TR1_TUPLE=0 /FIinline_c.h /D__SSE4_1__") # set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /Dinline=__inline") diff --git a/cmake/FindLibunwind.cmake b/cmake/FindLibunwind.cmake deleted file mode 100644 index 9946e7cd..00000000 --- a/cmake/FindLibunwind.cmake +++ /dev/null @@ -1,25 +0,0 @@ -# - Try to find libunwind -# Once done this will define -# -# LIBUNWIND_FOUND - system has libunwind -# LIBUNWIND_INCLUDE_DIR - the libunwind include directory -# LIBUNWIND_LIBRARIES - Link these to use libunwind -# LIBUNWIND_DEFINITIONS - Compiler switches required for using libunwind - -# Copyright (c) 2006, Alexander Dymo, -# -# Redistribution and use is allowed according to the terms of the BSD license. -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. - -find_path(LIBUNWIND_INCLUDE_DIR libunwind.h - /usr/include - /usr/local/include -) - -find_library(LIBUNWIND_LIBRARIES NAMES unwind ) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Libunwind "Could not find libunwind" LIBUNWIND_INCLUDE_DIR LIBUNWIND_LIBRARIES) -# show the LIBUNWIND_INCLUDE_DIR and LIBUNWIND_LIBRARIES variables only in the advanced view -mark_as_advanced(LIBUNWIND_INCLUDE_DIR LIBUNWIND_LIBRARIES ) - diff --git a/contrib/epee/include/misc_log_ex.h b/contrib/epee/include/misc_log_ex.h index 58d47c47..d1451ff1 100644 --- a/contrib/epee/include/misc_log_ex.h +++ b/contrib/epee/include/misc_log_ex.h @@ -1426,7 +1426,7 @@ POP_WARNINGS #define CATCH_ENTRY_L4(lacation, return_val) CATCH_ENTRY(lacation, return_val) -#define ASSERT_MES_AND_THROW(message) {LOG_ERROR(message); std::stringstream ss; ss << message; throw tools::runtime_error(ss.str());} +#define ASSERT_MES_AND_THROW(message) {LOG_ERROR(message); std::stringstream ss; ss << message; throw std::runtime_error(ss.str());} #define CHECK_AND_ASSERT_THROW_MES(expr, message) {if(!(expr)) ASSERT_MES_AND_THROW(message);} diff --git a/contrib/epee/include/net/abstract_tcp_server2.inl b/contrib/epee/include/net/abstract_tcp_server2.inl index 2f6e52a5..1c854dfb 100644 --- a/contrib/epee/include/net/abstract_tcp_server2.inl +++ b/contrib/epee/include/net/abstract_tcp_server2.inl @@ -777,7 +777,7 @@ POP_WARNINGS { m_thread_name_prefix = prefix_name; auto it = server_type_map.find(m_thread_name_prefix); - if (it==server_type_map.end()) throw tools::runtime_error("Unknown prefix/server type:" + std::string(prefix_name)); + if (it==server_type_map.end()) throw std::runtime_error("Unknown prefix/server type:" + std::string(prefix_name)); auto connection_type = it->second; // the value of type _info_c("net/RPClog", "Set server type to: " << connection_type << " from name: " << m_thread_name_prefix); _info_c("net/RPClog", "prefix_name = " << prefix_name); diff --git a/contrib/epee/include/storages/portable_storage_from_bin.h b/contrib/epee/include/storages/portable_storage_from_bin.h index 4cb2003e..bc2fb146 100644 --- a/contrib/epee/include/storages/portable_storage_from_bin.h +++ b/contrib/epee/include/storages/portable_storage_from_bin.h @@ -84,9 +84,9 @@ namespace epee inline throwable_buffer_reader::throwable_buffer_reader(const void* ptr, size_t sz) { if(!ptr) - throw tools::runtime_error("throwable_buffer_reader: ptr==nullptr"); + throw std::runtime_error("throwable_buffer_reader: ptr==nullptr"); if(!sz) - throw tools::runtime_error("throwable_buffer_reader: sz==0"); + throw std::runtime_error("throwable_buffer_reader: sz==0"); m_ptr = (uint8_t*)ptr; m_count = sz; m_recursion_count = 0; diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index b9db6d19..3396b8c2 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -33,7 +33,6 @@ #include #include #include -#include "common/exception.h" #include "crypto/hash.h" #include "cryptonote_core/cryptonote_basic.h" #include "cryptonote_core/difficulty.h" @@ -152,7 +151,7 @@ struct output_data_t /*********************************** * Exception Definitions ***********************************/ -class DB_EXCEPTION : public tools::exception +class DB_EXCEPTION : public std::exception { private: std::string m; diff --git a/src/blockchain_utilities/blockchain_export.cpp b/src/blockchain_utilities/blockchain_export.cpp index 4c5e5fd0..964c610c 100644 --- a/src/blockchain_utilities/blockchain_export.cpp +++ b/src/blockchain_utilities/blockchain_export.cpp @@ -198,7 +198,7 @@ int main(int argc, char* argv[]) else { LOG_ERROR("Attempted to use non-existent database type: " << db_type); - throw tools::runtime_error("Attempting to use non-existent database type"); + throw std::runtime_error("Attempting to use non-existent database type"); } LOG_PRINT_L0("database: " << db_type); diff --git a/src/blockchain_utilities/blockchain_import.cpp b/src/blockchain_utilities/blockchain_import.cpp index 43f6af6d..1aaf2bdd 100644 --- a/src/blockchain_utilities/blockchain_import.cpp +++ b/src/blockchain_utilities/blockchain_import.cpp @@ -359,14 +359,14 @@ int import_from_file(FakeCore& simple_core, const std::string& import_file_path, str1.assign(buffer1, sizeof(chunk_size)); if (! ::serialization::parse_binary(str1, chunk_size)) { - throw tools::runtime_error("Error in deserialization of chunk size"); + throw std::runtime_error("Error in deserialization of chunk size"); } LOG_PRINT_L3("chunk_size: " << chunk_size); if (chunk_size > BUFFER_SIZE) { LOG_PRINT_L0("WARNING: chunk_size " << chunk_size << " > BUFFER_SIZE " << BUFFER_SIZE); - throw tools::runtime_error("Aborting: chunk size exceeds buffer size"); + throw std::runtime_error("Aborting: chunk size exceeds buffer size"); } if (chunk_size > 100000) { @@ -406,7 +406,7 @@ int import_from_file(FakeCore& simple_core, const std::string& import_file_path, str1.assign(buffer_block, chunk_size); bootstrap::block_package bp; if (! ::serialization::parse_binary(str1, bp)) - throw tools::runtime_error("Error in deserialization of chunk"); + throw std::runtime_error("Error in deserialization of chunk"); int display_interval = 1000; int progress_interval = 10; diff --git a/src/blockchain_utilities/bootstrap_file.cpp b/src/blockchain_utilities/bootstrap_file.cpp index 247709f5..da3b4459 100644 --- a/src/blockchain_utilities/bootstrap_file.cpp +++ b/src/blockchain_utilities/bootstrap_file.cpp @@ -117,7 +117,7 @@ bool BootstrapFile::initialize_file() std::string blob; if (! ::serialization::dump_binary(file_magic, blob)) { - throw tools::runtime_error("Error in serialization of file magic"); + throw std::runtime_error("Error in serialization of file magic"); } *m_raw_data_file << blob; @@ -143,7 +143,7 @@ bool BootstrapFile::initialize_file() if (! ::serialization::dump_binary(bd_size, blob)) { - throw tools::runtime_error("Error in serialization of bootstrap::file_info size"); + throw std::runtime_error("Error in serialization of bootstrap::file_info size"); } *output_stream_header << blob; *output_stream_header << bd; @@ -154,7 +154,7 @@ bool BootstrapFile::initialize_file() if (! ::serialization::dump_binary(bd_size, blob)) { - throw tools::runtime_error("Error in serialization of bootstrap::blocks_info size"); + throw std::runtime_error("Error in serialization of bootstrap::blocks_info size"); } *output_stream_header << blob; *output_stream_header << bd; @@ -181,7 +181,7 @@ void BootstrapFile::flush_chunk() std::string blob; if (! ::serialization::dump_binary(chunk_size, blob)) { - throw tools::runtime_error("Error in serialization of chunk size"); + throw std::runtime_error("Error in serialization of chunk size"); } *m_raw_data_file << blob; @@ -197,7 +197,7 @@ void BootstrapFile::flush_chunk() if (static_cast(num_chars_written) != chunk_size) { LOG_PRINT_RED_L0("Error writing chunk: height: " << m_cur_height << " chunk_size: " << chunk_size << " num chars written: " << num_chars_written); - throw tools::runtime_error("Error writing chunk"); + throw std::runtime_error("Error writing chunk"); } m_buffer.clear(); @@ -221,7 +221,7 @@ void BootstrapFile::write_block(block& block) { if (tx_id == null_hash) { - throw tools::runtime_error("Aborting: tx == null_hash"); + throw std::runtime_error("Aborting: tx == null_hash"); } #if SOURCE_DB == DB_MEMORY const transaction* tx = m_blockchain_storage->get_tx(tx_id); @@ -233,14 +233,14 @@ void BootstrapFile::write_block(block& block) if(tx == NULL) { if (! m_tx_pool) - throw tools::runtime_error("Aborting: tx == NULL, so memory pool required to get tx, but memory pool isn't enabled"); + throw std::runtime_error("Aborting: tx == NULL, so memory pool required to get tx, but memory pool isn't enabled"); else { transaction tx; if(m_tx_pool->get_transaction(tx_id, tx)) txs.push_back(tx); else - throw tools::runtime_error("Aborting: tx not found in pool"); + throw std::runtime_error("Aborting: tx not found in pool"); } } else @@ -362,16 +362,16 @@ uint64_t BootstrapFile::seek_to_first_chunk(std::ifstream& import_file) char buf1[2048]; import_file.read(buf1, sizeof(file_magic)); if (! import_file) - throw tools::runtime_error("Error reading expected number of bytes"); + throw std::runtime_error("Error reading expected number of bytes"); str1.assign(buf1, sizeof(file_magic)); if (! ::serialization::parse_binary(str1, file_magic)) - throw tools::runtime_error("Error in deserialization of file_magic"); + throw std::runtime_error("Error in deserialization of file_magic"); if (file_magic != blockchain_raw_magic) { LOG_PRINT_RED_L0("bootstrap file not recognized"); - throw tools::runtime_error("Aborting"); + throw std::runtime_error("Aborting"); } else LOG_PRINT_L0("bootstrap file recognized"); @@ -381,20 +381,20 @@ uint64_t BootstrapFile::seek_to_first_chunk(std::ifstream& import_file) import_file.read(buf1, sizeof(buflen_file_info)); str1.assign(buf1, sizeof(buflen_file_info)); if (! import_file) - throw tools::runtime_error("Error reading expected number of bytes"); + throw std::runtime_error("Error reading expected number of bytes"); if (! ::serialization::parse_binary(str1, buflen_file_info)) - throw tools::runtime_error("Error in deserialization of buflen_file_info"); + throw std::runtime_error("Error in deserialization of buflen_file_info"); LOG_PRINT_L1("bootstrap::file_info size: " << buflen_file_info); if (buflen_file_info > sizeof(buf1)) - throw tools::runtime_error("Error: bootstrap::file_info size exceeds buffer size"); + throw std::runtime_error("Error: bootstrap::file_info size exceeds buffer size"); import_file.read(buf1, buflen_file_info); if (! import_file) - throw tools::runtime_error("Error reading expected number of bytes"); + throw std::runtime_error("Error reading expected number of bytes"); str1.assign(buf1, buflen_file_info); bootstrap::file_info bfi; if (! ::serialization::parse_binary(str1, bfi)) - throw tools::runtime_error("Error in deserialization of bootstrap::file_info"); + throw std::runtime_error("Error in deserialization of bootstrap::file_info"); LOG_PRINT_L0("bootstrap file v" << unsigned(bfi.major_version) << "." << unsigned(bfi.minor_version)); LOG_PRINT_L0("bootstrap magic size: " << sizeof(file_magic)); LOG_PRINT_L0("bootstrap header size: " << bfi.header_size); @@ -412,7 +412,7 @@ uint64_t BootstrapFile::count_blocks(const std::string& import_file_path) if (!boost::filesystem::exists(raw_file_path, ec)) { LOG_PRINT_L0("bootstrap file not found: " << raw_file_path); - throw tools::runtime_error("Aborting"); + throw std::runtime_error("Aborting"); } std::ifstream import_file; import_file.open(import_file_path, std::ios_base::binary | std::ifstream::in); @@ -421,7 +421,7 @@ uint64_t BootstrapFile::count_blocks(const std::string& import_file_path) if (import_file.fail()) { LOG_PRINT_L0("import_file.open() fail"); - throw tools::runtime_error("Aborting"); + throw std::runtime_error("Aborting"); } uint64_t full_header_size; // 4 byte magic + length of header structures @@ -456,7 +456,7 @@ uint64_t BootstrapFile::count_blocks(const std::string& import_file_path) str1.assign(buf1, sizeof(chunk_size)); if (! ::serialization::parse_binary(str1, chunk_size)) - throw tools::runtime_error("Error in deserialization of chunk_size"); + throw std::runtime_error("Error in deserialization of chunk_size"); LOG_PRINT_L3("chunk_size: " << chunk_size); if (chunk_size > BUFFER_SIZE) @@ -464,7 +464,7 @@ uint64_t BootstrapFile::count_blocks(const std::string& import_file_path) std::cout << refresh_string; LOG_PRINT_L0("WARNING: chunk_size " << chunk_size << " > BUFFER_SIZE " << BUFFER_SIZE << " height: " << h-1); - throw tools::runtime_error("Aborting: chunk size exceeds buffer size"); + throw std::runtime_error("Aborting: chunk size exceeds buffer size"); } if (chunk_size > 100000) { @@ -475,7 +475,7 @@ uint64_t BootstrapFile::count_blocks(const std::string& import_file_path) else if (chunk_size <= 0) { std::cout << refresh_string; LOG_PRINT_L0("ERROR: chunk_size " << chunk_size << " <= 0" << " height: " << h-1); - throw tools::runtime_error("Aborting"); + throw std::runtime_error("Aborting"); } // skip to next expected block size value import_file.seekg(chunk_size, std::ios_base::cur); @@ -483,7 +483,7 @@ uint64_t BootstrapFile::count_blocks(const std::string& import_file_path) std::cout << refresh_string; LOG_PRINT_L0("ERROR: unexpected end of file: bytes read before error: " << import_file.gcount() << " of chunk_size " << chunk_size); - throw tools::runtime_error("Aborting"); + throw std::runtime_error("Aborting"); } bytes_read += chunk_size; diff --git a/src/blockchain_utilities/fake_core.h b/src/blockchain_utilities/fake_core.h index 3840a1c1..2fb5031d 100644 --- a/src/blockchain_utilities/fake_core.h +++ b/src/blockchain_utilities/fake_core.h @@ -78,7 +78,7 @@ struct fake_core_db else { LOG_ERROR("Attempted to use non-existent database type: " << db_type); - throw tools::runtime_error("Attempting to use non-existent database type"); + throw std::runtime_error("Attempting to use non-existent database type"); } boost::filesystem::path folder(path); @@ -176,7 +176,7 @@ struct fake_core_memory { // TODO: // would need to refactor handle_block_to_main_chain() to have a direct add_block() method like Blockchain class - throw tools::runtime_error("direct add_block() method not implemented for in-memory db"); + throw std::runtime_error("direct add_block() method not implemented for in-memory db"); return 2; } diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 5537900a..2289704e 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -31,7 +31,6 @@ set(common_sources command_line.cpp dns_utils.cpp util.cpp - exception.cpp i18n.cpp) set(common_headers) @@ -49,7 +48,6 @@ set(common_private_headers unordered_containers_boost_serialization.h util.h varint.h - exception.h i18n.h) bitmonero_private_headers(common @@ -62,7 +60,6 @@ target_link_libraries(common LINK_PRIVATE crypto ${UNBOUND_LIBRARY} - ${LIBUNWIND_LIBRARIES} ${Boost_DATE_TIME_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} diff --git a/src/common/exception.cpp b/src/common/exception.cpp deleted file mode 100644 index f8954329..00000000 --- a/src/common/exception.cpp +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) 2016, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "common/exception.h" -#include "misc_log_ex.h" -#define UNW_LOCAL_ONLY -#include -#include - -namespace tools -{ - -void log_stack_trace(const char *msg) -{ -#ifdef HAVE_LIBUNWIND - unw_context_t ctx; - unw_cursor_t cur; - unw_word_t ip, off; - unsigned level; - char sym[512], *dsym; - int status; - - if (msg) - LOG_PRINT_L0(msg); - LOG_PRINT_L0("Unwinded call stack:"); - if (unw_getcontext(&ctx) < 0) { - LOG_PRINT_L0("Failed to create unwind context"); - return; - } - if (unw_init_local(&cur, &ctx) < 0) { - LOG_PRINT_L0("Failed to find the first unwind frame"); - return; - } - for (level = 1; level < 999; ++level) { // 999 for safety - int ret = unw_step(&cur); - if (ret < 0) { - LOG_PRINT_L0("Failed to find the next frame"); - return; - } - if (ret == 0) - break; - if (unw_get_reg(&cur, UNW_REG_IP, &ip) < 0) { - LOG_PRINT_L0(" " << std::setw(4) << level); - continue; - } - if (unw_get_proc_name(&cur, sym, sizeof(sym), &off) < 0) { - LOG_PRINT_L0(" " << std::setw(4) << level << std::setbase(16) << std::setw(20) << "0x" << ip); - continue; - } - dsym = abi::__cxa_demangle(sym, NULL, NULL, &status); - LOG_PRINT_L0(" " << std::setw(4) << level << std::setbase(16) << std::setw(20) << "0x" << ip << " " << (!status && dsym ? dsym : sym) << " + " << "0x" << off); - free(dsym); - } -#else -#warning libunwind disabled, no stack traces -#endif -} - -} // namespace tools diff --git a/src/common/exception.h b/src/common/exception.h deleted file mode 100644 index 93e0d401..00000000 --- a/src/common/exception.h +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) 2016, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef MONERO_EXCEPTION_H -#define MONERO_EXCEPTION_H - -#include -#include - -namespace tools -{ - -void log_stack_trace(const char *msg); - -class exception: public std::exception { -public: - exception() { log_stack_trace(what()); } -}; - -class runtime_error: public std::runtime_error { -public: - explicit runtime_error(const std::string &s): std::runtime_error(s) { log_stack_trace(what()); } - explicit runtime_error(const char *s): std::runtime_error(s) { log_stack_trace(what()); } -}; - -} // namespace tools - -#endif diff --git a/src/cryptonote_core/blockchain_storage.h b/src/cryptonote_core/blockchain_storage.h index e25f8011..878202cf 100644 --- a/src/cryptonote_core/blockchain_storage.h +++ b/src/cryptonote_core/blockchain_storage.h @@ -41,7 +41,6 @@ #include "syncobj.h" #include "string_tools.h" -#include "common/exception.h" #include "tx_pool.h" #include "cryptonote_basic.h" #include "common/util.h" @@ -315,7 +314,7 @@ namespace cryptonote "m_invalid_blocks: " << m_invalid_blocks.size() << ENDL << "m_current_block_cumul_sz_limit: " << m_current_block_cumul_sz_limit); - throw tools::runtime_error("Blockchain data corruption"); + throw std::runtime_error("Blockchain data corruption"); } } } diff --git a/src/cryptonote_core/checkpoints_create.cpp b/src/cryptonote_core/checkpoints_create.cpp index 41246e8d..41f2321d 100644 --- a/src/cryptonote_core/checkpoints_create.cpp +++ b/src/cryptonote_core/checkpoints_create.cpp @@ -28,7 +28,6 @@ // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers -#include "common/exception.h" #include "checkpoints_create.h" #include "common/dns_utils.h" #include "include_base_utils.h" diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 907f9d80..6f0fe88a 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -161,7 +161,7 @@ namespace cryptonote cryptonote::checkpoints checkpoints; if (!cryptonote::create_checkpoints(checkpoints)) { - throw tools::runtime_error("Failed to initialize checkpoints"); + throw std::runtime_error("Failed to initialize checkpoints"); } set_checkpoints(std::move(checkpoints)); diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 186c1c93..32f0b2ad 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -36,7 +36,6 @@ #include #include -#include #include "p2p/net_node_common.h" #include "cryptonote_protocol/cryptonote_protocol_handler_common.h" #include "storages/portable_storage_template_helper.h" diff --git a/src/cryptonote_core/miner.cpp b/src/cryptonote_core/miner.cpp index 7bc126cd..bad3f30b 100644 --- a/src/cryptonote_core/miner.cpp +++ b/src/cryptonote_core/miner.cpp @@ -34,7 +34,6 @@ #include #include #include -#include "common/exception.h" #include "misc_language.h" #include "include_base_utils.h" #include "cryptonote_basic_impl.h" diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.h b/src/cryptonote_protocol/cryptonote_protocol_handler.h index 2112b508..1c92958c 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.h +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.h @@ -38,7 +38,6 @@ #include #include -#include "common/exception.h" #include "storages/levin_abstract_invoke2.h" #include "warnings.h" #include "cryptonote_protocol_defs.h" diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index c48b836e..0b42257e 100644 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -26,7 +26,6 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#include "common/exception.h" #include "cryptonote_core/cryptonote_basic_impl.h" #include "daemon/command_parser_executor.h" diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp index 7e513963..206de9d4 100644 --- a/src/daemon/command_server.cpp +++ b/src/daemon/command_server.cpp @@ -26,7 +26,6 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#include "common/exception.h" #include "cryptonote_config.h" #include "version.h" #include "daemon/command_server.h" diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index d4453835..e79823d0 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -110,7 +110,7 @@ bool t_daemon::run(bool interactive) { if (nullptr == mp_internals) { - throw tools::runtime_error{"Can't run stopped daemon"}; + throw std::runtime_error{"Can't run stopped daemon"}; } tools::signal_handler::install(std::bind(&daemonize::t_daemon::stop_p2p, this)); @@ -155,7 +155,7 @@ void t_daemon::stop() { if (nullptr == mp_internals) { - throw tools::runtime_error{"Can't stop stopped daemon"}; + throw std::runtime_error{"Can't stop stopped daemon"}; } mp_internals->p2p.stop(); mp_internals->rpc.stop(); @@ -166,7 +166,7 @@ void t_daemon::stop_p2p() { if (nullptr == mp_internals) { - throw tools::runtime_error{"Can't send stop signal to a stopped daemon"}; + throw std::runtime_error{"Can't send stop signal to a stopped daemon"}; } mp_internals->p2p.get().send_stop_signal(); } diff --git a/src/daemon/p2p.h b/src/daemon/p2p.h index b74a5e72..3858989c 100644 --- a/src/daemon/p2p.h +++ b/src/daemon/p2p.h @@ -60,7 +60,7 @@ public: LOG_PRINT_L0("Initializing p2p server..."); if (!m_server.init(vm)) { - throw tools::runtime_error("Failed to initialize p2p server."); + throw std::runtime_error("Failed to initialize p2p server."); } LOG_PRINT_L0("P2p server initialized OK"); } diff --git a/src/daemon/protocol.h b/src/daemon/protocol.h index 5f6c317b..8e2add4a 100644 --- a/src/daemon/protocol.h +++ b/src/daemon/protocol.h @@ -50,7 +50,7 @@ public: LOG_PRINT_L0("Initializing cryptonote protocol..."); if (!m_protocol.init(vm)) { - throw tools::runtime_error("Failed to initialize cryptonote protocol."); + throw std::runtime_error("Failed to initialize cryptonote protocol."); } LOG_PRINT_L0("Cryptonote protocol initialized OK"); } diff --git a/src/daemon/rpc.h b/src/daemon/rpc.h index caeae9a3..bfd2afd8 100644 --- a/src/daemon/rpc.h +++ b/src/daemon/rpc.h @@ -55,7 +55,7 @@ public: LOG_PRINT_L0("Initializing core rpc server..."); if (!m_server.init(vm)) { - throw tools::runtime_error("Failed to initialize core rpc server."); + throw std::runtime_error("Failed to initialize core rpc server."); } LOG_PRINT_GREEN("Core rpc server initialized OK on port: " << m_server.get_binded_port(), LOG_LEVEL_0); } @@ -65,7 +65,7 @@ public: LOG_PRINT_L0("Starting core rpc server..."); if (!m_server.run(2, false)) { - throw tools::runtime_error("Failed to start core rpc server."); + throw std::runtime_error("Failed to start core rpc server."); } LOG_PRINT_L0("Core rpc server started ok"); } diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index b57c6094..15ddc081 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -28,7 +28,6 @@ // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers -#include "common/exception.h" #include "string_tools.h" #include "common/scoped_message_writer.h" #include "daemon/rpc_command_executor.h" @@ -89,7 +88,7 @@ t_rpc_command_executor::t_rpc_command_executor( { if (rpc_server == NULL) { - throw tools::runtime_error("If not calling commands via RPC, rpc_server pointer must be non-null"); + throw std::runtime_error("If not calling commands via RPC, rpc_server pointer must be non-null"); } } diff --git a/src/daemonizer/posix_fork.cpp b/src/daemonizer/posix_fork.cpp index 84ff8886..c068912e 100644 --- a/src/daemonizer/posix_fork.cpp +++ b/src/daemonizer/posix_fork.cpp @@ -4,7 +4,6 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#include "common/exception.h" #include "daemonizer/posix_fork.h" #include "misc_log_ex.h" @@ -21,7 +20,7 @@ namespace { void quit(std::string const & message) { LOG_ERROR(message); - throw tools::runtime_error(message); + throw std::runtime_error(message); } } diff --git a/src/miner/simpleminer.cpp b/src/miner/simpleminer.cpp index 19db3f53..ba956d90 100644 --- a/src/miner/simpleminer.cpp +++ b/src/miner/simpleminer.cpp @@ -28,7 +28,6 @@ // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers -#include "common/exception.h" #include "common/command_line.h" #include "misc_log_ex.h" #include "simpleminer.h" diff --git a/src/p2p/data_logger.cpp b/src/p2p/data_logger.cpp index d3ff0ca8..ca0726c5 100644 --- a/src/p2p/data_logger.cpp +++ b/src/p2p/data_logger.cpp @@ -33,7 +33,6 @@ #include #include #include -#include "common/exception.h" #include "../../contrib/otshell_utils/utils.hpp" namespace epee @@ -44,7 +43,7 @@ namespace net_utils boost::call_once(m_singleton, [] { _info_c("dbg/data","Creating singleton of data_logger"); - if (m_state != data_logger_state::state_before_init) { _erro_c("dbg/data","Internal error in singleton"); throw tools::runtime_error("data_logger singleton"); } + if (m_state != data_logger_state::state_before_init) { _erro_c("dbg/data","Internal error in singleton"); throw std::runtime_error("data_logger singleton"); } m_state = data_logger_state::state_during_init; m_obj.reset(new data_logger()); m_state = data_logger_state::state_ready_to_use; @@ -53,7 +52,7 @@ namespace net_utils if (m_state != data_logger_state::state_ready_to_use) { _erro ("trying to use not working data_logger"); - throw tools::runtime_error("data_logger ctor state"); + throw std::runtime_error("data_logger ctor state"); } return * m_obj; @@ -61,7 +60,7 @@ namespace net_utils data_logger::data_logger() { _note_c("dbg/data","Starting data logger (for graphs data)"); - if (m_state != data_logger_state::state_during_init) { _erro_c("dbg/data","Singleton ctor state"); throw tools::runtime_error("data_logger ctor state"); } + if (m_state != data_logger_state::state_during_init) { _erro_c("dbg/data","Singleton ctor state"); throw std::runtime_error("data_logger ctor state"); } boost::lock_guard lock(mMutex); // lock // prepare all the files for given data channels: diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index eb12f52e..ea1993c8 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -561,7 +561,7 @@ bool simple_wallet::set_variable(const std::vector &args) { if (args.empty()) { - fail_msg_writer() << tr("set: needs an argument. available options: seed, always-confirm-transfers, store-tx-info, default-mixin, auto-refresh, refresh-type"); + fail_msg_writer() << tr("set: needs an argument. available options: seed, always-confirm-transfers, default-mixin, auto-refresh, refresh-type"); return true; } else diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index ef17a770..2afe08cb 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -32,7 +32,6 @@ #include #include -#include "common/exception.h" #include "include_base_utils.h" using namespace epee; @@ -804,7 +803,7 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re // handle error from async fetching thread if (error) { - throw tools::runtime_error("proxy exception in refresh thread"); + throw std::runtime_error("proxy exception in refresh thread"); } } catch (const std::exception&) @@ -2013,7 +2012,7 @@ std::vector wallet2::create_transactions(std::vector ptx_vector; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 0961ad98..f798f404 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -35,7 +35,6 @@ #include #include -#include "common/exception.h" #include "include_base_utils.h" #include "cryptonote_core/account.h" #include "cryptonote_core/account_boost_serialization.h" diff --git a/src/wallet/wallet2_api.cpp b/src/wallet/wallet2_api.cpp index 0cb25339..0644e369 100644 --- a/src/wallet/wallet2_api.cpp +++ b/src/wallet/wallet2_api.cpp @@ -28,7 +28,6 @@ // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers -#include "common/exception.h" #include "wallet2_api.h" #include "wallet2.h" #include "mnemonics/electrum-words.h" diff --git a/tests/core_proxy/core_proxy.h b/tests/core_proxy/core_proxy.h index 22f4c042..0315fc8c 100644 --- a/tests/core_proxy/core_proxy.h +++ b/tests/core_proxy/core_proxy.h @@ -82,7 +82,7 @@ namespace tests bool on_idle(){return true;} bool find_blockchain_supplement(const std::list& qblock_ids, cryptonote::NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp){return true;} bool handle_get_objects(cryptonote::NOTIFY_REQUEST_GET_OBJECTS::request& arg, cryptonote::NOTIFY_RESPONSE_GET_OBJECTS::request& rsp, cryptonote::cryptonote_connection_context& context){return true;} - cryptonote::blockchain_storage &get_blockchain_storage() { throw tools::runtime_error("Called invalid member function: please never call get_blockchain_storage on the TESTING class proxy_core."); } + cryptonote::blockchain_storage &get_blockchain_storage() { throw std::runtime_error("Called invalid member function: please never call get_blockchain_storage on the TESTING class proxy_core."); } bool get_test_drop_download() {return true;} bool get_test_drop_download_height() {return true;} bool prepare_handle_incoming_blocks(const std::list &blocks) { return true; } diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp index b897d923..4fdd0a60 100644 --- a/tests/core_tests/chaingen.cpp +++ b/tests/core_tests/chaingen.cpp @@ -58,7 +58,7 @@ void test_generator::get_block_chain(std::vector& blockchain, const auto it = m_blocks_info.find(curr); if (m_blocks_info.end() == it) { - throw tools::runtime_error("block hash wasn't found"); + throw std::runtime_error("block hash wasn't found"); } blockchain.push_back(it->second); @@ -82,7 +82,7 @@ uint64_t test_generator::get_already_generated_coins(const crypto::hash& blk_id) { auto it = m_blocks_info.find(blk_id); if (it == m_blocks_info.end()) - throw tools::runtime_error("block hash wasn't found"); + throw std::runtime_error("block hash wasn't found"); return it->second.already_generated_coins; } @@ -323,7 +323,7 @@ bool init_output_indices(map_output_idx_t& outs, std::mapsecond); } @@ -486,11 +486,11 @@ void fill_tx_sources_and_destinations(const std::vector& event destinations.clear(); if (!fill_tx_sources(sources, events, blk_head, from, amount + fee, nmix)) - throw tools::runtime_error("couldn't fill transaction sources"); + throw std::runtime_error("couldn't fill transaction sources"); tx_destination_entry de; if (!fill_tx_destination(de, to, amount)) - throw tools::runtime_error("couldn't fill transaction destination"); + throw std::runtime_error("couldn't fill transaction destination"); destinations.push_back(de); tx_destination_entry de_change; @@ -498,7 +498,7 @@ void fill_tx_sources_and_destinations(const std::vector& event if (0 < cache_back) { if (!fill_tx_destination(de_change, from, cache_back)) - throw tools::runtime_error("couldn't fill transaction cache back destination"); + throw std::runtime_error("couldn't fill transaction cache back destination"); destinations.push_back(de_change); } } diff --git a/tests/core_tests/chaingen.h b/tests/core_tests/chaingen.h index d51d9d32..d0d912cb 100644 --- a/tests/core_tests/chaingen.h +++ b/tests/core_tests/chaingen.h @@ -284,7 +284,7 @@ bool do_check_tx_verification_context(const cryptonote::tx_verification_context& { // Default block verification context check if (tvc.m_verifivation_failed) - throw tools::runtime_error("Transaction verification failed"); + throw std::runtime_error("Transaction verification failed"); return true; } //-------------------------------------------------------------------------- @@ -307,7 +307,7 @@ bool do_check_block_verification_context(const cryptonote::block_verification_co { // Default block verification context check if (bvc.m_verifivation_failed) - throw tools::runtime_error("Block verification failed"); + throw std::runtime_error("Block verification failed"); return true; } //-------------------------------------------------------------------------- @@ -621,7 +621,7 @@ inline bool do_replay_file(const std::string& filename) if (!tools::serialize_obj_to_file(events, filename)) \ { \ std::cout << concolor::magenta << "Failed to serialize data to file: " << filename << concolor::normal << std::endl; \ - throw tools::runtime_error("Failed to serialize data to file"); \ + throw std::runtime_error("Failed to serialize data to file"); \ } \ } diff --git a/tests/core_tests/tx_validation.cpp b/tests/core_tests/tx_validation.cpp index 41d5f1e2..f72c906e 100644 --- a/tests/core_tests/tx_validation.cpp +++ b/tests/core_tests/tx_validation.cpp @@ -156,7 +156,7 @@ namespace } } - throw tools::runtime_error("invalid public key wasn't found"); + throw std::runtime_error("invalid public key wasn't found"); return crypto::public_key(); } } diff --git a/tests/net_load_tests/clt.cpp b/tests/net_load_tests/clt.cpp index 36026167..56089a4d 100644 --- a/tests/net_load_tests/clt.cpp +++ b/tests/net_load_tests/clt.cpp @@ -37,7 +37,6 @@ #include "gtest/gtest.h" -#include "common/exception.h" #include "include_base_utils.h" #include "misc_language.h" #include "misc_log_ex.h" diff --git a/tests/net_load_tests/srv.cpp b/tests/net_load_tests/srv.cpp index 13b31215..d8d3eae2 100644 --- a/tests/net_load_tests/srv.cpp +++ b/tests/net_load_tests/srv.cpp @@ -31,7 +31,6 @@ #include #include -#include "common/exception.h" #include "include_base_utils.h" #include "misc_log_ex.h" #include "storages/levin_abstract_invoke2.h" diff --git a/tests/unit_tests/ban.cpp b/tests/unit_tests/ban.cpp index 5d40e90c..a5ce244d 100644 --- a/tests/unit_tests/ban.cpp +++ b/tests/unit_tests/ban.cpp @@ -56,7 +56,7 @@ public: bool on_idle(){return true;} bool find_blockchain_supplement(const std::list& qblock_ids, cryptonote::NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp){return true;} bool handle_get_objects(cryptonote::NOTIFY_REQUEST_GET_OBJECTS::request& arg, cryptonote::NOTIFY_RESPONSE_GET_OBJECTS::request& rsp, cryptonote::cryptonote_connection_context& context){return true;} - cryptonote::blockchain_storage &get_blockchain_storage() { throw tools::runtime_error("Called invalid member function: please never call get_blockchain_storage on the TESTING class test_core."); } + cryptonote::blockchain_storage &get_blockchain_storage() { throw std::runtime_error("Called invalid member function: please never call get_blockchain_storage on the TESTING class test_core."); } bool get_test_drop_download() const {return true;} bool get_test_drop_download_height() const {return true;} bool prepare_handle_incoming_blocks(const std::list &blocks) { return true; } diff --git a/tests/unit_tests/epee_boosted_tcp_server.cpp b/tests/unit_tests/epee_boosted_tcp_server.cpp index b68ce77b..946db292 100644 --- a/tests/unit_tests/epee_boosted_tcp_server.cpp +++ b/tests/unit_tests/epee_boosted_tcp_server.cpp @@ -104,7 +104,7 @@ TEST(boosted_tcp_server, worker_threads_are_exception_resistant) // 2 theads, but 4 exceptions ASSERT_TRUE(srv.run_server(2, false)); - ASSERT_TRUE(srv.async_call([&counter_incrementer]() { counter_incrementer(); throw tools::runtime_error("test 1"); })); + ASSERT_TRUE(srv.async_call([&counter_incrementer]() { counter_incrementer(); throw std::runtime_error("test 1"); })); ASSERT_TRUE(srv.async_call([&counter_incrementer]() { counter_incrementer(); throw std::string("test 2"); })); ASSERT_TRUE(srv.async_call([&counter_incrementer]() { counter_incrementer(); throw "test 3"; })); ASSERT_TRUE(srv.async_call([&counter_incrementer]() { counter_incrementer(); throw 4; })); diff --git a/tests/unit_tests/mnemonics.cpp b/tests/unit_tests/mnemonics.cpp index 3bf1df7e..3dc5db7d 100644 --- a/tests/unit_tests/mnemonics.cpp +++ b/tests/unit_tests/mnemonics.cpp @@ -27,7 +27,6 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "gtest/gtest.h" -#include "common/exception.h" #include "mnemonics/electrum-words.h" #include "crypto/crypto.h" #include diff --git a/tests/unit_tests/test_protocol_pack.cpp b/tests/unit_tests/test_protocol_pack.cpp index 1ff70b4d..a9a1a34a 100644 --- a/tests/unit_tests/test_protocol_pack.cpp +++ b/tests/unit_tests/test_protocol_pack.cpp @@ -30,7 +30,6 @@ #include "gtest/gtest.h" -#include "common/exception.h" #include "include_base_utils.h" #include "cryptonote_protocol/cryptonote_protocol_defs.h" #include "storages/portable_storage_template_helper.h" From eda51a0dc36946af9528ee82216d73330587e145 Mon Sep 17 00:00:00 2001 From: Riccardo Spagni Date: Mon, 21 Mar 2016 12:53:49 +0200 Subject: [PATCH 14/47] set fork date for September, add hyc's GPG key, remove aabramov's --- src/cryptonote_core/blockchain.cpp | 3 ++ utils/gpg_keys/aabramov.asc | 31 --------------------- utils/gpg_keys/hyc.asc | 44 ++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 31 deletions(-) delete mode 100644 utils/gpg_keys/aabramov.asc create mode 100644 utils/gpg_keys/hyc.asc diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 8b7f298e..cec4feec 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -81,6 +81,9 @@ static const struct { // version 2 starts from block 1009827, which is on or around the 20th of March, 2016. Fork time finalised on 2015-09-20. No fork voting occurs for the v2 fork. { 2, 1009827, 0, 1442763710 }, + + // version 3 starts from block 1141317, which is on or around the 24th of September, 2016. Fork time finalised on 2016-03-21. + { 3, 1009827, 0, 1442763710 }, }; static const uint64_t mainnet_hard_fork_version_1_till = 1009826; diff --git a/utils/gpg_keys/aabramov.asc b/utils/gpg_keys/aabramov.asc deleted file mode 100644 index 1626f95b..00000000 --- a/utils/gpg_keys/aabramov.asc +++ /dev/null @@ -1,31 +0,0 @@ ------BEGIN PGP PUBLIC KEY BLOCK----- -Version: GnuPG v1 - -mQENBFQwICMBCACyHKYHaSEdNyE8kqYm+V37IsX5xxqMTkOCuVmaWt8i8FP2aPDz -TgsITQ8tHoJubL0wh/y6Z/KQY8cSWT/JU39gP64I1XdGVgaPErF3kZ7n2jh+K5HY -vBWrFvw4Xj2XvHaJZW6rVVX5dc4rxqnnOjfKTPI6/tQhT16dwbMuCyZOyMfStAF9 -q+14I9M2MAbMlgNO0el0aR3GMVjoDeaWBk/qWxTaHjyOBAVyryoFpwIl7Gmwkk1d -+jv4542LjJOiiwBEDcWUmNvlsRk5sXMcXImJSpaCN1Ta9Eyu2NBIK/qAbuC6YgLe -nn/0DaA7TOpeIMU/WNq/Dpbathst+fMJNK6BABEBAAG0JkFsZXhhbmRlciBBYnJh -bW92IDxhYWJyYW1vdkBnbWFpbC5jb20+iQE+BBMBAgAoBQJUMCAjAhsDBQkDwmcA -BgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRA/57nKbsMV+F/+B/9f7C1iiEFD -18Af48WLJXZaJJTqxMeWzztWhZ2LS27rFOGJcj50UeON7aVu8LWU8gvyPJct6wJA -T1hvPWc6TTsbYp8KHP/nUfUWWV+np0qVSj91eGHp7NjAxXYe4NnWt1JUtINxvNll -BKY2l0MHHHKwuWxky6zug48CToB8Y0RBDUgFM0pmnCzmySs6OAJbLmnnvesTDFZk -m6wycq4/SrbVByTZHVqBmZNnypMQxbqXTti9ltpXWqO89RpQQ+QBQQlrNy9QRHJq -VW9Qn5eL4O4IdmXpHqln1ppSu2il+pqQCGc5O9xFxqG6YRHljQEngBfgKLxOsqa1 -OGJHu1pLyUYRuQENBFQwICMBCACmQ+1DRgm9AMNo4iDur47obDzUTQ/1g2ah1+AT -IYNhhltBD3jURsRZ6lMeancxU2/6hyUzCon5ETKfnF0+ujqldSDuo+XY9CDgmXLC -s3wPAKZvyjScwMFTyW1hGfyT9SFwYVDaXOeW+VrJ97Xh5FWHx8LU7eieYIl8tOUY -YWEeIps/y5zBkk7bB4LVNVc3AlKPS4vb/py5szA5fMg8ix/ggvpq8be4BnImP3a1 -yrl+W9HUtcTs8jBnFLMoZYaUklZ9LR7yfnjhhtKqW833UB07qYQY//dJGy4kVnit -M5LX9VKa+wnWsgP+w5jElPX0bweV6+V/yFRhjLLwV7dv/MJnABEBAAGJASUEGAEC -AA8FAlQwICMCGwwFCQPCZwAACgkQP+e5ym7DFfgrvAf/bnQD5iR/HiNsH0HQE96G -Wsrr9VW/ucnm06pd1D/r7ogYcVWuNIglx3QdvFla4uZDvYR0PhyjNrOyPzUgLWp/ -fjQw9buyI3BawAScbPuN06zHLUIvDQpaPq2+uBRq7sCcdsox6sMiUH6x5QcD3lbR -nh6aAGwSkTabW5a/GVZTIHSicGbhXUaIIh3w+sQZ/y5xTVjgVi4I4XdrxOZoo5RN -nF5oyn74n5p9euxJMWgvC/tz/bD7VKYWJHRnuyeumLTPudLjPFFQpTX+I+gMJ5FT -Vs7byaOlrZbQxzXd4G0uBnCvJ1FH3cL6Ddqlsz/l2WzddMknMbVMn7+fSVMdpbIm -hw== -=7XDh ------END PGP PUBLIC KEY BLOCK----- diff --git a/utils/gpg_keys/hyc.asc b/utils/gpg_keys/hyc.asc new file mode 100644 index 00000000..4d174e98 --- /dev/null +++ b/utils/gpg_keys/hyc.asc @@ -0,0 +1,44 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1 + +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 +GQ== +=sUzZ +-----END PGP PUBLIC KEY BLOCK----- \ No newline at end of file From 23d1538fe70d76817f6f6e07908e93bfd232d30c Mon Sep 17 00:00:00 2001 From: Riccardo Spagni Date: Mon, 21 Mar 2016 13:09:35 +0200 Subject: [PATCH 15/47] also update the timestamp for the hard fork --- src/cryptonote_core/blockchain.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index cec4feec..5a7c9ba5 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -83,7 +83,7 @@ static const struct { { 2, 1009827, 0, 1442763710 }, // version 3 starts from block 1141317, which is on or around the 24th of September, 2016. Fork time finalised on 2016-03-21. - { 3, 1009827, 0, 1442763710 }, + { 3, 1009827, 0, 1458558528 }, }; static const uint64_t mainnet_hard_fork_version_1_till = 1009826; From a1c38299b3d8a1826bf4f2b31853c195bff2619d Mon Sep 17 00:00:00 2001 From: Riccardo Spagni Date: Mon, 21 Mar 2016 13:19:49 +0200 Subject: [PATCH 16/47] also maybe do the block height this time, you know, just so that it actually works. --- src/cryptonote_core/blockchain.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 5a7c9ba5..00bae374 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -83,7 +83,7 @@ static const struct { { 2, 1009827, 0, 1442763710 }, // version 3 starts from block 1141317, which is on or around the 24th of September, 2016. Fork time finalised on 2016-03-21. - { 3, 1009827, 0, 1458558528 }, + { 3, 1141317, 0, 1458558528 }, }; static const uint64_t mainnet_hard_fork_version_1_till = 1009826; From a4242c42b24d936c1ca79f98aa1062177c222c04 Mon Sep 17 00:00:00 2001 From: Riccardo Spagni Date: Mon, 21 Mar 2016 13:37:54 +0200 Subject: [PATCH 17/47] update miniupnpc --- external/miniupnpc/Changelog.txt | 10 ++- external/miniupnpc/Makefile | 4 +- external/miniupnpc/Makefile.mingw | 6 +- external/miniupnpc/apiversions.txt | 7 +- external/miniupnpc/minissdpc.c | 25 ++++++- external/miniupnpc/miniupnpc.c | 73 ++++++++++++++----- external/miniupnpc/miniupnpc.h | 6 +- external/miniupnpc/miniwget.c | 65 ++++++++++++----- external/miniupnpc/miniwget.h | 10 +-- .../miniupnpc/testdesc/new_LiveBox_desc.xml | 2 +- external/miniupnpc/testminiwget.c | 12 +-- external/miniupnpc/upnpc.c | 18 +++-- external/miniupnpc/upnpcommands.c | 4 +- 13 files changed, 174 insertions(+), 68 deletions(-) diff --git a/external/miniupnpc/Changelog.txt b/external/miniupnpc/Changelog.txt index bef61f59..bf6343bd 100644 --- a/external/miniupnpc/Changelog.txt +++ b/external/miniupnpc/Changelog.txt @@ -1,6 +1,14 @@ -$Id: Changelog.txt,v 1.219 2015/10/26 17:05:06 nanard Exp $ +$Id: Changelog.txt,v 1.222 2016/01/24 17:24:35 nanard Exp $ miniUPnP client Changelog. +2016/01/24: + change miniwget to return HTTP status code + increments API_VERSION to 16 + +2016/01/22: + Improve UPNPIGD_IsConnected() to check if WAN address is not private. + parse HTTP response status line in miniwget.c + 2015/10/26: snprintf() overflow check. check overflow in simpleUPnPcommand2() diff --git a/external/miniupnpc/Makefile b/external/miniupnpc/Makefile index 21845f8a..99a69301 100644 --- a/external/miniupnpc/Makefile +++ b/external/miniupnpc/Makefile @@ -1,4 +1,4 @@ -# $Id: Makefile,v 1.126 2015/08/28 12:14:18 nanard Exp $ +# $Id: Makefile,v 1.133 2016/01/24 17:24:35 nanard Exp $ # MiniUPnP Project # http://miniupnp.free.fr/ # http://miniupnp.tuxfamily.org/ @@ -66,7 +66,7 @@ ifeq (SunOS, $(OS)) endif # APIVERSION is used to build SONAME -APIVERSION = 15 +APIVERSION = 16 SRCS = igd_desc_parse.c miniupnpc.c minixml.c minisoap.c miniwget.c \ upnpc.c upnpcommands.c upnpreplyparse.c testminixml.c \ diff --git a/external/miniupnpc/Makefile.mingw b/external/miniupnpc/Makefile.mingw index 54c62de1..1b0d44aa 100644 --- a/external/miniupnpc/Makefile.mingw +++ b/external/miniupnpc/Makefile.mingw @@ -18,7 +18,8 @@ OBJS=miniwget.o minixml.o igd_desc_parse.o minisoap.o \ upnpdev.o OBJSDLL=$(addprefix dll/, $(OBJS)) -all: init upnpc-static upnpc-shared testminixml libminiupnpc.a miniupnpc.dll +all: init upnpc-static upnpc-shared testminixml libminiupnpc.a \ + miniupnpc.dll listdevices init: mkdir dll @@ -66,6 +67,9 @@ upnpc-static: upnpc.o libminiupnpc.a upnpc-shared: dll/upnpc.o miniupnpc.lib $(CC) -o $@ $^ $(LDLIBS) +listdevices: listdevices.o libminiupnpc.a + $(CC) -o $@ $^ $(LDLIBS) + wingenminiupnpcstrings: wingenminiupnpcstrings.o wingenminiupnpcstrings.o: wingenminiupnpcstrings.c diff --git a/external/miniupnpc/apiversions.txt b/external/miniupnpc/apiversions.txt index daac18fa..9464a867 100644 --- a/external/miniupnpc/apiversions.txt +++ b/external/miniupnpc/apiversions.txt @@ -1,7 +1,12 @@ -$Id: apiversions.txt,v 1.7 2015/07/23 20:40:08 nanard Exp $ +$Id: apiversions.txt,v 1.9 2016/01/24 17:24:36 nanard Exp $ Differences in API between miniUPnPc versions +API version 16 + added "status_code" argument to getHTTPResponse(), miniwget() and miniwget_getaddr() + updated macro : + #define MINIUPNPC_API_VERSION 16 + API version 15 changed "sameport" argument of upnpDiscover() upnpDiscoverAll() upnpDiscoverDevice() to "localport". When 0 or 1, behaviour is not changed, but it can take diff --git a/external/miniupnpc/minissdpc.c b/external/miniupnpc/minissdpc.c index 7b1c317b..a6380dfd 100644 --- a/external/miniupnpc/minissdpc.c +++ b/external/miniupnpc/minissdpc.c @@ -1,5 +1,6 @@ /* $Id: minissdpc.c,v 1.28 2015/09/18 13:05:39 nanard Exp $ */ -/* Project : miniupnp +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * Project : miniupnp * Web : http://miniupnp.free.fr/ * Author : Thomas BERNARD * copyright (c) 2005-2015 Thomas Bernard @@ -67,6 +68,10 @@ struct sockaddr_un { #define HAS_IP_MREQN #endif +#if !defined(HAS_IP_MREQN) && !defined(_WIN32) +#include +#endif + #if defined(HAS_IP_MREQN) && defined(NEED_STRUCT_IP_MREQN) /* Several versions of glibc don't define this structure, * define it here and compile with CFLAGS NEED_STRUCT_IP_MREQN */ @@ -647,11 +652,25 @@ ssdpDiscoverDevices(const char * const deviceTypes[], { PRINT_SOCKET_ERROR("setsockopt"); } -#else +#elif !defined(_WIN32) + struct ifreq ifr; + int ifrlen = sizeof(ifr); + strncpy(ifr.ifr_name, multicastif, IFNAMSIZ); + ifr.ifr_name[IFNAMSIZ-1] = '\0'; + if(ioctl(sudp, SIOCGIFADDR, &ifr, &ifrlen) < 0) + { + PRINT_SOCKET_ERROR("ioctl(...SIOCGIFADDR...)"); + } + mc_if.s_addr = ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr.s_addr; + if(setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_IF, (const char *)&mc_if, sizeof(mc_if)) < 0) + { + PRINT_SOCKET_ERROR("setsockopt"); + } +#else /* _WIN32 */ #ifdef DEBUG printf("Setting of multicast interface not supported with interface name.\n"); #endif -#endif +#endif /* #ifdef HAS_IP_MREQN / !defined(_WIN32) */ } } } diff --git a/external/miniupnpc/miniupnpc.c b/external/miniupnpc/miniupnpc.c index fc665a0a..5eb9500e 100644 --- a/external/miniupnpc/miniupnpc.c +++ b/external/miniupnpc/miniupnpc.c @@ -1,9 +1,9 @@ -/* $Id: miniupnpc.c,v 1.135 2015/07/23 20:40:08 nanard Exp $ */ -/* vim: tabstop=4 shiftwidth=4 noexpandtab */ -/* Project : miniupnp +/* $Id: miniupnpc.c,v 1.148 2016/01/24 17:24:36 nanard Exp $ */ +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * Project : miniupnp * Web : http://miniupnp.free.fr/ * Author : Thomas BERNARD - * copyright (c) 2005-2015 Thomas Bernard + * copyright (c) 2005-2016 Thomas Bernard * This software is subjet to the conditions detailed in the * provided LICENSE file. */ #include @@ -72,6 +72,25 @@ #define SERVICEPREFIX "u" #define SERVICEPREFIX2 'u' +/* check if an ip address is a private (LAN) address + * see https://tools.ietf.org/html/rfc1918 */ +static int is_rfc1918addr(const char * addr) +{ + /* 192.168.0.0 - 192.168.255.255 (192.168/16 prefix) */ + if(COMPARE(addr, "192.168.")) + return 1; + /* 10.0.0.0 - 10.255.255.255 (10/8 prefix) */ + if(COMPARE(addr, "10.")) + return 1; + /* 172.16.0.0 - 172.31.255.255 (172.16/12 prefix) */ + if(COMPARE(addr, "172.")) { + int i = atoi(addr + 4); + if((16 <= i) && (i <= 31)) + return 1; + } + return 0; +} + /* root description parsing */ MINIUPNP_LIBSPEC void parserootdesc(const char * buffer, int bufsize, struct IGDdatas * data) { @@ -107,6 +126,7 @@ char * simpleUPnPcommand2(int s, const char * url, const char * service, int soapbodylen; char * buf; int n; + int status_code; *bufsize = 0; snprintf(soapact, sizeof(soapact), "%s#%s", service, action); @@ -210,11 +230,15 @@ char * simpleUPnPcommand2(int s, const char * url, const char * service, return NULL; } - buf = getHTTPResponse(s, bufsize); + buf = getHTTPResponse(s, bufsize, &status_code); #ifdef DEBUG if(*bufsize > 0 && buf) { - printf("SOAP Response :\n%.*s\n", *bufsize, buf); + printf("HTTP %d SOAP Response :\n%.*s\n", status_code, *bufsize, buf); + } + else + { + printf("HTTP %d, empty SOAP response. size=%d\n", status_code, *bufsize); } #endif closesocket(s); @@ -526,7 +550,7 @@ UPNPIGD_IsConnected(struct UPNPUrls * urls, struct IGDdatas * data) * 3 = an UPnP device has been found but was not recognized as an IGD * * In any positive non zero return case, the urls and data structures - * passed as parameters are set. Donc forget to call FreeUPNPUrls(urls) to + * passed as parameters are set. Dont forget to call FreeUPNPUrls(urls) to * free allocated memory. */ MINIUPNP_LIBSPEC int @@ -547,6 +571,8 @@ UPNP_GetValidIGD(struct UPNPDev * devlist, int n_igd = 0; char extIpAddr[16]; char myLanAddr[40]; + int status_code = -1; + if(!devlist) { #ifdef DEBUG @@ -570,7 +596,7 @@ UPNP_GetValidIGD(struct UPNPDev * devlist, * with st == urn:schemas-upnp-org:device:InternetGatewayDevice:1 */ desc[i].xml = miniwget_getaddr(dev->descURL, &(desc[i].size), myLanAddr, sizeof(myLanAddr), - dev->scope_id); + dev->scope_id, &status_code); #ifdef DEBUG if(!desc[i].xml) { @@ -604,20 +630,25 @@ UPNP_GetValidIGD(struct UPNPDev * devlist, parserootdesc(desc[i].xml, desc[i].size, data); if(desc[i].is_igd || state >= 3 ) { + int is_connected; + GetUPNPUrls(urls, data, dev->descURL, dev->scope_id); /* in state 2 and 3 we dont test if device is connected ! */ if(state >= 2) goto free_and_return; + is_connected = UPNPIGD_IsConnected(urls, data); #ifdef DEBUG printf("UPNPIGD_IsConnected(%s) = %d\n", - urls->controlURL, - UPNPIGD_IsConnected(urls, data)); + urls->controlURL, is_connected); #endif /* checks that status is connected AND there is a external IP address assigned */ - if(UPNPIGD_IsConnected(urls, data) - && (UPNP_GetExternalIPAddress(urls->controlURL, data->first.servicetype, extIpAddr) == 0)) - goto free_and_return; + if(is_connected && + (UPNP_GetExternalIPAddress(urls->controlURL, data->first.servicetype, extIpAddr) == 0)) { + if(!is_rfc1918addr(extIpAddr) && (extIpAddr[0] != '\0') + && (0 != strcmp(extIpAddr, "0.0.0.0"))) + goto free_and_return; + } FreeUPNPUrls(urls); if(data->second.servicetype[0] != '\0') { #ifdef DEBUG @@ -629,14 +660,17 @@ UPNP_GetValidIGD(struct UPNPDev * devlist, memcpy(&data->first, &data->second, sizeof(struct IGDdatas_service)); memcpy(&data->second, &data->tmp, sizeof(struct IGDdatas_service)); GetUPNPUrls(urls, data, dev->descURL, dev->scope_id); + is_connected = UPNPIGD_IsConnected(urls, data); #ifdef DEBUG printf("UPNPIGD_IsConnected(%s) = %d\n", - urls->controlURL, - UPNPIGD_IsConnected(urls, data)); + urls->controlURL, is_connected); #endif - if(UPNPIGD_IsConnected(urls, data) - && (UPNP_GetExternalIPAddress(urls->controlURL, data->first.servicetype, extIpAddr) == 0)) - goto free_and_return; + if(is_connected && + (UPNP_GetExternalIPAddress(urls->controlURL, data->first.servicetype, extIpAddr) == 0)) { + if(!is_rfc1918addr(extIpAddr) && (extIpAddr[0] != '\0') + && (0 != strcmp(extIpAddr, "0.0.0.0"))) + goto free_and_return; + } FreeUPNPUrls(urls); } } @@ -670,8 +704,9 @@ UPNP_GetIGDFromUrl(const char * rootdescurl, { char * descXML; int descXMLsize = 0; + descXML = miniwget_getaddr(rootdescurl, &descXMLsize, - lanaddr, lanaddrlen, 0); + lanaddr, lanaddrlen, 0, NULL); if(descXML) { memset(data, 0, sizeof(struct IGDdatas)); memset(urls, 0, sizeof(struct UPNPUrls)); diff --git a/external/miniupnpc/miniupnpc.h b/external/miniupnpc/miniupnpc.h index a694978c..5e824885 100644 --- a/external/miniupnpc/miniupnpc.h +++ b/external/miniupnpc/miniupnpc.h @@ -1,8 +1,8 @@ -/* $Id: miniupnpc.h,v 1.44 2015/07/23 20:40:10 nanard Exp $ */ +/* $Id: miniupnpc.h,v 1.49 2016/01/24 17:24:36 nanard Exp $ */ /* Project: miniupnp * http://miniupnp.free.fr/ * Author: Thomas Bernard - * Copyright (c) 2005-2015 Thomas Bernard + * Copyright (c) 2005-2016 Thomas Bernard * This software is subjects to the conditions detailed * in the LICENCE file provided within this distribution */ #ifndef MINIUPNPC_H_INCLUDED @@ -20,7 +20,7 @@ /* versions : */ #define MINIUPNPC_VERSION "1.9" -#define MINIUPNPC_API_VERSION 15 +#define MINIUPNPC_API_VERSION 16 /* Source port: Using "1" as an alias for 1900 for backwards compatability diff --git a/external/miniupnpc/miniwget.c b/external/miniupnpc/miniwget.c index bc6e19be..ca88a1e9 100644 --- a/external/miniupnpc/miniwget.c +++ b/external/miniupnpc/miniwget.c @@ -1,8 +1,8 @@ -/* $Id: miniwget.c,v 1.70 2015/07/15 12:41:13 nanard Exp $ */ +/* $Id: miniwget.c,v 1.75 2016/01/24 17:24:36 nanard Exp $ */ /* Project : miniupnp * Website : http://miniupnp.free.fr/ * Author : Thomas Bernard - * Copyright (c) 2005-2015 Thomas Bernard + * Copyright (c) 2005-2016 Thomas Bernard * This software is subject to the conditions detailed in the * LICENCE file provided in this distribution. */ @@ -65,7 +65,7 @@ * to the length parameter. */ void * -getHTTPResponse(int s, int * size) +getHTTPResponse(int s, int * size, int * status_code) { char buf[2048]; int n; @@ -83,7 +83,10 @@ getHTTPResponse(int s, int * size) unsigned int content_buf_used = 0; char chunksize_buf[32]; unsigned int chunksize_buf_index; + char * reason_phrase = NULL; + int reason_phrase_len = 0; + if(status_code) *status_code = -1; header_buf = malloc(header_buf_len); if(header_buf == NULL) { @@ -155,7 +158,7 @@ getHTTPResponse(int s, int * size) continue; /* parse header lines */ for(i = 0; i < endofheaders - 1; i++) { - if(colon <= linestart && header_buf[i]==':') + if(linestart > 0 && colon <= linestart && header_buf[i]==':') { colon = i; while(i < (endofheaders-1) @@ -166,7 +169,29 @@ getHTTPResponse(int s, int * size) /* detecting end of line */ else if(header_buf[i]=='\r' || header_buf[i]=='\n') { - if(colon > linestart && valuestart > colon) + if(linestart == 0 && status_code) + { + /* Status line + * HTTP-Version SP Status-Code SP Reason-Phrase CRLF */ + int sp; + for(sp = 0; sp < i; sp++) + if(header_buf[sp] == ' ') + { + if(*status_code < 0) + *status_code = atoi(header_buf + sp + 1); + else + { + reason_phrase = header_buf + sp + 1; + reason_phrase_len = i - sp - 1; + break; + } + } +#ifdef DEBUG + printf("HTTP status code = %d, Reason phrase = %.*s\n", + *status_code, reason_phrase_len, reason_phrase); +#endif + } + else if(colon > linestart && valuestart > colon) { #ifdef DEBUG printf("header='%.*s', value='%.*s'\n", @@ -337,7 +362,8 @@ static void * miniwget3(const char * host, unsigned short port, const char * path, int * size, char * addr_str, int addr_str_len, - const char * httpversion, unsigned int scope_id) + const char * httpversion, unsigned int scope_id, + int * status_code) { char buf[2048]; int s; @@ -435,7 +461,7 @@ miniwget3(const char * host, sent += n; } } - content = getHTTPResponse(s, size); + content = getHTTPResponse(s, size, status_code); closesocket(s); return content; } @@ -444,18 +470,20 @@ miniwget3(const char * host, * Call miniwget3(); retry with HTTP/1.1 if 1.0 fails. */ static void * miniwget2(const char * host, - unsigned short port, const char * path, - int * size, char * addr_str, int addr_str_len, - unsigned int scope_id) + unsigned short port, const char * path, + int * size, char * addr_str, int addr_str_len, + unsigned int scope_id, int * status_code) { char * respbuffer; #if 1 respbuffer = miniwget3(host, port, path, size, - addr_str, addr_str_len, "1.1", scope_id); + addr_str, addr_str_len, "1.1", + scope_id, status_code); #else respbuffer = miniwget3(host, port, path, size, - addr_str, addr_str_len, "1.0", scope_id); + addr_str, addr_str_len, "1.0", + scope_id, status_code); if (*size == 0) { #ifdef DEBUG @@ -463,7 +491,8 @@ miniwget2(const char * host, #endif free(respbuffer); respbuffer = miniwget3(host, port, path, size, - addr_str, addr_str_len, "1.1", scope_id); + addr_str, addr_str_len, "1.1", + scope_id, status_code); } #endif return respbuffer; @@ -588,7 +617,8 @@ parseURL(const char * url, } void * -miniwget(const char * url, int * size, unsigned int scope_id) +miniwget(const char * url, int * size, + unsigned int scope_id, int * status_code) { unsigned short port; char * path; @@ -601,12 +631,13 @@ miniwget(const char * url, int * size, unsigned int scope_id) printf("parsed url : hostname='%s' port=%hu path='%s' scope_id=%u\n", hostname, port, path, scope_id); #endif - return miniwget2(hostname, port, path, size, 0, 0, scope_id); + return miniwget2(hostname, port, path, size, 0, 0, scope_id, status_code); } void * miniwget_getaddr(const char * url, int * size, - char * addr, int addrlen, unsigned int scope_id) + char * addr, int addrlen, unsigned int scope_id, + int * status_code) { unsigned short port; char * path; @@ -621,6 +652,6 @@ miniwget_getaddr(const char * url, int * size, printf("parsed url : hostname='%s' port=%hu path='%s' scope_id=%u\n", hostname, port, path, scope_id); #endif - return miniwget2(hostname, port, path, size, addr, addrlen, scope_id); + return miniwget2(hostname, port, path, size, addr, addrlen, scope_id, status_code); } diff --git a/external/miniupnpc/miniwget.h b/external/miniupnpc/miniwget.h index e231fc27..0701494d 100644 --- a/external/miniupnpc/miniwget.h +++ b/external/miniupnpc/miniwget.h @@ -1,7 +1,7 @@ -/* $Id: miniwget.h,v 1.7 2012/06/23 22:35:59 nanard Exp $ */ +/* $Id: miniwget.h,v 1.12 2016/01/24 17:24:36 nanard Exp $ */ /* Project : miniupnp * Author : Thomas Bernard - * Copyright (c) 2005-2015 Thomas Bernard + * Copyright (c) 2005-2016 Thomas Bernard * This software is subject to the conditions detailed in the * LICENCE file provided in this distribution. * */ @@ -14,11 +14,11 @@ extern "C" { #endif -MINIUPNP_LIBSPEC void * getHTTPResponse(int s, int * size); +MINIUPNP_LIBSPEC void * getHTTPResponse(int s, int * size, int * status_code); -MINIUPNP_LIBSPEC void * miniwget(const char *, int *, unsigned int); +MINIUPNP_LIBSPEC void * miniwget(const char *, int *, unsigned int, int *); -MINIUPNP_LIBSPEC void * miniwget_getaddr(const char *, int *, char *, int, unsigned int); +MINIUPNP_LIBSPEC void * miniwget_getaddr(const char *, int *, char *, int, unsigned int, int *); int parseURL(const char *, char *, unsigned short *, char * *, unsigned int *); diff --git a/external/miniupnpc/testdesc/new_LiveBox_desc.xml b/external/miniupnpc/testdesc/new_LiveBox_desc.xml index 9d5160bb..620eb55a 100644 --- a/external/miniupnpc/testdesc/new_LiveBox_desc.xml +++ b/external/miniupnpc/testdesc/new_LiveBox_desc.xml @@ -87,4 +87,4 @@ - + \ No newline at end of file diff --git a/external/miniupnpc/testminiwget.c b/external/miniupnpc/testminiwget.c index 8ae90320..5eb49ec1 100644 --- a/external/miniupnpc/testminiwget.c +++ b/external/miniupnpc/testminiwget.c @@ -1,7 +1,7 @@ -/* $Id: testminiwget.c,v 1.4 2012/06/23 22:35:59 nanard Exp $ */ +/* $Id: testminiwget.c,v 1.5 2016/01/24 17:24:36 nanard Exp $ */ /* Project : miniupnp * Author : Thomas Bernard - * Copyright (c) 2005-2012 Thomas Bernard + * Copyright (c) 2005-2016 Thomas Bernard * This software is subject to the conditions detailed in the * LICENCE file provided in this distribution. * */ @@ -20,15 +20,17 @@ int main(int argc, char * * argv) int size, writtensize; FILE *f; char addr[64]; + int status_code = -1; if(argc < 3) { fprintf(stderr, "Usage:\t%s url file\n", argv[0]); fprintf(stderr, "Example:\t%s http://www.google.com/ out.html\n", argv[0]); return 1; } - data = miniwget_getaddr(argv[1], &size, addr, sizeof(addr), 0); - if(!data) { - fprintf(stderr, "Error fetching %s\n", argv[1]); + data = miniwget_getaddr(argv[1], &size, addr, sizeof(addr), 0, &status_code); + if(!data || (status_code != 200)) { + if(data) free(data); + fprintf(stderr, "Error %d fetching %s\n", status_code, argv[1]); return 1; } printf("local address : %s\n", addr); diff --git a/external/miniupnpc/upnpc.c b/external/miniupnpc/upnpc.c index 1a910ccd..94f131c8 100644 --- a/external/miniupnpc/upnpc.c +++ b/external/miniupnpc/upnpc.c @@ -1,7 +1,7 @@ -/* $Id: upnpc.c,v 1.111 2015/07/23 20:40:10 nanard Exp $ */ +/* $Id: upnpc.c,v 1.114 2016/01/22 15:04:23 nanard Exp $ */ /* Project : miniupnp * Author : Thomas Bernard - * Copyright (c) 2005-2015 Thomas Bernard + * Copyright (c) 2005-2016 Thomas Bernard * This software is subject to the conditions detailed in the * LICENCE file provided in this distribution. */ @@ -66,7 +66,7 @@ static void DisplayInfos(struct UPNPUrls * urls, char connectionType[64]; char status[64]; char lastconnerr[64]; - unsigned int uptime; + unsigned int uptime = 0; unsigned int brUp, brDown; time_t timenow, timestarted; int r; @@ -82,9 +82,11 @@ static void DisplayInfos(struct UPNPUrls * urls, else printf("Status : %s, uptime=%us, LastConnectionError : %s\n", status, uptime, lastconnerr); - timenow = time(NULL); - timestarted = timenow - uptime; - printf(" Time started : %s", ctime(×tarted)); + if(uptime > 0) { + timenow = time(NULL); + timestarted = timenow - uptime; + printf(" Time started : %s", ctime(×tarted)); + } if(UPNP_GetLinkLayerMaxBitRates(urls->controlURL_CIF, data->CIF.servicetype, &brDown, &brUp) != UPNPCOMMAND_SUCCESS) { printf("GetLinkLayerMaxBitRates failed.\n"); @@ -538,7 +540,7 @@ int main(int argc, char ** argv) char ** commandargv = 0; int commandargc = 0; struct UPNPDev * devlist = 0; - char lanaddr[64]; /* my ip address on the LAN */ + char lanaddr[64] = "unset"; /* my ip address on the LAN */ int i; const char * rootdescurl = 0; const char * multicastif = 0; @@ -560,7 +562,7 @@ int main(int argc, char ** argv) } #endif printf("upnpc : miniupnpc library test client, version %s.\n", MINIUPNPC_VERSION_STRING); - printf(" (c) 2005-2015 Thomas Bernard.\n"); + printf(" (c) 2005-2016 Thomas Bernard.\n"); printf("Go to http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/\n" "for more information.\n"); /* command line processing */ diff --git a/external/miniupnpc/upnpcommands.c b/external/miniupnpc/upnpcommands.c index cabdb50d..76cf9e39 100644 --- a/external/miniupnpc/upnpcommands.c +++ b/external/miniupnpc/upnpcommands.c @@ -616,14 +616,14 @@ UPNP_GetGenericPortMappingEntry(const char * controlURL, protocol[3] = '\0'; } p = GetValueFromNameValueList(&pdata, "NewInternalClient"); - if(p && intClient) + if(p) { strncpy(intClient, p, 16); intClient[15] = '\0'; r = 0; } p = GetValueFromNameValueList(&pdata, "NewInternalPort"); - if(p && intPort) + if(p) { strncpy(intPort, p, 6); intPort[5] = '\0'; From 1800d611a2a60ee5cdce7af266ac927118147b1f Mon Sep 17 00:00:00 2001 From: Riccardo Spagni Date: Mon, 21 Mar 2016 15:05:50 +0200 Subject: [PATCH 18/47] bump miniupnpc API version number --- external/miniupnpc/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/miniupnpc/CMakeLists.txt b/external/miniupnpc/CMakeLists.txt index 1e7645a9..2d75a530 100644 --- a/external/miniupnpc/CMakeLists.txt +++ b/external/miniupnpc/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required (VERSION 2.6) project (miniupnpc C) set (MINIUPNPC_VERSION 1.9) -set (MINIUPNPC_API_VERSION 15) +set (MINIUPNPC_API_VERSION 16) # - we comment out this block as we don't support these other build types if(0) From 40974b155e5ef48bead0989e80fe17366029491b Mon Sep 17 00:00:00 2001 From: Riccardo Spagni Date: Mon, 21 Mar 2016 19:37:07 +0200 Subject: [PATCH 19/47] fix building on FreeBSD --- external/CMakeLists.txt | 3 --- external/miniupnpc/CMakeLists.txt | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 33e843e6..f0e65ac0 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -37,9 +37,6 @@ find_package(Miniupnpc QUIET) -# FreeBSD doesn't play well with the local copy, so default to using shared -set(USE_SHARED_MINIUPNPC false) - # If we have the correct shared version and we're not building static, use it if(STATIC) set(USE_SHARED_MINIUPNPC false) diff --git a/external/miniupnpc/CMakeLists.txt b/external/miniupnpc/CMakeLists.txt index 2d75a530..fd75f9ff 100644 --- a/external/miniupnpc/CMakeLists.txt +++ b/external/miniupnpc/CMakeLists.txt @@ -33,7 +33,7 @@ endif (NO_GETADDRINFO) if (NOT WIN32) add_definitions (-DMINIUPNPC_SET_SOCKET_TIMEOUT) - add_definitions (-D_BSD_SOURCE -D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=200112L) + add_definitions (-D_BSD_SOURCE -D_DEFAULT_SOURCE) else (NOT WIN32) add_definitions (-D_WIN32_WINNT=0x0501) # XP or higher for getnameinfo and friends endif (NOT WIN32) From 4b425a397c63d6585504fd1b95907011386d16d6 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Mon, 21 Mar 2016 20:58:07 +0000 Subject: [PATCH 20/47] core_tests: fix compile failure with GCC 4.8.4 Reported and tested by smooth --- tests/core_tests/chaingen.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core_tests/chaingen.h b/tests/core_tests/chaingen.h index d0d912cb..44170d11 100644 --- a/tests/core_tests/chaingen.h +++ b/tests/core_tests/chaingen.h @@ -472,7 +472,7 @@ inline bool replay_events_through_core(cryptonote::core& cr, const std::vector struct get_test_options { - const std::pair hard_forks[1] = {std::make_pair(1, 0)}; + const std::pair hard_forks[1] = {std::make_pair((uint8_t)1, (uint64_t)0)}; const cryptonote::test_options test_options = { hard_forks }; From 8438aeb7403b5a1b4cb0efdd1cbeb03be23d67ea Mon Sep 17 00:00:00 2001 From: Riccardo Spagni Date: Mon, 21 Mar 2016 23:10:48 +0200 Subject: [PATCH 21/47] update version number --- src/version.h.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h.in b/src/version.h.in index 07312981..a0832db9 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -1,4 +1,4 @@ #define MONERO_VERSION_TAG "@VERSIONTAG@" -#define MONERO_VERSION "0.9.2.0" +#define MONERO_VERSION "0.9.3.0" #define MONERO_RELEASE_NAME "Hydrogen Helix" #define MONERO_VERSION_FULL MONERO_VERSION "-" MONERO_VERSION_TAG From cc4b19c32ede3c377597ff8cb9e7e6a86a9cdfa2 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Tue, 22 Mar 2016 15:37:16 +0000 Subject: [PATCH 22/47] blockchain: fix partial block reward detection --- src/cryptonote_core/blockchain.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 00bae374..131f56a4 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -984,9 +984,9 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl // to show the amount of coins that were actually generated, the remainder will be pushed back for later // emission. This modifies the emission curve very slightly. CHECK_AND_ASSERT_MES(money_in_use - fee <= base_reward, false, "base reward calculation bug"); - base_reward = money_in_use - fee; if(base_reward + fee != money_in_use) partial_block_reward = true; + base_reward = money_in_use - fee; } return true; } From 3a484497bc3b363aabbb0170c4334bb5176b4769 Mon Sep 17 00:00:00 2001 From: Thomas Winget Date: Thu, 24 Sep 2015 11:00:46 -0400 Subject: [PATCH 23/47] Updated documentation for blockchain.* All functions are now documented in doxygen format. Comments have been updated to reflect the current state of the code. Many areas for improvement in clarity and design have been noted, as well as cruft to be removed. These changes are not reflected in this commit both to allow time for comment and to keep commits organized by purpose. --- src/cryptonote_core/blockchain.cpp | 40 +- src/cryptonote_core/blockchain.h | 899 ++++++++++++++++++++++++++++- 2 files changed, 925 insertions(+), 14 deletions(-) diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 00bae374..a55a435b 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -352,6 +352,7 @@ bool Blockchain::init(BlockchainDB* db, const bool testnet, const cryptonote::te // we only need 1 m_async_pool.create_thread(boost::bind(&boost::asio::io_service::run, &m_async_service)); + //TODO: move this block into separate functions? #if defined(PER_BLOCK_CHECKPOINT) if (!fakechain) load_compiled_in_block_hashes(); @@ -840,7 +841,7 @@ bool Blockchain::switch_to_alternative_blockchain(std::list blocks, starting at -// and return by reference . +// get the block sizes of the last blocks, and return by reference . void Blockchain::get_last_n_blocks_sizes(std::vector& sz, size_t count) const { LOG_PRINT_L3("Blockchain::" << __func__); @@ -1408,6 +1408,10 @@ bool Blockchain::get_blocks(uint64_t start_offset, size_t count, std::list missed_tx_ids; std::list txs; + + // FIXME: s/rsp.missed_ids/missed_tx_id/ ? Seems like rsp.missed_ids + // is for missed blocks, not missed transactions as well. get_transactions(bl.tx_hashes, txs, missed_tx_ids); if (missed_tx_ids.size() != 0) @@ -1668,6 +1675,8 @@ uint64_t Blockchain::block_difficulty(uint64_t i) const return 0; } //------------------------------------------------------------------ +//TODO: return type should be void, throw on exception +// alternatively, return true only if no blocks missed template bool Blockchain::get_blocks(const t_ids_container& block_ids, t_blocks_container& blocks, t_missed_container& missed_bs) const { @@ -1692,6 +1701,8 @@ bool Blockchain::get_blocks(const t_ids_container& block_ids, t_blocks_container return true; } //------------------------------------------------------------------ +//TODO: return type should be void, throw on exception +// alternatively, return true only if no transactions missed template bool Blockchain::get_transactions(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs) const { @@ -1708,7 +1719,6 @@ bool Blockchain::get_transactions(const t_ids_container& txs_ids, t_tx_container { missed_txs.push_back(tx_hash); } - //FIXME: is this the correct way to handle this? catch (const std::exception& e) { return false; @@ -1965,6 +1975,11 @@ bool Blockchain::get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vectorheight() < m_blocks_hash_check.size() && kept_by_block) { TIME_MEASURE_START(a); @@ -2038,6 +2054,10 @@ bool Blockchain::have_tx_keyimges_as_spent(const transaction &tx) const } //------------------------------------------------------------------ // This function validates transaction inputs and their keys. +// FIXME: consider moving functionality specific to one input into +// check_tx_input() rather than here, and use this function simply +// to iterate the inputs as necessary (splitting the task +// using threads, etc.) bool Blockchain::check_tx_inputs(const transaction& tx, uint64_t* pmax_used_block_height) { LOG_PRINT_L3("Blockchain::" << __func__); @@ -2320,7 +2340,7 @@ bool Blockchain::check_tx_input(const txin_to_key& txin, const crypto::hash& tx_ output_keys.clear(); - //check ring signature + // collect output keys outputs_visitor vi(output_keys, *this); if (!scan_outputkeys_for_indexes(txin, vi, tx_prefix_hash, pmax_related_block_height)) { @@ -2617,6 +2637,11 @@ leave: txs.push_back(tx); TIME_MEASURE_START(dd); + // FIXME: the storage should not be responsible for validation. + // If it does any, it is merely a sanity check. + // Validation is the purview of the Blockchain class + // - TW + // // ND: this is not needed, db->add_block() checks for duplicate k_images and fails accordingly. // if (!check_for_double_spend(tx, keys)) // { @@ -2797,6 +2822,8 @@ bool Blockchain::add_new_block(const block& bl_, block_verification_context& bvc return handle_block_to_main_chain(bl, id, bvc); } //------------------------------------------------------------------ +//TODO: Refactor, consider returning a failure height and letting +// caller decide course of action. void Blockchain::check_against_checkpoints(const checkpoints& points, bool enforce) { const auto& pts = points.get_points(); @@ -2878,6 +2905,8 @@ void Blockchain::block_longhash_worker(const uint64_t height, const std::vector< TIME_MEASURE_START(t); slow_hash_allocate_state(); + //FIXME: height should be changing here, as get_block_longhash expects + // the height of the block passed to it for (const auto & block : blocks) { crypto::hash id = get_block_hash(block); @@ -2933,6 +2962,7 @@ bool Blockchain::cleanup_handle_incoming_blocks(bool force_sync) } //------------------------------------------------------------------ +//FIXME: unused parameter txs void Blockchain::output_scan_worker(const uint64_t amount, const std::vector &offsets, std::vector &outputs, std::unordered_map &txs) const { try diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index ab2c8f9e..98a58c54 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -60,11 +60,14 @@ namespace cryptonote class tx_memory_pool; struct test_options; + /** Declares ways in which the BlockchainDB backend should be told to sync + * + */ enum blockchain_db_sync_mode { - db_sync, - db_async, - db_nosync + db_sync, //!< handle syncing calls instead of the backing db, synchronously + db_async, //!< handle syncing calls instead of the backing db, asynchronously + db_nosync //!< Leave syncing up to the backing db (safest, but slowest because of disk I/O) }; /************************************************************************/ @@ -73,6 +76,9 @@ namespace cryptonote class Blockchain { public: + /** + * @brief Now-defunct (TODO: remove) struct from in-memory blockchain + */ struct transaction_chain_entry { transaction tx; @@ -81,127 +87,700 @@ namespace cryptonote std::vector m_global_output_indexes; }; + /** + * @brief container for passing a block and metadata about it on the blockchain + */ struct block_extended_info { - block bl; - uint64_t height; - size_t block_cumulative_size; - difficulty_type cumulative_difficulty; - uint64_t already_generated_coins; + block bl; //!< the block + uint64_t height; //!< the height of the block in the blockchain + size_t block_cumulative_size; //!< the size (in bytes) of the block + difficulty_type cumulative_difficulty; //!< the accumulated difficulty after that block + uint64_t already_generated_coins; //!< the total coins minted after that block }; + /** + * @brief Blockchain constructor + * + * @param tx_pool a reference to the transaction pool to be kept by the Blockchain + */ Blockchain(tx_memory_pool& tx_pool); + /** + * @brief Initialize the Blockchain state + * + * @param db a pointer to the backing store to use for the blockchain + * @param testnet true if on testnet, else false + * @param test_options test parameters + * + * @return true on success, false if any initialization steps fail + */ bool init(BlockchainDB* db, const bool testnet = false, const cryptonote::test_options *test_options = NULL); + + /** + * @brief Initialize the Blockchain state + * + * @param db a pointer to the backing store to use for the blockchain + * @param hf a structure containing hardfork information + * @param testnet true if on testnet, else false + * + * @return true on success, false if any initialization steps fail + */ bool init(BlockchainDB* db, HardFork*& hf, const bool testnet = false); + + /** + * @brief Uninitializes the blockchain state + * + * Saves to disk any state that needs to be maintained + * + * @return true on success, false if any uninitialization steps fail + */ bool deinit(); + /** + * @brief assign a set of blockchain checkpoint hashes + * + * @param chk_pts the set of checkpoints to assign + */ void set_checkpoints(checkpoints&& chk_pts) { m_checkpoints = chk_pts; } - //bool push_new_block(); + /** + * @brief get blocks and transactions from blocks based on start height and count + * + * @param start_offset the height on the blockchain to start at + * @param count the number of blocks to get, if there are as many after start_offset + * @param blocks return-by-reference container to put result blocks in + * @param txs return-by-reference container to put result transactions in + * + * @return false if start_offset > blockchain height, else true + */ bool get_blocks(uint64_t start_offset, size_t count, std::list& blocks, std::list& txs) const; + + /** + * @brief get blocks from blocks based on start height and count + * + * @param start_offset the height on the blockchain to start at + * @param count the number of blocks to get, if there are as many after start_offset + * @param blocks return-by-reference container to put result blocks in + * + * @return false if start_offset > blockchain height, else true + */ bool get_blocks(uint64_t start_offset, size_t count, std::list& blocks) const; + + /** + * @brief compiles a list of all blocks stored as alternative chains + * + * @param blocks return-by-reference container to put result blocks in + * + * @return true + */ bool get_alternative_blocks(std::list& blocks) const; + + /** + * @brief returns the number of alternative blocks stored + * + * @return the number of alternative blocks stored + */ size_t get_alternative_blocks_count() const; + + /** + * @brief gets a block's hash given a height + * + * @param height the height of the block + * + * @return the hash of the block at the requested height, or a zeroed hash if there is no such block + */ crypto::hash get_block_id_by_height(uint64_t height) const; + + /** + * @brief gets the block with a given hash + * + * @param h the hash to look for + * @param blk return-by-reference variable to put result block in + * + * @return true if the block was found, else false + */ bool get_block_by_hash(const crypto::hash &h, block &blk) const; + + /** + * @brief get all block hashes (main chain, alt chains, and invalid blocks) + * + * @param main return-by-reference container to put result main chain blocks' hashes in + * @param alt return-by-reference container to put result alt chain blocks' hashes in + * @param invalid return-by-reference container to put result invalid blocks' hashes in + */ void get_all_known_block_ids(std::list &main, std::list &alt, std::list &invalid) const; + + /** + * @brief performs some preprocessing on a group of incoming blocks to speed up verification + * + * @param blocks a list of incoming blocks + * + * @return false on erroneous blocks, else true + */ bool prepare_handle_incoming_blocks(const std::list &blocks); + + /** + * @brief incoming blocks post-processing, cleanup, and disk sync + * + * @param force_sync if true, and Blockchain is handling syncing to disk, always sync + * + * @return true + */ bool cleanup_handle_incoming_blocks(bool force_sync = false); + /** + * @brief search the blockchain for a transaction by hash + * + * @param id the hash to search for + * + * @return true if the tx exists, else false + */ bool have_tx(const crypto::hash &id) const; + + /** + * @brief check if any key image in a transaction has already been spent + * + * @param tx the transaction to check + * + * @return true if any key image is already spent in the blockchain, else false + */ bool have_tx_keyimges_as_spent(const transaction &tx) const; + + /** + * @brief check if a key image is already spent on the blockchain + * + * Whenever a transaction output is used as an input for another transaction + * (a true input, not just one of a mixing set), a key image is generated + * and stored in the transaction in order to prevent double spending. If + * this key image is seen again, the transaction using it is rejected. + * + * @param key_im the key image to search for + * + * @return true if the key image is already spent in the blockchain, else false + */ bool have_tx_keyimg_as_spent(const crypto::key_image &key_im) const; + /** + * @brief get the current height of the blockchain + * + * @return the height + */ uint64_t get_current_blockchain_height() const; + + /** + * @brief get the hash of the most recent block on the blockchain + * + * @return the hash + */ crypto::hash get_tail_id() const; + + /** + * @brief get the height and hash of the most recent block on the blockchain + * + * @param height return-by-reference variable to store the height in + * + * @return the hash + */ crypto::hash get_tail_id(uint64_t& height) const; + + /** + * @brief returns the difficulty target the next block to be added must meet + * + * @return the target + */ difficulty_type get_difficulty_for_next_block(); + + /** + * @brief adds a block to the blockchain + * + * Adds a new block to the blockchain. If the block's parent is not the + * current top of the blockchain, the block may be added to an alternate + * chain. If the block does not belong, is already in the blockchain + * or an alternate chain, or is invalid, return false. + * + * @param bl_ the block to be added + * @param bvc metadata about the block addition's success/failure + * + * @return true on successful addition to the blockchain, else false + */ bool add_new_block(const block& bl_, block_verification_context& bvc); + + /** + * @brief clears the blockchain and starts a new one + * + * @param b the first block in the new chain (the genesis block) + * + * @return true on success, else false + */ bool reset_and_set_genesis_block(const block& b); + + /** + * @brief creates a new block to mine against + * + * @param b return-by-reference block to be filled in + * @param miner_address address new coins for the block will go to + * @param di return-by-reference tells the miner what the difficulty target is + * @param height return-by-reference tells the miner what height it's mining against + * @param ex_nonce extra data to be added to the miner transaction's extra + * + * @return true if block template filled in successfully, else false + */ bool create_block_template(block& b, const account_public_address& miner_address, difficulty_type& di, uint64_t& height, const blobdata& ex_nonce); + + /** + * @brief checks if a block is known about with a given hash + * + * This function checks the main chain, alternate chains, and invalid blocks + * for a block with the given hash + * + * @param id the hash to search for + * + * @return true if the block is known, else false + */ bool have_block(const crypto::hash& id) const; + + /** + * @brief gets the total number of transactions on the main chain + * + * @return the number of transactions on the main chain + */ size_t get_total_transactions() const; + + /** + * @brief gets the hashes for a subset of the blockchain + * + * puts into list a list of hashes representing certain blocks + * from the blockchain in reverse chronological order + * + * the blocks chosen, at the time of this writing, are: + * the most recent 11 + * powers of 2 less recent from there, so 13, 17, 25, etc... + * + * @param ids return-by-reference list to put the resulting hashes in + * + * @return true + */ bool get_short_chain_history(std::list& ids) const; + + /** + * @brief get recent block hashes for a foreign chain + * + * Find the split point between us and foreign blockchain and return + * (by reference) the most recent common block hash along with up to + * BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT additional (more recent) hashes. + * + * @param qblock_ids the foreign chain's "short history" (see get_short_chain_history) + * @param resp return-by-reference the split height and subsequent blocks' hashes + * + * @return true if a block found in common, else false + */ bool find_blockchain_supplement(const std::list& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const; + + /** + * @brief find the most recent common point between ours and a foreign chain + * + * This function takes a list of block hashes from another node + * on the network to find where the split point is between us and them. + * This is used to see what to send another node that needs to sync. + * + * @param qblock_ids the foreign chain's "short history" (see get_short_chain_history) + * @param starter_offset return-by-reference the most recent common block's height + * + * @return true if a block found in common, else false + */ bool find_blockchain_supplement(const std::list& qblock_ids, uint64_t& starter_offset) const; + + /** + * @brief get recent blocks for a foreign chain + * + * This function gets recent blocks relative to a foreign chain, starting either at + * a requested height or whatever height is the most recent ours and the foreign + * chain have in common. + * + * @param req_start_block if non-zero, specifies a start point (otherwise find most recent commonality) + * @param qblock_ids the foreign chain's "short history" (see get_short_chain_history) + * @param blocks return-by-reference the blocks and their transactions + * @param total_height return-by-reference our current blockchain height + * @param start_height return-by-reference the height of the first block returned + * @param max_count the max number of blocks to get + * + * @return true if a block found in common or req_start_block specified, else false + */ bool find_blockchain_supplement(const uint64_t req_start_block, const std::list& qblock_ids, std::list > >& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count) const; + + /** + * @brief retrieves a set of blocks and their transactions, and possibly other transactions + * + * the request object encapsulates a list of block hashes and a (possibly empty) list of + * transaction hashes. for each block hash, the block is fetched along with all of that + * block's transactions. Any transactions requested separately are fetched afterwards. + * + * @param arg the request + * @param rsp return-by-reference the response to fill in + * + * @return true unless any blocks or transactions are missing + */ bool handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NOTIFY_RESPONSE_GET_OBJECTS::request& rsp); + + //FIXME: function declared, but never defined or used. Candidate for removal. bool handle_get_objects(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response& res); + + /** + * @brief gets random outputs to mix with + * + * This function takes an RPC request for outputs to mix with + * and creates an RPC response with the resultant output indices. + * + * Outputs to mix with are randomly selected from the utxo set + * for each output amount in the request. + * + * @param req the output amounts and number of mixins to select + * @param res return-by-reference the resultant output indices + * + * @return true + */ bool get_random_outs_for_amounts(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response& res) const; + + /** + * @brief gets the global indices for outputs from a given transaction + * + * This function gets the global indices for all outputs belonging + * to a specific transaction. + * + * @param tx_id the hash of the transaction to fetch indices for + * @param indexs return-by-reference the global indices for the transaction's outputs + * + * @return false if the transaction does not exist, or if no indices are found, otherwise true + */ bool get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vector& indexs) const; + + /** + * @brief stores the blockchain + * + * If Blockchain is handling storing of the blockchain (rather than BlockchainDB), + * this initiates a blockchain save. + * + * @return true unless saving the blockchain fails + */ bool store_blockchain(); + /** + * @brief validates a transaction's inputs + * + * validates a transaction's inputs as correctly used and not previously + * spent. also returns the hash and height of the most recent block + * which contains an output that was used as an input to the transaction. + * + * @param tx the transaction to validate + * @param pmax_used_block_height return-by-reference block height of most recent input + * @param max_used_block_id return-by-reference block hash of most recent input + * @param kept_by_block whether or not the transaction is from a previously-verified block + * + * @return false if any input is invalid, otherwise true + */ bool check_tx_inputs(const transaction& tx, uint64_t& pmax_used_block_height, crypto::hash& max_used_block_id, bool kept_by_block = false); + + /** + * @brief check that a transaction's outputs conform to current standards + * + * This function checks, for example at the time of this writing, that + * each output is of the form a * 10^b (phrased differently, that if + * written out would have only one non-zero digit in base 10). + * + * @param tx the transaction to check the outputs of + * + * @return false if any outputs do not conform, otherwise true + */ bool check_tx_outputs(const transaction& tx); + + /** + * @brief gets the blocksize limit based on recent blocks + * + * @return the limit + */ uint64_t get_current_cumulative_blocksize_limit() const; + + /** + * @brief checks if the blockchain is currently being stored + * + * Note: this should be meaningless in cases where Blockchain is not + * directly managing saving the blockchain to disk. + * + * @return true if Blockchain is having the chain stored currently, else false + */ bool is_storing_blockchain()const{return m_is_blockchain_storing;} + + /** + * @brief gets the difficulty of the block with a given height + * + * @param i the height + * + * @return the difficulty + */ uint64_t block_difficulty(uint64_t i) const; + /** + * @brief gets blocks based on a list of block hashes + * + * @tparam t_ids_container a standard-iterable container + * @tparam t_blocks_container a standard-iterable container + * @tparam t_missed_container a standard-iterable container + * @param block_ids a container of block hashes for which to get the corresponding blocks + * @param blocks return-by-reference a container to store result blocks in + * @param missed_bs return-by-reference a container to store missed blocks in + * + * @return false if an unexpected exception occurs, else true + */ template bool get_blocks(const t_ids_container& block_ids, t_blocks_container& blocks, t_missed_container& missed_bs) const; + /** + * @brief gets transactions based on a list of transaction hashes + * + * @tparam t_ids_container a standard-iterable container + * @tparam t_tx_container a standard-iterable container + * @tparam t_missed_container a standard-iterable container + * @param txs_ids a container of hashes for which to get the corresponding transactions + * @param txs return-by-reference a container to store result transactions in + * @param missed_txs return-by-reference a container to store missed transactions in + * + * @return false if an unexpected exception occurs, else true + */ template bool get_transactions(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs) const; + //debug functions + + /** + * @brief prints data about a snippet of the blockchain + * + * if start_index is greater than the blockchain height, do nothing + * + * @param start_index height on chain to start at + * @param end_index height on chain to end at + */ void print_blockchain(uint64_t start_index, uint64_t end_index) const; + + /** + * @brief prints every block's hash + * + * WARNING: This function will absolutely crush a terminal in prints, so + * it is recommended to redirect this output to a log file (or null sink + * if a log file is already set up, as should be the default) + */ void print_blockchain_index() const; + + /** + * @brief currently does nothing, candidate for removal + * + * @param file + */ void print_blockchain_outs(const std::string& file) const; + /** + * @brief check the blockchain against a set of checkpoints + * + * If a block fails a checkpoint and enforce is enabled, the blockchain + * will be rolled back to two blocks prior to that block. If enforce + * is disabled, as is currently the default case with DNS-based checkpoints, + * an error will be printed to the user but no other action will be taken. + * + * @param points the checkpoints to check against + * @param enforce whether or not to take action on failure + */ void check_against_checkpoints(const checkpoints& points, bool enforce); + + /** + * @brief configure whether or not to enforce DNS-based checkpoints + * + * @param enforce the new enforcement setting + */ void set_enforce_dns_checkpoints(bool enforce); + + /** + * @brief loads new checkpoints from a file and optionally from DNS + * + * @param file_path the path of the file to look for and load checkpoints from + * @param check_dns whether or not to check for new DNS-based checkpoints + * + * @return false if any enforced checkpoint type fails to load, otherwise true + */ bool update_checkpoints(const std::string& file_path, bool check_dns); + // user options, must be called before calling init() + + //FIXME: parameter names don't match function definition in .cpp file + /** + * @brief sets various performance options + * + * @param block_threads max number of threads when preparing blocks for addition + * @param blocks_per_sync number of blocks to cache before syncing to database + * @param sync_mode the ::blockchain_db_sync_mode to use + * @param fast_sync sync using built-in block hashes as trusted + */ void set_user_options(uint64_t block_threads, uint64_t blocks_per_sync, blockchain_db_sync_mode sync_mode, bool fast_sync); + /** + * @brief set whether or not to show/print time statistics + * + * @param stats the new time stats setting + */ void set_show_time_stats(bool stats) { m_show_time_stats = stats; } + /** + * @brief gets the hardfork voting state object + * + * @return the HardFork object + */ HardFork::State get_hard_fork_state() const; + + /** + * @brief gets the current hardfork version in use/voted for + * + * @return the version + */ uint8_t get_current_hard_fork_version() const { return m_hardfork->get_current_version(); } + + /** + * @brief returns the newest hardfork version known to the blockchain + * + * @return the version + */ uint8_t get_ideal_hard_fork_version() const { return m_hardfork->get_ideal_version(); } + + /** + * @brief returns the newest hardfork version voted to be enabled + * as of a certain height + * + * @param height the height for which to check version info + * + * @return the version + */ uint8_t get_ideal_hard_fork_version(uint64_t height) const { return m_hardfork->get_ideal_version(height); } + /** + * @brief get information about hardfork voting for a version + * + * @param version the version in question + * @param window the size of the voting window + * @param votes the number of votes to enable + * @param threshold the number of votes required to enable + * @param earliest_height the earliest height at which is allowed + * @param voting which version this node is voting for/using + * + * @return whether the version queried is enabled + */ bool get_hard_fork_voting_info(uint8_t version, uint32_t &window, uint32_t &votes, uint32_t &threshold, uint64_t &earliest_height, uint8_t &voting) const; + /** + * @brief remove transactions from the transaction pool (if present) + * + * @param txids a list of hashes of transactions to be removed + * + * @return false if any removals fail, otherwise true + */ bool flush_txes_from_pool(const std::list &txids); + /** + * @brief perform a check on all key images in the blockchain + * + * @param std::function the check to perform, pass/fail + * + * @return false if any key image fails the check, otherwise true + */ bool for_all_key_images(std::function) const; + + /** + * @brief perform a check on all blocks in the blockchain + * + * @param std::function the check to perform, pass/fail + * + * @return false if any block fails the check, otherwise true + */ bool for_all_blocks(std::function) const; + + /** + * @brief perform a check on all transactions in the blockchain + * + * @param std::function the check to perform, pass/fail + * + * @return false if any transaction fails the check, otherwise true + */ bool for_all_transactions(std::function) const; + + /** + * @brief perform a check on all outputs in the blockchain + * + * @param std::function the check to perform, pass/fail + * + * @return false if any output fails the check, otherwise true + */ bool for_all_outputs(std::function) const; + /** + * @brief get a reference to the BlockchainDB in use by Blockchain + * + * @return a reference to the BlockchainDB instance + */ BlockchainDB& get_db() { return *m_db; } + /** + * @brief get a number of outputs of a specific amount + * + * @param amount the amount + * @param offsets the indices (indexed to the amount) of the outputs + * @param outputs return-by-reference the outputs collected + * @param txs unused, candidate for removal + */ void output_scan_worker(const uint64_t amount,const std::vector &offsets, std::vector &outputs, std::unordered_map &txs) const; + /** + * @brief computes the "short" and "long" hashes for a set of blocks + * + * @param height the height of the first block + * @param blocks the blocks to be hashed + * @param map return-by-reference the hashes for each block + */ void block_longhash_worker(const uint64_t height, const std::vector &blocks, std::unordered_map &map) const; private: + + // TODO: evaluate whether or not each of these typedefs are left over from blockchain_storage typedef std::unordered_map blocks_by_id_index; + typedef std::unordered_map transactions_container; + typedef std::unordered_set key_images_container; + typedef std::vector blocks_container; + typedef std::unordered_map blocks_ext_by_hash; + typedef std::unordered_map blocks_by_hash; + typedef std::map>> outputs_container; //crypto::hash - tx hash, size_t - index of out in transaction + BlockchainDB* m_db; tx_memory_pool& m_tx_pool; + mutable epee::critical_section m_blockchain_lock; // TODO: add here reader/writer lock // main chain transactions_container m_transactions; size_t m_current_block_cumul_sz_limit; + // metadata containers std::unordered_map>> m_scan_table; std::unordered_map> m_check_tx_inputs_table; std::unordered_map m_blocks_longhash_table; @@ -243,39 +822,341 @@ namespace cryptonote bool m_testnet; + /** + * @brief collects the keys for all outputs being "spent" as an input + * + * This function makes sure that each "input" in an input (mixins) exists + * and collects the public key for each from the transaction it was included in + * via the visitor passed to it. + * + * If pmax_related_block_height is not NULL, its value is set to the height + * of the most recent block which contains an output used in the input set + * + * @tparam visitor_t a class encapsulating tx is unlocked and collect tx key + * @param tx_in_to_key a transaction input instance + * @param vis an instance of the visitor to use + * @param tx_prefix_hash the hash of the associated transaction_prefix + * @param pmax_related_block_height return-by-pointer the height of the most recent block in the input set + * + * @return false if any keys are not found or any inputs are not unlocked, otherwise true + */ template inline bool scan_outputkeys_for_indexes(const txin_to_key& tx_in_to_key, visitor_t &vis, const crypto::hash &tx_prefix_hash, uint64_t* pmax_related_block_height = NULL) const; + + /** + * @brief collect output public keys of a transaction input set + * + * This function locates all outputs associated with a given input set (mixins) + * and validates that they exist and are usable + * (unlocked, unspent is checked elsewhere). + * + * If pmax_related_block_height is not NULL, its value is set to the height + * of the most recent block which contains an output used in the input set + * + * @param txin the transaction input + * @param tx_prefix_hash the transaction prefix hash, for caching organization + * @param sig the input signature + * @param output_keys return-by-reference the public keys of the outputs in the input set + * @param pmax_related_block_height return-by-pointer the height of the most recent block in the input set + * + * @return false if any output is not yet unlocked, or is missing, otherwise true + */ bool check_tx_input(const txin_to_key& txin, const crypto::hash& tx_prefix_hash, const std::vector& sig, std::vector &output_keys, uint64_t* pmax_related_block_height); + + /** + * @brief validate a transaction's inputs and their keys + * + * This function validates transaction inputs and their keys. Previously + * it also performed double spend checking, but that has been moved to its + * own function. + * + * If pmax_related_block_height is not NULL, its value is set to the height + * of the most recent block which contains an output used in any input set + * + * Currently this function calls ring signature validation for each + * transaction. + * + * @param tx the transaction to validate + * @param pmax_related_block_height return-by-pointer the height of the most recent block in the input set + * + * @return false if any validation step fails, otherwise true + */ bool check_tx_inputs(const transaction& tx, uint64_t* pmax_used_block_height = NULL); + /** + * @brief performs a blockchain reorganization according to the longest chain rule + * + * This function aggregates all the actions necessary to switch to a + * newly-longer chain. If any step in the reorganization process fails, + * the blockchain is reverted to its previous state. + * + * @param alt_chain the chain to switch to + * @param discard_disconnected_chain whether or not to keep the old chain as an alternate + * + * @return false if the reorganization fails, otherwise true + */ bool switch_to_alternative_blockchain(std::list& alt_chain, bool discard_disconnected_chain); + + /** + * @brief removes the most recent block from the blockchain + * + * @return the block removed + */ block pop_block_from_blockchain(); + /** + * @brief validate and add a new block to the end of the blockchain + * + * This function is merely a convenience wrapper around the other + * of the same name. This one passes the block's hash to the other + * as well as the block and verification context. + * + * @param bl the block to be added + * @param bvc metadata concerning the block's validity + * + * @return true if the block was added successfully, otherwise false + */ bool handle_block_to_main_chain(const block& bl, block_verification_context& bvc); + + /** + * @brief validate and add a new block to the end of the blockchain + * + * When a block is given to Blockchain to be added to the blockchain, it + * is passed here if it is determined to belong at the end of the current + * chain. + * + * @param bl the block to be added + * @param id the hash of the block + * @param bvc metadata concerning the block's validity + * + * @return true if the block was added successfully, otherwise false + */ bool handle_block_to_main_chain(const block& bl, const crypto::hash& id, block_verification_context& bvc); + + /** + * @brief validate and add a new block to an alternate blockchain + * + * If a block to be added does not belong to the main chain, but there + * is an alternate chain to which it should be added, that is handled + * here. + * + * @param b the block to be added + * @param id the hash of the block + * @param bvc metadata concerning the block's validity + * + * @return true if the block was added successfully, otherwise false + */ bool handle_alternative_block(const block& b, const crypto::hash& id, block_verification_context& bvc); + + /** + * @brief gets the difficulty requirement for a new block on an alternate chain + * + * @param alt_chain the chain to be added to + * @param bei the block being added (and metadata, see ::block_extended_info) + * + * @return the difficulty requirement + */ difficulty_type get_next_difficulty_for_alternative_chain(const std::list& alt_chain, block_extended_info& bei) const; + + /** + * @brief sanity checks a miner transaction before validating an entire block + * + * This function merely checks basic things like the structure of the miner + * transaction, the unlock time, and that the amount doesn't overflow. + * + * @param b the block containing the miner transaction + * @param height the height at which the block will be added + * + * @return false if anything is found wrong with the miner transaction, otherwise true + */ bool prevalidate_miner_transaction(const block& b, uint64_t height); + + /** + * @brief validates a miner (coinbase) transaction + * + * This function makes sure that the miner calculated his reward correctly + * and that his miner transaction totals reward + fee. + * + * @param b the block containing the miner transaction to be validated + * @param cumulative_block_size the block's size + * @param fee the total fees collected in the block + * @param base_reward return-by-reference the new block's generated coins + * @param already_generated_coins the amount of currency generated prior to this block + * @param partial_block_reward return-by-reference true if miner accepted only partial reward + * + * @return false if anything is found wrong with the miner transaction, otherwise true + */ bool validate_miner_transaction(const block& b, size_t cumulative_block_size, uint64_t fee, uint64_t& base_reward, uint64_t already_generated_coins, bool &partial_block_reward); + + //FIXME: function declared but neither defined nor used, candidate for removal bool validate_transaction(const block& b, uint64_t height, const transaction& tx); + + /** + * @brief reverts the blockchain to its previous state following a failed switch + * + * If Blockchain fails to switch to an alternate chain when it means + * to do so, this function reverts the blockchain to how it was before + * the attempted switch. + * + * @param original_chain the chain to switch back to + * @param rollback_height the height to revert to before appending the original chain + * + * @return false if something goes wrong with reverting (very bad), otherwise true + */ bool rollback_blockchain_switching(std::list& original_chain, uint64_t rollback_height); + + //FIXME: function declared but neither defined nor used, candidate for removal, + // remnant from old blockchain_storage implementation bool add_transaction_from_block(const transaction& tx, const crypto::hash& tx_id, const crypto::hash& bl_id, uint64_t bl_height); + + //FIXME: function declared but neither defined nor used, candidate for removal, + // remnant from old blockchain_storage implementation bool push_transaction_to_global_outs_index(const transaction& tx, const crypto::hash& tx_id, std::vector& global_indexes); + + //FIXME: function declared but neither defined nor used, candidate for removal, + // remnant from old blockchain_storage implementation bool pop_transaction_from_global_index(const transaction& tx, const crypto::hash& tx_id); + + /** + * @brief gets recent block sizes for median calculation + * + * get the block sizes of the last blocks, and return by reference . + * + * @param sz return-by-reference the list of sizes + * @param count the number of blocks to get sizes for + */ void get_last_n_blocks_sizes(std::vector& sz, size_t count) const; + + /** + * @brief adds the given output to the requested set of random outputs + * + * @param result_outs return-by-reference the set the output is to be added to + * @param amount the output amount + * @param i the output index (indexed to amount) + */ void add_out_to_get_random_outs(COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& result_outs, uint64_t amount, size_t i) const; + + /** + * @brief checks if a transaction is unlocked (its outputs spendable) + * + * This function checks to see if a transaction is unlocked. + * unlock_time is either a block index or a unix time. + * + * @param unlock_time the unlock parameter (height or time) + * + * @return true if spendable, otherwise false + */ bool is_tx_spendtime_unlocked(uint64_t unlock_time) const; + + /** + * @brief stores an invalid block in a separate container + * + * Storing invalid blocks allows quick dismissal of the same block + * if it is seen again. + * + * @param bl the invalid block + * @param h the block's hash + * + * @return false if the block cannot be stored for some reason, otherwise true + */ bool add_block_as_invalid(const block& bl, const crypto::hash& h); + + /** + * @brief stores an invalid block in a separate container + * + * Storing invalid blocks allows quick dismissal of the same block + * if it is seen again. + * + * @param bei the invalid block (see ::block_extended_info) + * @param h the block's hash + * + * @return false if the block cannot be stored for some reason, otherwise true + */ bool add_block_as_invalid(const block_extended_info& bei, const crypto::hash& h); + + /** + * @brief checks a block's timestamp + * + * This function grabs the timestamps from the most recent blocks, + * where n = BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW. If there are not those many + * blocks in the blockchain, the timestap is assumed to be valid. If there + * are, this function returns: + * true if the block's timestamp is not less than the timestamp of the + * median of the selected blocks + * false otherwise + * + * @param b the block to be checked + * + * @return true if the block's timestamp is valid, otherwise false + */ bool check_block_timestamp(const block& b) const; + + /** + * @brief checks a block's timestamp + * + * If the block is not more recent than the median of the recent + * timestamps passed here, it is considered invalid. + * + * @param timestamps a list of the most recent timestamps to check against + * @param b the block to be checked + * + * @return true if the block's timestamp is valid, otherwise false + */ bool check_block_timestamp(std::vector& timestamps, const block& b) const; + + /** + * @brief get the "adjusted time" + * + * Currently this simply returns the current time according to the + * user's machine. + * + * @return the current time + */ uint64_t get_adjusted_time() const; + + /** + * @brief finish an alternate chain's timestamp window from the main chain + * + * for an alternate chain, get the timestamps from the main chain to complete + * the needed number of timestamps for the BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW. + * + * @param start_height the alternate chain's attachment height to the main chain + * @param timestamps return-by-value the timestamps set to be populated + * + * @return true unless start_height is greater than the current blockchain height + */ bool complete_timestamps_vector(uint64_t start_height, std::vector& timestamps); + + /** + * @brief calculate the block size limit for the next block to be added + * + * @return true + */ bool update_next_cumulative_size_limit(); void return_tx_to_pool(const std::vector &txs); + /** + * @brief make sure a transaction isn't attempting a double-spend + * + * @param tx the transaction to check + * @param keys_this_block a cumulative list of spent keys for the current block + * + * @return false if a double spend was detected, otherwise true + */ bool check_for_double_spend(const transaction& tx, key_images_container& keys_this_block) const; + + //FIXME: function declared but neither defined nor used, candidate for removal, void get_timestamp_and_difficulty(uint64_t ×tamp, difficulty_type &difficulty, const int offset) const; + + /** + * @brief validates a transaction input's ring signature + * + * @param tx_prefix_hash the transaction prefix' hash + * @param key_image the key image generated from the true input + * @param pubkeys the public keys for each input in the ring signature + * @param sig the signature generated for each input in the ring signature + * @param result false if the ring signature is invalid, otherwise true + */ void check_ring_signature(const crypto::hash &tx_prefix_hash, const crypto::key_image &key_image, const std::vector &pubkeys, const std::vector &sig, uint64_t &result); From ab0ed14a411b31ced641241eea3a68567eaa4474 Mon Sep 17 00:00:00 2001 From: Thomas Winget Date: Thu, 24 Sep 2015 11:26:17 -0400 Subject: [PATCH 24/47] doxygen include private and static members This can be easily reverted or removed before this branch is merged, so I'm going ahead and committing these couple changes. --- Doxyfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Doxyfile b/Doxyfile index a70ef812..a2700b34 100644 --- a/Doxyfile +++ b/Doxyfile @@ -409,13 +409,13 @@ LOOKUP_CACHE_SIZE = 0 # normally produced when WARNINGS is set to YES. # The default value is: NO. -EXTRACT_ALL = NO +EXTRACT_ALL = YES # If the EXTRACT_PRIVATE tag is set to YES all private members of a class will # be included in the documentation. # The default value is: NO. -EXTRACT_PRIVATE = NO +EXTRACT_PRIVATE = YES # If the EXTRACT_PACKAGE tag is set to YES all members with package or internal # scope will be included in the documentation. @@ -427,7 +427,7 @@ EXTRACT_PACKAGE = NO # included in the documentation. # The default value is: NO. -EXTRACT_STATIC = NO +EXTRACT_STATIC = YES # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined # locally in source files will be included in the documentation. If set to NO @@ -452,7 +452,7 @@ EXTRACT_LOCAL_METHODS = NO # are hidden. # The default value is: NO. -EXTRACT_ANON_NSPACES = NO +EXTRACT_ANON_NSPACES = YES # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all # undocumented members inside documented classes or files. If set to NO these From 89c24ac2be8b00474cd3f0973ee458a6e80f76f3 Mon Sep 17 00:00:00 2001 From: Thomas Winget Date: Wed, 7 Oct 2015 22:25:06 -0400 Subject: [PATCH 25/47] Remove unnecessary or defunct code --- src/cryptonote_core/blockchain.cpp | 1 - src/cryptonote_core/blockchain.h | 21 --------------------- 2 files changed, 22 deletions(-) diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index a55a435b..2be4f6ae 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -352,7 +352,6 @@ bool Blockchain::init(BlockchainDB* db, const bool testnet, const cryptonote::te // we only need 1 m_async_pool.create_thread(boost::bind(&boost::asio::io_service::run, &m_async_service)); - //TODO: move this block into separate functions? #if defined(PER_BLOCK_CHECKPOINT) if (!fakechain) load_compiled_in_block_hashes(); diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 98a58c54..94def1aa 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -423,9 +423,6 @@ namespace cryptonote */ bool handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NOTIFY_RESPONSE_GET_OBJECTS::request& rsp); - //FIXME: function declared, but never defined or used. Candidate for removal. - bool handle_get_objects(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response& res); - /** * @brief gets random outputs to mix with * @@ -988,9 +985,6 @@ namespace cryptonote */ bool validate_miner_transaction(const block& b, size_t cumulative_block_size, uint64_t fee, uint64_t& base_reward, uint64_t already_generated_coins, bool &partial_block_reward); - //FIXME: function declared but neither defined nor used, candidate for removal - bool validate_transaction(const block& b, uint64_t height, const transaction& tx); - /** * @brief reverts the blockchain to its previous state following a failed switch * @@ -1005,18 +999,6 @@ namespace cryptonote */ bool rollback_blockchain_switching(std::list& original_chain, uint64_t rollback_height); - //FIXME: function declared but neither defined nor used, candidate for removal, - // remnant from old blockchain_storage implementation - bool add_transaction_from_block(const transaction& tx, const crypto::hash& tx_id, const crypto::hash& bl_id, uint64_t bl_height); - - //FIXME: function declared but neither defined nor used, candidate for removal, - // remnant from old blockchain_storage implementation - bool push_transaction_to_global_outs_index(const transaction& tx, const crypto::hash& tx_id, std::vector& global_indexes); - - //FIXME: function declared but neither defined nor used, candidate for removal, - // remnant from old blockchain_storage implementation - bool pop_transaction_from_global_index(const transaction& tx, const crypto::hash& tx_id); - /** * @brief gets recent block sizes for median calculation * @@ -1145,9 +1127,6 @@ namespace cryptonote */ bool check_for_double_spend(const transaction& tx, key_images_container& keys_this_block) const; - //FIXME: function declared but neither defined nor used, candidate for removal, - void get_timestamp_and_difficulty(uint64_t ×tamp, difficulty_type &difficulty, const int offset) const; - /** * @brief validates a transaction input's ring signature * From 1b0c98e7e9951fbeff8b713dd80b040e628059ff Mon Sep 17 00:00:00 2001 From: Thomas Winget Date: Wed, 7 Oct 2015 22:28:31 -0400 Subject: [PATCH 26/47] doxygen documentation for checkpoints.{h,cpp} All functions in src/cryptonote_core/checkpoints.h are now documented in doxygen style. checkpoints.cpp has been reviewed, one function has been marked for discussion on correctness. --- src/cryptonote_core/checkpoints.cpp | 5 +- src/cryptonote_core/checkpoints.h | 100 +++++++++++++++++++++++++++- 2 files changed, 99 insertions(+), 6 deletions(-) diff --git a/src/cryptonote_core/checkpoints.cpp b/src/cryptonote_core/checkpoints.cpp index 24d066da..42f1163f 100644 --- a/src/cryptonote_core/checkpoints.cpp +++ b/src/cryptonote_core/checkpoints.cpp @@ -84,10 +84,7 @@ namespace cryptonote return check_block(height, h, ignored); } //--------------------------------------------------------------------------- - // this basically says if the blockchain is smaller than the first - // checkpoint then alternate blocks are allowed. Alternatively, if the - // last checkpoint *before* the end of the current chain is also before - // the block to be added, then this is fine. + //FIXME: is this the desired behavior? bool checkpoints::is_alternative_block_allowed(uint64_t blockchain_height, uint64_t block_height) const { if (0 == block_height) diff --git a/src/cryptonote_core/checkpoints.h b/src/cryptonote_core/checkpoints.h index 00a53ec2..56d57db4 100644 --- a/src/cryptonote_core/checkpoints.h +++ b/src/cryptonote_core/checkpoints.h @@ -36,19 +36,115 @@ namespace cryptonote { + /** + * @brief A container for blockchain checkpoints + * + * A checkpoint is a pre-defined hash for the block at a given height. + * Some of these are compiled-in, while others can be loaded at runtime + * either from a json file or via DNS from a checkpoint-hosting server. + */ class checkpoints { public: + + /** + * @brief default constructor + */ checkpoints(); + + /** + * @brief adds a checkpoint to the container + * + * @param height the height of the block the checkpoint is for + * @param hash_str the hash of the block, as a string + * + * @return false if parsing the hash fails, or if the height is a duplicate + * AND the existing checkpoint hash does not match the new one, + * otherwise returns true + */ bool add_checkpoint(uint64_t height, const std::string& hash_str); + + /** + * @brief checks if there is a checkpoint in the future + * + * This function checks if the height passed is lower than the highest + * checkpoint. + * + * @param height the height to check against + * + * @return false if no checkpoints, otherwise returns whether or not + * the height passed is lower than the highest checkpoint. + */ bool is_in_checkpoint_zone(uint64_t height) const; - bool check_block(uint64_t height, const crypto::hash& h) const; + + /** + * @brief checks if the given height and hash agree with the checkpoints + * + * This function checks if the given height and hash exist in the + * checkpoints container. If so, it returns whether or not the passed + * parameters match the stored values. + * + * @param height the height to be checked + * @param h the hash to be checked + * @param is_a_checkpoint return-by-reference if there is a checkpoint at the given height + * + * @return true if there is no checkpoint at the given height, + * true if the passed parameters match the stored checkpoint, + * false otherwise + */ bool check_block(uint64_t height, const crypto::hash& h, bool& is_a_checkpoint) const; + + /** + * @overload + */ + bool check_block(uint64_t height, const crypto::hash& h) const; + + /** + * @brief checks if alternate chain blocks should be kept for a given height + * + * this basically says if the blockchain is smaller than the first + * checkpoint then alternate blocks are allowed. Alternatively, if the + * last checkpoint *before* the end of the current chain is also before + * the block to be added, then this is fine. + * + * @param blockchain_height the current blockchain height + * @param block_height the height of the block to be added as alternate + * + * @return true if alternate blocks are allowed given the parameters, + * otherwise false + */ bool is_alternative_block_allowed(uint64_t blockchain_height, uint64_t block_height) const; + + /** + * @brief gets the highest checkpoint height + * + * @return the height of the highest checkpoint + */ uint64_t get_max_height() const; + + /** + * @brief gets the checkpoints container + * + * @return a const reference to the checkpoints container + */ const std::map& get_points() const; + + /** + * @brief checks if our checkpoints container conflicts with another + * + * A conflict refers to a case where both checkpoint sets have a checkpoint + * for a specific height but their hashes for that height do not match. + * + * @param other the other checkpoints instance to check against + * + * @return false if any conflict is found, otherwise true + */ bool check_for_conflicts(const checkpoints& other) const; + + private: - std::map m_points; + + std::map m_points; //!< the checkpoints container + }; } From 540a76c5c29aa0742c00af4391ce718e7ec69c97 Mon Sep 17 00:00:00 2001 From: Thomas Winget Date: Wed, 7 Oct 2015 22:28:37 -0400 Subject: [PATCH 27/47] Move checkpoint functions into checkpoints class The functions in src/cryptonote_core/checkpoints_create.{h,cpp} should be member functions of the checkpoints class, if nothing else for the sake of keeping their documentation together. This commit covers moving those functions to be member functions of the checkpoints class as well as documenting those functions. --- src/cryptonote_core/CMakeLists.txt | 2 - src/cryptonote_core/blockchain.cpp | 10 +- src/cryptonote_core/blockchain_storage.cpp | 8 +- src/cryptonote_core/checkpoints.cpp | 246 +++++++++++++++++- src/cryptonote_core/checkpoints.h | 87 ++++++- src/cryptonote_core/checkpoints_create.cpp | 280 --------------------- src/cryptonote_core/checkpoints_create.h | 48 ---- src/cryptonote_core/cryptonote_core.cpp | 4 +- src/daemon/core.h | 1 - 9 files changed, 327 insertions(+), 359 deletions(-) delete mode 100644 src/cryptonote_core/checkpoints_create.cpp delete mode 100644 src/cryptonote_core/checkpoints_create.h diff --git a/src/cryptonote_core/CMakeLists.txt b/src/cryptonote_core/CMakeLists.txt index 88eea1d7..20535679 100644 --- a/src/cryptonote_core/CMakeLists.txt +++ b/src/cryptonote_core/CMakeLists.txt @@ -31,7 +31,6 @@ set(cryptonote_core_sources blockchain_storage.cpp blockchain.cpp checkpoints.cpp - checkpoints_create.cpp cryptonote_basic_impl.cpp cryptonote_core.cpp cryptonote_format_utils.cpp @@ -49,7 +48,6 @@ set(cryptonote_core_private_headers blockchain_storage_boost_serialization.h blockchain.h checkpoints.h - checkpoints_create.h connection_context.h cryptonote_basic.h cryptonote_basic_impl.h diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 2be4f6ae..dec7b8e2 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -49,7 +49,7 @@ #include "common/boost_serialization_helper.h" #include "warnings.h" #include "crypto/hash.h" -#include "cryptonote_core/checkpoints_create.h" +#include "cryptonote_core/checkpoints.h" #include "cryptonote_core/cryptonote_core.h" #if defined(PER_BLOCK_CHECKPOINT) #include "blocks/blocks.h" @@ -2860,16 +2860,16 @@ void Blockchain::check_against_checkpoints(const checkpoints& points, bool enfor // with an existing checkpoint. bool Blockchain::update_checkpoints(const std::string& file_path, bool check_dns) { - if (!cryptonote::load_checkpoints_from_json(m_checkpoints, file_path)) + if (!m_checkpoints.load_checkpoints_from_json(file_path)) { - return false; + return false; } // if we're checking both dns and json, load checkpoints from dns. // if we're not hard-enforcing dns checkpoints, handle accordingly if (m_enforce_dns_checkpoints && check_dns) { - if (!cryptonote::load_checkpoints_from_dns(m_checkpoints)) + if (!m_checkpoints.load_checkpoints_from_dns()) { return false; } @@ -2877,7 +2877,7 @@ bool Blockchain::update_checkpoints(const std::string& file_path, bool check_dns else if (check_dns) { checkpoints dns_points; - cryptonote::load_checkpoints_from_dns(dns_points); + dns_points.load_checkpoints_from_dns(); if (m_checkpoints.check_for_conflicts(dns_points)) { check_against_checkpoints(dns_points, false); diff --git a/src/cryptonote_core/blockchain_storage.cpp b/src/cryptonote_core/blockchain_storage.cpp index e1b89f88..a829b7cb 100644 --- a/src/cryptonote_core/blockchain_storage.cpp +++ b/src/cryptonote_core/blockchain_storage.cpp @@ -48,7 +48,7 @@ #include "common/boost_serialization_helper.h" #include "warnings.h" #include "crypto/hash.h" -#include "cryptonote_core/checkpoints_create.h" +#include "cryptonote_core/checkpoints.h" //#include "serialization/json_archive.h" #include "../../contrib/otshell_utils/utils.hpp" #include "../../src/p2p/data_logger.hpp" @@ -1854,7 +1854,7 @@ void blockchain_storage::check_against_checkpoints(const checkpoints& points, bo // with an existing checkpoint. bool blockchain_storage::update_checkpoints(const std::string& file_path, bool check_dns) { - if (!cryptonote::load_checkpoints_from_json(m_checkpoints, file_path)) + if (!m_checkpoints.load_checkpoints_from_json(file_path)) { return false; } @@ -1863,7 +1863,7 @@ bool blockchain_storage::update_checkpoints(const std::string& file_path, bool c // if we're not hard-enforcing dns checkpoints, handle accordingly if (m_enforce_dns_checkpoints && check_dns) { - if (!cryptonote::load_checkpoints_from_dns(m_checkpoints)) + if (!m_checkpoints.load_checkpoints_from_dns()) { return false; } @@ -1871,7 +1871,7 @@ bool blockchain_storage::update_checkpoints(const std::string& file_path, bool c else if (check_dns) { checkpoints dns_points; - cryptonote::load_checkpoints_from_dns(dns_points, m_testnet); + dns_points.load_checkpoints_from_dns(m_testnet); if (m_checkpoints.check_for_conflicts(dns_points)) { check_against_checkpoints(dns_points, false); diff --git a/src/cryptonote_core/checkpoints.cpp b/src/cryptonote_core/checkpoints.cpp index 42f1163f..c038a480 100644 --- a/src/cryptonote_core/checkpoints.cpp +++ b/src/cryptonote_core/checkpoints.cpp @@ -1,21 +1,21 @@ // Copyright (c) 2014-2016, The Monero Project -// +// // All rights reserved. -// +// // Redistribution and use in source and binary forms, with or without modification, are // permitted provided that the following conditions are met: -// +// // 1. Redistributions of source code must retain the above copyright notice, this list of // conditions and the following disclaimer. -// +// // 2. Redistributions in binary form must reproduce the above copyright notice, this list // of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. -// +// // 3. Neither the name of the copyright holder nor the names of its contributors may be // used to endorse or promote products derived from this software without specific // prior written permission. -// +// // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL @@ -25,14 +25,44 @@ // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// +// // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #include "include_base_utils.h" + using namespace epee; #include "checkpoints.h" +#include "common/dns_utils.h" +#include "include_base_utils.h" +#include +#include + +namespace +{ + bool dns_records_match(const std::vector& a, const std::vector& b) + { + if (a.size() != b.size()) return false; + + for (const auto& record_in_a : a) + { + bool ok = false; + for (const auto& record_in_b : b) + { + if (record_in_a == record_in_b) + { + ok = true; + break; + } + } + if (!ok) return false; + } + + return true; + } +} // anonymous namespace + namespace cryptonote { //--------------------------------------------------------------------------- @@ -125,4 +155,206 @@ namespace cryptonote } return true; } + + bool checkpoints::init_default_checkpoints() + { + ADD_CHECKPOINT(1, "771fbcd656ec1464d3a02ead5e18644030007a0fc664c0a964d30922821a8148"); + ADD_CHECKPOINT(10, "c0e3b387e47042f72d8ccdca88071ff96bff1ac7cde09ae113dbb7ad3fe92381"); + ADD_CHECKPOINT(100, "ac3e11ca545e57c49fca2b4e8c48c03c23be047c43e471e1394528b1f9f80b2d"); + ADD_CHECKPOINT(1000, "5acfc45acffd2b2e7345caf42fa02308c5793f15ec33946e969e829f40b03876"); + ADD_CHECKPOINT(10000, "c758b7c81f928be3295d45e230646de8b852ec96a821eac3fea4daf3fcac0ca2"); + ADD_CHECKPOINT(22231, "7cb10e29d67e1c069e6e11b17d30b809724255fee2f6868dc14cfc6ed44dfb25"); + ADD_CHECKPOINT(29556, "53c484a8ed91e4da621bb2fa88106dbde426fe90d7ef07b9c1e5127fb6f3a7f6"); + ADD_CHECKPOINT(50000, "0fe8758ab06a8b9cb35b7328fd4f757af530a5d37759f9d3e421023231f7b31c"); + ADD_CHECKPOINT(80000, "a62dcd7b536f22e003ebae8726e9e7276f63d594e264b6f0cd7aab27b66e75e3"); + ADD_CHECKPOINT(202612, "bbd604d2ba11ba27935e006ed39c9bfdd99b76bf4a50654bc1e1e61217962698"); + ADD_CHECKPOINT(202613, "e2aa337e78df1f98f462b3b1e560c6b914dec47b610698b7b7d1e3e86b6197c2"); + ADD_CHECKPOINT(202614, "c29e3dc37d8da3e72e506e31a213a58771b24450144305bcba9e70fa4d6ea6fb"); + ADD_CHECKPOINT(205000, "5d3d7a26e6dc7535e34f03def711daa8c263785f73ec1fadef8a45880fde8063"); + ADD_CHECKPOINT(220000, "9613f455933c00e3e33ac315cc6b455ee8aa0c567163836858c2d9caff111553"); + ADD_CHECKPOINT(230300, "bae7a80c46859db355556e3a9204a337ae8f24309926a1312323fdecf1920e61"); + ADD_CHECKPOINT(230700, "93e631240ceac831da1aebfc5dac8f722c430463024763ebafa888796ceaeedf"); + ADD_CHECKPOINT(231350, "b5add137199b820e1ea26640e5c3e121fd85faa86a1e39cf7e6cc097bdeb1131"); + ADD_CHECKPOINT(232150, "955de8e6b6508af2c24f7334f97beeea651d78e9ade3ab18fec3763be3201aa8"); + ADD_CHECKPOINT(249380, "654fb0a81ce3e5caf7e3264a70f447d4bd07586c08fa50f6638cc54da0a52b2d"); + ADD_CHECKPOINT(460000, "75037a7aed3e765db96c75bcf908f59d690a5f3390baebb9edeafd336a1c4831"); + ADD_CHECKPOINT(500000, "2428f0dbe49796be05ed81b347f53e1f7f44aed0abf641446ec2b94cae066b02"); + ADD_CHECKPOINT(600000, "f5828ebf7d7d1cb61762c4dfe3ccf4ecab2e1aad23e8113668d981713b7a54c5"); + ADD_CHECKPOINT(700000, "12be9b3d210b93f574d2526abb9c1ab2a881b479131fd0d4f7dac93875f503cd"); + ADD_CHECKPOINT(825000, "56503f9ad766774b575be3aff73245e9d159be88132c93d1754764f28da2ff60"); + ADD_CHECKPOINT(900000, "d9958d0e7dcf91a5a7b11de225927bf7efc6eb26240315ce12372be902cc1337"); + ADD_CHECKPOINT(913193, "5292d5d56f6ba4de33a58d9a34d263e2cb3c6fee0aed2286fd4ac7f36d53c85f"); + + return true; + } + + bool checkpoints::load_checkpoints_from_json(const std::string json_hashfile_fullpath) + { + boost::system::error_code errcode; + if (! (boost::filesystem::exists(json_hashfile_fullpath, errcode))) + { + LOG_PRINT_L1("Blockchain checkpoints file not found"); + return true; + } + + LOG_PRINT_L1("Adding checkpoints from blockchain hashfile"); + + uint64_t prev_max_height = get_max_height(); + LOG_PRINT_L1("Hard-coded max checkpoint height is " << prev_max_height); + t_hash_json hashes; + epee::serialization::load_t_from_json_file(hashes, json_hashfile_fullpath); + for (std::vector::const_iterator it = hashes.hashlines.begin(); it != hashes.hashlines.end(); ) + { + uint64_t height; + height = it->height; + if (height <= prev_max_height) { + LOG_PRINT_L1("ignoring checkpoint height " << height); + } else { + std::string blockhash = it->hash; + LOG_PRINT_L1("Adding checkpoint height " << height << ", hash=" << blockhash); + ADD_CHECKPOINT(height, blockhash); + } + ++it; + } + + return true; + } + + bool checkpoints::load_checkpoints_from_dns(bool testnet) + { + // All four MoneroPulse domains have DNSSEC on and valid + static const std::vector dns_urls = { "checkpoints.moneropulse.se" + , "checkpoints.moneropulse.org" + , "checkpoints.moneropulse.net" + , "checkpoints.moneropulse.co" + }; + + static const std::vector testnet_dns_urls = { "testpoints.moneropulse.se" + , "testpoints.moneropulse.org" + , "testpoints.moneropulse.net" + , "testpoints.moneropulse.co" + }; + + std::vector > records; + records.resize(dns_urls.size()); + + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution dis(0, dns_urls.size() - 1); + size_t first_index = dis(gen); + + bool avail, valid; + size_t cur_index = first_index; + do + { + std::string url; + if (testnet) + { + url = testnet_dns_urls[cur_index]; + } + else + { + url = dns_urls[cur_index]; + } + + records[cur_index] = tools::DNSResolver::instance().get_txt_record(url, avail, valid); + if (!avail) + { + records[cur_index].clear(); + LOG_PRINT_L2("DNSSEC not available for checkpoint update at URL: " << url << ", skipping."); + } + if (!valid) + { + records[cur_index].clear(); + LOG_PRINT_L2("DNSSEC validation failed for checkpoint update at URL: " << url << ", skipping."); + } + + cur_index++; + if (cur_index == dns_urls.size()) + { + cur_index = 0; + } + records[cur_index].clear(); + } while (cur_index != first_index); + + size_t num_valid_records = 0; + + for( const auto& record_set : records) + { + if (record_set.size() != 0) + { + num_valid_records++; + } + } + + if (num_valid_records < 2) + { + LOG_PRINT_L0("WARNING: no two valid MoneroPulse DNS checkpoint records were received"); + return true; + } + + int good_records_index = -1; + for (size_t i = 0; i < records.size() - 1; ++i) + { + if (records[i].size() == 0) continue; + + for (size_t j = i + 1; j < records.size(); ++j) + { + if (dns_records_match(records[i], records[j])) + { + good_records_index = i; + break; + } + } + if (good_records_index >= 0) break; + } + + if (good_records_index < 0) + { + LOG_PRINT_L0("WARNING: no two MoneroPulse DNS checkpoint records matched"); + return true; + } + + for (auto& record : records[good_records_index]) + { + auto pos = record.find(":"); + if (pos != std::string::npos) + { + uint64_t height; + crypto::hash hash; + + // parse the first part as uint64_t, + // if this fails move on to the next record + std::stringstream ss(record.substr(0, pos)); + if (!(ss >> height)) + { + continue; + } + + // parse the second part as crypto::hash, + // if this fails move on to the next record + std::string hashStr = record.substr(pos + 1); + if (!epee::string_tools::parse_tpod_from_hex_string(hashStr, hash)) + { + continue; + } + + ADD_CHECKPOINT(height, hashStr); + } + } + return true; + } + + bool checkpoints::load_new_checkpoints(const std::string json_hashfile_fullpath, bool testnet, bool dns) + { + bool result; + + result = load_checkpoints_from_json(json_hashfile_fullpath); + if (dns) + { + result &= load_checkpoints_from_dns(testnet); + } + + return result; + } } diff --git a/src/cryptonote_core/checkpoints.h b/src/cryptonote_core/checkpoints.h index 56d57db4..71727753 100644 --- a/src/cryptonote_core/checkpoints.h +++ b/src/cryptonote_core/checkpoints.h @@ -1,21 +1,21 @@ // Copyright (c) 2014-2016, The Monero Project -// +// // All rights reserved. -// +// // Redistribution and use in source and binary forms, with or without modification, are // permitted provided that the following conditions are met: -// +// // 1. Redistributions of source code must retain the above copyright notice, this list of // conditions and the following disclaimer. -// +// // 2. Redistributions in binary form must reproduce the above copyright notice, this list // of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. -// +// // 3. Neither the name of the copyright holder nor the names of its contributors may be // used to endorse or promote products derived from this software without specific // prior written permission. -// +// // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL @@ -25,13 +25,18 @@ // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// +// // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #pragma once #include #include #include "cryptonote_basic_impl.h" +#include "misc_log_ex.h" +#include "storages/portable_storage_template_helper.h" // epee json include + +#define ADD_CHECKPOINT(h, hash) CHECK_AND_ASSERT(add_checkpoint(h, hash), false); +#define JSON_HASH_FILE_NAME "checkpoints.json" namespace cryptonote @@ -88,8 +93,8 @@ namespace cryptonote * @param h the hash to be checked * @param is_a_checkpoint return-by-reference if there is a checkpoint at the given height * - * @return true if there is no checkpoint at the given height, - * true if the passed parameters match the stored checkpoint, + * @return true if there is no checkpoint at the given height, + * true if the passed parameters match the stored checkpoint, * false otherwise */ bool check_block(uint64_t height, const crypto::hash& h, bool& is_a_checkpoint) const; @@ -110,7 +115,7 @@ namespace cryptonote * @param blockchain_height the current blockchain height * @param block_height the height of the block to be added as alternate * - * @return true if alternate blocks are allowed given the parameters, + * @return true if alternate blocks are allowed given the parameters, * otherwise false */ bool is_alternative_block_allowed(uint64_t blockchain_height, uint64_t block_height) const; @@ -141,9 +146,71 @@ namespace cryptonote */ bool check_for_conflicts(const checkpoints& other) const; + /** + * @brief loads the default main chain checkpoints + * + * @return true unless adding a checkpoint fails + */ + bool init_default_checkpoints(); + + /** + * @brief load new checkpoints + * + * Loads new checkpoints from the specified json file, as well as + * (optionally) from DNS. + * + * @param json_hashfile_fullpath path to the json checkpoints file + * @param testnet whether to load testnet checkpoints or mainnet + * @param dns whether or not to load DNS checkpoints + * + * @return true if loading successful and no conflicts + */ + bool load_new_checkpoints(const std::string json_hashfile_fullpath, bool testnet=false, bool dns=true); + + /** + * @brief load new checkpoints from json + * + * @param json_hashfile_fullpath path to the json checkpoints file + * + * @return true if loading successful and no conflicts + */ + bool load_checkpoints_from_json(const std::string json_hashfile_fullpath); + + /** + * @brief load new checkpoints from DNS + * + * @param testnet whether to load testnet checkpoints or mainnet + * + * @return true if loading successful and no conflicts + */ + bool load_checkpoints_from_dns(bool testnet = false); private: + + /** + * @brief struct for loading a checkpoint from json + */ + struct t_hashline + { + uint64_t height; //!< the height of the checkpoint + std::string hash; //!< the hash for the checkpoint + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(height) + KV_SERIALIZE(hash) + END_KV_SERIALIZE_MAP() + }; + + /** + * @brief struct for loading many checkpoints from json + */ + struct t_hash_json { + std::vector hashlines; //!< the checkpoint lines from the file + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(hashlines) + END_KV_SERIALIZE_MAP() + }; + std::map m_points; //!< the checkpoints container }; diff --git a/src/cryptonote_core/checkpoints_create.cpp b/src/cryptonote_core/checkpoints_create.cpp deleted file mode 100644 index 41f2321d..00000000 --- a/src/cryptonote_core/checkpoints_create.cpp +++ /dev/null @@ -1,280 +0,0 @@ -// Copyright (c) 2014-2016, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers - -#include "checkpoints_create.h" -#include "common/dns_utils.h" -#include "include_base_utils.h" -#include -#include -#include "storages/portable_storage_template_helper.h" // epee json include - -namespace -{ - bool dns_records_match(const std::vector& a, const std::vector& b) - { - if (a.size() != b.size()) return false; - - for (const auto& record_in_a : a) - { - bool ok = false; - for (const auto& record_in_b : b) - { - if (record_in_a == record_in_b) - { - ok = true; - break; - } - } - if (!ok) return false; - } - - return true; - } -} // anonymous namespace - -namespace cryptonote -{ - -struct t_hashline -{ - uint64_t height; - std::string hash; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(height) - KV_SERIALIZE(hash) - END_KV_SERIALIZE_MAP() -}; - -struct t_hash_json { - std::vector hashlines; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(hashlines) - END_KV_SERIALIZE_MAP() -}; - -bool create_checkpoints(cryptonote::checkpoints& checkpoints) -{ - ADD_CHECKPOINT(1, "771fbcd656ec1464d3a02ead5e18644030007a0fc664c0a964d30922821a8148"); - ADD_CHECKPOINT(10, "c0e3b387e47042f72d8ccdca88071ff96bff1ac7cde09ae113dbb7ad3fe92381"); - ADD_CHECKPOINT(100, "ac3e11ca545e57c49fca2b4e8c48c03c23be047c43e471e1394528b1f9f80b2d"); - ADD_CHECKPOINT(1000, "5acfc45acffd2b2e7345caf42fa02308c5793f15ec33946e969e829f40b03876"); - ADD_CHECKPOINT(10000, "c758b7c81f928be3295d45e230646de8b852ec96a821eac3fea4daf3fcac0ca2"); - ADD_CHECKPOINT(22231, "7cb10e29d67e1c069e6e11b17d30b809724255fee2f6868dc14cfc6ed44dfb25"); - ADD_CHECKPOINT(29556, "53c484a8ed91e4da621bb2fa88106dbde426fe90d7ef07b9c1e5127fb6f3a7f6"); - ADD_CHECKPOINT(50000, "0fe8758ab06a8b9cb35b7328fd4f757af530a5d37759f9d3e421023231f7b31c"); - ADD_CHECKPOINT(80000, "a62dcd7b536f22e003ebae8726e9e7276f63d594e264b6f0cd7aab27b66e75e3"); - ADD_CHECKPOINT(202612, "bbd604d2ba11ba27935e006ed39c9bfdd99b76bf4a50654bc1e1e61217962698"); - ADD_CHECKPOINT(202613, "e2aa337e78df1f98f462b3b1e560c6b914dec47b610698b7b7d1e3e86b6197c2"); - ADD_CHECKPOINT(202614, "c29e3dc37d8da3e72e506e31a213a58771b24450144305bcba9e70fa4d6ea6fb"); - ADD_CHECKPOINT(205000, "5d3d7a26e6dc7535e34f03def711daa8c263785f73ec1fadef8a45880fde8063"); - ADD_CHECKPOINT(220000, "9613f455933c00e3e33ac315cc6b455ee8aa0c567163836858c2d9caff111553"); - ADD_CHECKPOINT(230300, "bae7a80c46859db355556e3a9204a337ae8f24309926a1312323fdecf1920e61"); - ADD_CHECKPOINT(230700, "93e631240ceac831da1aebfc5dac8f722c430463024763ebafa888796ceaeedf"); - ADD_CHECKPOINT(231350, "b5add137199b820e1ea26640e5c3e121fd85faa86a1e39cf7e6cc097bdeb1131"); - ADD_CHECKPOINT(232150, "955de8e6b6508af2c24f7334f97beeea651d78e9ade3ab18fec3763be3201aa8"); - ADD_CHECKPOINT(249380, "654fb0a81ce3e5caf7e3264a70f447d4bd07586c08fa50f6638cc54da0a52b2d"); - ADD_CHECKPOINT(300000, "0c1cd46df6ccff90ec4ab493281f2583c344cd62216c427628990fe9db1bb8b6"); - ADD_CHECKPOINT(400000, "1b2b0e7a30e59691491529a3d506d1ba3d6052d0f6b52198b7330b28a6f1b6ac"); - ADD_CHECKPOINT(450000, "4d098b511ca97723e81737c448343cfd4e6dadb3d8a0e757c6e4d595e6e48357"); - ADD_CHECKPOINT(460000, "75037a7aed3e765db96c75bcf908f59d690a5f3390baebb9edeafd336a1c4831"); - ADD_CHECKPOINT(500000, "2428f0dbe49796be05ed81b347f53e1f7f44aed0abf641446ec2b94cae066b02"); - ADD_CHECKPOINT(600000, "f5828ebf7d7d1cb61762c4dfe3ccf4ecab2e1aad23e8113668d981713b7a54c5"); - ADD_CHECKPOINT(700000, "12be9b3d210b93f574d2526abb9c1ab2a881b479131fd0d4f7dac93875f503cd"); - ADD_CHECKPOINT(825000, "56503f9ad766774b575be3aff73245e9d159be88132c93d1754764f28da2ff60"); - ADD_CHECKPOINT(900000, "d9958d0e7dcf91a5a7b11de225927bf7efc6eb26240315ce12372be902cc1337"); - ADD_CHECKPOINT(913193, "5292d5d56f6ba4de33a58d9a34d263e2cb3c6fee0aed2286fd4ac7f36d53c85f"); - - return true; -} - -bool load_checkpoints_from_json(cryptonote::checkpoints& checkpoints, std::string json_hashfile_fullpath) -{ - boost::system::error_code errcode; - if (! (boost::filesystem::exists(json_hashfile_fullpath, errcode))) - { - LOG_PRINT_L1("Blockchain checkpoints file not found"); - return true; - } - - LOG_PRINT_L1("Adding checkpoints from blockchain hashfile"); - - uint64_t prev_max_height = checkpoints.get_max_height(); - LOG_PRINT_L1("Hard-coded max checkpoint height is " << prev_max_height); - t_hash_json hashes; - epee::serialization::load_t_from_json_file(hashes, json_hashfile_fullpath); - for (std::vector::const_iterator it = hashes.hashlines.begin(); it != hashes.hashlines.end(); ) - { - uint64_t height; - height = it->height; - if (height <= prev_max_height) { - LOG_PRINT_L1("ignoring checkpoint height " << height); - } else { - std::string blockhash = it->hash; - LOG_PRINT_L1("Adding checkpoint height " << height << ", hash=" << blockhash); - ADD_CHECKPOINT(height, blockhash); - } - ++it; - } - - return true; -} - -bool load_checkpoints_from_dns(cryptonote::checkpoints& checkpoints, bool testnet) -{ - // All four MoneroPulse domains have DNSSEC on and valid - static const std::vector dns_urls = { "checkpoints.moneropulse.se" - , "checkpoints.moneropulse.org" - , "checkpoints.moneropulse.net" - , "checkpoints.moneropulse.co" - }; - - static const std::vector testnet_dns_urls = { "testpoints.moneropulse.se" - , "testpoints.moneropulse.org" - , "testpoints.moneropulse.net" - , "testpoints.moneropulse.co" - }; - - std::vector > records; - records.resize(dns_urls.size()); - - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_int_distribution dis(0, dns_urls.size() - 1); - size_t first_index = dis(gen); - - bool avail, valid; - size_t cur_index = first_index; - do - { - std::string url; - if (testnet) - { - url = testnet_dns_urls[cur_index]; - } - else - { - url = dns_urls[cur_index]; - } - - records[cur_index] = tools::DNSResolver::instance().get_txt_record(url, avail, valid); - if (!avail) - { - records[cur_index].clear(); - LOG_PRINT_L2("DNSSEC not available for checkpoint update at URL: " << url << ", skipping."); - } - if (!valid) - { - records[cur_index].clear(); - LOG_PRINT_L2("DNSSEC validation failed for checkpoint update at URL: " << url << ", skipping."); - } - - cur_index++; - if (cur_index == dns_urls.size()) - { - cur_index = 0; - } - records[cur_index].clear(); - } while (cur_index != first_index); - - size_t num_valid_records = 0; - - for( const auto& record_set : records) - { - if (record_set.size() != 0) - { - num_valid_records++; - } - } - - if (num_valid_records < 2) - { - LOG_PRINT_L0("WARNING: no two valid MoneroPulse DNS checkpoint records were received"); - return true; - } - - int good_records_index = -1; - for (size_t i = 0; i < records.size() - 1; ++i) - { - if (records[i].size() == 0) continue; - - for (size_t j = i + 1; j < records.size(); ++j) - { - if (dns_records_match(records[i], records[j])) - { - good_records_index = i; - break; - } - } - if (good_records_index >= 0) break; - } - - if (good_records_index < 0) - { - LOG_PRINT_L0("WARNING: no two MoneroPulse DNS checkpoint records matched"); - return true; - } - - for (auto& record : records[good_records_index]) - { - auto pos = record.find(":"); - if (pos != std::string::npos) - { - uint64_t height; - crypto::hash hash; - - // parse the first part as uint64_t, - // if this fails move on to the next record - std::stringstream ss(record.substr(0, pos)); - if (!(ss >> height)) - { - continue; - } - - // parse the second part as crypto::hash, - // if this fails move on to the next record - std::string hashStr = record.substr(pos + 1); - if (!epee::string_tools::parse_tpod_from_hex_string(hashStr, hash)) - { - continue; - } - - ADD_CHECKPOINT(height, hashStr); - } - } - return true; -} - -bool load_new_checkpoints(cryptonote::checkpoints& checkpoints, std::string json_hashfile_fullpath) -{ - // TODO: replace hard-coded url with const string or #define - return (load_checkpoints_from_json(checkpoints, json_hashfile_fullpath) && load_checkpoints_from_dns(checkpoints)); -} - -} // namespace cryptonote diff --git a/src/cryptonote_core/checkpoints_create.h b/src/cryptonote_core/checkpoints_create.h deleted file mode 100644 index 83830f8a..00000000 --- a/src/cryptonote_core/checkpoints_create.h +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) 2014-2016, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers - -#pragma once - -#include "checkpoints.h" -#include "misc_log_ex.h" - -#define ADD_CHECKPOINT(h, hash) CHECK_AND_ASSERT(checkpoints.add_checkpoint(h, hash), false); -#define JSON_HASH_FILE_NAME "checkpoints.json" - -namespace cryptonote -{ - - bool create_checkpoints(cryptonote::checkpoints& checkpoints); - - bool load_checkpoints_from_json(cryptonote::checkpoints& checkpoints, std::string json_hashfile_fullpath); - bool load_checkpoints_from_dns(cryptonote::checkpoints& checkpoints, bool testnet = false); - bool load_new_checkpoints(cryptonote::checkpoints& checkpoints, std::string json_hashfile_fullpath); - -} // namespace cryptonote diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 6f0fe88a..91e96917 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -42,7 +42,7 @@ using namespace epee; #include "cryptonote_format_utils.h" #include "misc_language.h" #include -#include "cryptonote_core/checkpoints_create.h" +#include "cryptonote_core/checkpoints.h" #include "blockchain_db/blockchain_db.h" #include "blockchain_db/lmdb/db_lmdb.h" #if defined(BERKELEY_DB) @@ -159,7 +159,7 @@ namespace cryptonote if (!m_testnet && !m_fakechain) { cryptonote::checkpoints checkpoints; - if (!cryptonote::create_checkpoints(checkpoints)) + if (!checkpoints.init_default_checkpoints()) { throw std::runtime_error("Failed to initialize checkpoints"); } diff --git a/src/daemon/core.h b/src/daemon/core.h index 2208ef25..2b7f0d17 100644 --- a/src/daemon/core.h +++ b/src/daemon/core.h @@ -28,7 +28,6 @@ #pragma once -#include "cryptonote_core/checkpoints_create.h" #include "cryptonote_core/cryptonote_core.h" #include "cryptonote_protocol/cryptonote_protocol_handler.h" #include "misc_log_ex.h" From 8ac329df0294f16efbc48a2785eda165878d3511 Mon Sep 17 00:00:00 2001 From: Thomas Winget Date: Wed, 7 Oct 2015 22:28:43 -0400 Subject: [PATCH 28/47] doxygen documentation for difficulty functions --- src/cryptonote_core/difficulty.cpp | 6 ++++-- src/cryptonote_core/difficulty.h | 12 ++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/cryptonote_core/difficulty.cpp b/src/cryptonote_core/difficulty.cpp index 236e8448..54da7739 100644 --- a/src/cryptonote_core/difficulty.cpp +++ b/src/cryptonote_core/difficulty.cpp @@ -116,8 +116,8 @@ namespace cryptonote { return !carry; } - difficulty_type next_difficulty(vector timestamps, vector cumulative_difficulties, size_t target_seconds) { - //cutoff DIFFICULTY_LAG + difficulty_type next_difficulty(std::vector timestamps, std::vector cumulative_difficulties, size_t target_seconds) { + if(timestamps.size() > DIFFICULTY_WINDOW) { timestamps.resize(DIFFICULTY_WINDOW); @@ -151,6 +151,8 @@ namespace cryptonote { assert(total_work > 0); uint64_t low, high; mul(total_work, target_seconds, low, high); + // blockchain errors "difficulty overhead" if this function returns zero. + // TODO: consider throwing an exception instead if (high != 0 || low + time_span - 1 < low) { return 0; } diff --git a/src/cryptonote_core/difficulty.h b/src/cryptonote_core/difficulty.h index d49c2f3b..910f9703 100644 --- a/src/cryptonote_core/difficulty.h +++ b/src/cryptonote_core/difficulty.h @@ -39,6 +39,18 @@ namespace cryptonote { typedef std::uint64_t difficulty_type; + /** + * @brief checks if a hash fits the given difficulty + * + * The hash passes if (hash * difficulty) < 2^192. + * Phrased differently, if (hash * difficulty) fits without overflow into + * the least significant 192 bits of the 256 bit multiplication result. + * + * @param hash the hash to check + * @param difficulty the difficulty to check against + * + * @return true if valid, else false + */ bool check_hash(const crypto::hash &hash, difficulty_type difficulty); difficulty_type next_difficulty(std::vector timestamps, std::vector cumulative_difficulties, size_t target_seconds); } From 50dba6d30110336047cc45e5bbf9b8e59639277c Mon Sep 17 00:00:00 2001 From: Thomas Winget Date: Wed, 7 Oct 2015 22:28:51 -0400 Subject: [PATCH 29/47] cryptonote::core doxygen documentation --- src/cryptonote_core/cryptonote_core.cpp | 2 +- src/cryptonote_core/cryptonote_core.h | 653 ++++++++++++++++++++++-- 2 files changed, 623 insertions(+), 32 deletions(-) diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 91e96917..eb971895 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -415,7 +415,7 @@ namespace cryptonote CHECK_AND_ASSERT_MES(update_checkpoints(), false, "One or more checkpoints loaded from json or dns conflicted with existing checkpoints."); r = m_miner.init(vm, m_testnet); - CHECK_AND_ASSERT_MES(r, false, "Failed to initialize blockchain storage"); + CHECK_AND_ASSERT_MES(r, false, "Failed to initialize miner instance"); return load_state_data(); } diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 32f0b2ad..cee9bba3 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -63,157 +63,748 @@ namespace cryptonote /************************************************************************/ /* */ /************************************************************************/ + + /** + * @brief handles core cryptonote functionality + * + * This class coordinates cryptonote functionality including, but not + * limited to, communication among the Blockchain, the transaction pool, + * any miners, and the network. + */ class core: public i_miner_handler { public: - core(i_cryptonote_protocol* pprotocol); - bool handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NOTIFY_RESPONSE_GET_OBJECTS::request& rsp, cryptonote_connection_context& context); - bool on_idle(); - bool handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, bool keeped_by_block, bool relayed); - bool handle_incoming_block(const blobdata& block_blob, block_verification_context& bvc, bool update_miner_blocktemplate = true); - bool prepare_handle_incoming_blocks(const std::list &blocks); - bool cleanup_handle_incoming_blocks(bool force_sync = false); + /** + * @brief constructor + * + * sets member variables into a usable state + * + * @param pprotocol pre-constructed protocol object to store and use + */ + core(i_cryptonote_protocol* pprotocol); + + /** + * @copydoc Blockchain::handle_get_objects + * + * @note see Blockchain::handle_get_objects() + * @param context connection context associated with the request + */ + bool handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NOTIFY_RESPONSE_GET_OBJECTS::request& rsp, cryptonote_connection_context& context); + + /** + * @brief calls various idle routines + * + * @note see miner::on_idle and tx_memory_pool::on_idle + * + * @return true + */ + bool on_idle(); + + /** + * @brief handles an incoming transaction + * + * Parses an incoming transaction and, if nothing is obviously wrong, + * passes it along to the transaction pool + * + * @param tx_blob the tx to handle + * @param tvc metadata about the transaction's validity + * @param keeped_by_block if the transaction has been in a block + * @param relayed whether or not the transaction was relayed to us + * + * @return true if the transaction made it to the transaction pool, otherwise false + */ + bool handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, bool keeped_by_block, bool relayed); + + /** + * @brief handles an incoming block + * + * periodic update to checkpoints is triggered here + * Attempts to add the block to the Blockchain and, on success, + * optionally updates the miner's block template. + * + * @param block_blob the block to be added + * @param bvc return-by-reference metadata context about the block's validity + * @param update_miner_blocktemplate whether or not to update the miner's block template + * + * @return false if loading new checkpoints fails, or the block is not + * added, otherwise true + */ + bool handle_incoming_block(const blobdata& block_blob, block_verification_context& bvc, bool update_miner_blocktemplate = true); + + /** + * @copydoc Blockchain::prepare_handle_incoming_blocks + * + * @note see Blockchain::prepare_handle_incoming_blocks + */ + bool prepare_handle_incoming_blocks(const std::list &blocks); + + /** + * @copydoc Blockchain::cleanup_handle_incoming_blocks + * + * @note see Blockchain::cleanup_handle_incoming_blocks + */ + bool cleanup_handle_incoming_blocks(bool force_sync = false); + + /** + * @brief check the size of a block against the current maximum + * + * @param block_blob the block to check + * + * @return whether or not the block is too big + */ bool check_incoming_block_size(const blobdata& block_blob) const; + + /** + * @brief get the cryptonote protocol instance + * + * @return the instance + */ i_cryptonote_protocol* get_protocol(){return m_pprotocol;} //-------------------- i_miner_handler ----------------------- + + /** + * @brief stores and relays a block found by a miner + * + * Updates the miner's target block, attempts to store the found + * block in Blockchain, and -- on success -- relays that block to + * the network. + * + * @param b the block found + * + * @return true if the block was added to the main chain, otherwise false + */ virtual bool handle_block_found( block& b); + + /** + * @copydoc Blockchain::create_block_template + * + * @note see Blockchain::create_block_template + */ virtual bool get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, const blobdata& ex_nonce); + /** + * @brief gets the miner instance + * + * @return a reference to the miner instance + */ miner& get_miner(){return m_miner;} + + /** + * @brief gets the miner instance (const) + * + * @return a const reference to the miner instance + */ const miner& get_miner()const{return m_miner;} + + /** + * @brief adds command line options to the given options set + * + * As of now, there are no command line options specific to core, + * so this function simply returns. + * + * @param desc return-by-reference the command line options set to add to + */ static void init_options(boost::program_options::options_description& desc); + + /** + * @brief initializes the core as needed + * + * This function initializes the transaction pool, the Blockchain, and + * a miner instance with parameters given on the command line (or defaults) + * + * @param vm command line parameters + * @param test_options configuration options for testing + * + * @return false if one of the init steps fails, otherwise true + */ bool init(const boost::program_options::variables_map& vm, const test_options *test_options = NULL); + + /** + * @copydoc Blockchain::reset_and_set_genesis_block + * + * @note see Blockchain::reset_and_set_genesis_block + */ bool set_genesis_block(const block& b); + + /** + * @brief performs safe shutdown steps for core and core components + * + * Uninitializes the miner instance, transaction pool, and Blockchain + * + * if m_fast_exit is set, the call to Blockchain::deinit() is not made. + * + * @return true + */ bool deinit(); + + /** + * @brief sets fast exit flag + * + * @note see deinit() + */ static void set_fast_exit(); + + /** + * @brief gets the current state of the fast exit flag + * + * @return the fast exit flag + * + * @note see deinit() + */ static bool get_fast_exit(); + + /** + * @brief sets to drop blocks downloaded (for testing) + */ void test_drop_download(); + + /** + * @brief sets to drop blocks downloaded below a certain height + * + * @param height height below which to drop blocks + */ void test_drop_download_height(uint64_t height); + + /** + * @brief gets whether or not to drop blocks (for testing) + * + * @return whether or not to drop blocks + */ bool get_test_drop_download() const; + + /** + * @brief gets whether or not to drop blocks + * + * If the current blockchain height <= our block drop threshold + * and test drop blocks is set, return true + * + * @return see above + */ bool get_test_drop_download_height() const; + + /** + * @copydoc Blockchain::get_current_blockchain_height + * + * @note see Blockchain::get_current_blockchain_height() + */ uint64_t get_current_blockchain_height() const; bool get_blockchain_top(uint64_t& heeight, crypto::hash& top_id) const; + + /** + * @brief get the hash and height of the most recent block + * + * @param height return-by-reference height of the block + * @param top_id return-by-reference hash of the block + * + * @return true + */ + bool get_blockchain_top(uint64_t& height, crypto::hash& top_id) const; + + /** + * @copydoc Blockchain::get_blocks(uint64_t, size_t, std::list&, std::list&) const + * + * @note see Blockchain::get_blocks(uint64_t, size_t, std::list&, std::list&) const + */ bool get_blocks(uint64_t start_offset, size_t count, std::list& blocks, std::list& txs) const; + + /** + * @copydoc Blockchain::get_blocks(uint64_t, size_t, std::list&) const + * + * @note see Blockchain::get_blocks(uint64_t, size_t, std::list&) const + */ bool get_blocks(uint64_t start_offset, size_t count, std::list& blocks) const; + + /** + * @copydoc Blockchain::get_blocks(const t_ids_container&, t_blocks_container&, t_missed_container&) const + * + * @note see Blockchain::get_blocks(const t_ids_container&, t_blocks_container&, t_missed_container&) const + */ template bool get_blocks(const t_ids_container& block_ids, t_blocks_container& blocks, t_missed_container& missed_bs) const { return m_blockchain_storage.get_blocks(block_ids, blocks, missed_bs); } + + /** + * @copydoc Blockchain::get_block_id_by_height + * + * @note see Blockchain::get_block_id_by_height + */ crypto::hash get_block_id_by_height(uint64_t height) const; + + /** + * @copydoc Blockchain::get_transactions + * + * @note see Blockchain::get_transactions + */ bool get_transactions(const std::vector& txs_ids, std::list& txs, std::list& missed_txs) const; + + /** + * @copydoc Blockchain::get_block_by_hash + * + * @note see Blockchain::get_block_by_hash + */ bool get_block_by_hash(const crypto::hash &h, block &blk) const; //void get_all_known_block_ids(std::list &main, std::list &alt, std::list &invalid); + /** + * @copydoc Blockchain::get_alternative_blocks + * + * @note see Blockchain::get_alternative_blocks(std::list&) const + */ bool get_alternative_blocks(std::list& blocks) const; + + /** + * @copydoc Blockchain::get_alternative_blocks_count + * + * @note see Blockchain::get_alternative_blocks_count() const + */ size_t get_alternative_blocks_count() const; + /** + * @brief set the pointer to the cryptonote protocol object to use + * + * @param pprotocol the pointer to set ours as + */ void set_cryptonote_protocol(i_cryptonote_protocol* pprotocol); + + /** + * @copydoc Blockchain::set_checkpoints + * + * @note see Blockchain::set_checkpoints() + */ void set_checkpoints(checkpoints&& chk_pts); + + /** + * @brief set the file path to read from when loading checkpoints + * + * @param path the path to set ours as + */ void set_checkpoints_file_path(const std::string& path); + + /** + * @brief set whether or not we enforce DNS checkpoints + * + * @param enforce_dns enforce DNS checkpoints or not + */ void set_enforce_dns_checkpoints(bool enforce_dns); + /** + * @copydoc tx_memory_pool::get_transactions + * + * @note see tx_memory_pool::get_transactions + */ bool get_pool_transactions(std::list& txs) const; + + /** + * @copydoc tx_memory_pool::get_pool_transactions_and_spent_keys_info + * + * @note see tx_memory_pool::get_pool_transactions_and_spent_keys_info + */ bool get_pool_transactions_and_spent_keys_info(std::vector& tx_infos, std::vector& key_image_infos) const; + + /** + * @copydoc tx_memory_pool::get_transactions_count + * + * @note see tx_memory_pool::get_transactions_count + */ size_t get_pool_transactions_count() const; + + /** + * @copydoc Blockchain::get_total_transactions + * + * @note see Blockchain::get_total_transactions + */ size_t get_blockchain_total_transactions() const; //bool get_outs(uint64_t amount, std::list& pkeys); + + /** + * @copydoc Blockchain::have_block + * + * @note see Blockchain::have_block + */ bool have_block(const crypto::hash& id) const; + + /** + * @copydoc Blockchain::get_short_chain_history + * + * @note see Blockchain::get_short_chain_history + */ bool get_short_chain_history(std::list& ids) const; + + /** + * @copydoc Blockchain::find_blockchain_supplement(const std::list&, NOTIFY_RESPONSE_CHAIN_ENTRY::request&) const + * + * @note see Blockchain::find_blockchain_supplement(const std::list&, NOTIFY_RESPONSE_CHAIN_ENTRY::request&) const + */ bool find_blockchain_supplement(const std::list& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const; + + /** + * @copydoc Blockchain::find_blockchain_supplement(const uint64_t, const std::list&, std::list > >&, uint64_t&, uint64_t&, size_t) const + * + * @note see Blockchain::find_blockchain_supplement(const uint64_t, const std::list&, std::list > >&, uint64_t&, uint64_t&, size_t) const + */ bool find_blockchain_supplement(const uint64_t req_start_block, const std::list& qblock_ids, std::list > >& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count) const; + + /** + * @brief gets some stats about the daemon + * + * @param st_inf return-by-reference container for the stats requested + * + * @return true + */ bool get_stat_info(core_stat_info& st_inf) const; //bool get_backward_blocks_sizes(uint64_t from_height, std::vector& sizes, size_t count); + + /** + * @copydoc Blockchain::get_tx_outputs_gindexs + * + * @note see Blockchain::get_tx_outputs_gindexs + */ bool get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vector& indexs) const; + + /** + * @copydoc Blockchain::get_tail_id + * + * @note see Blockchain::get_tail_id + */ crypto::hash get_tail_id() const; + + /** + * @copydoc Blockchain::get_random_outs_for_amounts + * + * @note see Blockchain::get_random_outs_for_amounts + */ bool get_random_outs_for_amounts(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response& res) const; + + + /** + * @copydoc miner::pause + * + * @note see miner::pause + */ void pause_mine(); + + /** + * @copydoc miner::resume + * + * @note see miner::resume + */ void resume_mine(); + #if BLOCKCHAIN_DB == DB_LMDB + /** + * @brief gets the Blockchain instance + * + * @return a reference to the Blockchain instance + */ Blockchain& get_blockchain_storage(){return m_blockchain_storage;} + + /** + * @brief gets the Blockchain instance (const) + * + * @return a const reference to the Blockchain instance + */ const Blockchain& get_blockchain_storage()const{return m_blockchain_storage;} #else blockchain_storage& get_blockchain_storage(){return m_blockchain_storage;} const blockchain_storage& get_blockchain_storage()const{return m_blockchain_storage;} #endif - //debug functions + + /** + * @copydoc Blockchain::print_blockchain + * + * @note see Blockchain::print_blockchain + */ void print_blockchain(uint64_t start_index, uint64_t end_index) const; + + /** + * @copydoc Blockchain::print_blockchain_index + * + * @note see Blockchain::print_blockchain_index + */ void print_blockchain_index() const; + + /** + * @copydoc tx_memory_pool::print_pool + * + * @note see tx_memory_pool::print_pool + */ std::string print_pool(bool short_format) const; + + /** + * @copydoc Blockchain::print_blockchain_outs + * + * @note see Blockchain::print_blockchain_outs + */ void print_blockchain_outs(const std::string& file); + + /** + * @copydoc miner::on_synchronized + * + * @note see miner::on_synchronized + */ void on_synchronized(); + /** + * @brief sets the target blockchain height + * + * @param target_blockchain_height the height to set + */ void set_target_blockchain_height(uint64_t target_blockchain_height); + + /** + * @brief gets the target blockchain height + * + * @param target_blockchain_height the target height + */ uint64_t get_target_blockchain_height() const; + /** + * @brief tells the Blockchain to update its checkpoints + * + * This function will check if enough time has passed since the last + * time checkpoints were updated and tell the Blockchain to update + * its checkpoints if it is time. If updating checkpoints fails, + * the daemon is told to shut down. + * + * @note see Blockchain::update_checkpoints() + */ bool update_checkpoints(); + /** + * @brief tells the daemon to wind down operations and stop running + * + * Currently this function raises SIGTERM, allowing the installed signal + * handlers to do the actual stopping. + */ + void graceful_exit(); + + /** + * @brief stops the daemon running + * + * @note see graceful_exit() + */ void stop(); + /** + * @copydoc Blockchain::have_tx_keyimg_as_spent + * + * @note see Blockchain::have_tx_keyimg_as_spent + */ bool is_key_image_spent(const crypto::key_image& key_im) const; + + /** + * @brief check if multiple key images are spent + * + * plural version of is_key_image_spent() + * + * @param key_im list of key images to check + * @param spent return-by-reference result for each image checked + * + * @return true + */ bool are_key_images_spent(const std::vector& key_im, std::vector &spent) const; private: + + /** + * @copydoc add_new_tx(const transaction&, tx_verification_context&, bool) + * + * @param tx_hash the transaction's hash + * @param tx_prefix_hash the transaction prefix' hash + * @param blob_size the size of the transaction + * @param relayed whether or not the transaction was relayed to us + * + */ bool add_new_tx(const transaction& tx, const crypto::hash& tx_hash, const crypto::hash& tx_prefix_hash, size_t blob_size, tx_verification_context& tvc, bool keeped_by_block, bool relayed); + + /** + * @brief add a new transaction to the transaction pool + * + * Adds a new transaction to the transaction pool. + * + * @param tx the transaction to add + * @param tvc return-by-reference metadata about the transaction's verification process + * @param keeped_by_block whether or not the transaction has been in a block + * @param relayed whether or not the transaction was relayed to us + * + * @return true if the transaction is already in the transaction pool, + * is already in a block on the Blockchain, or is successfully added + * to the transaction pool + */ bool add_new_tx(const transaction& tx, tx_verification_context& tvc, bool keeped_by_block, bool relayed); + + /** + * @copydoc Blockchain::add_new_block + * + * @note see Blockchain::add_new_block + */ bool add_new_block(const block& b, block_verification_context& bvc); + + /** + * @brief load any core state stored on disk + * + * currently does nothing, but may have state to load in the future. + * + * @return true + */ bool load_state_data(); + + /** + * @copydoc parse_tx_from_blob(transaction&, crypto::hash&, crypto::hash&, const blobdata&) const + * + * @note see parse_tx_from_blob(transaction&, crypto::hash&, crypto::hash&, const blobdata&) const + */ bool parse_tx_from_blob(transaction& tx, crypto::hash& tx_hash, crypto::hash& tx_prefix_hash, const blobdata& blob) const; + /** + * @brief check a transaction's syntax + * + * For now this does nothing, but it may check something about the tx + * in the future. + * + * @param tx the transaction to check + * + * @return true + */ bool check_tx_syntax(const transaction& tx) const; //check correct values, amounts and all lightweight checks not related with database + + /** + * @brief validates some simple properties of a transaction + * + * Currently checks: tx has inputs, + * tx inputs all of supported type(s), + * tx outputs valid (type, key, amount), + * input and output total amounts don't overflow, + * output amount <= input amount, + * tx not too large, + * each input has a different key image. + * + * @param tx the transaction to check + * @param keeped_by_block if the transaction has been in a block + * + * @return true if all the checks pass, otherwise false + */ bool check_tx_semantic(const transaction& tx, bool keeped_by_block) const; //check if tx already in memory pool or in main blockchain bool check_tx_ring_signature(const txin_to_key& tx, const crypto::hash& tx_prefix_hash, const std::vector& sig) const; bool is_tx_spendtime_unlocked(uint64_t unlock_time) const; + /** + * @copydoc miner::on_block_chain_update + * + * @note see miner::on_block_chain_update + * + * @return true + */ bool update_miner_block_template(); + + /** + * @brief act on a set of command line options given + * + * @param vm the command line options + * + * @return true + */ bool handle_command_line(const boost::program_options::variables_map& vm); bool on_update_blocktemplate_interval(); + + /** + * @brief verify that each input key image in a transaction is unique + * + * @param tx the transaction to check + * + * @return false if any key image is repeated, otherwise true + */ bool check_tx_inputs_keyimages_diff(const transaction& tx) const; - void graceful_exit(); + + /** + * @brief checks HardFork status and prints messages about it + * + * Checks the status of HardFork and logs/prints if an update to + * the daemon is necessary. + * + * @note see Blockchain::get_hard_fork_state and HardFork::State + * + * @return true + */ bool check_fork_time(); + + /** + * @brief attempts to relay any transactions in the mempool which need it + * + * @return true + */ bool relay_txpool_transactions(); + + /** + * @brief locks a file in the BlockchainDB directory + * + * @param path the directory in which to place the file + * + * @return true if lock acquired successfully, otherwise false + */ bool lock_db_directory(const boost::filesystem::path &path); + + /** + * @brief unlocks the db directory + * + * @note see lock_db_directory() + * + * @return true + */ bool unlock_db_directory(); - static std::atomic m_fast_exit; - bool m_test_drop_download = true; - uint64_t m_test_drop_download_height = 0; + static std::atomic m_fast_exit; //!< whether or not to deinit Blockchain on exit - tx_memory_pool m_mempool; + bool m_test_drop_download = true; //!< whether or not to drop incoming blocks (for testing) + + uint64_t m_test_drop_download_height = 0; //!< height under which to drop incoming blocks, if doing so + + tx_memory_pool m_mempool; //!< transaction pool instance #if BLOCKCHAIN_DB == DB_LMDB - Blockchain m_blockchain_storage; + Blockchain m_blockchain_storage; //!< Blockchain instance #else blockchain_storage m_blockchain_storage; #endif - i_cryptonote_protocol* m_pprotocol; - epee::critical_section m_incoming_tx_lock; + + i_cryptonote_protocol* m_pprotocol; //!< cryptonote protocol instance + + epee::critical_section m_incoming_tx_lock; //!< incoming transaction lock + //m_miner and m_miner_addres are probably temporary here - miner m_miner; - account_public_address m_miner_address; - std::string m_config_folder; - cryptonote_protocol_stub m_protocol_stub; - epee::math_helper::once_a_time_seconds<60*60*12, false> m_store_blockchain_interval; - epee::math_helper::once_a_time_seconds<60*60*2, true> m_fork_moaner; + miner m_miner; //!< miner instance + account_public_address m_miner_address; //!< address to mine to (for miner instance) + + std::string m_config_folder; //!< folder to look in for configs and other files + + cryptonote_protocol_stub m_protocol_stub; //!< cryptonote protocol stub instance + + epee::math_helper::once_a_time_seconds<60*60*12, false> m_store_blockchain_interval; //!< interval for manual storing of Blockchain, if enabled + epee::math_helper::once_a_time_seconds<60*60*2, false> m_fork_moaner; //!< interval for checking HardFork status epee::math_helper::once_a_time_seconds<60*2, false> m_txpool_auto_relayer; //!< interval for checking re-relaying txpool transactions + friend class tx_validate_inputs; - std::atomic m_starter_message_showed; + std::atomic m_starter_message_showed; //!< has the "daemon will sync now" message been shown? - uint64_t m_target_blockchain_height; + uint64_t m_target_blockchain_height; //!< blockchain height target - bool m_testnet; - bool m_fakechain; - std::string m_checkpoints_path; - time_t m_last_dns_checkpoints_update; - time_t m_last_json_checkpoints_update; + bool m_testnet; //!< are we on testnet? - std::atomic_flag m_checkpoints_updating; + bool m_fakechain; //!< are we using a fake chain (for testing purposes)? - boost::interprocess::file_lock db_lock; + std::string m_checkpoints_path; //!< path to json checkpoints file + time_t m_last_dns_checkpoints_update; //!< time when dns checkpoints were last updated + time_t m_last_json_checkpoints_update; //!< time when json checkpoints were last updated + + std::atomic_flag m_checkpoints_updating; //!< set if checkpoints are currently updating to avoid multiple threads attempting to update at once + + boost::interprocess::file_lock db_lock; //!< a lock object for a file lock in the db directory }; } From c835215ea9756bf374b4cc7a8e7a95699b00de1d Mon Sep 17 00:00:00 2001 From: Thomas Winget Date: Wed, 7 Oct 2015 22:28:54 -0400 Subject: [PATCH 30/47] remove defunct code from cryptonote::core --- src/cryptonote_core/cryptonote_core.cpp | 13 ------------- src/cryptonote_core/cryptonote_core.h | 9 --------- 2 files changed, 22 deletions(-) diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index eb971895..c31be5ac 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -634,11 +634,6 @@ namespace cryptonote return m_blockchain_storage.get_total_transactions(); } //----------------------------------------------------------------------------------------------- - //bool core::get_outs(uint64_t amount, std::list& pkeys) - //{ - // return m_blockchain_storage.get_outs(amount, pkeys); - //} - //----------------------------------------------------------------------------------------------- bool core::add_new_tx(const transaction& tx, const crypto::hash& tx_hash, const crypto::hash& tx_prefix_hash, size_t blob_size, tx_verification_context& tvc, bool keeped_by_block, bool relayed) { if(m_mempool.have_tx(tx_hash)) @@ -770,10 +765,6 @@ namespace cryptonote { m_miner.on_synchronized(); } - //bool core::get_backward_blocks_sizes(uint64_t from_height, std::vector& sizes, size_t count) - //{ - // return m_blockchain_storage.get_backward_blocks_sizes(from_height, sizes, count); - //} //----------------------------------------------------------------------------------------------- bool core::add_new_block(const block& b, block_verification_context& bvc) { @@ -894,10 +885,6 @@ namespace cryptonote return m_blockchain_storage.get_block_by_hash(h, blk); } //----------------------------------------------------------------------------------------------- - //void core::get_all_known_block_ids(std::list &main, std::list &alt, std::list &invalid) { - // m_blockchain_storage.get_all_known_block_ids(main, alt, invalid); - //} - //----------------------------------------------------------------------------------------------- std::string core::print_pool(bool short_format) const { return m_mempool.print_pool(short_format); diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index cee9bba3..30384209 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -291,7 +291,6 @@ namespace cryptonote * @note see Blockchain::get_current_blockchain_height() */ uint64_t get_current_blockchain_height() const; - bool get_blockchain_top(uint64_t& heeight, crypto::hash& top_id) const; /** * @brief get the hash and height of the most recent block @@ -348,7 +347,6 @@ namespace cryptonote * @note see Blockchain::get_block_by_hash */ bool get_block_by_hash(const crypto::hash &h, block &blk) const; - //void get_all_known_block_ids(std::list &main, std::list &alt, std::list &invalid); /** * @copydoc Blockchain::get_alternative_blocks @@ -419,7 +417,6 @@ namespace cryptonote * @note see Blockchain::get_total_transactions */ size_t get_blockchain_total_transactions() const; - //bool get_outs(uint64_t amount, std::list& pkeys); /** * @copydoc Blockchain::have_block @@ -457,7 +454,6 @@ namespace cryptonote * @return true */ bool get_stat_info(core_stat_info& st_inf) const; - //bool get_backward_blocks_sizes(uint64_t from_height, std::vector& sizes, size_t count); /** * @copydoc Blockchain::get_tx_outputs_gindexs @@ -672,7 +668,6 @@ namespace cryptonote * @return true */ bool check_tx_syntax(const transaction& tx) const; - //check correct values, amounts and all lightweight checks not related with database /** * @brief validates some simple properties of a transaction @@ -691,10 +686,7 @@ namespace cryptonote * @return true if all the checks pass, otherwise false */ bool check_tx_semantic(const transaction& tx, bool keeped_by_block) const; - //check if tx already in memory pool or in main blockchain - bool check_tx_ring_signature(const txin_to_key& tx, const crypto::hash& tx_prefix_hash, const std::vector& sig) const; - bool is_tx_spendtime_unlocked(uint64_t unlock_time) const; /** * @copydoc miner::on_block_chain_update * @@ -712,7 +704,6 @@ namespace cryptonote * @return true */ bool handle_command_line(const boost::program_options::variables_map& vm); - bool on_update_blocktemplate_interval(); /** * @brief verify that each input key image in a transaction is unique From 797357eefb33535e61f81f5c65fea8ec0dd020a2 Mon Sep 17 00:00:00 2001 From: Thomas Winget Date: Wed, 7 Oct 2015 22:28:59 -0400 Subject: [PATCH 31/47] Change Doxyfile, Blockchain not blockchain_storage Changes the Doxyfile to expand preprocessor macros, but only the ones defined in the Doxyfile. This way we can specify that BLOCKCHAIN_DB == DB_LMDB for the sake of documentation. --- Doxyfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doxyfile b/Doxyfile index a2700b34..93a5c6e7 100644 --- a/Doxyfile +++ b/Doxyfile @@ -1902,7 +1902,7 @@ ENABLE_PREPROCESSING = YES # The default value is: NO. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -MACRO_EXPANSION = NO +MACRO_EXPANSION = YES # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then # the macro expansion is limited to the macros specified with the PREDEFINED and @@ -1910,7 +1910,7 @@ MACRO_EXPANSION = NO # The default value is: NO. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -EXPAND_ONLY_PREDEF = NO +EXPAND_ONLY_PREDEF = YES # If the SEARCH_INCLUDES tag is set to YES the includes files in the # INCLUDE_PATH will be searched if a #include is found. @@ -1942,7 +1942,7 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = +PREDEFINED = "BLOCKCHAIN_DB=2" \ # DB_LMDB # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The From bfd4a28c4100cf94f6e8bb6f038e0bd1b6c41059 Mon Sep 17 00:00:00 2001 From: Thomas Winget Date: Tue, 1 Dec 2015 17:38:22 -0500 Subject: [PATCH 32/47] Update BlockchainDB documentation BlockchainDB is now Doxygen-compliant and its documentation is up-to-date with recent changes. --- src/blockchain_db/blockchain_db.h | 1094 +++++++++++++++++++++++++---- 1 file changed, 938 insertions(+), 156 deletions(-) diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index 3396b8c2..3e0ca141 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -38,18 +38,20 @@ #include "cryptonote_core/difficulty.h" #include "cryptonote_core/hardfork.h" -/* DB Driver Interface +/** \file + * Cryptonote Blockchain Database Interface * * The DB interface is a store for the canonical block chain. * It serves as a persistent storage for the blockchain. * - * For the sake of efficiency, the reference implementation will also + * For the sake of efficiency, a concrete implementation may also * store some blockchain data outside of the blocks, such as spent * transfer key images, unspent transaction outputs, etc. * + * Examples are as follows: + * * Transactions are duplicated so that we don't have to fetch a whole block - * in order to fetch a transaction from that block. If this is deemed - * unnecessary later, this can change. + * in order to fetch a transaction from that block. * * Spent key images are duplicated outside of the blocks so it is quick * to verify an output hasn't already been spent @@ -57,100 +59,34 @@ * Unspent transaction outputs are duplicated to quickly gather random * outputs to use for mixins * - * IMPORTANT: - * A concrete implementation of this interface should populate these - * duplicated members! It is possible to have a partial implementation - * of this interface call to private members of the interface to be added - * later that will then populate as needed. - * - * General: - * open() - * is_open() - * close() - * sync() - * reset() - * - * Lock and unlock provided for reorg externally, and for block - * additions internally, this way threaded reads are completely fine - * unless the blockchain is changing. - * bool lock() - * unlock() - * - * vector get_filenames() - * - * Blocks: - * bool block_exists(hash) - * height add_block(block, block_size, cumulative_difficulty, coins_generated, transactions) - * block get_block(hash) - * height get_block_height(hash) - * header get_block_header(hash) - * block get_block_from_height(height) - * size_t get_block_size(height) - * difficulty get_block_cumulative_difficulty(height) - * uint64_t get_block_already_generated_coins(height) - * uint64_t get_block_timestamp(height) - * uint64_t get_top_block_timestamp() - * hash get_block_hash_from_height(height) - * blocks get_blocks_range(height1, height2) - * hashes get_hashes_range(height1, height2) - * hash top_block_hash() - * block get_top_block() - * height height() - * void pop_block(block&, tx_list&) - * - * Transactions: - * bool tx_exists(hash) - * uint64_t get_tx_unlock_time(hash) - * tx get_tx(hash) - * uint64_t get_tx_count() - * tx_list get_tx_list(hash_list) - * height get_tx_block_height(hash) - * - * Outputs: - * uint64_t get_num_outputs(amount) - * pub_key get_output_key(amount, index) - * hash,index get_output_tx_and_index_from_global(index) - * hash,index get_output_tx_and_index(amount, index) - * vec get_tx_output_indices(tx_hash) - * - * - * Spent Output Key Images: - * bool has_key_image(key_image) - * - * Exceptions: - * DB_ERROR -- generic - * DB_OPEN_FAILURE - * DB_CREATE_FAILURE - * DB_SYNC_FAILURE - * BLOCK_DNE - * BLOCK_PARENT_DNE - * BLOCK_EXISTS - * BLOCK_INVALID -- considering making this multiple errors - * TX_DNE - * TX_EXISTS - * OUTPUT_DNE - * OUTPUT_EXISTS - * KEY_IMAGE_EXISTS */ namespace cryptonote { -// typedef for convenience +/** a pair of , typedef for convenience */ typedef std::pair tx_out_index; #pragma pack(push, 1) + +/** + * @brief a struct containing output metadata + */ struct output_data_t { - crypto::public_key pubkey; - uint64_t unlock_time; - uint64_t height; + crypto::public_key pubkey; //!< the output's public key (for spend verification) + uint64_t unlock_time; //!< the output's unlock time (or height) + uint64_t height; //!< the height of the block which created the output }; #pragma pack(pop) /*********************************** * Exception Definitions ***********************************/ + +/** + * @brief A base class for BlockchainDB exceptions + */ class DB_EXCEPTION : public std::exception { private: @@ -168,6 +104,9 @@ class DB_EXCEPTION : public std::exception } }; +/** + * @brief A generic BlockchainDB exception + */ class DB_ERROR : public DB_EXCEPTION { public: @@ -175,7 +114,9 @@ class DB_ERROR : public DB_EXCEPTION DB_ERROR(const char* s) : DB_EXCEPTION(s) { } }; -// For distinguishing errors trying to set up a DB txn from other errors +/** + * @brief thrown when there is an error starting a DB transaction + */ class DB_ERROR_TXN_START : public DB_EXCEPTION { public: @@ -183,6 +124,9 @@ class DB_ERROR_TXN_START : public DB_EXCEPTION DB_ERROR_TXN_START(const char* s) : DB_EXCEPTION(s) { } }; +/** + * @brief thrown when opening the BlockchainDB fails + */ class DB_OPEN_FAILURE : public DB_EXCEPTION { public: @@ -190,6 +134,9 @@ class DB_OPEN_FAILURE : public DB_EXCEPTION DB_OPEN_FAILURE(const char* s) : DB_EXCEPTION(s) { } }; +/** + * @brief thrown when creating the BlockchainDB fails + */ class DB_CREATE_FAILURE : public DB_EXCEPTION { public: @@ -197,6 +144,9 @@ class DB_CREATE_FAILURE : public DB_EXCEPTION DB_CREATE_FAILURE(const char* s) : DB_EXCEPTION(s) { } }; +/** + * @brief thrown when synchronizing the BlockchainDB to disk fails + */ class DB_SYNC_FAILURE : public DB_EXCEPTION { public: @@ -204,6 +154,9 @@ class DB_SYNC_FAILURE : public DB_EXCEPTION DB_SYNC_FAILURE(const char* s) : DB_EXCEPTION(s) { } }; +/** + * @brief thrown when a requested block does not exist + */ class BLOCK_DNE : public DB_EXCEPTION { public: @@ -211,6 +164,9 @@ class BLOCK_DNE : public DB_EXCEPTION BLOCK_DNE(const char* s) : DB_EXCEPTION(s) { } }; +/** + * @brief thrown when a block's parent does not exist (and it needed to) + */ class BLOCK_PARENT_DNE : public DB_EXCEPTION { public: @@ -218,6 +174,9 @@ class BLOCK_PARENT_DNE : public DB_EXCEPTION BLOCK_PARENT_DNE(const char* s) : DB_EXCEPTION(s) { } }; +/** + * @brief thrown when a block exists, but shouldn't, namely when adding a block + */ class BLOCK_EXISTS : public DB_EXCEPTION { public: @@ -225,6 +184,9 @@ class BLOCK_EXISTS : public DB_EXCEPTION BLOCK_EXISTS(const char* s) : DB_EXCEPTION(s) { } }; +/** + * @brief thrown when something is wrong with the block to be added + */ class BLOCK_INVALID : public DB_EXCEPTION { public: @@ -232,6 +194,9 @@ class BLOCK_INVALID : public DB_EXCEPTION BLOCK_INVALID(const char* s) : DB_EXCEPTION(s) { } }; +/** + * @brief thrown when a requested transaction does not exist + */ class TX_DNE : public DB_EXCEPTION { public: @@ -239,6 +204,9 @@ class TX_DNE : public DB_EXCEPTION TX_DNE(const char* s) : DB_EXCEPTION(s) { } }; +/** + * @brief thrown when a transaction exists, but shouldn't, namely when adding a block + */ class TX_EXISTS : public DB_EXCEPTION { public: @@ -246,6 +214,9 @@ class TX_EXISTS : public DB_EXCEPTION TX_EXISTS(const char* s) : DB_EXCEPTION(s) { } }; +/** + * @brief thrown when a requested output does not exist + */ class OUTPUT_DNE : public DB_EXCEPTION { public: @@ -253,6 +224,9 @@ class OUTPUT_DNE : public DB_EXCEPTION OUTPUT_DNE(const char* s) : DB_EXCEPTION(s) { } }; +/** + * @brief thrown when an output exists, but shouldn't, namely when adding a block + */ class OUTPUT_EXISTS : public DB_EXCEPTION { public: @@ -260,6 +234,9 @@ class OUTPUT_EXISTS : public DB_EXCEPTION OUTPUT_EXISTS(const char* s) : DB_EXCEPTION(s) { } }; +/** + * @brief thrown when a spent key image exists, but shouldn't, namely when adding a block + */ class KEY_IMAGE_EXISTS : public DB_EXCEPTION { public: @@ -272,6 +249,18 @@ class KEY_IMAGE_EXISTS : public DB_EXCEPTION ***********************************/ +/** + * @brief The BlockchainDB backing store interface declaration/contract + * + * This class provides a uniform interface for using BlockchainDB to store + * a blockchain. Any implementation of this class will also implement all + * functions exposed here, so one can use this class without knowing what + * implementation is being used. Refer to each pure virtual function's + * documentation here when implementing a BlockchainDB subclass. + * + * A subclass which encounters an issue should report that issue by throwing + * a DB_EXCEPTION which adequately conveys the issue. + */ class BlockchainDB { private: @@ -279,7 +268,22 @@ private: * private virtual members *********************************************************************/ - // tells the subclass to add the block and metadata to storage + /** + * @brief add the block and metadata to the db + * + * The subclass implementing this will add the specified block and + * block metadata to its backing store. This does not include its + * transactions, those are added in a separate step. + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + * + * @param blk the block to be added + * @param block_size the size of the block (transactions and all) + * @param cumulative_difficulty the accumulated difficulty after this block + * @param coins_generated the number of coins generated total after this block + * @param blk_hash the hash of the block + */ virtual void add_block( const block& blk , const size_t& block_size , const difficulty_type& cumulative_difficulty @@ -287,84 +291,274 @@ private: , const crypto::hash& blk_hash ) = 0; - // tells the subclass to remove data about the top block + /** + * @brief remove data about the top block + * + * The subclass implementing this will remove the block data from the top + * block in the chain. The data to be removed is that which was added in + * BlockchainDB::add_block(const block& blk, const size_t& block_size, const difficulty_type& cumulative_difficulty, const uint64_t& coins_generated, const crypto::hash& blk_hash) + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + */ virtual void remove_block() = 0; - // tells the subclass to store the transaction and its metadata + /** + * @brief store the transaction and its metadata + * + * The subclass implementing this will add the specified transaction data + * to its backing store. This includes only the transaction blob itself + * and the other data passed here, not the separate outputs of the + * transaction. + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + * + * @param blk_hash the hash of the block containing the transaction + * @param tx the transaction to be added + * @param tx_hash the hash of the transaction + */ virtual void add_transaction_data(const crypto::hash& blk_hash, const transaction& tx, const crypto::hash& tx_hash) = 0; - // tells the subclass to remove data about a transaction + /** + * @brief remove data about a transaction + * + * The subclass implementing this will remove the transaction data + * for the passed transaction. The data to be removed was added in + * add_transaction_data(). Additionally, current subclasses have behavior + * which requires the transaction itself as a parameter here. Future + * implementations should note that this parameter is subject to be removed + * at a later time. + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + * + * @param tx_hash the hash of the transaction to be removed + * @param tx the transaction + */ virtual void remove_transaction_data(const crypto::hash& tx_hash, const transaction& tx) = 0; - // tells the subclass to store an output + /** + * @brief store an output + * + * The subclass implementing this will add the output data passed to its + * backing store in a suitable manner. In addition, the subclass is responsible + * for keeping track of the global output count in some manner, so that + * outputs may be indexed by the order in which they were created. In the + * future, this tracking (of the number, at least) should be moved to + * this class, as it is necessary and the same among all BlockchainDB. + * + * This data should be stored in such a manner that the only thing needed to + * reverse the process is the tx_out. + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + * + * @param tx_hash hash of the transaction the output was created by + * @param tx_output the output + * @param local_index index of the output in its transaction + * @param unlock_time unlock time/height of the output + */ virtual void add_output(const crypto::hash& tx_hash, const tx_out& tx_output, const uint64_t& local_index, const uint64_t unlock_time) = 0; - // tells the subclass to remove an output + /** + * @brief remove an output + * + * The subclass implementing this will remove all output data it stored + * in add_output(). + * + * In addition, the subclass is responsible for correctly decrementing + * its global output counter (this may be automatic for some, such as using + * a database backend "count" feature). + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + * + * @param tx_output the output to be removed + */ virtual void remove_output(const tx_out& tx_output) = 0; - // tells the subclass to store a spent key + /** + * @brief store a spent key + * + * The subclass implementing this will store the spent key image. + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + * + * @param k_image the spent key image to store + */ virtual void add_spent_key(const crypto::key_image& k_image) = 0; - // tells the subclass to remove a spent key + /** + * @brief remove a spent key + * + * The subclass implementing this will remove the key image. + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + * + * @param k_image the spent key image to remove + */ virtual void remove_spent_key(const crypto::key_image& k_image) = 0; /********************************************************************* * private concrete members *********************************************************************/ - // private version of pop_block, for undoing if an add_block goes tits up + /** + * @brief private version of pop_block, for undoing if an add_block fails + * + * This function simply calls pop_block(block& blk, std::vector& txs) + * with dummy parameters, as the returns-by-reference can be discarded. + */ void pop_block(); - // helper function for add_transactions, to add each individual tx + /** + * @brief helper function for add_transactions, to add each individual transaction + * + * This function is called by add_transactions() for each transaction to be + * added. + * + * @param blk_hash hash of the block which has the transaction + * @param tx the transaction to add + * @param tx_hash_ptr the hash of the transaction, if already calculated + */ void add_transaction(const crypto::hash& blk_hash, const transaction& tx, const crypto::hash* tx_hash_ptr = NULL); // helper function to remove transaction from blockchain + /** + * @brief helper function to remove transaction from the blockchain + * + * This function encapsulates aspects of removing a transaction. + * + * @param tx_hash the hash of the transaction to be removed + */ void remove_transaction(const crypto::hash& tx_hash); - uint64_t num_calls = 0; - uint64_t time_blk_hash = 0; - uint64_t time_add_block1 = 0; - uint64_t time_add_transaction = 0; + uint64_t num_calls = 0; //!< a performance metric + uint64_t time_blk_hash = 0; //!< a performance metric + uint64_t time_add_block1 = 0; //!< a performance metric + uint64_t time_add_transaction = 0; //!< a performance metric protected: - mutable uint64_t time_tx_exists = 0; - uint64_t time_commit1 = 0; - bool m_auto_remove_logs = true; + mutable uint64_t time_tx_exists = 0; //!< a performance metric + uint64_t time_commit1 = 0; //!< a performance metric + bool m_auto_remove_logs = true; //!< whether or not to automatically remove old logs HardFork* m_hardfork; public: - // virtual dtor + /** + * @brief An empty destructor. + */ virtual ~BlockchainDB() { }; - // reset profiling stats + /** + * @brief reset profiling stats + */ void reset_stats(); - // show profiling stats + /** + * @brief show profiling stats + * + * This function prints current performance/profiling data to whichever + * log file(s) are set up (possibly including stdout or stderr) + */ void show_stats(); - // open the db at location , or create it if there isn't one. + /** + * @brief open a db, or create it if necessary. + * + * This function opens an existing database or creates it if it + * does not exist. + * + * The subclass implementing this will handle all file opening/creation, + * and is responsible for maintaining its state. + * + * The parameter may not refer to a file name, necessarily, but + * could be an IP:PORT for a database which needs it, and so on. Calling it + * is convenient and should be descriptive enough, however. + * + * For now, db_flags are + * specific to the subclass being instantiated. This is subject to change, + * and the db_flags parameter may be deprecated. + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + * + * @param filename a string referring to the BlockchainDB to open + * @param db_flags flags relevant to how to open/use the BlockchainDB + */ virtual void open(const std::string& filename, const int db_flags = 0) = 0; - // returns true of the db is open/ready, else false + /** + * @brief Gets the current open/ready state of the BlockchainDB + * + * @return true if open/ready, otherwise false + */ bool is_open() const; - // close and sync the db + /** + * @brief close the BlockchainDB + * + * At minimum, this call ensures that further use of the BlockchainDB + * instance will not have effect. In any case where it is necessary + * to do so, a subclass implementing this will sync with disk. + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + */ virtual void close() = 0; - // sync the db + /** + * @brief sync the BlockchainDB with disk + * + * This function should write any changes to whatever permanent backing + * store the subclass uses. Example: a BlockchainDB instance which + * keeps the whole blockchain in RAM won't need to regularly access a + * disk, but should write out its state when this is called. + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + */ virtual void sync() = 0; - // reset the db -- USE WITH CARE + /** + * @brief Remove everything from the BlockchainDB + * + * This function should completely remove all data from a BlockchainDB. + * + * Use with caution! + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + */ virtual void reset() = 0; - // get all files used by this db (if any) + /** + * @brief get all files used by the BlockchainDB (if any) + * + * This function is largely for ease of automation, namely for unit tests. + * + * The subclass implementation should return all filenames it uses. + * + * @return a list of filenames + */ virtual std::vector get_filenames() const = 0; // return the name of the folder the db's file(s) should reside in + /** + * @brief gets the name of the folder the BlockchainDB's file(s) should be in + * + * The subclass implementation should return the name of the folder in which + * it stores files, or an empty string if there is none. + * + * @return the name of the folder with the BlockchainDB's files, if any. + */ virtual std::string get_db_name() const = 0; @@ -372,13 +566,86 @@ public: // RAII-friendly and multi-read one-write friendly locking mechanism // // acquire db lock + /** + * @brief acquires the BlockchainDB lock + * + * This function is a stub until such a time as locking is implemented at + * this level. + * + * The subclass implementation should return true unless implementing a + * locking scheme of some sort, in which case it should return true upon + * acquisition of the lock and block until then. + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + * + * @return true, unless at a future time false makes sense (timeout, etc) + */ virtual bool lock() = 0; // release db lock + /** + * @brief This function releases the BlockchainDB lock + * + * The subclass, should it have implemented lock(), will release any lock + * held by the calling thread. In the case of recursive locking, it should + * release one instance of a lock. + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + */ virtual void unlock() = 0; + /** + * @brief tells the BlockchainDB to start a new "batch" of blocks + * + * If the subclass implements a batching method of caching blocks in RAM to + * be added to a backing store in groups, it should start a batch which will + * end either when has been added or batch_stop() has + * been called. In either case, it should end the batch and write to its + * backing store. + * + * If a batch is already in-progress, this function should throw a DB_ERROR. + * This exception may change in the future if it is deemed necessary to + * have a more granular exception type for this scenario. + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + * + * @param batch_num_blocks number of blocks to batch together + */ virtual void batch_start(uint64_t batch_num_blocks=0) = 0; + + /** + * @brief ends a batch transaction + * + * If the subclass implements batching, this function should store the + * batch it is currently on and mark it finished. + * + * If no batch is in-progress, this function should throw a DB_ERROR. + * This exception may change in the future if it is deemed necessary to + * have a more granular exception type for this scenario. + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + */ virtual void batch_stop() = 0; + + /** + * @brief sets whether or not to batch transactions + * + * If the subclass implements batching, this function tells it to begin + * batching automatically. + * + * If the subclass implements batching and has a batch in-progress, a + * parameter of false should disable batching and call batch_stop() to + * store the current batch. + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + * + * @param bool batch whether or not to use batch transactions. + */ virtual void set_batch_transactions(bool) = 0; virtual void block_txn_start(bool readonly=false) = 0; @@ -388,8 +655,26 @@ public: virtual void set_hard_fork(HardFork* hf); // adds a block with the given metadata to the top of the blockchain, returns the new height - // NOTE: subclass implementations of this (or the functions it calls) need - // to handle undoing any partially-added blocks in the event of a failure. + /** + * @brief handles the addition of a new block to BlockchainDB + * + * This function organizes block addition and calls various functions as + * necessary. + * + * NOTE: subclass implementations of this (or the functions it calls) need + * to handle undoing any partially-added blocks in the event of a failure. + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + * + * @param blk the block to be added + * @param block_size the size of the block (transactions and all) + * @param cumulative_difficulty the accumulated difficulty after this block + * @param coins_generated the number of coins generated total after this block + * @param txs the transactions in the block + * + * @return the height of the chain post-addition + */ virtual uint64_t add_block( const block& blk , const size_t& block_size , const difficulty_type& cumulative_difficulty @@ -397,142 +682,639 @@ public: , const std::vector& txs ); - // return true if a block with hash exists in the blockchain + /** + * @brief checks if a block exists + * + * @param h the hash of the requested block + * + * @return true of the block exists, otherwise false + */ virtual bool block_exists(const crypto::hash& h) const = 0; - // return block with hash + /** + * @brief fetches the block with the given hash + * + * The subclass should return the requested block. + * + * If the block does not exist, the subclass should throw BLOCK_DNE + * + * @param h the hash to look for + * + * @return the block requested + */ virtual block get_block(const crypto::hash& h) const = 0; - // return the height of the block with hash on the blockchain, - // throw if it doesn't exist + /** + * @brief gets the height of the block with a given hash + * + * The subclass should return the requested height. + * + * If the block does not exist, the subclass should throw BLOCK_DNE + * + * @param h the hash to look for + * + * @return the height + */ virtual uint64_t get_block_height(const crypto::hash& h) const = 0; - // return header for block with hash + /** + * @brief fetch a block header + * + * The subclass should return the block header from the block with + * the given hash. + * + * If the block does not exist, the subclass should throw BLOCK_DNE + * + * @param h the hash to look for + * + * @return the block header + */ virtual block_header get_block_header(const crypto::hash& h) const = 0; - // return block at height + /** + * @brief fetch a block by height + * + * The subclass should return the block at the given height. + * + * If the block does not exist, that is to say if the blockchain is not + * that high, then the subclass should throw BLOCK_DNE + * + * @param height the height to look for + * + * @return the block + */ virtual block get_block_from_height(const uint64_t& height) const = 0; - // return timestamp of block at height + /** + * @brief fetch a block's timestamp + * + * The subclass should return the timestamp of the block with the + * given height. + * + * If the block does not exist, the subclass should throw BLOCK_DNE + * + * @param height the height requested + * + * @return the timestamp + */ virtual uint64_t get_block_timestamp(const uint64_t& height) const = 0; - // return timestamp of most recent block + /** + * @brief fetch the top block's timestamp + * + * The subclass should return the timestamp of the most recent block. + * + * @return the top block's timestamp + */ virtual uint64_t get_top_block_timestamp() const = 0; - // return block size of block at height + /** + * @brief fetch a block's size + * + * The subclass should return the size of the block with the + * given height. + * + * If the block does not exist, the subclass should throw BLOCK_DNE + * + * @param height the height requested + * + * @return the size + */ virtual size_t get_block_size(const uint64_t& height) const = 0; - // return cumulative difficulty up to and including block at height + /** + * @brief fetch a block's cumulative difficulty + * + * The subclass should return the cumulative difficulty of the block with the + * given height. + * + * If the block does not exist, the subclass should throw BLOCK_DNE + * + * @param height the height requested + * + * @return the cumulative difficulty + */ virtual difficulty_type get_block_cumulative_difficulty(const uint64_t& height) const = 0; - // return difficulty of block at height + /** + * @brief fetch a block's difficulty + * + * The subclass should return the difficulty of the block with the + * given height. + * + * If the block does not exist, the subclass should throw BLOCK_DNE + * + * @param height the height requested + * + * @return the difficulty + */ virtual difficulty_type get_block_difficulty(const uint64_t& height) const = 0; - // return number of coins generated up to and including block at height + /** + * @brief fetch a block's already generated coins + * + * The subclass should return the total coins generated as of the block + * with the given height. + * + * If the block does not exist, the subclass should throw BLOCK_DNE + * + * @param height the height requested + * + * @return the already generated coins + */ virtual uint64_t get_block_already_generated_coins(const uint64_t& height) const = 0; - // return hash of block at height + /** + * @brief fetch a block's hash + * + * The subclass should return hash of the block with the + * given height. + * + * If the block does not exist, the subclass should throw BLOCK_DNE + * + * @param height the height requested + * + * @return the hash + */ virtual crypto::hash get_block_hash_from_height(const uint64_t& height) const = 0; - // return vector of blocks in range of height (inclusively) + /** + * @brief fetch a list of blocks + * + * The subclass should return a vector of blocks with heights starting at + * h1 and ending at h2, inclusively. + * + * If the height range requested goes past the end of the blockchain, + * the subclass should throw BLOCK_DNE. (current implementations simply + * don't catch this exception as thrown by methods called within) + * + * @param h1 the start height + * @param h2 the end height + * + * @return a vector of blocks + */ virtual std::vector get_blocks_range(const uint64_t& h1, const uint64_t& h2) const = 0; - // return vector of block hashes in range of height (inclusively) + /** + * @brief fetch a list of block hashes + * + * The subclass should return a vector of block hashes from blocks with + * heights starting at h1 and ending at h2, inclusively. + * + * If the height range requested goes past the end of the blockchain, + * the subclass should throw BLOCK_DNE. (current implementations simply + * don't catch this exception as thrown by methods called within) + * + * @param h1 the start height + * @param h2 the end height + * + * @return a vector of block hashes + */ virtual std::vector get_hashes_range(const uint64_t& h1, const uint64_t& h2) const = 0; - // return the hash of the top block on the chain + /** + * @brief fetch the top block's hash + * + * The subclass should return the hash of the most recent block + * + * @return the top block's hash + */ virtual crypto::hash top_block_hash() const = 0; - // return the block at the top of the blockchain + /** + * @brief fetch the top block + * + * The subclass should return most recent block + * + * @return the top block + */ virtual block get_top_block() const = 0; - // return the height of the chain + /** + * @brief fetch the current blockchain height + * + * The subclass should return the current blockchain height + * + * @return the current blockchain height + */ virtual uint64_t height() const = 0; - // pops the top block off the blockchain. - // Returns by reference the popped block and its associated transactions - // - // IMPORTANT: - // When a block is popped, the transactions associated with it need to be - // removed, as well as outputs and spent key images associated with - // those transactions. + + /** + * + * + * @brief pops the top block off the blockchain + * + * The subclass should remove the most recent block from the blockchain, + * along with all transactions, outputs, and other metadata created as + * a result of its addition to the blockchain. Most of this is handled + * by the concrete members of the base class provided the subclass correctly + * implements remove_* functions. + * + * The subclass should return by reference the popped block and + * its associated transactions + * + * @param blk return-by-reference the block which was popped + * @param txs return-by-reference the transactions from the popped block + */ virtual void pop_block(block& blk, std::vector& txs); - // return true if a transaction with hash exists + /** + * @brief check if a transaction with a given hash exists + * + * The subclass should check if a transaction is stored which has the + * given hash and return true if so, false otherwise. + * + * @param h the hash to check against + * + * @return true if the transaction exists, otherwise false + */ virtual bool tx_exists(const crypto::hash& h) const = 0; // return unlock time of tx with hash + /** + * @brief fetch a transaction's unlock time/height + * + * The subclass should return the stored unlock time for the transaction + * with the given hash. + * + * If no such transaction exists, the subclass should throw TX_DNE. + * + * @param h the hash of the requested transaction + * + * @return the unlock time/height + */ virtual uint64_t get_tx_unlock_time(const crypto::hash& h) const = 0; // return tx with hash // throw if no such tx exists + /** + * @brief fetches the transaction with the given hash + * + * The subclass should return the transaction stored which has the given + * hash. + * + * If the transaction does not exist, the subclass should throw TX_DNE. + * + * @param h the hash to look for + * + * @return the transaction with the given hash + */ virtual transaction get_tx(const crypto::hash& h) const = 0; - // returns the total number of transactions in all blocks + /** + * @brief fetches the total number of transactions ever + * + * The subclass should return a count of all the transactions from + * all blocks. + * + * @return the number of transactions in the blockchain + */ virtual uint64_t get_tx_count() const = 0; - // return list of tx with hashes . - // TODO: decide if a missing hash means return empty list - // or just skip that hash + /** + * @brief fetches a list of transactions based on their hashes + * + * The subclass should attempt to fetch each transaction referred to by + * the hashes passed. + * + * Currently, if any of the transactions is not in BlockchainDB, the call + * to get_tx in the implementation will throw TX_DNE. + * + * + * + * @param hlist a list of hashes + * + * @return the list of transactions + */ virtual std::vector get_tx_list(const std::vector& hlist) const = 0; // returns height of block that contains transaction with hash + /** + * @brief fetches the height of a transaction's block + * + * The subclass should attempt to return the height of the block containing + * the transaction with the given hash. + * + * If the transaction cannot be found, the subclass should throw TX_DNE. + * + * @param h the hash of the transaction + * + * @return the height of the transaction's block + */ virtual uint64_t get_tx_block_height(const crypto::hash& h) const = 0; // returns the total number of outputs of amount + /** + * @brief fetches the number of outputs of a given amount + * + * The subclass should return a count of outputs of the given amount, + * or zero if there are none. + * + * + * + * @param amount the output amount being looked up + * + * @return the number of outputs of the given amount + */ virtual uint64_t get_num_outputs(const uint64_t& amount) const = 0; - // return index of the first element (should be hidden, but isn't) + /** + * @brief return index of the first element (should be hidden, but isn't) + * + * @return the index + */ virtual uint64_t get_indexing_base() const { return 0; } - // return public key for output with global output amount and index + /** + * @brief get some of an output's data + * + * The subclass should return the public key, unlock time, and block height + * for the output with the given amount and index, collected in a struct. + * + * If the output cannot be found, the subclass should throw OUTPUT_DNE. + * + * If any of these parts cannot be found, but some are, the subclass + * should throw DB_ERROR with a message stating as much. + * + * @param amount the output amount + * @param index the output's index (indexed by amount) + * + * @return the requested output data + */ virtual output_data_t get_output_key(const uint64_t& amount, const uint64_t& index) = 0; + + /** + * @brief get some of an output's data + * + * The subclass should return the public key, unlock time, and block height + * for the output with the given global index, collected in a struct. + * + * If the output cannot be found, the subclass should throw OUTPUT_DNE. + * + * If any of these parts cannot be found, but some are, the subclass + * should throw DB_ERROR with a message stating as much. + * + * @param global_index the output's index (global) + * + * @return the requested output data + */ virtual output_data_t get_output_key(const uint64_t& global_index) const = 0; - // returns the tx hash associated with an output, referenced by global output index + /** + * @brief gets an output's tx hash and index + * + * The subclass should return the hash of the transaction which created the + * output with the global index given, as well as its index in that transaction. + * + * @param index an output's global index + * + * @return the tx hash and output index + */ virtual tx_out_index get_output_tx_and_index_from_global(const uint64_t& index) const = 0; - // returns the transaction-local reference for the output with at - // return type is pair of tx hash and index + /** + * @brief gets an output's tx hash and index + * + * The subclass should return the hash of the transaction which created the + * output with the amount and index given, as well as its index in that + * transaction. + * + * @param amount an output amount + * @param index an output's amount-specific index + * + * @return the tx hash and output index + */ virtual tx_out_index get_output_tx_and_index(const uint64_t& amount, const uint64_t& index) = 0; - virtual void get_output_tx_and_index(const uint64_t& amount, const std::vector &offsets, std::vector &indices) = 0; - virtual void get_output_key(const uint64_t &amount, const std::vector &offsets, std::vector &outputs) = 0; + /** + * @brief gets some outputs' tx hashes and indices + * + * This function is a mirror of + * get_output_tx_and_index(const uint64_t& amount, const uint64_t& index), + * but for a list of outputs rather than just one. + * + * @param amount an output amount + * @param offsets a list of amount-specific output indices + * @param indices return-by-reference a list of tx hashes and output indices (as pairs) + */ + virtual void get_output_tx_and_index(const uint64_t& amount, const std::vector &offsets, std::vector &indices) = 0; + + /** + * @brief gets outputs' data + * + * This function is a mirror of + * get_output_data(const uint64_t& amount, const uint64_t& index) + * but for a list of outputs rather than just one. + * + * @param amount an output amount + * @param offsets a list of amount-specific output indices + * @param outputs return-by-reference a list of outputs' metadata + */ + virtual void get_output_key(const uint64_t &amount, const std::vector &offsets, std::vector &outputs) = 0; + + /* + * FIXME: Need to check with git blame and ask what this does to + * document it + */ virtual bool can_thread_bulk_indices() const = 0; - // return a vector of indices corresponding to the global output index for - // each output in the transaction with hash + /** + * @brief gets output indices (global) for a transaction's outputs + * + * The subclass should fetch the global output indices for each output + * in the transaction with the given hash. + * + * If the transaction does not exist, the subclass should throw TX_DNE. + * + * If an output cannot be found, the subclass should throw OUTPUT_DNE. + * + * @param h a transaction hash + * + * @return a list of global output indices + */ virtual std::vector get_tx_output_indices(const crypto::hash& h) const = 0; - // return a vector of indices corresponding to the amount output index for - // each output in the transaction with hash + + /** + * @brief gets output indices (amount-specific) for a transaction's outputs + * + * The subclass should fetch the amount-specific output indices for each + * output in the transaction with the given hash. + * + * If the transaction does not exist, the subclass should throw TX_DNE. + * + * If an output cannot be found, the subclass should throw OUTPUT_DNE. + * + * @param h a transaction hash + * + * @return a list of amount-specific output indices + */ virtual std::vector get_tx_amount_output_indices(const crypto::hash& h) const = 0; - // returns true if key image is present in spent key images storage + /** + * @brief check if a key image is stored as spent + * + * @param img the key image to check for + * + * @return true if the image is present, otherwise false + */ virtual bool has_key_image(const crypto::key_image& img) const = 0; + /** + * @brief runs a function over all key images stored + * + * The subclass should run the passed function for each key image it has + * stored, passing the key image as its parameter. + * + * If any call to the function returns false, the subclass should return + * false. Otherwise, the subclass returns true. + * + * @param std::function fn the function to run + * + * @return false if the function returns false for any key image, otherwise true + */ virtual bool for_all_key_images(std::function) const = 0; + + /** + * @brief runs a function over all blocks stored + * + * The subclass should run the passed function for each block it has + * stored, passing (block_height, block_hash, block) as its parameters. + * + * If any call to the function returns false, the subclass should return + * false. Otherwise, the subclass returns true. + * + * The subclass should throw DB_ERROR if any of the expected values are + * not found. Current implementations simply return false. + * + * @param std::function fn the function to run + * + * @return false if the function returns false for any block, otherwise true + */ virtual bool for_all_blocks(std::function) const = 0; + + /** + * @brief runs a function over all transactions stored + * + * The subclass should run the passed function for each transaction it has + * stored, passing (transaction_hash, transaction) as its parameters. + * + * If any call to the function returns false, the subclass should return + * false. Otherwise, the subclass returns true. + * + * The subclass should throw DB_ERROR if any of the expected values are + * not found. Current implementations simply return false. + * + * @param std::function fn the function to run + * + * @return false if the function returns false for any transaction, otherwise true + */ virtual bool for_all_transactions(std::function) const = 0; + + /** + * @brief runs a function over all outputs stored + * + * The subclass should run the passed function for each output it has + * stored, passing (amount, transaction_hash, tx_local_output_index) + * as its parameters. + * + * If any call to the function returns false, the subclass should return + * false. Otherwise, the subclass returns true. + * + * The subclass should throw DB_ERROR if any of the expected values are + * not found. Current implementations simply return false. + * + * @param std::function f the function to run + * + * @return false if the function returns false for any output, otherwise true + */ virtual bool for_all_outputs(std::function f) const = 0; + + // // Hard fork related storage + // + + // FIXME: verify that this is all correct + // - TW + /** + * @brief sets the height at which a hard fork has been voted to happen + * + * + * @param version the version voted to fork to + * @param height the height of the first block on the new fork + */ virtual void set_hard_fork_starting_height(uint8_t version, uint64_t height) = 0; + + /** + * @brief gets the height at which a hard fork has been voted to happen + * + * @param version the version to check + * + * @return the height at which the hard fork was accepted, if it has been, + * otherwise max(uint64_t) + */ virtual uint64_t get_hard_fork_starting_height(uint8_t version) const = 0; + + /** + * @brief sets which hardfork version a height is on + * + * @param height the height + * @param version the version + */ virtual void set_hard_fork_version(uint64_t height, uint8_t version) = 0; + + /** + * @brief checks which hardfork version a height is on + * + * @param height the height + * + * @return the version + */ virtual uint8_t get_hard_fork_version(uint64_t height) const = 0; + + /** + * @brief verify hard fork info in database + */ virtual void check_hard_fork_info() = 0; + + /** + * @brief delete hard fork info from database + */ virtual void drop_hard_fork_info() = 0; + /** + * @brief is BlockchainDB in read-only mode? + * + * @return true if in read-only mode, otherwise false + */ virtual bool is_read_only() const = 0; - // fix up anything that may be wrong due to past bugs + // TODO: this should perhaps be (or call) a series of functions which + // progressively update through version updates + /** + * @brief fix up anything that may be wrong due to past bugs + */ virtual void fixup(); + /** + * @brief set whether or not to automatically remove logs + * + * This function is only relevant for one implementation (BlockchainBDB), but + * is here to keep BlockchainDB users implementation-agnostic. + * + * @param auto_remove whether or not to auto-remove logs + */ void set_auto_remove_logs(bool auto_remove) { m_auto_remove_logs = auto_remove; } - bool m_open; - mutable epee::critical_section m_synchronization_lock; + bool m_open; //!< Whether or not the BlockchainDB is open/ready for use + mutable epee::critical_section m_synchronization_lock; //!< A lock, currently for when BlockchainLMDB needs to resize the backing db file + }; // class BlockchainDB From a2e378b91bf5d2a54284f8e20df5080f11d25ff6 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Fri, 25 Mar 2016 00:48:11 +0000 Subject: [PATCH 33/47] wallet: add a --generate-from-json flag It takes a filename containing JSON data to generate a wallet. The following fields are valid: version: integer, should be 1 filename: string, path/filename for the newly created wallet scan_from_height: 64 bit unsigned integer, optional password: string, optional viewkey: string, hex representation spendkey: string, hex representation seed: string, optional, list of words separated by spaces Either seed or private keys should be given. If using private keys, the spend key may be omitted (the wallet will not be able to spend, but will see incoming transactions). If scan_from_height is given, blocks below this height will not be checked for transactions as an optimization. --- src/simplewallet/simplewallet.cpp | 186 +++++++++++++++++++++++++++++- src/simplewallet/simplewallet.h | 2 + src/wallet/wallet2.cpp | 2 +- src/wallet/wallet2.h | 12 +- 4 files changed, 192 insertions(+), 10 deletions(-) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index ea1993c8..1856118f 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -57,6 +57,7 @@ #include "version.h" #include "crypto/crypto.h" // for crypto::secret_key definition #include "mnemonics/electrum-words.h" +#include "rapidjson/document.h" #include #if defined(WIN32) @@ -81,6 +82,7 @@ namespace const command_line::arg_descriptor arg_generate_new_wallet = {"generate-new-wallet", sw::tr("Generate new wallet and save it to or
.wallet by default"), ""}; const command_line::arg_descriptor arg_generate_from_view_key = {"generate-from-view-key", sw::tr("Generate incoming-only wallet from view key"), ""}; const command_line::arg_descriptor arg_generate_from_keys = {"generate-from-keys", sw::tr("Generate wallet from private keys"), ""}; + const command_line::arg_descriptor arg_generate_from_json = {"generate-from-json", sw::tr("Generate wallet from JSON format file"), ""}; const command_line::arg_descriptor arg_daemon_address = {"daemon-address", sw::tr("Use daemon instance at :"), ""}; const command_line::arg_descriptor arg_daemon_host = {"daemon-host", sw::tr("Use daemon instance at host instead of localhost"), ""}; const command_line::arg_descriptor arg_password = {"password", sw::tr("Wallet password"), "", true}; @@ -716,7 +718,7 @@ bool simple_wallet::ask_wallet_create_if_needed() // add logic to error out if new wallet requested but named wallet file exists if (keys_file_exists || wallet_file_exists) { - if (!m_generate_new.empty() || m_restore_deterministic_wallet || !m_generate_from_view_key.empty() || !m_generate_from_keys.empty()) + if (!m_generate_new.empty() || m_restore_deterministic_wallet || !m_generate_from_view_key.empty() || !m_generate_from_keys.empty() || !m_generate_from_json.empty()) { fail_msg_writer() << tr("attempting to generate or restore wallet, but specified file(s) exist. Exiting to not risk overwriting."); return false; @@ -763,6 +765,13 @@ void simple_wallet::print_seed(std::string seed) //---------------------------------------------------------------------------------------------------- static bool get_password(const boost::program_options::variables_map& vm, bool allow_entry, tools::password_container &pwd_container) { + // has_arg returns true even when the parameter is not passed ?? + const std::string gfj = command_line::get_arg(vm, arg_generate_from_json); + if (!gfj.empty()) { + // will be in the json file, if any + return true; + } + if (has_arg(vm, arg_password) && has_arg(vm, arg_password_file)) { fail_msg_writer() << tr("can't specify more than one of --password and --password-file"); @@ -808,6 +817,153 @@ static bool get_password(const boost::program_options::variables_map& vm, bool a return false; } +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::generate_from_json(const boost::program_options::variables_map& vm, std::string &wallet_file, std::string &password) +{ + std::string buf; + bool r = epee::file_io_utils::load_file_to_string(m_generate_from_json, buf); + if (!r) { + fail_msg_writer() << tr("Failed to load file ") << m_generate_from_json; + return false; + } + + rapidjson::Document json; + if (json.Parse(buf.c_str()).HasParseError()) { + fail_msg_writer() << tr("Failed to parse JSON"); + return false; + } + + if (!json.HasMember("version")) { + fail_msg_writer() << tr("Version not found in JSON"); + return false; + } + unsigned int version = json["version"].GetUint(); + const int current_version = 1; + if (version > current_version) { + fail_msg_writer() << boost::format(tr("Version %u too new, we can only grok up to %u")) % version % current_version; + return false; + } + if (!json.HasMember("filename")) { + fail_msg_writer() << tr("Filename not found in JSON"); + return false; + } + std::string filename = json["filename"].GetString(); + + bool recover = false; + uint64_t scan_from_height = 0; + if (json.HasMember("scan_from_height")) { + scan_from_height = json["scan_from_height"].GetUint64(); + recover = true; + } + + password = ""; + if (json.HasMember("password")) { + password = json["password"].GetString(); + } + + std::string viewkey_string(""); + crypto::secret_key viewkey; + if (json.HasMember("viewkey")) { + viewkey_string = json["viewkey"].GetString(); + cryptonote::blobdata viewkey_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(viewkey_string, viewkey_data)) + { + fail_msg_writer() << tr("failed to parse view key secret key"); + return false; + } + viewkey = *reinterpret_cast(viewkey_data.data()); + } + + std::string spendkey_string(""); + crypto::secret_key spendkey; + if (json.HasMember("spendkey")) { + spendkey_string = json["spendkey"].GetString(); + cryptonote::blobdata spendkey_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(spendkey_string, spendkey_data)) + { + fail_msg_writer() << tr("failed to parse spend key secret key"); + return false; + } + spendkey = *reinterpret_cast(spendkey_data.data()); + } + + std::string seed(""); + std::string old_language; + if (json.HasMember("seed")) { + seed = json["seed"].GetString(); + if (!crypto::ElectrumWords::words_to_bytes(seed, m_recovery_key, old_language)) + { + fail_msg_writer() << tr("Electrum-style word list failed verification"); + return false; + } + m_electrum_seed = seed; + m_restore_deterministic_wallet = true; + } + + // compatibility checks + if (seed.empty() && viewkey_string.empty()) { + fail_msg_writer() << tr("At least one of Electrum-style word list and private view key must be specified"); + return false; + } + if (!seed.empty() && (!viewkey_string.empty() || !spendkey_string.empty())) { + fail_msg_writer() << tr("Both Electrum-style word list and private key(s) specified"); + return false; + } + + m_wallet_file = filename; + + bool was_deprecated_wallet = m_restore_deterministic_wallet && ((old_language == crypto::ElectrumWords::old_language_name) || + crypto::ElectrumWords::get_is_old_style_seed(m_electrum_seed)); + if (was_deprecated_wallet) { + fail_msg_writer() << tr("Cannot create deprecated wallets from JSON"); + return false; + } + + bool testnet = command_line::get_arg(vm, arg_testnet); + m_wallet.reset(new tools::wallet2(testnet)); + m_wallet->callback(this); + + try + { + if (!seed.empty()) + { + m_wallet->generate(m_wallet_file, password, m_recovery_key, recover, false); + } + else + { + cryptonote::account_public_address address; + if (!crypto::secret_key_to_public_key(viewkey, address.m_view_public_key)) { + fail_msg_writer() << tr("failed to verify view key secret key"); + return false; + } + if (!crypto::secret_key_to_public_key(spendkey, address.m_spend_public_key)) { + fail_msg_writer() << tr("failed to verify spend key secret key"); + return false; + } + + if (spendkey_string.empty()) + { + m_wallet->generate(m_wallet_file, password, address, viewkey); + } + else + { + m_wallet->generate(m_wallet_file, password, address, spendkey, viewkey); + } + } + } + catch (const std::exception& e) + { + fail_msg_writer() << tr("failed to generate new wallet: ") << e.what(); + return false; + } + + m_wallet->set_refresh_from_block_height(scan_from_height); + + wallet_file = m_wallet_file; + + return r; +} + //---------------------------------------------------------------------------------------------------- bool simple_wallet::init(const boost::program_options::variables_map& vm) { @@ -820,12 +976,12 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) return false; } - if((!m_generate_new.empty()) + (!m_wallet_file.empty()) + (!m_generate_from_view_key.empty()) + (!m_generate_from_keys.empty()) > 1) + if((!m_generate_new.empty()) + (!m_wallet_file.empty()) + (!m_generate_from_view_key.empty()) + (!m_generate_from_keys.empty()) + (!m_generate_from_json.empty()) > 1) { - fail_msg_writer() << tr("can't specify more than one of --generate-new-wallet=\"wallet_name\", --wallet-file=\"wallet_name\", --generate-from-view-key=\"wallet_name\" and --generate-from-keys=\"wallet_name\""); + fail_msg_writer() << tr("can't specify more than one of --generate-new-wallet=\"wallet_name\", --wallet-file=\"wallet_name\", --generate-from-view-key=\"wallet_name\", --generate-from-json=\"jsonfilename\" and --generate-from-keys=\"wallet_name\""); return false; } - else if (m_generate_new.empty() && m_wallet_file.empty() && m_generate_from_view_key.empty() && m_generate_from_keys.empty()) + else if (m_generate_new.empty() && m_wallet_file.empty() && m_generate_from_view_key.empty() && m_generate_from_keys.empty() && m_generate_from_json.empty()) { if(!ask_wallet_create_if_needed()) return false; } @@ -847,7 +1003,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) if (!get_password(vm, true, pwd_container)) return false; - if (!m_generate_new.empty() || m_restore_deterministic_wallet || !m_generate_from_view_key.empty() || !m_generate_from_keys.empty()) + if (!m_generate_new.empty() || m_restore_deterministic_wallet || !m_generate_from_view_key.empty() || !m_generate_from_keys.empty() || !m_generate_from_json.empty()) { if (m_wallet_file.empty()) m_wallet_file = m_generate_new; // alias for simplicity later @@ -993,6 +1149,12 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) bool r = new_wallet(m_wallet_file, pwd_container.password(), address, spendkey, viewkey, testnet); CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); } + else if (!m_generate_from_json.empty()) + { + std::string wallet_file, password; // we don't need to remember them + if (!generate_from_json(vm, wallet_file, password)) + return false; + } else { bool r = new_wallet(m_wallet_file, pwd_container.password(), m_recovery_key, m_restore_deterministic_wallet, @@ -1023,6 +1185,7 @@ bool simple_wallet::handle_command_line(const boost::program_options::variables_ m_generate_new = command_line::get_arg(vm, arg_generate_new_wallet); m_generate_from_view_key = command_line::get_arg(vm, arg_generate_from_view_key); m_generate_from_keys = command_line::get_arg(vm, arg_generate_from_keys); + m_generate_from_json = command_line::get_arg(vm, arg_generate_from_json); m_daemon_address = command_line::get_arg(vm, arg_daemon_address); m_daemon_host = command_line::get_arg(vm, arg_daemon_host); m_daemon_port = command_line::get_arg(vm, arg_daemon_port); @@ -2575,6 +2738,7 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_params, arg_generate_new_wallet); command_line::add_arg(desc_params, arg_generate_from_view_key); command_line::add_arg(desc_params, arg_generate_from_keys); + command_line::add_arg(desc_params, arg_generate_from_json); command_line::add_arg(desc_params, arg_password); command_line::add_arg(desc_params, arg_password_file); command_line::add_arg(desc_params, arg_daemon_address); @@ -2707,11 +2871,21 @@ int main(int argc, char* argv[]) if (daemon_address.empty()) daemon_address = std::string("http://") + daemon_host + ":" + std::to_string(daemon_port); + std::string password; + const std::string gfj = command_line::get_arg(vm, arg_generate_from_json); + if (!gfj.empty()) { + if (!w.generate_from_json(vm, wallet_file, password)) + return 1; + } + else { + password = pwd_container.password(); + } + tools::wallet2 wal(testnet,restricted); try { LOG_PRINT_L0(sw::tr("Loading wallet...")); - wal.load(wallet_file, pwd_container.password()); + wal.load(wallet_file, password); wal.init(daemon_address); wal.refresh(); LOG_PRINT_GREEN(sw::tr("Loaded ok"), LOG_LEVEL_0); diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index d1617dbf..7dadb853 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -69,6 +69,7 @@ namespace cryptonote bool run(); void stop(); void interrupt(); + bool generate_from_json(const boost::program_options::variables_map& vm, std::string &wallet_file, std::string &password); //wallet *create_wallet(); bool process_command(const std::vector &args); @@ -221,6 +222,7 @@ namespace cryptonote std::string m_generate_new; std::string m_generate_from_view_key; std::string m_generate_from_keys; + std::string m_generate_from_json; std::string m_import_path; std::string m_electrum_seed; // electrum-style seed parameter diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 2afe08cb..1c7187cf 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -490,7 +490,7 @@ void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cry //handle transactions from new block //optimization: seeking only for blocks that are not older then the wallet creation time plus 1 day. 1 day is for possible user incorrect time setup - if(b.timestamp + 60*60*24 > m_account.get_createtime()) + if(b.timestamp + 60*60*24 > m_account.get_createtime() && height >= m_refresh_from_block_height) { TIME_MEASURE_START(miner_tx_handle_time); process_new_transaction(b.miner_tx, height, true); diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index f798f404..e7b00292 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -88,10 +88,10 @@ namespace tools }; private: - wallet2(const wallet2&) : m_run(true), m_callback(0), m_testnet(false), m_always_confirm_transfers (false), m_store_tx_info(true), m_default_mixin(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true) {} + wallet2(const wallet2&) : m_run(true), m_callback(0), m_testnet(false), m_always_confirm_transfers (false), m_store_tx_info(true), m_default_mixin(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true), m_refresh_from_block_height(0) {} public: - wallet2(bool testnet = false, bool restricted = false) : m_run(true), m_callback(0), m_testnet(testnet), m_restricted(restricted), is_old_file_format(false), m_store_tx_info(true), m_default_mixin(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true) {} + wallet2(bool testnet = false, bool restricted = false) : m_run(true), m_callback(0), m_testnet(testnet), m_restricted(restricted), is_old_file_format(false), m_store_tx_info(true), m_default_mixin(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true), m_refresh_from_block_height(0) {} struct transfer_details { uint64_t m_block_height; @@ -226,6 +226,8 @@ namespace tools cryptonote::account_base& get_account(){return m_account;} const cryptonote::account_base& get_account()const{return m_account;} + void set_refresh_from_block_height(uint64_t height) {m_refresh_from_block_height = height;} + // upper_transaction_size_limit as defined below is set to // approximately 125% of the fixed minimum allowable penalty // free block size. TODO: fix this so that it actually takes @@ -319,6 +321,9 @@ namespace tools if(ver < 9) return; a & m_confirmed_txs; + if(ver < 11) + return; + a & m_refresh_from_block_height; } /*! @@ -430,9 +435,10 @@ namespace tools uint32_t m_default_mixin; RefreshType m_refresh_type; bool m_auto_refresh; + uint64_t m_refresh_from_block_height; }; } -BOOST_CLASS_VERSION(tools::wallet2, 10) +BOOST_CLASS_VERSION(tools::wallet2, 11) BOOST_CLASS_VERSION(tools::wallet2::payment_details, 0) BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 2) BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 1) From 01e0a69c175852adb01897431a138739faf39fe1 Mon Sep 17 00:00:00 2001 From: Riccardo Spagni Date: Fri, 25 Mar 2016 08:42:42 +0200 Subject: [PATCH 34/47] Revert "Merge pull request #749" This reverts commit 7fa63a82a1c3a0243f6757c1689855ed3ca61695, reversing changes made to cb6be986c36b78eddb4b7f16e9ad440af8567dc4. --- Doxyfile | 14 +- src/blockchain_db/blockchain_db.h | 1092 +++----------------- src/cryptonote_core/CMakeLists.txt | 2 + src/cryptonote_core/blockchain.cpp | 49 +- src/cryptonote_core/blockchain.h | 890 +--------------- src/cryptonote_core/blockchain_storage.cpp | 8 +- src/cryptonote_core/checkpoints.cpp | 251 +---- src/cryptonote_core/checkpoints.h | 181 +--- src/cryptonote_core/checkpoints_create.cpp | 280 +++++ src/cryptonote_core/checkpoints_create.h | 48 + src/cryptonote_core/cryptonote_core.cpp | 19 +- src/cryptonote_core/cryptonote_core.h | 650 +----------- src/cryptonote_core/difficulty.cpp | 6 +- src/cryptonote_core/difficulty.h | 12 - src/daemon/core.h | 1 + 15 files changed, 594 insertions(+), 2909 deletions(-) create mode 100644 src/cryptonote_core/checkpoints_create.cpp create mode 100644 src/cryptonote_core/checkpoints_create.h diff --git a/Doxyfile b/Doxyfile index 93a5c6e7..a70ef812 100644 --- a/Doxyfile +++ b/Doxyfile @@ -409,13 +409,13 @@ LOOKUP_CACHE_SIZE = 0 # normally produced when WARNINGS is set to YES. # The default value is: NO. -EXTRACT_ALL = YES +EXTRACT_ALL = NO # If the EXTRACT_PRIVATE tag is set to YES all private members of a class will # be included in the documentation. # The default value is: NO. -EXTRACT_PRIVATE = YES +EXTRACT_PRIVATE = NO # If the EXTRACT_PACKAGE tag is set to YES all members with package or internal # scope will be included in the documentation. @@ -427,7 +427,7 @@ EXTRACT_PACKAGE = NO # included in the documentation. # The default value is: NO. -EXTRACT_STATIC = YES +EXTRACT_STATIC = NO # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined # locally in source files will be included in the documentation. If set to NO @@ -452,7 +452,7 @@ EXTRACT_LOCAL_METHODS = NO # are hidden. # The default value is: NO. -EXTRACT_ANON_NSPACES = YES +EXTRACT_ANON_NSPACES = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all # undocumented members inside documented classes or files. If set to NO these @@ -1902,7 +1902,7 @@ ENABLE_PREPROCESSING = YES # The default value is: NO. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -MACRO_EXPANSION = YES +MACRO_EXPANSION = NO # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then # the macro expansion is limited to the macros specified with the PREDEFINED and @@ -1910,7 +1910,7 @@ MACRO_EXPANSION = YES # The default value is: NO. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -EXPAND_ONLY_PREDEF = YES +EXPAND_ONLY_PREDEF = NO # If the SEARCH_INCLUDES tag is set to YES the includes files in the # INCLUDE_PATH will be searched if a #include is found. @@ -1942,7 +1942,7 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = "BLOCKCHAIN_DB=2" \ # DB_LMDB +PREDEFINED = # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index 3e0ca141..3396b8c2 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -38,20 +38,18 @@ #include "cryptonote_core/difficulty.h" #include "cryptonote_core/hardfork.h" -/** \file - * Cryptonote Blockchain Database Interface +/* DB Driver Interface * * The DB interface is a store for the canonical block chain. * It serves as a persistent storage for the blockchain. * - * For the sake of efficiency, a concrete implementation may also + * For the sake of efficiency, the reference implementation will also * store some blockchain data outside of the blocks, such as spent * transfer key images, unspent transaction outputs, etc. * - * Examples are as follows: - * * Transactions are duplicated so that we don't have to fetch a whole block - * in order to fetch a transaction from that block. + * in order to fetch a transaction from that block. If this is deemed + * unnecessary later, this can change. * * Spent key images are duplicated outside of the blocks so it is quick * to verify an output hasn't already been spent @@ -59,34 +57,100 @@ * Unspent transaction outputs are duplicated to quickly gather random * outputs to use for mixins * + * IMPORTANT: + * A concrete implementation of this interface should populate these + * duplicated members! It is possible to have a partial implementation + * of this interface call to private members of the interface to be added + * later that will then populate as needed. + * + * General: + * open() + * is_open() + * close() + * sync() + * reset() + * + * Lock and unlock provided for reorg externally, and for block + * additions internally, this way threaded reads are completely fine + * unless the blockchain is changing. + * bool lock() + * unlock() + * + * vector get_filenames() + * + * Blocks: + * bool block_exists(hash) + * height add_block(block, block_size, cumulative_difficulty, coins_generated, transactions) + * block get_block(hash) + * height get_block_height(hash) + * header get_block_header(hash) + * block get_block_from_height(height) + * size_t get_block_size(height) + * difficulty get_block_cumulative_difficulty(height) + * uint64_t get_block_already_generated_coins(height) + * uint64_t get_block_timestamp(height) + * uint64_t get_top_block_timestamp() + * hash get_block_hash_from_height(height) + * blocks get_blocks_range(height1, height2) + * hashes get_hashes_range(height1, height2) + * hash top_block_hash() + * block get_top_block() + * height height() + * void pop_block(block&, tx_list&) + * + * Transactions: + * bool tx_exists(hash) + * uint64_t get_tx_unlock_time(hash) + * tx get_tx(hash) + * uint64_t get_tx_count() + * tx_list get_tx_list(hash_list) + * height get_tx_block_height(hash) + * + * Outputs: + * uint64_t get_num_outputs(amount) + * pub_key get_output_key(amount, index) + * hash,index get_output_tx_and_index_from_global(index) + * hash,index get_output_tx_and_index(amount, index) + * vec get_tx_output_indices(tx_hash) + * + * + * Spent Output Key Images: + * bool has_key_image(key_image) + * + * Exceptions: + * DB_ERROR -- generic + * DB_OPEN_FAILURE + * DB_CREATE_FAILURE + * DB_SYNC_FAILURE + * BLOCK_DNE + * BLOCK_PARENT_DNE + * BLOCK_EXISTS + * BLOCK_INVALID -- considering making this multiple errors + * TX_DNE + * TX_EXISTS + * OUTPUT_DNE + * OUTPUT_EXISTS + * KEY_IMAGE_EXISTS */ namespace cryptonote { -/** a pair of , typedef for convenience */ +// typedef for convenience typedef std::pair tx_out_index; #pragma pack(push, 1) - -/** - * @brief a struct containing output metadata - */ struct output_data_t { - crypto::public_key pubkey; //!< the output's public key (for spend verification) - uint64_t unlock_time; //!< the output's unlock time (or height) - uint64_t height; //!< the height of the block which created the output + crypto::public_key pubkey; + uint64_t unlock_time; + uint64_t height; }; #pragma pack(pop) /*********************************** * Exception Definitions ***********************************/ - -/** - * @brief A base class for BlockchainDB exceptions - */ class DB_EXCEPTION : public std::exception { private: @@ -104,9 +168,6 @@ class DB_EXCEPTION : public std::exception } }; -/** - * @brief A generic BlockchainDB exception - */ class DB_ERROR : public DB_EXCEPTION { public: @@ -114,9 +175,7 @@ class DB_ERROR : public DB_EXCEPTION DB_ERROR(const char* s) : DB_EXCEPTION(s) { } }; -/** - * @brief thrown when there is an error starting a DB transaction - */ +// For distinguishing errors trying to set up a DB txn from other errors class DB_ERROR_TXN_START : public DB_EXCEPTION { public: @@ -124,9 +183,6 @@ class DB_ERROR_TXN_START : public DB_EXCEPTION DB_ERROR_TXN_START(const char* s) : DB_EXCEPTION(s) { } }; -/** - * @brief thrown when opening the BlockchainDB fails - */ class DB_OPEN_FAILURE : public DB_EXCEPTION { public: @@ -134,9 +190,6 @@ class DB_OPEN_FAILURE : public DB_EXCEPTION DB_OPEN_FAILURE(const char* s) : DB_EXCEPTION(s) { } }; -/** - * @brief thrown when creating the BlockchainDB fails - */ class DB_CREATE_FAILURE : public DB_EXCEPTION { public: @@ -144,9 +197,6 @@ class DB_CREATE_FAILURE : public DB_EXCEPTION DB_CREATE_FAILURE(const char* s) : DB_EXCEPTION(s) { } }; -/** - * @brief thrown when synchronizing the BlockchainDB to disk fails - */ class DB_SYNC_FAILURE : public DB_EXCEPTION { public: @@ -154,9 +204,6 @@ class DB_SYNC_FAILURE : public DB_EXCEPTION DB_SYNC_FAILURE(const char* s) : DB_EXCEPTION(s) { } }; -/** - * @brief thrown when a requested block does not exist - */ class BLOCK_DNE : public DB_EXCEPTION { public: @@ -164,9 +211,6 @@ class BLOCK_DNE : public DB_EXCEPTION BLOCK_DNE(const char* s) : DB_EXCEPTION(s) { } }; -/** - * @brief thrown when a block's parent does not exist (and it needed to) - */ class BLOCK_PARENT_DNE : public DB_EXCEPTION { public: @@ -174,9 +218,6 @@ class BLOCK_PARENT_DNE : public DB_EXCEPTION BLOCK_PARENT_DNE(const char* s) : DB_EXCEPTION(s) { } }; -/** - * @brief thrown when a block exists, but shouldn't, namely when adding a block - */ class BLOCK_EXISTS : public DB_EXCEPTION { public: @@ -184,9 +225,6 @@ class BLOCK_EXISTS : public DB_EXCEPTION BLOCK_EXISTS(const char* s) : DB_EXCEPTION(s) { } }; -/** - * @brief thrown when something is wrong with the block to be added - */ class BLOCK_INVALID : public DB_EXCEPTION { public: @@ -194,9 +232,6 @@ class BLOCK_INVALID : public DB_EXCEPTION BLOCK_INVALID(const char* s) : DB_EXCEPTION(s) { } }; -/** - * @brief thrown when a requested transaction does not exist - */ class TX_DNE : public DB_EXCEPTION { public: @@ -204,9 +239,6 @@ class TX_DNE : public DB_EXCEPTION TX_DNE(const char* s) : DB_EXCEPTION(s) { } }; -/** - * @brief thrown when a transaction exists, but shouldn't, namely when adding a block - */ class TX_EXISTS : public DB_EXCEPTION { public: @@ -214,9 +246,6 @@ class TX_EXISTS : public DB_EXCEPTION TX_EXISTS(const char* s) : DB_EXCEPTION(s) { } }; -/** - * @brief thrown when a requested output does not exist - */ class OUTPUT_DNE : public DB_EXCEPTION { public: @@ -224,9 +253,6 @@ class OUTPUT_DNE : public DB_EXCEPTION OUTPUT_DNE(const char* s) : DB_EXCEPTION(s) { } }; -/** - * @brief thrown when an output exists, but shouldn't, namely when adding a block - */ class OUTPUT_EXISTS : public DB_EXCEPTION { public: @@ -234,9 +260,6 @@ class OUTPUT_EXISTS : public DB_EXCEPTION OUTPUT_EXISTS(const char* s) : DB_EXCEPTION(s) { } }; -/** - * @brief thrown when a spent key image exists, but shouldn't, namely when adding a block - */ class KEY_IMAGE_EXISTS : public DB_EXCEPTION { public: @@ -249,18 +272,6 @@ class KEY_IMAGE_EXISTS : public DB_EXCEPTION ***********************************/ -/** - * @brief The BlockchainDB backing store interface declaration/contract - * - * This class provides a uniform interface for using BlockchainDB to store - * a blockchain. Any implementation of this class will also implement all - * functions exposed here, so one can use this class without knowing what - * implementation is being used. Refer to each pure virtual function's - * documentation here when implementing a BlockchainDB subclass. - * - * A subclass which encounters an issue should report that issue by throwing - * a DB_EXCEPTION which adequately conveys the issue. - */ class BlockchainDB { private: @@ -268,22 +279,7 @@ private: * private virtual members *********************************************************************/ - /** - * @brief add the block and metadata to the db - * - * The subclass implementing this will add the specified block and - * block metadata to its backing store. This does not include its - * transactions, those are added in a separate step. - * - * If any of this cannot be done, the subclass should throw the corresponding - * subclass of DB_EXCEPTION - * - * @param blk the block to be added - * @param block_size the size of the block (transactions and all) - * @param cumulative_difficulty the accumulated difficulty after this block - * @param coins_generated the number of coins generated total after this block - * @param blk_hash the hash of the block - */ + // tells the subclass to add the block and metadata to storage virtual void add_block( const block& blk , const size_t& block_size , const difficulty_type& cumulative_difficulty @@ -291,274 +287,84 @@ private: , const crypto::hash& blk_hash ) = 0; - /** - * @brief remove data about the top block - * - * The subclass implementing this will remove the block data from the top - * block in the chain. The data to be removed is that which was added in - * BlockchainDB::add_block(const block& blk, const size_t& block_size, const difficulty_type& cumulative_difficulty, const uint64_t& coins_generated, const crypto::hash& blk_hash) - * - * If any of this cannot be done, the subclass should throw the corresponding - * subclass of DB_EXCEPTION - */ + // tells the subclass to remove data about the top block virtual void remove_block() = 0; - /** - * @brief store the transaction and its metadata - * - * The subclass implementing this will add the specified transaction data - * to its backing store. This includes only the transaction blob itself - * and the other data passed here, not the separate outputs of the - * transaction. - * - * If any of this cannot be done, the subclass should throw the corresponding - * subclass of DB_EXCEPTION - * - * @param blk_hash the hash of the block containing the transaction - * @param tx the transaction to be added - * @param tx_hash the hash of the transaction - */ + // tells the subclass to store the transaction and its metadata virtual void add_transaction_data(const crypto::hash& blk_hash, const transaction& tx, const crypto::hash& tx_hash) = 0; - /** - * @brief remove data about a transaction - * - * The subclass implementing this will remove the transaction data - * for the passed transaction. The data to be removed was added in - * add_transaction_data(). Additionally, current subclasses have behavior - * which requires the transaction itself as a parameter here. Future - * implementations should note that this parameter is subject to be removed - * at a later time. - * - * If any of this cannot be done, the subclass should throw the corresponding - * subclass of DB_EXCEPTION - * - * @param tx_hash the hash of the transaction to be removed - * @param tx the transaction - */ + // tells the subclass to remove data about a transaction virtual void remove_transaction_data(const crypto::hash& tx_hash, const transaction& tx) = 0; - /** - * @brief store an output - * - * The subclass implementing this will add the output data passed to its - * backing store in a suitable manner. In addition, the subclass is responsible - * for keeping track of the global output count in some manner, so that - * outputs may be indexed by the order in which they were created. In the - * future, this tracking (of the number, at least) should be moved to - * this class, as it is necessary and the same among all BlockchainDB. - * - * This data should be stored in such a manner that the only thing needed to - * reverse the process is the tx_out. - * - * If any of this cannot be done, the subclass should throw the corresponding - * subclass of DB_EXCEPTION - * - * @param tx_hash hash of the transaction the output was created by - * @param tx_output the output - * @param local_index index of the output in its transaction - * @param unlock_time unlock time/height of the output - */ + // tells the subclass to store an output virtual void add_output(const crypto::hash& tx_hash, const tx_out& tx_output, const uint64_t& local_index, const uint64_t unlock_time) = 0; - /** - * @brief remove an output - * - * The subclass implementing this will remove all output data it stored - * in add_output(). - * - * In addition, the subclass is responsible for correctly decrementing - * its global output counter (this may be automatic for some, such as using - * a database backend "count" feature). - * - * If any of this cannot be done, the subclass should throw the corresponding - * subclass of DB_EXCEPTION - * - * @param tx_output the output to be removed - */ + // tells the subclass to remove an output virtual void remove_output(const tx_out& tx_output) = 0; - /** - * @brief store a spent key - * - * The subclass implementing this will store the spent key image. - * - * If any of this cannot be done, the subclass should throw the corresponding - * subclass of DB_EXCEPTION - * - * @param k_image the spent key image to store - */ + // tells the subclass to store a spent key virtual void add_spent_key(const crypto::key_image& k_image) = 0; - /** - * @brief remove a spent key - * - * The subclass implementing this will remove the key image. - * - * If any of this cannot be done, the subclass should throw the corresponding - * subclass of DB_EXCEPTION - * - * @param k_image the spent key image to remove - */ + // tells the subclass to remove a spent key virtual void remove_spent_key(const crypto::key_image& k_image) = 0; /********************************************************************* * private concrete members *********************************************************************/ - /** - * @brief private version of pop_block, for undoing if an add_block fails - * - * This function simply calls pop_block(block& blk, std::vector& txs) - * with dummy parameters, as the returns-by-reference can be discarded. - */ + // private version of pop_block, for undoing if an add_block goes tits up void pop_block(); - /** - * @brief helper function for add_transactions, to add each individual transaction - * - * This function is called by add_transactions() for each transaction to be - * added. - * - * @param blk_hash hash of the block which has the transaction - * @param tx the transaction to add - * @param tx_hash_ptr the hash of the transaction, if already calculated - */ + // helper function for add_transactions, to add each individual tx void add_transaction(const crypto::hash& blk_hash, const transaction& tx, const crypto::hash* tx_hash_ptr = NULL); // helper function to remove transaction from blockchain - /** - * @brief helper function to remove transaction from the blockchain - * - * This function encapsulates aspects of removing a transaction. - * - * @param tx_hash the hash of the transaction to be removed - */ void remove_transaction(const crypto::hash& tx_hash); - uint64_t num_calls = 0; //!< a performance metric - uint64_t time_blk_hash = 0; //!< a performance metric - uint64_t time_add_block1 = 0; //!< a performance metric - uint64_t time_add_transaction = 0; //!< a performance metric + uint64_t num_calls = 0; + uint64_t time_blk_hash = 0; + uint64_t time_add_block1 = 0; + uint64_t time_add_transaction = 0; protected: - mutable uint64_t time_tx_exists = 0; //!< a performance metric - uint64_t time_commit1 = 0; //!< a performance metric - bool m_auto_remove_logs = true; //!< whether or not to automatically remove old logs + mutable uint64_t time_tx_exists = 0; + uint64_t time_commit1 = 0; + bool m_auto_remove_logs = true; HardFork* m_hardfork; public: - /** - * @brief An empty destructor. - */ + // virtual dtor virtual ~BlockchainDB() { }; - /** - * @brief reset profiling stats - */ + // reset profiling stats void reset_stats(); - /** - * @brief show profiling stats - * - * This function prints current performance/profiling data to whichever - * log file(s) are set up (possibly including stdout or stderr) - */ + // show profiling stats void show_stats(); - /** - * @brief open a db, or create it if necessary. - * - * This function opens an existing database or creates it if it - * does not exist. - * - * The subclass implementing this will handle all file opening/creation, - * and is responsible for maintaining its state. - * - * The parameter may not refer to a file name, necessarily, but - * could be an IP:PORT for a database which needs it, and so on. Calling it - * is convenient and should be descriptive enough, however. - * - * For now, db_flags are - * specific to the subclass being instantiated. This is subject to change, - * and the db_flags parameter may be deprecated. - * - * If any of this cannot be done, the subclass should throw the corresponding - * subclass of DB_EXCEPTION - * - * @param filename a string referring to the BlockchainDB to open - * @param db_flags flags relevant to how to open/use the BlockchainDB - */ + // open the db at location , or create it if there isn't one. virtual void open(const std::string& filename, const int db_flags = 0) = 0; - /** - * @brief Gets the current open/ready state of the BlockchainDB - * - * @return true if open/ready, otherwise false - */ + // returns true of the db is open/ready, else false bool is_open() const; - /** - * @brief close the BlockchainDB - * - * At minimum, this call ensures that further use of the BlockchainDB - * instance will not have effect. In any case where it is necessary - * to do so, a subclass implementing this will sync with disk. - * - * If any of this cannot be done, the subclass should throw the corresponding - * subclass of DB_EXCEPTION - */ + // close and sync the db virtual void close() = 0; - /** - * @brief sync the BlockchainDB with disk - * - * This function should write any changes to whatever permanent backing - * store the subclass uses. Example: a BlockchainDB instance which - * keeps the whole blockchain in RAM won't need to regularly access a - * disk, but should write out its state when this is called. - * - * If any of this cannot be done, the subclass should throw the corresponding - * subclass of DB_EXCEPTION - */ + // sync the db virtual void sync() = 0; - /** - * @brief Remove everything from the BlockchainDB - * - * This function should completely remove all data from a BlockchainDB. - * - * Use with caution! - * - * If any of this cannot be done, the subclass should throw the corresponding - * subclass of DB_EXCEPTION - */ + // reset the db -- USE WITH CARE virtual void reset() = 0; - /** - * @brief get all files used by the BlockchainDB (if any) - * - * This function is largely for ease of automation, namely for unit tests. - * - * The subclass implementation should return all filenames it uses. - * - * @return a list of filenames - */ + // get all files used by this db (if any) virtual std::vector get_filenames() const = 0; // return the name of the folder the db's file(s) should reside in - /** - * @brief gets the name of the folder the BlockchainDB's file(s) should be in - * - * The subclass implementation should return the name of the folder in which - * it stores files, or an empty string if there is none. - * - * @return the name of the folder with the BlockchainDB's files, if any. - */ virtual std::string get_db_name() const = 0; @@ -566,86 +372,13 @@ public: // RAII-friendly and multi-read one-write friendly locking mechanism // // acquire db lock - /** - * @brief acquires the BlockchainDB lock - * - * This function is a stub until such a time as locking is implemented at - * this level. - * - * The subclass implementation should return true unless implementing a - * locking scheme of some sort, in which case it should return true upon - * acquisition of the lock and block until then. - * - * If any of this cannot be done, the subclass should throw the corresponding - * subclass of DB_EXCEPTION - * - * @return true, unless at a future time false makes sense (timeout, etc) - */ virtual bool lock() = 0; // release db lock - /** - * @brief This function releases the BlockchainDB lock - * - * The subclass, should it have implemented lock(), will release any lock - * held by the calling thread. In the case of recursive locking, it should - * release one instance of a lock. - * - * If any of this cannot be done, the subclass should throw the corresponding - * subclass of DB_EXCEPTION - */ virtual void unlock() = 0; - /** - * @brief tells the BlockchainDB to start a new "batch" of blocks - * - * If the subclass implements a batching method of caching blocks in RAM to - * be added to a backing store in groups, it should start a batch which will - * end either when has been added or batch_stop() has - * been called. In either case, it should end the batch and write to its - * backing store. - * - * If a batch is already in-progress, this function should throw a DB_ERROR. - * This exception may change in the future if it is deemed necessary to - * have a more granular exception type for this scenario. - * - * If any of this cannot be done, the subclass should throw the corresponding - * subclass of DB_EXCEPTION - * - * @param batch_num_blocks number of blocks to batch together - */ virtual void batch_start(uint64_t batch_num_blocks=0) = 0; - - /** - * @brief ends a batch transaction - * - * If the subclass implements batching, this function should store the - * batch it is currently on and mark it finished. - * - * If no batch is in-progress, this function should throw a DB_ERROR. - * This exception may change in the future if it is deemed necessary to - * have a more granular exception type for this scenario. - * - * If any of this cannot be done, the subclass should throw the corresponding - * subclass of DB_EXCEPTION - */ virtual void batch_stop() = 0; - - /** - * @brief sets whether or not to batch transactions - * - * If the subclass implements batching, this function tells it to begin - * batching automatically. - * - * If the subclass implements batching and has a batch in-progress, a - * parameter of false should disable batching and call batch_stop() to - * store the current batch. - * - * If any of this cannot be done, the subclass should throw the corresponding - * subclass of DB_EXCEPTION - * - * @param bool batch whether or not to use batch transactions. - */ virtual void set_batch_transactions(bool) = 0; virtual void block_txn_start(bool readonly=false) = 0; @@ -655,26 +388,8 @@ public: virtual void set_hard_fork(HardFork* hf); // adds a block with the given metadata to the top of the blockchain, returns the new height - /** - * @brief handles the addition of a new block to BlockchainDB - * - * This function organizes block addition and calls various functions as - * necessary. - * - * NOTE: subclass implementations of this (or the functions it calls) need - * to handle undoing any partially-added blocks in the event of a failure. - * - * If any of this cannot be done, the subclass should throw the corresponding - * subclass of DB_EXCEPTION - * - * @param blk the block to be added - * @param block_size the size of the block (transactions and all) - * @param cumulative_difficulty the accumulated difficulty after this block - * @param coins_generated the number of coins generated total after this block - * @param txs the transactions in the block - * - * @return the height of the chain post-addition - */ + // NOTE: subclass implementations of this (or the functions it calls) need + // to handle undoing any partially-added blocks in the event of a failure. virtual uint64_t add_block( const block& blk , const size_t& block_size , const difficulty_type& cumulative_difficulty @@ -682,639 +397,142 @@ public: , const std::vector& txs ); - /** - * @brief checks if a block exists - * - * @param h the hash of the requested block - * - * @return true of the block exists, otherwise false - */ + // return true if a block with hash exists in the blockchain virtual bool block_exists(const crypto::hash& h) const = 0; - /** - * @brief fetches the block with the given hash - * - * The subclass should return the requested block. - * - * If the block does not exist, the subclass should throw BLOCK_DNE - * - * @param h the hash to look for - * - * @return the block requested - */ + // return block with hash virtual block get_block(const crypto::hash& h) const = 0; - /** - * @brief gets the height of the block with a given hash - * - * The subclass should return the requested height. - * - * If the block does not exist, the subclass should throw BLOCK_DNE - * - * @param h the hash to look for - * - * @return the height - */ + // return the height of the block with hash on the blockchain, + // throw if it doesn't exist virtual uint64_t get_block_height(const crypto::hash& h) const = 0; - /** - * @brief fetch a block header - * - * The subclass should return the block header from the block with - * the given hash. - * - * If the block does not exist, the subclass should throw BLOCK_DNE - * - * @param h the hash to look for - * - * @return the block header - */ + // return header for block with hash virtual block_header get_block_header(const crypto::hash& h) const = 0; - /** - * @brief fetch a block by height - * - * The subclass should return the block at the given height. - * - * If the block does not exist, that is to say if the blockchain is not - * that high, then the subclass should throw BLOCK_DNE - * - * @param height the height to look for - * - * @return the block - */ + // return block at height virtual block get_block_from_height(const uint64_t& height) const = 0; - /** - * @brief fetch a block's timestamp - * - * The subclass should return the timestamp of the block with the - * given height. - * - * If the block does not exist, the subclass should throw BLOCK_DNE - * - * @param height the height requested - * - * @return the timestamp - */ + // return timestamp of block at height virtual uint64_t get_block_timestamp(const uint64_t& height) const = 0; - /** - * @brief fetch the top block's timestamp - * - * The subclass should return the timestamp of the most recent block. - * - * @return the top block's timestamp - */ + // return timestamp of most recent block virtual uint64_t get_top_block_timestamp() const = 0; - /** - * @brief fetch a block's size - * - * The subclass should return the size of the block with the - * given height. - * - * If the block does not exist, the subclass should throw BLOCK_DNE - * - * @param height the height requested - * - * @return the size - */ + // return block size of block at height virtual size_t get_block_size(const uint64_t& height) const = 0; - /** - * @brief fetch a block's cumulative difficulty - * - * The subclass should return the cumulative difficulty of the block with the - * given height. - * - * If the block does not exist, the subclass should throw BLOCK_DNE - * - * @param height the height requested - * - * @return the cumulative difficulty - */ + // return cumulative difficulty up to and including block at height virtual difficulty_type get_block_cumulative_difficulty(const uint64_t& height) const = 0; - /** - * @brief fetch a block's difficulty - * - * The subclass should return the difficulty of the block with the - * given height. - * - * If the block does not exist, the subclass should throw BLOCK_DNE - * - * @param height the height requested - * - * @return the difficulty - */ + // return difficulty of block at height virtual difficulty_type get_block_difficulty(const uint64_t& height) const = 0; - /** - * @brief fetch a block's already generated coins - * - * The subclass should return the total coins generated as of the block - * with the given height. - * - * If the block does not exist, the subclass should throw BLOCK_DNE - * - * @param height the height requested - * - * @return the already generated coins - */ + // return number of coins generated up to and including block at height virtual uint64_t get_block_already_generated_coins(const uint64_t& height) const = 0; - /** - * @brief fetch a block's hash - * - * The subclass should return hash of the block with the - * given height. - * - * If the block does not exist, the subclass should throw BLOCK_DNE - * - * @param height the height requested - * - * @return the hash - */ + // return hash of block at height virtual crypto::hash get_block_hash_from_height(const uint64_t& height) const = 0; - /** - * @brief fetch a list of blocks - * - * The subclass should return a vector of blocks with heights starting at - * h1 and ending at h2, inclusively. - * - * If the height range requested goes past the end of the blockchain, - * the subclass should throw BLOCK_DNE. (current implementations simply - * don't catch this exception as thrown by methods called within) - * - * @param h1 the start height - * @param h2 the end height - * - * @return a vector of blocks - */ + // return vector of blocks in range of height (inclusively) virtual std::vector get_blocks_range(const uint64_t& h1, const uint64_t& h2) const = 0; - /** - * @brief fetch a list of block hashes - * - * The subclass should return a vector of block hashes from blocks with - * heights starting at h1 and ending at h2, inclusively. - * - * If the height range requested goes past the end of the blockchain, - * the subclass should throw BLOCK_DNE. (current implementations simply - * don't catch this exception as thrown by methods called within) - * - * @param h1 the start height - * @param h2 the end height - * - * @return a vector of block hashes - */ + // return vector of block hashes in range of height (inclusively) virtual std::vector get_hashes_range(const uint64_t& h1, const uint64_t& h2) const = 0; - /** - * @brief fetch the top block's hash - * - * The subclass should return the hash of the most recent block - * - * @return the top block's hash - */ + // return the hash of the top block on the chain virtual crypto::hash top_block_hash() const = 0; - /** - * @brief fetch the top block - * - * The subclass should return most recent block - * - * @return the top block - */ + // return the block at the top of the blockchain virtual block get_top_block() const = 0; - /** - * @brief fetch the current blockchain height - * - * The subclass should return the current blockchain height - * - * @return the current blockchain height - */ + // return the height of the chain virtual uint64_t height() const = 0; - - /** - * - * - * @brief pops the top block off the blockchain - * - * The subclass should remove the most recent block from the blockchain, - * along with all transactions, outputs, and other metadata created as - * a result of its addition to the blockchain. Most of this is handled - * by the concrete members of the base class provided the subclass correctly - * implements remove_* functions. - * - * The subclass should return by reference the popped block and - * its associated transactions - * - * @param blk return-by-reference the block which was popped - * @param txs return-by-reference the transactions from the popped block - */ + // pops the top block off the blockchain. + // Returns by reference the popped block and its associated transactions + // + // IMPORTANT: + // When a block is popped, the transactions associated with it need to be + // removed, as well as outputs and spent key images associated with + // those transactions. virtual void pop_block(block& blk, std::vector& txs); - /** - * @brief check if a transaction with a given hash exists - * - * The subclass should check if a transaction is stored which has the - * given hash and return true if so, false otherwise. - * - * @param h the hash to check against - * - * @return true if the transaction exists, otherwise false - */ + // return true if a transaction with hash exists virtual bool tx_exists(const crypto::hash& h) const = 0; // return unlock time of tx with hash - /** - * @brief fetch a transaction's unlock time/height - * - * The subclass should return the stored unlock time for the transaction - * with the given hash. - * - * If no such transaction exists, the subclass should throw TX_DNE. - * - * @param h the hash of the requested transaction - * - * @return the unlock time/height - */ virtual uint64_t get_tx_unlock_time(const crypto::hash& h) const = 0; // return tx with hash // throw if no such tx exists - /** - * @brief fetches the transaction with the given hash - * - * The subclass should return the transaction stored which has the given - * hash. - * - * If the transaction does not exist, the subclass should throw TX_DNE. - * - * @param h the hash to look for - * - * @return the transaction with the given hash - */ virtual transaction get_tx(const crypto::hash& h) const = 0; - /** - * @brief fetches the total number of transactions ever - * - * The subclass should return a count of all the transactions from - * all blocks. - * - * @return the number of transactions in the blockchain - */ + // returns the total number of transactions in all blocks virtual uint64_t get_tx_count() const = 0; - /** - * @brief fetches a list of transactions based on their hashes - * - * The subclass should attempt to fetch each transaction referred to by - * the hashes passed. - * - * Currently, if any of the transactions is not in BlockchainDB, the call - * to get_tx in the implementation will throw TX_DNE. - * - * - * - * @param hlist a list of hashes - * - * @return the list of transactions - */ + // return list of tx with hashes . + // TODO: decide if a missing hash means return empty list + // or just skip that hash virtual std::vector get_tx_list(const std::vector& hlist) const = 0; // returns height of block that contains transaction with hash - /** - * @brief fetches the height of a transaction's block - * - * The subclass should attempt to return the height of the block containing - * the transaction with the given hash. - * - * If the transaction cannot be found, the subclass should throw TX_DNE. - * - * @param h the hash of the transaction - * - * @return the height of the transaction's block - */ virtual uint64_t get_tx_block_height(const crypto::hash& h) const = 0; // returns the total number of outputs of amount - /** - * @brief fetches the number of outputs of a given amount - * - * The subclass should return a count of outputs of the given amount, - * or zero if there are none. - * - * - * - * @param amount the output amount being looked up - * - * @return the number of outputs of the given amount - */ virtual uint64_t get_num_outputs(const uint64_t& amount) const = 0; - /** - * @brief return index of the first element (should be hidden, but isn't) - * - * @return the index - */ + // return index of the first element (should be hidden, but isn't) virtual uint64_t get_indexing_base() const { return 0; } - /** - * @brief get some of an output's data - * - * The subclass should return the public key, unlock time, and block height - * for the output with the given amount and index, collected in a struct. - * - * If the output cannot be found, the subclass should throw OUTPUT_DNE. - * - * If any of these parts cannot be found, but some are, the subclass - * should throw DB_ERROR with a message stating as much. - * - * @param amount the output amount - * @param index the output's index (indexed by amount) - * - * @return the requested output data - */ + // return public key for output with global output amount and index virtual output_data_t get_output_key(const uint64_t& amount, const uint64_t& index) = 0; - - /** - * @brief get some of an output's data - * - * The subclass should return the public key, unlock time, and block height - * for the output with the given global index, collected in a struct. - * - * If the output cannot be found, the subclass should throw OUTPUT_DNE. - * - * If any of these parts cannot be found, but some are, the subclass - * should throw DB_ERROR with a message stating as much. - * - * @param global_index the output's index (global) - * - * @return the requested output data - */ virtual output_data_t get_output_key(const uint64_t& global_index) const = 0; - /** - * @brief gets an output's tx hash and index - * - * The subclass should return the hash of the transaction which created the - * output with the global index given, as well as its index in that transaction. - * - * @param index an output's global index - * - * @return the tx hash and output index - */ + // returns the tx hash associated with an output, referenced by global output index virtual tx_out_index get_output_tx_and_index_from_global(const uint64_t& index) const = 0; - /** - * @brief gets an output's tx hash and index - * - * The subclass should return the hash of the transaction which created the - * output with the amount and index given, as well as its index in that - * transaction. - * - * @param amount an output amount - * @param index an output's amount-specific index - * - * @return the tx hash and output index - */ + // returns the transaction-local reference for the output with at + // return type is pair of tx hash and index virtual tx_out_index get_output_tx_and_index(const uint64_t& amount, const uint64_t& index) = 0; - - /** - * @brief gets some outputs' tx hashes and indices - * - * This function is a mirror of - * get_output_tx_and_index(const uint64_t& amount, const uint64_t& index), - * but for a list of outputs rather than just one. - * - * @param amount an output amount - * @param offsets a list of amount-specific output indices - * @param indices return-by-reference a list of tx hashes and output indices (as pairs) - */ virtual void get_output_tx_and_index(const uint64_t& amount, const std::vector &offsets, std::vector &indices) = 0; - - /** - * @brief gets outputs' data - * - * This function is a mirror of - * get_output_data(const uint64_t& amount, const uint64_t& index) - * but for a list of outputs rather than just one. - * - * @param amount an output amount - * @param offsets a list of amount-specific output indices - * @param outputs return-by-reference a list of outputs' metadata - */ virtual void get_output_key(const uint64_t &amount, const std::vector &offsets, std::vector &outputs) = 0; - - /* - * FIXME: Need to check with git blame and ask what this does to - * document it - */ + virtual bool can_thread_bulk_indices() const = 0; - /** - * @brief gets output indices (global) for a transaction's outputs - * - * The subclass should fetch the global output indices for each output - * in the transaction with the given hash. - * - * If the transaction does not exist, the subclass should throw TX_DNE. - * - * If an output cannot be found, the subclass should throw OUTPUT_DNE. - * - * @param h a transaction hash - * - * @return a list of global output indices - */ + // return a vector of indices corresponding to the global output index for + // each output in the transaction with hash virtual std::vector get_tx_output_indices(const crypto::hash& h) const = 0; - - /** - * @brief gets output indices (amount-specific) for a transaction's outputs - * - * The subclass should fetch the amount-specific output indices for each - * output in the transaction with the given hash. - * - * If the transaction does not exist, the subclass should throw TX_DNE. - * - * If an output cannot be found, the subclass should throw OUTPUT_DNE. - * - * @param h a transaction hash - * - * @return a list of amount-specific output indices - */ + // return a vector of indices corresponding to the amount output index for + // each output in the transaction with hash virtual std::vector get_tx_amount_output_indices(const crypto::hash& h) const = 0; - /** - * @brief check if a key image is stored as spent - * - * @param img the key image to check for - * - * @return true if the image is present, otherwise false - */ + // returns true if key image is present in spent key images storage virtual bool has_key_image(const crypto::key_image& img) const = 0; - /** - * @brief runs a function over all key images stored - * - * The subclass should run the passed function for each key image it has - * stored, passing the key image as its parameter. - * - * If any call to the function returns false, the subclass should return - * false. Otherwise, the subclass returns true. - * - * @param std::function fn the function to run - * - * @return false if the function returns false for any key image, otherwise true - */ virtual bool for_all_key_images(std::function) const = 0; - - /** - * @brief runs a function over all blocks stored - * - * The subclass should run the passed function for each block it has - * stored, passing (block_height, block_hash, block) as its parameters. - * - * If any call to the function returns false, the subclass should return - * false. Otherwise, the subclass returns true. - * - * The subclass should throw DB_ERROR if any of the expected values are - * not found. Current implementations simply return false. - * - * @param std::function fn the function to run - * - * @return false if the function returns false for any block, otherwise true - */ virtual bool for_all_blocks(std::function) const = 0; - - /** - * @brief runs a function over all transactions stored - * - * The subclass should run the passed function for each transaction it has - * stored, passing (transaction_hash, transaction) as its parameters. - * - * If any call to the function returns false, the subclass should return - * false. Otherwise, the subclass returns true. - * - * The subclass should throw DB_ERROR if any of the expected values are - * not found. Current implementations simply return false. - * - * @param std::function fn the function to run - * - * @return false if the function returns false for any transaction, otherwise true - */ virtual bool for_all_transactions(std::function) const = 0; - - /** - * @brief runs a function over all outputs stored - * - * The subclass should run the passed function for each output it has - * stored, passing (amount, transaction_hash, tx_local_output_index) - * as its parameters. - * - * If any call to the function returns false, the subclass should return - * false. Otherwise, the subclass returns true. - * - * The subclass should throw DB_ERROR if any of the expected values are - * not found. Current implementations simply return false. - * - * @param std::function f the function to run - * - * @return false if the function returns false for any output, otherwise true - */ virtual bool for_all_outputs(std::function f) const = 0; - - // // Hard fork related storage - // - - // FIXME: verify that this is all correct - // - TW - /** - * @brief sets the height at which a hard fork has been voted to happen - * - * - * @param version the version voted to fork to - * @param height the height of the first block on the new fork - */ virtual void set_hard_fork_starting_height(uint8_t version, uint64_t height) = 0; - - /** - * @brief gets the height at which a hard fork has been voted to happen - * - * @param version the version to check - * - * @return the height at which the hard fork was accepted, if it has been, - * otherwise max(uint64_t) - */ virtual uint64_t get_hard_fork_starting_height(uint8_t version) const = 0; - - /** - * @brief sets which hardfork version a height is on - * - * @param height the height - * @param version the version - */ virtual void set_hard_fork_version(uint64_t height, uint8_t version) = 0; - - /** - * @brief checks which hardfork version a height is on - * - * @param height the height - * - * @return the version - */ virtual uint8_t get_hard_fork_version(uint64_t height) const = 0; - - /** - * @brief verify hard fork info in database - */ virtual void check_hard_fork_info() = 0; - - /** - * @brief delete hard fork info from database - */ virtual void drop_hard_fork_info() = 0; - /** - * @brief is BlockchainDB in read-only mode? - * - * @return true if in read-only mode, otherwise false - */ virtual bool is_read_only() const = 0; - // TODO: this should perhaps be (or call) a series of functions which - // progressively update through version updates - /** - * @brief fix up anything that may be wrong due to past bugs - */ + // fix up anything that may be wrong due to past bugs virtual void fixup(); - /** - * @brief set whether or not to automatically remove logs - * - * This function is only relevant for one implementation (BlockchainBDB), but - * is here to keep BlockchainDB users implementation-agnostic. - * - * @param auto_remove whether or not to auto-remove logs - */ void set_auto_remove_logs(bool auto_remove) { m_auto_remove_logs = auto_remove; } - bool m_open; //!< Whether or not the BlockchainDB is open/ready for use - mutable epee::critical_section m_synchronization_lock; //!< A lock, currently for when BlockchainLMDB needs to resize the backing db file - + bool m_open; + mutable epee::critical_section m_synchronization_lock; }; // class BlockchainDB diff --git a/src/cryptonote_core/CMakeLists.txt b/src/cryptonote_core/CMakeLists.txt index 20535679..88eea1d7 100644 --- a/src/cryptonote_core/CMakeLists.txt +++ b/src/cryptonote_core/CMakeLists.txt @@ -31,6 +31,7 @@ set(cryptonote_core_sources blockchain_storage.cpp blockchain.cpp checkpoints.cpp + checkpoints_create.cpp cryptonote_basic_impl.cpp cryptonote_core.cpp cryptonote_format_utils.cpp @@ -48,6 +49,7 @@ set(cryptonote_core_private_headers blockchain_storage_boost_serialization.h blockchain.h checkpoints.h + checkpoints_create.h connection_context.h cryptonote_basic.h cryptonote_basic_impl.h diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 2f42e1db..131f56a4 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -49,7 +49,7 @@ #include "common/boost_serialization_helper.h" #include "warnings.h" #include "crypto/hash.h" -#include "cryptonote_core/checkpoints.h" +#include "cryptonote_core/checkpoints_create.h" #include "cryptonote_core/cryptonote_core.h" #if defined(PER_BLOCK_CHECKPOINT) #include "blocks/blocks.h" @@ -840,7 +840,7 @@ bool Blockchain::switch_to_alternative_blockchain(std::list blocks, and return by reference . +// get the block sizes of the last blocks, starting at +// and return by reference . void Blockchain::get_last_n_blocks_sizes(std::vector& sz, size_t count) const { LOG_PRINT_L3("Blockchain::" << __func__); @@ -1407,10 +1408,6 @@ bool Blockchain::get_blocks(uint64_t start_offset, size_t count, std::list missed_tx_ids; std::list txs; - - // FIXME: s/rsp.missed_ids/missed_tx_id/ ? Seems like rsp.missed_ids - // is for missed blocks, not missed transactions as well. get_transactions(bl.tx_hashes, txs, missed_tx_ids); if (missed_tx_ids.size() != 0) @@ -1674,8 +1668,6 @@ uint64_t Blockchain::block_difficulty(uint64_t i) const return 0; } //------------------------------------------------------------------ -//TODO: return type should be void, throw on exception -// alternatively, return true only if no blocks missed template bool Blockchain::get_blocks(const t_ids_container& block_ids, t_blocks_container& blocks, t_missed_container& missed_bs) const { @@ -1700,8 +1692,6 @@ bool Blockchain::get_blocks(const t_ids_container& block_ids, t_blocks_container return true; } //------------------------------------------------------------------ -//TODO: return type should be void, throw on exception -// alternatively, return true only if no transactions missed template bool Blockchain::get_transactions(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs) const { @@ -1718,6 +1708,7 @@ bool Blockchain::get_transactions(const t_ids_container& txs_ids, t_tx_container { missed_txs.push_back(tx_hash); } + //FIXME: is this the correct way to handle this? catch (const std::exception& e) { return false; @@ -1974,11 +1965,6 @@ bool Blockchain::get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vectorheight() < m_blocks_hash_check.size() && kept_by_block) { TIME_MEASURE_START(a); @@ -2053,10 +2038,6 @@ bool Blockchain::have_tx_keyimges_as_spent(const transaction &tx) const } //------------------------------------------------------------------ // This function validates transaction inputs and their keys. -// FIXME: consider moving functionality specific to one input into -// check_tx_input() rather than here, and use this function simply -// to iterate the inputs as necessary (splitting the task -// using threads, etc.) bool Blockchain::check_tx_inputs(const transaction& tx, uint64_t* pmax_used_block_height) { LOG_PRINT_L3("Blockchain::" << __func__); @@ -2339,7 +2320,7 @@ bool Blockchain::check_tx_input(const txin_to_key& txin, const crypto::hash& tx_ output_keys.clear(); - // collect output keys + //check ring signature outputs_visitor vi(output_keys, *this); if (!scan_outputkeys_for_indexes(txin, vi, tx_prefix_hash, pmax_related_block_height)) { @@ -2636,11 +2617,6 @@ leave: txs.push_back(tx); TIME_MEASURE_START(dd); - // FIXME: the storage should not be responsible for validation. - // If it does any, it is merely a sanity check. - // Validation is the purview of the Blockchain class - // - TW - // // ND: this is not needed, db->add_block() checks for duplicate k_images and fails accordingly. // if (!check_for_double_spend(tx, keys)) // { @@ -2821,8 +2797,6 @@ bool Blockchain::add_new_block(const block& bl_, block_verification_context& bvc return handle_block_to_main_chain(bl, id, bvc); } //------------------------------------------------------------------ -//TODO: Refactor, consider returning a failure height and letting -// caller decide course of action. void Blockchain::check_against_checkpoints(const checkpoints& points, bool enforce) { const auto& pts = points.get_points(); @@ -2860,16 +2834,16 @@ void Blockchain::check_against_checkpoints(const checkpoints& points, bool enfor // with an existing checkpoint. bool Blockchain::update_checkpoints(const std::string& file_path, bool check_dns) { - if (!m_checkpoints.load_checkpoints_from_json(file_path)) + if (!cryptonote::load_checkpoints_from_json(m_checkpoints, file_path)) { - return false; + return false; } // if we're checking both dns and json, load checkpoints from dns. // if we're not hard-enforcing dns checkpoints, handle accordingly if (m_enforce_dns_checkpoints && check_dns) { - if (!m_checkpoints.load_checkpoints_from_dns()) + if (!cryptonote::load_checkpoints_from_dns(m_checkpoints)) { return false; } @@ -2877,7 +2851,7 @@ bool Blockchain::update_checkpoints(const std::string& file_path, bool check_dns else if (check_dns) { checkpoints dns_points; - dns_points.load_checkpoints_from_dns(); + cryptonote::load_checkpoints_from_dns(dns_points); if (m_checkpoints.check_for_conflicts(dns_points)) { check_against_checkpoints(dns_points, false); @@ -2904,8 +2878,6 @@ void Blockchain::block_longhash_worker(const uint64_t height, const std::vector< TIME_MEASURE_START(t); slow_hash_allocate_state(); - //FIXME: height should be changing here, as get_block_longhash expects - // the height of the block passed to it for (const auto & block : blocks) { crypto::hash id = get_block_hash(block); @@ -2961,7 +2933,6 @@ bool Blockchain::cleanup_handle_incoming_blocks(bool force_sync) } //------------------------------------------------------------------ -//FIXME: unused parameter txs void Blockchain::output_scan_worker(const uint64_t amount, const std::vector &offsets, std::vector &outputs, std::unordered_map &txs) const { try diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 94def1aa..ab2c8f9e 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -60,14 +60,11 @@ namespace cryptonote class tx_memory_pool; struct test_options; - /** Declares ways in which the BlockchainDB backend should be told to sync - * - */ enum blockchain_db_sync_mode { - db_sync, //!< handle syncing calls instead of the backing db, synchronously - db_async, //!< handle syncing calls instead of the backing db, asynchronously - db_nosync //!< Leave syncing up to the backing db (safest, but slowest because of disk I/O) + db_sync, + db_async, + db_nosync }; /************************************************************************/ @@ -76,9 +73,6 @@ namespace cryptonote class Blockchain { public: - /** - * @brief Now-defunct (TODO: remove) struct from in-memory blockchain - */ struct transaction_chain_entry { transaction tx; @@ -87,697 +81,127 @@ namespace cryptonote std::vector m_global_output_indexes; }; - /** - * @brief container for passing a block and metadata about it on the blockchain - */ struct block_extended_info { - block bl; //!< the block - uint64_t height; //!< the height of the block in the blockchain - size_t block_cumulative_size; //!< the size (in bytes) of the block - difficulty_type cumulative_difficulty; //!< the accumulated difficulty after that block - uint64_t already_generated_coins; //!< the total coins minted after that block + block bl; + uint64_t height; + size_t block_cumulative_size; + difficulty_type cumulative_difficulty; + uint64_t already_generated_coins; }; - /** - * @brief Blockchain constructor - * - * @param tx_pool a reference to the transaction pool to be kept by the Blockchain - */ Blockchain(tx_memory_pool& tx_pool); - /** - * @brief Initialize the Blockchain state - * - * @param db a pointer to the backing store to use for the blockchain - * @param testnet true if on testnet, else false - * @param test_options test parameters - * - * @return true on success, false if any initialization steps fail - */ bool init(BlockchainDB* db, const bool testnet = false, const cryptonote::test_options *test_options = NULL); - - /** - * @brief Initialize the Blockchain state - * - * @param db a pointer to the backing store to use for the blockchain - * @param hf a structure containing hardfork information - * @param testnet true if on testnet, else false - * - * @return true on success, false if any initialization steps fail - */ bool init(BlockchainDB* db, HardFork*& hf, const bool testnet = false); - - /** - * @brief Uninitializes the blockchain state - * - * Saves to disk any state that needs to be maintained - * - * @return true on success, false if any uninitialization steps fail - */ bool deinit(); - /** - * @brief assign a set of blockchain checkpoint hashes - * - * @param chk_pts the set of checkpoints to assign - */ void set_checkpoints(checkpoints&& chk_pts) { m_checkpoints = chk_pts; } - /** - * @brief get blocks and transactions from blocks based on start height and count - * - * @param start_offset the height on the blockchain to start at - * @param count the number of blocks to get, if there are as many after start_offset - * @param blocks return-by-reference container to put result blocks in - * @param txs return-by-reference container to put result transactions in - * - * @return false if start_offset > blockchain height, else true - */ + //bool push_new_block(); bool get_blocks(uint64_t start_offset, size_t count, std::list& blocks, std::list& txs) const; - - /** - * @brief get blocks from blocks based on start height and count - * - * @param start_offset the height on the blockchain to start at - * @param count the number of blocks to get, if there are as many after start_offset - * @param blocks return-by-reference container to put result blocks in - * - * @return false if start_offset > blockchain height, else true - */ bool get_blocks(uint64_t start_offset, size_t count, std::list& blocks) const; - - /** - * @brief compiles a list of all blocks stored as alternative chains - * - * @param blocks return-by-reference container to put result blocks in - * - * @return true - */ bool get_alternative_blocks(std::list& blocks) const; - - /** - * @brief returns the number of alternative blocks stored - * - * @return the number of alternative blocks stored - */ size_t get_alternative_blocks_count() const; - - /** - * @brief gets a block's hash given a height - * - * @param height the height of the block - * - * @return the hash of the block at the requested height, or a zeroed hash if there is no such block - */ crypto::hash get_block_id_by_height(uint64_t height) const; - - /** - * @brief gets the block with a given hash - * - * @param h the hash to look for - * @param blk return-by-reference variable to put result block in - * - * @return true if the block was found, else false - */ bool get_block_by_hash(const crypto::hash &h, block &blk) const; - - /** - * @brief get all block hashes (main chain, alt chains, and invalid blocks) - * - * @param main return-by-reference container to put result main chain blocks' hashes in - * @param alt return-by-reference container to put result alt chain blocks' hashes in - * @param invalid return-by-reference container to put result invalid blocks' hashes in - */ void get_all_known_block_ids(std::list &main, std::list &alt, std::list &invalid) const; - - /** - * @brief performs some preprocessing on a group of incoming blocks to speed up verification - * - * @param blocks a list of incoming blocks - * - * @return false on erroneous blocks, else true - */ bool prepare_handle_incoming_blocks(const std::list &blocks); - - /** - * @brief incoming blocks post-processing, cleanup, and disk sync - * - * @param force_sync if true, and Blockchain is handling syncing to disk, always sync - * - * @return true - */ bool cleanup_handle_incoming_blocks(bool force_sync = false); - /** - * @brief search the blockchain for a transaction by hash - * - * @param id the hash to search for - * - * @return true if the tx exists, else false - */ bool have_tx(const crypto::hash &id) const; - - /** - * @brief check if any key image in a transaction has already been spent - * - * @param tx the transaction to check - * - * @return true if any key image is already spent in the blockchain, else false - */ bool have_tx_keyimges_as_spent(const transaction &tx) const; - - /** - * @brief check if a key image is already spent on the blockchain - * - * Whenever a transaction output is used as an input for another transaction - * (a true input, not just one of a mixing set), a key image is generated - * and stored in the transaction in order to prevent double spending. If - * this key image is seen again, the transaction using it is rejected. - * - * @param key_im the key image to search for - * - * @return true if the key image is already spent in the blockchain, else false - */ bool have_tx_keyimg_as_spent(const crypto::key_image &key_im) const; - /** - * @brief get the current height of the blockchain - * - * @return the height - */ uint64_t get_current_blockchain_height() const; - - /** - * @brief get the hash of the most recent block on the blockchain - * - * @return the hash - */ crypto::hash get_tail_id() const; - - /** - * @brief get the height and hash of the most recent block on the blockchain - * - * @param height return-by-reference variable to store the height in - * - * @return the hash - */ crypto::hash get_tail_id(uint64_t& height) const; - - /** - * @brief returns the difficulty target the next block to be added must meet - * - * @return the target - */ difficulty_type get_difficulty_for_next_block(); - - /** - * @brief adds a block to the blockchain - * - * Adds a new block to the blockchain. If the block's parent is not the - * current top of the blockchain, the block may be added to an alternate - * chain. If the block does not belong, is already in the blockchain - * or an alternate chain, or is invalid, return false. - * - * @param bl_ the block to be added - * @param bvc metadata about the block addition's success/failure - * - * @return true on successful addition to the blockchain, else false - */ bool add_new_block(const block& bl_, block_verification_context& bvc); - - /** - * @brief clears the blockchain and starts a new one - * - * @param b the first block in the new chain (the genesis block) - * - * @return true on success, else false - */ bool reset_and_set_genesis_block(const block& b); - - /** - * @brief creates a new block to mine against - * - * @param b return-by-reference block to be filled in - * @param miner_address address new coins for the block will go to - * @param di return-by-reference tells the miner what the difficulty target is - * @param height return-by-reference tells the miner what height it's mining against - * @param ex_nonce extra data to be added to the miner transaction's extra - * - * @return true if block template filled in successfully, else false - */ bool create_block_template(block& b, const account_public_address& miner_address, difficulty_type& di, uint64_t& height, const blobdata& ex_nonce); - - /** - * @brief checks if a block is known about with a given hash - * - * This function checks the main chain, alternate chains, and invalid blocks - * for a block with the given hash - * - * @param id the hash to search for - * - * @return true if the block is known, else false - */ bool have_block(const crypto::hash& id) const; - - /** - * @brief gets the total number of transactions on the main chain - * - * @return the number of transactions on the main chain - */ size_t get_total_transactions() const; - - /** - * @brief gets the hashes for a subset of the blockchain - * - * puts into list a list of hashes representing certain blocks - * from the blockchain in reverse chronological order - * - * the blocks chosen, at the time of this writing, are: - * the most recent 11 - * powers of 2 less recent from there, so 13, 17, 25, etc... - * - * @param ids return-by-reference list to put the resulting hashes in - * - * @return true - */ bool get_short_chain_history(std::list& ids) const; - - /** - * @brief get recent block hashes for a foreign chain - * - * Find the split point between us and foreign blockchain and return - * (by reference) the most recent common block hash along with up to - * BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT additional (more recent) hashes. - * - * @param qblock_ids the foreign chain's "short history" (see get_short_chain_history) - * @param resp return-by-reference the split height and subsequent blocks' hashes - * - * @return true if a block found in common, else false - */ bool find_blockchain_supplement(const std::list& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const; - - /** - * @brief find the most recent common point between ours and a foreign chain - * - * This function takes a list of block hashes from another node - * on the network to find where the split point is between us and them. - * This is used to see what to send another node that needs to sync. - * - * @param qblock_ids the foreign chain's "short history" (see get_short_chain_history) - * @param starter_offset return-by-reference the most recent common block's height - * - * @return true if a block found in common, else false - */ bool find_blockchain_supplement(const std::list& qblock_ids, uint64_t& starter_offset) const; - - /** - * @brief get recent blocks for a foreign chain - * - * This function gets recent blocks relative to a foreign chain, starting either at - * a requested height or whatever height is the most recent ours and the foreign - * chain have in common. - * - * @param req_start_block if non-zero, specifies a start point (otherwise find most recent commonality) - * @param qblock_ids the foreign chain's "short history" (see get_short_chain_history) - * @param blocks return-by-reference the blocks and their transactions - * @param total_height return-by-reference our current blockchain height - * @param start_height return-by-reference the height of the first block returned - * @param max_count the max number of blocks to get - * - * @return true if a block found in common or req_start_block specified, else false - */ bool find_blockchain_supplement(const uint64_t req_start_block, const std::list& qblock_ids, std::list > >& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count) const; - - /** - * @brief retrieves a set of blocks and their transactions, and possibly other transactions - * - * the request object encapsulates a list of block hashes and a (possibly empty) list of - * transaction hashes. for each block hash, the block is fetched along with all of that - * block's transactions. Any transactions requested separately are fetched afterwards. - * - * @param arg the request - * @param rsp return-by-reference the response to fill in - * - * @return true unless any blocks or transactions are missing - */ bool handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NOTIFY_RESPONSE_GET_OBJECTS::request& rsp); - - /** - * @brief gets random outputs to mix with - * - * This function takes an RPC request for outputs to mix with - * and creates an RPC response with the resultant output indices. - * - * Outputs to mix with are randomly selected from the utxo set - * for each output amount in the request. - * - * @param req the output amounts and number of mixins to select - * @param res return-by-reference the resultant output indices - * - * @return true - */ + bool handle_get_objects(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response& res); bool get_random_outs_for_amounts(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response& res) const; - - /** - * @brief gets the global indices for outputs from a given transaction - * - * This function gets the global indices for all outputs belonging - * to a specific transaction. - * - * @param tx_id the hash of the transaction to fetch indices for - * @param indexs return-by-reference the global indices for the transaction's outputs - * - * @return false if the transaction does not exist, or if no indices are found, otherwise true - */ bool get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vector& indexs) const; - - /** - * @brief stores the blockchain - * - * If Blockchain is handling storing of the blockchain (rather than BlockchainDB), - * this initiates a blockchain save. - * - * @return true unless saving the blockchain fails - */ bool store_blockchain(); - /** - * @brief validates a transaction's inputs - * - * validates a transaction's inputs as correctly used and not previously - * spent. also returns the hash and height of the most recent block - * which contains an output that was used as an input to the transaction. - * - * @param tx the transaction to validate - * @param pmax_used_block_height return-by-reference block height of most recent input - * @param max_used_block_id return-by-reference block hash of most recent input - * @param kept_by_block whether or not the transaction is from a previously-verified block - * - * @return false if any input is invalid, otherwise true - */ bool check_tx_inputs(const transaction& tx, uint64_t& pmax_used_block_height, crypto::hash& max_used_block_id, bool kept_by_block = false); - - /** - * @brief check that a transaction's outputs conform to current standards - * - * This function checks, for example at the time of this writing, that - * each output is of the form a * 10^b (phrased differently, that if - * written out would have only one non-zero digit in base 10). - * - * @param tx the transaction to check the outputs of - * - * @return false if any outputs do not conform, otherwise true - */ bool check_tx_outputs(const transaction& tx); - - /** - * @brief gets the blocksize limit based on recent blocks - * - * @return the limit - */ uint64_t get_current_cumulative_blocksize_limit() const; - - /** - * @brief checks if the blockchain is currently being stored - * - * Note: this should be meaningless in cases where Blockchain is not - * directly managing saving the blockchain to disk. - * - * @return true if Blockchain is having the chain stored currently, else false - */ bool is_storing_blockchain()const{return m_is_blockchain_storing;} - - /** - * @brief gets the difficulty of the block with a given height - * - * @param i the height - * - * @return the difficulty - */ uint64_t block_difficulty(uint64_t i) const; - /** - * @brief gets blocks based on a list of block hashes - * - * @tparam t_ids_container a standard-iterable container - * @tparam t_blocks_container a standard-iterable container - * @tparam t_missed_container a standard-iterable container - * @param block_ids a container of block hashes for which to get the corresponding blocks - * @param blocks return-by-reference a container to store result blocks in - * @param missed_bs return-by-reference a container to store missed blocks in - * - * @return false if an unexpected exception occurs, else true - */ template bool get_blocks(const t_ids_container& block_ids, t_blocks_container& blocks, t_missed_container& missed_bs) const; - /** - * @brief gets transactions based on a list of transaction hashes - * - * @tparam t_ids_container a standard-iterable container - * @tparam t_tx_container a standard-iterable container - * @tparam t_missed_container a standard-iterable container - * @param txs_ids a container of hashes for which to get the corresponding transactions - * @param txs return-by-reference a container to store result transactions in - * @param missed_txs return-by-reference a container to store missed transactions in - * - * @return false if an unexpected exception occurs, else true - */ template bool get_transactions(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs) const; - //debug functions - - /** - * @brief prints data about a snippet of the blockchain - * - * if start_index is greater than the blockchain height, do nothing - * - * @param start_index height on chain to start at - * @param end_index height on chain to end at - */ void print_blockchain(uint64_t start_index, uint64_t end_index) const; - - /** - * @brief prints every block's hash - * - * WARNING: This function will absolutely crush a terminal in prints, so - * it is recommended to redirect this output to a log file (or null sink - * if a log file is already set up, as should be the default) - */ void print_blockchain_index() const; - - /** - * @brief currently does nothing, candidate for removal - * - * @param file - */ void print_blockchain_outs(const std::string& file) const; - /** - * @brief check the blockchain against a set of checkpoints - * - * If a block fails a checkpoint and enforce is enabled, the blockchain - * will be rolled back to two blocks prior to that block. If enforce - * is disabled, as is currently the default case with DNS-based checkpoints, - * an error will be printed to the user but no other action will be taken. - * - * @param points the checkpoints to check against - * @param enforce whether or not to take action on failure - */ void check_against_checkpoints(const checkpoints& points, bool enforce); - - /** - * @brief configure whether or not to enforce DNS-based checkpoints - * - * @param enforce the new enforcement setting - */ void set_enforce_dns_checkpoints(bool enforce); - - /** - * @brief loads new checkpoints from a file and optionally from DNS - * - * @param file_path the path of the file to look for and load checkpoints from - * @param check_dns whether or not to check for new DNS-based checkpoints - * - * @return false if any enforced checkpoint type fails to load, otherwise true - */ bool update_checkpoints(const std::string& file_path, bool check_dns); - // user options, must be called before calling init() - - //FIXME: parameter names don't match function definition in .cpp file - /** - * @brief sets various performance options - * - * @param block_threads max number of threads when preparing blocks for addition - * @param blocks_per_sync number of blocks to cache before syncing to database - * @param sync_mode the ::blockchain_db_sync_mode to use - * @param fast_sync sync using built-in block hashes as trusted - */ void set_user_options(uint64_t block_threads, uint64_t blocks_per_sync, blockchain_db_sync_mode sync_mode, bool fast_sync); - /** - * @brief set whether or not to show/print time statistics - * - * @param stats the new time stats setting - */ void set_show_time_stats(bool stats) { m_show_time_stats = stats; } - /** - * @brief gets the hardfork voting state object - * - * @return the HardFork object - */ HardFork::State get_hard_fork_state() const; - - /** - * @brief gets the current hardfork version in use/voted for - * - * @return the version - */ uint8_t get_current_hard_fork_version() const { return m_hardfork->get_current_version(); } - - /** - * @brief returns the newest hardfork version known to the blockchain - * - * @return the version - */ uint8_t get_ideal_hard_fork_version() const { return m_hardfork->get_ideal_version(); } - - /** - * @brief returns the newest hardfork version voted to be enabled - * as of a certain height - * - * @param height the height for which to check version info - * - * @return the version - */ uint8_t get_ideal_hard_fork_version(uint64_t height) const { return m_hardfork->get_ideal_version(height); } - /** - * @brief get information about hardfork voting for a version - * - * @param version the version in question - * @param window the size of the voting window - * @param votes the number of votes to enable - * @param threshold the number of votes required to enable - * @param earliest_height the earliest height at which is allowed - * @param voting which version this node is voting for/using - * - * @return whether the version queried is enabled - */ bool get_hard_fork_voting_info(uint8_t version, uint32_t &window, uint32_t &votes, uint32_t &threshold, uint64_t &earliest_height, uint8_t &voting) const; - /** - * @brief remove transactions from the transaction pool (if present) - * - * @param txids a list of hashes of transactions to be removed - * - * @return false if any removals fail, otherwise true - */ bool flush_txes_from_pool(const std::list &txids); - /** - * @brief perform a check on all key images in the blockchain - * - * @param std::function the check to perform, pass/fail - * - * @return false if any key image fails the check, otherwise true - */ bool for_all_key_images(std::function) const; - - /** - * @brief perform a check on all blocks in the blockchain - * - * @param std::function the check to perform, pass/fail - * - * @return false if any block fails the check, otherwise true - */ bool for_all_blocks(std::function) const; - - /** - * @brief perform a check on all transactions in the blockchain - * - * @param std::function the check to perform, pass/fail - * - * @return false if any transaction fails the check, otherwise true - */ bool for_all_transactions(std::function) const; - - /** - * @brief perform a check on all outputs in the blockchain - * - * @param std::function the check to perform, pass/fail - * - * @return false if any output fails the check, otherwise true - */ bool for_all_outputs(std::function) const; - /** - * @brief get a reference to the BlockchainDB in use by Blockchain - * - * @return a reference to the BlockchainDB instance - */ BlockchainDB& get_db() { return *m_db; } - /** - * @brief get a number of outputs of a specific amount - * - * @param amount the amount - * @param offsets the indices (indexed to the amount) of the outputs - * @param outputs return-by-reference the outputs collected - * @param txs unused, candidate for removal - */ void output_scan_worker(const uint64_t amount,const std::vector &offsets, std::vector &outputs, std::unordered_map &txs) const; - /** - * @brief computes the "short" and "long" hashes for a set of blocks - * - * @param height the height of the first block - * @param blocks the blocks to be hashed - * @param map return-by-reference the hashes for each block - */ void block_longhash_worker(const uint64_t height, const std::vector &blocks, std::unordered_map &map) const; private: - - // TODO: evaluate whether or not each of these typedefs are left over from blockchain_storage typedef std::unordered_map blocks_by_id_index; - typedef std::unordered_map transactions_container; - typedef std::unordered_set key_images_container; - typedef std::vector blocks_container; - typedef std::unordered_map blocks_ext_by_hash; - typedef std::unordered_map blocks_by_hash; - typedef std::map>> outputs_container; //crypto::hash - tx hash, size_t - index of out in transaction - BlockchainDB* m_db; tx_memory_pool& m_tx_pool; - mutable epee::critical_section m_blockchain_lock; // TODO: add here reader/writer lock // main chain transactions_container m_transactions; size_t m_current_block_cumul_sz_limit; - // metadata containers std::unordered_map>> m_scan_table; std::unordered_map> m_check_tx_inputs_table; std::unordered_map m_blocks_longhash_table; @@ -819,323 +243,39 @@ namespace cryptonote bool m_testnet; - /** - * @brief collects the keys for all outputs being "spent" as an input - * - * This function makes sure that each "input" in an input (mixins) exists - * and collects the public key for each from the transaction it was included in - * via the visitor passed to it. - * - * If pmax_related_block_height is not NULL, its value is set to the height - * of the most recent block which contains an output used in the input set - * - * @tparam visitor_t a class encapsulating tx is unlocked and collect tx key - * @param tx_in_to_key a transaction input instance - * @param vis an instance of the visitor to use - * @param tx_prefix_hash the hash of the associated transaction_prefix - * @param pmax_related_block_height return-by-pointer the height of the most recent block in the input set - * - * @return false if any keys are not found or any inputs are not unlocked, otherwise true - */ template inline bool scan_outputkeys_for_indexes(const txin_to_key& tx_in_to_key, visitor_t &vis, const crypto::hash &tx_prefix_hash, uint64_t* pmax_related_block_height = NULL) const; - - /** - * @brief collect output public keys of a transaction input set - * - * This function locates all outputs associated with a given input set (mixins) - * and validates that they exist and are usable - * (unlocked, unspent is checked elsewhere). - * - * If pmax_related_block_height is not NULL, its value is set to the height - * of the most recent block which contains an output used in the input set - * - * @param txin the transaction input - * @param tx_prefix_hash the transaction prefix hash, for caching organization - * @param sig the input signature - * @param output_keys return-by-reference the public keys of the outputs in the input set - * @param pmax_related_block_height return-by-pointer the height of the most recent block in the input set - * - * @return false if any output is not yet unlocked, or is missing, otherwise true - */ bool check_tx_input(const txin_to_key& txin, const crypto::hash& tx_prefix_hash, const std::vector& sig, std::vector &output_keys, uint64_t* pmax_related_block_height); - - /** - * @brief validate a transaction's inputs and their keys - * - * This function validates transaction inputs and their keys. Previously - * it also performed double spend checking, but that has been moved to its - * own function. - * - * If pmax_related_block_height is not NULL, its value is set to the height - * of the most recent block which contains an output used in any input set - * - * Currently this function calls ring signature validation for each - * transaction. - * - * @param tx the transaction to validate - * @param pmax_related_block_height return-by-pointer the height of the most recent block in the input set - * - * @return false if any validation step fails, otherwise true - */ bool check_tx_inputs(const transaction& tx, uint64_t* pmax_used_block_height = NULL); - /** - * @brief performs a blockchain reorganization according to the longest chain rule - * - * This function aggregates all the actions necessary to switch to a - * newly-longer chain. If any step in the reorganization process fails, - * the blockchain is reverted to its previous state. - * - * @param alt_chain the chain to switch to - * @param discard_disconnected_chain whether or not to keep the old chain as an alternate - * - * @return false if the reorganization fails, otherwise true - */ bool switch_to_alternative_blockchain(std::list& alt_chain, bool discard_disconnected_chain); - - /** - * @brief removes the most recent block from the blockchain - * - * @return the block removed - */ block pop_block_from_blockchain(); - /** - * @brief validate and add a new block to the end of the blockchain - * - * This function is merely a convenience wrapper around the other - * of the same name. This one passes the block's hash to the other - * as well as the block and verification context. - * - * @param bl the block to be added - * @param bvc metadata concerning the block's validity - * - * @return true if the block was added successfully, otherwise false - */ bool handle_block_to_main_chain(const block& bl, block_verification_context& bvc); - - /** - * @brief validate and add a new block to the end of the blockchain - * - * When a block is given to Blockchain to be added to the blockchain, it - * is passed here if it is determined to belong at the end of the current - * chain. - * - * @param bl the block to be added - * @param id the hash of the block - * @param bvc metadata concerning the block's validity - * - * @return true if the block was added successfully, otherwise false - */ bool handle_block_to_main_chain(const block& bl, const crypto::hash& id, block_verification_context& bvc); - - /** - * @brief validate and add a new block to an alternate blockchain - * - * If a block to be added does not belong to the main chain, but there - * is an alternate chain to which it should be added, that is handled - * here. - * - * @param b the block to be added - * @param id the hash of the block - * @param bvc metadata concerning the block's validity - * - * @return true if the block was added successfully, otherwise false - */ bool handle_alternative_block(const block& b, const crypto::hash& id, block_verification_context& bvc); - - /** - * @brief gets the difficulty requirement for a new block on an alternate chain - * - * @param alt_chain the chain to be added to - * @param bei the block being added (and metadata, see ::block_extended_info) - * - * @return the difficulty requirement - */ difficulty_type get_next_difficulty_for_alternative_chain(const std::list& alt_chain, block_extended_info& bei) const; - - /** - * @brief sanity checks a miner transaction before validating an entire block - * - * This function merely checks basic things like the structure of the miner - * transaction, the unlock time, and that the amount doesn't overflow. - * - * @param b the block containing the miner transaction - * @param height the height at which the block will be added - * - * @return false if anything is found wrong with the miner transaction, otherwise true - */ bool prevalidate_miner_transaction(const block& b, uint64_t height); - - /** - * @brief validates a miner (coinbase) transaction - * - * This function makes sure that the miner calculated his reward correctly - * and that his miner transaction totals reward + fee. - * - * @param b the block containing the miner transaction to be validated - * @param cumulative_block_size the block's size - * @param fee the total fees collected in the block - * @param base_reward return-by-reference the new block's generated coins - * @param already_generated_coins the amount of currency generated prior to this block - * @param partial_block_reward return-by-reference true if miner accepted only partial reward - * - * @return false if anything is found wrong with the miner transaction, otherwise true - */ bool validate_miner_transaction(const block& b, size_t cumulative_block_size, uint64_t fee, uint64_t& base_reward, uint64_t already_generated_coins, bool &partial_block_reward); - - /** - * @brief reverts the blockchain to its previous state following a failed switch - * - * If Blockchain fails to switch to an alternate chain when it means - * to do so, this function reverts the blockchain to how it was before - * the attempted switch. - * - * @param original_chain the chain to switch back to - * @param rollback_height the height to revert to before appending the original chain - * - * @return false if something goes wrong with reverting (very bad), otherwise true - */ + bool validate_transaction(const block& b, uint64_t height, const transaction& tx); bool rollback_blockchain_switching(std::list& original_chain, uint64_t rollback_height); - - /** - * @brief gets recent block sizes for median calculation - * - * get the block sizes of the last blocks, and return by reference . - * - * @param sz return-by-reference the list of sizes - * @param count the number of blocks to get sizes for - */ + bool add_transaction_from_block(const transaction& tx, const crypto::hash& tx_id, const crypto::hash& bl_id, uint64_t bl_height); + bool push_transaction_to_global_outs_index(const transaction& tx, const crypto::hash& tx_id, std::vector& global_indexes); + bool pop_transaction_from_global_index(const transaction& tx, const crypto::hash& tx_id); void get_last_n_blocks_sizes(std::vector& sz, size_t count) const; - - /** - * @brief adds the given output to the requested set of random outputs - * - * @param result_outs return-by-reference the set the output is to be added to - * @param amount the output amount - * @param i the output index (indexed to amount) - */ void add_out_to_get_random_outs(COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& result_outs, uint64_t amount, size_t i) const; - - /** - * @brief checks if a transaction is unlocked (its outputs spendable) - * - * This function checks to see if a transaction is unlocked. - * unlock_time is either a block index or a unix time. - * - * @param unlock_time the unlock parameter (height or time) - * - * @return true if spendable, otherwise false - */ bool is_tx_spendtime_unlocked(uint64_t unlock_time) const; - - /** - * @brief stores an invalid block in a separate container - * - * Storing invalid blocks allows quick dismissal of the same block - * if it is seen again. - * - * @param bl the invalid block - * @param h the block's hash - * - * @return false if the block cannot be stored for some reason, otherwise true - */ bool add_block_as_invalid(const block& bl, const crypto::hash& h); - - /** - * @brief stores an invalid block in a separate container - * - * Storing invalid blocks allows quick dismissal of the same block - * if it is seen again. - * - * @param bei the invalid block (see ::block_extended_info) - * @param h the block's hash - * - * @return false if the block cannot be stored for some reason, otherwise true - */ bool add_block_as_invalid(const block_extended_info& bei, const crypto::hash& h); - - /** - * @brief checks a block's timestamp - * - * This function grabs the timestamps from the most recent blocks, - * where n = BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW. If there are not those many - * blocks in the blockchain, the timestap is assumed to be valid. If there - * are, this function returns: - * true if the block's timestamp is not less than the timestamp of the - * median of the selected blocks - * false otherwise - * - * @param b the block to be checked - * - * @return true if the block's timestamp is valid, otherwise false - */ bool check_block_timestamp(const block& b) const; - - /** - * @brief checks a block's timestamp - * - * If the block is not more recent than the median of the recent - * timestamps passed here, it is considered invalid. - * - * @param timestamps a list of the most recent timestamps to check against - * @param b the block to be checked - * - * @return true if the block's timestamp is valid, otherwise false - */ bool check_block_timestamp(std::vector& timestamps, const block& b) const; - - /** - * @brief get the "adjusted time" - * - * Currently this simply returns the current time according to the - * user's machine. - * - * @return the current time - */ uint64_t get_adjusted_time() const; - - /** - * @brief finish an alternate chain's timestamp window from the main chain - * - * for an alternate chain, get the timestamps from the main chain to complete - * the needed number of timestamps for the BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW. - * - * @param start_height the alternate chain's attachment height to the main chain - * @param timestamps return-by-value the timestamps set to be populated - * - * @return true unless start_height is greater than the current blockchain height - */ bool complete_timestamps_vector(uint64_t start_height, std::vector& timestamps); - - /** - * @brief calculate the block size limit for the next block to be added - * - * @return true - */ bool update_next_cumulative_size_limit(); void return_tx_to_pool(const std::vector &txs); - /** - * @brief make sure a transaction isn't attempting a double-spend - * - * @param tx the transaction to check - * @param keys_this_block a cumulative list of spent keys for the current block - * - * @return false if a double spend was detected, otherwise true - */ bool check_for_double_spend(const transaction& tx, key_images_container& keys_this_block) const; - - /** - * @brief validates a transaction input's ring signature - * - * @param tx_prefix_hash the transaction prefix' hash - * @param key_image the key image generated from the true input - * @param pubkeys the public keys for each input in the ring signature - * @param sig the signature generated for each input in the ring signature - * @param result false if the ring signature is invalid, otherwise true - */ + void get_timestamp_and_difficulty(uint64_t ×tamp, difficulty_type &difficulty, const int offset) const; void check_ring_signature(const crypto::hash &tx_prefix_hash, const crypto::key_image &key_image, const std::vector &pubkeys, const std::vector &sig, uint64_t &result); diff --git a/src/cryptonote_core/blockchain_storage.cpp b/src/cryptonote_core/blockchain_storage.cpp index a829b7cb..e1b89f88 100644 --- a/src/cryptonote_core/blockchain_storage.cpp +++ b/src/cryptonote_core/blockchain_storage.cpp @@ -48,7 +48,7 @@ #include "common/boost_serialization_helper.h" #include "warnings.h" #include "crypto/hash.h" -#include "cryptonote_core/checkpoints.h" +#include "cryptonote_core/checkpoints_create.h" //#include "serialization/json_archive.h" #include "../../contrib/otshell_utils/utils.hpp" #include "../../src/p2p/data_logger.hpp" @@ -1854,7 +1854,7 @@ void blockchain_storage::check_against_checkpoints(const checkpoints& points, bo // with an existing checkpoint. bool blockchain_storage::update_checkpoints(const std::string& file_path, bool check_dns) { - if (!m_checkpoints.load_checkpoints_from_json(file_path)) + if (!cryptonote::load_checkpoints_from_json(m_checkpoints, file_path)) { return false; } @@ -1863,7 +1863,7 @@ bool blockchain_storage::update_checkpoints(const std::string& file_path, bool c // if we're not hard-enforcing dns checkpoints, handle accordingly if (m_enforce_dns_checkpoints && check_dns) { - if (!m_checkpoints.load_checkpoints_from_dns()) + if (!cryptonote::load_checkpoints_from_dns(m_checkpoints)) { return false; } @@ -1871,7 +1871,7 @@ bool blockchain_storage::update_checkpoints(const std::string& file_path, bool c else if (check_dns) { checkpoints dns_points; - dns_points.load_checkpoints_from_dns(m_testnet); + cryptonote::load_checkpoints_from_dns(dns_points, m_testnet); if (m_checkpoints.check_for_conflicts(dns_points)) { check_against_checkpoints(dns_points, false); diff --git a/src/cryptonote_core/checkpoints.cpp b/src/cryptonote_core/checkpoints.cpp index c038a480..24d066da 100644 --- a/src/cryptonote_core/checkpoints.cpp +++ b/src/cryptonote_core/checkpoints.cpp @@ -1,21 +1,21 @@ // Copyright (c) 2014-2016, The Monero Project -// +// // All rights reserved. -// +// // Redistribution and use in source and binary forms, with or without modification, are // permitted provided that the following conditions are met: -// +// // 1. Redistributions of source code must retain the above copyright notice, this list of // conditions and the following disclaimer. -// +// // 2. Redistributions in binary form must reproduce the above copyright notice, this list // of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. -// +// // 3. Neither the name of the copyright holder nor the names of its contributors may be // used to endorse or promote products derived from this software without specific // prior written permission. -// +// // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL @@ -25,44 +25,14 @@ // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// +// // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #include "include_base_utils.h" - using namespace epee; #include "checkpoints.h" -#include "common/dns_utils.h" -#include "include_base_utils.h" -#include -#include - -namespace -{ - bool dns_records_match(const std::vector& a, const std::vector& b) - { - if (a.size() != b.size()) return false; - - for (const auto& record_in_a : a) - { - bool ok = false; - for (const auto& record_in_b : b) - { - if (record_in_a == record_in_b) - { - ok = true; - break; - } - } - if (!ok) return false; - } - - return true; - } -} // anonymous namespace - namespace cryptonote { //--------------------------------------------------------------------------- @@ -114,7 +84,10 @@ namespace cryptonote return check_block(height, h, ignored); } //--------------------------------------------------------------------------- - //FIXME: is this the desired behavior? + // this basically says if the blockchain is smaller than the first + // checkpoint then alternate blocks are allowed. Alternatively, if the + // last checkpoint *before* the end of the current chain is also before + // the block to be added, then this is fine. bool checkpoints::is_alternative_block_allowed(uint64_t blockchain_height, uint64_t block_height) const { if (0 == block_height) @@ -155,206 +128,4 @@ namespace cryptonote } return true; } - - bool checkpoints::init_default_checkpoints() - { - ADD_CHECKPOINT(1, "771fbcd656ec1464d3a02ead5e18644030007a0fc664c0a964d30922821a8148"); - ADD_CHECKPOINT(10, "c0e3b387e47042f72d8ccdca88071ff96bff1ac7cde09ae113dbb7ad3fe92381"); - ADD_CHECKPOINT(100, "ac3e11ca545e57c49fca2b4e8c48c03c23be047c43e471e1394528b1f9f80b2d"); - ADD_CHECKPOINT(1000, "5acfc45acffd2b2e7345caf42fa02308c5793f15ec33946e969e829f40b03876"); - ADD_CHECKPOINT(10000, "c758b7c81f928be3295d45e230646de8b852ec96a821eac3fea4daf3fcac0ca2"); - ADD_CHECKPOINT(22231, "7cb10e29d67e1c069e6e11b17d30b809724255fee2f6868dc14cfc6ed44dfb25"); - ADD_CHECKPOINT(29556, "53c484a8ed91e4da621bb2fa88106dbde426fe90d7ef07b9c1e5127fb6f3a7f6"); - ADD_CHECKPOINT(50000, "0fe8758ab06a8b9cb35b7328fd4f757af530a5d37759f9d3e421023231f7b31c"); - ADD_CHECKPOINT(80000, "a62dcd7b536f22e003ebae8726e9e7276f63d594e264b6f0cd7aab27b66e75e3"); - ADD_CHECKPOINT(202612, "bbd604d2ba11ba27935e006ed39c9bfdd99b76bf4a50654bc1e1e61217962698"); - ADD_CHECKPOINT(202613, "e2aa337e78df1f98f462b3b1e560c6b914dec47b610698b7b7d1e3e86b6197c2"); - ADD_CHECKPOINT(202614, "c29e3dc37d8da3e72e506e31a213a58771b24450144305bcba9e70fa4d6ea6fb"); - ADD_CHECKPOINT(205000, "5d3d7a26e6dc7535e34f03def711daa8c263785f73ec1fadef8a45880fde8063"); - ADD_CHECKPOINT(220000, "9613f455933c00e3e33ac315cc6b455ee8aa0c567163836858c2d9caff111553"); - ADD_CHECKPOINT(230300, "bae7a80c46859db355556e3a9204a337ae8f24309926a1312323fdecf1920e61"); - ADD_CHECKPOINT(230700, "93e631240ceac831da1aebfc5dac8f722c430463024763ebafa888796ceaeedf"); - ADD_CHECKPOINT(231350, "b5add137199b820e1ea26640e5c3e121fd85faa86a1e39cf7e6cc097bdeb1131"); - ADD_CHECKPOINT(232150, "955de8e6b6508af2c24f7334f97beeea651d78e9ade3ab18fec3763be3201aa8"); - ADD_CHECKPOINT(249380, "654fb0a81ce3e5caf7e3264a70f447d4bd07586c08fa50f6638cc54da0a52b2d"); - ADD_CHECKPOINT(460000, "75037a7aed3e765db96c75bcf908f59d690a5f3390baebb9edeafd336a1c4831"); - ADD_CHECKPOINT(500000, "2428f0dbe49796be05ed81b347f53e1f7f44aed0abf641446ec2b94cae066b02"); - ADD_CHECKPOINT(600000, "f5828ebf7d7d1cb61762c4dfe3ccf4ecab2e1aad23e8113668d981713b7a54c5"); - ADD_CHECKPOINT(700000, "12be9b3d210b93f574d2526abb9c1ab2a881b479131fd0d4f7dac93875f503cd"); - ADD_CHECKPOINT(825000, "56503f9ad766774b575be3aff73245e9d159be88132c93d1754764f28da2ff60"); - ADD_CHECKPOINT(900000, "d9958d0e7dcf91a5a7b11de225927bf7efc6eb26240315ce12372be902cc1337"); - ADD_CHECKPOINT(913193, "5292d5d56f6ba4de33a58d9a34d263e2cb3c6fee0aed2286fd4ac7f36d53c85f"); - - return true; - } - - bool checkpoints::load_checkpoints_from_json(const std::string json_hashfile_fullpath) - { - boost::system::error_code errcode; - if (! (boost::filesystem::exists(json_hashfile_fullpath, errcode))) - { - LOG_PRINT_L1("Blockchain checkpoints file not found"); - return true; - } - - LOG_PRINT_L1("Adding checkpoints from blockchain hashfile"); - - uint64_t prev_max_height = get_max_height(); - LOG_PRINT_L1("Hard-coded max checkpoint height is " << prev_max_height); - t_hash_json hashes; - epee::serialization::load_t_from_json_file(hashes, json_hashfile_fullpath); - for (std::vector::const_iterator it = hashes.hashlines.begin(); it != hashes.hashlines.end(); ) - { - uint64_t height; - height = it->height; - if (height <= prev_max_height) { - LOG_PRINT_L1("ignoring checkpoint height " << height); - } else { - std::string blockhash = it->hash; - LOG_PRINT_L1("Adding checkpoint height " << height << ", hash=" << blockhash); - ADD_CHECKPOINT(height, blockhash); - } - ++it; - } - - return true; - } - - bool checkpoints::load_checkpoints_from_dns(bool testnet) - { - // All four MoneroPulse domains have DNSSEC on and valid - static const std::vector dns_urls = { "checkpoints.moneropulse.se" - , "checkpoints.moneropulse.org" - , "checkpoints.moneropulse.net" - , "checkpoints.moneropulse.co" - }; - - static const std::vector testnet_dns_urls = { "testpoints.moneropulse.se" - , "testpoints.moneropulse.org" - , "testpoints.moneropulse.net" - , "testpoints.moneropulse.co" - }; - - std::vector > records; - records.resize(dns_urls.size()); - - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_int_distribution dis(0, dns_urls.size() - 1); - size_t first_index = dis(gen); - - bool avail, valid; - size_t cur_index = first_index; - do - { - std::string url; - if (testnet) - { - url = testnet_dns_urls[cur_index]; - } - else - { - url = dns_urls[cur_index]; - } - - records[cur_index] = tools::DNSResolver::instance().get_txt_record(url, avail, valid); - if (!avail) - { - records[cur_index].clear(); - LOG_PRINT_L2("DNSSEC not available for checkpoint update at URL: " << url << ", skipping."); - } - if (!valid) - { - records[cur_index].clear(); - LOG_PRINT_L2("DNSSEC validation failed for checkpoint update at URL: " << url << ", skipping."); - } - - cur_index++; - if (cur_index == dns_urls.size()) - { - cur_index = 0; - } - records[cur_index].clear(); - } while (cur_index != first_index); - - size_t num_valid_records = 0; - - for( const auto& record_set : records) - { - if (record_set.size() != 0) - { - num_valid_records++; - } - } - - if (num_valid_records < 2) - { - LOG_PRINT_L0("WARNING: no two valid MoneroPulse DNS checkpoint records were received"); - return true; - } - - int good_records_index = -1; - for (size_t i = 0; i < records.size() - 1; ++i) - { - if (records[i].size() == 0) continue; - - for (size_t j = i + 1; j < records.size(); ++j) - { - if (dns_records_match(records[i], records[j])) - { - good_records_index = i; - break; - } - } - if (good_records_index >= 0) break; - } - - if (good_records_index < 0) - { - LOG_PRINT_L0("WARNING: no two MoneroPulse DNS checkpoint records matched"); - return true; - } - - for (auto& record : records[good_records_index]) - { - auto pos = record.find(":"); - if (pos != std::string::npos) - { - uint64_t height; - crypto::hash hash; - - // parse the first part as uint64_t, - // if this fails move on to the next record - std::stringstream ss(record.substr(0, pos)); - if (!(ss >> height)) - { - continue; - } - - // parse the second part as crypto::hash, - // if this fails move on to the next record - std::string hashStr = record.substr(pos + 1); - if (!epee::string_tools::parse_tpod_from_hex_string(hashStr, hash)) - { - continue; - } - - ADD_CHECKPOINT(height, hashStr); - } - } - return true; - } - - bool checkpoints::load_new_checkpoints(const std::string json_hashfile_fullpath, bool testnet, bool dns) - { - bool result; - - result = load_checkpoints_from_json(json_hashfile_fullpath); - if (dns) - { - result &= load_checkpoints_from_dns(testnet); - } - - return result; - } } diff --git a/src/cryptonote_core/checkpoints.h b/src/cryptonote_core/checkpoints.h index 71727753..00a53ec2 100644 --- a/src/cryptonote_core/checkpoints.h +++ b/src/cryptonote_core/checkpoints.h @@ -1,21 +1,21 @@ // Copyright (c) 2014-2016, The Monero Project -// +// // All rights reserved. -// +// // Redistribution and use in source and binary forms, with or without modification, are // permitted provided that the following conditions are met: -// +// // 1. Redistributions of source code must retain the above copyright notice, this list of // conditions and the following disclaimer. -// +// // 2. Redistributions in binary form must reproduce the above copyright notice, this list // of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. -// +// // 3. Neither the name of the copyright holder nor the names of its contributors may be // used to endorse or promote products derived from this software without specific // prior written permission. -// +// // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL @@ -25,193 +25,30 @@ // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// +// // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #pragma once #include #include #include "cryptonote_basic_impl.h" -#include "misc_log_ex.h" -#include "storages/portable_storage_template_helper.h" // epee json include - -#define ADD_CHECKPOINT(h, hash) CHECK_AND_ASSERT(add_checkpoint(h, hash), false); -#define JSON_HASH_FILE_NAME "checkpoints.json" namespace cryptonote { - /** - * @brief A container for blockchain checkpoints - * - * A checkpoint is a pre-defined hash for the block at a given height. - * Some of these are compiled-in, while others can be loaded at runtime - * either from a json file or via DNS from a checkpoint-hosting server. - */ class checkpoints { public: - - /** - * @brief default constructor - */ checkpoints(); - - /** - * @brief adds a checkpoint to the container - * - * @param height the height of the block the checkpoint is for - * @param hash_str the hash of the block, as a string - * - * @return false if parsing the hash fails, or if the height is a duplicate - * AND the existing checkpoint hash does not match the new one, - * otherwise returns true - */ bool add_checkpoint(uint64_t height, const std::string& hash_str); - - /** - * @brief checks if there is a checkpoint in the future - * - * This function checks if the height passed is lower than the highest - * checkpoint. - * - * @param height the height to check against - * - * @return false if no checkpoints, otherwise returns whether or not - * the height passed is lower than the highest checkpoint. - */ bool is_in_checkpoint_zone(uint64_t height) const; - - /** - * @brief checks if the given height and hash agree with the checkpoints - * - * This function checks if the given height and hash exist in the - * checkpoints container. If so, it returns whether or not the passed - * parameters match the stored values. - * - * @param height the height to be checked - * @param h the hash to be checked - * @param is_a_checkpoint return-by-reference if there is a checkpoint at the given height - * - * @return true if there is no checkpoint at the given height, - * true if the passed parameters match the stored checkpoint, - * false otherwise - */ - bool check_block(uint64_t height, const crypto::hash& h, bool& is_a_checkpoint) const; - - /** - * @overload - */ bool check_block(uint64_t height, const crypto::hash& h) const; - - /** - * @brief checks if alternate chain blocks should be kept for a given height - * - * this basically says if the blockchain is smaller than the first - * checkpoint then alternate blocks are allowed. Alternatively, if the - * last checkpoint *before* the end of the current chain is also before - * the block to be added, then this is fine. - * - * @param blockchain_height the current blockchain height - * @param block_height the height of the block to be added as alternate - * - * @return true if alternate blocks are allowed given the parameters, - * otherwise false - */ + bool check_block(uint64_t height, const crypto::hash& h, bool& is_a_checkpoint) const; bool is_alternative_block_allowed(uint64_t blockchain_height, uint64_t block_height) const; - - /** - * @brief gets the highest checkpoint height - * - * @return the height of the highest checkpoint - */ uint64_t get_max_height() const; - - /** - * @brief gets the checkpoints container - * - * @return a const reference to the checkpoints container - */ const std::map& get_points() const; - - /** - * @brief checks if our checkpoints container conflicts with another - * - * A conflict refers to a case where both checkpoint sets have a checkpoint - * for a specific height but their hashes for that height do not match. - * - * @param other the other checkpoints instance to check against - * - * @return false if any conflict is found, otherwise true - */ bool check_for_conflicts(const checkpoints& other) const; - - /** - * @brief loads the default main chain checkpoints - * - * @return true unless adding a checkpoint fails - */ - bool init_default_checkpoints(); - - /** - * @brief load new checkpoints - * - * Loads new checkpoints from the specified json file, as well as - * (optionally) from DNS. - * - * @param json_hashfile_fullpath path to the json checkpoints file - * @param testnet whether to load testnet checkpoints or mainnet - * @param dns whether or not to load DNS checkpoints - * - * @return true if loading successful and no conflicts - */ - bool load_new_checkpoints(const std::string json_hashfile_fullpath, bool testnet=false, bool dns=true); - - /** - * @brief load new checkpoints from json - * - * @param json_hashfile_fullpath path to the json checkpoints file - * - * @return true if loading successful and no conflicts - */ - bool load_checkpoints_from_json(const std::string json_hashfile_fullpath); - - /** - * @brief load new checkpoints from DNS - * - * @param testnet whether to load testnet checkpoints or mainnet - * - * @return true if loading successful and no conflicts - */ - bool load_checkpoints_from_dns(bool testnet = false); - private: - - - /** - * @brief struct for loading a checkpoint from json - */ - struct t_hashline - { - uint64_t height; //!< the height of the checkpoint - std::string hash; //!< the hash for the checkpoint - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(height) - KV_SERIALIZE(hash) - END_KV_SERIALIZE_MAP() - }; - - /** - * @brief struct for loading many checkpoints from json - */ - struct t_hash_json { - std::vector hashlines; //!< the checkpoint lines from the file - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(hashlines) - END_KV_SERIALIZE_MAP() - }; - - std::map m_points; //!< the checkpoints container - + std::map m_points; }; } diff --git a/src/cryptonote_core/checkpoints_create.cpp b/src/cryptonote_core/checkpoints_create.cpp new file mode 100644 index 00000000..41f2321d --- /dev/null +++ b/src/cryptonote_core/checkpoints_create.cpp @@ -0,0 +1,280 @@ +// Copyright (c) 2014-2016, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#include "checkpoints_create.h" +#include "common/dns_utils.h" +#include "include_base_utils.h" +#include +#include +#include "storages/portable_storage_template_helper.h" // epee json include + +namespace +{ + bool dns_records_match(const std::vector& a, const std::vector& b) + { + if (a.size() != b.size()) return false; + + for (const auto& record_in_a : a) + { + bool ok = false; + for (const auto& record_in_b : b) + { + if (record_in_a == record_in_b) + { + ok = true; + break; + } + } + if (!ok) return false; + } + + return true; + } +} // anonymous namespace + +namespace cryptonote +{ + +struct t_hashline +{ + uint64_t height; + std::string hash; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(height) + KV_SERIALIZE(hash) + END_KV_SERIALIZE_MAP() +}; + +struct t_hash_json { + std::vector hashlines; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(hashlines) + END_KV_SERIALIZE_MAP() +}; + +bool create_checkpoints(cryptonote::checkpoints& checkpoints) +{ + ADD_CHECKPOINT(1, "771fbcd656ec1464d3a02ead5e18644030007a0fc664c0a964d30922821a8148"); + ADD_CHECKPOINT(10, "c0e3b387e47042f72d8ccdca88071ff96bff1ac7cde09ae113dbb7ad3fe92381"); + ADD_CHECKPOINT(100, "ac3e11ca545e57c49fca2b4e8c48c03c23be047c43e471e1394528b1f9f80b2d"); + ADD_CHECKPOINT(1000, "5acfc45acffd2b2e7345caf42fa02308c5793f15ec33946e969e829f40b03876"); + ADD_CHECKPOINT(10000, "c758b7c81f928be3295d45e230646de8b852ec96a821eac3fea4daf3fcac0ca2"); + ADD_CHECKPOINT(22231, "7cb10e29d67e1c069e6e11b17d30b809724255fee2f6868dc14cfc6ed44dfb25"); + ADD_CHECKPOINT(29556, "53c484a8ed91e4da621bb2fa88106dbde426fe90d7ef07b9c1e5127fb6f3a7f6"); + ADD_CHECKPOINT(50000, "0fe8758ab06a8b9cb35b7328fd4f757af530a5d37759f9d3e421023231f7b31c"); + ADD_CHECKPOINT(80000, "a62dcd7b536f22e003ebae8726e9e7276f63d594e264b6f0cd7aab27b66e75e3"); + ADD_CHECKPOINT(202612, "bbd604d2ba11ba27935e006ed39c9bfdd99b76bf4a50654bc1e1e61217962698"); + ADD_CHECKPOINT(202613, "e2aa337e78df1f98f462b3b1e560c6b914dec47b610698b7b7d1e3e86b6197c2"); + ADD_CHECKPOINT(202614, "c29e3dc37d8da3e72e506e31a213a58771b24450144305bcba9e70fa4d6ea6fb"); + ADD_CHECKPOINT(205000, "5d3d7a26e6dc7535e34f03def711daa8c263785f73ec1fadef8a45880fde8063"); + ADD_CHECKPOINT(220000, "9613f455933c00e3e33ac315cc6b455ee8aa0c567163836858c2d9caff111553"); + ADD_CHECKPOINT(230300, "bae7a80c46859db355556e3a9204a337ae8f24309926a1312323fdecf1920e61"); + ADD_CHECKPOINT(230700, "93e631240ceac831da1aebfc5dac8f722c430463024763ebafa888796ceaeedf"); + ADD_CHECKPOINT(231350, "b5add137199b820e1ea26640e5c3e121fd85faa86a1e39cf7e6cc097bdeb1131"); + ADD_CHECKPOINT(232150, "955de8e6b6508af2c24f7334f97beeea651d78e9ade3ab18fec3763be3201aa8"); + ADD_CHECKPOINT(249380, "654fb0a81ce3e5caf7e3264a70f447d4bd07586c08fa50f6638cc54da0a52b2d"); + ADD_CHECKPOINT(300000, "0c1cd46df6ccff90ec4ab493281f2583c344cd62216c427628990fe9db1bb8b6"); + ADD_CHECKPOINT(400000, "1b2b0e7a30e59691491529a3d506d1ba3d6052d0f6b52198b7330b28a6f1b6ac"); + ADD_CHECKPOINT(450000, "4d098b511ca97723e81737c448343cfd4e6dadb3d8a0e757c6e4d595e6e48357"); + ADD_CHECKPOINT(460000, "75037a7aed3e765db96c75bcf908f59d690a5f3390baebb9edeafd336a1c4831"); + ADD_CHECKPOINT(500000, "2428f0dbe49796be05ed81b347f53e1f7f44aed0abf641446ec2b94cae066b02"); + ADD_CHECKPOINT(600000, "f5828ebf7d7d1cb61762c4dfe3ccf4ecab2e1aad23e8113668d981713b7a54c5"); + ADD_CHECKPOINT(700000, "12be9b3d210b93f574d2526abb9c1ab2a881b479131fd0d4f7dac93875f503cd"); + ADD_CHECKPOINT(825000, "56503f9ad766774b575be3aff73245e9d159be88132c93d1754764f28da2ff60"); + ADD_CHECKPOINT(900000, "d9958d0e7dcf91a5a7b11de225927bf7efc6eb26240315ce12372be902cc1337"); + ADD_CHECKPOINT(913193, "5292d5d56f6ba4de33a58d9a34d263e2cb3c6fee0aed2286fd4ac7f36d53c85f"); + + return true; +} + +bool load_checkpoints_from_json(cryptonote::checkpoints& checkpoints, std::string json_hashfile_fullpath) +{ + boost::system::error_code errcode; + if (! (boost::filesystem::exists(json_hashfile_fullpath, errcode))) + { + LOG_PRINT_L1("Blockchain checkpoints file not found"); + return true; + } + + LOG_PRINT_L1("Adding checkpoints from blockchain hashfile"); + + uint64_t prev_max_height = checkpoints.get_max_height(); + LOG_PRINT_L1("Hard-coded max checkpoint height is " << prev_max_height); + t_hash_json hashes; + epee::serialization::load_t_from_json_file(hashes, json_hashfile_fullpath); + for (std::vector::const_iterator it = hashes.hashlines.begin(); it != hashes.hashlines.end(); ) + { + uint64_t height; + height = it->height; + if (height <= prev_max_height) { + LOG_PRINT_L1("ignoring checkpoint height " << height); + } else { + std::string blockhash = it->hash; + LOG_PRINT_L1("Adding checkpoint height " << height << ", hash=" << blockhash); + ADD_CHECKPOINT(height, blockhash); + } + ++it; + } + + return true; +} + +bool load_checkpoints_from_dns(cryptonote::checkpoints& checkpoints, bool testnet) +{ + // All four MoneroPulse domains have DNSSEC on and valid + static const std::vector dns_urls = { "checkpoints.moneropulse.se" + , "checkpoints.moneropulse.org" + , "checkpoints.moneropulse.net" + , "checkpoints.moneropulse.co" + }; + + static const std::vector testnet_dns_urls = { "testpoints.moneropulse.se" + , "testpoints.moneropulse.org" + , "testpoints.moneropulse.net" + , "testpoints.moneropulse.co" + }; + + std::vector > records; + records.resize(dns_urls.size()); + + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution dis(0, dns_urls.size() - 1); + size_t first_index = dis(gen); + + bool avail, valid; + size_t cur_index = first_index; + do + { + std::string url; + if (testnet) + { + url = testnet_dns_urls[cur_index]; + } + else + { + url = dns_urls[cur_index]; + } + + records[cur_index] = tools::DNSResolver::instance().get_txt_record(url, avail, valid); + if (!avail) + { + records[cur_index].clear(); + LOG_PRINT_L2("DNSSEC not available for checkpoint update at URL: " << url << ", skipping."); + } + if (!valid) + { + records[cur_index].clear(); + LOG_PRINT_L2("DNSSEC validation failed for checkpoint update at URL: " << url << ", skipping."); + } + + cur_index++; + if (cur_index == dns_urls.size()) + { + cur_index = 0; + } + records[cur_index].clear(); + } while (cur_index != first_index); + + size_t num_valid_records = 0; + + for( const auto& record_set : records) + { + if (record_set.size() != 0) + { + num_valid_records++; + } + } + + if (num_valid_records < 2) + { + LOG_PRINT_L0("WARNING: no two valid MoneroPulse DNS checkpoint records were received"); + return true; + } + + int good_records_index = -1; + for (size_t i = 0; i < records.size() - 1; ++i) + { + if (records[i].size() == 0) continue; + + for (size_t j = i + 1; j < records.size(); ++j) + { + if (dns_records_match(records[i], records[j])) + { + good_records_index = i; + break; + } + } + if (good_records_index >= 0) break; + } + + if (good_records_index < 0) + { + LOG_PRINT_L0("WARNING: no two MoneroPulse DNS checkpoint records matched"); + return true; + } + + for (auto& record : records[good_records_index]) + { + auto pos = record.find(":"); + if (pos != std::string::npos) + { + uint64_t height; + crypto::hash hash; + + // parse the first part as uint64_t, + // if this fails move on to the next record + std::stringstream ss(record.substr(0, pos)); + if (!(ss >> height)) + { + continue; + } + + // parse the second part as crypto::hash, + // if this fails move on to the next record + std::string hashStr = record.substr(pos + 1); + if (!epee::string_tools::parse_tpod_from_hex_string(hashStr, hash)) + { + continue; + } + + ADD_CHECKPOINT(height, hashStr); + } + } + return true; +} + +bool load_new_checkpoints(cryptonote::checkpoints& checkpoints, std::string json_hashfile_fullpath) +{ + // TODO: replace hard-coded url with const string or #define + return (load_checkpoints_from_json(checkpoints, json_hashfile_fullpath) && load_checkpoints_from_dns(checkpoints)); +} + +} // namespace cryptonote diff --git a/src/cryptonote_core/checkpoints_create.h b/src/cryptonote_core/checkpoints_create.h new file mode 100644 index 00000000..83830f8a --- /dev/null +++ b/src/cryptonote_core/checkpoints_create.h @@ -0,0 +1,48 @@ +// Copyright (c) 2014-2016, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once + +#include "checkpoints.h" +#include "misc_log_ex.h" + +#define ADD_CHECKPOINT(h, hash) CHECK_AND_ASSERT(checkpoints.add_checkpoint(h, hash), false); +#define JSON_HASH_FILE_NAME "checkpoints.json" + +namespace cryptonote +{ + + bool create_checkpoints(cryptonote::checkpoints& checkpoints); + + bool load_checkpoints_from_json(cryptonote::checkpoints& checkpoints, std::string json_hashfile_fullpath); + bool load_checkpoints_from_dns(cryptonote::checkpoints& checkpoints, bool testnet = false); + bool load_new_checkpoints(cryptonote::checkpoints& checkpoints, std::string json_hashfile_fullpath); + +} // namespace cryptonote diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index c31be5ac..6f0fe88a 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -42,7 +42,7 @@ using namespace epee; #include "cryptonote_format_utils.h" #include "misc_language.h" #include -#include "cryptonote_core/checkpoints.h" +#include "cryptonote_core/checkpoints_create.h" #include "blockchain_db/blockchain_db.h" #include "blockchain_db/lmdb/db_lmdb.h" #if defined(BERKELEY_DB) @@ -159,7 +159,7 @@ namespace cryptonote if (!m_testnet && !m_fakechain) { cryptonote::checkpoints checkpoints; - if (!checkpoints.init_default_checkpoints()) + if (!cryptonote::create_checkpoints(checkpoints)) { throw std::runtime_error("Failed to initialize checkpoints"); } @@ -415,7 +415,7 @@ namespace cryptonote CHECK_AND_ASSERT_MES(update_checkpoints(), false, "One or more checkpoints loaded from json or dns conflicted with existing checkpoints."); r = m_miner.init(vm, m_testnet); - CHECK_AND_ASSERT_MES(r, false, "Failed to initialize miner instance"); + CHECK_AND_ASSERT_MES(r, false, "Failed to initialize blockchain storage"); return load_state_data(); } @@ -634,6 +634,11 @@ namespace cryptonote return m_blockchain_storage.get_total_transactions(); } //----------------------------------------------------------------------------------------------- + //bool core::get_outs(uint64_t amount, std::list& pkeys) + //{ + // return m_blockchain_storage.get_outs(amount, pkeys); + //} + //----------------------------------------------------------------------------------------------- bool core::add_new_tx(const transaction& tx, const crypto::hash& tx_hash, const crypto::hash& tx_prefix_hash, size_t blob_size, tx_verification_context& tvc, bool keeped_by_block, bool relayed) { if(m_mempool.have_tx(tx_hash)) @@ -765,6 +770,10 @@ namespace cryptonote { m_miner.on_synchronized(); } + //bool core::get_backward_blocks_sizes(uint64_t from_height, std::vector& sizes, size_t count) + //{ + // return m_blockchain_storage.get_backward_blocks_sizes(from_height, sizes, count); + //} //----------------------------------------------------------------------------------------------- bool core::add_new_block(const block& b, block_verification_context& bvc) { @@ -885,6 +894,10 @@ namespace cryptonote return m_blockchain_storage.get_block_by_hash(h, blk); } //----------------------------------------------------------------------------------------------- + //void core::get_all_known_block_ids(std::list &main, std::list &alt, std::list &invalid) { + // m_blockchain_storage.get_all_known_block_ids(main, alt, invalid); + //} + //----------------------------------------------------------------------------------------------- std::string core::print_pool(bool short_format) const { return m_mempool.print_pool(short_format); diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 30384209..32f0b2ad 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -63,739 +63,157 @@ namespace cryptonote /************************************************************************/ /* */ /************************************************************************/ - - /** - * @brief handles core cryptonote functionality - * - * This class coordinates cryptonote functionality including, but not - * limited to, communication among the Blockchain, the transaction pool, - * any miners, and the network. - */ class core: public i_miner_handler { public: - - /** - * @brief constructor - * - * sets member variables into a usable state - * - * @param pprotocol pre-constructed protocol object to store and use - */ core(i_cryptonote_protocol* pprotocol); - - /** - * @copydoc Blockchain::handle_get_objects - * - * @note see Blockchain::handle_get_objects() - * @param context connection context associated with the request - */ bool handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NOTIFY_RESPONSE_GET_OBJECTS::request& rsp, cryptonote_connection_context& context); - - /** - * @brief calls various idle routines - * - * @note see miner::on_idle and tx_memory_pool::on_idle - * - * @return true - */ bool on_idle(); - - /** - * @brief handles an incoming transaction - * - * Parses an incoming transaction and, if nothing is obviously wrong, - * passes it along to the transaction pool - * - * @param tx_blob the tx to handle - * @param tvc metadata about the transaction's validity - * @param keeped_by_block if the transaction has been in a block - * @param relayed whether or not the transaction was relayed to us - * - * @return true if the transaction made it to the transaction pool, otherwise false - */ bool handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, bool keeped_by_block, bool relayed); - - /** - * @brief handles an incoming block - * - * periodic update to checkpoints is triggered here - * Attempts to add the block to the Blockchain and, on success, - * optionally updates the miner's block template. - * - * @param block_blob the block to be added - * @param bvc return-by-reference metadata context about the block's validity - * @param update_miner_blocktemplate whether or not to update the miner's block template - * - * @return false if loading new checkpoints fails, or the block is not - * added, otherwise true - */ bool handle_incoming_block(const blobdata& block_blob, block_verification_context& bvc, bool update_miner_blocktemplate = true); - - /** - * @copydoc Blockchain::prepare_handle_incoming_blocks - * - * @note see Blockchain::prepare_handle_incoming_blocks - */ bool prepare_handle_incoming_blocks(const std::list &blocks); - - /** - * @copydoc Blockchain::cleanup_handle_incoming_blocks - * - * @note see Blockchain::cleanup_handle_incoming_blocks - */ bool cleanup_handle_incoming_blocks(bool force_sync = false); - - /** - * @brief check the size of a block against the current maximum - * - * @param block_blob the block to check - * - * @return whether or not the block is too big - */ - bool check_incoming_block_size(const blobdata& block_blob) const; - /** - * @brief get the cryptonote protocol instance - * - * @return the instance - */ + bool check_incoming_block_size(const blobdata& block_blob) const; i_cryptonote_protocol* get_protocol(){return m_pprotocol;} //-------------------- i_miner_handler ----------------------- - - /** - * @brief stores and relays a block found by a miner - * - * Updates the miner's target block, attempts to store the found - * block in Blockchain, and -- on success -- relays that block to - * the network. - * - * @param b the block found - * - * @return true if the block was added to the main chain, otherwise false - */ virtual bool handle_block_found( block& b); - - /** - * @copydoc Blockchain::create_block_template - * - * @note see Blockchain::create_block_template - */ virtual bool get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, const blobdata& ex_nonce); - /** - * @brief gets the miner instance - * - * @return a reference to the miner instance - */ miner& get_miner(){return m_miner;} - - /** - * @brief gets the miner instance (const) - * - * @return a const reference to the miner instance - */ const miner& get_miner()const{return m_miner;} - - /** - * @brief adds command line options to the given options set - * - * As of now, there are no command line options specific to core, - * so this function simply returns. - * - * @param desc return-by-reference the command line options set to add to - */ static void init_options(boost::program_options::options_description& desc); - - /** - * @brief initializes the core as needed - * - * This function initializes the transaction pool, the Blockchain, and - * a miner instance with parameters given on the command line (or defaults) - * - * @param vm command line parameters - * @param test_options configuration options for testing - * - * @return false if one of the init steps fails, otherwise true - */ bool init(const boost::program_options::variables_map& vm, const test_options *test_options = NULL); - - /** - * @copydoc Blockchain::reset_and_set_genesis_block - * - * @note see Blockchain::reset_and_set_genesis_block - */ bool set_genesis_block(const block& b); - - /** - * @brief performs safe shutdown steps for core and core components - * - * Uninitializes the miner instance, transaction pool, and Blockchain - * - * if m_fast_exit is set, the call to Blockchain::deinit() is not made. - * - * @return true - */ bool deinit(); - - /** - * @brief sets fast exit flag - * - * @note see deinit() - */ static void set_fast_exit(); - - /** - * @brief gets the current state of the fast exit flag - * - * @return the fast exit flag - * - * @note see deinit() - */ static bool get_fast_exit(); - - /** - * @brief sets to drop blocks downloaded (for testing) - */ void test_drop_download(); - - /** - * @brief sets to drop blocks downloaded below a certain height - * - * @param height height below which to drop blocks - */ void test_drop_download_height(uint64_t height); - - /** - * @brief gets whether or not to drop blocks (for testing) - * - * @return whether or not to drop blocks - */ bool get_test_drop_download() const; - - /** - * @brief gets whether or not to drop blocks - * - * If the current blockchain height <= our block drop threshold - * and test drop blocks is set, return true - * - * @return see above - */ bool get_test_drop_download_height() const; - - /** - * @copydoc Blockchain::get_current_blockchain_height - * - * @note see Blockchain::get_current_blockchain_height() - */ uint64_t get_current_blockchain_height() const; - - /** - * @brief get the hash and height of the most recent block - * - * @param height return-by-reference height of the block - * @param top_id return-by-reference hash of the block - * - * @return true - */ - bool get_blockchain_top(uint64_t& height, crypto::hash& top_id) const; - - /** - * @copydoc Blockchain::get_blocks(uint64_t, size_t, std::list&, std::list&) const - * - * @note see Blockchain::get_blocks(uint64_t, size_t, std::list&, std::list&) const - */ + bool get_blockchain_top(uint64_t& heeight, crypto::hash& top_id) const; bool get_blocks(uint64_t start_offset, size_t count, std::list& blocks, std::list& txs) const; - - /** - * @copydoc Blockchain::get_blocks(uint64_t, size_t, std::list&) const - * - * @note see Blockchain::get_blocks(uint64_t, size_t, std::list&) const - */ bool get_blocks(uint64_t start_offset, size_t count, std::list& blocks) const; - - /** - * @copydoc Blockchain::get_blocks(const t_ids_container&, t_blocks_container&, t_missed_container&) const - * - * @note see Blockchain::get_blocks(const t_ids_container&, t_blocks_container&, t_missed_container&) const - */ template bool get_blocks(const t_ids_container& block_ids, t_blocks_container& blocks, t_missed_container& missed_bs) const { return m_blockchain_storage.get_blocks(block_ids, blocks, missed_bs); } - - /** - * @copydoc Blockchain::get_block_id_by_height - * - * @note see Blockchain::get_block_id_by_height - */ crypto::hash get_block_id_by_height(uint64_t height) const; - - /** - * @copydoc Blockchain::get_transactions - * - * @note see Blockchain::get_transactions - */ bool get_transactions(const std::vector& txs_ids, std::list& txs, std::list& missed_txs) const; - - /** - * @copydoc Blockchain::get_block_by_hash - * - * @note see Blockchain::get_block_by_hash - */ bool get_block_by_hash(const crypto::hash &h, block &blk) const; + //void get_all_known_block_ids(std::list &main, std::list &alt, std::list &invalid); - /** - * @copydoc Blockchain::get_alternative_blocks - * - * @note see Blockchain::get_alternative_blocks(std::list&) const - */ bool get_alternative_blocks(std::list& blocks) const; - - /** - * @copydoc Blockchain::get_alternative_blocks_count - * - * @note see Blockchain::get_alternative_blocks_count() const - */ size_t get_alternative_blocks_count() const; - /** - * @brief set the pointer to the cryptonote protocol object to use - * - * @param pprotocol the pointer to set ours as - */ void set_cryptonote_protocol(i_cryptonote_protocol* pprotocol); - - /** - * @copydoc Blockchain::set_checkpoints - * - * @note see Blockchain::set_checkpoints() - */ void set_checkpoints(checkpoints&& chk_pts); - - /** - * @brief set the file path to read from when loading checkpoints - * - * @param path the path to set ours as - */ void set_checkpoints_file_path(const std::string& path); - - /** - * @brief set whether or not we enforce DNS checkpoints - * - * @param enforce_dns enforce DNS checkpoints or not - */ void set_enforce_dns_checkpoints(bool enforce_dns); - /** - * @copydoc tx_memory_pool::get_transactions - * - * @note see tx_memory_pool::get_transactions - */ bool get_pool_transactions(std::list& txs) const; - - /** - * @copydoc tx_memory_pool::get_pool_transactions_and_spent_keys_info - * - * @note see tx_memory_pool::get_pool_transactions_and_spent_keys_info - */ bool get_pool_transactions_and_spent_keys_info(std::vector& tx_infos, std::vector& key_image_infos) const; - - /** - * @copydoc tx_memory_pool::get_transactions_count - * - * @note see tx_memory_pool::get_transactions_count - */ size_t get_pool_transactions_count() const; - - /** - * @copydoc Blockchain::get_total_transactions - * - * @note see Blockchain::get_total_transactions - */ size_t get_blockchain_total_transactions() const; - - /** - * @copydoc Blockchain::have_block - * - * @note see Blockchain::have_block - */ + //bool get_outs(uint64_t amount, std::list& pkeys); bool have_block(const crypto::hash& id) const; - - /** - * @copydoc Blockchain::get_short_chain_history - * - * @note see Blockchain::get_short_chain_history - */ bool get_short_chain_history(std::list& ids) const; - - /** - * @copydoc Blockchain::find_blockchain_supplement(const std::list&, NOTIFY_RESPONSE_CHAIN_ENTRY::request&) const - * - * @note see Blockchain::find_blockchain_supplement(const std::list&, NOTIFY_RESPONSE_CHAIN_ENTRY::request&) const - */ bool find_blockchain_supplement(const std::list& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const; - - /** - * @copydoc Blockchain::find_blockchain_supplement(const uint64_t, const std::list&, std::list > >&, uint64_t&, uint64_t&, size_t) const - * - * @note see Blockchain::find_blockchain_supplement(const uint64_t, const std::list&, std::list > >&, uint64_t&, uint64_t&, size_t) const - */ bool find_blockchain_supplement(const uint64_t req_start_block, const std::list& qblock_ids, std::list > >& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count) const; - - /** - * @brief gets some stats about the daemon - * - * @param st_inf return-by-reference container for the stats requested - * - * @return true - */ bool get_stat_info(core_stat_info& st_inf) const; - - /** - * @copydoc Blockchain::get_tx_outputs_gindexs - * - * @note see Blockchain::get_tx_outputs_gindexs - */ + //bool get_backward_blocks_sizes(uint64_t from_height, std::vector& sizes, size_t count); bool get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vector& indexs) const; - - /** - * @copydoc Blockchain::get_tail_id - * - * @note see Blockchain::get_tail_id - */ crypto::hash get_tail_id() const; - - /** - * @copydoc Blockchain::get_random_outs_for_amounts - * - * @note see Blockchain::get_random_outs_for_amounts - */ bool get_random_outs_for_amounts(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response& res) const; - - - /** - * @copydoc miner::pause - * - * @note see miner::pause - */ void pause_mine(); - - /** - * @copydoc miner::resume - * - * @note see miner::resume - */ void resume_mine(); - #if BLOCKCHAIN_DB == DB_LMDB - /** - * @brief gets the Blockchain instance - * - * @return a reference to the Blockchain instance - */ Blockchain& get_blockchain_storage(){return m_blockchain_storage;} - - /** - * @brief gets the Blockchain instance (const) - * - * @return a const reference to the Blockchain instance - */ const Blockchain& get_blockchain_storage()const{return m_blockchain_storage;} #else blockchain_storage& get_blockchain_storage(){return m_blockchain_storage;} const blockchain_storage& get_blockchain_storage()const{return m_blockchain_storage;} #endif - - /** - * @copydoc Blockchain::print_blockchain - * - * @note see Blockchain::print_blockchain - */ + //debug functions void print_blockchain(uint64_t start_index, uint64_t end_index) const; - - /** - * @copydoc Blockchain::print_blockchain_index - * - * @note see Blockchain::print_blockchain_index - */ void print_blockchain_index() const; - - /** - * @copydoc tx_memory_pool::print_pool - * - * @note see tx_memory_pool::print_pool - */ std::string print_pool(bool short_format) const; - - /** - * @copydoc Blockchain::print_blockchain_outs - * - * @note see Blockchain::print_blockchain_outs - */ void print_blockchain_outs(const std::string& file); - - /** - * @copydoc miner::on_synchronized - * - * @note see miner::on_synchronized - */ void on_synchronized(); - /** - * @brief sets the target blockchain height - * - * @param target_blockchain_height the height to set - */ void set_target_blockchain_height(uint64_t target_blockchain_height); - - /** - * @brief gets the target blockchain height - * - * @param target_blockchain_height the target height - */ uint64_t get_target_blockchain_height() const; - /** - * @brief tells the Blockchain to update its checkpoints - * - * This function will check if enough time has passed since the last - * time checkpoints were updated and tell the Blockchain to update - * its checkpoints if it is time. If updating checkpoints fails, - * the daemon is told to shut down. - * - * @note see Blockchain::update_checkpoints() - */ bool update_checkpoints(); - /** - * @brief tells the daemon to wind down operations and stop running - * - * Currently this function raises SIGTERM, allowing the installed signal - * handlers to do the actual stopping. - */ - void graceful_exit(); - - /** - * @brief stops the daemon running - * - * @note see graceful_exit() - */ void stop(); - /** - * @copydoc Blockchain::have_tx_keyimg_as_spent - * - * @note see Blockchain::have_tx_keyimg_as_spent - */ bool is_key_image_spent(const crypto::key_image& key_im) const; - - /** - * @brief check if multiple key images are spent - * - * plural version of is_key_image_spent() - * - * @param key_im list of key images to check - * @param spent return-by-reference result for each image checked - * - * @return true - */ bool are_key_images_spent(const std::vector& key_im, std::vector &spent) const; private: - - /** - * @copydoc add_new_tx(const transaction&, tx_verification_context&, bool) - * - * @param tx_hash the transaction's hash - * @param tx_prefix_hash the transaction prefix' hash - * @param blob_size the size of the transaction - * @param relayed whether or not the transaction was relayed to us - * - */ bool add_new_tx(const transaction& tx, const crypto::hash& tx_hash, const crypto::hash& tx_prefix_hash, size_t blob_size, tx_verification_context& tvc, bool keeped_by_block, bool relayed); - - /** - * @brief add a new transaction to the transaction pool - * - * Adds a new transaction to the transaction pool. - * - * @param tx the transaction to add - * @param tvc return-by-reference metadata about the transaction's verification process - * @param keeped_by_block whether or not the transaction has been in a block - * @param relayed whether or not the transaction was relayed to us - * - * @return true if the transaction is already in the transaction pool, - * is already in a block on the Blockchain, or is successfully added - * to the transaction pool - */ bool add_new_tx(const transaction& tx, tx_verification_context& tvc, bool keeped_by_block, bool relayed); - - /** - * @copydoc Blockchain::add_new_block - * - * @note see Blockchain::add_new_block - */ bool add_new_block(const block& b, block_verification_context& bvc); - - /** - * @brief load any core state stored on disk - * - * currently does nothing, but may have state to load in the future. - * - * @return true - */ bool load_state_data(); - - /** - * @copydoc parse_tx_from_blob(transaction&, crypto::hash&, crypto::hash&, const blobdata&) const - * - * @note see parse_tx_from_blob(transaction&, crypto::hash&, crypto::hash&, const blobdata&) const - */ bool parse_tx_from_blob(transaction& tx, crypto::hash& tx_hash, crypto::hash& tx_prefix_hash, const blobdata& blob) const; - /** - * @brief check a transaction's syntax - * - * For now this does nothing, but it may check something about the tx - * in the future. - * - * @param tx the transaction to check - * - * @return true - */ bool check_tx_syntax(const transaction& tx) const; - - /** - * @brief validates some simple properties of a transaction - * - * Currently checks: tx has inputs, - * tx inputs all of supported type(s), - * tx outputs valid (type, key, amount), - * input and output total amounts don't overflow, - * output amount <= input amount, - * tx not too large, - * each input has a different key image. - * - * @param tx the transaction to check - * @param keeped_by_block if the transaction has been in a block - * - * @return true if all the checks pass, otherwise false - */ + //check correct values, amounts and all lightweight checks not related with database bool check_tx_semantic(const transaction& tx, bool keeped_by_block) const; + //check if tx already in memory pool or in main blockchain - /** - * @copydoc miner::on_block_chain_update - * - * @note see miner::on_block_chain_update - * - * @return true - */ + bool check_tx_ring_signature(const txin_to_key& tx, const crypto::hash& tx_prefix_hash, const std::vector& sig) const; + bool is_tx_spendtime_unlocked(uint64_t unlock_time) const; bool update_miner_block_template(); - - /** - * @brief act on a set of command line options given - * - * @param vm the command line options - * - * @return true - */ bool handle_command_line(const boost::program_options::variables_map& vm); - - /** - * @brief verify that each input key image in a transaction is unique - * - * @param tx the transaction to check - * - * @return false if any key image is repeated, otherwise true - */ + bool on_update_blocktemplate_interval(); bool check_tx_inputs_keyimages_diff(const transaction& tx) const; - - /** - * @brief checks HardFork status and prints messages about it - * - * Checks the status of HardFork and logs/prints if an update to - * the daemon is necessary. - * - * @note see Blockchain::get_hard_fork_state and HardFork::State - * - * @return true - */ + void graceful_exit(); bool check_fork_time(); - - /** - * @brief attempts to relay any transactions in the mempool which need it - * - * @return true - */ bool relay_txpool_transactions(); - - /** - * @brief locks a file in the BlockchainDB directory - * - * @param path the directory in which to place the file - * - * @return true if lock acquired successfully, otherwise false - */ bool lock_db_directory(const boost::filesystem::path &path); - - /** - * @brief unlocks the db directory - * - * @note see lock_db_directory() - * - * @return true - */ bool unlock_db_directory(); - static std::atomic m_fast_exit; //!< whether or not to deinit Blockchain on exit + static std::atomic m_fast_exit; + bool m_test_drop_download = true; + uint64_t m_test_drop_download_height = 0; - bool m_test_drop_download = true; //!< whether or not to drop incoming blocks (for testing) - - uint64_t m_test_drop_download_height = 0; //!< height under which to drop incoming blocks, if doing so - - tx_memory_pool m_mempool; //!< transaction pool instance + tx_memory_pool m_mempool; #if BLOCKCHAIN_DB == DB_LMDB - Blockchain m_blockchain_storage; //!< Blockchain instance + Blockchain m_blockchain_storage; #else blockchain_storage m_blockchain_storage; #endif - - i_cryptonote_protocol* m_pprotocol; //!< cryptonote protocol instance - - epee::critical_section m_incoming_tx_lock; //!< incoming transaction lock - + i_cryptonote_protocol* m_pprotocol; + epee::critical_section m_incoming_tx_lock; //m_miner and m_miner_addres are probably temporary here - miner m_miner; //!< miner instance - account_public_address m_miner_address; //!< address to mine to (for miner instance) - - std::string m_config_folder; //!< folder to look in for configs and other files - - cryptonote_protocol_stub m_protocol_stub; //!< cryptonote protocol stub instance - - epee::math_helper::once_a_time_seconds<60*60*12, false> m_store_blockchain_interval; //!< interval for manual storing of Blockchain, if enabled - epee::math_helper::once_a_time_seconds<60*60*2, false> m_fork_moaner; //!< interval for checking HardFork status + miner m_miner; + account_public_address m_miner_address; + std::string m_config_folder; + cryptonote_protocol_stub m_protocol_stub; + epee::math_helper::once_a_time_seconds<60*60*12, false> m_store_blockchain_interval; + epee::math_helper::once_a_time_seconds<60*60*2, true> m_fork_moaner; epee::math_helper::once_a_time_seconds<60*2, false> m_txpool_auto_relayer; //!< interval for checking re-relaying txpool transactions - friend class tx_validate_inputs; - std::atomic m_starter_message_showed; //!< has the "daemon will sync now" message been shown? + std::atomic m_starter_message_showed; - uint64_t m_target_blockchain_height; //!< blockchain height target + uint64_t m_target_blockchain_height; - bool m_testnet; //!< are we on testnet? + bool m_testnet; + bool m_fakechain; + std::string m_checkpoints_path; + time_t m_last_dns_checkpoints_update; + time_t m_last_json_checkpoints_update; - bool m_fakechain; //!< are we using a fake chain (for testing purposes)? + std::atomic_flag m_checkpoints_updating; - std::string m_checkpoints_path; //!< path to json checkpoints file - time_t m_last_dns_checkpoints_update; //!< time when dns checkpoints were last updated - time_t m_last_json_checkpoints_update; //!< time when json checkpoints were last updated - - std::atomic_flag m_checkpoints_updating; //!< set if checkpoints are currently updating to avoid multiple threads attempting to update at once - - boost::interprocess::file_lock db_lock; //!< a lock object for a file lock in the db directory + boost::interprocess::file_lock db_lock; }; } diff --git a/src/cryptonote_core/difficulty.cpp b/src/cryptonote_core/difficulty.cpp index 54da7739..236e8448 100644 --- a/src/cryptonote_core/difficulty.cpp +++ b/src/cryptonote_core/difficulty.cpp @@ -116,8 +116,8 @@ namespace cryptonote { return !carry; } - difficulty_type next_difficulty(std::vector timestamps, std::vector cumulative_difficulties, size_t target_seconds) { - + difficulty_type next_difficulty(vector timestamps, vector cumulative_difficulties, size_t target_seconds) { + //cutoff DIFFICULTY_LAG if(timestamps.size() > DIFFICULTY_WINDOW) { timestamps.resize(DIFFICULTY_WINDOW); @@ -151,8 +151,6 @@ namespace cryptonote { assert(total_work > 0); uint64_t low, high; mul(total_work, target_seconds, low, high); - // blockchain errors "difficulty overhead" if this function returns zero. - // TODO: consider throwing an exception instead if (high != 0 || low + time_span - 1 < low) { return 0; } diff --git a/src/cryptonote_core/difficulty.h b/src/cryptonote_core/difficulty.h index 910f9703..d49c2f3b 100644 --- a/src/cryptonote_core/difficulty.h +++ b/src/cryptonote_core/difficulty.h @@ -39,18 +39,6 @@ namespace cryptonote { typedef std::uint64_t difficulty_type; - /** - * @brief checks if a hash fits the given difficulty - * - * The hash passes if (hash * difficulty) < 2^192. - * Phrased differently, if (hash * difficulty) fits without overflow into - * the least significant 192 bits of the 256 bit multiplication result. - * - * @param hash the hash to check - * @param difficulty the difficulty to check against - * - * @return true if valid, else false - */ bool check_hash(const crypto::hash &hash, difficulty_type difficulty); difficulty_type next_difficulty(std::vector timestamps, std::vector cumulative_difficulties, size_t target_seconds); } diff --git a/src/daemon/core.h b/src/daemon/core.h index 2b7f0d17..2208ef25 100644 --- a/src/daemon/core.h +++ b/src/daemon/core.h @@ -28,6 +28,7 @@ #pragma once +#include "cryptonote_core/checkpoints_create.h" #include "cryptonote_core/cryptonote_core.h" #include "cryptonote_protocol/cryptonote_protocol_handler.h" #include "misc_log_ex.h" From 0d30b657859d6ebd3188bd16dfaa9c79c3dfcaa7 Mon Sep 17 00:00:00 2001 From: Riccardo Spagni Date: Fri, 25 Mar 2016 08:22:06 +0200 Subject: [PATCH 35/47] Merge pull request #749 bfd4a28 Update BlockchainDB documentation (Thomas Winget) 797357e Change Doxyfile, Blockchain not blockchain_storage (Thomas Winget) c835215 remove defunct code from cryptonote::core (Thomas Winget) 50dba6d cryptonote::core doxygen documentation (Thomas Winget) 8ac329d doxygen documentation for difficulty functions (Thomas Winget) 540a76c Move checkpoint functions into checkpoints class (Thomas Winget) 1b0c98e doxygen documentation for checkpoints.{h,cpp} (Thomas Winget) 89c24ac Remove unnecessary or defunct code (Thomas Winget) ab0ed14 doxygen include private and static members (Thomas Winget) 3a48449 Updated documentation for blockchain.* (Thomas Winget) --- Doxyfile | 14 +- src/blockchain_db/blockchain_db.h | 1094 +++++++++++++++++--- src/cryptonote_core/CMakeLists.txt | 2 - src/cryptonote_core/blockchain.cpp | 49 +- src/cryptonote_core/blockchain.h | 890 +++++++++++++++- src/cryptonote_core/blockchain_storage.cpp | 8 +- src/cryptonote_core/checkpoints.cpp | 251 ++++- src/cryptonote_core/checkpoints.h | 181 +++- src/cryptonote_core/checkpoints_create.cpp | 280 ----- src/cryptonote_core/checkpoints_create.h | 48 - src/cryptonote_core/cryptonote_core.cpp | 19 +- src/cryptonote_core/cryptonote_core.h | 670 +++++++++++- src/cryptonote_core/difficulty.cpp | 6 +- src/cryptonote_core/difficulty.h | 12 + src/daemon/core.h | 1 - 15 files changed, 2920 insertions(+), 605 deletions(-) delete mode 100644 src/cryptonote_core/checkpoints_create.cpp delete mode 100644 src/cryptonote_core/checkpoints_create.h diff --git a/Doxyfile b/Doxyfile index a70ef812..93a5c6e7 100644 --- a/Doxyfile +++ b/Doxyfile @@ -409,13 +409,13 @@ LOOKUP_CACHE_SIZE = 0 # normally produced when WARNINGS is set to YES. # The default value is: NO. -EXTRACT_ALL = NO +EXTRACT_ALL = YES # If the EXTRACT_PRIVATE tag is set to YES all private members of a class will # be included in the documentation. # The default value is: NO. -EXTRACT_PRIVATE = NO +EXTRACT_PRIVATE = YES # If the EXTRACT_PACKAGE tag is set to YES all members with package or internal # scope will be included in the documentation. @@ -427,7 +427,7 @@ EXTRACT_PACKAGE = NO # included in the documentation. # The default value is: NO. -EXTRACT_STATIC = NO +EXTRACT_STATIC = YES # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined # locally in source files will be included in the documentation. If set to NO @@ -452,7 +452,7 @@ EXTRACT_LOCAL_METHODS = NO # are hidden. # The default value is: NO. -EXTRACT_ANON_NSPACES = NO +EXTRACT_ANON_NSPACES = YES # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all # undocumented members inside documented classes or files. If set to NO these @@ -1902,7 +1902,7 @@ ENABLE_PREPROCESSING = YES # The default value is: NO. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -MACRO_EXPANSION = NO +MACRO_EXPANSION = YES # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then # the macro expansion is limited to the macros specified with the PREDEFINED and @@ -1910,7 +1910,7 @@ MACRO_EXPANSION = NO # The default value is: NO. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -EXPAND_ONLY_PREDEF = NO +EXPAND_ONLY_PREDEF = YES # If the SEARCH_INCLUDES tag is set to YES the includes files in the # INCLUDE_PATH will be searched if a #include is found. @@ -1942,7 +1942,7 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = +PREDEFINED = "BLOCKCHAIN_DB=2" \ # DB_LMDB # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index 3396b8c2..3e0ca141 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -38,18 +38,20 @@ #include "cryptonote_core/difficulty.h" #include "cryptonote_core/hardfork.h" -/* DB Driver Interface +/** \file + * Cryptonote Blockchain Database Interface * * The DB interface is a store for the canonical block chain. * It serves as a persistent storage for the blockchain. * - * For the sake of efficiency, the reference implementation will also + * For the sake of efficiency, a concrete implementation may also * store some blockchain data outside of the blocks, such as spent * transfer key images, unspent transaction outputs, etc. * + * Examples are as follows: + * * Transactions are duplicated so that we don't have to fetch a whole block - * in order to fetch a transaction from that block. If this is deemed - * unnecessary later, this can change. + * in order to fetch a transaction from that block. * * Spent key images are duplicated outside of the blocks so it is quick * to verify an output hasn't already been spent @@ -57,100 +59,34 @@ * Unspent transaction outputs are duplicated to quickly gather random * outputs to use for mixins * - * IMPORTANT: - * A concrete implementation of this interface should populate these - * duplicated members! It is possible to have a partial implementation - * of this interface call to private members of the interface to be added - * later that will then populate as needed. - * - * General: - * open() - * is_open() - * close() - * sync() - * reset() - * - * Lock and unlock provided for reorg externally, and for block - * additions internally, this way threaded reads are completely fine - * unless the blockchain is changing. - * bool lock() - * unlock() - * - * vector get_filenames() - * - * Blocks: - * bool block_exists(hash) - * height add_block(block, block_size, cumulative_difficulty, coins_generated, transactions) - * block get_block(hash) - * height get_block_height(hash) - * header get_block_header(hash) - * block get_block_from_height(height) - * size_t get_block_size(height) - * difficulty get_block_cumulative_difficulty(height) - * uint64_t get_block_already_generated_coins(height) - * uint64_t get_block_timestamp(height) - * uint64_t get_top_block_timestamp() - * hash get_block_hash_from_height(height) - * blocks get_blocks_range(height1, height2) - * hashes get_hashes_range(height1, height2) - * hash top_block_hash() - * block get_top_block() - * height height() - * void pop_block(block&, tx_list&) - * - * Transactions: - * bool tx_exists(hash) - * uint64_t get_tx_unlock_time(hash) - * tx get_tx(hash) - * uint64_t get_tx_count() - * tx_list get_tx_list(hash_list) - * height get_tx_block_height(hash) - * - * Outputs: - * uint64_t get_num_outputs(amount) - * pub_key get_output_key(amount, index) - * hash,index get_output_tx_and_index_from_global(index) - * hash,index get_output_tx_and_index(amount, index) - * vec get_tx_output_indices(tx_hash) - * - * - * Spent Output Key Images: - * bool has_key_image(key_image) - * - * Exceptions: - * DB_ERROR -- generic - * DB_OPEN_FAILURE - * DB_CREATE_FAILURE - * DB_SYNC_FAILURE - * BLOCK_DNE - * BLOCK_PARENT_DNE - * BLOCK_EXISTS - * BLOCK_INVALID -- considering making this multiple errors - * TX_DNE - * TX_EXISTS - * OUTPUT_DNE - * OUTPUT_EXISTS - * KEY_IMAGE_EXISTS */ namespace cryptonote { -// typedef for convenience +/** a pair of , typedef for convenience */ typedef std::pair tx_out_index; #pragma pack(push, 1) + +/** + * @brief a struct containing output metadata + */ struct output_data_t { - crypto::public_key pubkey; - uint64_t unlock_time; - uint64_t height; + crypto::public_key pubkey; //!< the output's public key (for spend verification) + uint64_t unlock_time; //!< the output's unlock time (or height) + uint64_t height; //!< the height of the block which created the output }; #pragma pack(pop) /*********************************** * Exception Definitions ***********************************/ + +/** + * @brief A base class for BlockchainDB exceptions + */ class DB_EXCEPTION : public std::exception { private: @@ -168,6 +104,9 @@ class DB_EXCEPTION : public std::exception } }; +/** + * @brief A generic BlockchainDB exception + */ class DB_ERROR : public DB_EXCEPTION { public: @@ -175,7 +114,9 @@ class DB_ERROR : public DB_EXCEPTION DB_ERROR(const char* s) : DB_EXCEPTION(s) { } }; -// For distinguishing errors trying to set up a DB txn from other errors +/** + * @brief thrown when there is an error starting a DB transaction + */ class DB_ERROR_TXN_START : public DB_EXCEPTION { public: @@ -183,6 +124,9 @@ class DB_ERROR_TXN_START : public DB_EXCEPTION DB_ERROR_TXN_START(const char* s) : DB_EXCEPTION(s) { } }; +/** + * @brief thrown when opening the BlockchainDB fails + */ class DB_OPEN_FAILURE : public DB_EXCEPTION { public: @@ -190,6 +134,9 @@ class DB_OPEN_FAILURE : public DB_EXCEPTION DB_OPEN_FAILURE(const char* s) : DB_EXCEPTION(s) { } }; +/** + * @brief thrown when creating the BlockchainDB fails + */ class DB_CREATE_FAILURE : public DB_EXCEPTION { public: @@ -197,6 +144,9 @@ class DB_CREATE_FAILURE : public DB_EXCEPTION DB_CREATE_FAILURE(const char* s) : DB_EXCEPTION(s) { } }; +/** + * @brief thrown when synchronizing the BlockchainDB to disk fails + */ class DB_SYNC_FAILURE : public DB_EXCEPTION { public: @@ -204,6 +154,9 @@ class DB_SYNC_FAILURE : public DB_EXCEPTION DB_SYNC_FAILURE(const char* s) : DB_EXCEPTION(s) { } }; +/** + * @brief thrown when a requested block does not exist + */ class BLOCK_DNE : public DB_EXCEPTION { public: @@ -211,6 +164,9 @@ class BLOCK_DNE : public DB_EXCEPTION BLOCK_DNE(const char* s) : DB_EXCEPTION(s) { } }; +/** + * @brief thrown when a block's parent does not exist (and it needed to) + */ class BLOCK_PARENT_DNE : public DB_EXCEPTION { public: @@ -218,6 +174,9 @@ class BLOCK_PARENT_DNE : public DB_EXCEPTION BLOCK_PARENT_DNE(const char* s) : DB_EXCEPTION(s) { } }; +/** + * @brief thrown when a block exists, but shouldn't, namely when adding a block + */ class BLOCK_EXISTS : public DB_EXCEPTION { public: @@ -225,6 +184,9 @@ class BLOCK_EXISTS : public DB_EXCEPTION BLOCK_EXISTS(const char* s) : DB_EXCEPTION(s) { } }; +/** + * @brief thrown when something is wrong with the block to be added + */ class BLOCK_INVALID : public DB_EXCEPTION { public: @@ -232,6 +194,9 @@ class BLOCK_INVALID : public DB_EXCEPTION BLOCK_INVALID(const char* s) : DB_EXCEPTION(s) { } }; +/** + * @brief thrown when a requested transaction does not exist + */ class TX_DNE : public DB_EXCEPTION { public: @@ -239,6 +204,9 @@ class TX_DNE : public DB_EXCEPTION TX_DNE(const char* s) : DB_EXCEPTION(s) { } }; +/** + * @brief thrown when a transaction exists, but shouldn't, namely when adding a block + */ class TX_EXISTS : public DB_EXCEPTION { public: @@ -246,6 +214,9 @@ class TX_EXISTS : public DB_EXCEPTION TX_EXISTS(const char* s) : DB_EXCEPTION(s) { } }; +/** + * @brief thrown when a requested output does not exist + */ class OUTPUT_DNE : public DB_EXCEPTION { public: @@ -253,6 +224,9 @@ class OUTPUT_DNE : public DB_EXCEPTION OUTPUT_DNE(const char* s) : DB_EXCEPTION(s) { } }; +/** + * @brief thrown when an output exists, but shouldn't, namely when adding a block + */ class OUTPUT_EXISTS : public DB_EXCEPTION { public: @@ -260,6 +234,9 @@ class OUTPUT_EXISTS : public DB_EXCEPTION OUTPUT_EXISTS(const char* s) : DB_EXCEPTION(s) { } }; +/** + * @brief thrown when a spent key image exists, but shouldn't, namely when adding a block + */ class KEY_IMAGE_EXISTS : public DB_EXCEPTION { public: @@ -272,6 +249,18 @@ class KEY_IMAGE_EXISTS : public DB_EXCEPTION ***********************************/ +/** + * @brief The BlockchainDB backing store interface declaration/contract + * + * This class provides a uniform interface for using BlockchainDB to store + * a blockchain. Any implementation of this class will also implement all + * functions exposed here, so one can use this class without knowing what + * implementation is being used. Refer to each pure virtual function's + * documentation here when implementing a BlockchainDB subclass. + * + * A subclass which encounters an issue should report that issue by throwing + * a DB_EXCEPTION which adequately conveys the issue. + */ class BlockchainDB { private: @@ -279,7 +268,22 @@ private: * private virtual members *********************************************************************/ - // tells the subclass to add the block and metadata to storage + /** + * @brief add the block and metadata to the db + * + * The subclass implementing this will add the specified block and + * block metadata to its backing store. This does not include its + * transactions, those are added in a separate step. + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + * + * @param blk the block to be added + * @param block_size the size of the block (transactions and all) + * @param cumulative_difficulty the accumulated difficulty after this block + * @param coins_generated the number of coins generated total after this block + * @param blk_hash the hash of the block + */ virtual void add_block( const block& blk , const size_t& block_size , const difficulty_type& cumulative_difficulty @@ -287,84 +291,274 @@ private: , const crypto::hash& blk_hash ) = 0; - // tells the subclass to remove data about the top block + /** + * @brief remove data about the top block + * + * The subclass implementing this will remove the block data from the top + * block in the chain. The data to be removed is that which was added in + * BlockchainDB::add_block(const block& blk, const size_t& block_size, const difficulty_type& cumulative_difficulty, const uint64_t& coins_generated, const crypto::hash& blk_hash) + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + */ virtual void remove_block() = 0; - // tells the subclass to store the transaction and its metadata + /** + * @brief store the transaction and its metadata + * + * The subclass implementing this will add the specified transaction data + * to its backing store. This includes only the transaction blob itself + * and the other data passed here, not the separate outputs of the + * transaction. + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + * + * @param blk_hash the hash of the block containing the transaction + * @param tx the transaction to be added + * @param tx_hash the hash of the transaction + */ virtual void add_transaction_data(const crypto::hash& blk_hash, const transaction& tx, const crypto::hash& tx_hash) = 0; - // tells the subclass to remove data about a transaction + /** + * @brief remove data about a transaction + * + * The subclass implementing this will remove the transaction data + * for the passed transaction. The data to be removed was added in + * add_transaction_data(). Additionally, current subclasses have behavior + * which requires the transaction itself as a parameter here. Future + * implementations should note that this parameter is subject to be removed + * at a later time. + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + * + * @param tx_hash the hash of the transaction to be removed + * @param tx the transaction + */ virtual void remove_transaction_data(const crypto::hash& tx_hash, const transaction& tx) = 0; - // tells the subclass to store an output + /** + * @brief store an output + * + * The subclass implementing this will add the output data passed to its + * backing store in a suitable manner. In addition, the subclass is responsible + * for keeping track of the global output count in some manner, so that + * outputs may be indexed by the order in which they were created. In the + * future, this tracking (of the number, at least) should be moved to + * this class, as it is necessary and the same among all BlockchainDB. + * + * This data should be stored in such a manner that the only thing needed to + * reverse the process is the tx_out. + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + * + * @param tx_hash hash of the transaction the output was created by + * @param tx_output the output + * @param local_index index of the output in its transaction + * @param unlock_time unlock time/height of the output + */ virtual void add_output(const crypto::hash& tx_hash, const tx_out& tx_output, const uint64_t& local_index, const uint64_t unlock_time) = 0; - // tells the subclass to remove an output + /** + * @brief remove an output + * + * The subclass implementing this will remove all output data it stored + * in add_output(). + * + * In addition, the subclass is responsible for correctly decrementing + * its global output counter (this may be automatic for some, such as using + * a database backend "count" feature). + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + * + * @param tx_output the output to be removed + */ virtual void remove_output(const tx_out& tx_output) = 0; - // tells the subclass to store a spent key + /** + * @brief store a spent key + * + * The subclass implementing this will store the spent key image. + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + * + * @param k_image the spent key image to store + */ virtual void add_spent_key(const crypto::key_image& k_image) = 0; - // tells the subclass to remove a spent key + /** + * @brief remove a spent key + * + * The subclass implementing this will remove the key image. + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + * + * @param k_image the spent key image to remove + */ virtual void remove_spent_key(const crypto::key_image& k_image) = 0; /********************************************************************* * private concrete members *********************************************************************/ - // private version of pop_block, for undoing if an add_block goes tits up + /** + * @brief private version of pop_block, for undoing if an add_block fails + * + * This function simply calls pop_block(block& blk, std::vector& txs) + * with dummy parameters, as the returns-by-reference can be discarded. + */ void pop_block(); - // helper function for add_transactions, to add each individual tx + /** + * @brief helper function for add_transactions, to add each individual transaction + * + * This function is called by add_transactions() for each transaction to be + * added. + * + * @param blk_hash hash of the block which has the transaction + * @param tx the transaction to add + * @param tx_hash_ptr the hash of the transaction, if already calculated + */ void add_transaction(const crypto::hash& blk_hash, const transaction& tx, const crypto::hash* tx_hash_ptr = NULL); // helper function to remove transaction from blockchain + /** + * @brief helper function to remove transaction from the blockchain + * + * This function encapsulates aspects of removing a transaction. + * + * @param tx_hash the hash of the transaction to be removed + */ void remove_transaction(const crypto::hash& tx_hash); - uint64_t num_calls = 0; - uint64_t time_blk_hash = 0; - uint64_t time_add_block1 = 0; - uint64_t time_add_transaction = 0; + uint64_t num_calls = 0; //!< a performance metric + uint64_t time_blk_hash = 0; //!< a performance metric + uint64_t time_add_block1 = 0; //!< a performance metric + uint64_t time_add_transaction = 0; //!< a performance metric protected: - mutable uint64_t time_tx_exists = 0; - uint64_t time_commit1 = 0; - bool m_auto_remove_logs = true; + mutable uint64_t time_tx_exists = 0; //!< a performance metric + uint64_t time_commit1 = 0; //!< a performance metric + bool m_auto_remove_logs = true; //!< whether or not to automatically remove old logs HardFork* m_hardfork; public: - // virtual dtor + /** + * @brief An empty destructor. + */ virtual ~BlockchainDB() { }; - // reset profiling stats + /** + * @brief reset profiling stats + */ void reset_stats(); - // show profiling stats + /** + * @brief show profiling stats + * + * This function prints current performance/profiling data to whichever + * log file(s) are set up (possibly including stdout or stderr) + */ void show_stats(); - // open the db at location , or create it if there isn't one. + /** + * @brief open a db, or create it if necessary. + * + * This function opens an existing database or creates it if it + * does not exist. + * + * The subclass implementing this will handle all file opening/creation, + * and is responsible for maintaining its state. + * + * The parameter may not refer to a file name, necessarily, but + * could be an IP:PORT for a database which needs it, and so on. Calling it + * is convenient and should be descriptive enough, however. + * + * For now, db_flags are + * specific to the subclass being instantiated. This is subject to change, + * and the db_flags parameter may be deprecated. + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + * + * @param filename a string referring to the BlockchainDB to open + * @param db_flags flags relevant to how to open/use the BlockchainDB + */ virtual void open(const std::string& filename, const int db_flags = 0) = 0; - // returns true of the db is open/ready, else false + /** + * @brief Gets the current open/ready state of the BlockchainDB + * + * @return true if open/ready, otherwise false + */ bool is_open() const; - // close and sync the db + /** + * @brief close the BlockchainDB + * + * At minimum, this call ensures that further use of the BlockchainDB + * instance will not have effect. In any case where it is necessary + * to do so, a subclass implementing this will sync with disk. + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + */ virtual void close() = 0; - // sync the db + /** + * @brief sync the BlockchainDB with disk + * + * This function should write any changes to whatever permanent backing + * store the subclass uses. Example: a BlockchainDB instance which + * keeps the whole blockchain in RAM won't need to regularly access a + * disk, but should write out its state when this is called. + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + */ virtual void sync() = 0; - // reset the db -- USE WITH CARE + /** + * @brief Remove everything from the BlockchainDB + * + * This function should completely remove all data from a BlockchainDB. + * + * Use with caution! + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + */ virtual void reset() = 0; - // get all files used by this db (if any) + /** + * @brief get all files used by the BlockchainDB (if any) + * + * This function is largely for ease of automation, namely for unit tests. + * + * The subclass implementation should return all filenames it uses. + * + * @return a list of filenames + */ virtual std::vector get_filenames() const = 0; // return the name of the folder the db's file(s) should reside in + /** + * @brief gets the name of the folder the BlockchainDB's file(s) should be in + * + * The subclass implementation should return the name of the folder in which + * it stores files, or an empty string if there is none. + * + * @return the name of the folder with the BlockchainDB's files, if any. + */ virtual std::string get_db_name() const = 0; @@ -372,13 +566,86 @@ public: // RAII-friendly and multi-read one-write friendly locking mechanism // // acquire db lock + /** + * @brief acquires the BlockchainDB lock + * + * This function is a stub until such a time as locking is implemented at + * this level. + * + * The subclass implementation should return true unless implementing a + * locking scheme of some sort, in which case it should return true upon + * acquisition of the lock and block until then. + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + * + * @return true, unless at a future time false makes sense (timeout, etc) + */ virtual bool lock() = 0; // release db lock + /** + * @brief This function releases the BlockchainDB lock + * + * The subclass, should it have implemented lock(), will release any lock + * held by the calling thread. In the case of recursive locking, it should + * release one instance of a lock. + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + */ virtual void unlock() = 0; + /** + * @brief tells the BlockchainDB to start a new "batch" of blocks + * + * If the subclass implements a batching method of caching blocks in RAM to + * be added to a backing store in groups, it should start a batch which will + * end either when has been added or batch_stop() has + * been called. In either case, it should end the batch and write to its + * backing store. + * + * If a batch is already in-progress, this function should throw a DB_ERROR. + * This exception may change in the future if it is deemed necessary to + * have a more granular exception type for this scenario. + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + * + * @param batch_num_blocks number of blocks to batch together + */ virtual void batch_start(uint64_t batch_num_blocks=0) = 0; + + /** + * @brief ends a batch transaction + * + * If the subclass implements batching, this function should store the + * batch it is currently on and mark it finished. + * + * If no batch is in-progress, this function should throw a DB_ERROR. + * This exception may change in the future if it is deemed necessary to + * have a more granular exception type for this scenario. + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + */ virtual void batch_stop() = 0; + + /** + * @brief sets whether or not to batch transactions + * + * If the subclass implements batching, this function tells it to begin + * batching automatically. + * + * If the subclass implements batching and has a batch in-progress, a + * parameter of false should disable batching and call batch_stop() to + * store the current batch. + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + * + * @param bool batch whether or not to use batch transactions. + */ virtual void set_batch_transactions(bool) = 0; virtual void block_txn_start(bool readonly=false) = 0; @@ -388,8 +655,26 @@ public: virtual void set_hard_fork(HardFork* hf); // adds a block with the given metadata to the top of the blockchain, returns the new height - // NOTE: subclass implementations of this (or the functions it calls) need - // to handle undoing any partially-added blocks in the event of a failure. + /** + * @brief handles the addition of a new block to BlockchainDB + * + * This function organizes block addition and calls various functions as + * necessary. + * + * NOTE: subclass implementations of this (or the functions it calls) need + * to handle undoing any partially-added blocks in the event of a failure. + * + * If any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + * + * @param blk the block to be added + * @param block_size the size of the block (transactions and all) + * @param cumulative_difficulty the accumulated difficulty after this block + * @param coins_generated the number of coins generated total after this block + * @param txs the transactions in the block + * + * @return the height of the chain post-addition + */ virtual uint64_t add_block( const block& blk , const size_t& block_size , const difficulty_type& cumulative_difficulty @@ -397,142 +682,639 @@ public: , const std::vector& txs ); - // return true if a block with hash exists in the blockchain + /** + * @brief checks if a block exists + * + * @param h the hash of the requested block + * + * @return true of the block exists, otherwise false + */ virtual bool block_exists(const crypto::hash& h) const = 0; - // return block with hash + /** + * @brief fetches the block with the given hash + * + * The subclass should return the requested block. + * + * If the block does not exist, the subclass should throw BLOCK_DNE + * + * @param h the hash to look for + * + * @return the block requested + */ virtual block get_block(const crypto::hash& h) const = 0; - // return the height of the block with hash on the blockchain, - // throw if it doesn't exist + /** + * @brief gets the height of the block with a given hash + * + * The subclass should return the requested height. + * + * If the block does not exist, the subclass should throw BLOCK_DNE + * + * @param h the hash to look for + * + * @return the height + */ virtual uint64_t get_block_height(const crypto::hash& h) const = 0; - // return header for block with hash + /** + * @brief fetch a block header + * + * The subclass should return the block header from the block with + * the given hash. + * + * If the block does not exist, the subclass should throw BLOCK_DNE + * + * @param h the hash to look for + * + * @return the block header + */ virtual block_header get_block_header(const crypto::hash& h) const = 0; - // return block at height + /** + * @brief fetch a block by height + * + * The subclass should return the block at the given height. + * + * If the block does not exist, that is to say if the blockchain is not + * that high, then the subclass should throw BLOCK_DNE + * + * @param height the height to look for + * + * @return the block + */ virtual block get_block_from_height(const uint64_t& height) const = 0; - // return timestamp of block at height + /** + * @brief fetch a block's timestamp + * + * The subclass should return the timestamp of the block with the + * given height. + * + * If the block does not exist, the subclass should throw BLOCK_DNE + * + * @param height the height requested + * + * @return the timestamp + */ virtual uint64_t get_block_timestamp(const uint64_t& height) const = 0; - // return timestamp of most recent block + /** + * @brief fetch the top block's timestamp + * + * The subclass should return the timestamp of the most recent block. + * + * @return the top block's timestamp + */ virtual uint64_t get_top_block_timestamp() const = 0; - // return block size of block at height + /** + * @brief fetch a block's size + * + * The subclass should return the size of the block with the + * given height. + * + * If the block does not exist, the subclass should throw BLOCK_DNE + * + * @param height the height requested + * + * @return the size + */ virtual size_t get_block_size(const uint64_t& height) const = 0; - // return cumulative difficulty up to and including block at height + /** + * @brief fetch a block's cumulative difficulty + * + * The subclass should return the cumulative difficulty of the block with the + * given height. + * + * If the block does not exist, the subclass should throw BLOCK_DNE + * + * @param height the height requested + * + * @return the cumulative difficulty + */ virtual difficulty_type get_block_cumulative_difficulty(const uint64_t& height) const = 0; - // return difficulty of block at height + /** + * @brief fetch a block's difficulty + * + * The subclass should return the difficulty of the block with the + * given height. + * + * If the block does not exist, the subclass should throw BLOCK_DNE + * + * @param height the height requested + * + * @return the difficulty + */ virtual difficulty_type get_block_difficulty(const uint64_t& height) const = 0; - // return number of coins generated up to and including block at height + /** + * @brief fetch a block's already generated coins + * + * The subclass should return the total coins generated as of the block + * with the given height. + * + * If the block does not exist, the subclass should throw BLOCK_DNE + * + * @param height the height requested + * + * @return the already generated coins + */ virtual uint64_t get_block_already_generated_coins(const uint64_t& height) const = 0; - // return hash of block at height + /** + * @brief fetch a block's hash + * + * The subclass should return hash of the block with the + * given height. + * + * If the block does not exist, the subclass should throw BLOCK_DNE + * + * @param height the height requested + * + * @return the hash + */ virtual crypto::hash get_block_hash_from_height(const uint64_t& height) const = 0; - // return vector of blocks in range of height (inclusively) + /** + * @brief fetch a list of blocks + * + * The subclass should return a vector of blocks with heights starting at + * h1 and ending at h2, inclusively. + * + * If the height range requested goes past the end of the blockchain, + * the subclass should throw BLOCK_DNE. (current implementations simply + * don't catch this exception as thrown by methods called within) + * + * @param h1 the start height + * @param h2 the end height + * + * @return a vector of blocks + */ virtual std::vector get_blocks_range(const uint64_t& h1, const uint64_t& h2) const = 0; - // return vector of block hashes in range of height (inclusively) + /** + * @brief fetch a list of block hashes + * + * The subclass should return a vector of block hashes from blocks with + * heights starting at h1 and ending at h2, inclusively. + * + * If the height range requested goes past the end of the blockchain, + * the subclass should throw BLOCK_DNE. (current implementations simply + * don't catch this exception as thrown by methods called within) + * + * @param h1 the start height + * @param h2 the end height + * + * @return a vector of block hashes + */ virtual std::vector get_hashes_range(const uint64_t& h1, const uint64_t& h2) const = 0; - // return the hash of the top block on the chain + /** + * @brief fetch the top block's hash + * + * The subclass should return the hash of the most recent block + * + * @return the top block's hash + */ virtual crypto::hash top_block_hash() const = 0; - // return the block at the top of the blockchain + /** + * @brief fetch the top block + * + * The subclass should return most recent block + * + * @return the top block + */ virtual block get_top_block() const = 0; - // return the height of the chain + /** + * @brief fetch the current blockchain height + * + * The subclass should return the current blockchain height + * + * @return the current blockchain height + */ virtual uint64_t height() const = 0; - // pops the top block off the blockchain. - // Returns by reference the popped block and its associated transactions - // - // IMPORTANT: - // When a block is popped, the transactions associated with it need to be - // removed, as well as outputs and spent key images associated with - // those transactions. + + /** + * + * + * @brief pops the top block off the blockchain + * + * The subclass should remove the most recent block from the blockchain, + * along with all transactions, outputs, and other metadata created as + * a result of its addition to the blockchain. Most of this is handled + * by the concrete members of the base class provided the subclass correctly + * implements remove_* functions. + * + * The subclass should return by reference the popped block and + * its associated transactions + * + * @param blk return-by-reference the block which was popped + * @param txs return-by-reference the transactions from the popped block + */ virtual void pop_block(block& blk, std::vector& txs); - // return true if a transaction with hash exists + /** + * @brief check if a transaction with a given hash exists + * + * The subclass should check if a transaction is stored which has the + * given hash and return true if so, false otherwise. + * + * @param h the hash to check against + * + * @return true if the transaction exists, otherwise false + */ virtual bool tx_exists(const crypto::hash& h) const = 0; // return unlock time of tx with hash + /** + * @brief fetch a transaction's unlock time/height + * + * The subclass should return the stored unlock time for the transaction + * with the given hash. + * + * If no such transaction exists, the subclass should throw TX_DNE. + * + * @param h the hash of the requested transaction + * + * @return the unlock time/height + */ virtual uint64_t get_tx_unlock_time(const crypto::hash& h) const = 0; // return tx with hash // throw if no such tx exists + /** + * @brief fetches the transaction with the given hash + * + * The subclass should return the transaction stored which has the given + * hash. + * + * If the transaction does not exist, the subclass should throw TX_DNE. + * + * @param h the hash to look for + * + * @return the transaction with the given hash + */ virtual transaction get_tx(const crypto::hash& h) const = 0; - // returns the total number of transactions in all blocks + /** + * @brief fetches the total number of transactions ever + * + * The subclass should return a count of all the transactions from + * all blocks. + * + * @return the number of transactions in the blockchain + */ virtual uint64_t get_tx_count() const = 0; - // return list of tx with hashes . - // TODO: decide if a missing hash means return empty list - // or just skip that hash + /** + * @brief fetches a list of transactions based on their hashes + * + * The subclass should attempt to fetch each transaction referred to by + * the hashes passed. + * + * Currently, if any of the transactions is not in BlockchainDB, the call + * to get_tx in the implementation will throw TX_DNE. + * + * + * + * @param hlist a list of hashes + * + * @return the list of transactions + */ virtual std::vector get_tx_list(const std::vector& hlist) const = 0; // returns height of block that contains transaction with hash + /** + * @brief fetches the height of a transaction's block + * + * The subclass should attempt to return the height of the block containing + * the transaction with the given hash. + * + * If the transaction cannot be found, the subclass should throw TX_DNE. + * + * @param h the hash of the transaction + * + * @return the height of the transaction's block + */ virtual uint64_t get_tx_block_height(const crypto::hash& h) const = 0; // returns the total number of outputs of amount + /** + * @brief fetches the number of outputs of a given amount + * + * The subclass should return a count of outputs of the given amount, + * or zero if there are none. + * + * + * + * @param amount the output amount being looked up + * + * @return the number of outputs of the given amount + */ virtual uint64_t get_num_outputs(const uint64_t& amount) const = 0; - // return index of the first element (should be hidden, but isn't) + /** + * @brief return index of the first element (should be hidden, but isn't) + * + * @return the index + */ virtual uint64_t get_indexing_base() const { return 0; } - // return public key for output with global output amount and index + /** + * @brief get some of an output's data + * + * The subclass should return the public key, unlock time, and block height + * for the output with the given amount and index, collected in a struct. + * + * If the output cannot be found, the subclass should throw OUTPUT_DNE. + * + * If any of these parts cannot be found, but some are, the subclass + * should throw DB_ERROR with a message stating as much. + * + * @param amount the output amount + * @param index the output's index (indexed by amount) + * + * @return the requested output data + */ virtual output_data_t get_output_key(const uint64_t& amount, const uint64_t& index) = 0; + + /** + * @brief get some of an output's data + * + * The subclass should return the public key, unlock time, and block height + * for the output with the given global index, collected in a struct. + * + * If the output cannot be found, the subclass should throw OUTPUT_DNE. + * + * If any of these parts cannot be found, but some are, the subclass + * should throw DB_ERROR with a message stating as much. + * + * @param global_index the output's index (global) + * + * @return the requested output data + */ virtual output_data_t get_output_key(const uint64_t& global_index) const = 0; - // returns the tx hash associated with an output, referenced by global output index + /** + * @brief gets an output's tx hash and index + * + * The subclass should return the hash of the transaction which created the + * output with the global index given, as well as its index in that transaction. + * + * @param index an output's global index + * + * @return the tx hash and output index + */ virtual tx_out_index get_output_tx_and_index_from_global(const uint64_t& index) const = 0; - // returns the transaction-local reference for the output with at - // return type is pair of tx hash and index + /** + * @brief gets an output's tx hash and index + * + * The subclass should return the hash of the transaction which created the + * output with the amount and index given, as well as its index in that + * transaction. + * + * @param amount an output amount + * @param index an output's amount-specific index + * + * @return the tx hash and output index + */ virtual tx_out_index get_output_tx_and_index(const uint64_t& amount, const uint64_t& index) = 0; - virtual void get_output_tx_and_index(const uint64_t& amount, const std::vector &offsets, std::vector &indices) = 0; - virtual void get_output_key(const uint64_t &amount, const std::vector &offsets, std::vector &outputs) = 0; + /** + * @brief gets some outputs' tx hashes and indices + * + * This function is a mirror of + * get_output_tx_and_index(const uint64_t& amount, const uint64_t& index), + * but for a list of outputs rather than just one. + * + * @param amount an output amount + * @param offsets a list of amount-specific output indices + * @param indices return-by-reference a list of tx hashes and output indices (as pairs) + */ + virtual void get_output_tx_and_index(const uint64_t& amount, const std::vector &offsets, std::vector &indices) = 0; + + /** + * @brief gets outputs' data + * + * This function is a mirror of + * get_output_data(const uint64_t& amount, const uint64_t& index) + * but for a list of outputs rather than just one. + * + * @param amount an output amount + * @param offsets a list of amount-specific output indices + * @param outputs return-by-reference a list of outputs' metadata + */ + virtual void get_output_key(const uint64_t &amount, const std::vector &offsets, std::vector &outputs) = 0; + + /* + * FIXME: Need to check with git blame and ask what this does to + * document it + */ virtual bool can_thread_bulk_indices() const = 0; - // return a vector of indices corresponding to the global output index for - // each output in the transaction with hash + /** + * @brief gets output indices (global) for a transaction's outputs + * + * The subclass should fetch the global output indices for each output + * in the transaction with the given hash. + * + * If the transaction does not exist, the subclass should throw TX_DNE. + * + * If an output cannot be found, the subclass should throw OUTPUT_DNE. + * + * @param h a transaction hash + * + * @return a list of global output indices + */ virtual std::vector get_tx_output_indices(const crypto::hash& h) const = 0; - // return a vector of indices corresponding to the amount output index for - // each output in the transaction with hash + + /** + * @brief gets output indices (amount-specific) for a transaction's outputs + * + * The subclass should fetch the amount-specific output indices for each + * output in the transaction with the given hash. + * + * If the transaction does not exist, the subclass should throw TX_DNE. + * + * If an output cannot be found, the subclass should throw OUTPUT_DNE. + * + * @param h a transaction hash + * + * @return a list of amount-specific output indices + */ virtual std::vector get_tx_amount_output_indices(const crypto::hash& h) const = 0; - // returns true if key image is present in spent key images storage + /** + * @brief check if a key image is stored as spent + * + * @param img the key image to check for + * + * @return true if the image is present, otherwise false + */ virtual bool has_key_image(const crypto::key_image& img) const = 0; + /** + * @brief runs a function over all key images stored + * + * The subclass should run the passed function for each key image it has + * stored, passing the key image as its parameter. + * + * If any call to the function returns false, the subclass should return + * false. Otherwise, the subclass returns true. + * + * @param std::function fn the function to run + * + * @return false if the function returns false for any key image, otherwise true + */ virtual bool for_all_key_images(std::function) const = 0; + + /** + * @brief runs a function over all blocks stored + * + * The subclass should run the passed function for each block it has + * stored, passing (block_height, block_hash, block) as its parameters. + * + * If any call to the function returns false, the subclass should return + * false. Otherwise, the subclass returns true. + * + * The subclass should throw DB_ERROR if any of the expected values are + * not found. Current implementations simply return false. + * + * @param std::function fn the function to run + * + * @return false if the function returns false for any block, otherwise true + */ virtual bool for_all_blocks(std::function) const = 0; + + /** + * @brief runs a function over all transactions stored + * + * The subclass should run the passed function for each transaction it has + * stored, passing (transaction_hash, transaction) as its parameters. + * + * If any call to the function returns false, the subclass should return + * false. Otherwise, the subclass returns true. + * + * The subclass should throw DB_ERROR if any of the expected values are + * not found. Current implementations simply return false. + * + * @param std::function fn the function to run + * + * @return false if the function returns false for any transaction, otherwise true + */ virtual bool for_all_transactions(std::function) const = 0; + + /** + * @brief runs a function over all outputs stored + * + * The subclass should run the passed function for each output it has + * stored, passing (amount, transaction_hash, tx_local_output_index) + * as its parameters. + * + * If any call to the function returns false, the subclass should return + * false. Otherwise, the subclass returns true. + * + * The subclass should throw DB_ERROR if any of the expected values are + * not found. Current implementations simply return false. + * + * @param std::function f the function to run + * + * @return false if the function returns false for any output, otherwise true + */ virtual bool for_all_outputs(std::function f) const = 0; + + // // Hard fork related storage + // + + // FIXME: verify that this is all correct + // - TW + /** + * @brief sets the height at which a hard fork has been voted to happen + * + * + * @param version the version voted to fork to + * @param height the height of the first block on the new fork + */ virtual void set_hard_fork_starting_height(uint8_t version, uint64_t height) = 0; + + /** + * @brief gets the height at which a hard fork has been voted to happen + * + * @param version the version to check + * + * @return the height at which the hard fork was accepted, if it has been, + * otherwise max(uint64_t) + */ virtual uint64_t get_hard_fork_starting_height(uint8_t version) const = 0; + + /** + * @brief sets which hardfork version a height is on + * + * @param height the height + * @param version the version + */ virtual void set_hard_fork_version(uint64_t height, uint8_t version) = 0; + + /** + * @brief checks which hardfork version a height is on + * + * @param height the height + * + * @return the version + */ virtual uint8_t get_hard_fork_version(uint64_t height) const = 0; + + /** + * @brief verify hard fork info in database + */ virtual void check_hard_fork_info() = 0; + + /** + * @brief delete hard fork info from database + */ virtual void drop_hard_fork_info() = 0; + /** + * @brief is BlockchainDB in read-only mode? + * + * @return true if in read-only mode, otherwise false + */ virtual bool is_read_only() const = 0; - // fix up anything that may be wrong due to past bugs + // TODO: this should perhaps be (or call) a series of functions which + // progressively update through version updates + /** + * @brief fix up anything that may be wrong due to past bugs + */ virtual void fixup(); + /** + * @brief set whether or not to automatically remove logs + * + * This function is only relevant for one implementation (BlockchainBDB), but + * is here to keep BlockchainDB users implementation-agnostic. + * + * @param auto_remove whether or not to auto-remove logs + */ void set_auto_remove_logs(bool auto_remove) { m_auto_remove_logs = auto_remove; } - bool m_open; - mutable epee::critical_section m_synchronization_lock; + bool m_open; //!< Whether or not the BlockchainDB is open/ready for use + mutable epee::critical_section m_synchronization_lock; //!< A lock, currently for when BlockchainLMDB needs to resize the backing db file + }; // class BlockchainDB diff --git a/src/cryptonote_core/CMakeLists.txt b/src/cryptonote_core/CMakeLists.txt index 88eea1d7..20535679 100644 --- a/src/cryptonote_core/CMakeLists.txt +++ b/src/cryptonote_core/CMakeLists.txt @@ -31,7 +31,6 @@ set(cryptonote_core_sources blockchain_storage.cpp blockchain.cpp checkpoints.cpp - checkpoints_create.cpp cryptonote_basic_impl.cpp cryptonote_core.cpp cryptonote_format_utils.cpp @@ -49,7 +48,6 @@ set(cryptonote_core_private_headers blockchain_storage_boost_serialization.h blockchain.h checkpoints.h - checkpoints_create.h connection_context.h cryptonote_basic.h cryptonote_basic_impl.h diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 131f56a4..2f42e1db 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -49,7 +49,7 @@ #include "common/boost_serialization_helper.h" #include "warnings.h" #include "crypto/hash.h" -#include "cryptonote_core/checkpoints_create.h" +#include "cryptonote_core/checkpoints.h" #include "cryptonote_core/cryptonote_core.h" #if defined(PER_BLOCK_CHECKPOINT) #include "blocks/blocks.h" @@ -840,7 +840,7 @@ bool Blockchain::switch_to_alternative_blockchain(std::list blocks, starting at -// and return by reference . +// get the block sizes of the last blocks, and return by reference . void Blockchain::get_last_n_blocks_sizes(std::vector& sz, size_t count) const { LOG_PRINT_L3("Blockchain::" << __func__); @@ -1408,6 +1407,10 @@ bool Blockchain::get_blocks(uint64_t start_offset, size_t count, std::list missed_tx_ids; std::list txs; + + // FIXME: s/rsp.missed_ids/missed_tx_id/ ? Seems like rsp.missed_ids + // is for missed blocks, not missed transactions as well. get_transactions(bl.tx_hashes, txs, missed_tx_ids); if (missed_tx_ids.size() != 0) @@ -1668,6 +1674,8 @@ uint64_t Blockchain::block_difficulty(uint64_t i) const return 0; } //------------------------------------------------------------------ +//TODO: return type should be void, throw on exception +// alternatively, return true only if no blocks missed template bool Blockchain::get_blocks(const t_ids_container& block_ids, t_blocks_container& blocks, t_missed_container& missed_bs) const { @@ -1692,6 +1700,8 @@ bool Blockchain::get_blocks(const t_ids_container& block_ids, t_blocks_container return true; } //------------------------------------------------------------------ +//TODO: return type should be void, throw on exception +// alternatively, return true only if no transactions missed template bool Blockchain::get_transactions(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs) const { @@ -1708,7 +1718,6 @@ bool Blockchain::get_transactions(const t_ids_container& txs_ids, t_tx_container { missed_txs.push_back(tx_hash); } - //FIXME: is this the correct way to handle this? catch (const std::exception& e) { return false; @@ -1965,6 +1974,11 @@ bool Blockchain::get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vectorheight() < m_blocks_hash_check.size() && kept_by_block) { TIME_MEASURE_START(a); @@ -2038,6 +2053,10 @@ bool Blockchain::have_tx_keyimges_as_spent(const transaction &tx) const } //------------------------------------------------------------------ // This function validates transaction inputs and their keys. +// FIXME: consider moving functionality specific to one input into +// check_tx_input() rather than here, and use this function simply +// to iterate the inputs as necessary (splitting the task +// using threads, etc.) bool Blockchain::check_tx_inputs(const transaction& tx, uint64_t* pmax_used_block_height) { LOG_PRINT_L3("Blockchain::" << __func__); @@ -2320,7 +2339,7 @@ bool Blockchain::check_tx_input(const txin_to_key& txin, const crypto::hash& tx_ output_keys.clear(); - //check ring signature + // collect output keys outputs_visitor vi(output_keys, *this); if (!scan_outputkeys_for_indexes(txin, vi, tx_prefix_hash, pmax_related_block_height)) { @@ -2617,6 +2636,11 @@ leave: txs.push_back(tx); TIME_MEASURE_START(dd); + // FIXME: the storage should not be responsible for validation. + // If it does any, it is merely a sanity check. + // Validation is the purview of the Blockchain class + // - TW + // // ND: this is not needed, db->add_block() checks for duplicate k_images and fails accordingly. // if (!check_for_double_spend(tx, keys)) // { @@ -2797,6 +2821,8 @@ bool Blockchain::add_new_block(const block& bl_, block_verification_context& bvc return handle_block_to_main_chain(bl, id, bvc); } //------------------------------------------------------------------ +//TODO: Refactor, consider returning a failure height and letting +// caller decide course of action. void Blockchain::check_against_checkpoints(const checkpoints& points, bool enforce) { const auto& pts = points.get_points(); @@ -2834,16 +2860,16 @@ void Blockchain::check_against_checkpoints(const checkpoints& points, bool enfor // with an existing checkpoint. bool Blockchain::update_checkpoints(const std::string& file_path, bool check_dns) { - if (!cryptonote::load_checkpoints_from_json(m_checkpoints, file_path)) + if (!m_checkpoints.load_checkpoints_from_json(file_path)) { - return false; + return false; } // if we're checking both dns and json, load checkpoints from dns. // if we're not hard-enforcing dns checkpoints, handle accordingly if (m_enforce_dns_checkpoints && check_dns) { - if (!cryptonote::load_checkpoints_from_dns(m_checkpoints)) + if (!m_checkpoints.load_checkpoints_from_dns()) { return false; } @@ -2851,7 +2877,7 @@ bool Blockchain::update_checkpoints(const std::string& file_path, bool check_dns else if (check_dns) { checkpoints dns_points; - cryptonote::load_checkpoints_from_dns(dns_points); + dns_points.load_checkpoints_from_dns(); if (m_checkpoints.check_for_conflicts(dns_points)) { check_against_checkpoints(dns_points, false); @@ -2878,6 +2904,8 @@ void Blockchain::block_longhash_worker(const uint64_t height, const std::vector< TIME_MEASURE_START(t); slow_hash_allocate_state(); + //FIXME: height should be changing here, as get_block_longhash expects + // the height of the block passed to it for (const auto & block : blocks) { crypto::hash id = get_block_hash(block); @@ -2933,6 +2961,7 @@ bool Blockchain::cleanup_handle_incoming_blocks(bool force_sync) } //------------------------------------------------------------------ +//FIXME: unused parameter txs void Blockchain::output_scan_worker(const uint64_t amount, const std::vector &offsets, std::vector &outputs, std::unordered_map &txs) const { try diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index ab2c8f9e..94def1aa 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -60,11 +60,14 @@ namespace cryptonote class tx_memory_pool; struct test_options; + /** Declares ways in which the BlockchainDB backend should be told to sync + * + */ enum blockchain_db_sync_mode { - db_sync, - db_async, - db_nosync + db_sync, //!< handle syncing calls instead of the backing db, synchronously + db_async, //!< handle syncing calls instead of the backing db, asynchronously + db_nosync //!< Leave syncing up to the backing db (safest, but slowest because of disk I/O) }; /************************************************************************/ @@ -73,6 +76,9 @@ namespace cryptonote class Blockchain { public: + /** + * @brief Now-defunct (TODO: remove) struct from in-memory blockchain + */ struct transaction_chain_entry { transaction tx; @@ -81,127 +87,697 @@ namespace cryptonote std::vector m_global_output_indexes; }; + /** + * @brief container for passing a block and metadata about it on the blockchain + */ struct block_extended_info { - block bl; - uint64_t height; - size_t block_cumulative_size; - difficulty_type cumulative_difficulty; - uint64_t already_generated_coins; + block bl; //!< the block + uint64_t height; //!< the height of the block in the blockchain + size_t block_cumulative_size; //!< the size (in bytes) of the block + difficulty_type cumulative_difficulty; //!< the accumulated difficulty after that block + uint64_t already_generated_coins; //!< the total coins minted after that block }; + /** + * @brief Blockchain constructor + * + * @param tx_pool a reference to the transaction pool to be kept by the Blockchain + */ Blockchain(tx_memory_pool& tx_pool); + /** + * @brief Initialize the Blockchain state + * + * @param db a pointer to the backing store to use for the blockchain + * @param testnet true if on testnet, else false + * @param test_options test parameters + * + * @return true on success, false if any initialization steps fail + */ bool init(BlockchainDB* db, const bool testnet = false, const cryptonote::test_options *test_options = NULL); + + /** + * @brief Initialize the Blockchain state + * + * @param db a pointer to the backing store to use for the blockchain + * @param hf a structure containing hardfork information + * @param testnet true if on testnet, else false + * + * @return true on success, false if any initialization steps fail + */ bool init(BlockchainDB* db, HardFork*& hf, const bool testnet = false); + + /** + * @brief Uninitializes the blockchain state + * + * Saves to disk any state that needs to be maintained + * + * @return true on success, false if any uninitialization steps fail + */ bool deinit(); + /** + * @brief assign a set of blockchain checkpoint hashes + * + * @param chk_pts the set of checkpoints to assign + */ void set_checkpoints(checkpoints&& chk_pts) { m_checkpoints = chk_pts; } - //bool push_new_block(); + /** + * @brief get blocks and transactions from blocks based on start height and count + * + * @param start_offset the height on the blockchain to start at + * @param count the number of blocks to get, if there are as many after start_offset + * @param blocks return-by-reference container to put result blocks in + * @param txs return-by-reference container to put result transactions in + * + * @return false if start_offset > blockchain height, else true + */ bool get_blocks(uint64_t start_offset, size_t count, std::list& blocks, std::list& txs) const; + + /** + * @brief get blocks from blocks based on start height and count + * + * @param start_offset the height on the blockchain to start at + * @param count the number of blocks to get, if there are as many after start_offset + * @param blocks return-by-reference container to put result blocks in + * + * @return false if start_offset > blockchain height, else true + */ bool get_blocks(uint64_t start_offset, size_t count, std::list& blocks) const; + + /** + * @brief compiles a list of all blocks stored as alternative chains + * + * @param blocks return-by-reference container to put result blocks in + * + * @return true + */ bool get_alternative_blocks(std::list& blocks) const; + + /** + * @brief returns the number of alternative blocks stored + * + * @return the number of alternative blocks stored + */ size_t get_alternative_blocks_count() const; + + /** + * @brief gets a block's hash given a height + * + * @param height the height of the block + * + * @return the hash of the block at the requested height, or a zeroed hash if there is no such block + */ crypto::hash get_block_id_by_height(uint64_t height) const; + + /** + * @brief gets the block with a given hash + * + * @param h the hash to look for + * @param blk return-by-reference variable to put result block in + * + * @return true if the block was found, else false + */ bool get_block_by_hash(const crypto::hash &h, block &blk) const; + + /** + * @brief get all block hashes (main chain, alt chains, and invalid blocks) + * + * @param main return-by-reference container to put result main chain blocks' hashes in + * @param alt return-by-reference container to put result alt chain blocks' hashes in + * @param invalid return-by-reference container to put result invalid blocks' hashes in + */ void get_all_known_block_ids(std::list &main, std::list &alt, std::list &invalid) const; + + /** + * @brief performs some preprocessing on a group of incoming blocks to speed up verification + * + * @param blocks a list of incoming blocks + * + * @return false on erroneous blocks, else true + */ bool prepare_handle_incoming_blocks(const std::list &blocks); + + /** + * @brief incoming blocks post-processing, cleanup, and disk sync + * + * @param force_sync if true, and Blockchain is handling syncing to disk, always sync + * + * @return true + */ bool cleanup_handle_incoming_blocks(bool force_sync = false); + /** + * @brief search the blockchain for a transaction by hash + * + * @param id the hash to search for + * + * @return true if the tx exists, else false + */ bool have_tx(const crypto::hash &id) const; + + /** + * @brief check if any key image in a transaction has already been spent + * + * @param tx the transaction to check + * + * @return true if any key image is already spent in the blockchain, else false + */ bool have_tx_keyimges_as_spent(const transaction &tx) const; + + /** + * @brief check if a key image is already spent on the blockchain + * + * Whenever a transaction output is used as an input for another transaction + * (a true input, not just one of a mixing set), a key image is generated + * and stored in the transaction in order to prevent double spending. If + * this key image is seen again, the transaction using it is rejected. + * + * @param key_im the key image to search for + * + * @return true if the key image is already spent in the blockchain, else false + */ bool have_tx_keyimg_as_spent(const crypto::key_image &key_im) const; + /** + * @brief get the current height of the blockchain + * + * @return the height + */ uint64_t get_current_blockchain_height() const; + + /** + * @brief get the hash of the most recent block on the blockchain + * + * @return the hash + */ crypto::hash get_tail_id() const; + + /** + * @brief get the height and hash of the most recent block on the blockchain + * + * @param height return-by-reference variable to store the height in + * + * @return the hash + */ crypto::hash get_tail_id(uint64_t& height) const; + + /** + * @brief returns the difficulty target the next block to be added must meet + * + * @return the target + */ difficulty_type get_difficulty_for_next_block(); + + /** + * @brief adds a block to the blockchain + * + * Adds a new block to the blockchain. If the block's parent is not the + * current top of the blockchain, the block may be added to an alternate + * chain. If the block does not belong, is already in the blockchain + * or an alternate chain, or is invalid, return false. + * + * @param bl_ the block to be added + * @param bvc metadata about the block addition's success/failure + * + * @return true on successful addition to the blockchain, else false + */ bool add_new_block(const block& bl_, block_verification_context& bvc); + + /** + * @brief clears the blockchain and starts a new one + * + * @param b the first block in the new chain (the genesis block) + * + * @return true on success, else false + */ bool reset_and_set_genesis_block(const block& b); + + /** + * @brief creates a new block to mine against + * + * @param b return-by-reference block to be filled in + * @param miner_address address new coins for the block will go to + * @param di return-by-reference tells the miner what the difficulty target is + * @param height return-by-reference tells the miner what height it's mining against + * @param ex_nonce extra data to be added to the miner transaction's extra + * + * @return true if block template filled in successfully, else false + */ bool create_block_template(block& b, const account_public_address& miner_address, difficulty_type& di, uint64_t& height, const blobdata& ex_nonce); + + /** + * @brief checks if a block is known about with a given hash + * + * This function checks the main chain, alternate chains, and invalid blocks + * for a block with the given hash + * + * @param id the hash to search for + * + * @return true if the block is known, else false + */ bool have_block(const crypto::hash& id) const; + + /** + * @brief gets the total number of transactions on the main chain + * + * @return the number of transactions on the main chain + */ size_t get_total_transactions() const; + + /** + * @brief gets the hashes for a subset of the blockchain + * + * puts into list a list of hashes representing certain blocks + * from the blockchain in reverse chronological order + * + * the blocks chosen, at the time of this writing, are: + * the most recent 11 + * powers of 2 less recent from there, so 13, 17, 25, etc... + * + * @param ids return-by-reference list to put the resulting hashes in + * + * @return true + */ bool get_short_chain_history(std::list& ids) const; + + /** + * @brief get recent block hashes for a foreign chain + * + * Find the split point between us and foreign blockchain and return + * (by reference) the most recent common block hash along with up to + * BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT additional (more recent) hashes. + * + * @param qblock_ids the foreign chain's "short history" (see get_short_chain_history) + * @param resp return-by-reference the split height and subsequent blocks' hashes + * + * @return true if a block found in common, else false + */ bool find_blockchain_supplement(const std::list& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const; + + /** + * @brief find the most recent common point between ours and a foreign chain + * + * This function takes a list of block hashes from another node + * on the network to find where the split point is between us and them. + * This is used to see what to send another node that needs to sync. + * + * @param qblock_ids the foreign chain's "short history" (see get_short_chain_history) + * @param starter_offset return-by-reference the most recent common block's height + * + * @return true if a block found in common, else false + */ bool find_blockchain_supplement(const std::list& qblock_ids, uint64_t& starter_offset) const; + + /** + * @brief get recent blocks for a foreign chain + * + * This function gets recent blocks relative to a foreign chain, starting either at + * a requested height or whatever height is the most recent ours and the foreign + * chain have in common. + * + * @param req_start_block if non-zero, specifies a start point (otherwise find most recent commonality) + * @param qblock_ids the foreign chain's "short history" (see get_short_chain_history) + * @param blocks return-by-reference the blocks and their transactions + * @param total_height return-by-reference our current blockchain height + * @param start_height return-by-reference the height of the first block returned + * @param max_count the max number of blocks to get + * + * @return true if a block found in common or req_start_block specified, else false + */ bool find_blockchain_supplement(const uint64_t req_start_block, const std::list& qblock_ids, std::list > >& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count) const; + + /** + * @brief retrieves a set of blocks and their transactions, and possibly other transactions + * + * the request object encapsulates a list of block hashes and a (possibly empty) list of + * transaction hashes. for each block hash, the block is fetched along with all of that + * block's transactions. Any transactions requested separately are fetched afterwards. + * + * @param arg the request + * @param rsp return-by-reference the response to fill in + * + * @return true unless any blocks or transactions are missing + */ bool handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NOTIFY_RESPONSE_GET_OBJECTS::request& rsp); - bool handle_get_objects(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response& res); + + /** + * @brief gets random outputs to mix with + * + * This function takes an RPC request for outputs to mix with + * and creates an RPC response with the resultant output indices. + * + * Outputs to mix with are randomly selected from the utxo set + * for each output amount in the request. + * + * @param req the output amounts and number of mixins to select + * @param res return-by-reference the resultant output indices + * + * @return true + */ bool get_random_outs_for_amounts(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response& res) const; + + /** + * @brief gets the global indices for outputs from a given transaction + * + * This function gets the global indices for all outputs belonging + * to a specific transaction. + * + * @param tx_id the hash of the transaction to fetch indices for + * @param indexs return-by-reference the global indices for the transaction's outputs + * + * @return false if the transaction does not exist, or if no indices are found, otherwise true + */ bool get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vector& indexs) const; + + /** + * @brief stores the blockchain + * + * If Blockchain is handling storing of the blockchain (rather than BlockchainDB), + * this initiates a blockchain save. + * + * @return true unless saving the blockchain fails + */ bool store_blockchain(); + /** + * @brief validates a transaction's inputs + * + * validates a transaction's inputs as correctly used and not previously + * spent. also returns the hash and height of the most recent block + * which contains an output that was used as an input to the transaction. + * + * @param tx the transaction to validate + * @param pmax_used_block_height return-by-reference block height of most recent input + * @param max_used_block_id return-by-reference block hash of most recent input + * @param kept_by_block whether or not the transaction is from a previously-verified block + * + * @return false if any input is invalid, otherwise true + */ bool check_tx_inputs(const transaction& tx, uint64_t& pmax_used_block_height, crypto::hash& max_used_block_id, bool kept_by_block = false); + + /** + * @brief check that a transaction's outputs conform to current standards + * + * This function checks, for example at the time of this writing, that + * each output is of the form a * 10^b (phrased differently, that if + * written out would have only one non-zero digit in base 10). + * + * @param tx the transaction to check the outputs of + * + * @return false if any outputs do not conform, otherwise true + */ bool check_tx_outputs(const transaction& tx); + + /** + * @brief gets the blocksize limit based on recent blocks + * + * @return the limit + */ uint64_t get_current_cumulative_blocksize_limit() const; + + /** + * @brief checks if the blockchain is currently being stored + * + * Note: this should be meaningless in cases where Blockchain is not + * directly managing saving the blockchain to disk. + * + * @return true if Blockchain is having the chain stored currently, else false + */ bool is_storing_blockchain()const{return m_is_blockchain_storing;} + + /** + * @brief gets the difficulty of the block with a given height + * + * @param i the height + * + * @return the difficulty + */ uint64_t block_difficulty(uint64_t i) const; + /** + * @brief gets blocks based on a list of block hashes + * + * @tparam t_ids_container a standard-iterable container + * @tparam t_blocks_container a standard-iterable container + * @tparam t_missed_container a standard-iterable container + * @param block_ids a container of block hashes for which to get the corresponding blocks + * @param blocks return-by-reference a container to store result blocks in + * @param missed_bs return-by-reference a container to store missed blocks in + * + * @return false if an unexpected exception occurs, else true + */ template bool get_blocks(const t_ids_container& block_ids, t_blocks_container& blocks, t_missed_container& missed_bs) const; + /** + * @brief gets transactions based on a list of transaction hashes + * + * @tparam t_ids_container a standard-iterable container + * @tparam t_tx_container a standard-iterable container + * @tparam t_missed_container a standard-iterable container + * @param txs_ids a container of hashes for which to get the corresponding transactions + * @param txs return-by-reference a container to store result transactions in + * @param missed_txs return-by-reference a container to store missed transactions in + * + * @return false if an unexpected exception occurs, else true + */ template bool get_transactions(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs) const; + //debug functions + + /** + * @brief prints data about a snippet of the blockchain + * + * if start_index is greater than the blockchain height, do nothing + * + * @param start_index height on chain to start at + * @param end_index height on chain to end at + */ void print_blockchain(uint64_t start_index, uint64_t end_index) const; + + /** + * @brief prints every block's hash + * + * WARNING: This function will absolutely crush a terminal in prints, so + * it is recommended to redirect this output to a log file (or null sink + * if a log file is already set up, as should be the default) + */ void print_blockchain_index() const; + + /** + * @brief currently does nothing, candidate for removal + * + * @param file + */ void print_blockchain_outs(const std::string& file) const; + /** + * @brief check the blockchain against a set of checkpoints + * + * If a block fails a checkpoint and enforce is enabled, the blockchain + * will be rolled back to two blocks prior to that block. If enforce + * is disabled, as is currently the default case with DNS-based checkpoints, + * an error will be printed to the user but no other action will be taken. + * + * @param points the checkpoints to check against + * @param enforce whether or not to take action on failure + */ void check_against_checkpoints(const checkpoints& points, bool enforce); + + /** + * @brief configure whether or not to enforce DNS-based checkpoints + * + * @param enforce the new enforcement setting + */ void set_enforce_dns_checkpoints(bool enforce); + + /** + * @brief loads new checkpoints from a file and optionally from DNS + * + * @param file_path the path of the file to look for and load checkpoints from + * @param check_dns whether or not to check for new DNS-based checkpoints + * + * @return false if any enforced checkpoint type fails to load, otherwise true + */ bool update_checkpoints(const std::string& file_path, bool check_dns); + // user options, must be called before calling init() + + //FIXME: parameter names don't match function definition in .cpp file + /** + * @brief sets various performance options + * + * @param block_threads max number of threads when preparing blocks for addition + * @param blocks_per_sync number of blocks to cache before syncing to database + * @param sync_mode the ::blockchain_db_sync_mode to use + * @param fast_sync sync using built-in block hashes as trusted + */ void set_user_options(uint64_t block_threads, uint64_t blocks_per_sync, blockchain_db_sync_mode sync_mode, bool fast_sync); + /** + * @brief set whether or not to show/print time statistics + * + * @param stats the new time stats setting + */ void set_show_time_stats(bool stats) { m_show_time_stats = stats; } + /** + * @brief gets the hardfork voting state object + * + * @return the HardFork object + */ HardFork::State get_hard_fork_state() const; + + /** + * @brief gets the current hardfork version in use/voted for + * + * @return the version + */ uint8_t get_current_hard_fork_version() const { return m_hardfork->get_current_version(); } + + /** + * @brief returns the newest hardfork version known to the blockchain + * + * @return the version + */ uint8_t get_ideal_hard_fork_version() const { return m_hardfork->get_ideal_version(); } + + /** + * @brief returns the newest hardfork version voted to be enabled + * as of a certain height + * + * @param height the height for which to check version info + * + * @return the version + */ uint8_t get_ideal_hard_fork_version(uint64_t height) const { return m_hardfork->get_ideal_version(height); } + /** + * @brief get information about hardfork voting for a version + * + * @param version the version in question + * @param window the size of the voting window + * @param votes the number of votes to enable + * @param threshold the number of votes required to enable + * @param earliest_height the earliest height at which is allowed + * @param voting which version this node is voting for/using + * + * @return whether the version queried is enabled + */ bool get_hard_fork_voting_info(uint8_t version, uint32_t &window, uint32_t &votes, uint32_t &threshold, uint64_t &earliest_height, uint8_t &voting) const; + /** + * @brief remove transactions from the transaction pool (if present) + * + * @param txids a list of hashes of transactions to be removed + * + * @return false if any removals fail, otherwise true + */ bool flush_txes_from_pool(const std::list &txids); + /** + * @brief perform a check on all key images in the blockchain + * + * @param std::function the check to perform, pass/fail + * + * @return false if any key image fails the check, otherwise true + */ bool for_all_key_images(std::function) const; + + /** + * @brief perform a check on all blocks in the blockchain + * + * @param std::function the check to perform, pass/fail + * + * @return false if any block fails the check, otherwise true + */ bool for_all_blocks(std::function) const; + + /** + * @brief perform a check on all transactions in the blockchain + * + * @param std::function the check to perform, pass/fail + * + * @return false if any transaction fails the check, otherwise true + */ bool for_all_transactions(std::function) const; + + /** + * @brief perform a check on all outputs in the blockchain + * + * @param std::function the check to perform, pass/fail + * + * @return false if any output fails the check, otherwise true + */ bool for_all_outputs(std::function) const; + /** + * @brief get a reference to the BlockchainDB in use by Blockchain + * + * @return a reference to the BlockchainDB instance + */ BlockchainDB& get_db() { return *m_db; } + /** + * @brief get a number of outputs of a specific amount + * + * @param amount the amount + * @param offsets the indices (indexed to the amount) of the outputs + * @param outputs return-by-reference the outputs collected + * @param txs unused, candidate for removal + */ void output_scan_worker(const uint64_t amount,const std::vector &offsets, std::vector &outputs, std::unordered_map &txs) const; + /** + * @brief computes the "short" and "long" hashes for a set of blocks + * + * @param height the height of the first block + * @param blocks the blocks to be hashed + * @param map return-by-reference the hashes for each block + */ void block_longhash_worker(const uint64_t height, const std::vector &blocks, std::unordered_map &map) const; private: + + // TODO: evaluate whether or not each of these typedefs are left over from blockchain_storage typedef std::unordered_map blocks_by_id_index; + typedef std::unordered_map transactions_container; + typedef std::unordered_set key_images_container; + typedef std::vector blocks_container; + typedef std::unordered_map blocks_ext_by_hash; + typedef std::unordered_map blocks_by_hash; + typedef std::map>> outputs_container; //crypto::hash - tx hash, size_t - index of out in transaction + BlockchainDB* m_db; tx_memory_pool& m_tx_pool; + mutable epee::critical_section m_blockchain_lock; // TODO: add here reader/writer lock // main chain transactions_container m_transactions; size_t m_current_block_cumul_sz_limit; + // metadata containers std::unordered_map>> m_scan_table; std::unordered_map> m_check_tx_inputs_table; std::unordered_map m_blocks_longhash_table; @@ -243,39 +819,323 @@ namespace cryptonote bool m_testnet; + /** + * @brief collects the keys for all outputs being "spent" as an input + * + * This function makes sure that each "input" in an input (mixins) exists + * and collects the public key for each from the transaction it was included in + * via the visitor passed to it. + * + * If pmax_related_block_height is not NULL, its value is set to the height + * of the most recent block which contains an output used in the input set + * + * @tparam visitor_t a class encapsulating tx is unlocked and collect tx key + * @param tx_in_to_key a transaction input instance + * @param vis an instance of the visitor to use + * @param tx_prefix_hash the hash of the associated transaction_prefix + * @param pmax_related_block_height return-by-pointer the height of the most recent block in the input set + * + * @return false if any keys are not found or any inputs are not unlocked, otherwise true + */ template inline bool scan_outputkeys_for_indexes(const txin_to_key& tx_in_to_key, visitor_t &vis, const crypto::hash &tx_prefix_hash, uint64_t* pmax_related_block_height = NULL) const; + + /** + * @brief collect output public keys of a transaction input set + * + * This function locates all outputs associated with a given input set (mixins) + * and validates that they exist and are usable + * (unlocked, unspent is checked elsewhere). + * + * If pmax_related_block_height is not NULL, its value is set to the height + * of the most recent block which contains an output used in the input set + * + * @param txin the transaction input + * @param tx_prefix_hash the transaction prefix hash, for caching organization + * @param sig the input signature + * @param output_keys return-by-reference the public keys of the outputs in the input set + * @param pmax_related_block_height return-by-pointer the height of the most recent block in the input set + * + * @return false if any output is not yet unlocked, or is missing, otherwise true + */ bool check_tx_input(const txin_to_key& txin, const crypto::hash& tx_prefix_hash, const std::vector& sig, std::vector &output_keys, uint64_t* pmax_related_block_height); + + /** + * @brief validate a transaction's inputs and their keys + * + * This function validates transaction inputs and their keys. Previously + * it also performed double spend checking, but that has been moved to its + * own function. + * + * If pmax_related_block_height is not NULL, its value is set to the height + * of the most recent block which contains an output used in any input set + * + * Currently this function calls ring signature validation for each + * transaction. + * + * @param tx the transaction to validate + * @param pmax_related_block_height return-by-pointer the height of the most recent block in the input set + * + * @return false if any validation step fails, otherwise true + */ bool check_tx_inputs(const transaction& tx, uint64_t* pmax_used_block_height = NULL); + /** + * @brief performs a blockchain reorganization according to the longest chain rule + * + * This function aggregates all the actions necessary to switch to a + * newly-longer chain. If any step in the reorganization process fails, + * the blockchain is reverted to its previous state. + * + * @param alt_chain the chain to switch to + * @param discard_disconnected_chain whether or not to keep the old chain as an alternate + * + * @return false if the reorganization fails, otherwise true + */ bool switch_to_alternative_blockchain(std::list& alt_chain, bool discard_disconnected_chain); + + /** + * @brief removes the most recent block from the blockchain + * + * @return the block removed + */ block pop_block_from_blockchain(); + /** + * @brief validate and add a new block to the end of the blockchain + * + * This function is merely a convenience wrapper around the other + * of the same name. This one passes the block's hash to the other + * as well as the block and verification context. + * + * @param bl the block to be added + * @param bvc metadata concerning the block's validity + * + * @return true if the block was added successfully, otherwise false + */ bool handle_block_to_main_chain(const block& bl, block_verification_context& bvc); + + /** + * @brief validate and add a new block to the end of the blockchain + * + * When a block is given to Blockchain to be added to the blockchain, it + * is passed here if it is determined to belong at the end of the current + * chain. + * + * @param bl the block to be added + * @param id the hash of the block + * @param bvc metadata concerning the block's validity + * + * @return true if the block was added successfully, otherwise false + */ bool handle_block_to_main_chain(const block& bl, const crypto::hash& id, block_verification_context& bvc); + + /** + * @brief validate and add a new block to an alternate blockchain + * + * If a block to be added does not belong to the main chain, but there + * is an alternate chain to which it should be added, that is handled + * here. + * + * @param b the block to be added + * @param id the hash of the block + * @param bvc metadata concerning the block's validity + * + * @return true if the block was added successfully, otherwise false + */ bool handle_alternative_block(const block& b, const crypto::hash& id, block_verification_context& bvc); + + /** + * @brief gets the difficulty requirement for a new block on an alternate chain + * + * @param alt_chain the chain to be added to + * @param bei the block being added (and metadata, see ::block_extended_info) + * + * @return the difficulty requirement + */ difficulty_type get_next_difficulty_for_alternative_chain(const std::list& alt_chain, block_extended_info& bei) const; + + /** + * @brief sanity checks a miner transaction before validating an entire block + * + * This function merely checks basic things like the structure of the miner + * transaction, the unlock time, and that the amount doesn't overflow. + * + * @param b the block containing the miner transaction + * @param height the height at which the block will be added + * + * @return false if anything is found wrong with the miner transaction, otherwise true + */ bool prevalidate_miner_transaction(const block& b, uint64_t height); + + /** + * @brief validates a miner (coinbase) transaction + * + * This function makes sure that the miner calculated his reward correctly + * and that his miner transaction totals reward + fee. + * + * @param b the block containing the miner transaction to be validated + * @param cumulative_block_size the block's size + * @param fee the total fees collected in the block + * @param base_reward return-by-reference the new block's generated coins + * @param already_generated_coins the amount of currency generated prior to this block + * @param partial_block_reward return-by-reference true if miner accepted only partial reward + * + * @return false if anything is found wrong with the miner transaction, otherwise true + */ bool validate_miner_transaction(const block& b, size_t cumulative_block_size, uint64_t fee, uint64_t& base_reward, uint64_t already_generated_coins, bool &partial_block_reward); - bool validate_transaction(const block& b, uint64_t height, const transaction& tx); + + /** + * @brief reverts the blockchain to its previous state following a failed switch + * + * If Blockchain fails to switch to an alternate chain when it means + * to do so, this function reverts the blockchain to how it was before + * the attempted switch. + * + * @param original_chain the chain to switch back to + * @param rollback_height the height to revert to before appending the original chain + * + * @return false if something goes wrong with reverting (very bad), otherwise true + */ bool rollback_blockchain_switching(std::list& original_chain, uint64_t rollback_height); - bool add_transaction_from_block(const transaction& tx, const crypto::hash& tx_id, const crypto::hash& bl_id, uint64_t bl_height); - bool push_transaction_to_global_outs_index(const transaction& tx, const crypto::hash& tx_id, std::vector& global_indexes); - bool pop_transaction_from_global_index(const transaction& tx, const crypto::hash& tx_id); + + /** + * @brief gets recent block sizes for median calculation + * + * get the block sizes of the last blocks, and return by reference . + * + * @param sz return-by-reference the list of sizes + * @param count the number of blocks to get sizes for + */ void get_last_n_blocks_sizes(std::vector& sz, size_t count) const; + + /** + * @brief adds the given output to the requested set of random outputs + * + * @param result_outs return-by-reference the set the output is to be added to + * @param amount the output amount + * @param i the output index (indexed to amount) + */ void add_out_to_get_random_outs(COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& result_outs, uint64_t amount, size_t i) const; + + /** + * @brief checks if a transaction is unlocked (its outputs spendable) + * + * This function checks to see if a transaction is unlocked. + * unlock_time is either a block index or a unix time. + * + * @param unlock_time the unlock parameter (height or time) + * + * @return true if spendable, otherwise false + */ bool is_tx_spendtime_unlocked(uint64_t unlock_time) const; + + /** + * @brief stores an invalid block in a separate container + * + * Storing invalid blocks allows quick dismissal of the same block + * if it is seen again. + * + * @param bl the invalid block + * @param h the block's hash + * + * @return false if the block cannot be stored for some reason, otherwise true + */ bool add_block_as_invalid(const block& bl, const crypto::hash& h); + + /** + * @brief stores an invalid block in a separate container + * + * Storing invalid blocks allows quick dismissal of the same block + * if it is seen again. + * + * @param bei the invalid block (see ::block_extended_info) + * @param h the block's hash + * + * @return false if the block cannot be stored for some reason, otherwise true + */ bool add_block_as_invalid(const block_extended_info& bei, const crypto::hash& h); + + /** + * @brief checks a block's timestamp + * + * This function grabs the timestamps from the most recent blocks, + * where n = BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW. If there are not those many + * blocks in the blockchain, the timestap is assumed to be valid. If there + * are, this function returns: + * true if the block's timestamp is not less than the timestamp of the + * median of the selected blocks + * false otherwise + * + * @param b the block to be checked + * + * @return true if the block's timestamp is valid, otherwise false + */ bool check_block_timestamp(const block& b) const; + + /** + * @brief checks a block's timestamp + * + * If the block is not more recent than the median of the recent + * timestamps passed here, it is considered invalid. + * + * @param timestamps a list of the most recent timestamps to check against + * @param b the block to be checked + * + * @return true if the block's timestamp is valid, otherwise false + */ bool check_block_timestamp(std::vector& timestamps, const block& b) const; + + /** + * @brief get the "adjusted time" + * + * Currently this simply returns the current time according to the + * user's machine. + * + * @return the current time + */ uint64_t get_adjusted_time() const; + + /** + * @brief finish an alternate chain's timestamp window from the main chain + * + * for an alternate chain, get the timestamps from the main chain to complete + * the needed number of timestamps for the BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW. + * + * @param start_height the alternate chain's attachment height to the main chain + * @param timestamps return-by-value the timestamps set to be populated + * + * @return true unless start_height is greater than the current blockchain height + */ bool complete_timestamps_vector(uint64_t start_height, std::vector& timestamps); + + /** + * @brief calculate the block size limit for the next block to be added + * + * @return true + */ bool update_next_cumulative_size_limit(); void return_tx_to_pool(const std::vector &txs); + /** + * @brief make sure a transaction isn't attempting a double-spend + * + * @param tx the transaction to check + * @param keys_this_block a cumulative list of spent keys for the current block + * + * @return false if a double spend was detected, otherwise true + */ bool check_for_double_spend(const transaction& tx, key_images_container& keys_this_block) const; - void get_timestamp_and_difficulty(uint64_t ×tamp, difficulty_type &difficulty, const int offset) const; + + /** + * @brief validates a transaction input's ring signature + * + * @param tx_prefix_hash the transaction prefix' hash + * @param key_image the key image generated from the true input + * @param pubkeys the public keys for each input in the ring signature + * @param sig the signature generated for each input in the ring signature + * @param result false if the ring signature is invalid, otherwise true + */ void check_ring_signature(const crypto::hash &tx_prefix_hash, const crypto::key_image &key_image, const std::vector &pubkeys, const std::vector &sig, uint64_t &result); diff --git a/src/cryptonote_core/blockchain_storage.cpp b/src/cryptonote_core/blockchain_storage.cpp index e1b89f88..a829b7cb 100644 --- a/src/cryptonote_core/blockchain_storage.cpp +++ b/src/cryptonote_core/blockchain_storage.cpp @@ -48,7 +48,7 @@ #include "common/boost_serialization_helper.h" #include "warnings.h" #include "crypto/hash.h" -#include "cryptonote_core/checkpoints_create.h" +#include "cryptonote_core/checkpoints.h" //#include "serialization/json_archive.h" #include "../../contrib/otshell_utils/utils.hpp" #include "../../src/p2p/data_logger.hpp" @@ -1854,7 +1854,7 @@ void blockchain_storage::check_against_checkpoints(const checkpoints& points, bo // with an existing checkpoint. bool blockchain_storage::update_checkpoints(const std::string& file_path, bool check_dns) { - if (!cryptonote::load_checkpoints_from_json(m_checkpoints, file_path)) + if (!m_checkpoints.load_checkpoints_from_json(file_path)) { return false; } @@ -1863,7 +1863,7 @@ bool blockchain_storage::update_checkpoints(const std::string& file_path, bool c // if we're not hard-enforcing dns checkpoints, handle accordingly if (m_enforce_dns_checkpoints && check_dns) { - if (!cryptonote::load_checkpoints_from_dns(m_checkpoints)) + if (!m_checkpoints.load_checkpoints_from_dns()) { return false; } @@ -1871,7 +1871,7 @@ bool blockchain_storage::update_checkpoints(const std::string& file_path, bool c else if (check_dns) { checkpoints dns_points; - cryptonote::load_checkpoints_from_dns(dns_points, m_testnet); + dns_points.load_checkpoints_from_dns(m_testnet); if (m_checkpoints.check_for_conflicts(dns_points)) { check_against_checkpoints(dns_points, false); diff --git a/src/cryptonote_core/checkpoints.cpp b/src/cryptonote_core/checkpoints.cpp index 24d066da..c038a480 100644 --- a/src/cryptonote_core/checkpoints.cpp +++ b/src/cryptonote_core/checkpoints.cpp @@ -1,21 +1,21 @@ // Copyright (c) 2014-2016, The Monero Project -// +// // All rights reserved. -// +// // Redistribution and use in source and binary forms, with or without modification, are // permitted provided that the following conditions are met: -// +// // 1. Redistributions of source code must retain the above copyright notice, this list of // conditions and the following disclaimer. -// +// // 2. Redistributions in binary form must reproduce the above copyright notice, this list // of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. -// +// // 3. Neither the name of the copyright holder nor the names of its contributors may be // used to endorse or promote products derived from this software without specific // prior written permission. -// +// // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL @@ -25,14 +25,44 @@ // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// +// // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #include "include_base_utils.h" + using namespace epee; #include "checkpoints.h" +#include "common/dns_utils.h" +#include "include_base_utils.h" +#include +#include + +namespace +{ + bool dns_records_match(const std::vector& a, const std::vector& b) + { + if (a.size() != b.size()) return false; + + for (const auto& record_in_a : a) + { + bool ok = false; + for (const auto& record_in_b : b) + { + if (record_in_a == record_in_b) + { + ok = true; + break; + } + } + if (!ok) return false; + } + + return true; + } +} // anonymous namespace + namespace cryptonote { //--------------------------------------------------------------------------- @@ -84,10 +114,7 @@ namespace cryptonote return check_block(height, h, ignored); } //--------------------------------------------------------------------------- - // this basically says if the blockchain is smaller than the first - // checkpoint then alternate blocks are allowed. Alternatively, if the - // last checkpoint *before* the end of the current chain is also before - // the block to be added, then this is fine. + //FIXME: is this the desired behavior? bool checkpoints::is_alternative_block_allowed(uint64_t blockchain_height, uint64_t block_height) const { if (0 == block_height) @@ -128,4 +155,206 @@ namespace cryptonote } return true; } + + bool checkpoints::init_default_checkpoints() + { + ADD_CHECKPOINT(1, "771fbcd656ec1464d3a02ead5e18644030007a0fc664c0a964d30922821a8148"); + ADD_CHECKPOINT(10, "c0e3b387e47042f72d8ccdca88071ff96bff1ac7cde09ae113dbb7ad3fe92381"); + ADD_CHECKPOINT(100, "ac3e11ca545e57c49fca2b4e8c48c03c23be047c43e471e1394528b1f9f80b2d"); + ADD_CHECKPOINT(1000, "5acfc45acffd2b2e7345caf42fa02308c5793f15ec33946e969e829f40b03876"); + ADD_CHECKPOINT(10000, "c758b7c81f928be3295d45e230646de8b852ec96a821eac3fea4daf3fcac0ca2"); + ADD_CHECKPOINT(22231, "7cb10e29d67e1c069e6e11b17d30b809724255fee2f6868dc14cfc6ed44dfb25"); + ADD_CHECKPOINT(29556, "53c484a8ed91e4da621bb2fa88106dbde426fe90d7ef07b9c1e5127fb6f3a7f6"); + ADD_CHECKPOINT(50000, "0fe8758ab06a8b9cb35b7328fd4f757af530a5d37759f9d3e421023231f7b31c"); + ADD_CHECKPOINT(80000, "a62dcd7b536f22e003ebae8726e9e7276f63d594e264b6f0cd7aab27b66e75e3"); + ADD_CHECKPOINT(202612, "bbd604d2ba11ba27935e006ed39c9bfdd99b76bf4a50654bc1e1e61217962698"); + ADD_CHECKPOINT(202613, "e2aa337e78df1f98f462b3b1e560c6b914dec47b610698b7b7d1e3e86b6197c2"); + ADD_CHECKPOINT(202614, "c29e3dc37d8da3e72e506e31a213a58771b24450144305bcba9e70fa4d6ea6fb"); + ADD_CHECKPOINT(205000, "5d3d7a26e6dc7535e34f03def711daa8c263785f73ec1fadef8a45880fde8063"); + ADD_CHECKPOINT(220000, "9613f455933c00e3e33ac315cc6b455ee8aa0c567163836858c2d9caff111553"); + ADD_CHECKPOINT(230300, "bae7a80c46859db355556e3a9204a337ae8f24309926a1312323fdecf1920e61"); + ADD_CHECKPOINT(230700, "93e631240ceac831da1aebfc5dac8f722c430463024763ebafa888796ceaeedf"); + ADD_CHECKPOINT(231350, "b5add137199b820e1ea26640e5c3e121fd85faa86a1e39cf7e6cc097bdeb1131"); + ADD_CHECKPOINT(232150, "955de8e6b6508af2c24f7334f97beeea651d78e9ade3ab18fec3763be3201aa8"); + ADD_CHECKPOINT(249380, "654fb0a81ce3e5caf7e3264a70f447d4bd07586c08fa50f6638cc54da0a52b2d"); + ADD_CHECKPOINT(460000, "75037a7aed3e765db96c75bcf908f59d690a5f3390baebb9edeafd336a1c4831"); + ADD_CHECKPOINT(500000, "2428f0dbe49796be05ed81b347f53e1f7f44aed0abf641446ec2b94cae066b02"); + ADD_CHECKPOINT(600000, "f5828ebf7d7d1cb61762c4dfe3ccf4ecab2e1aad23e8113668d981713b7a54c5"); + ADD_CHECKPOINT(700000, "12be9b3d210b93f574d2526abb9c1ab2a881b479131fd0d4f7dac93875f503cd"); + ADD_CHECKPOINT(825000, "56503f9ad766774b575be3aff73245e9d159be88132c93d1754764f28da2ff60"); + ADD_CHECKPOINT(900000, "d9958d0e7dcf91a5a7b11de225927bf7efc6eb26240315ce12372be902cc1337"); + ADD_CHECKPOINT(913193, "5292d5d56f6ba4de33a58d9a34d263e2cb3c6fee0aed2286fd4ac7f36d53c85f"); + + return true; + } + + bool checkpoints::load_checkpoints_from_json(const std::string json_hashfile_fullpath) + { + boost::system::error_code errcode; + if (! (boost::filesystem::exists(json_hashfile_fullpath, errcode))) + { + LOG_PRINT_L1("Blockchain checkpoints file not found"); + return true; + } + + LOG_PRINT_L1("Adding checkpoints from blockchain hashfile"); + + uint64_t prev_max_height = get_max_height(); + LOG_PRINT_L1("Hard-coded max checkpoint height is " << prev_max_height); + t_hash_json hashes; + epee::serialization::load_t_from_json_file(hashes, json_hashfile_fullpath); + for (std::vector::const_iterator it = hashes.hashlines.begin(); it != hashes.hashlines.end(); ) + { + uint64_t height; + height = it->height; + if (height <= prev_max_height) { + LOG_PRINT_L1("ignoring checkpoint height " << height); + } else { + std::string blockhash = it->hash; + LOG_PRINT_L1("Adding checkpoint height " << height << ", hash=" << blockhash); + ADD_CHECKPOINT(height, blockhash); + } + ++it; + } + + return true; + } + + bool checkpoints::load_checkpoints_from_dns(bool testnet) + { + // All four MoneroPulse domains have DNSSEC on and valid + static const std::vector dns_urls = { "checkpoints.moneropulse.se" + , "checkpoints.moneropulse.org" + , "checkpoints.moneropulse.net" + , "checkpoints.moneropulse.co" + }; + + static const std::vector testnet_dns_urls = { "testpoints.moneropulse.se" + , "testpoints.moneropulse.org" + , "testpoints.moneropulse.net" + , "testpoints.moneropulse.co" + }; + + std::vector > records; + records.resize(dns_urls.size()); + + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution dis(0, dns_urls.size() - 1); + size_t first_index = dis(gen); + + bool avail, valid; + size_t cur_index = first_index; + do + { + std::string url; + if (testnet) + { + url = testnet_dns_urls[cur_index]; + } + else + { + url = dns_urls[cur_index]; + } + + records[cur_index] = tools::DNSResolver::instance().get_txt_record(url, avail, valid); + if (!avail) + { + records[cur_index].clear(); + LOG_PRINT_L2("DNSSEC not available for checkpoint update at URL: " << url << ", skipping."); + } + if (!valid) + { + records[cur_index].clear(); + LOG_PRINT_L2("DNSSEC validation failed for checkpoint update at URL: " << url << ", skipping."); + } + + cur_index++; + if (cur_index == dns_urls.size()) + { + cur_index = 0; + } + records[cur_index].clear(); + } while (cur_index != first_index); + + size_t num_valid_records = 0; + + for( const auto& record_set : records) + { + if (record_set.size() != 0) + { + num_valid_records++; + } + } + + if (num_valid_records < 2) + { + LOG_PRINT_L0("WARNING: no two valid MoneroPulse DNS checkpoint records were received"); + return true; + } + + int good_records_index = -1; + for (size_t i = 0; i < records.size() - 1; ++i) + { + if (records[i].size() == 0) continue; + + for (size_t j = i + 1; j < records.size(); ++j) + { + if (dns_records_match(records[i], records[j])) + { + good_records_index = i; + break; + } + } + if (good_records_index >= 0) break; + } + + if (good_records_index < 0) + { + LOG_PRINT_L0("WARNING: no two MoneroPulse DNS checkpoint records matched"); + return true; + } + + for (auto& record : records[good_records_index]) + { + auto pos = record.find(":"); + if (pos != std::string::npos) + { + uint64_t height; + crypto::hash hash; + + // parse the first part as uint64_t, + // if this fails move on to the next record + std::stringstream ss(record.substr(0, pos)); + if (!(ss >> height)) + { + continue; + } + + // parse the second part as crypto::hash, + // if this fails move on to the next record + std::string hashStr = record.substr(pos + 1); + if (!epee::string_tools::parse_tpod_from_hex_string(hashStr, hash)) + { + continue; + } + + ADD_CHECKPOINT(height, hashStr); + } + } + return true; + } + + bool checkpoints::load_new_checkpoints(const std::string json_hashfile_fullpath, bool testnet, bool dns) + { + bool result; + + result = load_checkpoints_from_json(json_hashfile_fullpath); + if (dns) + { + result &= load_checkpoints_from_dns(testnet); + } + + return result; + } } diff --git a/src/cryptonote_core/checkpoints.h b/src/cryptonote_core/checkpoints.h index 00a53ec2..71727753 100644 --- a/src/cryptonote_core/checkpoints.h +++ b/src/cryptonote_core/checkpoints.h @@ -1,21 +1,21 @@ // Copyright (c) 2014-2016, The Monero Project -// +// // All rights reserved. -// +// // Redistribution and use in source and binary forms, with or without modification, are // permitted provided that the following conditions are met: -// +// // 1. Redistributions of source code must retain the above copyright notice, this list of // conditions and the following disclaimer. -// +// // 2. Redistributions in binary form must reproduce the above copyright notice, this list // of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. -// +// // 3. Neither the name of the copyright holder nor the names of its contributors may be // used to endorse or promote products derived from this software without specific // prior written permission. -// +// // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL @@ -25,30 +25,193 @@ // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// +// // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #pragma once #include #include #include "cryptonote_basic_impl.h" +#include "misc_log_ex.h" +#include "storages/portable_storage_template_helper.h" // epee json include + +#define ADD_CHECKPOINT(h, hash) CHECK_AND_ASSERT(add_checkpoint(h, hash), false); +#define JSON_HASH_FILE_NAME "checkpoints.json" namespace cryptonote { + /** + * @brief A container for blockchain checkpoints + * + * A checkpoint is a pre-defined hash for the block at a given height. + * Some of these are compiled-in, while others can be loaded at runtime + * either from a json file or via DNS from a checkpoint-hosting server. + */ class checkpoints { public: + + /** + * @brief default constructor + */ checkpoints(); + + /** + * @brief adds a checkpoint to the container + * + * @param height the height of the block the checkpoint is for + * @param hash_str the hash of the block, as a string + * + * @return false if parsing the hash fails, or if the height is a duplicate + * AND the existing checkpoint hash does not match the new one, + * otherwise returns true + */ bool add_checkpoint(uint64_t height, const std::string& hash_str); + + /** + * @brief checks if there is a checkpoint in the future + * + * This function checks if the height passed is lower than the highest + * checkpoint. + * + * @param height the height to check against + * + * @return false if no checkpoints, otherwise returns whether or not + * the height passed is lower than the highest checkpoint. + */ bool is_in_checkpoint_zone(uint64_t height) const; - bool check_block(uint64_t height, const crypto::hash& h) const; + + /** + * @brief checks if the given height and hash agree with the checkpoints + * + * This function checks if the given height and hash exist in the + * checkpoints container. If so, it returns whether or not the passed + * parameters match the stored values. + * + * @param height the height to be checked + * @param h the hash to be checked + * @param is_a_checkpoint return-by-reference if there is a checkpoint at the given height + * + * @return true if there is no checkpoint at the given height, + * true if the passed parameters match the stored checkpoint, + * false otherwise + */ bool check_block(uint64_t height, const crypto::hash& h, bool& is_a_checkpoint) const; + + /** + * @overload + */ + bool check_block(uint64_t height, const crypto::hash& h) const; + + /** + * @brief checks if alternate chain blocks should be kept for a given height + * + * this basically says if the blockchain is smaller than the first + * checkpoint then alternate blocks are allowed. Alternatively, if the + * last checkpoint *before* the end of the current chain is also before + * the block to be added, then this is fine. + * + * @param blockchain_height the current blockchain height + * @param block_height the height of the block to be added as alternate + * + * @return true if alternate blocks are allowed given the parameters, + * otherwise false + */ bool is_alternative_block_allowed(uint64_t blockchain_height, uint64_t block_height) const; + + /** + * @brief gets the highest checkpoint height + * + * @return the height of the highest checkpoint + */ uint64_t get_max_height() const; + + /** + * @brief gets the checkpoints container + * + * @return a const reference to the checkpoints container + */ const std::map& get_points() const; + + /** + * @brief checks if our checkpoints container conflicts with another + * + * A conflict refers to a case where both checkpoint sets have a checkpoint + * for a specific height but their hashes for that height do not match. + * + * @param other the other checkpoints instance to check against + * + * @return false if any conflict is found, otherwise true + */ bool check_for_conflicts(const checkpoints& other) const; + + /** + * @brief loads the default main chain checkpoints + * + * @return true unless adding a checkpoint fails + */ + bool init_default_checkpoints(); + + /** + * @brief load new checkpoints + * + * Loads new checkpoints from the specified json file, as well as + * (optionally) from DNS. + * + * @param json_hashfile_fullpath path to the json checkpoints file + * @param testnet whether to load testnet checkpoints or mainnet + * @param dns whether or not to load DNS checkpoints + * + * @return true if loading successful and no conflicts + */ + bool load_new_checkpoints(const std::string json_hashfile_fullpath, bool testnet=false, bool dns=true); + + /** + * @brief load new checkpoints from json + * + * @param json_hashfile_fullpath path to the json checkpoints file + * + * @return true if loading successful and no conflicts + */ + bool load_checkpoints_from_json(const std::string json_hashfile_fullpath); + + /** + * @brief load new checkpoints from DNS + * + * @param testnet whether to load testnet checkpoints or mainnet + * + * @return true if loading successful and no conflicts + */ + bool load_checkpoints_from_dns(bool testnet = false); + private: - std::map m_points; + + + /** + * @brief struct for loading a checkpoint from json + */ + struct t_hashline + { + uint64_t height; //!< the height of the checkpoint + std::string hash; //!< the hash for the checkpoint + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(height) + KV_SERIALIZE(hash) + END_KV_SERIALIZE_MAP() + }; + + /** + * @brief struct for loading many checkpoints from json + */ + struct t_hash_json { + std::vector hashlines; //!< the checkpoint lines from the file + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(hashlines) + END_KV_SERIALIZE_MAP() + }; + + std::map m_points; //!< the checkpoints container + }; } diff --git a/src/cryptonote_core/checkpoints_create.cpp b/src/cryptonote_core/checkpoints_create.cpp deleted file mode 100644 index 41f2321d..00000000 --- a/src/cryptonote_core/checkpoints_create.cpp +++ /dev/null @@ -1,280 +0,0 @@ -// Copyright (c) 2014-2016, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers - -#include "checkpoints_create.h" -#include "common/dns_utils.h" -#include "include_base_utils.h" -#include -#include -#include "storages/portable_storage_template_helper.h" // epee json include - -namespace -{ - bool dns_records_match(const std::vector& a, const std::vector& b) - { - if (a.size() != b.size()) return false; - - for (const auto& record_in_a : a) - { - bool ok = false; - for (const auto& record_in_b : b) - { - if (record_in_a == record_in_b) - { - ok = true; - break; - } - } - if (!ok) return false; - } - - return true; - } -} // anonymous namespace - -namespace cryptonote -{ - -struct t_hashline -{ - uint64_t height; - std::string hash; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(height) - KV_SERIALIZE(hash) - END_KV_SERIALIZE_MAP() -}; - -struct t_hash_json { - std::vector hashlines; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(hashlines) - END_KV_SERIALIZE_MAP() -}; - -bool create_checkpoints(cryptonote::checkpoints& checkpoints) -{ - ADD_CHECKPOINT(1, "771fbcd656ec1464d3a02ead5e18644030007a0fc664c0a964d30922821a8148"); - ADD_CHECKPOINT(10, "c0e3b387e47042f72d8ccdca88071ff96bff1ac7cde09ae113dbb7ad3fe92381"); - ADD_CHECKPOINT(100, "ac3e11ca545e57c49fca2b4e8c48c03c23be047c43e471e1394528b1f9f80b2d"); - ADD_CHECKPOINT(1000, "5acfc45acffd2b2e7345caf42fa02308c5793f15ec33946e969e829f40b03876"); - ADD_CHECKPOINT(10000, "c758b7c81f928be3295d45e230646de8b852ec96a821eac3fea4daf3fcac0ca2"); - ADD_CHECKPOINT(22231, "7cb10e29d67e1c069e6e11b17d30b809724255fee2f6868dc14cfc6ed44dfb25"); - ADD_CHECKPOINT(29556, "53c484a8ed91e4da621bb2fa88106dbde426fe90d7ef07b9c1e5127fb6f3a7f6"); - ADD_CHECKPOINT(50000, "0fe8758ab06a8b9cb35b7328fd4f757af530a5d37759f9d3e421023231f7b31c"); - ADD_CHECKPOINT(80000, "a62dcd7b536f22e003ebae8726e9e7276f63d594e264b6f0cd7aab27b66e75e3"); - ADD_CHECKPOINT(202612, "bbd604d2ba11ba27935e006ed39c9bfdd99b76bf4a50654bc1e1e61217962698"); - ADD_CHECKPOINT(202613, "e2aa337e78df1f98f462b3b1e560c6b914dec47b610698b7b7d1e3e86b6197c2"); - ADD_CHECKPOINT(202614, "c29e3dc37d8da3e72e506e31a213a58771b24450144305bcba9e70fa4d6ea6fb"); - ADD_CHECKPOINT(205000, "5d3d7a26e6dc7535e34f03def711daa8c263785f73ec1fadef8a45880fde8063"); - ADD_CHECKPOINT(220000, "9613f455933c00e3e33ac315cc6b455ee8aa0c567163836858c2d9caff111553"); - ADD_CHECKPOINT(230300, "bae7a80c46859db355556e3a9204a337ae8f24309926a1312323fdecf1920e61"); - ADD_CHECKPOINT(230700, "93e631240ceac831da1aebfc5dac8f722c430463024763ebafa888796ceaeedf"); - ADD_CHECKPOINT(231350, "b5add137199b820e1ea26640e5c3e121fd85faa86a1e39cf7e6cc097bdeb1131"); - ADD_CHECKPOINT(232150, "955de8e6b6508af2c24f7334f97beeea651d78e9ade3ab18fec3763be3201aa8"); - ADD_CHECKPOINT(249380, "654fb0a81ce3e5caf7e3264a70f447d4bd07586c08fa50f6638cc54da0a52b2d"); - ADD_CHECKPOINT(300000, "0c1cd46df6ccff90ec4ab493281f2583c344cd62216c427628990fe9db1bb8b6"); - ADD_CHECKPOINT(400000, "1b2b0e7a30e59691491529a3d506d1ba3d6052d0f6b52198b7330b28a6f1b6ac"); - ADD_CHECKPOINT(450000, "4d098b511ca97723e81737c448343cfd4e6dadb3d8a0e757c6e4d595e6e48357"); - ADD_CHECKPOINT(460000, "75037a7aed3e765db96c75bcf908f59d690a5f3390baebb9edeafd336a1c4831"); - ADD_CHECKPOINT(500000, "2428f0dbe49796be05ed81b347f53e1f7f44aed0abf641446ec2b94cae066b02"); - ADD_CHECKPOINT(600000, "f5828ebf7d7d1cb61762c4dfe3ccf4ecab2e1aad23e8113668d981713b7a54c5"); - ADD_CHECKPOINT(700000, "12be9b3d210b93f574d2526abb9c1ab2a881b479131fd0d4f7dac93875f503cd"); - ADD_CHECKPOINT(825000, "56503f9ad766774b575be3aff73245e9d159be88132c93d1754764f28da2ff60"); - ADD_CHECKPOINT(900000, "d9958d0e7dcf91a5a7b11de225927bf7efc6eb26240315ce12372be902cc1337"); - ADD_CHECKPOINT(913193, "5292d5d56f6ba4de33a58d9a34d263e2cb3c6fee0aed2286fd4ac7f36d53c85f"); - - return true; -} - -bool load_checkpoints_from_json(cryptonote::checkpoints& checkpoints, std::string json_hashfile_fullpath) -{ - boost::system::error_code errcode; - if (! (boost::filesystem::exists(json_hashfile_fullpath, errcode))) - { - LOG_PRINT_L1("Blockchain checkpoints file not found"); - return true; - } - - LOG_PRINT_L1("Adding checkpoints from blockchain hashfile"); - - uint64_t prev_max_height = checkpoints.get_max_height(); - LOG_PRINT_L1("Hard-coded max checkpoint height is " << prev_max_height); - t_hash_json hashes; - epee::serialization::load_t_from_json_file(hashes, json_hashfile_fullpath); - for (std::vector::const_iterator it = hashes.hashlines.begin(); it != hashes.hashlines.end(); ) - { - uint64_t height; - height = it->height; - if (height <= prev_max_height) { - LOG_PRINT_L1("ignoring checkpoint height " << height); - } else { - std::string blockhash = it->hash; - LOG_PRINT_L1("Adding checkpoint height " << height << ", hash=" << blockhash); - ADD_CHECKPOINT(height, blockhash); - } - ++it; - } - - return true; -} - -bool load_checkpoints_from_dns(cryptonote::checkpoints& checkpoints, bool testnet) -{ - // All four MoneroPulse domains have DNSSEC on and valid - static const std::vector dns_urls = { "checkpoints.moneropulse.se" - , "checkpoints.moneropulse.org" - , "checkpoints.moneropulse.net" - , "checkpoints.moneropulse.co" - }; - - static const std::vector testnet_dns_urls = { "testpoints.moneropulse.se" - , "testpoints.moneropulse.org" - , "testpoints.moneropulse.net" - , "testpoints.moneropulse.co" - }; - - std::vector > records; - records.resize(dns_urls.size()); - - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_int_distribution dis(0, dns_urls.size() - 1); - size_t first_index = dis(gen); - - bool avail, valid; - size_t cur_index = first_index; - do - { - std::string url; - if (testnet) - { - url = testnet_dns_urls[cur_index]; - } - else - { - url = dns_urls[cur_index]; - } - - records[cur_index] = tools::DNSResolver::instance().get_txt_record(url, avail, valid); - if (!avail) - { - records[cur_index].clear(); - LOG_PRINT_L2("DNSSEC not available for checkpoint update at URL: " << url << ", skipping."); - } - if (!valid) - { - records[cur_index].clear(); - LOG_PRINT_L2("DNSSEC validation failed for checkpoint update at URL: " << url << ", skipping."); - } - - cur_index++; - if (cur_index == dns_urls.size()) - { - cur_index = 0; - } - records[cur_index].clear(); - } while (cur_index != first_index); - - size_t num_valid_records = 0; - - for( const auto& record_set : records) - { - if (record_set.size() != 0) - { - num_valid_records++; - } - } - - if (num_valid_records < 2) - { - LOG_PRINT_L0("WARNING: no two valid MoneroPulse DNS checkpoint records were received"); - return true; - } - - int good_records_index = -1; - for (size_t i = 0; i < records.size() - 1; ++i) - { - if (records[i].size() == 0) continue; - - for (size_t j = i + 1; j < records.size(); ++j) - { - if (dns_records_match(records[i], records[j])) - { - good_records_index = i; - break; - } - } - if (good_records_index >= 0) break; - } - - if (good_records_index < 0) - { - LOG_PRINT_L0("WARNING: no two MoneroPulse DNS checkpoint records matched"); - return true; - } - - for (auto& record : records[good_records_index]) - { - auto pos = record.find(":"); - if (pos != std::string::npos) - { - uint64_t height; - crypto::hash hash; - - // parse the first part as uint64_t, - // if this fails move on to the next record - std::stringstream ss(record.substr(0, pos)); - if (!(ss >> height)) - { - continue; - } - - // parse the second part as crypto::hash, - // if this fails move on to the next record - std::string hashStr = record.substr(pos + 1); - if (!epee::string_tools::parse_tpod_from_hex_string(hashStr, hash)) - { - continue; - } - - ADD_CHECKPOINT(height, hashStr); - } - } - return true; -} - -bool load_new_checkpoints(cryptonote::checkpoints& checkpoints, std::string json_hashfile_fullpath) -{ - // TODO: replace hard-coded url with const string or #define - return (load_checkpoints_from_json(checkpoints, json_hashfile_fullpath) && load_checkpoints_from_dns(checkpoints)); -} - -} // namespace cryptonote diff --git a/src/cryptonote_core/checkpoints_create.h b/src/cryptonote_core/checkpoints_create.h deleted file mode 100644 index 83830f8a..00000000 --- a/src/cryptonote_core/checkpoints_create.h +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) 2014-2016, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers - -#pragma once - -#include "checkpoints.h" -#include "misc_log_ex.h" - -#define ADD_CHECKPOINT(h, hash) CHECK_AND_ASSERT(checkpoints.add_checkpoint(h, hash), false); -#define JSON_HASH_FILE_NAME "checkpoints.json" - -namespace cryptonote -{ - - bool create_checkpoints(cryptonote::checkpoints& checkpoints); - - bool load_checkpoints_from_json(cryptonote::checkpoints& checkpoints, std::string json_hashfile_fullpath); - bool load_checkpoints_from_dns(cryptonote::checkpoints& checkpoints, bool testnet = false); - bool load_new_checkpoints(cryptonote::checkpoints& checkpoints, std::string json_hashfile_fullpath); - -} // namespace cryptonote diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 6f0fe88a..c31be5ac 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -42,7 +42,7 @@ using namespace epee; #include "cryptonote_format_utils.h" #include "misc_language.h" #include -#include "cryptonote_core/checkpoints_create.h" +#include "cryptonote_core/checkpoints.h" #include "blockchain_db/blockchain_db.h" #include "blockchain_db/lmdb/db_lmdb.h" #if defined(BERKELEY_DB) @@ -159,7 +159,7 @@ namespace cryptonote if (!m_testnet && !m_fakechain) { cryptonote::checkpoints checkpoints; - if (!cryptonote::create_checkpoints(checkpoints)) + if (!checkpoints.init_default_checkpoints()) { throw std::runtime_error("Failed to initialize checkpoints"); } @@ -415,7 +415,7 @@ namespace cryptonote CHECK_AND_ASSERT_MES(update_checkpoints(), false, "One or more checkpoints loaded from json or dns conflicted with existing checkpoints."); r = m_miner.init(vm, m_testnet); - CHECK_AND_ASSERT_MES(r, false, "Failed to initialize blockchain storage"); + CHECK_AND_ASSERT_MES(r, false, "Failed to initialize miner instance"); return load_state_data(); } @@ -634,11 +634,6 @@ namespace cryptonote return m_blockchain_storage.get_total_transactions(); } //----------------------------------------------------------------------------------------------- - //bool core::get_outs(uint64_t amount, std::list& pkeys) - //{ - // return m_blockchain_storage.get_outs(amount, pkeys); - //} - //----------------------------------------------------------------------------------------------- bool core::add_new_tx(const transaction& tx, const crypto::hash& tx_hash, const crypto::hash& tx_prefix_hash, size_t blob_size, tx_verification_context& tvc, bool keeped_by_block, bool relayed) { if(m_mempool.have_tx(tx_hash)) @@ -770,10 +765,6 @@ namespace cryptonote { m_miner.on_synchronized(); } - //bool core::get_backward_blocks_sizes(uint64_t from_height, std::vector& sizes, size_t count) - //{ - // return m_blockchain_storage.get_backward_blocks_sizes(from_height, sizes, count); - //} //----------------------------------------------------------------------------------------------- bool core::add_new_block(const block& b, block_verification_context& bvc) { @@ -894,10 +885,6 @@ namespace cryptonote return m_blockchain_storage.get_block_by_hash(h, blk); } //----------------------------------------------------------------------------------------------- - //void core::get_all_known_block_ids(std::list &main, std::list &alt, std::list &invalid) { - // m_blockchain_storage.get_all_known_block_ids(main, alt, invalid); - //} - //----------------------------------------------------------------------------------------------- std::string core::print_pool(bool short_format) const { return m_mempool.print_pool(short_format); diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 32f0b2ad..30384209 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -63,157 +63,739 @@ namespace cryptonote /************************************************************************/ /* */ /************************************************************************/ + + /** + * @brief handles core cryptonote functionality + * + * This class coordinates cryptonote functionality including, but not + * limited to, communication among the Blockchain, the transaction pool, + * any miners, and the network. + */ class core: public i_miner_handler { public: - core(i_cryptonote_protocol* pprotocol); - bool handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NOTIFY_RESPONSE_GET_OBJECTS::request& rsp, cryptonote_connection_context& context); - bool on_idle(); - bool handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, bool keeped_by_block, bool relayed); - bool handle_incoming_block(const blobdata& block_blob, block_verification_context& bvc, bool update_miner_blocktemplate = true); - bool prepare_handle_incoming_blocks(const std::list &blocks); - bool cleanup_handle_incoming_blocks(bool force_sync = false); + /** + * @brief constructor + * + * sets member variables into a usable state + * + * @param pprotocol pre-constructed protocol object to store and use + */ + core(i_cryptonote_protocol* pprotocol); + + /** + * @copydoc Blockchain::handle_get_objects + * + * @note see Blockchain::handle_get_objects() + * @param context connection context associated with the request + */ + bool handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NOTIFY_RESPONSE_GET_OBJECTS::request& rsp, cryptonote_connection_context& context); + + /** + * @brief calls various idle routines + * + * @note see miner::on_idle and tx_memory_pool::on_idle + * + * @return true + */ + bool on_idle(); + + /** + * @brief handles an incoming transaction + * + * Parses an incoming transaction and, if nothing is obviously wrong, + * passes it along to the transaction pool + * + * @param tx_blob the tx to handle + * @param tvc metadata about the transaction's validity + * @param keeped_by_block if the transaction has been in a block + * @param relayed whether or not the transaction was relayed to us + * + * @return true if the transaction made it to the transaction pool, otherwise false + */ + bool handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, bool keeped_by_block, bool relayed); + + /** + * @brief handles an incoming block + * + * periodic update to checkpoints is triggered here + * Attempts to add the block to the Blockchain and, on success, + * optionally updates the miner's block template. + * + * @param block_blob the block to be added + * @param bvc return-by-reference metadata context about the block's validity + * @param update_miner_blocktemplate whether or not to update the miner's block template + * + * @return false if loading new checkpoints fails, or the block is not + * added, otherwise true + */ + bool handle_incoming_block(const blobdata& block_blob, block_verification_context& bvc, bool update_miner_blocktemplate = true); + + /** + * @copydoc Blockchain::prepare_handle_incoming_blocks + * + * @note see Blockchain::prepare_handle_incoming_blocks + */ + bool prepare_handle_incoming_blocks(const std::list &blocks); + + /** + * @copydoc Blockchain::cleanup_handle_incoming_blocks + * + * @note see Blockchain::cleanup_handle_incoming_blocks + */ + bool cleanup_handle_incoming_blocks(bool force_sync = false); + + /** + * @brief check the size of a block against the current maximum + * + * @param block_blob the block to check + * + * @return whether or not the block is too big + */ bool check_incoming_block_size(const blobdata& block_blob) const; + + /** + * @brief get the cryptonote protocol instance + * + * @return the instance + */ i_cryptonote_protocol* get_protocol(){return m_pprotocol;} //-------------------- i_miner_handler ----------------------- + + /** + * @brief stores and relays a block found by a miner + * + * Updates the miner's target block, attempts to store the found + * block in Blockchain, and -- on success -- relays that block to + * the network. + * + * @param b the block found + * + * @return true if the block was added to the main chain, otherwise false + */ virtual bool handle_block_found( block& b); + + /** + * @copydoc Blockchain::create_block_template + * + * @note see Blockchain::create_block_template + */ virtual bool get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, const blobdata& ex_nonce); + /** + * @brief gets the miner instance + * + * @return a reference to the miner instance + */ miner& get_miner(){return m_miner;} + + /** + * @brief gets the miner instance (const) + * + * @return a const reference to the miner instance + */ const miner& get_miner()const{return m_miner;} + + /** + * @brief adds command line options to the given options set + * + * As of now, there are no command line options specific to core, + * so this function simply returns. + * + * @param desc return-by-reference the command line options set to add to + */ static void init_options(boost::program_options::options_description& desc); + + /** + * @brief initializes the core as needed + * + * This function initializes the transaction pool, the Blockchain, and + * a miner instance with parameters given on the command line (or defaults) + * + * @param vm command line parameters + * @param test_options configuration options for testing + * + * @return false if one of the init steps fails, otherwise true + */ bool init(const boost::program_options::variables_map& vm, const test_options *test_options = NULL); + + /** + * @copydoc Blockchain::reset_and_set_genesis_block + * + * @note see Blockchain::reset_and_set_genesis_block + */ bool set_genesis_block(const block& b); + + /** + * @brief performs safe shutdown steps for core and core components + * + * Uninitializes the miner instance, transaction pool, and Blockchain + * + * if m_fast_exit is set, the call to Blockchain::deinit() is not made. + * + * @return true + */ bool deinit(); + + /** + * @brief sets fast exit flag + * + * @note see deinit() + */ static void set_fast_exit(); + + /** + * @brief gets the current state of the fast exit flag + * + * @return the fast exit flag + * + * @note see deinit() + */ static bool get_fast_exit(); + + /** + * @brief sets to drop blocks downloaded (for testing) + */ void test_drop_download(); + + /** + * @brief sets to drop blocks downloaded below a certain height + * + * @param height height below which to drop blocks + */ void test_drop_download_height(uint64_t height); + + /** + * @brief gets whether or not to drop blocks (for testing) + * + * @return whether or not to drop blocks + */ bool get_test_drop_download() const; + + /** + * @brief gets whether or not to drop blocks + * + * If the current blockchain height <= our block drop threshold + * and test drop blocks is set, return true + * + * @return see above + */ bool get_test_drop_download_height() const; + + /** + * @copydoc Blockchain::get_current_blockchain_height + * + * @note see Blockchain::get_current_blockchain_height() + */ uint64_t get_current_blockchain_height() const; - bool get_blockchain_top(uint64_t& heeight, crypto::hash& top_id) const; + + /** + * @brief get the hash and height of the most recent block + * + * @param height return-by-reference height of the block + * @param top_id return-by-reference hash of the block + * + * @return true + */ + bool get_blockchain_top(uint64_t& height, crypto::hash& top_id) const; + + /** + * @copydoc Blockchain::get_blocks(uint64_t, size_t, std::list&, std::list&) const + * + * @note see Blockchain::get_blocks(uint64_t, size_t, std::list&, std::list&) const + */ bool get_blocks(uint64_t start_offset, size_t count, std::list& blocks, std::list& txs) const; + + /** + * @copydoc Blockchain::get_blocks(uint64_t, size_t, std::list&) const + * + * @note see Blockchain::get_blocks(uint64_t, size_t, std::list&) const + */ bool get_blocks(uint64_t start_offset, size_t count, std::list& blocks) const; + + /** + * @copydoc Blockchain::get_blocks(const t_ids_container&, t_blocks_container&, t_missed_container&) const + * + * @note see Blockchain::get_blocks(const t_ids_container&, t_blocks_container&, t_missed_container&) const + */ template bool get_blocks(const t_ids_container& block_ids, t_blocks_container& blocks, t_missed_container& missed_bs) const { return m_blockchain_storage.get_blocks(block_ids, blocks, missed_bs); } - crypto::hash get_block_id_by_height(uint64_t height) const; - bool get_transactions(const std::vector& txs_ids, std::list& txs, std::list& missed_txs) const; - bool get_block_by_hash(const crypto::hash &h, block &blk) const; - //void get_all_known_block_ids(std::list &main, std::list &alt, std::list &invalid); + /** + * @copydoc Blockchain::get_block_id_by_height + * + * @note see Blockchain::get_block_id_by_height + */ + crypto::hash get_block_id_by_height(uint64_t height) const; + + /** + * @copydoc Blockchain::get_transactions + * + * @note see Blockchain::get_transactions + */ + bool get_transactions(const std::vector& txs_ids, std::list& txs, std::list& missed_txs) const; + + /** + * @copydoc Blockchain::get_block_by_hash + * + * @note see Blockchain::get_block_by_hash + */ + bool get_block_by_hash(const crypto::hash &h, block &blk) const; + + /** + * @copydoc Blockchain::get_alternative_blocks + * + * @note see Blockchain::get_alternative_blocks(std::list&) const + */ bool get_alternative_blocks(std::list& blocks) const; + + /** + * @copydoc Blockchain::get_alternative_blocks_count + * + * @note see Blockchain::get_alternative_blocks_count() const + */ size_t get_alternative_blocks_count() const; + /** + * @brief set the pointer to the cryptonote protocol object to use + * + * @param pprotocol the pointer to set ours as + */ void set_cryptonote_protocol(i_cryptonote_protocol* pprotocol); + + /** + * @copydoc Blockchain::set_checkpoints + * + * @note see Blockchain::set_checkpoints() + */ void set_checkpoints(checkpoints&& chk_pts); + + /** + * @brief set the file path to read from when loading checkpoints + * + * @param path the path to set ours as + */ void set_checkpoints_file_path(const std::string& path); + + /** + * @brief set whether or not we enforce DNS checkpoints + * + * @param enforce_dns enforce DNS checkpoints or not + */ void set_enforce_dns_checkpoints(bool enforce_dns); + /** + * @copydoc tx_memory_pool::get_transactions + * + * @note see tx_memory_pool::get_transactions + */ bool get_pool_transactions(std::list& txs) const; + + /** + * @copydoc tx_memory_pool::get_pool_transactions_and_spent_keys_info + * + * @note see tx_memory_pool::get_pool_transactions_and_spent_keys_info + */ bool get_pool_transactions_and_spent_keys_info(std::vector& tx_infos, std::vector& key_image_infos) const; + + /** + * @copydoc tx_memory_pool::get_transactions_count + * + * @note see tx_memory_pool::get_transactions_count + */ size_t get_pool_transactions_count() const; + + /** + * @copydoc Blockchain::get_total_transactions + * + * @note see Blockchain::get_total_transactions + */ size_t get_blockchain_total_transactions() const; - //bool get_outs(uint64_t amount, std::list& pkeys); + + /** + * @copydoc Blockchain::have_block + * + * @note see Blockchain::have_block + */ bool have_block(const crypto::hash& id) const; + + /** + * @copydoc Blockchain::get_short_chain_history + * + * @note see Blockchain::get_short_chain_history + */ bool get_short_chain_history(std::list& ids) const; + + /** + * @copydoc Blockchain::find_blockchain_supplement(const std::list&, NOTIFY_RESPONSE_CHAIN_ENTRY::request&) const + * + * @note see Blockchain::find_blockchain_supplement(const std::list&, NOTIFY_RESPONSE_CHAIN_ENTRY::request&) const + */ bool find_blockchain_supplement(const std::list& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const; + + /** + * @copydoc Blockchain::find_blockchain_supplement(const uint64_t, const std::list&, std::list > >&, uint64_t&, uint64_t&, size_t) const + * + * @note see Blockchain::find_blockchain_supplement(const uint64_t, const std::list&, std::list > >&, uint64_t&, uint64_t&, size_t) const + */ bool find_blockchain_supplement(const uint64_t req_start_block, const std::list& qblock_ids, std::list > >& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count) const; + + /** + * @brief gets some stats about the daemon + * + * @param st_inf return-by-reference container for the stats requested + * + * @return true + */ bool get_stat_info(core_stat_info& st_inf) const; - //bool get_backward_blocks_sizes(uint64_t from_height, std::vector& sizes, size_t count); + + /** + * @copydoc Blockchain::get_tx_outputs_gindexs + * + * @note see Blockchain::get_tx_outputs_gindexs + */ bool get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vector& indexs) const; + + /** + * @copydoc Blockchain::get_tail_id + * + * @note see Blockchain::get_tail_id + */ crypto::hash get_tail_id() const; + + /** + * @copydoc Blockchain::get_random_outs_for_amounts + * + * @note see Blockchain::get_random_outs_for_amounts + */ bool get_random_outs_for_amounts(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response& res) const; + + + /** + * @copydoc miner::pause + * + * @note see miner::pause + */ void pause_mine(); + + /** + * @copydoc miner::resume + * + * @note see miner::resume + */ void resume_mine(); + #if BLOCKCHAIN_DB == DB_LMDB + /** + * @brief gets the Blockchain instance + * + * @return a reference to the Blockchain instance + */ Blockchain& get_blockchain_storage(){return m_blockchain_storage;} + + /** + * @brief gets the Blockchain instance (const) + * + * @return a const reference to the Blockchain instance + */ const Blockchain& get_blockchain_storage()const{return m_blockchain_storage;} #else blockchain_storage& get_blockchain_storage(){return m_blockchain_storage;} const blockchain_storage& get_blockchain_storage()const{return m_blockchain_storage;} #endif - //debug functions + + /** + * @copydoc Blockchain::print_blockchain + * + * @note see Blockchain::print_blockchain + */ void print_blockchain(uint64_t start_index, uint64_t end_index) const; + + /** + * @copydoc Blockchain::print_blockchain_index + * + * @note see Blockchain::print_blockchain_index + */ void print_blockchain_index() const; + + /** + * @copydoc tx_memory_pool::print_pool + * + * @note see tx_memory_pool::print_pool + */ std::string print_pool(bool short_format) const; + + /** + * @copydoc Blockchain::print_blockchain_outs + * + * @note see Blockchain::print_blockchain_outs + */ void print_blockchain_outs(const std::string& file); + + /** + * @copydoc miner::on_synchronized + * + * @note see miner::on_synchronized + */ void on_synchronized(); + /** + * @brief sets the target blockchain height + * + * @param target_blockchain_height the height to set + */ void set_target_blockchain_height(uint64_t target_blockchain_height); + + /** + * @brief gets the target blockchain height + * + * @param target_blockchain_height the target height + */ uint64_t get_target_blockchain_height() const; + /** + * @brief tells the Blockchain to update its checkpoints + * + * This function will check if enough time has passed since the last + * time checkpoints were updated and tell the Blockchain to update + * its checkpoints if it is time. If updating checkpoints fails, + * the daemon is told to shut down. + * + * @note see Blockchain::update_checkpoints() + */ bool update_checkpoints(); + /** + * @brief tells the daemon to wind down operations and stop running + * + * Currently this function raises SIGTERM, allowing the installed signal + * handlers to do the actual stopping. + */ + void graceful_exit(); + + /** + * @brief stops the daemon running + * + * @note see graceful_exit() + */ void stop(); + /** + * @copydoc Blockchain::have_tx_keyimg_as_spent + * + * @note see Blockchain::have_tx_keyimg_as_spent + */ bool is_key_image_spent(const crypto::key_image& key_im) const; + + /** + * @brief check if multiple key images are spent + * + * plural version of is_key_image_spent() + * + * @param key_im list of key images to check + * @param spent return-by-reference result for each image checked + * + * @return true + */ bool are_key_images_spent(const std::vector& key_im, std::vector &spent) const; private: + + /** + * @copydoc add_new_tx(const transaction&, tx_verification_context&, bool) + * + * @param tx_hash the transaction's hash + * @param tx_prefix_hash the transaction prefix' hash + * @param blob_size the size of the transaction + * @param relayed whether or not the transaction was relayed to us + * + */ bool add_new_tx(const transaction& tx, const crypto::hash& tx_hash, const crypto::hash& tx_prefix_hash, size_t blob_size, tx_verification_context& tvc, bool keeped_by_block, bool relayed); + + /** + * @brief add a new transaction to the transaction pool + * + * Adds a new transaction to the transaction pool. + * + * @param tx the transaction to add + * @param tvc return-by-reference metadata about the transaction's verification process + * @param keeped_by_block whether or not the transaction has been in a block + * @param relayed whether or not the transaction was relayed to us + * + * @return true if the transaction is already in the transaction pool, + * is already in a block on the Blockchain, or is successfully added + * to the transaction pool + */ bool add_new_tx(const transaction& tx, tx_verification_context& tvc, bool keeped_by_block, bool relayed); + + /** + * @copydoc Blockchain::add_new_block + * + * @note see Blockchain::add_new_block + */ bool add_new_block(const block& b, block_verification_context& bvc); + + /** + * @brief load any core state stored on disk + * + * currently does nothing, but may have state to load in the future. + * + * @return true + */ bool load_state_data(); + + /** + * @copydoc parse_tx_from_blob(transaction&, crypto::hash&, crypto::hash&, const blobdata&) const + * + * @note see parse_tx_from_blob(transaction&, crypto::hash&, crypto::hash&, const blobdata&) const + */ bool parse_tx_from_blob(transaction& tx, crypto::hash& tx_hash, crypto::hash& tx_prefix_hash, const blobdata& blob) const; + /** + * @brief check a transaction's syntax + * + * For now this does nothing, but it may check something about the tx + * in the future. + * + * @param tx the transaction to check + * + * @return true + */ bool check_tx_syntax(const transaction& tx) const; - //check correct values, amounts and all lightweight checks not related with database - bool check_tx_semantic(const transaction& tx, bool keeped_by_block) const; - //check if tx already in memory pool or in main blockchain - bool check_tx_ring_signature(const txin_to_key& tx, const crypto::hash& tx_prefix_hash, const std::vector& sig) const; - bool is_tx_spendtime_unlocked(uint64_t unlock_time) const; + /** + * @brief validates some simple properties of a transaction + * + * Currently checks: tx has inputs, + * tx inputs all of supported type(s), + * tx outputs valid (type, key, amount), + * input and output total amounts don't overflow, + * output amount <= input amount, + * tx not too large, + * each input has a different key image. + * + * @param tx the transaction to check + * @param keeped_by_block if the transaction has been in a block + * + * @return true if all the checks pass, otherwise false + */ + bool check_tx_semantic(const transaction& tx, bool keeped_by_block) const; + + /** + * @copydoc miner::on_block_chain_update + * + * @note see miner::on_block_chain_update + * + * @return true + */ bool update_miner_block_template(); + + /** + * @brief act on a set of command line options given + * + * @param vm the command line options + * + * @return true + */ bool handle_command_line(const boost::program_options::variables_map& vm); - bool on_update_blocktemplate_interval(); + + /** + * @brief verify that each input key image in a transaction is unique + * + * @param tx the transaction to check + * + * @return false if any key image is repeated, otherwise true + */ bool check_tx_inputs_keyimages_diff(const transaction& tx) const; - void graceful_exit(); + + /** + * @brief checks HardFork status and prints messages about it + * + * Checks the status of HardFork and logs/prints if an update to + * the daemon is necessary. + * + * @note see Blockchain::get_hard_fork_state and HardFork::State + * + * @return true + */ bool check_fork_time(); + + /** + * @brief attempts to relay any transactions in the mempool which need it + * + * @return true + */ bool relay_txpool_transactions(); + + /** + * @brief locks a file in the BlockchainDB directory + * + * @param path the directory in which to place the file + * + * @return true if lock acquired successfully, otherwise false + */ bool lock_db_directory(const boost::filesystem::path &path); + + /** + * @brief unlocks the db directory + * + * @note see lock_db_directory() + * + * @return true + */ bool unlock_db_directory(); - static std::atomic m_fast_exit; - bool m_test_drop_download = true; - uint64_t m_test_drop_download_height = 0; + static std::atomic m_fast_exit; //!< whether or not to deinit Blockchain on exit - tx_memory_pool m_mempool; + bool m_test_drop_download = true; //!< whether or not to drop incoming blocks (for testing) + + uint64_t m_test_drop_download_height = 0; //!< height under which to drop incoming blocks, if doing so + + tx_memory_pool m_mempool; //!< transaction pool instance #if BLOCKCHAIN_DB == DB_LMDB - Blockchain m_blockchain_storage; + Blockchain m_blockchain_storage; //!< Blockchain instance #else blockchain_storage m_blockchain_storage; #endif - i_cryptonote_protocol* m_pprotocol; - epee::critical_section m_incoming_tx_lock; + + i_cryptonote_protocol* m_pprotocol; //!< cryptonote protocol instance + + epee::critical_section m_incoming_tx_lock; //!< incoming transaction lock + //m_miner and m_miner_addres are probably temporary here - miner m_miner; - account_public_address m_miner_address; - std::string m_config_folder; - cryptonote_protocol_stub m_protocol_stub; - epee::math_helper::once_a_time_seconds<60*60*12, false> m_store_blockchain_interval; - epee::math_helper::once_a_time_seconds<60*60*2, true> m_fork_moaner; + miner m_miner; //!< miner instance + account_public_address m_miner_address; //!< address to mine to (for miner instance) + + std::string m_config_folder; //!< folder to look in for configs and other files + + cryptonote_protocol_stub m_protocol_stub; //!< cryptonote protocol stub instance + + epee::math_helper::once_a_time_seconds<60*60*12, false> m_store_blockchain_interval; //!< interval for manual storing of Blockchain, if enabled + epee::math_helper::once_a_time_seconds<60*60*2, false> m_fork_moaner; //!< interval for checking HardFork status epee::math_helper::once_a_time_seconds<60*2, false> m_txpool_auto_relayer; //!< interval for checking re-relaying txpool transactions + friend class tx_validate_inputs; - std::atomic m_starter_message_showed; + std::atomic m_starter_message_showed; //!< has the "daemon will sync now" message been shown? - uint64_t m_target_blockchain_height; + uint64_t m_target_blockchain_height; //!< blockchain height target - bool m_testnet; - bool m_fakechain; - std::string m_checkpoints_path; - time_t m_last_dns_checkpoints_update; - time_t m_last_json_checkpoints_update; + bool m_testnet; //!< are we on testnet? - std::atomic_flag m_checkpoints_updating; + bool m_fakechain; //!< are we using a fake chain (for testing purposes)? - boost::interprocess::file_lock db_lock; + std::string m_checkpoints_path; //!< path to json checkpoints file + time_t m_last_dns_checkpoints_update; //!< time when dns checkpoints were last updated + time_t m_last_json_checkpoints_update; //!< time when json checkpoints were last updated + + std::atomic_flag m_checkpoints_updating; //!< set if checkpoints are currently updating to avoid multiple threads attempting to update at once + + boost::interprocess::file_lock db_lock; //!< a lock object for a file lock in the db directory }; } diff --git a/src/cryptonote_core/difficulty.cpp b/src/cryptonote_core/difficulty.cpp index 236e8448..54da7739 100644 --- a/src/cryptonote_core/difficulty.cpp +++ b/src/cryptonote_core/difficulty.cpp @@ -116,8 +116,8 @@ namespace cryptonote { return !carry; } - difficulty_type next_difficulty(vector timestamps, vector cumulative_difficulties, size_t target_seconds) { - //cutoff DIFFICULTY_LAG + difficulty_type next_difficulty(std::vector timestamps, std::vector cumulative_difficulties, size_t target_seconds) { + if(timestamps.size() > DIFFICULTY_WINDOW) { timestamps.resize(DIFFICULTY_WINDOW); @@ -151,6 +151,8 @@ namespace cryptonote { assert(total_work > 0); uint64_t low, high; mul(total_work, target_seconds, low, high); + // blockchain errors "difficulty overhead" if this function returns zero. + // TODO: consider throwing an exception instead if (high != 0 || low + time_span - 1 < low) { return 0; } diff --git a/src/cryptonote_core/difficulty.h b/src/cryptonote_core/difficulty.h index d49c2f3b..910f9703 100644 --- a/src/cryptonote_core/difficulty.h +++ b/src/cryptonote_core/difficulty.h @@ -39,6 +39,18 @@ namespace cryptonote { typedef std::uint64_t difficulty_type; + /** + * @brief checks if a hash fits the given difficulty + * + * The hash passes if (hash * difficulty) < 2^192. + * Phrased differently, if (hash * difficulty) fits without overflow into + * the least significant 192 bits of the 256 bit multiplication result. + * + * @param hash the hash to check + * @param difficulty the difficulty to check against + * + * @return true if valid, else false + */ bool check_hash(const crypto::hash &hash, difficulty_type difficulty); difficulty_type next_difficulty(std::vector timestamps, std::vector cumulative_difficulties, size_t target_seconds); } diff --git a/src/daemon/core.h b/src/daemon/core.h index 2208ef25..2b7f0d17 100644 --- a/src/daemon/core.h +++ b/src/daemon/core.h @@ -28,7 +28,6 @@ #pragma once -#include "cryptonote_core/checkpoints_create.h" #include "cryptonote_core/cryptonote_core.h" #include "cryptonote_protocol/cryptonote_protocol_handler.h" #include "misc_log_ex.h" From 51a56e78ae82c1c01f7bbf89600f7dd1c064042d Mon Sep 17 00:00:00 2001 From: Riccardo Spagni Date: Fri, 25 Mar 2016 14:33:58 +0200 Subject: [PATCH 36/47] remove unecessary and bad std::move from portable_storage_template_helper.h --- .../epee/include/storages/portable_storage_template_helper.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/epee/include/storages/portable_storage_template_helper.h b/contrib/epee/include/storages/portable_storage_template_helper.h index 88515b5c..bbd8413f 100644 --- a/contrib/epee/include/storages/portable_storage_template_helper.h +++ b/contrib/epee/include/storages/portable_storage_template_helper.h @@ -72,7 +72,7 @@ namespace epee { std::string json_buff; store_t_to_json(str_in, json_buff, indent, insert_newlines); - return std::move(json_buff); + return json_buff; } //----------------------------------------------------------------------------------------------------------- template @@ -117,7 +117,7 @@ namespace epee { std::string binary_buff; store_t_to_binary(str_in, binary_buff, indent); - return std::move(binary_buff); + return binary_buff; } } } From 287e88283cba79cf74b4282c76931713dc0ee9c2 Mon Sep 17 00:00:00 2001 From: Riccardo Spagni Date: Fri, 25 Mar 2016 14:52:19 +0200 Subject: [PATCH 37/47] remove connectivity tool, comment it out from munin plugins too --- src/CMakeLists.txt | 1 - src/connectivity_tool/CMakeLists.txt | 46 --- src/connectivity_tool/conn_tool.cpp | 377 ------------------ utils/munin_plugins/alt_blocks_count | 2 +- utils/munin_plugins/difficulty | 2 +- utils/munin_plugins/grey_peerlist_size | 2 +- utils/munin_plugins/height | 2 +- .../munin_plugins/incoming_connections_count | 2 +- .../munin_plugins/outgoing_connections_count | 2 +- utils/munin_plugins/tx_count | 2 +- utils/munin_plugins/tx_pool_size | 2 +- utils/munin_plugins/white_peerlist_size | 2 +- 12 files changed, 9 insertions(+), 433 deletions(-) delete mode 100644 src/connectivity_tool/CMakeLists.txt delete mode 100644 src/connectivity_tool/conn_tool.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0ac4a0aa..e2349744 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -87,7 +87,6 @@ add_subdirectory(wallet) add_subdirectory(p2p) add_subdirectory(cryptonote_protocol) -add_subdirectory(connectivity_tool) add_subdirectory(miner) add_subdirectory(simplewallet) add_subdirectory(daemonizer) diff --git a/src/connectivity_tool/CMakeLists.txt b/src/connectivity_tool/CMakeLists.txt deleted file mode 100644 index 0ade952d..00000000 --- a/src/connectivity_tool/CMakeLists.txt +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright (c) 2014-2016, The Monero Project -# -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without modification, are -# permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, this list of -# conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, this list -# of conditions and the following disclaimer in the documentation and/or other -# materials provided with the distribution. -# -# 3. Neither the name of the copyright holder nor the names of its contributors may be -# used to endorse or promote products derived from this software without specific -# prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -set(connectivity_tool_sources - conn_tool.cpp) - -set(connectivity_tool_private_headers) - -bitmonero_add_executable(connectivity_tool - ${connectivity_tool_sources} - ${connectivity_tool_private_headers}) -target_link_libraries(connectivity_tool - LINK_PRIVATE - cryptonote_core - crypto - common - ${CMAKE_THREAD_LIBS_INIT} - ${Boost_PROGRAM_OPTIONS_LIBRARY} - ${Boost_REGEX_LIBRARY} - ${Boost_CHRONO_LIBRARY} - ${Boost_SYSTEM_LIBRARY}) diff --git a/src/connectivity_tool/conn_tool.cpp b/src/connectivity_tool/conn_tool.cpp deleted file mode 100644 index 458d30cc..00000000 --- a/src/connectivity_tool/conn_tool.cpp +++ /dev/null @@ -1,377 +0,0 @@ -// Copyright (c) 2014-2016, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers - -#include "include_base_utils.h" -#include "version.h" - -using namespace epee; -#include -#include "p2p/p2p_protocol_defs.h" -#include "common/command_line.h" -#include "cryptonote_core/cryptonote_core.h" -#include "cryptonote_protocol/cryptonote_protocol_handler.h" -#include "net/levin_client.h" -#include "storages/levin_abstract_invoke2.h" -#include "cryptonote_core/cryptonote_core.h" -#include "storages/portable_storage_template_helper.h" -#include "crypto/crypto.h" -#include "storages/http_abstract_invoke.h" -#include "net/http_client.h" - -namespace po = boost::program_options; -using namespace cryptonote; -using namespace nodetool; - -namespace -{ - const command_line::arg_descriptor arg_ip = {"ip", "set ip"}; - const command_line::arg_descriptor arg_port = {"port", "set port"}; - const command_line::arg_descriptor arg_rpc_port = {"rpc_port", "set rpc port"}; - const command_line::arg_descriptor arg_timeout = {"timeout", "set timeout"}; - const command_line::arg_descriptor arg_priv_key = {"private_key", "private key to subscribe debug command", "", true}; - const command_line::arg_descriptor arg_peer_id = {"peer_id", "peer_id if known(if not - will be requested)", 0}; - const command_line::arg_descriptor arg_generate_keys = {"generate_keys_pair", "generate private and public keys pair"}; - const command_line::arg_descriptor arg_request_stat_info = {"request_stat_info", "request statistics information"}; - const command_line::arg_descriptor arg_request_net_state = {"request_net_state", "request network state information (peer list, connections count)"}; - const command_line::arg_descriptor arg_get_daemon_info = {"rpc_get_daemon_info", "request daemon state info vie rpc (--rpc_port option should be set ).", "", true}; -} - -typedef COMMAND_REQUEST_STAT_INFO_T::stat_info> COMMAND_REQUEST_STAT_INFO; - -struct response_schema -{ - std::string status; - std::string COMMAND_REQUEST_STAT_INFO_status; - std::string COMMAND_REQUEST_NETWORK_STATE_status; - enableable si_rsp; - enableable ns_rsp; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) - KV_SERIALIZE(COMMAND_REQUEST_STAT_INFO_status) - KV_SERIALIZE(COMMAND_REQUEST_NETWORK_STATE_status) - KV_SERIALIZE(si_rsp) - KV_SERIALIZE(ns_rsp) - END_KV_SERIALIZE_MAP() -}; - - std::string get_response_schema_as_json(response_schema& rs) - { - std::stringstream ss; - ss << "{" << ENDL - << " \"status\": \"" << rs.status << "\"," << ENDL - << " \"COMMAND_REQUEST_NETWORK_STATE_status\": \"" << rs.COMMAND_REQUEST_NETWORK_STATE_status << "\"," << ENDL - << " \"COMMAND_REQUEST_STAT_INFO_status\": \"" << rs.COMMAND_REQUEST_STAT_INFO_status << "\""; - if(rs.si_rsp.enabled) - { - ss << "," << ENDL << " \"si_rsp\": " << epee::serialization::store_t_to_json(rs.si_rsp.v, 1); - } - if(rs.ns_rsp.enabled) - { - ss << "," << ENDL << " \"ns_rsp\": {" << ENDL - << " \"local_time\": " << rs.ns_rsp.v.local_time << "," << ENDL - << " \"my_id\": \"" << rs.ns_rsp.v.my_id << "\"," << ENDL - << " \"connections_list\": [" << ENDL; - - size_t i = 0; - BOOST_FOREACH(const connection_entry& ce, rs.ns_rsp.v.connections_list) - { - ss << " {\"peer_id\": \"" << ce.id << "\", \"ip\": \"" << string_tools::get_ip_string_from_int32(ce.adr.ip) << "\", \"port\": " << ce.adr.port << ", \"is_income\": "<< ce.is_income << "}"; - if(rs.ns_rsp.v.connections_list.size()-1 != i) - ss << ","; - ss << ENDL; - i++; - } - ss << " ]," << ENDL; - ss << " \"local_peerlist_white\": [" << ENDL; - i = 0; - BOOST_FOREACH(const peerlist_entry& pe, rs.ns_rsp.v.local_peerlist_white) - { - ss << " {\"peer_id\": \"" << pe.id << "\", \"ip\": \"" << string_tools::get_ip_string_from_int32(pe.adr.ip) << "\", \"port\": " << pe.adr.port << ", \"last_seen\": "<< rs.ns_rsp.v.local_time - pe.last_seen << "}"; - if(rs.ns_rsp.v.local_peerlist_white.size()-1 != i) - ss << ","; - ss << ENDL; - i++; - } - ss << " ]," << ENDL; - - ss << " \"local_peerlist_gray\": [" << ENDL; - i = 0; - BOOST_FOREACH(const peerlist_entry& pe, rs.ns_rsp.v.local_peerlist_gray) - { - ss << " {\"peer_id\": \"" << pe.id << "\", \"ip\": \"" << string_tools::get_ip_string_from_int32(pe.adr.ip) << "\", \"port\": " << pe.adr.port << ", \"last_seen\": "<< rs.ns_rsp.v.local_time - pe.last_seen << "}"; - if(rs.ns_rsp.v.local_peerlist_gray.size()-1 != i) - ss << ","; - ss << ENDL; - i++; - } - ss << " ]" << ENDL << " }" << ENDL; - } - ss << "}"; - return std::move(ss.str()); - } -//--------------------------------------------------------------------------------------------------------------- -bool print_COMMAND_REQUEST_STAT_INFO(const COMMAND_REQUEST_STAT_INFO::response& si) -{ - std::cout << " ------ COMMAND_REQUEST_STAT_INFO ------ " << ENDL; - std::cout << "Version: " << si.version << ENDL; - std::cout << "OS Version: " << si.os_version << ENDL; - std::cout << "Connections: " << si.connections_count << ENDL; - std::cout << "INC Connections: " << si.incoming_connections_count << ENDL; - - - std::cout << "Tx pool size: " << si.payload_info.tx_pool_size << ENDL; - std::cout << "BC height: " << si.payload_info.blockchain_height << ENDL; - std::cout << "Mining speed: " << si.payload_info.mining_speed << ENDL; - std::cout << "Alternative blocks: " << si.payload_info.alternative_blocks << ENDL; - std::cout << "Top block id: " << si.payload_info.top_block_id_str << ENDL; - return true; -} -//--------------------------------------------------------------------------------------------------------------- -bool print_COMMAND_REQUEST_NETWORK_STATE(const COMMAND_REQUEST_NETWORK_STATE::response& ns) -{ - std::cout << " ------ COMMAND_REQUEST_NETWORK_STATE ------ " << ENDL; - std::cout << "Peer id: " << ns.my_id << ENDL; - std::cout << "Active connections:" << ENDL; - BOOST_FOREACH(const connection_entry& ce, ns.connections_list) - { - std::cout << ce.id << "\t" << string_tools::get_ip_string_from_int32(ce.adr.ip) << ":" << ce.adr.port << (ce.is_income ? "(INC)":"(OUT)") << ENDL; - } - - std::cout << "Peer list white:" << ns.my_id << ENDL; - BOOST_FOREACH(const peerlist_entry& pe, ns.local_peerlist_white) - { - std::cout << pe.id << "\t" << string_tools::get_ip_string_from_int32(pe.adr.ip) << ":" << pe.adr.port << "\t" << misc_utils::get_time_interval_string(ns.local_time - pe.last_seen) << ENDL; - } - - std::cout << "Peer list gray:" << ns.my_id << ENDL; - BOOST_FOREACH(const peerlist_entry& pe, ns.local_peerlist_gray) - { - std::cout << pe.id << "\t" << string_tools::get_ip_string_from_int32(pe.adr.ip) << ":" << pe.adr.port << "\t" << misc_utils::get_time_interval_string(ns.local_time - pe.last_seen) << ENDL; - } - - - return true; -} -//--------------------------------------------------------------------------------------------------------------- -bool handle_get_daemon_info(po::variables_map& vm) -{ - if(!command_line::has_arg(vm, arg_rpc_port)) - { - std::cout << "ERROR: rpc port not set" << ENDL; - return false; - } - - epee::net_utils::http::http_simple_client http_client; - - cryptonote::COMMAND_RPC_GET_INFO::request req = AUTO_VAL_INIT(req); - cryptonote::COMMAND_RPC_GET_INFO::response res = AUTO_VAL_INIT(res); - std::string daemon_addr = command_line::get_arg(vm, arg_ip) + ":" + std::to_string(command_line::get_arg(vm, arg_rpc_port)); - bool r = net_utils::invoke_http_json_remote_command2(daemon_addr + "/getinfo", req, res, http_client, command_line::get_arg(vm, arg_timeout)); - if(!r) - { - std::cout << "ERROR: failed to invoke request" << ENDL; - return false; - } - std::cout << "OK" << ENDL - << "height: " << res.height << ENDL - << "difficulty: " << res.difficulty << ENDL - << "tx_count: " << res.tx_count << ENDL - << "tx_pool_size: " << res.tx_pool_size << ENDL - << "alt_blocks_count: " << res.alt_blocks_count << ENDL - << "outgoing_connections_count: " << res.outgoing_connections_count << ENDL - << "incoming_connections_count: " << res.incoming_connections_count << ENDL - << "white_peerlist_size: " << res.white_peerlist_size << ENDL - << "grey_peerlist_size: " << res.grey_peerlist_size << ENDL; - - return true; -} -//--------------------------------------------------------------------------------------------------------------- -bool handle_request_stat(po::variables_map& vm, peerid_type peer_id) -{ - - if(!command_line::has_arg(vm, arg_priv_key)) - { - std::cout << "{" << ENDL << " \"status\": \"ERROR: " << "secret key not set \"" << ENDL << "}"; - return false; - } - crypto::secret_key prvk = AUTO_VAL_INIT(prvk); - if(!string_tools::hex_to_pod(command_line::get_arg(vm, arg_priv_key) , prvk)) - { - std::cout << "{" << ENDL << " \"status\": \"ERROR: " << "wrong secret key set \"" << ENDL << "}"; - return false; - } - - - response_schema rs = AUTO_VAL_INIT(rs); - - levin::levin_client_impl2 transport; - if(!transport.connect(command_line::get_arg(vm, arg_ip), static_cast(command_line::get_arg(vm, arg_port)), static_cast(command_line::get_arg(vm, arg_timeout)))) - { - std::cout << "{" << ENDL << " \"status\": \"ERROR: " << "Failed to connect to " << command_line::get_arg(vm, arg_ip) << ":" << command_line::get_arg(vm, arg_port) << "\"" << ENDL << "}"; - return false; - }else - rs.status = "OK"; - - if(!peer_id) - { - COMMAND_REQUEST_PEER_ID::request req = AUTO_VAL_INIT(req); - COMMAND_REQUEST_PEER_ID::response rsp = AUTO_VAL_INIT(rsp); - if(!net_utils::invoke_remote_command2(COMMAND_REQUEST_PEER_ID::ID, req, rsp, transport)) - { - std::cout << "{" << ENDL << " \"status\": \"ERROR: " << "Failed to connect to " << command_line::get_arg(vm, arg_ip) << ":" << command_line::get_arg(vm, arg_port) << "\"" << ENDL << "}"; - return false; - }else - { - peer_id = rsp.my_id; - } - } - - - nodetool::proof_of_trust pot = AUTO_VAL_INIT(pot); - pot.peer_id = peer_id; - pot.time = time(NULL); - crypto::public_key pubk = AUTO_VAL_INIT(pubk); - string_tools::hex_to_pod(::config::P2P_REMOTE_DEBUG_TRUSTED_PUB_KEY, pubk); - crypto::hash h = tools::get_proof_of_trust_hash(pot); - crypto::generate_signature(h, pubk, prvk, pot.sign); - - if(command_line::get_arg(vm, arg_request_stat_info)) - { - COMMAND_REQUEST_STAT_INFO::request req = AUTO_VAL_INIT(req); - req.tr = pot; - if(!net_utils::invoke_remote_command2(COMMAND_REQUEST_STAT_INFO::ID, req, rs.si_rsp.v, transport)) - { - std::stringstream ss; - ss << "ERROR: " << "Failed to invoke remote command COMMAND_REQUEST_STAT_INFO to " << command_line::get_arg(vm, arg_ip) << ":" << command_line::get_arg(vm, arg_port); - rs.COMMAND_REQUEST_STAT_INFO_status = ss.str(); - }else - { - rs.si_rsp.enabled = true; - rs.COMMAND_REQUEST_STAT_INFO_status = "OK"; - } - } - - - if(command_line::get_arg(vm, arg_request_net_state)) - { - ++pot.time; - h = tools::get_proof_of_trust_hash(pot); - crypto::generate_signature(h, pubk, prvk, pot.sign); - COMMAND_REQUEST_NETWORK_STATE::request req = AUTO_VAL_INIT(req); - req.tr = pot; - if(!net_utils::invoke_remote_command2(COMMAND_REQUEST_NETWORK_STATE::ID, req, rs.ns_rsp.v, transport)) - { - std::stringstream ss; - ss << "ERROR: " << "Failed to invoke remote command COMMAND_REQUEST_NETWORK_STATE to " << command_line::get_arg(vm, arg_ip) << ":" << command_line::get_arg(vm, arg_port); - rs.COMMAND_REQUEST_NETWORK_STATE_status = ss.str(); - }else - { - rs.ns_rsp.enabled = true; - rs.COMMAND_REQUEST_NETWORK_STATE_status = "OK"; - } - } - std::cout << get_response_schema_as_json(rs); - return true; -} -//--------------------------------------------------------------------------------------------------------------- -bool generate_and_print_keys() -{ - crypto::public_key pk = AUTO_VAL_INIT(pk); - crypto::secret_key sk = AUTO_VAL_INIT(sk); - generate_keys(pk, sk); - std::cout << "PUBLIC KEY: " << epee::string_tools::pod_to_hex(pk) << ENDL - << "PRIVATE KEY: " << epee::string_tools::pod_to_hex(sk); - return true; -} -int main(int argc, char* argv[]) -{ - string_tools::set_module_name_and_folder(argv[0]); - log_space::get_set_log_detalisation_level(true, LOG_LEVEL_0); - - // Declare the supported options. - po::options_description desc_general("General options"); - command_line::add_arg(desc_general, command_line::arg_help); - - po::options_description desc_params("Connectivity options"); - command_line::add_arg(desc_params, arg_ip); - command_line::add_arg(desc_params, arg_port); - command_line::add_arg(desc_params, arg_rpc_port); - command_line::add_arg(desc_params, arg_timeout); - command_line::add_arg(desc_params, arg_request_stat_info); - command_line::add_arg(desc_params, arg_request_net_state); - command_line::add_arg(desc_params, arg_generate_keys); - command_line::add_arg(desc_params, arg_peer_id); - command_line::add_arg(desc_params, arg_priv_key); - command_line::add_arg(desc_params, arg_get_daemon_info); - - - po::options_description desc_all; - desc_all.add(desc_general).add(desc_params); - - po::variables_map vm; - bool r = command_line::handle_error_helper(desc_all, [&]() - { - po::store(command_line::parse_command_line(argc, argv, desc_general, true), vm); - if (command_line::get_arg(vm, command_line::arg_help)) - { - std::cout << desc_all << ENDL; - return false; - } - - po::store(command_line::parse_command_line(argc, argv, desc_params, false), vm); - po::notify(vm); - - return true; - }); - if (!r) - return 1; - - if(command_line::has_arg(vm, arg_request_stat_info) || command_line::has_arg(vm, arg_request_net_state)) - { - return handle_request_stat(vm, command_line::get_arg(vm, arg_peer_id)) ? 0:1; - } - if(command_line::has_arg(vm, arg_get_daemon_info)) - { - return handle_get_daemon_info(vm) ? 0:1; - } - else if(command_line::has_arg(vm, arg_generate_keys)) - { - return generate_and_print_keys() ? 0:1; - } - else - { - std::cerr << "Not enough arguments." << ENDL; - std::cerr << desc_all << ENDL; - } - - return 1; -} - diff --git a/utils/munin_plugins/alt_blocks_count b/utils/munin_plugins/alt_blocks_count index d68289c3..cf6b3651 100644 --- a/utils/munin_plugins/alt_blocks_count +++ b/utils/munin_plugins/alt_blocks_count @@ -42,4 +42,4 @@ EOM esac printf "alt_blocks_count.value " -/home/user/bitmonero/build/release/src/connectivity_tool --ip=127.0.0.1 --rpc_port=8081 --timeout=1000 --rpc_get_daemon_info | grep alt_blocks_count | cut -d ' ' -f2 +# rewrite using curl or similar: /home/user/bitmonero/build/release/src/connectivity_tool --ip=127.0.0.1 --rpc_port=8081 --timeout=1000 --rpc_get_daemon_info | grep alt_blocks_count | cut -d ' ' -f2 diff --git a/utils/munin_plugins/difficulty b/utils/munin_plugins/difficulty index e22b7682..edfd2bf1 100644 --- a/utils/munin_plugins/difficulty +++ b/utils/munin_plugins/difficulty @@ -42,4 +42,4 @@ EOM esac printf "difficulty.value " -/home/user/bitmonero/build/release/src/connectivity_tool --ip=127.0.0.1 --rpc_port=8081 --timeout=1000 --rpc_get_daemon_info | grep difficulty | cut -d ' ' -f2 +# rewrite using curl or similar: /home/user/bitmonero/build/release/src/connectivity_tool --ip=127.0.0.1 --rpc_port=8081 --timeout=1000 --rpc_get_daemon_info | grep difficulty | cut -d ' ' -f2 diff --git a/utils/munin_plugins/grey_peerlist_size b/utils/munin_plugins/grey_peerlist_size index e18c4cd0..d7b1bd6a 100644 --- a/utils/munin_plugins/grey_peerlist_size +++ b/utils/munin_plugins/grey_peerlist_size @@ -42,4 +42,4 @@ EOM esac printf "grey_peerlist_size.value " -/home/user/bitmonero/build/release/src/connectivity_tool --ip=127.0.0.1 --rpc_port=8081 --timeout=1000 --rpc_get_daemon_info | grep grey_peerlist_size | cut -d ' ' -f2 +# rewrite using curl or similar: /home/user/bitmonero/build/release/src/connectivity_tool --ip=127.0.0.1 --rpc_port=8081 --timeout=1000 --rpc_get_daemon_info | grep grey_peerlist_size | cut -d ' ' -f2 diff --git a/utils/munin_plugins/height b/utils/munin_plugins/height index ca68b31b..3f79fa30 100644 --- a/utils/munin_plugins/height +++ b/utils/munin_plugins/height @@ -43,4 +43,4 @@ EOM esac printf "height.value " -/home/user/bitmonero/build/release/src/connectivity_tool --ip=127.0.0.1 --rpc_port=8081 --timeout=1000 --rpc_get_daemon_info | grep height | cut -d ' ' -f2 +# rewrite using curl or similar: /home/user/bitmonero/build/release/src/connectivity_tool --ip=127.0.0.1 --rpc_port=8081 --timeout=1000 --rpc_get_daemon_info | grep height | cut -d ' ' -f2 diff --git a/utils/munin_plugins/incoming_connections_count b/utils/munin_plugins/incoming_connections_count index 2521d4c9..7232e573 100644 --- a/utils/munin_plugins/incoming_connections_count +++ b/utils/munin_plugins/incoming_connections_count @@ -42,4 +42,4 @@ EOM esac printf "incoming_connections_count.value " -/home/user/bitmonero/build/release/src/connectivity_tool --ip=127.0.0.1 --rpc_port=8081 --timeout=1000 --rpc_get_daemon_info | grep incoming_connections_count | cut -d ' ' -f2 +# rewrite using curl or similar: /home/user/bitmonero/build/release/src/connectivity_tool --ip=127.0.0.1 --rpc_port=8081 --timeout=1000 --rpc_get_daemon_info | grep incoming_connections_count | cut -d ' ' -f2 diff --git a/utils/munin_plugins/outgoing_connections_count b/utils/munin_plugins/outgoing_connections_count index 92edb3d6..773ce462 100644 --- a/utils/munin_plugins/outgoing_connections_count +++ b/utils/munin_plugins/outgoing_connections_count @@ -42,4 +42,4 @@ EOM esac printf "outgoing_connections_count.value " -/home/user/bitmonero/build/release/src/connectivity_tool --ip=127.0.0.1 --rpc_port=8081 --timeout=1000 --rpc_get_daemon_info | grep outgoing_connections_count | cut -d ' ' -f2 +# rewrite using curl or similar: /home/user/bitmonero/build/release/src/connectivity_tool --ip=127.0.0.1 --rpc_port=8081 --timeout=1000 --rpc_get_daemon_info | grep outgoing_connections_count | cut -d ' ' -f2 diff --git a/utils/munin_plugins/tx_count b/utils/munin_plugins/tx_count index 95bb8b54..f2504cd5 100644 --- a/utils/munin_plugins/tx_count +++ b/utils/munin_plugins/tx_count @@ -43,4 +43,4 @@ EOM esac printf "tx_count.value " -/home/user/bitmonero/build/release/src/connectivity_tool --ip=127.0.0.1 --rpc_port=8081 --timeout=1000 --rpc_get_daemon_info | grep tx_count| cut -d ' ' -f2 +# rewrite using curl or similar: /home/user/bitmonero/build/release/src/connectivity_tool --ip=127.0.0.1 --rpc_port=8081 --timeout=1000 --rpc_get_daemon_info | grep tx_count| cut -d ' ' -f2 diff --git a/utils/munin_plugins/tx_pool_size b/utils/munin_plugins/tx_pool_size index 58717cf1..3bf95207 100644 --- a/utils/munin_plugins/tx_pool_size +++ b/utils/munin_plugins/tx_pool_size @@ -42,4 +42,4 @@ EOM esac printf "tx_pool_size.value " -/home/user/bitmonero/build/release/src/connectivity_tool --ip=127.0.0.1 --rpc_port=8081 --timeout=1000 --rpc_get_daemon_info | grep tx_pool_size| cut -d ' ' -f2 +# rewrite using curl or similar: /home/user/bitmonero/build/release/src/connectivity_tool --ip=127.0.0.1 --rpc_port=8081 --timeout=1000 --rpc_get_daemon_info | grep tx_pool_size| cut -d ' ' -f2 diff --git a/utils/munin_plugins/white_peerlist_size b/utils/munin_plugins/white_peerlist_size index 2b92622e..9e5771cc 100644 --- a/utils/munin_plugins/white_peerlist_size +++ b/utils/munin_plugins/white_peerlist_size @@ -42,4 +42,4 @@ EOM esac printf "white_peerlist_size.value " -/home/user/bitmonero/build/release/src/connectivity_tool --ip=127.0.0.1 --rpc_port=8081 --timeout=1000 --rpc_get_daemon_info | grep white_peerlist_size | cut -d ' ' -f2 +# rewrite using curl or similar: /home/user/bitmonero/build/release/src/connectivity_tool --ip=127.0.0.1 --rpc_port=8081 --timeout=1000 --rpc_get_daemon_info | grep white_peerlist_size | cut -d ' ' -f2 From 97638b1fb7fde487e4025066874fa3274779eabc Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Wed, 23 Mar 2016 17:01:49 +0000 Subject: [PATCH 38/47] core: fix miner tx block reward with fees --- src/cryptonote_core/cryptonote_format_utils.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/cryptonote_core/cryptonote_format_utils.cpp b/src/cryptonote_core/cryptonote_format_utils.cpp index 253e568b..3c1acd8a 100644 --- a/src/cryptonote_core/cryptonote_format_utils.cpp +++ b/src/cryptonote_core/cryptonote_format_utils.cpp @@ -126,20 +126,19 @@ namespace cryptonote return false; } - // from hard fork 2, we cut out the low significant digits. This makes the tx smaller, and - // keeps the paid amount almost the same. The unpaid remainder gets pushed back to the - // emission schedule - if (hard_fork_version >= 2) - { - block_reward = block_reward - block_reward % ::config::BASE_REWARD_CLAMP_THRESHOLD; - } - #if defined(DEBUG_CREATE_BLOCK_TEMPLATE) LOG_PRINT_L1("Creating block template: reward " << block_reward << ", fee " << fee) #endif block_reward += fee; + // from hard fork 2, we cut out the low significant digits. This makes the tx smaller, and + // keeps the paid amount almost the same. The unpaid remainder gets pushed back to the + // emission schedule + if (hard_fork_version >= 2) { + block_reward = block_reward - block_reward % ::config::BASE_REWARD_CLAMP_THRESHOLD; + } + std::vector out_amounts; decompose_amount_into_digits(block_reward, hard_fork_version >= 2 ? 0 : ::config::DEFAULT_DUST_THRESHOLD, [&out_amounts](uint64_t a_chunk) { out_amounts.push_back(a_chunk); }, From b8527668ffbcc4c95fe33e2ea163f07f25af5fcb Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Wed, 23 Mar 2016 17:56:08 +0000 Subject: [PATCH 39/47] blockchain: for v3, require miner tx to have well behaved outs This was meant to go in v2, but the miner tx slipped through the cracks as it doesn't go through the main tx verification since it doesn't get added to the pool. --- src/cryptonote_core/blockchain.cpp | 13 +++++++++++-- src/cryptonote_core/blockchain.h | 3 ++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 2f42e1db..0f6afe74 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -948,7 +948,7 @@ bool Blockchain::prevalidate_miner_transaction(const block& b, uint64_t height) } //------------------------------------------------------------------ // This function validates the miner transaction reward -bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_block_size, uint64_t fee, uint64_t& base_reward, uint64_t already_generated_coins, bool &partial_block_reward) +bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_block_size, uint64_t fee, uint64_t& base_reward, uint64_t already_generated_coins, bool &partial_block_reward, uint8_t version) { LOG_PRINT_L3("Blockchain::" << __func__); //validate reward @@ -957,6 +957,15 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl money_in_use += o.amount; partial_block_reward = false; + if (version >= 3) { + for (auto &o: b.miner_tx.vout) { + if (!is_valid_decomposed_amount(o.amount)) { + LOG_PRINT_L1("miner tx output " << print_money(o.amount) << " is not a valid decomposed amount"); + return false; + } + } + } + std::vector last_blocks_sizes; get_last_n_blocks_sizes(last_blocks_sizes, CRYPTONOTE_REWARD_BLOCKS_WINDOW); if (!get_block_reward(epee::misc_utils::median(last_blocks_sizes), cumulative_block_size, already_generated_coins, base_reward, get_current_hard_fork_version())) @@ -2698,7 +2707,7 @@ leave: TIME_MEASURE_START(vmt); uint64_t base_reward = 0; uint64_t already_generated_coins = m_db->height() ? m_db->get_block_already_generated_coins(m_db->height() - 1) : 0; - if(!validate_miner_transaction(bl, cumulative_block_size, fee_summary, base_reward, already_generated_coins, bvc.m_partial_block_reward)) + if(!validate_miner_transaction(bl, cumulative_block_size, fee_summary, base_reward, already_generated_coins, bvc.m_partial_block_reward, m_hardfork->get_current_version())) { LOG_PRINT_L1("Block with id: " << id << " has incorrect miner transaction"); bvc.m_verifivation_failed = true; diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 94def1aa..393faef2 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -980,10 +980,11 @@ namespace cryptonote * @param base_reward return-by-reference the new block's generated coins * @param already_generated_coins the amount of currency generated prior to this block * @param partial_block_reward return-by-reference true if miner accepted only partial reward + * @param version hard fork version for that transaction * * @return false if anything is found wrong with the miner transaction, otherwise true */ - bool validate_miner_transaction(const block& b, size_t cumulative_block_size, uint64_t fee, uint64_t& base_reward, uint64_t already_generated_coins, bool &partial_block_reward); + bool validate_miner_transaction(const block& b, size_t cumulative_block_size, uint64_t fee, uint64_t& base_reward, uint64_t already_generated_coins, bool &partial_block_reward, uint8_t version); /** * @brief reverts the blockchain to its previous state following a failed switch From f26651ab8a3ed7f39a13f258880ee57f7ab85b68 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sat, 26 Mar 2016 11:44:43 +0000 Subject: [PATCH 40/47] wallet: factor fee calculation --- src/wallet/wallet2.cpp | 33 +++++++++++---------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 1c7187cf..7c54ca5e 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -92,6 +92,13 @@ void do_prepare_file_names(const std::string& file_path, std::string& keys_file, } } +uint64_t calculate_fee(const cryptonote::blobdata &blob) +{ + uint64_t bytes = blob.size(); + uint64_t kB = (bytes + 1023) / 1024; + return kB * FEE_PER_KB; +} + } //namespace namespace tools @@ -2030,13 +2037,7 @@ std::vector wallet2::create_transactions(std::vector wallet2::create_transactions_2(std::vector available_for_fee && dsts[0].amount > 0) @@ -2686,13 +2681,7 @@ std::vector wallet2::create_dust_sweep_transactions() { transfer_dust(num_outputs_per_tx, (uint64_t)0 /* unlock_time */, 0, detail::digit_split_strategy, dust_policy, extra, tx, ptx); auto txBlob = t_serializable_object_to_blob(ptx.tx); - uint64_t txSize = txBlob.size(); - uint64_t numKB = txSize / 1024; - if (txSize % 1024) - { - numKB++; - } - needed_fee = numKB * FEE_PER_KB; + needed_fee = calculate_fee(txBlob); // reroll the tx with the actual amount minus the fee // if there's not enough for the fee, it'll throw From f9a2fd2ff55654cdbc458378d0365189ffd1c46a Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sat, 26 Mar 2016 11:51:58 +0000 Subject: [PATCH 41/47] wallet: handle rare case where fee adjustment can bump to the next kB It resulted in a tx being sent with too low a fee, and thus rejected. --- src/wallet/wallet2.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 7c54ca5e..aa638143 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -2677,7 +2677,7 @@ std::vector wallet2::create_dust_sweep_transactions() // loop until fee is met without increasing tx size to next KB boundary. uint64_t needed_fee = 0; - if (1) + do { transfer_dust(num_outputs_per_tx, (uint64_t)0 /* unlock_time */, 0, detail::digit_split_strategy, dust_policy, extra, tx, ptx); auto txBlob = t_serializable_object_to_blob(ptx.tx); @@ -2687,7 +2687,8 @@ std::vector wallet2::create_dust_sweep_transactions() // if there's not enough for the fee, it'll throw transfer_dust(num_outputs_per_tx, (uint64_t)0 /* unlock_time */, needed_fee, detail::digit_split_strategy, dust_policy, extra, tx, ptx); txBlob = t_serializable_object_to_blob(ptx.tx); - } + needed_fee = calculate_fee(txBlob); + } while (ptx.fee < needed_fee); ptx_vector.push_back(ptx); From 600a3cf0c0ca0a99dbdc91d32138db3c8aa4165c Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sat, 26 Mar 2016 14:30:23 +0000 Subject: [PATCH 42/47] New RPC and daemon command to get output histogram This is a list of existing output amounts along with the number of outputs of that amount in the blockchain. The daemon command takes: - no parameters: all outputs with at least 3 instances - one parameter: all outputs with at least that many instances - two parameters: all outputs within that many instances The default starts at 3 to avoid massive spamming of all dust outputs in the blockchain, and is the current minimum mixin requirement. An optional vector of amounts may be passed, to request histogram only for those outputs. --- src/blockchain_db/berkeleydb/db_bdb.cpp | 6 +++ src/blockchain_db/berkeleydb/db_bdb.h | 9 ++++ src/blockchain_db/blockchain_db.h | 9 ++++ src/blockchain_db/lmdb/db_lmdb.cpp | 57 +++++++++++++++++++++++++ src/blockchain_db/lmdb/db_lmdb.h | 10 +++++ src/cryptonote_core/blockchain.cpp | 5 +++ src/cryptonote_core/blockchain.h | 9 ++++ src/daemon/command_parser_executor.cpp | 18 ++++++++ src/daemon/command_parser_executor.h | 2 + src/daemon/command_server.cpp | 5 +++ src/daemon/rpc_command_executor.cpp | 36 ++++++++++++++++ src/daemon/rpc_command_executor.h | 2 + src/rpc/core_rpc_server.cpp | 32 ++++++++++++++ src/rpc/core_rpc_server.h | 2 + src/rpc/core_rpc_server_commands_defs.h | 41 ++++++++++++++++++ tests/unit_tests/hardfork.cpp | 1 + 16 files changed, 244 insertions(+) diff --git a/src/blockchain_db/berkeleydb/db_bdb.cpp b/src/blockchain_db/berkeleydb/db_bdb.cpp index 1fef9e61..a7fa556b 100644 --- a/src/blockchain_db/berkeleydb/db_bdb.cpp +++ b/src/blockchain_db/berkeleydb/db_bdb.cpp @@ -2180,6 +2180,12 @@ void BlockchainBDB::get_output_tx_and_index(const uint64_t& amount, const std::v LOG_PRINT_L3("db3: " << db3); } +std::map::BlockchainBDB::get_output_histogram(const std::vector &amounts) const +{ + LOG_PRINT_L3("BlockchainBDB::" << __func__); + throw1(DB_ERROR("Not implemented.")); +} + void BlockchainBDB::set_hard_fork_starting_height(uint8_t version, uint64_t height) { LOG_PRINT_L3("BlockchainBDB::" << __func__); diff --git a/src/blockchain_db/berkeleydb/db_bdb.h b/src/blockchain_db/berkeleydb/db_bdb.h index d7cbd24e..5c6bda4e 100644 --- a/src/blockchain_db/berkeleydb/db_bdb.h +++ b/src/blockchain_db/berkeleydb/db_bdb.h @@ -341,6 +341,15 @@ public: virtual bool can_thread_bulk_indices() const { return false; } #endif + /** + * @brief return a histogram of outputs on the blockchain + * + * @param amounts optional set of amounts to lookup + * + * @return a set of amount/instances + */ + std::map get_output_histogram(const std::vector &amounts) const; + private: virtual void add_block( const block& blk , const size_t& block_size diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index 3e0ca141..3585bd06 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -1288,6 +1288,15 @@ public: */ virtual void drop_hard_fork_info() = 0; + /** + * @brief return a histogram of outputs on the blockchain + * + * @param amounts optional set of amounts to lookup + * + * @return a set of amount/instances + */ + virtual std::map get_output_histogram(const std::vector &amounts) const = 0; + /** * @brief is BlockchainDB in read-only mode? * diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index e928ab80..9b99520a 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -2692,6 +2692,63 @@ void BlockchainLMDB::get_output_tx_and_index(const uint64_t& amount, const std:: LOG_PRINT_L3("db3: " << db3); } +std::map BlockchainLMDB::get_output_histogram(const std::vector &amounts) const +{ + LOG_PRINT_L3("BlockchainLMDB::" << __func__); + check_open(); + + TXN_PREFIX_RDONLY(); + RCURSOR(output_amounts); + + std::map histogram; + MDB_val k; + MDB_val v; + + if (amounts.empty()) + { + MDB_cursor_op op = MDB_FIRST; + while (1) + { + int ret = mdb_cursor_get(m_cur_output_amounts, &k, &v, op); + op = MDB_NEXT_NODUP; + if (ret == MDB_NOTFOUND) + break; + if (ret) + throw0(DB_ERROR(lmdb_error("Failed to enumerate outputs: ", ret).c_str())); + mdb_size_t num_elems = 0; + mdb_cursor_count(m_cur_output_amounts, &num_elems); + uint64_t amount = *(const uint64_t*)k.mv_data; + histogram[amount] = num_elems; + } + } + else + { + for (const auto &amount: amounts) + { + MDB_val_copy k(amount); + int ret = mdb_cursor_get(m_cur_output_amounts, &k, &v, MDB_SET); + if (ret == MDB_NOTFOUND) + { + histogram[amount] = 0; + } + else if (ret == MDB_SUCCESS) + { + mdb_size_t num_elems = 0; + mdb_cursor_count(m_cur_output_amounts, &num_elems); + histogram[amount] = num_elems; + } + else + { + throw0(DB_ERROR(lmdb_error("Failed to enumerate outputs: ", ret).c_str())); + } + } + } + + TXN_POSTFIX_RDONLY(); + + return histogram; +} + void BlockchainLMDB::check_hard_fork_info() { LOG_PRINT_L3("BlockchainLMDB::" << __func__); diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h index a3f32ffa..6cd3e0e8 100644 --- a/src/blockchain_db/lmdb/db_lmdb.h +++ b/src/blockchain_db/lmdb/db_lmdb.h @@ -280,6 +280,16 @@ public: virtual void pop_block(block& blk, std::vector& txs); virtual bool can_thread_bulk_indices() const { return true; } + + /** + * @brief return a histogram of outputs on the blockchain + * + * @param amounts optional set of amounts to lookup + * + * @return a set of amount/instances + */ + std::map get_output_histogram(const std::vector &amounts) const; + private: void do_resize(uint64_t size_increase=0); diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 2f42e1db..035afc61 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -3313,6 +3313,11 @@ bool Blockchain::get_hard_fork_voting_info(uint8_t version, uint32_t &window, ui return m_hardfork->get_voting_info(version, window, votes, threshold, earliest_height, voting); } +std::map Blockchain:: get_output_histogram(const std::vector &amounts) const +{ + return m_db->get_output_histogram(amounts); +} + void Blockchain::load_compiled_in_block_hashes() { if (m_fast_sync && get_blocks_dat_start(m_testnet) != nullptr) diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 94def1aa..36e0fedf 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -682,6 +682,15 @@ namespace cryptonote */ bool flush_txes_from_pool(const std::list &txids); + /** + * @brief return a histogram of outputs on the blockchain + * + * @param amounts optional set of amounts to lookup + * + * @return a set of amount/instances + */ + std::map get_output_histogram(const std::vector &amounts) const; + /** * @brief perform a check on all key images in the blockchain * diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index 0b42257e..166fe04c 100644 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -439,5 +439,23 @@ bool t_command_parser_executor::flush_txpool(const std::vector& arg return m_executor.flush_txpool(txid); } +bool t_command_parser_executor::output_histogram(const std::vector& args) +{ + if (args.size() > 2) return false; + + uint64_t min_count = 3; + uint64_t max_count = 0; + + if (args.size() >= 1) + { + min_count = boost::lexical_cast(args[0]); + } + if (args.size() >= 2) + { + max_count = boost::lexical_cast(args[1]); + } + return m_executor.output_histogram(min_count, max_count); +} + } // namespace daemonize diff --git a/src/daemon/command_parser_executor.h b/src/daemon/command_parser_executor.h index 51c55a8c..11df92a5 100644 --- a/src/daemon/command_parser_executor.h +++ b/src/daemon/command_parser_executor.h @@ -114,6 +114,8 @@ public: bool unban(const std::vector& args); bool flush_txpool(const std::vector& args); + + bool output_histogram(const std::vector& args); }; } // namespace daemonize diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp index 206de9d4..aabc2f09 100644 --- a/src/daemon/command_server.cpp +++ b/src/daemon/command_server.cpp @@ -214,6 +214,11 @@ t_command_server::t_command_server( , std::bind(&t_command_parser_executor::flush_txpool, &m_parser, p::_1) , "Flush a transaction from the tx pool by its txid, or the whole tx pool" ); + m_command_lookup.set_handler( + "output_histogram" + , std::bind(&t_command_parser_executor::output_histogram, &m_parser, p::_1) + , "Print output histogram (amount, instances)" + ); } bool t_command_server::process_command_str(const std::string& cmd) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 15ddc081..933c93ed 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -1203,5 +1203,41 @@ bool t_rpc_command_executor::flush_txpool(const std::string &txid) return true; } +bool t_rpc_command_executor::output_histogram(uint64_t min_count, uint64_t max_count) +{ + cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request req; + cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response res; + std::string fail_message = "Unsuccessful"; + epee::json_rpc::error error_resp; + + req.min_count = min_count; + req.max_count = max_count; + + if (m_is_rpc) + { + if (!m_rpc_client->json_rpc_request(req, res, "get_output_histogram", fail_message.c_str())) + { + return true; + } + } + else + { + if (!m_rpc_server->on_get_output_histogram(req, res, error_resp)) + { + tools::fail_msg_writer() << fail_message.c_str(); + return true; + } + } + + std::sort(res.histogram.begin(), res.histogram.end(), + [](const cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::entry &e1, const cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::entry &e2)->bool { return e1.instances < e2.instances; }); + for (const auto &e: res.histogram) + { + tools::msg_writer() << e.instances << " " << cryptonote::print_money(e.amount); + } + + return true; +} + }// namespace daemonize diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index bc3f2d5c..7e73e7fa 100644 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -132,6 +132,8 @@ public: bool unban(const std::string &ip); bool flush_txpool(const std::string &txid); + + bool output_histogram(uint64_t min_count, uint64_t max_count); }; } // namespace daemonize diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 6b625e77..5350b6fe 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1041,6 +1041,38 @@ namespace cryptonote return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_get_output_histogram(const COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request& req, COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response& res, epee::json_rpc::error& error_resp) + { + if(!check_core_busy()) + { + error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; + error_resp.message = "Core is busy."; + return false; + } + + std::map histogram; + try + { + histogram = m_core.get_blockchain_storage().get_output_histogram(req.amounts); + } + catch (const std::exception &e) + { + res.status = "Failed to get output histogram"; + return true; + } + + res.histogram.clear(); + res.histogram.reserve(histogram.size()); + for (const auto &i: histogram) + { + if (i.second >= req.min_count && (i.second <= req.max_count || req.max_count == 0)) + res.histogram.push_back(COMMAND_RPC_GET_OUTPUT_HISTOGRAM::entry(i.first, i.second)); + } + + res.status = CORE_RPC_STATUS_OK; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_fast_exit(const COMMAND_RPC_FAST_EXIT::request& req, COMMAND_RPC_FAST_EXIT::response& res) { cryptonote::core::set_fast_exit(); diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index f7908703..5c370720 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -109,6 +109,7 @@ namespace cryptonote MAP_JON_RPC_WE("setbans", on_set_bans, COMMAND_RPC_SETBANS) MAP_JON_RPC_WE("getbans", on_get_bans, COMMAND_RPC_GETBANS) MAP_JON_RPC_WE("flush_txpool", on_flush_txpool, COMMAND_RPC_FLUSH_TRANSACTION_POOL) + MAP_JON_RPC_WE("get_output_histogram", on_get_output_histogram, COMMAND_RPC_GET_OUTPUT_HISTOGRAM) END_JSON_RPC_MAP() END_URI_MAP2() @@ -149,6 +150,7 @@ namespace cryptonote bool on_set_bans(const COMMAND_RPC_SETBANS::request& req, COMMAND_RPC_SETBANS::response& res, epee::json_rpc::error& error_resp); bool on_get_bans(const COMMAND_RPC_GETBANS::request& req, COMMAND_RPC_GETBANS::response& res, epee::json_rpc::error& error_resp); bool on_flush_txpool(const COMMAND_RPC_FLUSH_TRANSACTION_POOL::request& req, COMMAND_RPC_FLUSH_TRANSACTION_POOL::response& res, epee::json_rpc::error& error_resp); + bool on_get_output_histogram(const COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request& req, COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response& res, epee::json_rpc::error& error_resp); //----------------------- private: diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 72e399ec..6d4dd125 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -986,5 +986,46 @@ namespace cryptonote END_KV_SERIALIZE_MAP() }; }; + + struct COMMAND_RPC_GET_OUTPUT_HISTOGRAM + { + struct request + { + std::vector amounts; + uint64_t min_count; + uint64_t max_count; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(amounts); + KV_SERIALIZE(min_count); + KV_SERIALIZE(max_count); + END_KV_SERIALIZE_MAP() + }; + + struct entry + { + uint64_t amount; + uint64_t instances; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(amount); + KV_SERIALIZE(instances); + END_KV_SERIALIZE_MAP() + + entry(uint64_t amount, uint64_t instances): amount(amount), instances(instances) {} + entry() {} + }; + + struct response + { + std::string status; + std::vector histogram; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(status) + KV_SERIALIZE(histogram) + END_KV_SERIALIZE_MAP() + }; + }; } diff --git a/tests/unit_tests/hardfork.cpp b/tests/unit_tests/hardfork.cpp index 50e0e5ae..c0ee5fff 100644 --- a/tests/unit_tests/hardfork.cpp +++ b/tests/unit_tests/hardfork.cpp @@ -108,6 +108,7 @@ public: virtual bool for_all_transactions(std::function) const { return true; } virtual bool for_all_outputs(std::function f) const { return true; } virtual bool is_read_only() const { return false; } + virtual std::map get_output_histogram() const { return std::map(); } virtual void add_block( const block& blk , const size_t& block_size From 12146daeed66fc30319c481a378c8d317b49b4db Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sat, 26 Mar 2016 21:15:47 +0000 Subject: [PATCH 43/47] wallet: change sweep_dust to sweep_unmixable With the change in mixin rules for v2, the "annoying" outputs are slightly changed. There is high correlation between dust and unmixable, but no equivalence. --- src/simplewallet/simplewallet.cpp | 35 ++++++---- src/simplewallet/simplewallet.h | 2 +- src/wallet/wallet2.cpp | 104 ++++++++++++++++++++++++++---- src/wallet/wallet2.h | 7 +- src/wallet/wallet_errors.h | 9 +++ src/wallet/wallet_rpc_server.cpp | 2 +- 6 files changed, 129 insertions(+), 30 deletions(-) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 1856118f..dd166ede 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -541,7 +541,7 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("bc_height", boost::bind(&simple_wallet::show_blockchain_height, this, _1), tr("Show blockchain height")); m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer, this, _1), tr("transfer [] [ ... ] [payment_id] - Transfer ,... to ,... , respectively. is the number of extra inputs to include for untraceability (from 0 to maximum available)")); m_cmd_binder.set_handler("transfer_new", boost::bind(&simple_wallet::transfer_new, this, _1), tr("Same as transfer, but using a new transaction building algorithm")); - m_cmd_binder.set_handler("sweep_dust", boost::bind(&simple_wallet::sweep_dust, this, _1), tr("Send all dust outputs to yourself with mixin 0")); + m_cmd_binder.set_handler("sweep_unmixable", boost::bind(&simple_wallet::sweep_unmixable, this, _1), tr("Send all unmixable outputs to yourself with mixin 0")); m_cmd_binder.set_handler("set_log", boost::bind(&simple_wallet::set_log, this, _1), tr("set_log - Change current log detail level, <0-4>")); m_cmd_binder.set_handler("address", boost::bind(&simple_wallet::print_address, this, _1), tr("Show current wallet public address")); m_cmd_binder.set_handler("integrated_address", boost::bind(&simple_wallet::print_integrated_address, this, _1), tr("integrated_address [PID] - Encode a payment ID into an integrated address for the current wallet public address (no argument uses a random payment ID), or decode an integrated address to standard address and payment ID")); @@ -1722,8 +1722,7 @@ bool simple_wallet::refresh(const std::vector& args) bool simple_wallet::show_balance(const std::vector& args/* = std::vector()*/) { success_msg_writer() << tr("Balance: ") << print_money(m_wallet->balance()) << ", " - << tr("unlocked balance: ") << print_money(m_wallet->unlocked_balance()) << ", " - << tr("including unlocked dust: ") << print_money(m_wallet->unlocked_dust_balance(tools::tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD))); + << tr("unlocked balance: ") << print_money(m_wallet->unlocked_balance()); return true; } //---------------------------------------------------------------------------------------------------- @@ -2208,7 +2207,7 @@ bool simple_wallet::transfer_new(const std::vector &args_) } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::sweep_dust(const std::vector &args_) +bool simple_wallet::sweep_unmixable(const std::vector &args_) { if (!try_connect_to_daemon()) return true; @@ -2221,28 +2220,37 @@ bool simple_wallet::sweep_dust(const std::vector &args_) try { - uint64_t total_dust = m_wallet->unlocked_dust_balance(tools::tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD)); - // figure out what tx will be necessary - auto ptx_vector = m_wallet->create_dust_sweep_transactions(); + auto ptx_vector = m_wallet->create_unmixable_sweep_transactions(); + + if (ptx_vector.empty()) + { + fail_msg_writer() << tr("No unmixable outputs found"); + return true; + } // give user total and fee, and prompt to confirm - uint64_t total_fee = 0; + uint64_t total_fee = 0, total_unmixable = 0; for (size_t n = 0; n < ptx_vector.size(); ++n) { total_fee += ptx_vector[n].fee; + for (const auto &vin: ptx_vector[n].tx.vin) + { + if (vin.type() == typeid(txin_to_key)) + total_unmixable += boost::get(vin).amount; + } } - std::string prompt_str = tr("Sweeping ") + print_money(total_dust); + std::string prompt_str = tr("Sweeping ") + print_money(total_unmixable); if (ptx_vector.size() > 1) { prompt_str = (boost::format(tr("Sweeping %s in %llu transactions for a total fee of %s. Is this okay? (Y/Yes/N/No)")) % - print_money(total_dust) % + print_money(total_unmixable) % ((unsigned long long)ptx_vector.size()) % print_money(total_fee)).str(); } else { prompt_str = (boost::format(tr("Sweeping %s for a total fee of %s. Is this okay? (Y/Yes/N/No)")) % - print_money(total_dust) % + print_money(total_unmixable) % print_money(total_fee)).str(); } std::string accepted = command_line::input_line(prompt_str); @@ -2285,11 +2293,12 @@ bool simple_wallet::sweep_dust(const std::vector &args_) } catch (const tools::error::not_enough_money& e) { - fail_msg_writer() << boost::format(tr("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)")) % + fail_msg_writer() << boost::format(tr("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee).\n%s")) % print_money(e.available()) % print_money(e.tx_amount() + e.fee()) % print_money(e.tx_amount()) % - print_money(e.fee()); + print_money(e.fee()) % + tr("This is usually due to dust which is so small it cannot pay for itself in fees"); } catch (const tools::error::not_enough_outs_to_mix& e) { diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 7dadb853..21bbfa56 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -121,7 +121,7 @@ namespace cryptonote bool transfer_main(bool new_algorithm, const std::vector &args); bool transfer(const std::vector &args); bool transfer_new(const std::vector &args); - bool sweep_dust(const std::vector &args); + bool sweep_unmixable(const std::vector &args); std::vector> split_amounts( std::vector dsts, size_t num_splits ); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index aa638143..7d308e61 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -2508,7 +2508,7 @@ uint64_t wallet2::unlocked_dust_balance(const tx_dust_policy &dust_policy) const } template -void wallet2::transfer_dust(size_t num_outputs, uint64_t unlock_time, uint64_t needed_fee, T destination_split_strategy, const tx_dust_policy& dust_policy, const std::vector &extra, cryptonote::transaction& tx, pending_tx &ptx) +void wallet2::transfer_from(const std::vector &outs, size_t num_outputs, uint64_t unlock_time, uint64_t needed_fee, T destination_split_strategy, const tx_dust_policy& dust_policy, const std::vector &extra, cryptonote::transaction& tx, pending_tx &ptx) { using namespace cryptonote; @@ -2518,6 +2518,19 @@ void wallet2::transfer_dust(size_t num_outputs, uint64_t unlock_time, uint64_t n // throw if there are none uint64_t money = 0; std::list selected_transfers; +#if 1 + for (size_t n = 0; n < outs.size(); ++n) + { + const transfer_details& td = m_transfers[outs[n]]; + if (!td.m_spent) + { + selected_transfers.push_back (m_transfers.begin() + outs[n]); + money += td.amount(); + if (selected_transfers.size() >= num_outputs) + break; + } + } +#else for (transfer_container::iterator i = m_transfers.begin(); i != m_transfers.end(); ++i) { const transfer_details& td = *i; @@ -2529,6 +2542,7 @@ void wallet2::transfer_dust(size_t num_outputs, uint64_t unlock_time, uint64_t n break; } } +#endif // we don't allow no output to self, easier, but one may want to burn the dust if = fee THROW_WALLET_EXCEPTION_IF(money <= needed_fee, error::not_enough_money, money, needed_fee, needed_fee); @@ -2622,8 +2636,8 @@ bool wallet2::use_fork_rules(uint8_t version) r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/json_rpc", req_t, resp_t, m_http_client); m_daemon_rpc_mutex.unlock(); CHECK_AND_ASSERT_MES(r, false, "Failed to connect to daemon"); - CHECK_AND_ASSERT_MES(res.status != CORE_RPC_STATUS_BUSY, false, "Failed to connect to daemon"); - CHECK_AND_ASSERT_MES(res.status == CORE_RPC_STATUS_OK, false, "Failed to get hard fork status"); + CHECK_AND_ASSERT_MES(resp_t.result.status != CORE_RPC_STATUS_BUSY, false, "Failed to connect to daemon"); + CHECK_AND_ASSERT_MES(resp_t.result.status == CORE_RPC_STATUS_OK, false, "Failed to get hard fork status"); bool close_enough = res.height >= resp_t.result.earliest_height - 10; // start using the rules a bit beforehand if (close_enough) @@ -2641,20 +2655,84 @@ uint64_t wallet2::get_upper_tranaction_size_limit() return ((full_reward_zone * 125) / 100) - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE; } //---------------------------------------------------------------------------------------------------- -std::vector wallet2::create_dust_sweep_transactions() +std::vector wallet2::select_available_outputs(std::function f) +{ + std::vector outputs; + size_t n = 0; + for (transfer_container::const_iterator i = m_transfers.begin(); i != m_transfers.end(); ++i, ++n) + { + if (i->m_spent) + continue; + if (!is_transfer_unlocked(*i)) + continue; + if (f(*i)) + outputs.push_back(n); + } + return outputs; +} +//---------------------------------------------------------------------------------------------------- +std::vector wallet2::get_unspent_amounts_vector() +{ + std::set set; + for (const auto &td: m_transfers) + { + if (!td.m_spent) + set.insert(td.amount()); + } + std::vector vector; + vector.reserve(set.size()); + for (const auto &i: set) + { + vector.push_back(i); + } + return vector; +} +//---------------------------------------------------------------------------------------------------- +std::vector wallet2::select_available_unmixable_outputs() +{ + // request all outputs with at least 3 instances, so we can use mixin 2 with + epee::json_rpc::request req_t = AUTO_VAL_INIT(req_t); + epee::json_rpc::response resp_t = AUTO_VAL_INIT(resp_t); + m_daemon_rpc_mutex.lock(); + req_t.jsonrpc = "2.0"; + req_t.id = epee::serialization::storage_entry(0); + req_t.method = "get_output_histogram"; + req_t.params.amounts = get_unspent_amounts_vector(); + req_t.params.min_count = 3; + req_t.params.max_count = 0; + bool r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/json_rpc", req_t, resp_t, m_http_client); + m_daemon_rpc_mutex.unlock(); + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "select_available_unmixable_outputs"); + THROW_WALLET_EXCEPTION_IF(resp_t.result.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_histogram"); + THROW_WALLET_EXCEPTION_IF(resp_t.result.status != CORE_RPC_STATUS_OK, error::get_histogram_error, resp_t.result.status); + + std::set mixable; + for (const auto &i: resp_t.result.histogram) + { + mixable.insert(i.amount); + } + + return select_available_outputs([mixable](const transfer_details &td) { + const uint64_t amount = td.amount(); + if (mixable.find(amount) == mixable.end()) + return true; + return false; + }); +} +//---------------------------------------------------------------------------------------------------- +std::vector wallet2::create_unmixable_sweep_transactions() { // From hard fork 1, we don't consider small amounts to be dust anymore const bool hf1_rules = use_fork_rules(2); // first hard fork has version 2 tx_dust_policy dust_policy(hf1_rules ? 0 : ::config::DEFAULT_DUST_THRESHOLD); - size_t num_dust_outputs = 0; - for (transfer_container::const_iterator i = m_transfers.begin(); i != m_transfers.end(); ++i) + // may throw + std::vector unmixable_outputs = select_available_unmixable_outputs(); + size_t num_dust_outputs = unmixable_outputs.size(); + + if (num_dust_outputs == 0) { - const transfer_details& td = *i; - if (!td.m_spent && (td.amount() < dust_policy.dust_threshold || !is_valid_decomposed_amount(td.amount())) && is_transfer_unlocked(td)) - { - num_dust_outputs++; - } + return std::vector(); } // failsafe split attempt counter @@ -2679,13 +2757,13 @@ std::vector wallet2::create_dust_sweep_transactions() uint64_t needed_fee = 0; do { - transfer_dust(num_outputs_per_tx, (uint64_t)0 /* unlock_time */, 0, detail::digit_split_strategy, dust_policy, extra, tx, ptx); + transfer_from(unmixable_outputs, num_outputs_per_tx, (uint64_t)0 /* unlock_time */, 0, detail::digit_split_strategy, dust_policy, extra, tx, ptx); auto txBlob = t_serializable_object_to_blob(ptx.tx); needed_fee = calculate_fee(txBlob); // reroll the tx with the actual amount minus the fee // if there's not enough for the fee, it'll throw - transfer_dust(num_outputs_per_tx, (uint64_t)0 /* unlock_time */, needed_fee, detail::digit_split_strategy, dust_policy, extra, tx, ptx); + transfer_from(unmixable_outputs, num_outputs_per_tx, (uint64_t)0 /* unlock_time */, needed_fee, detail::digit_split_strategy, dust_policy, extra, tx, ptx); txBlob = t_serializable_object_to_blob(ptx.tx); needed_fee = calculate_fee(txBlob); } while (ptx.fee < needed_fee); diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index e7b00292..2b6cdab9 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -280,7 +280,7 @@ namespace tools void transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector& extra); void transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector& extra, cryptonote::transaction& tx, pending_tx& ptx); template - void transfer_dust(size_t num_outputs, uint64_t unlock_time, uint64_t needed_fee, T destination_split_strategy, const tx_dust_policy& dust_policy, const std::vector& extra, cryptonote::transaction& tx, pending_tx &ptx); + void transfer_from(const std::vector &outs, size_t num_outputs, uint64_t unlock_time, uint64_t needed_fee, T destination_split_strategy, const tx_dust_policy& dust_policy, const std::vector& extra, cryptonote::transaction& tx, pending_tx &ptx); template void transfer_selected(const std::vector& dsts, const std::list selected_transfers, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx); @@ -289,7 +289,7 @@ namespace tools void commit_tx(std::vector& ptx_vector); std::vector create_transactions(std::vector dsts, const size_t fake_outs_count, const uint64_t unlock_time, const uint64_t fee, const std::vector extra); std::vector create_transactions_2(std::vector dsts, const size_t fake_outs_count, const uint64_t unlock_time, const uint64_t fee_UNUSED, const std::vector extra); - std::vector create_dust_sweep_transactions(); + std::vector create_unmixable_sweep_transactions(); bool check_connection(); void get_transfers(wallet2::transfer_container& incoming_transfers) const; void get_payments(const crypto::hash& payment_id, std::list& payments, uint64_t min_height = 0) const; @@ -402,6 +402,9 @@ namespace tools void parse_block_round(const cryptonote::blobdata &blob, cryptonote::block &bl, crypto::hash &bl_id, bool &error) const; uint64_t get_upper_tranaction_size_limit(); void check_pending_txes(); + std::vector get_unspent_amounts_vector(); + std::vector select_available_outputs(std::function f); + std::vector select_available_unmixable_outputs(); cryptonote::account_base m_account; std::string m_daemon_address; diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index 6074e085..652b1949 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -76,6 +76,7 @@ namespace tools // daemon_busy // no_connection_to_daemon // is_key_image_spent_error + // get_histogram_error // wallet_files_doesnt_correspond // // * - class with protected ctor @@ -600,6 +601,14 @@ namespace tools } }; //---------------------------------------------------------------------------------------------------- + struct get_histogram_error : public wallet_rpc_error + { + explicit get_histogram_error(std::string&& loc, const std::string& request) + : wallet_rpc_error(std::move(loc), "failed to get output histogram", request) + { + } + }; + //---------------------------------------------------------------------------------------------------- struct wallet_files_doesnt_correspond : public wallet_logic_error { explicit wallet_files_doesnt_correspond(std::string&& loc, const std::string& keys_file, const std::string& wallet_file) diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 418de327..83e1f753 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -347,7 +347,7 @@ namespace tools try { - std::vector ptx_vector = m_wallet.create_dust_sweep_transactions(); + std::vector ptx_vector = m_wallet.create_unmixable_sweep_transactions(); m_wallet.commit_tx(ptx_vector); From 0be6e08dd0faf5f7e3492652f00b8904e7e8216d Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sat, 26 Mar 2016 23:22:57 +0000 Subject: [PATCH 44/47] wallet: do not leak owned amounts to the daemon unless --trusted-daemon This will be slower, though more private. New trusted_daemon parameter to the matching RPC call, false by default. --- src/simplewallet/simplewallet.cpp | 2 +- src/wallet/wallet2.cpp | 9 +++++---- src/wallet/wallet2.h | 4 ++-- src/wallet/wallet_rpc_server.cpp | 2 +- src/wallet/wallet_rpc_server_commands_defs.h | 2 ++ 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index dd166ede..04170df6 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -2221,7 +2221,7 @@ bool simple_wallet::sweep_unmixable(const std::vector &args_) try { // figure out what tx will be necessary - auto ptx_vector = m_wallet->create_unmixable_sweep_transactions(); + auto ptx_vector = m_wallet->create_unmixable_sweep_transactions(m_trusted_daemon); if (ptx_vector.empty()) { diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 7d308e61..3ec2265f 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -2688,7 +2688,7 @@ std::vector wallet2::get_unspent_amounts_vector() return vector; } //---------------------------------------------------------------------------------------------------- -std::vector wallet2::select_available_unmixable_outputs() +std::vector wallet2::select_available_unmixable_outputs(bool trusted_daemon) { // request all outputs with at least 3 instances, so we can use mixin 2 with epee::json_rpc::request req_t = AUTO_VAL_INIT(req_t); @@ -2697,7 +2697,8 @@ std::vector wallet2::select_available_unmixable_outputs() req_t.jsonrpc = "2.0"; req_t.id = epee::serialization::storage_entry(0); req_t.method = "get_output_histogram"; - req_t.params.amounts = get_unspent_amounts_vector(); + if (trusted_daemon) + req_t.params.amounts = get_unspent_amounts_vector(); req_t.params.min_count = 3; req_t.params.max_count = 0; bool r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/json_rpc", req_t, resp_t, m_http_client); @@ -2720,14 +2721,14 @@ std::vector wallet2::select_available_unmixable_outputs() }); } //---------------------------------------------------------------------------------------------------- -std::vector wallet2::create_unmixable_sweep_transactions() +std::vector wallet2::create_unmixable_sweep_transactions(bool trusted_daemon) { // From hard fork 1, we don't consider small amounts to be dust anymore const bool hf1_rules = use_fork_rules(2); // first hard fork has version 2 tx_dust_policy dust_policy(hf1_rules ? 0 : ::config::DEFAULT_DUST_THRESHOLD); // may throw - std::vector unmixable_outputs = select_available_unmixable_outputs(); + std::vector unmixable_outputs = select_available_unmixable_outputs(trusted_daemon); size_t num_dust_outputs = unmixable_outputs.size(); if (num_dust_outputs == 0) diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 2b6cdab9..566be59c 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -289,7 +289,7 @@ namespace tools void commit_tx(std::vector& ptx_vector); std::vector create_transactions(std::vector dsts, const size_t fake_outs_count, const uint64_t unlock_time, const uint64_t fee, const std::vector extra); std::vector create_transactions_2(std::vector dsts, const size_t fake_outs_count, const uint64_t unlock_time, const uint64_t fee_UNUSED, const std::vector extra); - std::vector create_unmixable_sweep_transactions(); + std::vector create_unmixable_sweep_transactions(bool trusted_daemon); bool check_connection(); void get_transfers(wallet2::transfer_container& incoming_transfers) const; void get_payments(const crypto::hash& payment_id, std::list& payments, uint64_t min_height = 0) const; @@ -404,7 +404,7 @@ namespace tools void check_pending_txes(); std::vector get_unspent_amounts_vector(); std::vector select_available_outputs(std::function f); - std::vector select_available_unmixable_outputs(); + std::vector select_available_unmixable_outputs(bool trusted_daemon); cryptonote::account_base m_account; std::string m_daemon_address; diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 83e1f753..d7d99c2a 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -347,7 +347,7 @@ namespace tools try { - std::vector ptx_vector = m_wallet.create_unmixable_sweep_transactions(); + std::vector ptx_vector = m_wallet.create_unmixable_sweep_transactions(req.trusted_daemon); m_wallet.commit_tx(ptx_vector); diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index 40d6fd8f..2c4e2640 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -178,9 +178,11 @@ namespace wallet_rpc struct request { bool get_tx_keys; + bool trusted_daemon; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(get_tx_keys) + KV_SERIALIZE(trusted_daemon) END_KV_SERIALIZE_MAP() }; From 25672d3f10e71f4db36d9497e2440f2ac26a6e01 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sat, 26 Mar 2016 23:32:45 +0000 Subject: [PATCH 45/47] wallet: pass std::function by const ref, not value Because we can. --- src/wallet/wallet2.cpp | 2 +- src/wallet/wallet2.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 3ec2265f..6fd77eea 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -2655,7 +2655,7 @@ uint64_t wallet2::get_upper_tranaction_size_limit() return ((full_reward_zone * 125) / 100) - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE; } //---------------------------------------------------------------------------------------------------- -std::vector wallet2::select_available_outputs(std::function f) +std::vector wallet2::select_available_outputs(const std::function &f) { std::vector outputs; size_t n = 0; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 566be59c..fc700a3d 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -403,7 +403,7 @@ namespace tools uint64_t get_upper_tranaction_size_limit(); void check_pending_txes(); std::vector get_unspent_amounts_vector(); - std::vector select_available_outputs(std::function f); + std::vector select_available_outputs(const std::function &f); std::vector select_available_unmixable_outputs(bool trusted_daemon); cryptonote::account_base m_account; From d5d46e6d6da569ec07169a12f59cbc6d120c35f2 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sat, 26 Mar 2016 23:44:04 +0000 Subject: [PATCH 46/47] tests: obligatory hardfork unit build fix after interface change --- tests/unit_tests/hardfork.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit_tests/hardfork.cpp b/tests/unit_tests/hardfork.cpp index c0ee5fff..cc3eba8e 100644 --- a/tests/unit_tests/hardfork.cpp +++ b/tests/unit_tests/hardfork.cpp @@ -108,7 +108,7 @@ public: virtual bool for_all_transactions(std::function) const { return true; } virtual bool for_all_outputs(std::function f) const { return true; } virtual bool is_read_only() const { return false; } - virtual std::map get_output_histogram() const { return std::map(); } + virtual std::map get_output_histogram(const std::vector &amounts) const { return std::map(); } virtual void add_block( const block& blk , const size_t& block_size From 878ab5d896c1a9cb5c50c36a0ded5da998c941d5 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Mon, 28 Mar 2016 16:46:19 +0100 Subject: [PATCH 47/47] wallet: fix --generate-from-keys saving as watch only --- src/wallet/wallet2.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 6fd77eea..36366502 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -1214,7 +1214,7 @@ void wallet2::generate(const std::string& wallet_, const std::string& password, m_account_public_address = account_public_address; m_watch_only = false; - bool r = store_keys(m_keys_file, password, true); + bool r = store_keys(m_keys_file, password, false); THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet));