From a122a555de1d24866d21a48d60bc2b9e47651529 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 28 Feb 2020 11:53:06 +0100 Subject: [PATCH] docs: adr-046 add bisection algorithm details (#4496) * docs: adr-046 add bisection algorithm details Closes #4329 * format fig. 1 title * docs: adr-046 we no longer download headers in TrustedHeader https://github.com/tendermint/tendermint/pull/4496#issuecomment-592446054 --- .../adr-046-light-client-implementation.md | 26 ++++++++++++++++-- docs/architecture/img/adr-046-fig1.png | Bin 0 -> 13276 bytes 2 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 docs/architecture/img/adr-046-fig1.png diff --git a/docs/architecture/adr-046-light-client-implementation.md b/docs/architecture/adr-046-light-client-implementation.md index dca1c6692..6058da8f8 100644 --- a/docs/architecture/adr-046-light-client-implementation.md +++ b/docs/architecture/adr-046-light-client-implementation.md @@ -3,6 +3,7 @@ ## Changelog * 13-02-2020: Initial draft * 26-02-2020: Cross-checking the first header +* 28-02-2020: Bisection algorithm details ## Context @@ -67,9 +68,13 @@ required to respond: 1. Note the very first header (`TrustOptions.Hash`) is also cross-checked with witnesses for additional security. Due to bisection algorithm nature, some headers might be skipped. If the light -client does not have a header for height `X` and `TrustedHeader(X)` or -`TrustedValidatorSet(X)` methods are called, it will download the header from -primary provider and perform a backwards verification. +client does not have a header for height `X` and `VerifyHeaderAtHeight(X)` or +`VerifyHeader(H#X)` methods are called, it will perform a backwards +verification from the latest header back to the header at height `X`. + +`TrustedHeader`, `TrustedValidatorSet` only communicate with the trusted store. +If some header is not there, an error will be returned indicating that +verification is required. ```go type Provider interface { @@ -127,6 +132,21 @@ cases of adjacent and non-adjacent headers. In the former case, it compares the hashes directly (2/3+ signed transition). Otherwise, it verifies 1/3+ (`trustLevel`) of trusted validators are still present in new validators. +### Bisection algorithm details + +Non-recursive bisection algorithm was implemented despite the spec containing +the recursive version. There are two major reasons: + +1) Constant memory consumption => no risk of getting OOM (Out-Of-Memory) exceptions; +2) Faster finality (see Fig. 1). + +_Fig. 1: Differences between recursive and non-recursive bisections_ + +![Fig. 1](./img/adr-046-fig1.png) + +Specification of the non-recursive bisection can be found +[here](https://github.com/tendermint/spec/blob/zm_non-recursive-verification/spec/consensus/light-client/non-recursive-verification.md). + ## Status Accepted. diff --git a/docs/architecture/img/adr-046-fig1.png b/docs/architecture/img/adr-046-fig1.png new file mode 100644 index 0000000000000000000000000000000000000000..d68712e8a1a463647493abd5ce6787a5a8dd8b59 GIT binary patch literal 13276 zcmb`uWmr{D6z~gpKn{&4(j2GU5o8V^+N zE~d`bj;_`Y_7J#SV-p8AS0PGDctij5&%b`UT3h_Dmh4^rQwva#4gQ9WofXRVKV<_| z1>sNmm7J~3ftKO&h1muFmi#}S{nw5l8@%!Vtuy~L{reP9RTxc>?SBrNFq(Wfoi+l( zD|T5)F?A2bgKYP5(%$y|ZSK4Ik}I-lZ|q=N5?gt8HcyZ^^oEKTE$%f=&l51E%5y~A zR|A{B2&ulMeG@@3&gZ560G@LdKO&WvKl-uMnAMoe-U(aWa2d;uiSD zQG1qsGkW+lMSMLg3pM-${KAM6-AAaJ{Y42uf?uoP*UIX+lA<8sdPM;d8w3?1;UePV zAmYa5m0|s>VkRycm;jbWrumebdg}LP1NsgAt zfz0=O(j+Tb>Y8^f{yjSRS#ox5^{sB_?cNt(4;*P*elT%^G0|(ePvELf*OXJrM-5sH zl+Np3w#7Q@>5rZ?WK1qoD$#V?avKnzN_I=nJd9|%UDOR|r%oi$Z!)0Y&S#@Mt$Z$9 zO`d04&BP9ElM1=QODsBd1Ah+PJ6#H;qfe0>zpKbJ%O=wD32Tl!FFN(}#Z2Z+@R9&M zv;T83do}BnbHKCg0h|75RT#ZUm+PZSUEfdUeGTo4A=MR>7S!W0AA0US#<%T}h-;lD z;@=@~y&BZs`LovYuD*EfcE5_vz=4=9%Tb&?YbuB5M)1%1^is%6gPmjd39+Zm+qwSB z2h}hv;iU!J+Gq!is^%>s_gQ`S8^>^T_q|W0CGSAS%`1L)H_(B*mxexT$h5TC*Uk5* z!%NNEIr~*%Pj<0Lqug(l$5*TRD(Sn2E621L;t0x z7h9PQUVYUbTj`dI+3w5MDpo8=loQ`T^O`(%oaLM?TUPRpSIgd2hE$N^!`;%{%P<^l zA~c(*G?Bd24*nRF728|RQ|2bqSfLrAo6W?IQk%7@37xF><@2|7(~2(Mhs~wAoH9F= zt%v+FR940luc=TL>{|~QyCw*9bTs(1osHIeA`XAoj$g_|Y%BTSCSv0u#(H%4Aby7me>>`}sZIt$2$#l8N{UhHU~% zA>)JS9`46q){!g?)uY1uRhlcJ&RS_@dj(ZVlj1YEYq++@f+&34!Pu4c6@v%mUT zh2siFX6D=pT`eYlZj>7*!M*Jld03M%&=WE44mR;Q>U`{Qz+s7TB&7oNen4qL8d+9OZjxI4^h;1il%*0 zgfTRI(S#g*-b^g+*T$^Lc!;-hKu>XR84d*x9TyM zZ)0)lP|Ek~)YR0Tbe;dBev~jB({ESW2VLEH#w`5{M{k=j;+dJ|y$Cd!gmH)Fcv2e; zUfqY|<4fGi3K}w5U7J?oW$pqWqZctTucsvEO-McM9rnX74%552qYHo2!?b26`A6L9 zFk>$cbrL&qNq7CkJUs3OikPRq#~Odydm5pnD^kG097`FehICS4`9d^Ayh?YfzWQVA zTczTQ&UdK%lt}e7qT2zAlI6NT*9Zvrm+pvnLwNMVo&?0Yxvul$)hKT83nYvgp1$1Q zv30HMbLYJ-KB*&(8Ec5IpQI~W^ z?C+k`s?#qCkpZP#F}qdL>4&~-shZM;Zlw1=UpW{Me1}Z`cO!%*0ozlk zsx>1J^#E%^2I&)W@fUX~(!?)iaAN_MOcs47wwsd2`A(ZrA6^DW3Noset%B&c%}}3L z1lIHWuUssG(jaDNVjc#{+Yko0Y(Bv1gU0zx!|+;A0+4EFnCjurU(1`ledbG%YQ@-( z?IG7;x2;|pO_j^PCY|C+rg(1eT0K0kp3NpwDhRdf+k1|GeF!{o@spix^u4@W(4K4` zaicrwX~_|9q$0OoeCBtPrySm4CZ&qCBcCVAwWhjGNY`-6 zCw`K4*)S^ejFk5*&LNv@GZeqA1^kCwP?{w&1m%-H!{RqneM+5%y%MmjyLo1Q6iH;b zdQ5+hovYJ4olQ9ZzHalTSlLw%K7x{vD-dT`!W|<70X`COm_T5`P4eO+Jh0fz0iutq zx1tDt%Mk-h$d;G!IecLZ2nBps@?`PwbUw@nf199#dd;4N`q3s!;0~k#78&IAL#9Jg zkXT)seVDySY1{91psX*t{-E;i(PxFDcb5Hz$jatptnSIl#LUb!TfVAR1w%(x%ajQD z!pBD|EhfRlSo>c7+!gf)u*fp+^pH%gs*g}zeKD1+-~Ft#ZUS})AWhZw-J`6Ij;hZ! zQ;IjA|Fa}#bys;1CHT00N_;3)H(Ty%-P%lbQvsxkH{eB0#y0&a@iRJH6H(A95K1MK zPH7mxXNM6Eu;fv2D+BT2-artD$~HgMCC?(_(idH4mmo?NMM~w-g4W4>!WCjbG$~2< zzj8z=df8OA$x;wF1p{UpHmw_@vOzSad1#7%g}(uW&n9Sgz=cChkv^?R4Bj^XiaIM? z{nz@QBw+3D4A~zwKzoXLdX^tOlf^%>y}Nx$kr*q~+Y-3ZJIMKv$fWA?Gl|WudeyS63rgT`F6wM|C4UwljXyb@7;~bBW~MR{FetZZ1O5% zgG-OdeUfw^-^!l)x`w7TDwHdapB{<9SmO|(3fbGRWLc5^+5t^4>%qqlsOpe79c9Mg zLf8c)u8pa|*6Aj7Ifa$czm06rc7|6jq)q!Q-!)RS0(D^XEz=ZXpW05e|2{9w>O~TK z&Z+~Nefn6+u2iWt^CJyREdGgTruj425!GBt8#h1nm!_WJ&G45}94aWQ(LL9p{Z0w*ze;ytmS|e=25?GPy=>VmKTzda{ zMTvwD@&*!LwUQbB_E8Av<3T!;^xy6$fC~IczJB>KtnYnLfB0~Bwlu5sWnf(3m(Ee(WF1HDY zVn;@^1!miUaNB+`f#yZ?E1fnVIanO`e{i`1;-X0wt?In|)50FOlSve<8?>ovzjSU~ zdaqj(uiN3%oXKsMQOfPKF4_1)L$`Tm#>#hy?jS*rO}AmB{qf=M@T8yI-DPXUDOUKl z>~1}hZE;NC=r?Qg<}>#Jim;}!v9W|NS((DVEdu-HwQb+jXq5y{f~6zBC=*>H?$cY> z@j9lUQILb?6UYw9O~1D6SGKmeulUZkU(D#nka6++><&R+N>mcwjjd?EoVSNz^0>mn zu&=gFx+n~qAuE~RQTx>*(9k|T73ejl5w5}>=H8`cuJ4wC*DlQ!5X%ejD*`!#^TENv zdPYVG>i}_pl^7wPa}lw?`|@0lbP<2Q$?v_G{n1yeLD(&9o%d4|%r@mUp~T#_Y4+dp zxGA1<-osNcas>!=t>yTo$p~LC3Wb%8jf=oRU7`RyrMp~kIJyclx(8zQj$No&hN(g1 zE2H41Un#mR^)othzcCdzGhTgj^#J-Vu!a73n%e1+$XX~pWDF!aR|U@qv)Up=S;3M@ zL&Vuqk@z>6_CrYw9hwTUVUO(5{28!>sV^hHZgVk$yDU0F@uPeZHw=K%6ZwBkh4)_%NI*=D4IXntQ%%Wf>~Y<+3Hu1 z0-54nM|A;`1V_3Yj{<>*>y4!tfx{gc=8Tow96|H*Nf~m_lO`bZ`SXMp2-;&dcw<`R zg+d6k3z#aKx=G*98F!R-U`y!&?ztGGA*YNQoEekzN{MF45r+8yrjcfwsa|_(+yw%n zaMWR7F&_ano;IHJ5vOL`Ze>`wgoE1ka;6GY#I4fJ{2y+%m%_;X=1Su!9cat;D;jea z96JAy#2S+O9Cz7g+BKYDiqKYRl}?&4Gi-1FK{2f($VIw&9+0nORFB*{VKifGznbHH zc#=3G&uaRr+JMZ)%Zrqhbom~hlr~9V3w@8(k)~slo+9qA3%cfPp<0&xH5+hgFR; znyWPERLbaEph;6&R{uy)#5OQNuc+|7g4;M$_I5(5YRCBP7wj4@y1#Dcc;AxUq7d7z z=4y0nL=Q|L%);1*C`Fbwq~%r9^xLHTC(X26oWEacph zhB~o34-a8TwGpvYa`w*H)M>?fedd+Qn?yCErB7ie(j?NM<*89x$-P%cZ};$u%;y`q zpDv?>hNEJ#9o0fOnKNy_Mu?25nVzr9L=mi!7n-x8)EFCY!Y()$%jJlziF9)UO(^V` z!;yu&r+T`DsjyReBVT&Q1KB$EeRvSqEjSn@bMonIJa#u1q8RU-g#wX>YepRh65r~F zCyO0-hx^;F2%??rtFxpdFGyLiG@2RpaINr>($PPfXGy0T2pv@l0*S1FmIofm89_8} zrC!j86Ojde57MOQYb}EMiCW8O1oojdQbhdA6Vrmg>a;kVTrhGW*7BjHnb_^zHi<(? zX43>T(GM@?jWy=rF{W6$nK^Sv-&P z_xW*p-Ik>k4*r(T&L7i)RiC*H?5cV@B8RLB*akQMYF0#2HkPPh);3Y)x~4wSBB75K1^X=Q;D6gI}D4;03#9jMZ^1efdwz z^RDjjPq?5W)nzW7@)fHZ)%2YvR17I3u9r!w;pYYH0_*|fcf{eC1%a#3Kx5V*K0*#9 z_VIvKXlejXns%h4HLnvbhTnFp|IBJ_UlM-C^oUuXTpF-Q(*46i<54V_IjSfa@7ur@ z+`)Z4^ihR`4O}|5p{lAxQh9vf@A}G&#u8RIu~U3lF%+~}mZRbQk~CRo5Iy2i=saK( z4u%*=^*Hw#tNe8>l*pJoV2$Jb@H>df$SeVf_hx!f z6Z=F$M+CrACLXraOby4D09FiI^%CggB5CwJ4TWKs|8A;)xhA!a8D`z1pULtXX9b&E z8!Ozh_}mjcMQ5_m*P-29(5rV{54#p$PQkxU-)GA%HpU^ld1Vkh_!`Y}t+oU!4gT!c z*oF42l3e5rCGqUW8$b{4P<*+&YU`0Z`&~l{WqpS#Icx7VkZ$!p;Dl=Q?_troLNp zY$ESm7}M<32LoWEe9+wuh6M@$@rGIi=d4yb4vL71#D-mDkkZ_*+{7mo|EG(uViFS!m{L*_%@-Z=JO3wc?&$Z(U(8I_}umKuRa2T-84TgNC< zj~M7PLH>hELWO*C%R`@&+DcLIu(V~;=XZ)dH1`^qU>GgY4G*$Kp2v~qPK6V zYO<2}v5HtP7Qm{Zx^=9Xm>D9=0447(>0*iM&o;>Ny4g z5!3WXgX=)r!T>1N!WQvmy>pKn5CpxPjz^7nG^>ji)a1$j9|W-Ct4dUX2Jto zB31)VK8FNhr2floo_+$Cvp@~B8FQL_8A@@cE1foZ#avE)b4u5^K`MVEHM{w>1r zARmq^qbg%u(foY{@Eur4^OtGc_B$vUpzPmI^kmAjRr^Xy5IA!EVF#5T4YBl6(m%0Fmub^916`qp+->P-FNi}(|!IA>V3Z6@&4Ri55u(C|Lt8pWN=F3%JbE9lu=kih^x&v*v2M8 zW@={sFtjmRiz|;&Mw;51(yk^(u(8pt$LmVoII~aXeUfqcz7^>f|9oh_mwUzFN_fm^ zCq$niZtZ|7@#||fkfCt_{(y6%0d^c40|vz)xk7aJrdE&tkmHzT%@eETyUEh9Xl%#aS!Yt%Zv}_-T>& zD688nKe-6)zG|EBO+G}r!4Dw9ZQAN28y76OwwKvbVG@O#3UE09fO(blM$Hp`5Ej7y z>@^F$emm1%nw}JJascp(i87EX`kO{jvC6RO?q5nvk7S**7ln2#p!5luMq%}o>0dby zkjEQ)XcovETE-|3mpwoM2hPLZDHktobI;^jIu2@AR>nRanpX-$(9K*t zZy~*Ty^(S}{4_-ToQ5gEc}H=GYDQ&DPV;U~ZAXEhgkid_ z&L*CT+P?nLkdkPp+%@s%NK2B_s)J4N6QU zl0^Q^I=yFr3SZ}vt^ZCGINbCuK~sTi>MRQ|yI~W}63s9;+Z(J@yl#;Oy_17HbcM~i z$|{@44jLYUpR4*X(Op%8^47q04Kzxv=D(@w37@=mgpYTeCg6VBQrb@cPGCY{u*@*j zyWq4%)Bs%MSHoVFm*jv;fAx&uj4@b^>+- zK>UIiGf$<)k~>*(9*yUw45@b5IE4Q&2n#NtKW>J6*GirD&u8kI$0Kg564||Xr(5gy ze@D<+@0BN+ix9*5-t!;hE0Y$bt$OF``WH${LNWk#iIN2Yzy=sv9UenU%WT7EgD9u~ zFnWQ?U?KcXxF{E($p5EmpH`9{r`aqu(3Q4P0^l@|4>h0h_202h`5;aI^HkmH??~wa z4J_ifzhZ?i;h%s_%!bwS|BjSBeD>*{4>iGiVkQUJgheTY7GA6wzxR{V`b3>U}pbs4c&qq?IR5cU&6hNE!Zmf|F69N+3`3!Bz=8- zINXZ4FlzozK1Wm@Ot1HEn*9bd8??>+*%o@`i>}feW zpHUIdSf2;gYq^}aS^9Iea`b+|jt3r>@_3!^+)fQ^|5ir}tpq5NeNQ-WYE;2u2};rj zbXP?R5$|HrZ~5+f-1YSEQ)!Mf0K6K#R|B5j`?FiWA&MD!02z{(CV(ms)J}GbLt&0IC07KWpG;W+Jrn zYdE#ldb;F>Qz47jX(EYjSe{2mYdD3)1%9|&g%c$Vd~~3~#yrQ%_8*Dl^}=t0gT=cL zkzwzn_HEXtRV4rWU_3=fU5EC1VqqVTLUWZ}z`GJa5b<#5(fCtbw+Q1WXeUCoGUYkL zk@2H2c@`%~Jyrvj6P&&wK5b%YT%5`rd@3RD( zJ3NMAyO(A;e;h}+d(Zl6N6)kHsTlSKkhVBu3t#880`%-&jbO#+Ec!@anK^u5b1?Cb z;0r+~`SL$|nw`!{!Z&8QXgzqucL$BR65svrH^}9*oZn_9{mg%Z;4-6W==)0`hOPZP z!TWbjzfV|VJXpygnR`Y4tLfyNQ$K0DbtJbZFze5|1HWN3=vEjKKb#J;EsdmeEPel^ z<{R{SBbn{mNiv*ke1}y^R-- z?FEP;^e1cU@Ab;2bu>fvP)uSCd3*`IxyQ5I$Ma;CO_6us=^Fto-2%p=ZDKd=Ys!8< zKUh)IN5mLJDP$Key76rF!P4YU?YQt6RqRUxkFH46#NSc!y#0U3(7*@CCIJyylLOZ$ z8%x;!*AfmapWW_Sbppkmj5f9z0vXgwuIbRK7t*O*0Gie=u$5-&1Cxd|0r}PTr8V^& zq#GTd*%4dlJT6z+|MI70JApEL_fUW%R9*^05g18Bv1asInPG9f(TNl_#Sw+TiPj@0{#j`q=HtUvCn?pl;1Fx%ta?0) zn>ArWhxUtm0h(}B+6uddpM7@KT_}}>g@vr?x^`j|^@2|4zy~o+fXK1&FL5`}Y1CnW z^&To1o28{%dk|rqMTvcqtF*ykpu){*gL>1&9tr)&Itxu8)W8bQCg!ov5?BwXB5`?3 zY*!b5Y7t}@*)KGxSANl0j z^Xf$7t+yVYTcO1E$1o0QHjB_M?C!6CgY!oHKdC2nx^y&Jch&&#smwamn4(TS4*rpg zF93>t<#nL!V#Q%yi~Rs|Js^JsZlPS4OV6&=5F@bh2++HqDNbqY1vd}UW_r}Dt8BDi zEiWidIYpgDL?@mP1A9*oUfx;1@0-%h%YezJDai6GZ9FFUD2Ln1l1+5GwkB(93@nGb z^GfB|i;dAUeB{-61gg(p)VBj@auGNOXd}d^#c;Hm(3-}xq({jTx}1ZNdhO(QYzy*x zvOf#y{STPVy&7Bre9rG}$sKK|dheEO$Qm7%QJ^8dOLbo0F(*Er1uC;h$hQGI2o=h@ z=)m@P?$NgSVbcbdq4t6HIn-}TR2%9n_Klz&O4%(h%TLhmSZM&Wkoz)2IbjRJ!8(+f zN71cTKc>c~?7pK&>u6?!U3T&NZob4dHp%A0Wkwr`gwWO{romvds6^397J(*&u;sAy zr+DK4gFBRA#yCLG-$Rgrh0ITtop1*JL?E1*RFg5Y`mn!7U>;A-h}w>h&j?Q3i$cP| zmRAf%+h&d}MiMs{qyK~vg@%L62ri+MM&Ufv=ARlV3qSv}Z}@mRUZxhoXK!6wAAugl z5J`T&`qW^x>{}9YJESK^5#a^jew{_SLkJC0J>=U)tDv#@0iqp}9Vmrp9%&wBOPs(H zLYvhe*yXn9zUZ>{MFW|EU_w z`8s=V@D}t;Nh0JMgp#wZZ7^bDZX#wwutJh8Clu400wyF{JCV8Ng%;{&4U<7s&0{W# z4nau?q*Y-4&2ef}gC&{-SDaZ z>YgCXC{&1_gJFvGl!L_cYpK*Kc}Z^F#_AxRX&Mc~lT*=o&yUznUFu;wYABm;6+xlT z5D!TYaSz=<0e9gcG9K(UR0M)8atlTU0sfhH^s{}0kz_e@58_lCy zin{ud2maXggc-Rz?kL?d#|agET-B7U5Vt=BGlOe@gy)#kJ{(278_8BZlS^D;Jfkl* z#P>d}48lxH-D4>(78^KZ+>UX{7sfK(6EY-rtIZtUKx>yW1I^~FJAXysq#`y~k}B4l zkF?fE7SlF&_B-k`{Sz`NNm~LxsyN@XdlKW(y2l(4PL2_{4eZJ6Nu(WV zi~5EL68v>nKg3Z{&iM?Ah{F^D!=0Z{yt3#nFD=u#tslci++%ekq{uS}qr#l0965{P zvj29kdL@?!&=ww<$z|^BCnMq{{>c^$e>(e}aUc283hp|<3!ICq*nVhq1t zcj+MKAr^icHIO|D_$Z1U7>ezG4k?pVM?+f}(FwMRCJV8A(Y0PnhG-+to6k2YZ;@ud zW2YudU_Ei1?iu}2%sf8*Ga@hsy)AK! z2{N6587UNVsE8?dqrte7gC|jvgSlRW^o=rNwmPVvUm#Q_SkAKky^O*$3O;4MubMUjjgx;cukT}hT_gQg2 zX56LlODyHbq+3$Z!(q zL-2L!T!NXyLnDx>tQ$W0OVo3K69bw;UnT{ZDb8ZCBHEh;XOmI#2vfqkqsPr?O@ekN zAZZi9Jl-CKVGO@Q8+(6GMrjdDLO8h#*3zP_a479vM<;`+iA^(Km9917hD0FV+3IE+ z2aEX{tCQ$ZmKVOxuI9&^p&BmEwv4XjDi9MYzzc*xo<(s=(`CPq4Dym%s^JeNCnj~N z&=UlbEJ3E5C~DCsqHKw>%0%e8(^_d05Pnq5=#Lk#D#Rcejtd3#D1lr;l9`3Zl0k$> zH8pg5R?m5rB)Y`0ze^Y4=gd5z6MZti^Fvg-b9&Ew~?5u z2f+uF5ETq`p&3WAXp}*(uNG*=EBDo-;u83M#3hzxwrU#OGS((t9qA==CCs?UpujPt zE&7q1nSfRPd$pJ-daXCZ(C65uFTZlRaF`nf8e_XE=cJYHNCvxPhokx7V9lhdHXGO< z0~stzZjH%Km?&>SKD)RX!Ow};$!M6g&oa%pYGwSRpJ67kSUf=E^WtAQi5Xe>Xjvs` zEuejgnAB`4fY!?;_M`gZ_I|U=u$jsrQ$}1;<`*Co_GMjM7);7eu=rw7Vr`;c4i}*#o zxP#WK&{!}?22LWQXCrY)t%W&@LT!_}>ia07WESe-U(T>N^ri$NM87~1mk9l)MP=#2 zd4GzebO8;MaQG?B$!YtC%ZfS(Wq^pwCh9l;0*}ab=_E(L)1}?06OoS|J;=6pHI@tM zdF9V7emvR+YZC2+hU^XA_5Kd7ylV>5&GgC@WiaSfXf@)wMRpa%sM%oyE&aFtIFpO5Y@rg09OLk%A4uXeV%F*ndee zkJy?C`4g=;f)hT9C=AX;2$~?>HNo$mE*rXKGA<6k+nd=XaN6zII>cTl9l2{f&UK) CA6?b} literal 0 HcmV?d00001