From b20ba9fac8abebe9e07cf0a708e655aed254c078 Mon Sep 17 00:00:00 2001 From: Raycho Mukelov Date: Wed, 9 Aug 2023 10:17:03 +0300 Subject: [PATCH] Refactor the types and API to accomodate user defined Log Entry Data Type and State Machine State type + user defined State Machine initialization/application functions --- doc/RAFT_Library_Roadmap_Proposal.odt | Bin 40678 -> 40541 bytes doc/road-map.md | 33 ++++++++++ raft_consensus.nim => raft.nim | 6 +- raft_consensus.nimble => raft.nimble | 9 +-- {raft_consensus => raft}/anti_entropy.nim | 2 +- {raft_consensus => raft}/consensus_module.nim | 2 +- .../log_compaction_module.nim | 2 +- .../membersip_change_module.nim | 2 +- {raft_consensus => raft}/protocol.nim | 27 ++++---- .../raft_api.nim | 33 ++++++---- {raft_consensus => raft}/types.nim | 58 +++++++++--------- tests/all_tests.nim | 2 +- 12 files changed, 112 insertions(+), 64 deletions(-) rename raft_consensus.nim => raft.nim (77%) rename raft_consensus.nimble => raft.nimble (86%) rename {raft_consensus => raft}/anti_entropy.nim (93%) rename {raft_consensus => raft}/consensus_module.nim (93%) rename {raft_consensus => raft}/log_compaction_module.nim (93%) rename {raft_consensus => raft}/membersip_change_module.nim (93%) rename {raft_consensus => raft}/protocol.nim (51%) rename raft_consensus/raft_consensus_api.nim => raft/raft_api.nim (51%) rename {raft_consensus => raft}/types.nim (66%) diff --git a/doc/RAFT_Library_Roadmap_Proposal.odt b/doc/RAFT_Library_Roadmap_Proposal.odt index a35fd9c19d5941c71c19f3fbd2181559b7549ac9..2f1f26d460dac12d6cce495b34f56298a78bf9ea 100644 GIT binary patch delta 38675 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 delta 38809 zcmY(q18^Ww(=HryV_O?+Y-eNJ)YN+qUie`+nbhZ{2@R)ze*lrl$H# zo$BuC^UOv&*kK14f`T+S1Ud)^33zl$J3 zlqcY?+EiYzq{j2LuhWNo`u6iYZ+aP3F@j8mbc6s&5B;9#W5u57>V49})U8ab&>ex! z(Juw+9<@hLlRc5GqCg`rbyCeJ1|5dFIay0Y$Pe>T=f}s9p2_zyX4XskVkT2a23464 zdh7n*T2=SWuUnXhE0MC}Tk7D^@4r$m3zh&G%cpSX49LBroN9)K}%?xjYDL zV%YsIe?jQYf#LsiszMIV9o(MDk zde*a{g>Xqo_i{|9#_T&en#)6NIjt=ZxZ;F`?4+LW~ zqG@^mIN8}LO#)rK1m4!%)i1Pt-G^IS$%oBO4b^?WUe&SgTK0+FW`%_sSJLz;Op?g) z9S$UW?*}XjRIff>Cz1YiS_poV(b8JKsYAb_${Q*u^AErE1W?k#t$T(YhrMEIvdYo_ z+`jBz*C$_V$Lx)q8WIs?WZ~OTcJMRJ5%wP&DP+O$K{e$Y{*D04kyhU{tWAm}N`(Uc@v4 zB1+e`)~}%t^H=MaDYHHYwe-D%%2a_ZEN<1TnZMt# z@OrhIui^0)=xpZoEaPzHDa;Yl4M?UJ^xd5VPJet&2wbDSKR7X6tr3Br;pd!FhA2KP&$d@ppJpL6Pkd1v^d0>>;Q5)QD?G4*# zn{RMJpURjC`ITpxp6^V#*!K40p)RK>c@xj#5RrP!kOTIu?@<5tBZubJBs#A28~9bqw$j3f_4&rW&St$cgx3xiRNNQ^?U=o#qVC% zIO1ZR@i)0ILdZsa-Z+#Oj+Zvq()0uk zsK3P_V|?z?0X|dU1I@!O{l3yLqDZKwn>jvil~xtwveZJj6%#IbF<3OvcRRgdglZTFxSK)UQY?kz4h_nVH|b_4xj!jh@}R z@wr|Ge}5|CiNIzG2r-le(e<{ci&wdsYqk~INDejCNe!_kvr`;pAb0v$N3o$kCT;ky zyU9+ZEFOy2A8N~DS8VNUcz*#zv)Pu8X75eo?p&!etUAT&fs+Bdr-xs)=3996%_;#G z%oJB}rf0tX)A8jVwgiI+m3+=I?zO^HFg$rEr^Y|v{dtsus?t4tOFOZBROg&r8GF@G zI&Z&1dIjgdN?RE&J1Et<;&JNN<+DiDdSXIMRf`?og9||DHl>FD!ig>1 zoY00}B6cr)%AO=YoQJ8@Pyr_+8jB0kn6K)?(#p%rOJflzc>lUoc{l3}=pY*W@T>-` zn6?Gn-Mg{9^`b#&-x3)+L~?uz;7U{ktRYY?L#~z!sjr#IUg-0za%fdN&WG ziJU$hHnnB|CVs-k825)5>6l>5^-<{LRw(23Nk99;9%eoaOg0|xEQby zSl_U4QQ$DpF<>#N5D8hZ@Tl-feqfVxk|KgrVt%8*!(_yR`$0rbLrTE@9gBp7f`pvr z`ws>hax%Ifv)1&;bf&Iu=eQT2_{SgPV<$hlh=YhmD(ulbwf~ z2aiF7kXf9BUhoIII31r7C$9+OPX&5m4IUvWE-^K3*?6X48)QgKSX&r zlz&ny@-qtZ3W^Hy$qKMa3-PK8(W!}Y8Ax-OD*Y4}6BmDckExJLstx=owfUYPcB6x|yn* znf^Ala<;Jdbg?vcwz2oHxA5}v(z5o|vkx?~@ilb`vGDrq{3p=CJI2~K(Zesy(J$U1 zFvTe(!y_oxJt+B4NP=g0ns-d58xWcE=Wn5}wPv8buCI+jh>L09ALqYb7O{YR01z1F z>yhYdof7Pv6X{+U>lGXv5)=_1^*1s+HYGkHC?z&LIXT%kIy*3_BqY5iF+DFcJ1Z)? zG9srjEw?zas5z^;BRkr+Bq<;-J+V41s5Ub+J1eIuJ32onqdX_REHACR7|3kQiD)iL zC@Lx{D6g-ot}Lx>tt&66uBvXWEoo|QPOj+9s2R+z=qYR(F7B9VXz%apZK>>@tms*2 z?Hy?xUTL4+Y98O|nAz{GNFJ=s7;Y-;Yj2$CD4yu99PMpc=&4v9Zrq&i+?i;Hm?^q_O`ayCU!2yckULp59f9-7EdnMclOuz z&(@C5myho^Z(fgE8?cV6yJ@9yqyfybAJ=ZE{3_m{`p z_m}(6&(D7!5no?lNc1{&ARzXzk|KgCZmSm`o;u2!iPw=vOWJa-&aM+{d{cLFAKr6! z<1+H<^3HE5@_cGW&QtL$$_K{*ASl(L>#XYm%-L=!4va3_lu*5NVl5Co@UJo0^uDkn zuy8w=tN}U@_6X=y%gIZFesxv0sNXxm`nLHI4cI0wx;`t;K&KJQV|z?f(T) zU;-{YRrG%$*ECRcBG>fbEMWEN*mWyUJ4@#Mvx>sPW6u)!SWQKu@(FwZ_@ES?lK5Z7 zmh`Gb@n-a)f9CSMKWz6FOr^XocGZCQpQm_8es;T*I3WuFsPT$gI>tY!y-vhMYq^{R%%-I9Kwlp3^x8)u-i#9vhXBK7zGG*p0??vncIXODKx;rlEwnGf@^Cj1y2f`-u$;qjiz z?>x@ZcZVHL+N7lmgN@x2NxC$@+l@8Ac_78m82R>o%=~)`tLjpp)k5Si@CWB(_1R@T z4g2BvHj4k_;C2gmrf-c75-L4`E>klTkXKkNJ8t>*{;Xa067Sh(QGYVsG*G5?wliK< zrdN>-JX@C6D14ljs-u59?SgNG>5|HVi=E?-aHEuPqdFFMbgjqzkbV}7LB3Tl__Nk@ zaiDqzW2B&@U=c$A=U15ngum)>cZ&<25NmdvPL=vZXg=JUQdgTV=-U`50QX9>NNNO; zuv$&^%_rIn;`Gi?rXwA zn!)>NdHRuBUA@CLe6)2DNjO;nCX?9n_Eg8+(@)-#U*@e}I+!0o5wnlR-_=Dk%C>@*}+Ej4Zy-x zrCg@uhaN=yBD78&%p|jgHm`)0Z}$}===D~>LD!{Kz=@jA5s;i9h{fZ-`L$7Ki^9Ns z%8#H$fL@;^l&S)3-YY=~MMCH@c|kusB`Q8au=!I`c>S z^m?MxSMOlI{66VdI?ERh#*+-89^SwO!}LE^Bdz9;$OLLF#?hR71a&*hmpM1U@`sFJ z8CZ|ERp0R&zdC|z&iADSw>)51qJ;%H1aRi14sX~YRJQ?!VW0DD@{dpQe|0fzAbrTV zpTtnw}`*xpaYC6rTZzkNI9`w*Xp5-u zegFyiB#bE2_5`k_Ub;GzaN}S6zj91;lZ#N858JZDIoka0%bT8^m>+ISsSOnQkzkh# z`M1Ul9_xx%4k`X80{?H^eTWOa+NtU+DzDv8zk}nQBqJd@w=%%3!zw8PT8rg39t-j) zmbyAkW$)3{w7zzHDVWKKK2JcQd+vi@1>pjoe!80SwlDF0v!6h+8o|;cGgLk^D|EkW zi?+FIzYX>@KJGb+dR^EnZO`_Xce&<4;`qxK!hML;G6{JFEN;qi_5UCgi6j~#61Z^3 zKmD2_61!NreOKfGsq}sV;+A#+$#8%79wYTHQj&Lub0@f9EZ!ZxslEI@Au3mYWJ~TV-nPT0UtV&+W=o;Arc{yOIwL{ovS(ZwCRgJ#O6d0_6UacIN?} zc=viSYA1cy4^1dgnLk&c*7HdsJE{_>l(y-c7x9Sg#_VdS34W zvS<<;R-ZAg!gfEtgR_f%#mAH%e9X%Mlu=(*bRb`0Sw6Q6XpLTYuily0L5*iv!w3N0 z?b5B^7rc4HP7!RCx-V7OljBeFu)~iR=$bP!DrSm+zM|7+-pSRIimjLJmOR3Tw`h@R z{&EnqUc2ubEf&DHk4yJBKOIBe@6*)O>M-{_9y2!A?E#8MT?p#xADG_GEl*eyWk?rL zuj-GoblZ!wRjBt>_uKD?4^jA)?|h%U*Lf>nYoK1u{2)byWKO^4ag@K*p^4}DReTIL zz&sX%y?q%DkY`VydOqNMC0f5C4C+^(yA0!ydCLfnIpTqFUpJ(W4mBwp-*_AvdXLA^ zE3nD?wtXc0By8e7PUqXEJDN;SN2~06me!`d!wfZ^TzhTv#aS=uI6>j(U^Cyv|jx}n<_ACzp zvqlw*U1&gT^0Pl&pQU1gcfjf$fFkf^aG!zreBD-8hKh!!#?udEtVgj3MI(Iwm&jIT zl~3-Y#e1G9g0}jyF`1wO-Y2iRh!SF+Zx7mv8+JTpNvR8n0@c}pp3MC3axCs zKVGQ9hQc!Nh&bF%9kzDHC1bxnWvZ`lk#T6j-E|pW&bPhm6*iC@4OST{o&~ zR9{gQx<(_|x|_VHQH2|GwXa?wJwB&j*r(Y?zLMXX{MpkcnqU(&`0E+)6rdi+A&#@? zKhBuy(Kg!Y=}|xLp;}UXjtw@D0gVzQ2q>09QD*@zF%yg#OKT=?iRQH<=jxBI9#bF*+y7E*0~sap5-;IcUVC#kMCCcuR2k};)WxaTrdtfN~X*8_~Feqwbz>Beh%kvHl;gOgqR2<2Z)bse%^^FJ--%BCCZv_DrAV-AR`ta6}Q&}rHC8Kwvz$_0P z3=g?Sn!}fYiqkhswpP&tZokBc%53kqmOO7SoChcsr;MHUG5yw8b)L-w?(Olmo{<2a z_l@m5fiVBA4S9E=rSB}IDqbMGwj^zyBiNu!uL7f~+u#Ont_b2txm`HUK~Rq4)hEpB zf6%UvT(oK_I8U26;YZHa(>I(hfwT5@ugeCBvB2w1Y#lGW$VcypGZT=Wmg|uMaOh@LC}u$*{+9G)545) zn+K8Zi%N?Maxk+v-p{yPAs9ZD7#pN??i`$sC1?x&yY#I+xdE7wWu0@PrSbxW)MsX=$IoB52QRH!^F6@j|yH%(G?uk4vP*4Oea< zo=QVB-sEh2J$kV@;JQ_&hQpHW!Mk6FroGQ+YNUVY)sQMMTJ%jVe^301O?PYLA;99i zC+{0ly6i2?5qMw!t%ZGt5mP$ymieY?COdz!-;Hn3mXE(}lx;K>8jOEl}R!$dCJa_$n5 znEQttZanRot7AfAQJk}Nrcvsbt_b@} zayN4N^uBU!XM#}!HLLh9)QDPJwS6i_rtnduo1r$8s+O+qW~gA9t=8N2`_jlguAD$j z1cqGPU~dxv09PMgisGt^wokxg<}8%jkG1u6i`R1gv%f9uM)}>Zg=;oQMpC*d%GI(j z+dfGoJj+|=3})^NgH?Ws{o>nqL4J)$fMD17?kX;_KGQR^x)r($J$^=A6kY+IM%)Pi$E znqfBeCW`dE*}cf>X0r0>+f(+M22b*u1^TafEb>OH~;-hbi zBhbtXz{q7ogP}+hg|FLTG09C7WZOhGMV`Q)Y>K`D*o-~J;Lkh?@I05QA1ybAuj1N1 z;;K!*=85g>a+EjSzhSoR(T7$Fk1v}$)pp+)ey@nbk7z^uthQbdjF{%=%_Q32GG=ViH5JQXAFNBHMLCZW{^zcKVI|w z^&!B^)GvAd4c{3c&j}*U%ems`u#>&lXVA!V=s;_)>c$2h_1k33wa@=OT#lrlxZ=?c z`hI-i;VLh0`or4M>I0iN{gf*tLb{m;Y?r`>!0eJMyYuY_zIo<>3wo;#KLJk?BbMAj zBk*cISm@lj`_BWfK~zXrh2XvsxBx9i>cQf+W|u>o^pk~&3!+FSL4G=<9m!rO>P zq{_FD2j=k59(V!aqNvdLP$K{jXhwcmy79 zcsp2L`{m57?QQShL&rzSHDGLA#TGc?7f9=SOOomHPV(~RO- zPSMHy#`>M5kFXz&oYhA7g|hBee{w-(z5X_=CbO5K6Wn#ngqMb|Pli=^!s;yw?j9Xw zSZFz4DxT)y5uTJe;`|MrUn`?XdQHRj?*rz)b_HOqFc*nn!CN>QcnZP-P&a7JZG?DF z`D>eir+KVE&F)GgWw}7PlJ6)&!xtvd!4I>ys+_VrQLu=W)zqlvv9Di1X$qA5`uYQ- zYGc(9X%VMYp_Hf8pe;I+n!t*Qdb#=(xc`<$Z`bgNUYdvZxB(!v$g@rzj6>d!-5)=a z#3u>f=rptvuVKxBDvSyOPmK+IMM@}m7h)&+{}(n?@9GF&bzu63NWZoQN&Rm=e1=| z(H~dd^F^tkuZmG}J0D9iUCjOEC!b&%AJ+-gOp{ute;-QURic6{|ybHCu zI)yNsfS+`NPxYCnvcZ5}<@?Op4?8glmYs51G_T03Y!2@3Rl-vc54r3b zErwQ7)HIZiD(PH+usyEB1Jcs5(Wo1uaE+54l_R_1iAr4#BT`Q1sOZR#a27@H0=9#f z6fQXCI#WCU-e&TKD>u@a?{SS4g5CWX`6C%7w6rs=?(ST0{=a;W;291hA21KIo2){l zos`KB(LKj~^}Hv2ed^gQ*?p1q6Z5w`28+w`T#0p0<}-VMJ~BARN|FuJ_;Tdk(l7+* z?HCGF^M+>47CZy{@8c8Vgjh#$gy%#7;5Ns~%xFWAVTH~TWzm$WhI>+cZ)PwJF^94E z+O$$k^fZlN+x^)Ri1+fw!#`ub{8TSa1AU#31|1lL+~)N17jpb})tU*|kB=0^?{lOx zhmqzKy`3ij{IHqRZy{}um9jp1wK$d{-ZUMHXulBqQ90(oKkff!fafD|(BTGVbk(2k z;TywD__ykc&zuQala)4f)jXXK&?#&_5SS1*hF6hfWdyT_K2SA9O@^6OEiA^6Q!%G_ zvqLlGaV8F_74FnRtn*Ku^1}SW5b{z~F%g{lvqn4wppDdvGZyzDTYcZi2{8^GxZk8| zAVhj;)-MZWjsDiCkyx!p6DS}Bjc~?jd{0WYF<7ik^7XH4+)I2f!~Pu^4~p)%qxLc; zSosC3FOe68W9}~cw0_Qi_n*NH)J-X=QxhS3;H)A7&QG&9R3jDYoSe z(@$`1-LH2#?%7OC66a-rO#j`P5|wGFsR8>r@|e0$HT@@j&rK*i`LS&BfsEty*=qg@ z6I`XSaZq3!KYQN9mCi4%dmX?MnhZ!3&PTTds9>xta*W0jCImi3&OWu*gU;XAzJ$8k zq67`jmIVsA=O&b`tB=eU$f5=i1`OwZU4)LJslwwgw9TQuI7BJGUy4vSDbzt^#(beE z(N%FVlJn2eDt!}u6}?RkZy}n0>#ZJ0sJ}NA5WdlDq1=ChbU1jc%jwe=C&zeEEIFzH z6r-|*Lw&aW_Q4CL(RK%$u=VkmX8W?-8jJZDAWt#`15%V5Ay~4#ZPeX>KH>xB~zEU2H?=w5Nn^>@GyDklU=TV zK;zx3E%+D3nYadNLWo(BvD*TXdtAAVyz@}$HxNhr$WN1#EmM3Hd6ygY2c_4cx2~5% z2=ohSfAj7)U(y|(wl-bwZlaXwkcgsb;^9Mal_w)jk~W*)J`eq&-)cPSuyTnUUu)%l zOtxFc9_V>I7LVX-G7F9xA0iyxt$xf^jtP6STzDN?9DOM(rQJi zJarCHNtmy9Awlq?z3DChq6rO+bFsxLLK+>{{IvPX`bsapQ{VTZ`eYflHtN54xP~FL ziay#DWOjNbr}Zuh3&(sWqI;ZX>NuA$HVUdj&JapEwakN2UQ}if9f>m?tB*ey?2*?w z7upplgrgLMt=9w!?>PZh)j=Jx2Pw!*wNq|gi2+n~7ipJ+lpH*A)iUlZ=2aiOj#QN) z+c9G_K4@E8!J^!6U=1c0mE*BO0b02k2{}CX!T|TPWAx$jiKB`CluDTq|D-M5ZzfaX zrzW8v_{YObxo|&iS7S(owZt5H64#`H1vvkfaa^+F--{^h#CHQT?&Wvbw|IND0jQZ= zSK`{0yOnc{qg3U}GB-FClhz3;52RLu9daZ%V16^FrsWxsaAvQnJJy*%rxqSh5+Il> z>henfhLmScaM4mNx5?bdGb&xrzgtSH=g>r_LwO=&3-L~b(E`U6Wvl0# z^KaHfYymAkJTO2nRhtreEyv@$JlnG7NnrLpfFyvRtPnG5?v@gN#*gVzcBw`6o$_>F7e|$%Ev+k zJr_Y>vW=q?!C5)6y*l@7$Jz)2iaHDNqZs3QM7iZ7Z65Gbb0?Haxc>FY@Ccthq0DNy z#&w=;>eiEV9!}U^%JF?@VKpV43M|I#%Mzv`SKWeDenqW3}Ugx?@!VDC1}mdFVNF3q-;JQmKBZYND0OO`)3HBVrr` z*&4XQ9X7xe|0yy2!dT*>c7o;hMj+c)H{(6%Zc3vJUMm5Itn=D%xw2vtA;Uo(ec%qs z7<<}zHpic=D2l6TdWWW1GD0h#F2+^u4>}|h2gK?K=%+$J+*LS9BSIE;qhBfy4YqEk zwx>yvqJ$%fI+GA>-lK?TGg(}VYRg28h_9L>jR^p@F1cwP<({w6i@@fHU2%z*!x+s_ zI7Rk4&(cC7x=BJnF&qi;VG{nwY<+7hK4K1c7cIJ~sganWF@7UA=HFAyf%;iyY^AiG zim0VLZoFCSq*dBVYO?9YofI(ud6<61lDvUFu@0v{0NoEH)9VWN2P@D`T>! zw(eSB9wM-^DljL}f_GM7o!a?WHe+I{p^&D4!G`VmU=;MqtRn3gtkJljPA}-nN0`!) zN%{rlPH7_HpN@iDJ)s{NfCBi$Ie75-+LTr)vPD#`p3fd1X5pRsZ4_!ib26llLoHKR z;dHD;p6T7K3GJ&;Mg&g~v}kOU^FqaJ*M;6M+=7yfkoCs+>0=x=xsj%D{lJwhvy}D` zW{o5;udqS+{kNx@a}LGgUF`mxT-fv%{x0Iaxv;miJo_r45tjZr?=i3x!o&Ms;^eTV z@mzfz>q)C#r6NCSK1~7@J(Tpqk?rXzO(xu(;x@hDfE8R#EAnve68P-isbL#p zbNP}p$ytlH9Z-Hxmk%s>>@5?_4@_eyYEVMgoG!s6ZU08N{g}M2&z;FUSH>W24N&9> zWI|jI3w}2au?K;Mqc%o%Y*z{2GFEa)l)uHE|5)**lE7J0@doqy@mti}0llA$0Ye@U zRsZgqr%rGdS9-q*F&(qsMa}Iw(~0rChXr(3Y^1ovz`2%kS6L3N9sVh)x&#;y@Of#|HP1FP81ZUSJTE5x)9Ox}y2)(|A{(=HGTsuxHN~1k=?M(z4ZxS2QTo~9k#u*YRHo?K(IoIVLV7;k zNLfo)*T=wkI>56x^v3f0ypIjz%k{+3aWqm-MZY4_aA@v=>t#UxsnE++A( z6&+t2D{_qSh{6EcwG+5&X?c0GN`i)(1Niz6RYsNi-Q`fV-tA1uL@%*4P8`1@cZ|sn zPN3KvZ>RCa3Vm6Oc-;(~29zwL4U28le*I&wmgEW>iF<0}tl{M*1XiV^HZtzmwi}AY z8iXtYGikR{z;z2#quM^(bM;VE?z$WS!@9QB3YjC1dcWOsosHlBe$fTKKx&18%{zVB z0a0KT=6E%fd0br}olyRlfa-80VztJ07GT?qT-p9$4)ku)>DQ_;l$%F5JBcxR?IFwE#F!0}RGx!nn!#Vg z{O!O+YJd^5eM%!{B!VVP@H8)EdPIHoTxrJYB}tYfmv8Wo<{gf1+;7_c@lfIR+NpRP z<%gR~PbvztkL%oy`;;CI-D2?PD~>SNMPeuUvpCtOc9iLf;zm=9rI2`%3B=UBby{hAvDei| zTJBHLu*kQe2FV*=6rak{qqL&U&vZ#+Pa*B>xMAR@$Vh=?z;p)K-vqL>xy0XzIn?Xo z&~G-Av{)g^JEcV?J&}*)ZiF&#x=q>(4&l7aPvyF`B69OPP7Y_OnU;PNQtx#O>&!X7 zwl&N_UWfIEef%TJ22fJtU|^G%3Q47KLHn-rNki?l?M?%Gy*s96!woQIf>&1fK}8f%9= z!zG-$*_{_4Oi&4vYgSf#&UicV`$qNP)cINB;AYw(ar{*>1`yYD4Hv(ul(sd@ZH^X| zlP{HK8i;+wNSOp35!)n`BIp_amr`{)g|2MWXEt8B$otHetyo5pN$p-6DcV6A>(HhD z@Z;?pV(X{i?2hTE<5yuVrAUX}QGT0A+03V`uV{u726!At```9c;5DSY( zP8LgfTOgrL7<1Nw<-?w+pV`sKWA^6CcY?=Bv*Zy9U+GpG-DZm$+mxYib3zbUlQnwmH7`vRqTKqC| z3iTXaXq^fR4!tO~DqmR8QIKXt@N>rsy*cgq<89bL`5F6?eWP@O?&x=7%+e41FR`3L z4q_sU@9Ic9j?GA(7L;NaQ7-mKy1j}}zQJbqt|1p4-z<(y*If>G`dmI>dhlT1zJFVE z8vN?=wXGO)*`G-VN67<2ucY5j%${fi66~E|i=fHh#21rYi{swcw9f*d@LMYiHI(z3 zYg3Lx)!h5W7G(k!rI{Ddr?sQ2uJ+i^?*ZHC?~{_XCj^n9j*y;mD$V{y73n{1%= zC_c0Qf-#rNrj)1h>S*g#m(?>Z5D0nsE0E#&LF?^=jBF!8yz@a%56M&UE4amN$?tUq zWQpM=`^{WIr>=nCD}fNec(-S~_Fu_R{@#wInMt~NjyPcbsD7{eW8mY*^J5zU{ST0* zz(4O)^X0hN$&>=(+RJyBrjMvEZK#X=AEI6YLr$$3lO|#1CjC$J$$3-v{y%t^6OWY8 zM0MVDx~)3VXNeoqXP?gCUtpdO!dvsVdT?-&l_EVQVfA6_FAjJ(Ua{HmK+ z57(L>E3T51%7YKl4%#X5S$4Kw|LPy`zK4P6yD?dsE_vSMOrC=oRCZT)A0fXik7KLv z!t>#wtsom>>7)s%wt@94srbTD z)S+)Fvv)C;O`~uXp|ks|{jdTpl5nockv)e&T7gV%j6;SA2@yZ)9Ml%mj{9deo^W6%FU(;bR%8v(8H z5-wN`qgnU;F z0zh$)%~Z&v-efxkt(f~tXv2BRHx}cpk$cTKvdl~TIH8=sssKLAAF)QvcumWL11|V4 zafrUB)Zj{5>ruF(%zLlV4`v#9e=8>Gn#Q+7ADrPhmYDxQR9$ux?4AnouG{x$T#A}B zg60$|+tm~q4^kZvv5JAoeeDy{Fcbg6M`epZO)jJPo&Kr%>86vg;x4F(QdOiUGVf~> ziKxnGD#IS$U=55|*CIx1I8k&>?7Lce7o~MW8H?r3&@w%MF|gckWTZFx)CeGL zB2(B?3ug3e#HIJ3$)Z}pQn4p1v9we5p zGkz*Na8&9v%%^v zbXi>v!@y2Lmpe`HkqI+adQ+|Mi|1AAv}V_vRx9KE9N`y+mC>&b(SMQX6&{Tt{59N% z(|L=VOYUPhBbp`tu6yHR@fYU95pt81i89;2nj9vn)Y~_&VNp2C!cI{w{5=s@H)N$0 zH-CW|Xm6K4iyx_H%%-m8?}YRh{ea##W*r)I2i)x@#NI~64VLhZ`2H-XqV0TPM48Z? zePa(to7+gQzIoc8bUKn2JQJ|H2eyB_(4H10Leitn?oEGc1>W+S(Wl>D-!$KbH!Jop z??`?zYGj<&vS|M92p!Vg>9-A z#wXV;E@%QJ-r2?8wQ3El-{(eX12<bo_w-eNE8i%JaeNf(jsW`zyi|^N{7xF^UHt zjf5n#n=Xo#W4^JcaaPPg)DSJx?1{A^br}_+gou)q;ODQfA5V@{p5S1N|0dud_u+wK zb`FE6prBP}N&I3Zag3wQNvi@7ne_s zZmiktR7!1+YYjA?|ByL39%&RqC|{^pdKxRb)>eodnvsaQ!dAsB#MfSw2Qf1}nelCK z?_rMODzr;W{F$EDK#|2msAVIeFQH04@u~C3;K;dZdwzX(9QzhKB}ShYB&%8_n_jZ2 zfxWr&_wx4^@d0|_x%M`4hbHqyl9fQZtE!ZHcQZ5+4=ym)aUKe?X=TSTgaKO9KawbX zYe8ar5|SA@wr>-LynF(~?IZ^Us+G! zpN1jBX*F&XTS6(*>oI+|Fod5UnW76;rtY4xCe5ZK8`uooD`vdA@ArUl7H3`wugW>A zQMdqY?BEg{kXQPW^eQz z|H@AEi}93U$tNZ!^v#SVNt%*Lf20X2dg#~@Y2SR~EZEg}fyZB*)3oLfv7o7^t42Pf z7!c7Xq@ii2>}N&iyZ1+uxrxAgt76IHt=FJ zW21+RD;W`ek>6a_-Hq!%(Zd*~VA^jI@)wr~?8ASrP`R%P1p|T9b>Nm}Z5D zJJm6VU#gc#pk@EkcDpssGR|x%VyFRZAGi#o`QowFDI>u>YOiLiqiujA{u8xXI<>Vl zGKFF@FYSU97*XDYmuu3eUkmCUQ68b;Jt(xLt^6LfkAnP`e0%m|t^cDQE;Ic7rr2C^ z;jUTXX-Ay2g0;kgh~*$_(!KR|T!ZsQRq(&RSbXCD>NP<{;~Q5%e1eQFpaZxwPHMof zN5sc-bB>LYMS}P8X(99yWQyueoKo5M_YoREE3{M4_NDYbdCKOz)SSvqrsiH$K5j3+ z+o3VATTI>3W?0ba6|mXqhaegaiFsWa5$GL}k^fc!snRm!uo9axL?dN=Sj;cj=Z`%5 z#8b`9s2c03t|g{s=_DFkga;O~YQEFfO%`0{6~R}G_y@hwxU{y(^EkDp4gQNlC++2pvejGn?&>*jeu`Kx>`|MC zLe7@Y>#F$Kj_w)Llm37)xfShtAxIw~lDgH^ium2Pm1-~6O})Ds<>MXUV@eX_qyLYw zcZ{wq`r18%iYm5k+qP}nwzFeYY}-!7X2o{JwpFPlm8A23?|X0ey*+x2{&vRsvi9EN z?0M!|^Z7kbf1Pe1cp+CQz?D}peD=OU-u}`zkr2)HBwN;TE(o>4zIK2QyBTbeB^my= zZErbXVtDjE8_HuO=E&AGsxH?nrEiw-Vq|fm6-BGMd_i!~SbNxK$bb(olFuD!AE+bj zyoUY!g}sMZyK(Twgl|IUM`cx~>h-rgj<_~W4~^>_ts@;WRBlmg^K|@v&bY6H)KK7) z#HyvKLAe{Z<8WCAVQ&g#8_Xk`_Zd?jOTsw$AQCU-EX(80oK!^L6HY8W=FGgm;CKK5 z-Jm^K8!24--I|}c3QVy~;$C3o9FPt~nQ@x#-Z zvG6*hacyYUF{t6ls0LkhoZJKQi6YFxkBXePjZ|qU(-%}JeJbce-q5S30^rsRu%N{M zk#p=s;;9)$(Bq0*pLmm1-cFJI*Jaj#=U1wkEYWWMl@t5B1zD&+iVj&eq|$`1=R%HR zLiKx`sqwT^3%G@0wrkx`E#FMrof~;M7#J?2CT-TAN3jbowM%b4S}MrY!5+WG_QMKm zfgGmRe4!7I_HfhTj_3(R5`g!559j9(Pm1gWP-M*P@V^WM&mpMCS+67ET zVuwq}86*!s@-B*5cQLquS==603ujIG{4y3N!A*1umPhP6HVd2-!H>P~HFV zrm_;|^O!S@7t9(9AEz`>^IXkM0(9kN8&08cRc4Tzh9z|+KrrOmW%e8Cf`%os!rw9U z+@^m^I9)HELU*rP2&c2gA`B+jS!A>zHIH`(E|5W$gQwjgk}B(uI%Y!p*)Nq$@3Uvh z4Pi7jZlnF!&X#7JSpX~wrJ`pK!xAx@4d^Rt-iLxz$JErL2zvR>qP)5x>I4jYAF(Ya zJDFwAiTWmE^5-6V&OL-c~kzr=3GmBr)N{FA^1|q*)t~|U)^YqMbWMl ziDqwHKeifHC*Y@-I_#JKZ?lDJyF4c|9vqZxtIzyUHNJ=Q2Cz%=nO9ylf1W^CUB&w> zDXXzU7_1~#&u(fc=@ypxre?lPqD`zG=sR_`I40{z!^?Mi+?+c^FMfk8up^RKld)N4 zHK*38UJg@QV>PAKU4jGESt3`(%Ll#HVsY=b)~#O3&1tdJXY%UzuCEKZP-=I#R znwAO{^r83@$DjnmPCR=^6(J85pzB7FChT;nfcd63xoF=95op#WQ6?RFPsh7k zX$EfP)$VC#hIvDDND;jew(WH*E`TX5w-X^6oGYHNkVVpltU5? zZ$&bEiQOly@q|jZ!%4hOKd~KZM10{JEv!i03xq%71BolyXZmZLFC9MXD55#md44~Y z39;H20n-3q*3hnyWpM{Su06HrQ=!^F*Sn$Ji=_iYwj8PB~{|0RC(b8fCq`sSe0sfkK^{p14BCKMS9r zhC)2gm{FAL6s!GV4x$l$HcYW%Ia>L@H$xQv$Q+z`7WMS65q~jAXRy2QjTNuCRl<(u z!jLMMEM4F~a(^%E#fTm?_%-E@&$V_L7hDa9ZVLAd50U>%f?YK0OiLxkR zgd#u15#GMlYsk3}YXYo2lBb9T{Ibjlu!$RGUe3yk@Kx6`nrma$uGh*>-CcISb;^Vd zC#U4?L6^^r*Sv;ygYCdNFN}X`lvaDD0?6`N;Xa3|lWH_;&36BCyic1u2`rXlmooLM zybGLRtqWG{`Xdm=*ZEoG{oB_0Xhr|vl`d*>Sg9WXVlj-jQw%vq{C?%NPGcS{8S!ENgqO>qN~ykR^-_!py*Bk)Sv47$hycZrn~{ zBjXu`Gs7=0d){P?RGaKiVSUtCj=r_9cL ze&ubU?|JNy&xVPJ?=zFu#(OfwBj)HD>50)0N;~^$@i?St z2vc?T#dw~52sWO;Y&#-_vm*l>F$C=Cv4z`%5i9-sh=cHTmv1bL9L0n^;4&RlJ-xMU znYW~jk?er*%cF0+yAhPa{b7q^I*Hc!cWKv=3==0xGfc$#MV^6!El)I5s9W~!Z6bLJ zH^N}L@7hGk4uO!N#k%sV=zQD1t%K7E|6o`~3L8GfeJO7)7xmYda`}Vqj=7GR9}0N< zF1b-dV)0@PKQ}OBq$E-~0R{YOjoT4#{x$ibgk5&}RphpV(?nHWqQs;v1*|9*i$}A_ zX>+L#43Xo9mYzWnN%rt!Nf$ZXndt1d#kcm9-+8XWn={rsN}ss3yz>*Y^k33p^t09W zq8z_IE4dm?f>RWZTDpJZ-_E~=&BzO*;N8*&EhK&7o5~#Vt9*Z8fT`utz(u2ofI=Xf ze>=q*AA!&}NSQF2{Ql|vt)M=UbyW0&6BU*HRY6XTYG5_bko>nl&i*0gLhv#Sj&v~A;#f0PeZtJF z44()uJ;|(GSZLe?WFr<`;GL5?%nGyY3oMbf1ufcp)~Qnd8dIcl$z)zbnqY%r0{5=F zECaR^c(C&Lo(IG8`610twYUaC^N;9D4c_ON&67}%n57sd2H>ITK4eM@uKCwJd8+sy|uR z`q0-XJgtd6Mxl5FL>woyD=`d1#CE*VuB6!zQ_-gZ`pb;|o{8gLw$v^y4iAkFk?{PM zQhG^>Xi-Xp`wpNgz^P!xmK z+qpue+T4?SYV#ez?x$=NIyMF?@5;)oh$p36vAq*wQv5k-qv9Pk-voOE_i2(%G;|DB zT$mXA|URuRFEV1fLfT~Ocw$5d*o+Y$|52RQd?;%P?K zq8528O8L76ivsWu@d`!`qMP4C;u=G=a|bX_Q&b;(o3PbW7Ro>zN5a(NN&;YQ z*=*+}N$ki}+RZtbTQ-pcPl!P~vCC1=j1}D5|C~4^Cq5Ohns&k3(QQHDO{u zmWS*@M!!}LTQ+6bTU5us^pRj>FUNdpYxHJk&SF?}sb{5`Q#ZwY$u|FQe}Mmd>MN|k zbbK!~7OZ>l6r9<-X9l)Z1VBEg9;f&|*sq{G$-iNg#$S;4qXo+`@ z@i49kF~L*?7w6wmLXp0HojTdJp5HMOAl=930|<}>!!5O>L)quQd7;ffhjubSQF}2o zIbb5rumPheBW}>H@$sJHsV=%xr`nIubrybOpWovBqKMD4a?czBh~SFL+FCIWc3Dj! z&=o#J*dpg>mFKc{&cz*9F?5Usi>}au$F{R`cNY3M11|qCY4ESdUxt*V#*mO^2y3`c z>$@l(N+P>ag;AQ>b%L6Kkvl!^N@4i%z=^;l@&MqiD7sfH(3CzNig)(ZSr0-I`u>Dl z%2Z(|D>+pK-ZO4Vl5(1DMoU3RpW#1QWc;o;|B$|ZY#FkhCx8$br1(}+iuiZ4(kd!S z2s=8Wtem@=Wzqr$zHY)lF@@uEU_Rnp7XvLl028mPMv`EDPJ{RA27NVDFzR>j^Q`iBv ztyUQZoIgQg8_D!M1vUprT>gQsQQrfvipd<$>09hLP>LnekKsn7NtTKxgHWKL@$#es zv;Ck8ca$dKu$BDT6e~zX`^PtujX5uxwqh?*J(De_Z zp>Tgf`=Cx5zLpp`+`h22k1yXp9mo4&!_lE~KCXSIcJEgko;~keuM?bHCS}i4?4O3-q0AzyyI-wH!#_u|ebQ<7lVB!k#cjVgTT>XXV@?^+mqgpF|WZ%%G9x86G zfib~FTJAWVbKM)r0auw&OR-F`+AwBUby< znr$iACWFu3=5j@;Uk?4oRxGL-iPF~8Gh1C;TRmh(G1b4a0JH2YF3df;pwWUK%mIIw#_fIB z^9)_=X(f*2=h;_Q{}Grm9^h5BA>kFx)Gk6Tzic3RQX%_@RZr()<_NfetC?!{j=@rf zku39*q4U7gKbY}f@QhtiEBv!o=#Zu5k%e*1vk!}Tx`;^V&bTh!yLr;y;zg5p@G=>)=J?i3kj9`sL)P z?ONf|!ffYGrqq89wg5WX+8qhn3k)wpG_75*uHo=sl1gg&qeXJ^^=$#4i?pJ><{jcaH3QYOv18o;ScR6es=SK~4bgSdBD z{pAc535g4C)Y(>C9-d}uY>4Js#oZjom?|wx-`bM&mJhM0gyoneQ^N)HNJ7XcWRQk_ z__vHK`btff)tG8n9E&Hfl9r1xO$nJynvn-#S`m)2ep8_h$G`;bk8qq!5Osqs6>W1O z0o(m(3HpjGMu6((0J9mTBi+g|fOX-VzS*z<%CR0voqfo%!-#7xKUUq+_(7_I{k=Hy zNSuz}k@@7o$~^qb&x3YlbO)(rUzsx({lio8Tl&5u^!lvX=$g4~Kz;uV#JoG61ql-R zZp^4{`%sD)U@=?!)}rH9YiWo$amryC+IAr5fc^5D2XH4aE$jYgp++)l&)&qq;6oVd zmVx`iG&)#;_);_(qcN&Wns`#UX&I~axKHiQ9J+O6FU@c}J5K3FGM;e+6ZCF~i-r20fMI`4&9GbJ&89u6xGXVPW30!oN^fD)70G*| zN2HIr36PHTQ{T4x_IPPf74Q@6Tq=jUS#{lWQF?~^wT(h*`8kQE{lia zfm4N2#`;`s1q0ROlAf1>rXeMI3Wh73xTTNunGn}@?`O%@oFVjT z3dJ!NZkA>K=*g}mm_{O%-9AIUjR$;{eWFPNGK3=vCp*Ia%dxfV`85V=C{$KiT|;zvt%P|OD)|bY*ajK=s}8B>4sXA~nLh`{%ZEK18F~;kg&xly_|C6ekW!aScH(2)rLH~CWeA7- z)82{-W|m3cZwo&S21B}4^*n@Pw)zOLhg8&hidL13SO!*pQbQ$F;+e=q%iy_*Zf{2K z>N4E>HK$XD`=ALp8nwb>ssT)0dZrxE)Cp=+1&8m}z1Q5gTOyR+>2ve4?b>>REPas( za8^`$Dc;J7Q7R}K&huT}pp+NlxR@-;KecObxFcsE{s|)S#CYuTY>i|1Jqc`1v7Id{ zU3(vUG~~2_upRzcQSYbjK2)pNnJqKFjJ1^!2)g z_(^u~N@voq9ULqzMa)O>Sl`Sajg@UM?h{=~Hp+N(akp+p=eisP1=P`f$LVY%T0(iJ zcn<_~CFPhHd_MVdhyx~zkk8NW(cVT53d6YH_@v>x#0X2MAloYxkQNtG1NQxS#FM24 z=*syG6L+QxuHOrc)0u;5zTjDxXA)OCO9&|9Ciu=2-mAh8@;UV`(L3VPJ(MDyb2ikL!9 z^Dh{09*o#yQ3Iy@E@hU95mnyZrAjtG;*~k08Ns2r!#_FIA%BLB&3n^n3Wntf&0_GN zJvSC!A`qwRExik%N(8os1{S~=hI+AWJ2$d?|JL57_<;u(S#rS92E?;&S;BYV%e!Ei z$22`lD7hK;_Zg??$?j=9qc83L1G0P@g-fD@0$To(39|eAgot^R-8YFaI-rd)1$%rCiq^Eidtk*c@AxBb=n#FQU95p2yRik+c#5(-Us!S0 zlv$qmhFe$QdL*y%1I_%=*=Z#Nf$6#jUkBQ-1V`uerC*o4C zxePM|e2=gg*+#Xj#vJa=CzyC>_k5oH`l`WzASgioV_!(j7s4?Zp-Pn+xP*sB`K$<5lA>5=cpOYI6*s7tXqi zCU#w7aVqM8C*VBj7^Y8P@0tF&1Kqh)4|vuaFew$;7wD(tZxG^V4%d%LMRx5xs^(Ij z&XT~l+8*ihTD=>NCYGiyEuNf>yXAUKR$@NFi%Jx8M8BI)lVf^m2cdrtc_J^t1p;r@ z(=d61P?9ZJKl&09@I}kjuf8Tj9$x;=Cccf8lA?%}Tl?j1HPZ+IJ~(A(^@uj2g*u&n z)rSj9eLHve2y~841pe*5af!4dF5cpOL2m$5#sXsf^1d`MHvUDHea$;6@kaU6_1AoM zdTlKFk4T=_g|4(ELC1eqE{EJ^yU1o&D{pbQ?`9t79?3;liSX z*X~-aso-F#7qpOdD+)ZwS;C~w5FMa-#(8lYGvN}Uo0rk4nIwxFYds+<6=rG=60TPBJ$&E!}gYnkQnwUUptJ;+GnIjFGs7Rnzg`$ zdih72Ys6w2}^MJk4zmw+zS3N<6m=|R&p$Vg`Xl@r9H{pt?C3W&Q~}D*ACb_L~q4+lHzHqK}qDn&NgRe?e`sI7l&-Dv^F? zE+06v4R;=cs!x$0-d+6@)CNcnJ9hiwRTEDl>kBlJjTd)gZpBC)|8RPQ%hLtrj@g z=awghICB(q&u7aM$(xdzMh?l2z%--FqaM_(<3~S(pn57zDyUbP&I$Z^%NOD6($Z3=l$LDyYjH5BcW%?BTKh&votwyx7Zl>zeBanjd8du?=|JXvMMTS+M3#YACG6uIvgG=6n0b}q zM7jMw==(YY<)ZWly-u@hDWCsyq-tc#ulZHgWCqb1GM}U=(*fApve`2^SRCCh$35vA ztkCvGsG!WeYuipNP5tN{^El%YKbO#$O4Y*d$^;v zL>6)Sf^2-sr3`3m?^hgeuwc5HWrjYv8K4^|Y;Jye&g!W07FM?ArKQGc-(?-Qu(*n5 z>Kz%UL67QElv-_ki%%-#$uFG9psp2L&n4xR+hm~aR9DqrE8&T^ap*{w&a^AKz>V_3 z=zA`T7B5Oeky+q*!S$uA+rN+0>oKZPje`Nu#P{YKRRP|+%~nN+Co@NKpGJ5nKHZdi z=?dQP$pxjN>YQWwJO5rL@K$(D58g%Vn%z6jPLH>fb2Nx)1IFnvtkBmI6Cta=6h*CN z7R0reCuxRjet2=G!hGxAaD{Q?_lz`xV@(IgMV0hMza!wsA_mSEDJsc)=^xGVH!JHT z5h0|kGXU@e-^fR{PyyV!od)=41gz(PF*iDrrJ2Y7aKbvWCl%aU)r?2i%5B)aKOw(t z&gA-iJrYj$IRDh~e_OEWJ1i<`M}8s6gXa9&(~3(}4^17oL(N{sS_>xon@^6>^GUV6 ztstBEx1B2zi0RbcBt;||yL#E=vgR^xI2XUD11Mo7U(1ARtrl8lEY6eT-T8+oA2iO2 z1dWKoDECp zXUp;zS!b&&dKSa`<3x{aL}rYxAQt7OwO)IC;j`Je)AFYw7C5s5Z)HURIfqs_u%BZ9 z*=em|pHR!}udusUgHWZD)lqra4c~JDw(}OQzJLBsdxEzo z88vnG+jUm%Qj?G2v1?0oAl(s#uijaCXnRi`f&4-dRcq-_kwLd~wl48><(7cxN9@_0p3gsjU{@oH@CZ=iRE+Y~Ff-S8- zW<=9ZW?ahCC^$>j`$#IztnCPGj>UA4He!AG{Y)VvFC`y)x)R$W@c0l3tuso{p)O(a zZ*y&WWBDY^A;vRhBCtlsG0ej(VR=a5N(TmDRo0}h!94fM#WGzf59p82+4;#-r zF_P=a`kGJk7@{1%xW!g|?r7$WQ%+qJ|7d<|LG_Jxi>o4YfpE9vIBX^82-V!AP|fC0 zyb^1pH$t#2a%v}(6mkc5#PG0z~zaC6@RA%m$yPeHIwK8&>@P3RiCjq zV5_w$ls-#Vs83;8$KX73IMn7Lb>A6fK2kUOc*0YTPD z-049lX&J=o7hR7EVgq;<-S^x5-fdTLI0DA<_mG#Rss-0s(1&aGy44Md>KzF7LxOG$ zx*qo$+oT+uqq7Qycv}`xO>pDQm7Zb09@g-8KG$m5?M!ArT0bVWQGA$O)oYwP<_ZXe zpS=%4J5C=Eapv8QScU?R*N^wm1MagHun}kL{{Wl9nKsfT9rL;K=W)%j z>;;l}U&pBe(bJpH_}z_qi(l~pk3D_D!^ghd*!_I{t_x8IG8H2BOFWg9@lV^S1?>FjNmiU2v3sPdqdOYxi!7II@j-H3 zW+&?>O*KAe_W;B9S@og3^v_ob#`lw+%!NmA-y@esX&O1jZ z!lQ(Kd0*Q(dN6nd1JI%*Sc+DV-G$=ZX0hU`mi(Yc12+NZ1$x*~Wbn9G5&YbnJZmkr zb#v0^{N){T_b@4EzwsVmc(VOwpu=a<`L+m&m=N*V57g+iWs1KS{Nme%+$4mpJt2B6 z)pp+4LiF91s|krX%?J7+2+foQA)jEhDd*eGbNGX>SS8Z} z!KEk=T@|cKxnKX4;JdiRyg&n7<#v6{B&oq+hp6fuYs>h#$V2RnW};$oNF8oZ6LyLg z@`cf=z(i^9U4=%6yKT*{%H;SW)M5}jYB#e5E7JQEjUC- z3)ps{1le_Dd#-wWS>3-S&Z-IoVYoP6r za$e%|<_?riX6-r3F-rX1%qE`+uBL%u3g851=bkPKn84YIGdVkHM4F}iJ-#{_hK(PI z)o}!YNf3C_O4l(mzz)~*7A1~LgJ3CP_fV3Vlm8PmlMAlZxnw3ycgm39YZlrw92A*H zAC^>=qWndQS6RrZxJwxL_m6WDQTp5DQrIyxbDRi0DKB9D%3jome}iy~Xkro@re&Him2-Brn%g~YD^s!&g8ujNZ>xb#Xfn?IsLqXG zoDvWzS z*FhmV!w|sy@w60fOER{$-}f8GihhNO%k;sOl(5eD!cX!YaWt!;??8bIZ!DX+Wb>-U zrC><>eas6`u$GX9!>aP-^HPYG@MFl^{jhhaeyAc@JD3-<3Q~}|UivRtT9PrwhEtHn z=3Z2u%rmTn>Buik%#2<~3pCAuXybZS!!0|mc6+%^g5`)VllW#ZHgkpnpC4ZjTE{*{ ztb!FxuVF$Z%axH)3>WxA(#WN8e9t^ZRYx5lKgKPhrd8~vNZ!&tGawP7?T7qst}KXr zK8=K=mtgg!yg`fPt;%-g#VE>D-()%ud+n+qE~D{8Jfy{9Ogcue(_XRB87>MCS=x%2 zGz-}k*d+2q-*S;we_9#5-am6lO{2pW@C;%mYF&6}hvRaQh?NA20b9GP;d z}nsWG~CTu5M?~L+kJ2DUFG(^YY{mwt4EaX2Ay=cOQ#4H#j#fB#{45yjCOEZioE5EPx zXC5uw$zwAuc-H`k<6AtOfh{~KRmstH&vuJTVY_v%Kj zFySrtsV0qDiW!&7Jv50}ZuNbJv8&afCbZsQoZLs$7991V2$erhyB1{AL($Ys6%V>Z z0ciF5y+zBUSS`#M?IEv4s=vRYZiE7O2>N=KTUwQu`)jCwW7n=YRiJ~L{EkrOO_pH{ z3NcwO&)KwNbm7D<^B%ZtlVup0QOBqJn z2P=3em8kkMjg8KTfh$wDx~^#`hujWdJxdCC(vUI_J=#ptjZsW_3@2?ld(kU~+?$4D zI)XJ@cI74>Sb@Q!yK{#Zc?*l;d55@?Gt67VQ&vzo8tj6;ahrtJka0hwV|F)+!c5gPpg30n#N=cSo3OMzt9`dtK z7c1>pO`ETLSTkgaUJeM8bco%@U`B{jb z;I26cOoamKr2cF9vSlKTTdHyK0|Tz|{i(2nexdZtWm*$WKDL@=CCXXkyzs00tOcXg zrG~gn##=}TLgI+q;|;oVN@0JvO=H(RSGaSflI1-$ONa%VJW7;81BMT*+TQo|$B}5K z66`S~u-E6kHc;D^E`nW}d_ep?f?SD|mNpyG)c6X*^hm|ixwfOSQTa$SpTJ|XyYY^A zGqi@*1dP@#Bn}K`6p^;-hWCac2WqTms-RBw>vjv+%{t04JoiY(MFHm>hS!Xig^1`b zch`(Oy)r>g_)p7DzdJ=6Etd6stfa5fd@OR{(-Qwb;I}XK4fs!#$$wNzfT%KOEt})q ziNek*`jx_86P{MJd|GUs<^>kWQ-GQf z`g6(`uDG%sE9B<_;_vsrJw1R?jLOIN$q)az7w9a9?-4Kt0ci1jFfOVO2gxx5oMMyx zVt&#Um^7xq$#s#2!}dU`^YQ+la;jS#Zgc6CFV?J4=|QPXtE)GI)x(U^1xD7E<-XYS zlB15?_3nYB zKOwd1&6+wQlDB|QC-l0f%%hx@OHn67(|oJ3pO_3tO&_*Jw~{ycFd2vDL0aO4clr1n%Yo z^^v-=5WyK?*9@j36WUY{+c~xlS@MkOWX;sPmadMMx8RtUy=in(q ztV*tvY!L%Y@LH&V=_CIoqzB+sPW`$y3 zuYo^n5k&!X0Y9qE=NNpQ$H;Z^n3k?s;aPUHm*VNmKmAMCOujNR+Mfp+W98h)m3DpD zDv(&SF40tH^5;fnEz%T#RPHVVUx^Fqlx5!H(#*Li zJd7a$Bt3BOx{)UeM(xug-5!=oL!GLdagPaQe&4DFB#Z>hezlJJl92W&O_Qdm|JKkgHXhb6j3onFzZKm((6#~o?Y4%BJlJ6J$D6ifQ1}sWW0|m2 z;`Gt4&LSRd?itop+hK0iO#s)-yMVf9F3-``ZCL zf2Os6$vpYPhbvnsF<*PbksjHAPmTxx1;5WePW+wgC%QB?H(d|uvj93i0}ma2=Y#Xn z;ytkPhHzw#pLQK-!SQe#?o|2Xzr(2hE0*d%iB#|K68vVS!U1~~a z0?N*U!V5j3eWK`NUpc$LvdtIZ4m5M;PPn=w#B3gk`GT`S&vDd==f}U9e$N@BqqUaB zuXDhbp`9+$O0w(u3d;KLjI93(%K8tw{ZUrPfd_FB!m6G1!5b-JP`gfU$JnD*zO~45ph9rfrH@zZprG9~-ggti4o5sFfGjY!uh){4Ua1`i1JFXFkf1DEaO@H4@r6qu9 zgNQRO7_dxtml9A4sOskJXu~7yYAWB%$Yn{7H0%nd8Gso0>`F$%naDMT5v9_+d-?|p z7V$gLjJ%&p(*~Cc4yVw=64I`tn3|%=^|Ny2q-uG!#`ybPhHJbRS4kB1pcPdF1cONJ ztTP)sgL9=Uw~{gs~}y5f$~OM zHxWAuA~R!lVwkKKLE8n&b}HN~M3gCKFb21pbFFa~kbkyzBqQE^k8CsKy$$vn@UW9u zCSqaV~|sHwXJ z3ULG}{uHKn)#_1?N^Q19V7m=5l((sIr{$m?EI5#5w&j?xs$bDtalI7GdN9$4+cdl$ z8pQ9mpD;(o)=}&ZLq$BG#U)IM!|K!Y2F$H=sjxV6jOXU4nkTE> zZSro7Z)up>yC&-F-Hgw`oiK=n#&{HU^Bkc?PFF5Q$?~2@T*PsE&d6pZ{%JdUx|=oA zY)gVE)BXGVS5R>-x>*J#%7!`rOx)_1WeZz!r1FWa>*f9hwQ1~fx)K4V>Se;d5Fp68 zTWocdhsN4TZu+X^(g6o$)xh-@ellDgeeZ_#hPTHEQ4XkMv73gq;25*h)c3 z*<)ZfZW+;IwYuLvZTqm{$I<7Df-z!`hVbVYeBsztKCZZU;BDt1xy?=hL=9mJOehS= zI$K!Ks0+QpyQg){*o|QVzx0$a6L?&A*z*TXA!6r^Yb63_;Ua86Znn=O2ugf$;6omD zho!I_r5ycw=^|JS2f3$NjgOU346M5?f!%$wzUK4af2;qU1PJ`cX6XMcx&O1u{cqkB z{|8C%-${f2l{xtTCvNcnxyJvh?bAiT|HvHt|61w)N2cL_r5pZlnTG%GNW=eCuK#Cv z;{W0z=>I!Cu`lob6H0xNXf>kG?$>`Z<)y_H#Og$hg8%n~aim~rfL120``)4PtS}n_ zERr<`d*+`7Tghpgpx~jKTYLvmO?zz89UIObi8(g4N87w({wD!rasJNx8U2QX^p90+ z*=jsY7L0qTpI8HjA~o=d%pSN`rF5&wFtpCb5~DFU6kp^&JM(N}Zt{2L^t?|et{6nu z>9?01RPku?fCSW0U|QB&;?HvEL6Y-Xd(#qYI(m5)y)0#hiFsEB*}*Sah3CkS%O9Aw z;op!iz>c(P%sVxFHhoq>A9Q%f;UeF*<~sUu%IO#r$#8#&5@Sn76sn*xhEn9z;jz1Q z?!%vmOf9g+>C35zsrG51KMYK&PtmJKG*uzwNKN7U#*yPx0kU`DRRmPjy>7;|+O-;} z^6oXd9BShs1$Svrp|^a0FzGL|%L{+lpR+%AiYS~E#;2i?%+I38`yA;R>8C6D&61GHAw~u@G|?7sJ6{RfvuI8@^p$q zd;>8+iDRdH^um3|PL=ytI&2A9tSs8RAMe>7cq=502}L3nQcT7Y#2wm0y^THR=lr0@{>QYxNpx_tpI=OjI(YyxjhsgxM``I{u{hPO$Nuxxa)`f77)hc zz18liPSqPQ@2w|>$d zTJ@uYY|e^SHtg^cHXI?8#S7w-%}~#RM46ekltB$5 zdbueVAo)XO8i7?4RyvH>Ht&{0HW`YR`*=XDFT#o^${J>>T!hxS;5EzjGO^S(;6xei zy7_ElZwteb7btq$` zWm`yMMP%1N>6SCa;+^_g?j@Y_QoXO_PYmwCH^uV* ztE=;lhHLHq_#jHcC}Tt&K}3n(lIYRf)!S%ch~9-!1`~o2Nf0f1PY|8xb*>&I2!bf1 zjT){t!YDuPy1%>L_kGqnYk${g?|sfX=dZKRbDn26BaN+f_m%wjJ)KWldr1X;WjAxX zUt}K*78O%^6o^xl(6a)^%ytxc7)S%|EMMKW!WvOR<2(ZTmxgSZIPU1K;7pogaHN4KM98GJR4QchQtv^)h-g`G6}iue z1nFhGB$7IxxSOT_L^1HQBkZbv|>jhn3CdB+JtS}jGEi7m^0Eg~QrdWO4vx`xRs~ zL;A?5U4?HwEK4zRJh^W+pUnH$vH-6XNt5`Ufo}s7+}tqF%W@9~r9<5l7Q`QLgnBz? zQ;^u4_Pt#Cz^`ZDz#N~IDb+7XEFJ05$D2-&ozxTcJ)REsN1e1RR?s{vx=@qIDQo4S z>iUJG*IX(EeOW=e_)@s8wNvi|g;P2VkGzv{j@Ezltac)HV3C}-%e<{qS+_xzGHOhy ziazvr6dQ;6f*Ey4f!7sG^jPdY8nHr1-@sI-l`i1arFHyZuiEWVi>4+V(*&kJTgge= z&(Tz`k?s9#M)Hnyqd+&-b;J?l#^``Sr(6LG%sdIxc?rgbd{%dxBcFKq zPpV#0p(>1{=?8`zoRT}Z*%IKvOo2Z&%C;8{60l#Jtplh1V~4R{ecmQ~h^hFwQ?0pm zM{U1t_B~hm##OG>xC%`x&qv+mAbrh~;ZR#m(#F)RzMdV@6N&2p z856^wR;A{kdd^9^Y%hbMjqiSHAu+bzl7h4Agddjj9dWeY`x^dvcNwu6g&V!7b(Xs6 z^R%V=C?8}wPtSqAY}Bu^1T@Ng2{nZaoWjIag{bMv5~ic{*6W^Bgj&g!t=Gx+Vvhpb zd%3YB21k@VYpc#+q(oA2PjTXpnhe=k!}gERH-6YXVWMBbsT<{HR&_ex@dN2>R6Xqx8XmvJZUE2Xmu>Tz4f;^k={PL5%WjV+t?c+ zfX}K6_A@~anw5q+mS3lPXcjp(k8fTjwxk!1qKFfrouPTt30C$UzY&UVCs>Q#!l2y) zsI5Fal`rii+GMLf8RAPDd6LxTN;krL-8|9cV6HrP?p-X~->&@Rm+<^AKX_d=o($=Q{`*lCr}# zw?Ve#?zSYC5(x4WuG5heUwbNg+!C`E0CyK$lTy4WM+CQ}mj}^1*S(~*A9|uMzge~9 zVCkd$+&3unMQy9uQPyJScv4q0?_4T0@vX=(UuaUFkD;}|_t&#EJ`@U~#8&pnM^+!km0*-DCi(3mwr;eJ; zd+)54kk7btGCa8C2ik8P#tv{L+Bsw@Ng%(d4sYQWh>@SKh$ZT!t>?l|7)n|K&S0P* zb_N-`38Vu~gilv)ar&NPZ}YRW!Cr z{qhGl(&S}qL(kNY!%=Sqh|dWw?y?M8DOn5`C{NtIce?{Gq^CtnCa6PiOL+$Xpm{C> z0Q~pMpV?ao(n>4wXCqDfx@m6G?1aYyme=4CQ@F!m^$$AkXM0JqC9Czs3AquF1Pmd@ z=}XZCoyJ(2w>YqfhEtmpybF zRpP{@>aLsy?s=M4zSG#tc(WKh9R4!sIaW;QojN}d8P@~4R1=oITN|7t0$;FDPa8T5 zNr`{bf$06+>Io$I9vV8&hYe7qSW6FWvdk7hG5(sww=;=xHYHyq1dW8q%P0dgQ5S)W zeIVw?N&HBQkw&Zj4GhH#sVrRWSc15XOtb9XN11H*)$E=p9JZ<+w11Y2Gor+LZNky# zow>P4ImZFFE}N8@3aG^>liBd4d^-YAhkl(vC{vdvbs<&H0}1!~9@sY~f8urI z+u~-kI2~cq5Ea95)Xx0~LAFY->F^wtI(8#sm`_3e+-w)`?F$E*Ovd-qz(p4v{2aFz zyST0Wb}irGp3d1K+6MQ{-?Mb)Q;Gi$*WD%WbaBcE$>S8;n7rb#;jQ&9S)=JngS!+6 zdV65}3%ZNrcK{cH`<<*J!OM}$%hwxkOP6^ToSSO|Eg-aDp7wHKau|lG@Fe{NRdLI# zlTZ{b*JY#9hsB*dfvK2<)Itj{cB z#pKUw;^YmwCbUR)S7y)>oQ!-)d?us$JdUFll%VYX1=+_rcn)YgAG97reW*i@{{q2z zvJN5~mW_F&-|r#FfcH$^5B^CM?hf=wEqhppCDuxXsz07JdfI<}IBuBQ!dDM~FRfWin< zlEX+SH(3Z7z_XuyM!3&Y?`R-jC9~)D-kQ?tWuGN|0;W@PNNhN@*Cam^>xY@Y<}cuU zp2QfTVM8#(E=;;xB~?S?@!^xLYtO_>r`VN*g&j zHXNOZ>J}T6WTkU2FMbl}!Vqt>yd*8m{OVcB4t^_|Z< z#@GGlb1AsA(K7gS7M>fwE83mS0s9%dxgw~v!z)@Z`=`!_-RD^xcJvnu*W3T(RP!a} zEQ5a@FAcg_vm%3i-uNL_>t}y(snU)A<)HtY9VH(3!9^f&1&1CWboIB2MIsLOv3Ld^7vw2 zP)pm_WiRCIyLJOXA)U)ZdETBE1otC-!LJm10U!+#YM5M+NEvH?LMYLkLI~`LL61q; zyd3r=o9wNf6-!?hp`;sd^)C&8vrC>6{C3_vlOo>vX(K@?d(7#@$V*>yqD>hZHb3tj zaId~AgPQIw)!{iOcqVgSc^HfQZ8iY`aJe9R<`-m?*MT_O#PB7|*(%Zu#}DuyZ#W~teDh|7l4(??}tJ2_W~s(8O2&0{3n zdfP}!-)T57^}4B&e3o6z>hFxb7HQo>N2Eko`m^`|m()hl<}ti(eMO;mR*Gv@=RbYQ~L zi&v{n3I}s+fgbU}?eVgY)mcH%Z$M-Migf1M6-I!&cG>*P|G{VlT$8x^S8Jn%tF=|z zrx#?4XNuZFx%8_=N!=ONHVYkT8J$HOxA(u=lnk}kngytAf}5mUZ+98>pK}iPqY<2MU{|LlK|T}%#Ada) z$uoX^i(*W+k>cwhjm|$p=C!RT$_E038MGQ?*oCqZ7;wR@@}UwtR;{bI zv}){;#L-kg0(3@3nU8->xP5Pd>QN;#0BTo)*$=F4Iq&dRM)k9s)?#0vTwE9`MxzM6 zvLQ%m$Q1xeMwe%U9mGN2r&tQBu-A_ZY|-PRYJ#IpE~bGX0mcp!D1S+W!fRWONrBaB z9!*>%}Qi2zkI+us8e zH8H2OTx5-C-q9(4Ab5L8(J^wU#FzirL@QL5JE){bIvkU0c}77OR?zgZY=KzE=z6)Z zkt+v3AA40yt>~_TxIb)vTic*95k>r_L-SBvTIsGi$VVXkjZGMZbvn}#d{0pzuMUtT z$1*0m>X3g);8#;DDs%wOu5z%pLhImN-h7j?H@;J%dElJzR@I-gzu22w`JQ&SMS?%NJRtZenWR*~cFrZ_%23u% zbL;fp>mrW&C?EIymQ$8_UGV(l$Fa(Ad~Cl*IFF~T2G;VyrQfDS_Tv~IhFCVFU}oQ) zbp;ae5J3QuW?5m`Y%(eS7Uiwa>1r4_Sn=Xp;BDQ)qNN|7+T_l}bBNGkiF$3w(!g*7 za_2p%7m0il^*(y%tgY=)=&6BjNS|b2*rcI^CSB7vxm8mhCrGJ$m znln@E+n$wBx~eJrY;8V;q4jY{JZI%Yj9&8s`jkM(^(Aj5=f2`3+YGslBdMYpT+Oxt z33Dow{rV%;1|Jp%N~g9-kMxduvTqodOC3X_lTaly5QQ19ZjcgGd~v&1%1mtl@2_Le z4pXnv4Yyv)Mt zau@+IXLXK2@AnN#%z}ehcX;?qmOhSton0Hx@y$RIbpKL)Qg;l(CIl()USjB zL+x0H6qd0MixZqL{Tdgy)FtcPV>8iC!XjA;Gr5LB`JcSaL$!nu?scoY&hvGIIS6gz zpGEz~50!a3Z;^jL-#JZ3F64`wRMH=Ff>}MJJ&vM_im4 zY6i0MQ)Bs!e<`u3dlmW7(#-H&`8EEr^5`oty+w0x`DAz=#POu(8k60mOz zlx*!Rx%y18*_utBJo>yeY#L+x*S!#@4Xm=kw1`2XQ5e_Q)Z z&A)9;=40UXwM;FMY@^=^0D$R4Tqf_m8`6?CmCZ#80PKkX02DXh{Qn*CiE>%YY&S8u zpUD;Kz5u|@!SJvDM*KyQNCjl#*V7^(1OvbTGBPs2O~U`zk2mGxbs6~|!y_!QBZ~og z6ZD^thWtN50Dv!V0028r4_|u^U%>!(xBoqRk`{_w;QA&W+yVgT{woomD3r}Z;+lQ~ F{|}stu)Y8Q diff --git a/doc/road-map.md b/doc/road-map.md index e69de29..8bc56e3 100644 --- a/doc/road-map.md +++ b/doc/road-map.md @@ -0,0 +1,33 @@ +# RAFT Consensus Nim library Road-map + +## Proposed milestones during the library development + +1. Create Nim library package. Implement basic functionality: fully functional RAFT Node and it’s API. The RAFT Node should be abstract working without network communication by the means of API calls and Callback calls only. The RAFT Node should cover all the functionality described in the RAFT Paper excluding Dynamic Adding/Removing of RAFT Node Peers and Log Compaction. Create appropriate tests. + + *Duration: 3 weeks (2 weeks for implementation, 1 week for test creation/testing)* + +2. Implement advanced functionality: Log Compaction and Dynamic Adding/Removing of RAFT Node Peers and the corresponding tests. Implement Anti Entropy measures observed in other projects (if appropriate). + + *Duration: 3 weeks (2 weeks for implementation, 1 week for test creation/testing)* + +3. Integrate the RAFT library in the Nimbus project - define p2p networking deal with serialization etc. Create relevant tests. I guess it is a good idea to add some kind of basic RAFT Node metrics. Optionally implement some of the following enhancements (if relevant): + - Optimistic pipelining to reduce log replication latency + - Writing to leader's disk in parallel + - Automatic stepping down when the leader loses quorum + - Leadership transfer extension + - Pre-vote protocol + + *Duration: 1+ week (?)[^note]* + +4. Final testing of the solution. Fix unexpected bugs. + + *Duration: 1 week (?)[^note]* + +5. Implement any new requirements aroused after milestone 4 completion. + + *Duration: 0+ week(s) (?)[^note]* + +6. End + +--- +[^note] Durations marked with an (?) means I am not pretty sure how much this will take. diff --git a/raft_consensus.nim b/raft.nim similarity index 77% rename from raft_consensus.nim rename to raft.nim index fe7ef24..f6e04bb 100644 --- a/raft_consensus.nim +++ b/raft.nim @@ -1,4 +1,4 @@ -# nim-raft-consesnsus +# nim-raft # Copyright (c) 2023 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) @@ -8,7 +8,7 @@ # those terms. import - raft_consensus/raft_consensus_api + raft/raft_api export - raft_consensus_api, types, protocol + raft_api, types, protocol diff --git a/raft_consensus.nimble b/raft.nimble similarity index 86% rename from raft_consensus.nimble rename to raft.nimble index 789476c..2baa6d2 100644 --- a/raft_consensus.nimble +++ b/raft.nimble @@ -1,4 +1,4 @@ -# nim-raft-consensus +# nim-raft # Copyright (c) 2023 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) @@ -9,7 +9,7 @@ mode = ScriptMode.Verbose -packageName = "raft_consensus" +packageName = "raft" version = "0.0.1" author = "Status Research & Development GmbH" description = "raft consensus in nim" @@ -21,6 +21,7 @@ requires "stew >= 0.1.0" requires "nimcrypto >= 0.5.4" requires "unittest2 >= 0.0.4" requires "chronicles >= 0.10.2" -requires "nim-eth >= 1.0.0" +requires "eth >= 1.0.0" +requires "chronos >= 3.2.0" -# Helper functions +# Helper functions \ No newline at end of file diff --git a/raft_consensus/anti_entropy.nim b/raft/anti_entropy.nim similarity index 93% rename from raft_consensus/anti_entropy.nim rename to raft/anti_entropy.nim index c4e0a10..98cb173 100644 --- a/raft_consensus/anti_entropy.nim +++ b/raft/anti_entropy.nim @@ -1,4 +1,4 @@ -# nim-raft-consesnsus +# nim-raft # Copyright (c) 2023 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) diff --git a/raft_consensus/consensus_module.nim b/raft/consensus_module.nim similarity index 93% rename from raft_consensus/consensus_module.nim rename to raft/consensus_module.nim index c4e0a10..98cb173 100644 --- a/raft_consensus/consensus_module.nim +++ b/raft/consensus_module.nim @@ -1,4 +1,4 @@ -# nim-raft-consesnsus +# nim-raft # Copyright (c) 2023 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) diff --git a/raft_consensus/log_compaction_module.nim b/raft/log_compaction_module.nim similarity index 93% rename from raft_consensus/log_compaction_module.nim rename to raft/log_compaction_module.nim index c4e0a10..98cb173 100644 --- a/raft_consensus/log_compaction_module.nim +++ b/raft/log_compaction_module.nim @@ -1,4 +1,4 @@ -# nim-raft-consesnsus +# nim-raft # Copyright (c) 2023 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) diff --git a/raft_consensus/membersip_change_module.nim b/raft/membersip_change_module.nim similarity index 93% rename from raft_consensus/membersip_change_module.nim rename to raft/membersip_change_module.nim index c4e0a10..98cb173 100644 --- a/raft_consensus/membersip_change_module.nim +++ b/raft/membersip_change_module.nim @@ -1,4 +1,4 @@ -# nim-raft-consesnsus +# nim-raft # Copyright (c) 2023 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) diff --git a/raft_consensus/protocol.nim b/raft/protocol.nim similarity index 51% rename from raft_consensus/protocol.nim rename to raft/protocol.nim index 30185ee..55e4979 100644 --- a/raft_consensus/protocol.nim +++ b/raft/protocol.nim @@ -1,4 +1,4 @@ -# nim-raft-consesnsus +# nim-raft # Copyright (c) 2023 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) @@ -10,36 +10,39 @@ # # # RAFT Messages Protocol definition # # # +import options import types type - # RAFT Node Messages definitions + # RAFT Node Messages OPs RAFTMessageOps* = enum REQUEST_VOTE = 0, APPEND_LOG_ENTRY = 1, INSTALL_SNAPSHOT = 2 # For dynamic adding of new RAFT Nodes RAFTMessagePayloadChecksum* = object # Checksum probably will be a SHA3 hash not sure about this at this point - RAFTMessagePayload* = ref object - data*: RAFTNodeLogEntry + RAFTMessagePayload*[LogEntryDataType] = ref object + data*: RAFTNodeLogEntry[LogEntryDataType] checksum*: RAFTMessagePayloadChecksum - RAFTMessage* = ref object of RAFTMessageBase + RAFTMessage*[LogEntryDataType] = ref object of RAFTMessageBase op*: RAFTMessageOps # Message Op - Ask For Votes, Append Entry(ies) or Install Snapshot - payload*: seq[RAFTMessagePayload] # Message Payload(s) - e.g. log entry(ies) etc. Will be empty for a Heart-Beat # Heart-Beat will be a message with Append Entry(ies) Op and empty payload + payload*: Option[seq[RAFTMessagePayload[LogEntryDataType]]] # Optional Message Payload(s) - e.g. log entry(ies). Will be empty for a Heart-Beat # Heart-Beat will be a message with Append Entry(ies) Op and empty payload - RAFTMessageResponse* = ref object of RAFTMessageBase + RAFTMessageResponse*[SMStateType] = ref object of RAFTMessageBase success*: bool # Indicates success/failure + state*: Option[SMStateType] # RAFT Abstract State Machine State # RAFT Node Client Request/Response definitions RAFTNodeClientRequestOps = enum REQUEST_STATE = 0, APPEND_NEW_ENTRY = 1 - RAFTNodeClientRequest* = ref object + RAFTNodeClientRequest*[LogEntryDataType] = ref object op*: RAFTNodeClientRequestOps - payload*: RAFTNodeLogEntry + payload*: Option[RAFTMessagePayload[LogEntryDataType]] # Optional RAFTMessagePayload carrying a Log Entry - RAFTNodeClientResponse* = ref object - success*: bool # Indicate succcess - raft_node_redirect_id*: RAFTNodeId # RAFT Node ID to redirect the request to in case of failure \ No newline at end of file + RAFTNodeClientResponse*[SMStateType] = ref object + success*: bool # Indicate succcess + state*: Option[SMStateType] # Optional RAFT Abstract State Machine State + raft_node_redirect_id*: Option[RAFTNodeId] # Optional RAFT Node ID to redirect the request to in case of failure diff --git a/raft_consensus/raft_consensus_api.nim b/raft/raft_api.nim similarity index 51% rename from raft_consensus/raft_consensus_api.nim rename to raft/raft_api.nim index 9985091..e23245b 100644 --- a/raft_consensus/raft_consensus_api.nim +++ b/raft/raft_api.nim @@ -1,4 +1,4 @@ -# nim-raft-consesnsus +# nim-raft # Copyright (c) 2023 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) @@ -13,19 +13,21 @@ import protocol export types, protocol # RAFT Node Public API procedures / functions -proc RAFTNodeCreateNew*(id: RAFTNodeId, peers: RAFTNodePeers, state_machine: RAFTNodeStateMachine, # Create New RAFT Node - log: RAFTNodeLog, persistent_storage: RAFTNodePersistentStorage, - msg_send_callback: RAFTMessageSendCallback): RAFTNode = +proc RAFTNodeCreateNew*[LogEntryDataType, SMStateType]( # Create New RAFT Node + id: RAFTNodeId, peers: RAFTNodePeers, + persistent_storage: RAFTNodePersistentStorage, + msg_send_callback: RAFTMessageSendCallback): RAFTNode[LogEntryDataType, SMStateType] = discard -proc RAFTNodeLoad*(state_machine: RAFTNodeStateMachine, log: RAFTNodeLog, # Load RAFT Node From Storage - persistent_storage: RAFTNodePersistentStorage, msg_send_callback: RAFTMessageSendCallback): Result[RAFTNode, string] = +proc RAFTNodeLoad*[LogEntryDataType, SMStateType]( + persistent_storage: RAFTNodePersistentStorage, # Load RAFT Node From Storage + msg_send_callback: RAFTMessageSendCallback): Result[RAFTNode[LogEntryDataType, SMStateType], string] = discard proc RAFTNodeStop*(node: RAFTNode) = discard -proc RAFTNodeStart*(node: RaftNode) = +proc RAFTNodeStart*(node: RAFTNode) = discard func RAFTNodeIdGet*(node: RAFTNode): RAFTNodeId = # Get RAFT Node ID @@ -49,12 +51,21 @@ proc RAFTNodeMessageDeliver*(node: RAFTNode, raft_message: RAFTMessageBase): RAF proc RAFTNodeRequest*(node: RAFTNode, req: RAFTNodeClientRequest): RAFTNodeClientResponse = # Process RAFTNodeClientRequest discard -proc RAFTNodeLogLenGet*(node: RAFTNode): RAFTLogIndex = +proc RAFTNodeLogIndexGet*(node: RAFTNode): RAFTLogIndex = + node.log_index discard -proc RAFTNodeLogEntryGet*(node: RAFTLogIndex): Result[RAFTNodeLogEntry, string] = +proc RAFTNodeLogEntryGet*(node: RAFTNode, log_index: RAFTLogIndex): Result[RAFTNodeLogEntry, string] = discard -proc RAFTNodeStateMachineStateGet*(node: RAFTNode): RAFTNodeStateMachineState = - discard +# Abstract State Machine Ops +func RAFTNodeSMStateGet*[LogEntryDataType, SMStateType](node: RAFTNode[LogEntryDataType, SMStateType]): SMStateType = + node.state_machine.state +proc RAFTNodeSMInit[LogEntryDataType, SMStateType](state_machine: var RAFTNodeStateMachine[LogEntryDataType, SMStateType]) = + mixin RAFTSMInit + RAFTSMInit(state_machine) + +proc RAFTNodeSMApply[LogEntryDataType, SMStateType](state_machine: RAFTNodeStateMachine[LogEntryDataType, SMStateType], log_entry: LogEntryDataType) = + mixin RAFTSMApply + RAFTSMApply(state_machine, log_entry) diff --git a/raft_consensus/types.nim b/raft/types.nim similarity index 66% rename from raft_consensus/types.nim rename to raft/types.nim index 5159e0a..116b5bb 100644 --- a/raft_consensus/types.nim +++ b/raft/types.nim @@ -1,4 +1,4 @@ -# nim-raft-consesnsus +# nim-raft # Copyright (c) 2023 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) @@ -30,53 +30,54 @@ type RAFTNodeTerm* = uint64 # RAFT Node Term Type RAFTLogIndex* = uint64 # RAFT Node Log Index Type - # RAFT Node State Machine basic definitions - RAFTNodeStateMachineState* = object # State Machine State - RAFTNodeStateMachine* = ref object # Some probably opaque State Machine Impelementation to be used by the RAFT Node + # RAFT Node Abstract State Machine type + RAFTNodeStateMachine*[LogEntryDataType, SMStateType] = ref object # Some probably opaque State Machine Impelementation to be used by the RAFT Node # providing at minimum operations for initialization, querying the current state # and RAFTNodeLogEntry application - state: RAFTNodeStateMachineState + state: SMStateType # RAFT Node Persistent Storage basic definition RAFTNodePersistentStorage* = ref object # Should be some kind of Persistent Transactional Store Wrapper # Basic modules (algos) definitions - RAFTNodeAccessCallback = proc: RAFTNode {.nimcall, gcsafe.} # This should be implementes as a closure holding the RAFTNode - RAFTConsensusModule* = object of RootObj + RAFTNodeAccessCallback[LogEntryDataType] = proc: RAFTNode[LogEntryDataType] {.nimcall, gcsafe.} # This should be implementes as a closure holding the RAFTNode + + RAFTConsensusModule*[LogEntryDataType] = object of RootObj state_transitions_fsm: seq[byte] # I plan to use nim.fsm https://github.com/ba0f3/fsm.nim - raft_node_access_callback: RAFTNodeAccessCallback + raft_node_access_callback: RAFTNodeAccessCallback[LogEntryDataType] - RAFTLogCompactionModule* = object of RootObj - raft_node_access_callback: RAFTNodeAccessCallback + RAFTLogCompactionModule*[LogEntryDataType] = object of RootObj + raft_node_access_callback: RAFTNodeAccessCallback[LogEntryDataType] - RAFTMembershipChangeModule* = object of RootObj - raft_node_access_callback: RAFTNodeAccessCallback + RAFTMembershipChangeModule*[LogEntryDataType] = object of RootObj + raft_node_access_callback: RAFTNodeAccessCallback[LogEntryDataType] # Callback for sending messages out of this RAFT Node RAFTMessageId* = UUID # UUID assigned to every RAFT Node Message, - # so it can be matched with it's coresponding response etc. + # so it can be matched with it's corresponding response etc. RAFTMessageSendCallback* = proc (raft_message: RAFTMessageBase) {.nimcall, gcsafe.} # Callback for Sending RAFT Node Messages # out of this RAFT Node. Can be used for broadcasting # (a Heart-Beat for example) # RAFT Node basic Log definitions - RAFTNodeLogEntry* = ref object # Abstarct RAFT Node Log entry containing opaque binary data (Blob) + RAFTNodeLogEntry*[LogEntryDataType] = ref object # Abstarct RAFT Node Log entry containing opaque binary data (Blob etc.) term*: RAFTNodeTerm - data*: Blob + data*: LogEntryDataType - RAFTNodeLog* = ref object # Needs more elaborate definition. Probably this will be a RocksDB/MDBX/SQLite Store Wrapper etc. - log_data*: seq[RAFTNodeLogEntry] # RAFT Node Log Data + RAFTNodeLog*[LogEntryDataType] = ref object # Needs more elaborate definition. + # Probably this will be a RocksDB/MDBX/SQLite Store Wrapper etc. + log_data*: seq[RAFTNodeLogEntry[LogEntryDataType]] # RAFT Node Log Data - # Base typoe for RAFT message objects + # Base type for RAFT message objects RAFTMessageBase* = ref object of RootObj # Base Type for RAFT Node Messages msg_id*: RAFTMessageId # Message UUID sender_id*: RAFTNodeId # Sender RAFT Node ID sender_term*: RAFTNodeTerm # Sender RAFT Node Term peers*: RAFTNodePeers # List of RAFT Node IDs, which should receive this message - # RAFT Node Object definitions - RAFTNode* = object + # RAFT Node Object type + RAFTNode*[LogEntryDataType, SMStateType] = ref object # Timers voting_timout: uint64 heart_beat_timeout: uint64 @@ -89,9 +90,9 @@ type raft_comm_mutex_client_response: Lock # Modules (Algos) - consensus_module: RAFTConsensusModule - log_compaction_module: RAFTLogCompactionModule - membership_change_module: RAFTMembershipChangeModule + consensus_module: RAFTConsensusModule[LogEntryDataType] + log_compaction_module: RAFTLogCompactionModule[LogEntryDataType] + membership_change_module: RAFTMembershipChangeModule[LogEntryDataType] # Misc msg_send_callback: RAFTMessageSendCallback @@ -101,17 +102,16 @@ type id: RAFTNodeId # This RAFT Node ID state: RAFTNodeState # This RAFT Node State current_term: RAFTNodeTerm # Latest term this RAFT Node has seen (initialized to 0 on first boot, increases monotonically) - log: RAFTNodeLog # This RAFT Node Log - voted_for: RAFTNodeId # Candidate RAFTNodeId that received vote in current term (or nil/zero if none) + log: RAFTNodeLog[LogEntryDataType] # This RAFT Node Log + voted_for: RAFTNodeId # Candidate RAFTNodeId that received vote in current term (or nil/zero if none), + # also used to redirect Client Requests in case this RAFT Node is not the leader peers: RAFTNodePeers # This RAFT Node Peers IDs. I am not sure if this must be persistent or volatile but making it persistent # makes sense for the moment - state_machine: RAFTNodeStateMachine # Not sure for now putting it here. I assume that persisting the State Machine's state is enough - # to consider it 'persisted' - + state_machine: RAFTNodeStateMachine[LogEntryDataType, SMStateType] # Not sure for now putting it here. I assume that persisting the State Machine's + # state is enough to consider it 'persisted' # Volatile state commit_index: RAFTLogIndex # Index of highest log entry known to be committed (initialized to 0, increases monotonically) last_applied: RAFTLogIndex # Index of highest log entry applied to state machine (initialized to 0, increases monotonically) - current_leader_id: RAFTNodeId # Current RAFT Node Leader ID (used to redirect Client Requests in case this RAFT Node is not the leader) # Volatile state on leaders next_index: seq[RAFTLogIndex] # For each peer RAFT Node, index of the next log entry to send to that Node diff --git a/tests/all_tests.nim b/tests/all_tests.nim index c4e0a10..98cb173 100644 --- a/tests/all_tests.nim +++ b/tests/all_tests.nim @@ -1,4 +1,4 @@ -# nim-raft-consesnsus +# nim-raft # Copyright (c) 2023 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))