From 01ac45c90adbe33396f82ff0341250ec0c500ce5 Mon Sep 17 00:00:00 2001 From: Aarsh Shah Date: Wed, 7 Feb 2024 12:17:46 +0400 Subject: [PATCH] Tests for builtin actor events API --- api/docgen/docgen.go | 14 +- build/openrpc/full.json.gz | Bin 35796 -> 35818 bytes build/openrpc/gateway.json.gz | Bin 12137 -> 12153 bytes chain/types/actor_event.go | 39 ++++-- chain/types/actor_event_test.go | 138 ++++++++++++++++++ chain/types/ethtypes/eth_types.go | 2 +- chain/types/event.go | 2 +- cmd/lotus-shed/indexes.go | 14 +- documentation/en/api-v1-unstable-methods.md | 16 +-- documentation/en/default-lotus-config.toml | 8 +- itests/actor_events_filter_test.go | 94 ------------- itests/direct_data_onboard_test.go | 146 ++++++++++++++++---- itests/kit/node_opts.go | 3 + node/config/doc_gen.go | 10 +- node/config/types.go | 7 +- node/impl/full/actor_event.go | 59 +++++++- node/impl/full/eth.go | 116 ++++++++-------- node/impl/full/eth_test.go | 76 ++++++++++ node/modules/actorevent.go | 22 ++- 19 files changed, 519 insertions(+), 247 deletions(-) create mode 100644 chain/types/actor_event_test.go delete mode 100644 itests/actor_events_filter_test.go diff --git a/api/docgen/docgen.go b/api/docgen/docgen.go index 76e6db564..4d485c442 100644 --- a/api/docgen/docgen.go +++ b/api/docgen/docgen.go @@ -417,7 +417,7 @@ func init() { addExample(&types.ActorEventBlock{ Codec: 0x51, - Value: []byte("data"), + Value: []byte("ddata"), }) addExample(&types.ActorEventFilter{ @@ -426,12 +426,12 @@ func init() { "abc": { { Codec: 0x51, - Value: []byte("data"), + Value: []byte("ddata"), }, }, }, - MinEpoch: 2301220, - MaxEpoch: 2301220, + FromEpoch: "earliest", + ToEpoch: "latest", }) addExample(&types.SubActorEventFilter{ @@ -441,12 +441,12 @@ func init() { "abc": { { Codec: 0x51, - Value: []byte("data"), + Value: []byte("ddata"), }, }, }, - MinEpoch: 2301220, - MaxEpoch: 2301220, + FromEpoch: "earliest", + ToEpoch: "latest", }, Prefill: true, }) diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index 663643bf7a19e4e6a27434e8b7497e39d095cf5d..176f299f2e92360449499af4fa8feb1a43e340f8 100644 GIT binary patch literal 35818 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 literal 35796 zcma%?Q;=ps*QHBcw$)|Zwr$(CZC7>Kwyn2}F59+kd;0q({+PL$h{=l+nR$02GWR)a zKWpJfLjwJG{9JZ>I&ZW!LcFUgc=>0Olz5noN$;zd>3K@;b8Gt8&Kz!|oa73SkQmAU z^MIY2D!pCT)jQ~eDJ9cxq&r&;6E(0167J&zn-+xY{k`TdUTPKl7)UFJpNCIdqHDc--@7^Yna>-yw*h&)ASZ?A z+?RymMvaaf!Rfdji|t0);HM%kb2se~oWRfRR#zi&_{v_AWQ2}=FyMOKFuXU+u5+9S zLgBq&q_$nR5Pbz9McO@%<=-F3UFpIu$P#LJ5LG#&vSSV#}iSy|B!`j1@>NIqphymvl04kytm{eAroC*N@T z&;AMN`-{ZS*Sn&Xn+6BI@P1wUI(4yZs{~-N6UZ3C0eHkb+H_&Y44_!cQQ$m6hS&?; zM7`dV0ifxnkg&j4p@Ys9K2r@pkS{Ht{+lno0F6_}2E@k^8hp1sW;JwaF%mxxW|r_m zR~ML*p76zvK;tq1fD`xYoi1X}s8tU1yePm}ao^njAmx?aMOTvPao*k&vCuDW=PgK! z>l=o%>*YfI@lgBm^n;$U?I(moz)odM)Gq*cbSMex5%XR3zIsO(aQ^XIQCunM_nY^m z`5q>ig&Zdc9+`T#A3bc>wx0cP`^>Z7hb>jK4TsD3hRDsI6+eMry1kx8d0%YLr>_7` z6Wm|Dg7`nj5RcE-JIW$#rH?;vZS7BH(lHfA;lHQjol|I1+_Y&{B+l@Xv@4v*kF+Ru zq~O@46Dl*e63P3-16Ofj1wix!+{Cjr1)$*8q#1-~514l&umh0WEpg|D?PG*;cxx&q()( z;`+n>E!sP5Z`1D^_`9pTcXz7c4bMbIi*qxicO_SvAS;Go5j`64@ple-Q(=q(7|Jq>Gb%IQSH zy}G@N&@E{H!RY>q!{453|5@?i6Le3STLz2!+nK{4qCId?lN)a_r>43;=g_1zee3A# zU9UWV6>C3a#vN=DRk*bjE1t}vGDJJL+4u+MGLQJ1yW;}XU*p~;+3qDi5h|B%pPrP( z<*()V2-z@^1xEAoZ7x%xM?je}D#Ms_JPpt(m;fm+{n+03nzdN|j9D@$2E^#z_U~QP zwsc)loQyrrX9EJfGOlxdWS0wqyqCae9}%5?3<_WsKiTCp-CE`sB@7!PS#$ zf63zrMh>)%yf%UH`v4_6&+zISLZ%~T2C+|YLeiIvINW}7oj~YKX(B792;Mov(Exk} zb{8<(G+s;&W*@ljl?9-V(?*OW;$zVJh6l7zEE5{AO}X_Mhw()D3qoG z>UIOz9O}L^(hQ;?Of}oAdXFhRRe zoWS?kRdfEG$yNLYEQHbJQ!95aXvu)hUUzbUiS_%+v&CWT>2kzRIP4eUv%S4}e+zS` zShxO8?+bM^#MI?FCCpec+*L7aUb7+++2LDh0mvI`AMWpvBq-?VyB!Itq_6E0hwl_{ zbqa&`{>XL9JeIhKh{d=W7K|}oJ`9;d83v@M{gpz?JLXF3f*A>+m*HeD$i(eBWN0hs zk~2!`R6N(9ISCGYX`NE))1BV;p>}PZiW)at!Ksm{tGz62g$F#xioRB1CdI4J@#$#w zDBiJo{9+z03yNrL`Vmr*(TT8K647fAT8eA##(*KQ+T_P+YN0JCfhwq)3^Em+o={IZ zS|cK9KEt4(e`SDa6*sok>udX$TW$R1*G!Xp8E{F`w%(0@B>U^iTmK~C53BIc*Ni{3 zoYy6x_+#zF=cVt^VSHG~%k%a#hAN-HY{6mrEs(X)w1vdx_V#l94tEf4>hi}nMo#v; zbM40Wb5q*;Q|V5U|L;x5ISWtFG6qEHh6#k4%+IJ>mtk-3Zo#^pk9>a{cs7VE#Ym0R z$hC8@_tUVJHd&ZOyG;3sJ3B3&SC1b49B>QhcGMmWr=&$+mj;rP2uF^WrTh5kPh5G~ zs@|eN3b}mp)&d(L*$rTm)89h(NvwmMZ-y-8l@woVS;QC<{KM&eyVvO)iz#5w!|?^b zIkmAPE#Qeth4?H2H20Z<{^owH4d70#`6|`DcyQl8fntK)%E6Pjz~T)&V!WDy7NXd# zC*Fpz1Bm$m;WF`VgCMfwW{;sQKuh*i~YUwTMVLTSF&;Z7_Dby0xulancSGeM)h!0+S*s z5D9=li_>FoM9udK*}!l=VDI?)KJj5aUZFkR$M2_3aZ9Ri*jFF=FoD@0dB=uz`aiz= zfgUk`@tMNL@siZs4;=%S8GzT#kviUq{H_OgF(>Pp>)OjQak^JJ7(UU!x`k*O@!%*fjCh3TOSQ_hxF^{ z6rHm`Bh?AKR|oedFdBjUWOw0iD4bq=_j4ePpARpPK9B#Ko`VD2KN}xE&|QDJ@8cjk zI%(#nzwF@Q;p=7OWcQGH72>39B%J(44!*yCgg(4C8a=KjU`C%#q+6y5Ltg~uY&{(R zhl7hxX-X$MI~QMHCy^gB3LqrL*2Jd(A{HyvDFGA?4(yaj>8SX4ba-3+`zUf<@!ZpP zuO;&9u+JFE6-`CUY+U7kUh36&;38M^8`>@MJB9#(6fXmui zbsF7Gin+b@UExKVxImB8M|S2Y>Xug7@I}a9FDT@v9CX5johBwwdVHyrjCd-9g4dcz zM`=qvl1CojO}w8dcy|aGS_pY)xhwb!44-0y7Tw#9o(AzEI{U_V*EuS9O#GmOAH&Sx8aB}fGs8LcyLNLkd!e1Kl=mw@~?r8MVdRYuj<_4*DW1EMJsvb~I%{DEm5&&>X0Wg}O%j9&9N9iy#WPnwNYTo)e; ztPx!`(mMa@KB1V_1kPQx)Nr{x0qOwCj&twm87mT9;gp=JkgQ3|?rFru1*`er>PGMd zxus;2#%M07x66OEVeHk(PuMP)REDy-V#BrN zrbVaHN876fHC9;CycE@2lc%a zf|7+6Rp?BM9-l!E?p+fFj+#qH2=ZwYu@)i6;3K*qn8&A61^TGbgC#v{Me%W#Qji`H z6^xiSl%ssApSu8ehynk=z4y374B{m!dcz`y=%kQ3M76o#w2=szkt(LfEAR;m;+H#! zjR`!PyrhAZ`*ZyH0sC>6EPen6^6}T_`R+Ys~AdjD||{nGby zE7ks%5}sKa0OIeVyw|@C{z~&DfVc~~!7u*a_J8{d=f!OpgI%$orvmf&i#$H)bzC#Y zqgOm6ae4es{@A?TI!jv9jTtKpBc&snh4+^q^p0ICezWc`+MEhd<@xc*`5#^()iJpl zexpo`3zv?UX6f=&RW!}-{f2cD07CzHwg1&P*7$Oe38j_W3pwsDudvhu+KSZ5Raji( z9G%Uw>1q%Qz#&h?wR&GjV=!~@rf3I`R9(8%GOdAgb_S8FL;t0?o(*3V>#+}!{vD=4LCv7361SdN zu#6#)?y!*B#7G`G{*ET1Tcz2{ZY>HK9_s@lF3-95uS;&$YO*fwQj4V+?kxfmp#Hvr zRCR4VThFJ1{iRM&&_`|}`UeuC3h6Jp2?U6Fuj zw~k=T%u22v!gpz$m%`Vy43C0r6tt20f}J=6*WMZ!>JNdpkAc()ZIv42#QP(Rye3ih z^Rb|u%>sc-<(>{J{aRKn&wkImKJ#F8-M7^c=<7F6SS8f}}YzjEQ}ywxWVX z7NM{)mV5bo27A8*=bYUb^I3;2{Yj8Pvqfib@2r78ioNbpATqj_BGk_)-Kl;Ev~(j; zm8o0CDK5&-Y@$muwfpQ>Pb&EA?hvlP++~6iT45KXm8zXuJuGWWjRxeMT!rTSwQ9fR zEOrUNak*X!x*CNp&BMKF*C&yyWDOa@lu7WsNB63Y!u1n9qJz<{Zavs!?RgSAZ@&zi zHD(aQ(?_K-y}a$UZ7t_C)>@}nZ0R$#&*boy-^lX9x>~TS&eg6=OD-#-0qAz-Yf9`N zoJ3Z0VQRYX1uBop+m*}ceIW~vQ%*T{7K zzF!ZxdscT80O}X^6xdeR7U>Ed2Hqkh<;5baFFKxkJj@eP#q@!4m%K_$!#!`e;E?y+ zU74#|fyO>tl=0?Ti_56NWYP23~-$$IovPRo-BdBG~r!7 zgJXZ*!?{v_MlhKk5j@qd7&8Ts&Ui~?Y#lLkE%$Ug73R-{6+00z}E= zK{FP;a8~J?%+eK`a{eBY&B#`3oV?oS;Or8Usmpw>&AOCaWP|`$PUq@U^>1x)v9tY3 z>!I80Pqpjk85iW`>{~zyu0oyHU5xnak~MW?Xom|!U>`;jKK-R|`GE7}Efk#Hx<$s_ zI@Gacu=Utjo<90o5?IN7%3FqVu*I$#tBNNUf-f|~S^V&3>Aam#)5}4cP2<+uCrY$o z!5)kJOUPP7xYi%SOYT$>lHfJatNc>~Wt)Ic#yrN5muXydr}A_#!OIu~v18I2kuYGY z-=T_d+#+p$w;d!Fph~CW<&Fs9DIPx%4TQ`_DR^4iMZBYchT=#Vu9LPF0iA~{nX@TQ zu^R6m*O#tTH{GW;cQrrUdcG%8*-lWGdxRR{bVyv_9kCZ|@hSO??HY^D&(E1C6(K`M zU|zzUtHSt2?d*kLGq|TqkIKBv z;FiVIFD;>(#t;cTW@r=g5l-giWIA-r!0Rw^@|?#2tt8@P^d=5Sq(l=S!=rH!jbt<# z{gn*FdOE$T`IcQ9lVuAyGINvn+w^JDJi;c&NPTrF!{j_w@atpCV%!=uDgz-bK#-3G zW74S}DbA{8^e$evF?*sY@|WBaBY=cr$Na_-z23tr1GTR9uO!=B_il zoW(VtT%UqpUGA50yD{!mC+pu4=NAIC>!PohO3n7Egm7HB;O$&4svq07w+)CQa`)^P6oqVZQuP8}|V>Hxtqfcxf?Z zSX)h>4_79xqKPi%2NfaJEaQ-6*qZ6o3P2O%&q>q1JHMe*IxP{T>(o4u(3+~@xsA*m zGLcE=xT_}9w0nA+5qAqJk)kWg%ZU2fZqyIFqQi?RlVHD*#BWz69Fqi=RS>nZFhe3+ zqadT%vBLx?P#*T>fyUjDn-> z;EvdXT|7j()(ehzN#S}A$`J%ia~=kI9z%2tq6|_}08oc+{oXBLcr~r6Za}TG9~+a8 z+d$T|{hH~(np9IM`mkjHbXR7%UJy1{vOI4?6LX#-j{COmUdJJMts)1%(}NCs%17y2yZ+)9?Eu#U>_YlPqRBJ zif*}H`O58-C8xw~&Qmor%w?F<(`g8pOh`Imh2$)KfC4QdI^X>1n7k^xyKdm%H|Kw#|xjsk-LoXi7VF(;^xU?B;uFZ#dpD-twlHmeueyKn@D5crZg2{DW){HAYFnVLa-pj5OjGDQhW zouh9l+)oc$Y)rga5`)X=5_tz|wCf#)l9mn4tVeN+U2I#t;kP`UREX3pPqg;gYmyMQ z=VqNll_;f76;ju+*z%B3eyO=6dtFmr75YV;zoNrKt5~4SQx!QP9CcJZ`#?S85o6Wd zNMiEl+QOH_qvJX^f2RR zYausv^BVJ5M7I@B-&`WKHDhy=#!;SX?>4f0*|OJ+l(65f^rAptDO}qm^IrxsyRMbzFyA66qbIQQww!=|z9YPcE=Clc2zoY&-rFdq$4ZCpI^7 zoi^Ie-$|?|8{B2kGN#SCBPw#o(qNwD<1jqXdO;X&MV(?>;NMPFi0 zsK$l8P(nZ`k3XS&M$ONy0yx$OIxt+El1#f~qQV`|)L-zQ3cb?GaHDS?$7L;bTvf>y z$V)@JwkqMqzap^YH@r8jh(g`Q>1RKld37bKYNOjV3ZB+agA(bqLa>tml*jHw-b49X zmO<9k&q4*{Vwel;IcjnxAoWP^jHz?oBV(N1D~a{YKc`<~{a$E!&T1_aK(K`agk77v zKtGyn0e0+G#1^HY{^SEt&5)j`8=@NI%{End8Ro89M2Rz(MJYs(kO|`p=}Y@aYH66m z>7r`0l#pIxZA{-j0+aevf#b<)1C}vn-$AtW1ukGLQewy}Oqy4isWDvhwk*tuzd6_>)U zFp_eFa;El~x1jYNZ{AFlHa*oJELU<-)jkd`R!Fhz0R>OR@`l2n=w)x3t5TWU(mILt z^4_Ro_?v1XN?j@!-P|Mir-I&;z~1rSN9h;Ae~MzXLx&s?!) zQ7!9vZIdEkhr35CWI|-2CP#v)vrlC9q%|>NY+#)Qzs`0f(>AwER`~&)m^zejsKu-T zw$ak@y(w9;N_TmzS+Oz(A>IES%#)>L&rz$;hHr{%>ekkFnkS>Qm5jS7!z$gngbGh= z#Mbr{7r?Y&me}u?JZ-Q;;_qAgf=%jFLGnB#n5l=KP0lb+W=N&%%V%2#dR&;w65*QF z%lv{iSt!>+KWBA)TqoUZ2@1PHJmYtFZk%QAzh%U4{pvA*`@uH!y&&JPLlH4^ymTWW zTkrXVFRk}A0`ndoENF{|Lkau5rYhZxuUZ%M#wXN2JeCmh!dd@@+~wgqZ3$Fnk*7-= z_N=J>TWp}LoPDerUpYR&EvyWzod63W9n2*+Iw9?}n|rqQWD&L+kjO~g9&LH$@@KyM z<4_u1(C#=#akINwk3uYa7&oC;<~<`m_bnR0-@mcn#IxAe=RC-f{ZTmhPrG?d846~6 zd&Yi0WSmDpdP4XLaJrVOCkmCH4JEEEE46NdTuf&vDJwr5a$K9)9v3pRl&x4)s1To# zRdUo#Z#FnOHQ$~mx;Ss?7$g!qC^ja*0tbE;D94s7x(stANor~))?W#xN&d}UYrz}n zcTnkd#|#%FYY@JmRk5hBB5#i$px7D^b4n*-lnt^MGmxn;uU~=wa%(vn2RE@wr6GoK ziaq8t4^T!de*vo8xGXvYVo#qTSyz1f+&>`~pEnXR1!rQE%(2~zs%vQc;<4(?vF@KO z>T?j~-uWUyXo|T#a9~)kG!K~{gK0VTtMo#fi1r=r0i%NsDJ8=U@iIwnqdCtPHl-DP zFyblj9yxbFs*vGp@XwVwM$tu|RLack>B)^!bT6wnHGdFva&xuOw{_X^HHt|YnOjO- z9X3;cYJqWE5x0&&vr5>d6q;M))4I=FA^nN^K4a~U6`s*FcF&z8r)U4sBEBYO-Zx9t zmhxo}kchU-9N0iv+C7%*Wgj7VsxpArXyDaw&Mmb*0-hW#QLBh~q{S}1uaiBL&dw+x^~?@a+#B5HyFL%kX%Ky z44&f_ye*z(6ozemvA;fnb3ur)8*&J0f>z znk>UL6(>c3KEfxSQAI>;MVI-sfQwD6_tBT%GNIMOiq7wgn%DVkPHPi(ftogNdzsI~wIl?4=fiwnGNzU#RQXg4?O zh2ha+K|CWH&c3AP2CCVl$64CjF$FsyKP49#hX6g{{hr)egQ(XFFhnE)aufuV6d}!1 zTx<-gpsrW+ff$MDbo`7rpoec_k;@vZmw`@C8BibeoR zu*Ov&l2@!X4A@&04j`nvMK5wDv4^|70P~O6N1n^-l4K~jHw%Mw$Ulk1V?G#^QOfsq z9geT)$O&xr?hRbY^y7zThd^tGihZ-$akP-oDxQ&9su@=df0dCTO8OFMhpcSLRf%qa zI+x0{O*SKt_Ix|;O)mf%GnN$MY!k*6aDu?)%9AE6ON2B4U;N(I;JVWpOP89TNl9ap z5L&_6wBt4o$sXGPTwCnNiYM){VZ&{6O*@!dGi_qWj0ET8)bv|4rO8UCd@dc|mO5xX z@LqK=;TG$v57zkz#gnHHo3yoSBGIM$Ov7OFR%`tt`)Dkzp?cG9E$E9DLQi)+q{*mKIZz)9r(SO1H6v7f^vX1>`^tw`zCJO<(ihy4g|J zN7Lb0U(Ywx!dhq`@w!i%EB$=y^kBkz$W?lA;!4op@-yslYr}9zRSsd3DcQ7A;*Tlm zjj_!i7T4A15c1?W3XnrD!_Wo>V=#l&F&cf6w)K5;YVon#I%c=fkkB<^%ft4&desEG zFS|I$Q2jlKeY-?VwSWch6k23X=qSvN@s#G5scEDC_v2;Ou1k{SMYk$aXgIJ%oMqkN z`3Q7D{Habr^aR1kcqURI;Y^xoV4T zuxl0V4wydl40mKTu5#9_1A&J6<-{gS(>JEV&bk{V{~0g5XuY3%U$!_Ty?meTR+|LKSnJ#_fc4aqF?TfP#|si6Qrv4C$=Lb0fcc?_m99Sj(mrK06rX}Bx6ouqOG@EU$bN}wcW668VL++p-56Af9Z+<@HT@3VImHNfY zb}J+CIZvA3Aif7+mG$dp@#ZYrb(SQdquX2*=kL|ZQL${TWhk!w*%}(I)ig^Pl38>= zZ;x=SNE?KH{?|={FHlB>lq_MWPOqT+*Z4ln6^ymCzu7o;FeVx62@IqL5*N1RUiow8 ziY=qk}e*4$1k$w<+J26K16YcoC<|3W#Y z8<(7}b#m&Vu#o`$&Uv&L9}xY+M#ID0>TP~-%wy@my}sqi?LaoV=EydZL~G9C$L;el zycW7~eVv8m^NbA8Pv%(1@Z2eP7%J` z8FCs?@XwJnoEv&cxj;brE*L;xgN^+rh$U&wLOV6NXSgs|Pb zT;jYQzaW4oK%BA+><-DkQ^zzUaTLdh{iC;#QJhl?&@w- z%g|@WsFHb0idlW_P0dC;g#*5C*}O*voWHNmFMr=4`G@ny1`S}5EF>ZrlYubVrjLTC z?j4?zdfZjMe#+4057F>1*pj=4BqNkz!{#cbh-sO3@ z&zvrmSR8h`lpfbOixxpEjuXvA#W=z@GoYp`JkuvX>+B?lYmb6c5w!T)zTDnz3K5ldq{po>5blr9(c4E*@|?Z=0+V=r~82Sh3()1AF-^are>e z09uE0;|3SWQ%^YFlzqONJs{TxX{n08U$ut`o7_MdYHzTTn8SIDiL|)43Gq8mNhWa)*yLjOk;23(TdqQG^ zF+U=(ZtRTpM`|D>AM3uWDjK*&x3#d1@TnK0-K!+`#9eMS2Es`$uG;S%4ky*@vFr4v zQ8HVR!$EM$(WkWSoiX}Bced0~vBebg?gJ&qE{>I*dRL%oD@vKGrwmj=_N^*hYA;N( zSmTJaN_vt?&iq(&|N3lpPB%^WZCT-~ebldqFY#4B8@3=+`D$DZSmDR1d|)n>8jkht zlYTZpU$NZ()sjj$UHw&ML$Z8l1^2rS9q4V}JNl3|5g@TmuSv=CjH+&pt060Td42D-T3P4Jnbpp&Wuh!i(|6?smvPTbwH(2A z6L=dV73R<(GBI7{^2hjwK(?zVsl$Fb+xrEb`{>}#MpT$Trb3%4DLS*9IQL;xrZZN3 zrw6fW#-&nm#z*E0Y;i-N#|;`5+*D6%_2@(5W61v`epc!8Pe#YAt!G#{j+F!dJhf%q z;uH3rh^{#+E+f|H_*=o2_8%-YuPM|F)$5wbf~=Fo2v`3p`{i99`s3@51}li)daRQi zhlWP)=rkk&@Nvz(Vac3(JdXq;E2qBKiF=aY{}wfYbomOBddxi+^YUqP^)?lE^RrVXP8Q*SS}fypEUtI0=})`+||yjzSMr zm&FyYj5ilt&%6EfOtJZCYqqo2&Ll4!>UnQ(yUN@x-pbx{0*b&llV=PYdPSQyqGbc7 zw&>JPoHQ|u$9mol)WZvpoXuvK)?p3*uZJg1)y-R`;kKH7-BSfkOn&HFNp%i#ll1pG z1`jV`v<;Q1Y}WP<(T4r`FI~pe5;!WDx`g<}<&RG>(Mh^Qjnkcv8h&lb(&innCLV&mp`{bLpip~4w@tC;_N!Bp^VA&XDEL;9(nqEd{G%DivdMFtI5g& z6eCI84$Q(L4jwL`Bn)5-khiQxQ?t$I>?f@S#c4^zW@vZh< z=zTZtmmW4-o=U5m!F68lFxd70WN*=i?!q>dSKB&@Ya*7>s8(|Fb+M(fcmAR*Wf2{j z(u#$Rk`P^0XWgKMNddqGmAIZ+UTx_4Vr4ErDHzeejifJPP73SK&^SUv2=DYjpR79D;uQ@(7YT{ zli6%tgqvB@H9)l2zMOQx8RcjV%gT2#dBL`Lwy4X~ZC;Z;P_J=bH#TA5Uc%Cw9%z8Q z6RUZZQfZIM`5dBv@-aVv-Oe8WlVVKX0lao3G>ZFTHY(rvhy9qQqCu58IfP6uFq#|% z71AW8wurzD{2xI^-OOh!nnv5dOdJJZ+>@FGAcI)|9kU1o$UW0RdC)RELV)SEstYX7 zOCjx@!)dP4=f5R-nML4sf#ck(b5 zop^Be62p@%sND;w>Q>8~1(`ejqBHuatfpL$2W+OFj6Bxqq>RsM_I5jwV%*TRPiAHu zBMb;Y)(*CH&4T-gKp_{WaC`gqEnpeRu5+I-?54~7sp4UjPpn~I;uNC;IVE6aq;)e@ z>Vz(>U=B&!9Rl2}Jq|lg{qkm^)M%2W#;+CiM}blY+CkYV1R5dO!hsGbV zAjs>G9LuA1ZuhgjR);?2cziWah35%@7`jM+KGOyFy-s)L*j`_7@336;L*|*l7VG$C z`Awh8_%LB;wF*R^?;#K;G-({>wxe+v<95So-h@XT?i%Fe`0*;n_aD9#SpB?n<8p zLiBo2^1GOV%V$#zurt`LL=dEaNaxU_kXWqOtsVIz&9n)0GJ?PJ`*)sx_LrjvFqrfT zEqx`|I)ye)8gI)mowrv8(CJnmH<^5MsMxKs1Ggampi1PweWQppUV`vIyDsi-v8}{m z9Iv`XK?mQ{H7ZKt8>$U;Qhw@qZFQ$R$eyEVuP23f-m%2!I8BaJ{EVk(ws7LJ*|U6( z%Kd-BiKIS|iR}98GX;CCPBk3RrK_nSSGl%e!5F#s^PW40f^Dk4!mOewNt+V1@RZqx z#1czB8(lG2N1*xohm5EN_783ZbCt`|_u0j{V@~y!nB2;myjoa{b|%y^8clCatY6yZ z6YLwF3Sg}4%BW_~S=9R}Kx??m|3fdjb^?bja088m6>RsYrdjB4$@!m;qwXB4cn>%i z4~h)Pil^2KO-Zf~zQGO&g+ka76N(?v#q8`o4#Q?~xJ~?G43_Hbcv;l4VguYXB20xqfnS@)xrls) zE`E+a&$Vx_=I_chE=+sC$r@339QF^)fbPtBH_!T&huPs|!nF_bEN4LE2Dm}RT(!x< zhOtZMQ`+YHK-6jPDFPe;y~Qz6xtULXX$@6r{y`6$OG7rjfX{?Ft8w|+!YnPpc?Z-< zIld(;PDRmj!6Ei99Xu(yRyK$La04cZX~ZI@>?mZ(p=l1eYwh%gj4=aej&&{B!4D$w z$Vyi$dRA>`vu0JKx+lCw|pDFch6CIB^O3tvDOEgi}r2@s;=}r`<8| z%X!dW+?~E3=E=lyKSt<>Rfb~AjQCoaZFDr2>XiP3S9Cu9d#n$Yi9ryOWA=uQg_C(8 z3~5Ypb^1RJ>CXJ_?uh`ywdsBmN-sXP1*;8j|y;z+9^;@^-CqGu$7{4;-9@YueG;H_i zO0RF382K|O+jV7@7qTxOwq8c|_gH>!=R>3R!T)hi51CN$6A5!`QxdBhHFxGuiQk)N zbsBw)8R-{@Yj1h>TOBlPx5f@hS>U00O*NP)bNgeg^gqGC_Wl1m?U%`(Kl-ICft48( z{xIS2R&|MOY@i92QgUKHjFTEjEsBBWD}|85fGS9H(TI};rwWj;#=H}6F-bKFh=>Kp z+%O4J#J9cYrvXp0(~UtvphWPV*YkVIiaCr5DPOd z{Ir4wS4+vWS`jfhGY*I-pn*Vip;kpCjG@68=;Bq?VlD(|Z>_Q-d#TxMjKcER?jRa7 z0%IU$Ky|fc2+}cz;Tu2zvlSJb1TiY58w-H~(My7nbNpiV_|pd~Z{9bcK03fGrTfoF z>8w|J*9Z1VKy|Oi^9dX;pQ9Q6eYt1#aSk>c7!xSufHI%NDAia6s@tOfU|b3%woD^ zmUAuu*$~e#XxyTK{la*^c^wN~!)NnkScOp6#FuRz9p?Rr(&Zaoc=a49`vcj~#bu-Z zb>lkLTlffsjR43@k_)FcQ*J46RnUl9L2vz{drQdN&*m-8_Q3vTJM;P zIzCFKJs^XUp1;dUZu6G!0G)NITN@cyBL_Rudc(siLgLyKU zVa2eZ_m`qk+t)(UbR5LP&ai?{@wK)Dje(S5(uVWps1cK~g#FEwS+9GI=t%E&FV1J3 z!E^86aHe<~&dpgTE(yaJ-}$aqnAs%}zI#f{M7R!3@`6k_PTjiL&Dj~FVe1OgiFLm< z21G|nibPK^lOdi&M)3jN_2fisT3OJ=;2aLr-j|m zhHV*8mL2K`2#aUvIKe=QAn+JpQ#VDCE||A;X6k;-)2sWVqRF5e_{4XI^8N ziLP3uwPvZ7Nnv?{U>6%vF&=pa4xz6-7kB~)(2rZFF-JHIusRhyK2x|MK^X9E?C=fR z;WPKBiv%oNTEr@9OnpPoCm^!3u2|_IgDKx?^UBibifgC2b`s+#T#q_g&UH)_h3amB zr-yGrP3ua08MYUQodk%B3@KCa42QCMRZPZ#Vz^sj-?gK^IP7devmFYwWGooeucHtx zmf8>q;u{hFT?RvQa0bT);u%YV^l-cp3pJtq7qZw8@l z+DjH>Spq#YImq2zpGxK2m3peOu6J?Qo^qc_MZnI*!M65mxO$>bMEMd*@~*>JZ%Oom zr!xVn*#mPK?EBB87I6>atzfHno2sgHJpO;gTj>Fme^{A*GJ%%mA9s+^OHpT zzvng4+aSGWlI5$JThun1*0+_Hp2g5IY)~iOquwUrc4eh45mo&uBoUu_ktyS`vn#+N z&f4ev84!`<#!IS?1O!f&f!e+owGs?O|0<(FZJ@n{{3o9y?0F&4Nu78Pr^0no{<&ar z?Hyl4vMIp#Jy_Zj!@nXTSg$pCJVE$6Z5U;1ATk}-Qqw>)WbE!i( zR`ku|?T)R$Md>Pi`V#B=bZ>q`O%~2VGG6B_SOW_7Tk5c(75fVdF0v|{AeJCpl@D=U zT~)r!O=rs+U9aM6n====JoR#BJM_@fbz!Mpx%zn_^0)NyfAcpwenTM!OuQv0I{vTr z{N5G%_V2uJX&vMpj}L5*E912VZg#j<_gY+q=d2rj_7%gwiUKE88jrAJSk~fc#j|W~ z+(6}8?Kvt>N+Pd`EK_9zJK2CC2ZP!L+8meLRV=G@6(Lu z8P>sEeMU|2Etk0G1F~ML7g;D-=kMOiLC$d89L4d@2M{$l>k6MR+z{U(AucB-x&F}q zlRKh9WfKO=0d04x$vdd9d~#=LOEelz;?`y_&oJ|r)`-V4^6p)wjJ&3{{& zo+jmlV=boV7m52*6sCf!>1ic67N-i#gFgdUi;nfGl->VM(HM)B{~nfhRO6q%rvMd}8|@&lUxfzQ3s_RtamLp3?0slxJgS z8%cudclTYxJ!N;oa`S%zTmQ_faJBPs(+LBX`Qv%SoKm&`CHuwqglFcb{Dla|IbtX2 zIN}ZPKQG?;mJXq)IgxORNdB=uV%^Insr49m-X2jS zcwXY5Pnz0Cklsd7E@Q>XX7tjuXQi29ZcpsX%25}GiZU)4eSNij;Up%i2-E zEW*WDZi}T>gXDBnr)74AUo%-nEm)trgbDs_M79L-Xlc}Pc> z&NC+wO!xh=&x+WnRj45jKM}18<(raYOr6O2O_(dL7&$rO$LILOofV5ZZRCznETQ+S zA(nl;3|gj=jCswgPhy_3&qzcSJjoiUe!d>Fyp^MR)3%pCLWsbiw?E~nw-%uIpwf(AiKLXHEpB(6~^S-~u4 zfHGRb#Ly2l;*}FLnrl=VFwl6iqM@xG#*fjgs@%1^H=;=&t4pKl9n4UHNT2e(}F=YL#9KW{3hYE zdEtZhO^xyEqb8Cuu597Og>c4g-qkjGmqHbyOo~Fp1!t){(W$Xzrmnn>f)!9yLhY0% z6ei6sj$Q_L7tNNYh#OA%MMZA&;T2SCNguK#Qi0XlG||= ztD*8{P_&CwF*H8^1>O+L!H**xE~3nI=c<`LC6W!R*0m;$8E3u>aut_`w3T4JA<+>$ zCk0~5wL+~1vsZMhFxcxKUA-)o&<)|&3W&d_s9eN6eb2OuKd*FR*>W+gEl7pDCiGp* z+wR(`D>#uyPk492Qe*+>qC_J{!#8yl|Wyj@IP!6pTk@6pw0hR%8TRj)xoEI~k-#C&c z@4)irO37upzQyeC3Cl6?*V}|H;GCu1-BWOP`z(-9T*ZsdXK-g%SFrp3$g>|u*uNtF zQBx~||0EiO%U&x>{Za{*E)8Ur=}=AFmTkKklu-HCWf4wJf>*7iH%V9ZK6q+1SaAZQHhOWg z&e^;Exn)%Tvqt;F@Ec!~Qp>Xbo!$fc_o^j)3bc#xLS3^%dYTMO=p)8v$%MxNWZU6O zu%EAY5bueqE4MwKB}oK!d@Fm5g+&Cwk!5HK{Mi?q-s%X0M;b}AN?_}XENasP57jsl zqn6-pWx2}vvYLK^Xa9Y)cOL_MrcD3;6z5-yXCoT>%6{Q1*9e|xSB@%c$rgq ziaH|W@{3ogr75{yb#(h>DowRsbIgLo(sIn=My2JYyaxN`m-p&ub{u92FtRi$TV79d zM?F%vbZ}l`kI+nqGENK1b^*sn(v#4H1O2#`x*UABM;LLH1TK?`7f^IaOA)`sm+a(% z58rZP5&{{FziJVF^o117o&bP>e*>4h9Q%(NMM;WZM&AdjhkX~RsN;6d4i04i@akg# zhT#B03ZxCw3#H8^=8bYNfk(QlLs_|+?r9JD*lO@~pQ=q3kwImlakw>8>f*Ujwh>*n zttir>*&4^9vZ+k&9vY3iHNfhhhWj$UNCwJ~DzcV> zl}H`R(6XL2?6H(#@l^Yx#mOj=R2_=Cq7&>FUQw3kzQU+W^ZJKiIqgj!3L|cYU)NtG z6b>Q5UHxIeti(a%IRL$V*Su8-0HiZ|8n4n3T&L>Fmc#Ha5=$CCcRNg(<=;$vaan1- z-HI35)=Ha|Mz)!Kt=%dazg}NExcT9z-+ach-<;_hT7vjAfA{&}-F1Cr}8nlIIoEm{EgLnd3c6b{#L^=um`!#JNf|lb{_dr0$bj_`C)^1w9ts z7r)it(v-sgyho7a8Q3w(e6*7bzZKee6!fE->M0ff4wGxocYvzU^()XFw+jKP(O}MF zmk}FAG@2Qafjg=}KfI*0W~aRL;518qKtwAYncj54>Mk|?H22_89Icf&4N<_&1!SwV zARhnY(e2tZg@cjU5^RVofe(QHw+1XG`3>ht$l8~N%ECbiOXGhZWVHA z2>sqE``}7U4OD&yJe;&Q3h3>fAZ7t1Ukw=TW+@S!}KbqWTcY0dfuf>!$bpX^8 zt{luRcA-;Qpa82-FHTq0^5+%u0ng`|NIGQAgAnZ^qCmP-Gru(VB{R88V5Lk~mm7u-ZuScUI?T5tmq zBGp?@z1M(qK6~9yJH=-lfu!|ghf0;yDuwY&5q<*cg0+onUkH|E)|bc43}8@fVh0Va zrRU#`4yJ_6$L%E%XqnyH&bp+Li6$R2P5nF8qvy-oE|M9vV6V4-gNYCR!H)8h%cUkqRnqI1ji#ahAT&BhM<%owm)A^NUSks1C^An~?_bT-G{p4T zSj#}H*2G!1BvB>%4&pT55>TG`1S6MX zYi4~twOK{cV`y`vsCHx#R6ESqrunB))T6?JTTx{Ih!{GSn!g_4i~^`|s=RZ$E>m~d zB*#2YD8Xx*TQ_@+z}nCS^1;&t1P7Rw>EN?qK%a_5T`eeI!=ni_F2*=I;`!XE5rFSG z5I#nK3-xMw8ORC+yJA-zbcB)}hk|{eocI!m9~|P|d3&?mB1sad0$1qv%EO#{2i*pH z8lQB8*U99VQ>I;>%1JbSD+?ECO7OwTDIHPlo2=W9-wc~=_5q%4QE6YVgZNvP0*>RK z5J&sysGygnU^Txu_xWEyHW;JiDTw=ulvKUwJn-RJr1uboX&9Dn|9r+Em{&mOc2!DT z;7tljw4g2hWJMm)q~o@3Xz8kd76+PDL=9DH&kVn+gMg_d;-1=Y@t5$?^~tiJH`HUT zR2%(orNFm>^2u))xgDZzAJ{tZ5nN7rh-#E$I`fjTG_aX%*hj?9QNeHbL!EiIsejTK zJb4u5ba*HJAE}h$k=G{iCQg{60YID*eP2Gc5GugD0o?Z6J$;_XV7I{lm;lE_{W)cC zSsPeIk$uu|=2a9hT5726DhYc(Jo_c*vQ5pIN@}{%`raSklFTk~nVgulGFuK`TTX2z z8@H22fufa+gjD;nb6HMhL@G`;S$bJYo>AhIkn#0#R=#xpHp$C@fItbeQu|q(-DYQS zn|d?Afj}|w6i~E~k6s|a^g@i&WB2M4hG^QUDH~MADMu&~=u@Tgqua+SgN?XmrE6!g zB5Ymd>$)&0jmv9YoRSZJE323l{Ba{p9v*KnJUYILRTIC%*0mFJs=EU5lgQwuEQ!J+ zQO@!2*rz(@k>ck$!7~?PN4fNOvOJYBhnpyaB;IEQzU}{mbb7@Ai>=uddLjpWfe)2T zlfC>xf4M=C8kDZ$5g6zBN?BJvf2)#XS%@iHhO}%hro&(z%L>ivv?E91xnk>C`S7Cl zY6R0V=MBk*OZ2G5L2fVeHRq~rKs38hp5jXj4VgI$$7CVe{n)Y~?bMM=t^~s#q$5g% zwY8*mXIlPPPNbcLkd?1(y!oAd@eZxi5!ji@doLCh-Fc%lGj+FgHJ78)_VEBVma+Mi zvBYhK^vBFUDsI*#s~nc5VXd*%Or>fia=Mq2DwBEL>7_AAAdbil)f=I389rV#4DSnL z?2qV~pb^o^tgQWf9na0}?@Z2G4;-Ey6_-Tav5`0m? zNYvA3BnY>En{pQt!l#wr3enLm`(rm&IJBOwTZvO!SN-M*QM4n4f%(Z({)HlllLcw` zu130BpKB7wZ07>B>3M>a3#5R!D>M7QQ7?35$DLR#XqsB!c>pwbv5px?M3B%VH>%g; zBxA#Zv8?^f*yQ$1E=iQVQ*{e;Qy(`6ct({0{w_5e66QB8GKB{135#l(&+h2`fds8 zLHp5wG{e7A{H{i1hCKmmlkEazmvn}}IGMDhKR_6+IF&viYAZ!TP;oRJ&F1!$ghL9#9S?F4enK(C3T2?G9FGwH zz9M$QKWKn&&C?_vu(AZf#%e{E+NUl9OeRWzUM0W4ce`J-_cl`^YI}=vub0v!r&Mcp zw<51-)OI>TA4OdJN6fS~BZjNi*E5q%$*Fv<0uYwf^9S=Z!_jH37&mLj!N^x(jS4M$ zrVXi7IBH$Z=O9;VJJ^+_7s5*9iE>~u7DCOBuj~%NfQp-}6D0<44FVFo^yWQq5OPay zhhWI(Rbt_oJO4SU`1^3jR3okT_J(8U7}S-+gA+)%`wW<_zaQQMDk(@VE*YkK>7N~J z_@`lwjP=(GF|}8k<6*a}a3b0DzH~F@`6+>Qe)RjAsGWiTfxy_oh7sQTC7&;^xTJGK z-m)m~D+n^DpSE*NQ1bX9)a#Sa1>YEBle131l#^#V5ddi4Qm^1moGDn+47wJjM_FW% z6bf(4^f$A@FP%$nr_-)Ha8Zzb0t?$<8)yCVP##HK5pdqspd$qjj_eI*h2;|w{?gE8 zmz&o}s}7!Y#~iV{%CK!%i-6HK-RC=A+z9ToiW!moOSNfL3tTyVOWEYmQ&ARs})o{a0@J6TsQZ_p?E!>eQ;j1E;jAt|Da zy6qX1nnToIS6EUIsMGa}8m5vS$YF9aA!iaC5+-7bI#`J_XH8Mtuf17nc4Sqt{_vzP z%lYg!J2B;S856ysAVOk~T|$79Rl9QZnVY4gYAaFYng3K#HycC$d$XE|(ZKDl8s%3d z1cAFARP4#s9vv#JB8figh)+YHsEE%h%Udof^(?o4w98V~Dzho-PI^~8=QA$r;V2zd zCLUV(EvqvOsv791v95UFTv}rrxa7?)!>(YS#g#=g6hL}>xCW3ebH2r^lS-!Fb ziG_AfgBvTnj@7YPFNDX}Mbo{H5{c;otQ(`41MLWba+gL94h@6~L7N&t08Ix(`6el6 z5lZX*kZH3)m!gk5S)#dO4X?*Zg*O!YFthkh1s#8Gjt#0fEztFT2ULQ@&goACaICar zLok5HL}Hi7Z0)ro+ls8oZ9TElK-fhLNU(Pr`~zn&^7rd$0@8;`H}|bhh)}BFf+jfT zD>3UV(^pz0!eLixWt?XzG$_R?*RQxufyFm*6x1wop?EBx zLnoDGp>8phvsPG;1z@JAmk2N0vcrE7<*5vTs`exy5g8i+WsTTF?ym@bb^#D5Y#>N# z68Z`3)<^&Wdc}pVDni`$ii%K(!+PiIX0TE~I>B^JLjlemn(vGmy_Jw7s^p643gUq4 zzK%S0`56EWK|GPoUfl!UB7^@*YWsQWV~@jzX$sB3jM6Rv@&EGh)Xlk_0kGOcK-Pn; zM*wA;XaKfcOZG5O9y)oI@sHJ2oeD)2Rs=II{nm-iBRxZvp)*Mr-<9V7pob> zOQY%~m{f)M;jf-m?Jy;dt~1YRpz6uZAd#8NLnEV_gHtP*K(;!TZON_kpwL7}S#I-v zgHZ!ATAwg8w6}3r!Cnf*>g<8DoBVikVJjo^P=jvTp-)(h*|bpdRP8go1}lq}Ni32_ z`aD0j6U#!IEm9@&`i?J!l{(_l(}3Ja*zOxOs>bYDdg8ij?Q<5R_6nx1!fR>Y;{NLa z6`eF|%}ab4UqSA4Ukr>n;BnCPMX0rF6MYB>in^EtPR^ocR8+OMm@J|$bxk5N9gy*P ze9Y$;;IPo%VtW-%FOf;_Ux*%<-m-c>Lnb&3lDyB!MccUHYwftO!_IR`QYQXD*sG(C z{#>X5dnc>#5#DE5;4yT`yekS>t|wtSO_(`7pug9@W-q!hhAobHo{OT`UT|q^Nfsyu zaSbU(K)F00VNz_?ZAccY_1QDx>IZ8*U~>TOXn-|7b4t=%-kjNlW2KA(;k+iFWX0b)T6;8`c$b;|FY{Q%w)Dbi zMCz63H9<{ibDF}Y*yaDbHRq7E@r2CqfBpchURCQxurjFMEX)(ze`wf;_qyV3E)`B9TVX+gkjs zj5H88`ke9F9CeKsT(}(2A>_ojto4OC6&e=YsDd3`<3*)YP99 z&bhS(S&wz|y{I5I)NO7kSHrAR)e~XU2If_2*x_E!4#Jm9jLh2@h<1`4Zv=d~@?_JT zCj=-~+30h5$9Z8`_vI^Q!5NHo)p$!)C6&~s!!|&lRczcS?~=!hj~<(!?=RgSk0{>H zKQB8smkqlyKb*GR+urQ1F1(+;(PtkK8@mf57dt;8a_bIFEZXT@C}~DRY}_aRKDqYO zkDwn^K6OU-MJ|+3F9+#}WR@kDjhyvFP$+)Zg*oDL&ygNIJorFq4Hx(XFT;!Gl`+Is zuHIrJI5?(|S6!FjCA<_&!UPD$);)(di~%f7H=21wtlE@h-~qoTUa{{^tK&0Z!i}w(Te@~cjt6M- zxskOVTYjv$nj6KLt3vqsgYhnOxwAd}JD+lna=*`f@|In_$|A$Dn`A>-1AM|gVP-F+ z<>cnD$R@5`6(-riHuV255MpH(bcNKJ%=?j%*t-Ec;Tf8?NHwlIWBp|0sK|NkV0iuM+kd z_Pwk_-_N!aJlfj`9}FD>`vQT?#vlG81tWHRNxLLT6ulBURm6GrYSdwYPl`<|qY^zP z!cIT3bKD+m?D)*wmrd$8a3-380@=7I`qU|xn!Aew!?TULY`k8&XODF+yJncf*#PDA zy78h`pEP7ew_w_3*+9MEe{mFIoDX3-7T$`Rm|~f*a=MIW330k|UvF{Fu8(iiH4NgK zs1udv02-TGnAtvAHv@kY|53y{I?kTsXk$L{SsA^Vh1UMp(b)TB4!U41VS?)ltw=TI z-oWDKZip$l%zF z&>1$h9q(hbTa1Jo80Y2*(o}2Bvi|>RBe6U;Hio1>%Qs-5aYuwDx4#gRq=r}0e#{5X zc^N3>dAc|s9g(r3MDYNBQ;fJDkH$E7M!XR?DcIP|-egJCY036_jDG9wL-$<0MAsnf zT}CQyFFjI;x{71WxYam5{4?A?O2p$2{tYQ|hbgl?+eG^q4qV}e+@-r>PhXG`PVYJ% z3!t&RW|^2FB^K{Nk7kt$PGJ*Ac_BbBHx_nb1R`JXNh^#&~RLdenPHO6)_}&-tXkLLRoar}4kVL{kC{!QFKx z$1l@sEeSP{h#xz31s8Flzp7UE70~%fI2>e{?Y zmoy1gD;~Q&nmVtAbpPc#vMOMwEP^pHju!R^Qx0WtN*a3Qj2jP)EZ*0JRk5HJnG~qi zHy>G<4%$h{?WjYMz_j&dj#Cx`cm|w}`QD*c=9;tJj4~@Y*`sx&ybYY*RAy8$5>JSt;;k2XFgrBKuoHq)Vn5xV0t^4@3U2h7@F# z7>QT9xU@8B16j6GIh<74M+28V6^I^F7I&*LUNp{2qjXh5G~*b?LVJWlNIJ1# zC?0cBL9WK8V4@35Js;YKFjxX0$>z+y6D&ODTt4LWs3|I z2Q;=d1p`X{Ob7K}Egj%k5cjD_xEDr#e@kNI%ZwqMZlJ&Ivw!OiU*k^*SX|s%kj6Ps zrkBl4&h$gmRXBB;1=@TYUbDD+eD!9p{`ba<|Hx74W)c59>06M9Jgb{Z|@l{YX&qRuKV7cZ9Fojc$fERlKKb_5*Wlk7JwWxch8a3^79RH z6XLqKEe~(j@{$m3(uk4kY~;=9e}FDQOU|3)E||ymHi;W7ui~l61r_GIFPMMSxQi^h zIGUW8r^>Gw8q}2#fdn{3j_b2O-h`R)UE36JYrB(v)APjx9 z=g~Q*C=w>VEsvUE5>t(~bdv*Rc(kH!dGq32`Es1VsT}YrAp?Z?^&&G=_FiyyES7~} zoMsm@{Cz-RHkl@n-lf7xIAGmD92JN%pJT)u zcE=Nww~oQWBI4mqANK=WVKeyeBoJYs@o$pU@5S!s!bq`K4B63E`b`N2_Jz5k*r2`y z(}SqtXp+)@?EHWs(TRcx*+F4uKrtoWSjd{_0co?PW3d%!w`2Y(&rFiUeI?1nx(ht8 z6nIgnS~>gEN{F+o=1}35ukIPZhby0XW=Tso1{@wnh``J2kVtT1S8%{#KoPUOWv)j9 zDeFh)rEHx=8v)8sMLvK1o_OHkm{wwJ#I=fEJx%HLkHr3;yDktsWXDsEBu^RuwpiQZ zo_KXs*>Gry{P(d;dgBXBqBZQ26r(-Qi8!Bfd`gTyZX@i;-KOIdW{B7UKm<)xB})sa zHIg_TG8o76jqlTey!+i=WDGvwpZR5diRZ$Z>_;b041R4O?TQW7+if~Z4iU2Nc}7yy zEn4ld--c_bnIVW(q;C{L?f0%+FW%-b#45|2LL~nHxu5g3ECZS4!JG{+l9ir6taPm( zBrAO%80l&+2v({dFj8gPy+SJ0KfV4g5K?8Jg_7fg79~3dm36Xx2>Vq|W9#N9QZ=ex zhHZ8sC2BLVYmWGeMA^rty8SBz|Lw83PVVMYM#FS#HmP08_$$=n-b>sy#`8xETHv_= z3I3D$s7zo^z{F0~Y;s!vZHF$iJcF&u%g+}XaMK=RHab$O&C5kI4@>4r+=yljb$#NA zoNyF6yT%d&R;I@SI4KP^yKXJJw48Ql*}8tq%lzF~cicmA-)%tHunk7~y6PcLvQ?$i z_EDapQGs9UTat;RbeY=T7iIexTaSG8%A6|E(Zst2?LC@wWZsX}75sc3po8?GbJ~E` z?jYz8?=iFQ!s-BXD={1~023-<+dq!Z`RNuQ27urn)iH${g-5|&+a-&2h#F3EaTh*){w$3so zZTRig*5!5nCt>9^Hm4R~TmnDl#($+nl~S5!<-?RpD<`(qNh^0aVkj0=sP;xcIFAo0 zexXw0=d_bVY_nkF7Qwiq9O)n9z-vi#^>uKP{EvO;^@HGC{BX~8!s+vMyzSERP;4Aw z=`Akg0k~QAST)5sch!kCF8_@fkD8RX9EVRz77Rr$ibZcn z6;bd_{~pl0di2F6W=R(qbB;RPzL|$@i__UQdmju;G|b)yK&lbRSQt64)9_ zxj~?li!mhD%3q(*4*L#g6X!v7TFVklzzh`7scZdd^rJBd;zWGKlNO;b@0dz5-EmkF zrw+D;-&0PhIahh0R*@U@>q_&QY~Q7IS({Cltkd5rD)y)F*zP`25PJt`R!)MNN;S<$ z7y8NIMuD7FwpnaVLwfO8q9U?OAutzcoyH2e1hoD%qx?vtB@@ie<`}-RV=xBBz6rHmZSD;&6m+sE@(JsNa z;BH_HpMM*#4kLA>R(lCA%h@jx9Pwd1zWehZ;P*Yjy#$8`*!hs>4`o1nFUCo?`L=IK#3BxFY!h1q* zhN1MnC_JmWMFSj;pbA(A#wgAWs{!KBf?f@&;R(I}Bx{EkX2An%qlyBSuK`E2}wnHoQ?r zba0@@EFR39giVc)dw0>qON_xx=dT)GPaA$0?(K8duKte9bQcTw9}Ol3;f})}WEJ3K z{bSdub4H-gmWwYn6^<{cKN@@zuA+>|4}O;8V-ND{#ow)HzuyC6UuYWQzdMWGL~PM|`4T~}kdm~-SZs!>@uI4&>TL$vfVvDpT;*~~kvB3L3fpKKon~9pG!xt4 zDS2W;Sas91ke(^(YGPqflM_+W*Fgs~`K^eX5vT&4E(`F2r>YV7CJ<3oR@GV)S4y!- z6zv&r{iC%U?{c}AFE(=iR0Q{r*i@d}q*J?SulM$*;wVWXt@Ny0k<=t}ATIZ)Zq}Me ztP+b-aj6uOkwSPcrd1j982kh8NX|jZZRXZw)(qRg^%k!eq^yD@kV?5-HpRf@KeSD6 z&N-iNMV$jotSh{dL_vY-#pkrO0LGqnx{+hFhM`Fv3a*sUP7 zou9nfLQ5(%hN8L%w&r60yplHb<<^*cCdjF5lX+2`00_vgLztR z*EL!m!qfz;JQ5pOW>;=!#&~NyptJEkmn2YB-p9m5{P~}^DHmb^N>%0 zAV$=^f(?G!1OE3fNGce)Qh-~4G*z#T#nz5l08>^t4JC&vuy%iCm{YyY%n+wNDKP_D zG2K)3WsEL?m1;*aY!i9iwm34ZcS8A88E-m4i*C5y@l-X7espuB21Jo)PNiIJ(I@5? zF4h)YnN!0UH(t1Sf$RLq>7vuN#&UV~LP_kIR1A-4VV`JOo!;ki-tW00KFMZR0VgDY zR71`s;Gak@>-?ahCI++yB6mMoR30J>6E*zE>zF$&?t6p~v32tsyxiQtfcVr4tqmN5 z5h_W|oD1rO#P~;ep_c}-tB7A1z2<{jAjFxW@6GO~5IBl1(ViB5>o>em^I!+laBF4r zvo-sN*2FQKcWm=*?d5$*Aaa(E~{XmG&T=4GCTD z;uT}X=~p}_PLHv|Y#$#BzqI&@V~;!|ugvxoZNOgb;li31x{GyjRMR)?!M)@Crii&4 z6T`Y~eRJtvK0j1+Sue9p3wo?wUL6RWbG%GTm9lOr-&;@`C4~!m<#er7+v@B& zp3c6jFr7V$3tAO)qEZckj~f*6;U_i+@2Z!qiHhcSw3|q#%#Wkm5fxiM=j>&X;Vhbg1e3}6AEE0b>`WBtxo1mDH!~s3E0!R825EO3wN$=0br(C}xqSwsqjN4SEHrwXj<0XO;BTD0Yck=P~GA8Tw@}l3CHY!i7?)7O^PMnd#8(CTt3y z08NZ9wCD^uqM?HV-LGc!t_ePMqjOn}+=^rGzf|W%N9ghev5lF!Gaa-l){OMj*8AR7 zw}ZvCiuIxv=vJTC%*8V-5a6cQae!mVvGgvJ@x@^L$%~xn?Q{7u^1qE(2Vb{Ec)6p0 zn&j1zz1ZYLN66{VUBjVEj{(_)rTJv32L45lmqwL_UES*Ln0>?wBq>HNk#DsErUaf| z(o|Yly_fQr2SD!pXX&cm`X0bp2C!Gc4zA16f+=jNM|k1dqj z)1F^qi&P$*_vhq25SOpwxG)j5x&PJfs9EtrmTp8%=sX3m5W4iy(Kd3K9&%BeEQY!s zFChG+u$Vq0>O4!7arF-J6qv6aXU+sb`6fgHeH<^&*4X-TD#yF9?mV~Uww{m~bXCJ? zS%0^8fS#O*r%s~Z%Ln+gbL~|D*5}ps83+fzN_L@a*Ma@1^s?kZ8?wKJdso0 z+&P2YXNCGz9n;>%H|C*sH^xX?fMPR^s9do6XfqfH5;{Hj*+F?5^jB>QGME@}Nm3(# zyWJRECy&J|g%@jimlBHUVT#C-P`qAt%yN)amzT(u!iLoSO`n6lbD*JXAb;| zskC|xG6%@48x8n{pvJLGdGkt)@|p6X1!cAFUFKjz5ac6VUXRzmfH_mp9k0trdV*7b zaen{Ur2RJVAWukg;6WX?>?$UP^apwJCH=anT!BZB-hzS?3uIF!gNgko?{bvyXA+V< zzopVr=bw@r@3_U1bV~fsQx%+dHJtjYe zkIPR8xlDBo-tpX%Udl^c@WU^A%#;Cc79;#gD(sWuNeELUwm{vMP?ua3D#izPF&;51 zfB#8E*#`*Yhx5%#oW~)Y72Kh>usE%1hg0yL!#X4uk zl0C`!)I)#Q;0ruSw@FsZo{(HwjVjW$EPERn&D)s4t@lEZ0}IZhrge^Yn{ctyxUmn< zdTCRK>!1p(eY1Bu_7c?`kg$?&vpnU37vrHf@U4{VLhi{2)k~MC&kAN&&Rgrsx<^&l z$~s>3^0)BefSQZ|TH(Y)hwp)9p?gPFv`&7*w%uDyj*sN8HbvwLTA^=xEVMcqQ>v2L zJ#A%aLC~e<92hOHezKNA#lkNBOl|&-TV|)wb1n#;EJ92wX*MkK!&A1)v29(V+xl9~ zb%M?Fd~u*Wcb=1VyBlGbR!d9_%SN|Cm;6#0Uj-^YZ(@|h`4ti$c-v+fq^f3aAd};gX7P@g~p-uSW zVv0|maS4vI-;nFqNtUc6LGe6Yj0fCvw2gDKl4_M>qcvM<3s3hpkd2$;Mm)K6AgWRA zuR6H@?^9K^0bw(V{-iw{27LhJ$_x|sof2AjM7hiv^3LXcv&KXfwWpfU%)6|h+~xP& zMR6{LWOF!S`Kpdkvy6Eq*~&M^pl6^0vNf*Wx4`Ef`AqSWiy06N>x72#l$W6a3$IFBQ)lq_@LahZw!7m+G7uKG z{Hf;x@`p&(lVvCod*rOU_oGVTGO*&)AwWzFq)Xte;1{eaV1la_7k^vR^sA=PVtbpg zq1&*q7y#wwXH*gR`#38+KTxoEa5$u4E57fr?-atB_JxO;HfYuHV9nX+Wzj&Y4Nt{| zcujM$7mIF(I_=p9#>I}9`RtNN%UgjbTaS_K5#6t)F~v|W-s$= z_Cm-#ESneS^^Dze3Qn9LRJY40IMC7^YbbX@{WDJYmN+jU&SQ!=g+PE`G>{*=Dp7XS9UccJSEW2P0;h62mAK# z#sXF=(>Ki7>vFsWB5!%>-3(FNMRm@XWd+@Qnf#2R&3}$D1n=xx@0<-iPRw5DBzt^5 zPmXhXzE6I13gmvKPVioK>RvB?I3T)rJUL%&>mI_oO7Q4x_7@T^dcHGaSi!Iu<4aRl zo6;z{4H4&uFuHa%z$~tM@x7J`TlTKEuP4}QoON%mdzztoQzz!6c&fIquSSa*h1L5D z%^rCD9Y{sVEa4#;FebdE(bDJSz(aEp;A$~L%12Vjll|g<66E2UK6=%H1s`0-p!d~O z?LdoW%oLQaOdILPyhS6Re!^WG@{$W~^~@C%9+-UI(x4gz*J1k>5|;BXJ><3LjB++; zvcVtP2Rzf5gKf7y=$n*ggTC$sSw7i5)0G_;=~wav9D@a^wRi?Wh9_Q#a}Dw(eT~h0 zX7H18rqRr?at|hogPCpc^rNIzAnzWO7kIH8MQD(rXsq*yig2G`QQW<#plPs z8M+%~51qzNo1{{~nsjfB#hJqDv2R7(J%BWzrg2_=2(!hDL=(Qy%xQ7TSK>(^YtPfu6Bq|Li!buAxgh~ z>qJpM9GHqO$|R1tnLX-`@>)`Ks>?jZslj|pvjw|-vrkE;h7p%(4KSgIE`GK)0YG?Y z2lAcvdGYi_Sg9zp>!jXfzTavi8;`d-x5t9j7~h?7K%KH6a(UiOh1tomdYSBGtH`Z% zvIW5u_~r20G|#nI6^D0ojn({d1uL}?U{ zpFgWgt;4MxC=CT%nb2gL)x>>+H0`u`0>3QMiG5^6tPPUiKJP?1w6d$)7y3*jH2)CO z_j|t4e)_yArClcul!~;;#by~8Xahep{#8G!h(^5xbCaiKxa^uy&#L{V2-q5mX(5rD zD*P#DAkWV9F8jKNkl6g@U)kZc6z8Qb@Wx4Lv0Sn_&8VJwH{cyF=Px}!pL*-!=wUl~ zIsV+v{FyyE-p=fao_%mPE^J|PBC3AoOo*v8RM1>7!#B5T_s~VNxLNm-D zD8LUhV)P*JDMa-E;Lvjn20%oo970OnQq3$tU$_nDN7OWs(R>w&Bs7l{4>Och$GfQl zUr7=T^Gr&LLR{uT+qka+KKPDhdw_tHZfKczzJNKYY4lntRzU6;nr!}piW zuYbKm28bf%Y+Ek>wtHX1gib+JJ>f(vJ%wo5HM9q7^(;3DUBFRsDCcMsUyf|_S#yNq zNTHrme~EvN^!Dyfvr!d&k&KC6hANLmYDp;Ol%S>`P@x#;aCU!!Bbz1$ilR1MIw`1yFQk$@;!`u7kvaA(VAZTims2z zQphxD%sRZdJHjbl^V(|L2`)I33**G02}P%U=|c93NwJV-&!OT&g(r{VGC9$jglN}H zLbKJ)V^{b-c!^u%q6&tt#iuJ5A>ahpWp0Rrw2m$-3mSn@Q~ytQi!?mw&x^RY7#_8$H`!o_ekJl8q)=^hU*u`AFU@}I!e35 zTVgx%ljXFx@6ui}1+oe>mN~Z3Retjze^Y|KJN1nhm1o`E4r!2SG`l1f;i`i2(MFoQ z!DV0*58P6mhjqs6)a)&eB{fKDzOJ3&TAbI#*j;m78+VXr4zc^>k7#W`MKtbMw6k{3 zGLJoS>N6>Z1)x zKt2INGD6sq`F*6T&24GXe1Yd9!rKGgrdpAPC)y3)Vq#Gq<3J5yqUQkkhvP9w4ShM( z`IECmN*k{9g#*kvSd$Rh*lI>mIB)?=eF7*nq1=SCJwwQ`M;&ZS&*6k|+Ev})1v${? z0t0;!;id6O1E>fs(g=9-2-J7UyI!F26?kCK|5B1E(|i^`qQ=JUF!M|y=QaHei?^j+ zc8VN2F6kM75l)mLKGuU#T6JQz*2>blYot#l&o$ZR+OD@Qu&fkx*i;UkNuU-=N zOPuV^DXKlW_@~0y8Zd)w<>K#=D{pm!l<3mYCPk#@%lDy~0DNWjiYq4@>mfND!G~n1 z9daEbInf8F6*CulD((t6Qb*mPBcU6^OzkSdK>9QIUWV5aL%SDXiG z0N9SvBx6fWU@m-s0o7*Q2+&gz6f1dYZ$XAI;^+Ed00-=6VosCa@I4z# zU2r3ZpJBBwX~;IP^V{>HP{d?cPNuj@k)3lUPIMv@&#te-PquiNpE{0DcVprmbTm1d z%T@G6#`8wH%|F_RiOt%@IKIEUCo;l==L~%KJkp3XVNW{)FR@SAbnW0-Wna*fmo;Nt zE#>aUf;%C=)E((?rOSn(c{Lgvrn4z#d*~4}GG&1JUdz$Gkv4H0M;o{`fwFl{MW&Kl zd*zM|c=!f$Aepm?MgVs09M5w&H1puI2Jw9d%Qw zDcaru81KHbeijw3_eYpRHy9&KVJE}TPFm;)@rud%!OB$1p~Wxu z69b8Vhvg}-+|yA`;&~P(&korI7P-PmEhLW@eya^)^q+!+S$#!Y$nrP!l!qF7O|cDy zzg2FWiH%Gxt>Es3s^%BSKDd<5I^TaNO^N+vS-D8)F)ZJnsO5Bq3NMAsZIqYm1ah zg@iP+82j9*^dB=UL~$|#PHv#OeXCW*lXqO#?^z2xC*}A~B0mZHlJ}=ch9jNwRFjHJ z-<u9n3!6U~@nD%NK6c&F5~MF4dygvRu*CY;2>oVaJ6$?vjTcE48bvG7G=kb$J_1 zziqJWv&3=x35AInjK{qS<>%6rcDchEST?(pGqVrrp( zF3*nCNdzt?1|Ww&Uk)qEfo+SZ2bBF&CW{ajCjX@U53vHnOz!)4I60NaxpRb~3b&X1 zzRSb1ERHGU!Eu>ko0P>L9ODXbe%-d@t9M;=5yv-SOlo3=j}k;*FG#8F&=3JLh@YwxE}xZqehkFbrbdun{fEh~R7Q)#Byhfkj|^@8%q zb4+;QD&8Xh{+#a_E_3WKgSx7%@I`60(WuB}jME6;G+f`EuyC9fIQ!8q%_|n*2>B+u zN~%bstl`+xbWr7J;s8jNYl$biRkvw<0C>N2rEG6S#hfqcBu`rOI3O%^$4YTe6sUHF z*fm;_k)Gzu_KiCg&Cqf#WmFZHZdyR_5$a%!3>l)$6I**g--pQxe4fp0>Jna z2V+CFGhT$@PlMs>muemZ{3`GFHTtOh?_KN-uUe+OH#|i*OqBjsHsu>U3Dn_Q%2WUB zW_l4J)otGdi^|XMkE9P@$P~N>x(cydCbI;K}in z9G<48EkAxnDsaiX(U&)5wCsdS3$}FO7$?1Hl~VI^?PNn`?Y4+$WTvrJTt*elorfE# zQejJo{SkCRhv%-3RGYUZDXu22K_t_Tn=z0zi4(l^lwxZAMVhEt77YA}0)`BY{~)p0 z_)8s~gBx?KbgdPZv&9BVc&%Pokg))++N zVRe5-p;?r5NfxygB`~>?xY$o<7Arltb zorSy4E>4*Ct|lHg$B2TyA+ZJvvQ@yBB3_A{j#d5-0!W+k0Pnl4t&7S)fXyz8#GI5e zek-bbFHJH`KnIatL%CBztcB9~@fan}pH_9gy^|8apl}^B_+3^^kUg8g7Zi^O9iO03 zkR%9<`I27L>Nm}SW-c;U-0RT&?ibN}^DVHs&yLv6;oCh$MtV3AKt#&^;m+$@^!%rs z$;T}QGqiFF(V&l5a+k#I^44jgJ4t81l<&Ddjw<5x(nPLt-GnMUKj*nkhUFmx$ul32 zI{-n{KU7d?jM_~pX&H=`Vo}aJT?ma_v;9u#dOEGAydW4-+nD5;cAO7y4*`^_o3EUq zvM$%`B#seifTNCz-+y52&cR4DxJNHY(MA=oxIX#Ejrakc422RQMl>u6fF^ zfRYgo5eX0o(VR~<``}7_aHT%D(gP2!)W->W7;%Eif}7gEZp|BMuYHoxKDtpK-KdXl z)JHe!qZ{?nja<~hH+2zpmn1k8lHvgj{nbIE@CtSC5AeGh-bNkIpjWNqU2R-#cqw@{ zhYhBAvuY_iGk`}~4o=%$dT>a31 z=}c9MqDQ{Mck1q^FG+9;On)*XCB1|NvsUl>XcreTF8!-C;`}#aWwP8gGx(ReRo^0Tr6EO1QHUZ)bH;! zo}&2pD9y;pR02^P>MWUqjRT2}>95qjvL)1Y7>|{@?)Lo!m)4f;f`v!Eh^24**4Fk5 z`TynC)?4}i|7;94-mCn-)h{z6jxD8&_51Dg-FzNlv824?4p{xvE^}tunVvzER0c5t zwNUg)2swJe@eEa`U~FgYD&0`y&^h*z8Y@>0^Eu+z4f(w6CfBDi7oOYQwY{yEb#D9u zJd)uIbGc~Sfq7oOP7wT4X(#fVQ#M{95E=x#5DIPnngv6%>HrcjL?9885T}62R{#Qn z7SR#}F_NeF%H*WZgg37UL3|;UJU)qOGM=c@T|&d|;#(K`(h>DPAsDK=r!qsLl>{3H zQ?)?9oqy0*Xv?P)0+=Ec2qv;?w%;*1qzO@Z_*WD@Kro=>J>fCdES*!CD^ct^N07tH zaz6*6k~4b2HSHh&ZWzB_4>sgq2TP7BGjt0AXHaa?oywDnq!nwiu%W3hO%5s_v=e#?DBDhKU5k5->fOj}=E8pr-Cx=vJ($k-&z6kmyKy__3#jR$3bfEZ znE%nYmRP(aR@ji=u6V^p>&JYBAkctKxYQkC8m-(#DGb1`{*@GdLCN2Xio|jNxG29jJoXzIdCZz6swsNKpWhf>b!t_3b zI;aJ33~_%Z+n>ok8RU3VcUy2tf^T{ST*^pgt>D8Z;T{KKNNKd{%+{t|4J@joJ@KW) zbTh~l_{f*XVuIvtBkP72*IzLOFo4E@P~N+4-Gl3vzHM?JS5O`(9BgmZc#D6BQH1yb zj35d8-88Jyj-eR-0KeEAKb&$MHQ6=)$1 z44L|XOb`sSkdl*5C5{IdKcQ-2ngdmXaM{Z~06=jKZ`b6rR&(;tyW()sjaIn7dTtBap_7!`Hu>7tY^5qW6*X+Kl%;nxv7QQyERV!YMQ*i5fiWF zGEs%yr9kbY^cPyL%G6`h@5Gn4!0@WoqiZ%dhr4N#Fat7|o{rqr_4@4hzCJ4N{h`c$ z&D9RIcup{Di!q{TwVCM0Pj5gG#2VnFM6m^+XnvcZL~;>f#$^^s(;S4>+P$kLc&SOH zVW|4T;p8WqhR;|aLL8y`ezU?=bUa{<_0Iaec=CjWQQnZh^y4QY5%}@>>VmdQ_f2Jfr>cej`D2|F%@DZ{T zJi{t(!$-(IAan@l`OC8y&2Tc)4^Cxv2^qELObp#`5T z+4$wn^k)9|i0mvz!9V-6>z&DrPWfUO-b~@m9^3fko8VTq+V^Ip(e4&Sb^kfWV$M3$ zx2Cl3UFy+;?R3Xak7L_?g!;Ec&p+>C>14mXU8~`=+d|^H)m!|?Gu-?$Sk8luTx@a- z;cK|Kbw#31u#vpM%4tf>D)gX!<>>@>Porx$_N^Q|sWsZ#>f@U%HBC{%E( z2M6WGtTH4Pxl6BM=I$~uJvUGh8g*#35Ct?;KG+Z}P!zq;cNujfI@1d7Jk)z{<-f%h zz1!#yE>oxa=fXA%``ajG*2I#H$l0?^Rokg^ThG^Vh XF5K!q~7l~Dlk>gE8G?|hjUa_e2)@D096GORMX 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 diff --git a/chain/types/actor_event.go b/chain/types/actor_event.go index 5baca6caa..7aa268faf 100644 --- a/chain/types/actor_event.go +++ b/chain/types/actor_event.go @@ -8,33 +8,46 @@ import ( ) type ActorEventBlock struct { - // what value codec does client want to match on ? + // The value codec to match when filtering event values. Codec uint64 `json:"codec"` - // data associated with the "event key" + + // The value to want to match on associated with the corresponding "event key" + // when filtering events. + // Should be a byte array encoded with the specified codec. + // Assumes base64 encoding when converting to/from JSON strings. Value []byte `json:"value"` } type SubActorEventFilter struct { - Filter ActorEventFilter `json:"filter"` - Prefill bool `json:"prefill"` + Filter ActorEventFilter `json:"filter"` + + // If true, all available matching historical events will be written to the response stream + // before any new real-time events that match the given filter are written. + // If `Prefill` is true and `FromEpoch` is set to latest, the pre-fill operation will become a no-op. + // if `Prefill` is false and `FromEpoch` is set to earliest, historical events will still be sent to the client. + Prefill bool `json:"prefill"` } type ActorEventFilter struct { // Matches events from one of these actors, or any actor if empty. - // TODO: Should we also allow Eth addresses here? // For now, this MUST be a Filecoin address. - Addresses []address.Address `json:"address"` + Addresses []address.Address `json:"addresses"` // Matches events with the specified key/values, or all events if empty. - // If the `Blocks` slice is empty, matches on the key only. - Fields map[string][]ActorEventBlock `json:"fields"` + // If the value is an empty slice, the filter will match on the key only, accepting any value. + Fields map[string][]ActorEventBlock `json:"fields,omitempty"` - // Epoch based filtering ? - // Start epoch for the filter; -1 means no minimum - MinEpoch abi.ChainEpoch `json:"minEpoch,omitempty"` + // Interpreted as an epoch (in hex) or one of "latest" for last mined block, "earliest" for first, + // Optional, default: "latest". + FromEpoch string `json:"fromEpoch,omitempty"` - // End epoch for the filter; -1 means no maximum - MaxEpoch abi.ChainEpoch `json:"maxEpoch,omitempty"` + // Interpreted as an epoch (in hex) or one of "latest" for last mined block, "earliest" for first, + // Optional, default: "latest". + ToEpoch string `json:"toEpoch,omitempty"` + + // Restricts events returned to those emitted from messages contained in this tipset. + // If `TipSetCid` is present in the filter criteria, then neither `FromEpoch` nor `ToEpoch` are allowed. + TipSetCid *cid.Cid `json:"tipsetCid,omitempty"` } type ActorEvent struct { diff --git a/chain/types/actor_event_test.go b/chain/types/actor_event_test.go new file mode 100644 index 000000000..aae4865d3 --- /dev/null +++ b/chain/types/actor_event_test.go @@ -0,0 +1,138 @@ +package types + +import ( + "encoding/json" + pseudo "math/rand" + "testing" + + "github.com/ipfs/go-cid" + mh "github.com/multiformats/go-multihash" + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-address" + builtintypes "github.com/filecoin-project/go-state-types/builtin" +) + +func TestActorEventJson(t *testing.T) { + // generate a mock Actor event for me + rng := pseudo.New(pseudo.NewSource(0)) + in := ActorEvent{ + Entries: []EventEntry{ + { + Key: "key1", + Codec: 0x51, + Value: []byte("value1"), + }, + { + Key: "key2", + Codec: 0x52, + Value: []byte("value2"), + }, + }, + EmitterAddr: randomF4Addr(t, rng), + Reverted: false, + Height: 1001, + TipSetKey: randomCid(t, rng), + MsgCid: randomCid(t, rng), + } + + bz, err := json.Marshal(in) + require.NoError(t, err) + require.NotEmpty(t, bz) + + var out ActorEvent + err = json.Unmarshal(bz, &out) + require.NoError(t, err) + require.Equal(t, in, out) + + s := ` +{"entries":[{"Flags":0,"Key":"key1","Codec":81,"Value":"dmFsdWUx"},{"Flags":0,"Key":"key2","Codec":82,"Value":"dmFsdWUy"}],"emitter":"f410fagkp3qx2f76maqot74jaiw3tzbxe76k76zrkl3xifk67isrnbn2sll3yua","reverted":false,"height":1001,"tipsetCid":{"/":"bafkqacx3dag26sfht3qlcdi"},"msgCid":{"/":"bafkqacrziziykd6uuf4islq"}} +` + var out2 ActorEvent + err = json.Unmarshal([]byte(s), &out2) + require.NoError(t, err) + require.Equal(t, out, out2) +} + +func TestActorEventBlockJson(t *testing.T) { + in := ActorEventBlock{ + Codec: 1, + Value: []byte("test"), + } + + bz, err := json.Marshal(in) + require.NoError(t, err) + require.NotEmpty(t, bz) + + var out ActorEventBlock + err = json.Unmarshal(bz, &out) + require.NoError(t, err) + require.Equal(t, in, out) + + var out2 ActorEventBlock + s := "{\"codec\":1,\"value\":\"dGVzdA==\"}" + err = json.Unmarshal([]byte(s), &out2) + require.NoError(t, err) + require.Equal(t, in, out2) +} + +func TestSubActorEventFilterJson(t *testing.T) { + c := randomCid(t, pseudo.New(pseudo.NewSource(0))) + from := "earliest" + to := "latest" + f := ActorEventFilter{ + Addresses: []address.Address{ + randomF4Addr(t, pseudo.New(pseudo.NewSource(0))), + randomF4Addr(t, pseudo.New(pseudo.NewSource(0))), + }, + Fields: map[string][]ActorEventBlock{ + "key1": { + { + Codec: 0x51, + Value: []byte("value1"), + }, + }, + "key2": { + { + Codec: 0x52, + Value: []byte("value2"), + }, + }, + }, + FromEpoch: from, + ToEpoch: to, + TipSetCid: &c, + } + + bz, err := json.Marshal(f) + require.NoError(t, err) + require.NotEmpty(t, bz) + + s := `{"addresses":["f410fagkp3qx2f76maqot74jaiw3tzbxe76k76zrkl3xifk67isrnbn2sll3yua","f410fagkp3qx2f76maqot74jaiw3tzbxe76k76zrkl3xifk67isrnbn2sll3yua"],"fields":{"key1":[{"codec":81,"value":"dmFsdWUx"}],"key2":[{"codec":82,"value":"dmFsdWUy"}]},"fromEpoch":"earliest","toEpoch":"latest","tipsetCid":{"/":"bafkqacqbst64f6rp7taeduy"}}` + var out ActorEventFilter + err = json.Unmarshal([]byte(s), &out) + require.NoError(t, err) + require.Equal(t, f, out) +} + +func randomF4Addr(tb testing.TB, rng *pseudo.Rand) address.Address { + tb.Helper() + addr, err := address.NewDelegatedAddress(builtintypes.EthereumAddressManagerActorID, randomBytes(32, rng)) + require.NoError(tb, err) + + return addr +} + +func randomCid(tb testing.TB, rng *pseudo.Rand) cid.Cid { + tb.Helper() + cb := cid.V1Builder{Codec: cid.Raw, MhType: mh.IDENTITY} + c, err := cb.Sum(randomBytes(10, rng)) + require.NoError(tb, err) + return c +} + +func randomBytes(n int, rng *pseudo.Rand) []byte { + buf := make([]byte, n) + rng.Read(buf) + return buf +} diff --git a/chain/types/ethtypes/eth_types.go b/chain/types/ethtypes/eth_types.go index a419874bd..b658b8856 100644 --- a/chain/types/ethtypes/eth_types.go +++ b/chain/types/ethtypes/eth_types.go @@ -598,7 +598,7 @@ type EthFilterSpec struct { Topics EthTopicSpec `json:"topics"` // Restricts event logs returned to those emitted from messages contained in this tipset. - // If BlockHash is present in in the filter criteria, then neither FromBlock nor ToBlock are allowed. + // If BlockHash is present in the filter criteria, then neither FromBlock nor ToBlock are allowed. // Added in EIP-234 BlockHash *EthHash `json:"blockHash,omitempty"` } diff --git a/chain/types/event.go b/chain/types/event.go index 106a120e2..5f6415d49 100644 --- a/chain/types/event.go +++ b/chain/types/event.go @@ -28,7 +28,7 @@ type EventEntry struct { // The event value's codec Codec uint64 - // The event value + // The event value. It is encoded using the codec specified above Value []byte } diff --git a/cmd/lotus-shed/indexes.go b/cmd/lotus-shed/indexes.go index be7d43e05..620933e25 100644 --- a/cmd/lotus-shed/indexes.go +++ b/cmd/lotus-shed/indexes.go @@ -9,13 +9,11 @@ import ( "strings" "github.com/mitchellh/go-homedir" - "github.com/multiformats/go-varint" "github.com/urfave/cli/v2" "golang.org/x/xerrors" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" - builtintypes "github.com/filecoin-project/go-state-types/builtin" "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/go-state-types/exitcode" @@ -109,6 +107,7 @@ var backfillEventsCmd = &cli.Command{ addressLookups := make(map[abi.ActorID]address.Address) + // TODO: We don't need this address resolution anymore once https://github.com/filecoin-project/lotus/issues/11594 lands resolveFn := func(ctx context.Context, emitter abi.ActorID, ts *types.TipSet) (address.Address, bool) { // we only want to match using f4 addresses idAddr, err := address.NewIDAddress(uint64(emitter)) @@ -118,18 +117,9 @@ var backfillEventsCmd = &cli.Command{ actor, err := api.StateGetActor(ctx, idAddr, ts.Key()) if err != nil || actor.Address == nil { - return address.Undef, false + return idAddr, true } - // if robust address is not f4 then we won't match against it so bail early - if actor.Address.Protocol() != address.Delegated { - return address.Undef, false - } - - // we have an f4 address, make sure it's assigned by the EAM - if namespace, _, err := varint.FromUvarint(actor.Address.Payload()); err != nil || namespace != builtintypes.EthereumAddressManagerActorID { - return address.Undef, false - } return *actor.Address, true } diff --git a/documentation/en/api-v1-unstable-methods.md b/documentation/en/api-v1-unstable-methods.md index 90879a69a..7c2e36ed7 100644 --- a/documentation/en/api-v1-unstable-methods.md +++ b/documentation/en/api-v1-unstable-methods.md @@ -3400,19 +3400,19 @@ Inputs: ```json [ { - "address": [ + "addresses": [ "f01234" ], "fields": { "abc": [ { "codec": 81, - "value": "ZGF0YQ==" + "value": "ZGRhdGE=" } ] }, - "minEpoch": 2301220, - "maxEpoch": 2301220 + "fromEpoch": "earliest", + "toEpoch": "latest" } ] ``` @@ -8837,19 +8837,19 @@ Inputs: [ { "filter": { - "address": [ + "addresses": [ "f01234" ], "fields": { "abc": [ { "codec": 81, - "value": "ZGF0YQ==" + "value": "ZGRhdGE=" } ] }, - "minEpoch": 2301220, - "maxEpoch": 2301220 + "fromEpoch": "earliest", + "toEpoch": "latest" }, "prefill": true } diff --git a/documentation/en/default-lotus-config.toml b/documentation/en/default-lotus-config.toml index c97ce0fe1..a403a580e 100644 --- a/documentation/en/default-lotus-config.toml +++ b/documentation/en/default-lotus-config.toml @@ -330,6 +330,9 @@ # env var: LOTUS_FEVM_ENABLEETHRPC #EnableEthRPC = false + # EnableActorEventsAPI enables the Actor events API that enables clients to consume events emitted by (smart contracts + built-in Actors). + # This will also enable the RealTimeFilterAPI and HistoricFilterAPI by default, but they can be disabled by config options above. + # # type: bool # env var: LOTUS_FEVM_ENABLEACTOREVENTSAPI #EnableActorEventsAPI = false @@ -342,9 +345,8 @@ #EthTxHashMappingLifetimeDays = 0 [Fevm.Events] - # EnableEthRPC enables APIs that # DisableRealTimeFilterAPI will disable the RealTimeFilterAPI that can create and query filters for actor events as they are emitted. - # The API is enabled when EnableEthRPC is true, but can be disabled selectively with this flag. + # The API is enabled when EnableEthRPC or EnableActorEventsAPI is true, but can be disabled selectively with this flag. # # type: bool # env var: LOTUS_FEVM_EVENTS_DISABLEREALTIMEFILTERAPI @@ -352,7 +354,7 @@ # DisableHistoricFilterAPI will disable the HistoricFilterAPI that can create and query filters for actor events # that occurred in the past. HistoricFilterAPI maintains a queryable index of events. - # The API is enabled when EnableEthRPC is true, but can be disabled selectively with this flag. + # The API is enabled when EnableEthRPC or EnableActorEventsAPI is true, but can be disabled selectively with this flag. # # type: bool # env var: LOTUS_FEVM_EVENTS_DISABLEHISTORICFILTERAPI diff --git a/itests/actor_events_filter_test.go b/itests/actor_events_filter_test.go deleted file mode 100644 index 9a3d4f8f3..000000000 --- a/itests/actor_events_filter_test.go +++ /dev/null @@ -1,94 +0,0 @@ -package itests - -import ( - "context" - "fmt" - "testing" - "time" - - "github.com/stretchr/testify/require" - - "github.com/filecoin-project/go-address" - - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/itests/kit" -) - -func TestGetActorEvents(t *testing.T) { - t.Skip("skipping for now") - //require := require.New(t) - kit.QuietAllLogsExcept("events", "messagepool") - - blockTime := 100 * time.Millisecond - - client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC()) - ens.InterconnectAll().BeginMining(blockTime) - - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - defer cancel() - - // Set up the test fixture with a standard list of invocations - contract1, contract2, invocations := prepareEventMatrixInvocations(ctx, t, client) - fmt.Printf("contract1:%s; contract2:%s\n", contract1, contract2) - - cf1, err := contract1.ToFilecoinAddress() - if err != nil { - panic(err) - } - - cf2, err := contract2.ToFilecoinAddress() - if err != nil { - panic(err) - } - - fmt.Printf("contract1 f4 is:%s; contract2 f4 is:%s\n", cf1.String(), cf2.String()) - - testCases := getCombinationFilterTestCases(contract1, contract2, "0x0") - - messages := invokeAndWaitUntilAllOnChain(t, client, invocations) - - // f410fiy2dwcbbvc5c6xwwrhlwgi2dby4rzgamxllpgva - - for _, tc := range testCases { - tc := tc // appease the lint despot - t.Run(tc.name, func(t *testing.T) { - - res, err := client.EthGetLogs(ctx, tc.spec) - require.NoError(t, err) - - /*ch, _ := client.SubscribeActorEvents(ctx, &types.SubActorEventFilter{ - Prefill: true, - ActorEventFilter: types.ActorEventFilter{ - MinEpoch: 0, - MaxEpoch: 1000, - }, - }) - - for i := range ch { - fmt.Println("Hello Chan", i.Entries[0].Key, i.Entries[0].Codec, i.EmitterAddr.String()) - }*/ - - res2, _ := client.GetActorEvents(ctx, &types.ActorEventFilter{ - MinEpoch: 0, - MaxEpoch: -1, - Addresses: []address.Address{cf2}, - //EthAddresses: []ethtypes.EthAddress{ - // contract1, - //}, - }) - for _, res := range res2 { - res := res - fmt.Println("Emitter Address is", res.EmitterAddr.String()) - for _, entry := range res.Entries { - fmt.Println("Hello", entry.Key, entry.Codec, string(entry.Value)) - } - - } - fmt.Println("Hello", res2[0].Entries[0].Key, res2[0].Entries[0].Codec, res2[0].EmitterAddr.String()) - - elogs, err := parseEthLogsFromFilterResult(res) - require.NoError(t, err) - AssertEthLogs(t, elogs, tc.expected, messages) - }) - } -} diff --git a/itests/direct_data_onboard_test.go b/itests/direct_data_onboard_test.go index 8377caecd..f769bdfb4 100644 --- a/itests/direct_data_onboard_test.go +++ b/itests/direct_data_onboard_test.go @@ -4,8 +4,10 @@ import ( "bytes" "context" "crypto/rand" + "encoding/json" "fmt" "os" + "sort" "strings" "testing" "time" @@ -149,22 +151,28 @@ func TestOnboardRawPieceVerified(t *testing.T) { kit.Account(verifiedClientKey, abi.NewTokenAmount(bal.Int64())), ) - evtChan, err := miner.FullNode.SubscribeActorEvents(ctx, &types.SubActorEventFilter{ + minerEvtsChan, err := miner.FullNode.SubscribeActorEvents(ctx, &types.SubActorEventFilter{ Filter: types.ActorEventFilter{ - MinEpoch: -1, - MaxEpoch: -1, + Addresses: []address.Address{miner.ActorAddr}, }, Prefill: true, }) require.NoError(t, err) - events := make([]types.ActorEvent, 0) - go func() { - for e := range evtChan { - fmt.Printf("%s Got ActorEvent: %+v", time.Now().Format(time.StampMilli), e) - events = append(events, *e) - } - }() + // only consume and match sector-activated events + sectorActivatedCbor, err := ipld.Encode(basicnode.NewString("sector-activated"), dagcbor.Encode) + require.NoError(t, err) + sectorActivatedEvtsCh, err := miner.FullNode.SubscribeActorEvents(ctx, &types.SubActorEventFilter{ + Filter: types.ActorEventFilter{ + Fields: map[string][]types.ActorEventBlock{ + "$type": { + {Codec: 0x51, Value: sectorActivatedCbor}, + }, + }, + }, + Prefill: true, + }) + require.NoError(t, err) ens.InterconnectAll().BeginMiningMustPost(blocktime) @@ -319,26 +327,112 @@ func TestOnboardRawPieceVerified(t *testing.T) { allocations, err = client.StateGetAllocations(ctx, verifiedClientAddr, types.EmptyTSK) require.NoError(t, err) require.Len(t, allocations, 0) + eventsFromMessages := buildActorEventsFromMessages(ctx, t, miner.FullNode) + fmt.Println("eventsFromMessages", eventsFromMessages) + writeEventsToFile(ctx, t, miner.FullNode, eventsFromMessages) - evts, err := miner.FullNode.GetActorEvents(ctx, &types.ActorEventFilter{ - MinEpoch: -1, - MaxEpoch: -1, + /* --- Tests for the Actor events API --- */ + // Match events from Get API and receipts + allEvtsFromGetAPI, err := miner.FullNode.GetActorEvents(ctx, &types.ActorEventFilter{ + FromEpoch: "earliest", + ToEpoch: "latest", }) require.NoError(t, err) - for _, evt := range evts { - fmt.Printf("Got ActorEvent: %+v", evt) - } + matchEvents(t, eventsFromMessages, getEventsArray(allEvtsFromGetAPI)) - eventsFromMessages := buildActorEventsFromMessages(t, ctx, miner.FullNode) - writeEventsToFile(t, ctx, miner.FullNode, eventsFromMessages) - for _, evt := range evts { - fmt.Printf("Got ActorEvent from messages: %+v", evt) + // match Miner Actor events from subscription channel and Miner Actor events obtained from receipts + var subMinerEvts []types.ActorEvent + for evt := range minerEvtsChan { + subMinerEvts = append(subMinerEvts, *evt) + if len(subMinerEvts) == 4 { + break + } } + var allMinerEvts []types.ActorEvent + for _, evt := range eventsFromMessages { + if evt.EmitterAddr == miner.ActorAddr { + allMinerEvts = append(allMinerEvts, evt) + } + } + matchEvents(t, allMinerEvts, subMinerEvts) - // TODO: compare GetActorEvents & SubscribeActorEvents & eventsFromMessages for equality + // Match pre-filled events from sector activated channel and events obtained from receipts + var prefillSectorActivatedEvts []types.ActorEvent + for evt := range sectorActivatedEvtsCh { + prefillSectorActivatedEvts = append(prefillSectorActivatedEvts, *evt) + if len(prefillSectorActivatedEvts) == 2 { + break + } + } + require.Len(t, prefillSectorActivatedEvts, 2) + var sectorActivatedEvts []types.ActorEvent + for _, evt := range eventsFromMessages { + for _, entry := range evt.Entries { + if entry.Key == "$type" && bytes.Equal(entry.Value, sectorActivatedCbor) { + sectorActivatedEvts = append(sectorActivatedEvts, evt) + break + } + } + } + matchEvents(t, sectorActivatedEvts, prefillSectorActivatedEvts) + + // Match pre-filled events from subscription channel and events obtained from receipts + allEvtsCh, err := miner.FullNode.SubscribeActorEvents(ctx, &types.SubActorEventFilter{ + Filter: types.ActorEventFilter{ + FromEpoch: "earliest", + ToEpoch: "latest", + }, + Prefill: true, + }) + require.NoError(t, err) + var prefillEvts []types.ActorEvent + for evt := range allEvtsCh { + prefillEvts = append(prefillEvts, *evt) + if len(prefillEvts) == len(eventsFromMessages) { + break + } + } + matchEvents(t, eventsFromMessages, prefillEvts) } -func buildActorEventsFromMessages(t *testing.T, ctx context.Context, node v1api.FullNode) []types.ActorEvent { +func getEventsArray(ptr []*types.ActorEvent) []types.ActorEvent { + var evts []types.ActorEvent + for _, evt := range ptr { + evts = append(evts, *evt) + } + return evts +} + +func matchEvents(t *testing.T, exp []types.ActorEvent, actual []types.ActorEvent) { + // height and tipset cid can mismatch because expected events are sourced using APIs that can put in different tipsets + for i := range exp { + exp[i].Height = 0 + exp[i].TipSetKey = cid.Undef + } + for i := range actual { + actual[i].Height = 0 + actual[i].TipSetKey = cid.Undef + } + + require.Equal(t, len(exp), len(actual)) + // marshal both arrays to json, sort by json, and compare + bz1, err := json.Marshal(exp) + require.NoError(t, err) + sort.Slice(bz1, func(i, j int) bool { + return bz1[i] <= bz1[j] + }) + + bz2, err := json.Marshal(actual) + require.NoError(t, err) + sort.Slice(bz2, func(i, j int) bool { + return bz2[i] <= bz2[j] + }) + fmt.Println("bz1", string(bz1)) + fmt.Println("bz2", string(bz2)) + require.True(t, bytes.Equal(bz1, bz2)) +} + +func buildActorEventsFromMessages(ctx context.Context, t *testing.T, node v1api.FullNode) []types.ActorEvent { actorEvents := make([]types.ActorEvent, 0) head, err := node.ChainHead(ctx) @@ -374,7 +468,7 @@ func buildActorEventsFromMessages(t *testing.T, ctx context.Context, node v1api. Entries: evt.Entries, EmitterAddr: addr, Reverted: false, - Height: abi.ChainEpoch(height), + Height: ts.Height(), TipSetKey: tsCid, MsgCid: m.Cid, }) @@ -386,7 +480,7 @@ func buildActorEventsFromMessages(t *testing.T, ctx context.Context, node v1api. return actorEvents } -func writeEventsToFile(t *testing.T, ctx context.Context, node v1api.FullNode, events []types.ActorEvent) { +func writeEventsToFile(ctx context.Context, t *testing.T, node v1api.FullNode, events []types.ActorEvent) { file, err := os.Create("block.out") require.NoError(t, err) defer func() { @@ -417,11 +511,11 @@ func writeEventsToFile(t *testing.T, ctx context.Context, node v1api.FullNode, e if e.Key == "$type" && bytes.Equal(e.Value, claimKeyCbor) { isClaim = true } else if isClaim && e.Key == "id" { - nd, err := ipld.DecodeUsingPrototype([]byte(e.Value), dagcbor.Decode, bindnode.Prototype((*int64)(nil), nil)) + nd, err := ipld.DecodeUsingPrototype(e.Value, dagcbor.Decode, bindnode.Prototype((*int64)(nil), nil)) require.NoError(t, err) claimId = *bindnode.Unwrap(nd).(*int64) } else if isClaim && e.Key == "provider" { - nd, err := ipld.DecodeUsingPrototype([]byte(e.Value), dagcbor.Decode, bindnode.Prototype((*int64)(nil), nil)) + nd, err := ipld.DecodeUsingPrototype(e.Value, dagcbor.Decode, bindnode.Prototype((*int64)(nil), nil)) require.NoError(t, err) providerId = *bindnode.Unwrap(nd).(*int64) } diff --git a/itests/kit/node_opts.go b/itests/kit/node_opts.go index 51db8d8b2..025334203 100644 --- a/itests/kit/node_opts.go +++ b/itests/kit/node_opts.go @@ -1,6 +1,8 @@ package kit import ( + "math" + "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" @@ -64,6 +66,7 @@ var DefaultNodeOpts = nodeOpts{ cfg.Fevm.EnableEthRPC = true cfg.Fevm.EnableActorEventsAPI = true + cfg.Fevm.Events.MaxFilterHeightRange = math.MaxInt64 return nil }, }, diff --git a/node/config/doc_gen.go b/node/config/doc_gen.go index a1ee2634f..1e8252ca9 100644 --- a/node/config/doc_gen.go +++ b/node/config/doc_gen.go @@ -362,9 +362,8 @@ see https://lotus.filecoin.io/storage-providers/advanced-configurations/market/# Name: "DisableRealTimeFilterAPI", Type: "bool", - Comment: `EnableEthRPC enables APIs that -DisableRealTimeFilterAPI will disable the RealTimeFilterAPI that can create and query filters for actor events as they are emitted. -The API is enabled when EnableEthRPC is true, but can be disabled selectively with this flag.`, + Comment: `DisableRealTimeFilterAPI will disable the RealTimeFilterAPI that can create and query filters for actor events as they are emitted. +The API is enabled when EnableEthRPC or EnableActorEventsAPI is true, but can be disabled selectively with this flag.`, }, { Name: "DisableHistoricFilterAPI", @@ -372,7 +371,7 @@ The API is enabled when EnableEthRPC is true, but can be disabled selectively wi Comment: `DisableHistoricFilterAPI will disable the HistoricFilterAPI that can create and query filters for actor events that occurred in the past. HistoricFilterAPI maintains a queryable index of events. -The API is enabled when EnableEthRPC is true, but can be disabled selectively with this flag.`, +The API is enabled when EnableEthRPC or EnableActorEventsAPI is true, but can be disabled selectively with this flag.`, }, { Name: "FilterTTL", @@ -459,7 +458,8 @@ This will also enable the RealTimeFilterAPI and HistoricFilterAPI by default, bu Name: "EnableActorEventsAPI", Type: "bool", - Comment: ``, + Comment: `EnableActorEventsAPI enables the Actor events API that enables clients to consume events emitted by (smart contracts + built-in Actors). +This will also enable the RealTimeFilterAPI and HistoricFilterAPI by default, but they can be disabled by config options above.`, }, { Name: "EthTxHashMappingLifetimeDays", diff --git a/node/config/types.go b/node/config/types.go index 57e06c093..6fb7389ff 100644 --- a/node/config/types.go +++ b/node/config/types.go @@ -786,6 +786,8 @@ type FevmConfig struct { // This will also enable the RealTimeFilterAPI and HistoricFilterAPI by default, but they can be disabled by config options above. EnableEthRPC bool + // EnableActorEventsAPI enables the Actor events API that enables clients to consume events emitted by (smart contracts + built-in Actors). + // This will also enable the RealTimeFilterAPI and HistoricFilterAPI by default, but they can be disabled by config options above. EnableActorEventsAPI bool // EthTxHashMappingLifetimeDays the transaction hash lookup database will delete mappings that have been stored for more than x days @@ -796,14 +798,13 @@ type FevmConfig struct { } type Events struct { - // EnableEthRPC enables APIs that // DisableRealTimeFilterAPI will disable the RealTimeFilterAPI that can create and query filters for actor events as they are emitted. - // The API is enabled when EnableEthRPC is true, but can be disabled selectively with this flag. + // The API is enabled when EnableEthRPC or EnableActorEventsAPI is true, but can be disabled selectively with this flag. DisableRealTimeFilterAPI bool // DisableHistoricFilterAPI will disable the HistoricFilterAPI that can create and query filters for actor events // that occurred in the past. HistoricFilterAPI maintains a queryable index of events. - // The API is enabled when EnableEthRPC is true, but can be disabled selectively with this flag. + // The API is enabled when EnableEthRPC or EnableActorEventsAPI is true, but can be disabled selectively with this flag. DisableHistoricFilterAPI bool // FilterTTL specifies the time to live for actor event filters. Filters that haven't been accessed longer than diff --git a/node/impl/full/actor_event.go b/node/impl/full/actor_event.go index a1b038dc6..ce555d6d2 100644 --- a/node/impl/full/actor_event.go +++ b/node/impl/full/actor_event.go @@ -3,6 +3,7 @@ package full import ( "context" "fmt" + "strings" "github.com/ipfs/go-cid" "go.uber.org/fx" @@ -11,6 +12,7 @@ import ( "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/events/filter" + "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" ) @@ -27,6 +29,7 @@ var ( type ActorEvent struct { EventFilterManager *filter.EventFilterManager MaxFilterHeightRange abi.ChainEpoch + Chain *store.ChainStore } var _ ActorEventAPI = (*ActorEvent)(nil) @@ -41,8 +44,13 @@ func (a *ActorEvent) GetActorEvents(ctx context.Context, filter *types.ActorEven return nil, api.ErrNotSupported } + params, err := a.parseFilter(filter) + if err != nil { + return nil, err + } + // Create a temporary filter - f, err := a.EventFilterManager.Install(ctx, filter.MinEpoch, filter.MaxEpoch, cid.Undef, filter.Addresses, filter.Fields, false) + f, err := a.EventFilterManager.Install(ctx, params.MinHeight, params.MaxHeight, params.TipSetCid, filter.Addresses, filter.Fields, false) if err != nil { return nil, err } @@ -52,11 +60,58 @@ func (a *ActorEvent) GetActorEvents(ctx context.Context, filter *types.ActorEven return evs, err } +type filterParams struct { + MinHeight abi.ChainEpoch + MaxHeight abi.ChainEpoch + TipSetCid cid.Cid +} + +func (a *ActorEvent) parseFilter(f *types.ActorEventFilter) (*filterParams, error) { + if f.TipSetCid != nil { + if len(f.FromEpoch) != 0 || len(f.ToEpoch) != 0 { + return nil, fmt.Errorf("cannot specify both TipSetCid and FromEpoch/ToEpoch") + } + + return &filterParams{ + MinHeight: 0, + MaxHeight: 0, + TipSetCid: *f.TipSetCid, + }, nil + } + + from := f.FromEpoch + if len(from) != 0 && from != "latest" && from != "earliest" && !strings.HasPrefix(from, "0x") { + from = "0x" + from + } + + to := f.ToEpoch + if len(to) != 0 && to != "latest" && to != "earliest" && !strings.HasPrefix(to, "0x") { + to = "0x" + to + } + + min, max, err := parseBlockRange(a.Chain.GetHeaviestTipSet().Height(), &from, &to, a.MaxFilterHeightRange) + if err != nil { + return nil, err + } + + return &filterParams{ + MinHeight: min, + MaxHeight: max, + TipSetCid: cid.Undef, + }, nil +} + func (a *ActorEvent) SubscribeActorEvents(ctx context.Context, f *types.SubActorEventFilter) (<-chan *types.ActorEvent, error) { if a.EventFilterManager == nil { return nil, api.ErrNotSupported } - fm, err := a.EventFilterManager.Install(ctx, f.Filter.MinEpoch, f.Filter.MaxEpoch, cid.Undef, f.Filter.Addresses, f.Filter.Fields, false) + + params, err := a.parseFilter(&f.Filter) + if err != nil { + return nil, err + } + + fm, err := a.EventFilterManager.Install(ctx, params.MinHeight, params.MaxHeight, params.TipSetCid, f.Filter.Addresses, f.Filter.Fields, false) if err != nil { return nil, err } diff --git a/node/impl/full/eth.go b/node/impl/full/eth.go index 6ab0a3d4e..f45b35a5a 100644 --- a/node/impl/full/eth.go +++ b/node/impl/full/eth.go @@ -1264,6 +1264,59 @@ func (e *EthEvent) EthGetFilterLogs(ctx context.Context, id ethtypes.EthFilterID return nil, xerrors.Errorf("wrong filter type") } +func parseBlockRange(heaviest abi.ChainEpoch, fromBlock, toBlock *string, maxRange abi.ChainEpoch) (minHeight abi.ChainEpoch, maxHeight abi.ChainEpoch, err error) { + if fromBlock == nil || *fromBlock == "latest" || len(*fromBlock) == 0 { + minHeight = heaviest + } else if *fromBlock == "earliest" { + minHeight = 0 + } else { + if !strings.HasPrefix(*fromBlock, "0x") { + return 0, 0, xerrors.Errorf("FromBlock is not a hex") + } + epoch, err := ethtypes.EthUint64FromHex(*fromBlock) + if err != nil { + return 0, 0, xerrors.Errorf("invalid epoch") + } + minHeight = abi.ChainEpoch(epoch) + } + + if toBlock == nil || *toBlock == "latest" || len(*toBlock) == 0 { + // here latest means the latest at the time + maxHeight = -1 + } else if *toBlock == "earliest" { + maxHeight = 0 + } else { + if !strings.HasPrefix(*toBlock, "0x") { + return 0, 0, xerrors.Errorf("ToBlock is not a hex") + } + epoch, err := ethtypes.EthUint64FromHex(*toBlock) + if err != nil { + return 0, 0, xerrors.Errorf("invalid epoch") + } + maxHeight = abi.ChainEpoch(epoch) + } + + // Validate height ranges are within limits set by node operator + if minHeight == -1 && maxHeight > 0 { + // Here the client is looking for events between the head and some future height + if maxHeight-heaviest > maxRange { + return 0, 0, xerrors.Errorf("invalid epoch range: to block is too far in the future (maximum: %d)", maxRange) + } + } else if minHeight >= 0 && maxHeight == -1 { + // Here the client is looking for events between some time in the past and the current head + if heaviest-minHeight > maxRange { + return 0, 0, xerrors.Errorf("invalid epoch range: from block is too far in the past (maximum: %d)", maxRange) + } + } else if minHeight >= 0 && maxHeight >= 0 { + if minHeight > maxHeight { + return 0, 0, xerrors.Errorf("invalid epoch range: to block (%d) must be after from block (%d)", minHeight, maxHeight) + } else if maxHeight-minHeight > maxRange { + return 0, 0, xerrors.Errorf("invalid epoch range: range between to and from blocks is too large (maximum: %d)", maxRange) + } + } + return minHeight, maxHeight, nil +} + func (e *EthEvent) installEthFilterSpec(ctx context.Context, filterSpec *ethtypes.EthFilterSpec) (*filter.EventFilter, error) { var ( minHeight abi.ChainEpoch @@ -1280,64 +1333,11 @@ func (e *EthEvent) installEthFilterSpec(ctx context.Context, filterSpec *ethtype tipsetCid = filterSpec.BlockHash.ToCid() } else { - if filterSpec.FromBlock == nil || *filterSpec.FromBlock == "latest" { - ts := e.Chain.GetHeaviestTipSet() - minHeight = ts.Height() - } else if *filterSpec.FromBlock == "earliest" { - minHeight = 0 - } else if *filterSpec.FromBlock == "pending" { - return nil, api.ErrNotSupported - } else { - if !strings.HasPrefix(*filterSpec.FromBlock, "0x") { - return nil, xerrors.Errorf("FromBlock is not a hex") - } - epoch, err := ethtypes.EthUint64FromHex(*filterSpec.FromBlock) - if err != nil { - return nil, xerrors.Errorf("invalid epoch") - } - minHeight = abi.ChainEpoch(epoch) + var err error + minHeight, maxHeight, err = parseBlockRange(e.Chain.GetHeaviestTipSet().Height(), filterSpec.FromBlock, filterSpec.ToBlock, e.MaxFilterHeightRange) + if err != nil { + return nil, err } - - if filterSpec.ToBlock == nil || *filterSpec.ToBlock == "latest" { - // here latest means the latest at the time - maxHeight = -1 - } else if *filterSpec.ToBlock == "earliest" { - maxHeight = 0 - } else if *filterSpec.ToBlock == "pending" { - return nil, api.ErrNotSupported - } else { - if !strings.HasPrefix(*filterSpec.ToBlock, "0x") { - return nil, xerrors.Errorf("ToBlock is not a hex") - } - epoch, err := ethtypes.EthUint64FromHex(*filterSpec.ToBlock) - if err != nil { - return nil, xerrors.Errorf("invalid epoch") - } - maxHeight = abi.ChainEpoch(epoch) - } - - // Validate height ranges are within limits set by node operator - if minHeight == -1 && maxHeight > 0 { - // Here the client is looking for events between the head and some future height - ts := e.Chain.GetHeaviestTipSet() - if maxHeight-ts.Height() > e.MaxFilterHeightRange { - return nil, xerrors.Errorf("invalid epoch range: to block is too far in the future (maximum: %d)", e.MaxFilterHeightRange) - } - } else if minHeight >= 0 && maxHeight == -1 { - // Here the client is looking for events between some time in the past and the current head - ts := e.Chain.GetHeaviestTipSet() - if ts.Height()-minHeight > e.MaxFilterHeightRange { - return nil, xerrors.Errorf("invalid epoch range: from block is too far in the past (maximum: %d)", e.MaxFilterHeightRange) - } - - } else if minHeight >= 0 && maxHeight >= 0 { - if minHeight > maxHeight { - return nil, xerrors.Errorf("invalid epoch range: to block (%d) must be after from block (%d)", minHeight, maxHeight) - } else if maxHeight-minHeight > e.MaxFilterHeightRange { - return nil, xerrors.Errorf("invalid epoch range: range between to and from blocks is too large (maximum: %d)", e.MaxFilterHeightRange) - } - } - } // Convert all addresses to filecoin f4 addresses @@ -1362,7 +1362,7 @@ func keysToKeysWithCodec(keys map[string][][]byte) map[string][]types.ActorEvent for k, v := range keys { for _, vv := range v { keysWithCodec[k] = append(keysWithCodec[k], types.ActorEventBlock{ - Codec: uint64(multicodec.Raw), + Codec: uint64(multicodec.Raw), // FEVM smart contract events are always encoded with the `raw` Codec. Value: vv, }) } diff --git a/node/impl/full/eth_test.go b/node/impl/full/eth_test.go index 05c3f2575..6f9d8f297 100644 --- a/node/impl/full/eth_test.go +++ b/node/impl/full/eth_test.go @@ -3,6 +3,7 @@ package full import ( "bytes" "encoding/hex" + "fmt" "testing" "github.com/ipfs/go-cid" @@ -10,12 +11,87 @@ import ( "github.com/stretchr/testify/require" cbg "github.com/whyrusleeping/cbor-gen" + "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types/ethtypes" ) +func TestParseBlockRange(t *testing.T) { + pstring := func(s string) *string { return &s } + + tcs := map[string]struct { + heaviest abi.ChainEpoch + from *string + to *string + maxRange abi.ChainEpoch + minOut abi.ChainEpoch + maxOut abi.ChainEpoch + errStr string + }{ + "fails when both are specified and range is greater than max allowed range": { + heaviest: 100, + from: pstring("0x100"), + to: pstring("0x200"), + maxRange: 10, + minOut: 0, + maxOut: 0, + errStr: "too large", + }, + "fails when min is specified and range is greater than max allowed range": { + heaviest: 500, + from: pstring("0x10"), + to: pstring("latest"), + maxRange: 10, + minOut: 0, + maxOut: 0, + errStr: "too far in the past", + }, + "fails when max is specified and range is greater than max allowed range": { + heaviest: 500, + from: pstring("earliest"), + to: pstring("0x10000"), + maxRange: 10, + minOut: 0, + maxOut: 0, + errStr: "too large", + }, + "works when range is valid": { + heaviest: 500, + from: pstring("earliest"), + to: pstring("latest"), + maxRange: 1000, + minOut: 0, + maxOut: -1, + }, + "works when range is valid and specified": { + heaviest: 500, + from: pstring("0x10"), + to: pstring("0x30"), + maxRange: 1000, + minOut: 16, + maxOut: 48, + }, + } + + for name, tc := range tcs { + tc2 := tc + t.Run(name, func(t *testing.T) { + min, max, err := parseBlockRange(tc2.heaviest, tc2.from, tc2.to, tc2.maxRange) + require.Equal(t, tc2.minOut, min) + require.Equal(t, tc2.maxOut, max) + if tc2.errStr != "" { + fmt.Println(err) + require.Error(t, err) + require.Contains(t, err.Error(), tc2.errStr) + } else { + require.NoError(t, err) + } + }) + } +} + func TestEthLogFromEvent(t *testing.T) { // basic empty data, topics, ok := ethLogFromEvent(nil) diff --git a/node/modules/actorevent.go b/node/modules/actorevent.go index 0f9049bf7..9cb7a4a16 100644 --- a/node/modules/actorevent.go +++ b/node/modules/actorevent.go @@ -2,15 +2,14 @@ package modules import ( "context" + "fmt" "path/filepath" "time" - "github.com/multiformats/go-varint" "go.uber.org/fx" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" - builtintypes "github.com/filecoin-project/go-state-types/builtin" "github.com/filecoin-project/lotus/chain/events" "github.com/filecoin-project/lotus/chain/events/filter" @@ -129,8 +128,9 @@ func EventFilterManager(cfg config.FevmConfig) func(helpers.MetricsCtx, repo.Loc fm := &filter.EventFilterManager{ ChainStore: cs, EventIndex: eventIndex, // will be nil unless EnableHistoricFilterAPI is true + // TODO: + // We don't need this address resolution anymore once https://github.com/filecoin-project/lotus/issues/11594 lands AddressResolver: func(ctx context.Context, emitter abi.ActorID, ts *types.TipSet) (address.Address, bool) { - // we only want to match using f4 addresses idAddr, err := address.NewIDAddress(uint64(emitter)) if err != nil { return address.Undef, false @@ -138,18 +138,11 @@ func EventFilterManager(cfg config.FevmConfig) func(helpers.MetricsCtx, repo.Loc actor, err := sm.LoadActor(ctx, idAddr, ts) if err != nil || actor.Address == nil { - return address.Undef, false + return idAddr, true } - // if robust address is not f4 then we won't match against it so bail early - if actor.Address.Protocol() != address.Delegated { - return address.Undef, false - } - // we have an f4 address, make sure it's assigned by the EAM - // What happens when we introduce events for built-in Actor events here ? - if namespace, _, err := varint.FromUvarint(actor.Address.Payload()); err != nil || namespace != builtintypes.EthereumAddressManagerActorID { - return address.Undef, false - } + fmt.Println("") + return *actor.Address, true }, @@ -175,9 +168,10 @@ func ActorEventAPI(cfg config.FevmConfig) func(helpers.MetricsCtx, repo.LockedRe return func(mctx helpers.MetricsCtx, r repo.LockedRepo, lc fx.Lifecycle, fm *filter.EventFilterManager, cs *store.ChainStore, sm *stmgr.StateManager, evapi EventAPI, mp *messagepool.MessagePool, stateapi full.StateAPI, chainapi full.ChainAPI) (*full.ActorEvent, error) { ee := &full.ActorEvent{ MaxFilterHeightRange: abi.ChainEpoch(cfg.Events.MaxFilterHeightRange), + Chain: cs, } - if !cfg.EnableActorEventsAPI { + if !cfg.EnableActorEventsAPI || cfg.Events.DisableRealTimeFilterAPI { // all Actor events functionality is disabled return ee, nil }