From 295248db8e90e90375ce9e00f73874337965695c Mon Sep 17 00:00:00 2001 From: aman035 Date: Thu, 9 Jul 2026 16:32:57 +0530 Subject: [PATCH 01/60] fix: summary no. corrections --- ... Apr2026_P-2025-1758_6_20260629 17_10.pdf} | Bin 3665762 -> 3683690 bytes 1 file changed, 0 insertions(+), 0 deletions(-) rename audits/{Hacken_Push Chain_[L1] Push Chain _ Blockchain Audit _ Apr2026_P-2025-1758_5_20260626 08_15.pdf => Hacken_Push Chain_[L1] Push Chain _ Blockchain Audit _ Apr2026_P-2025-1758_6_20260629 17_10.pdf} (79%) diff --git a/audits/Hacken_Push Chain_[L1] Push Chain _ Blockchain Audit _ Apr2026_P-2025-1758_5_20260626 08_15.pdf b/audits/Hacken_Push Chain_[L1] Push Chain _ Blockchain Audit _ Apr2026_P-2025-1758_6_20260629 17_10.pdf similarity index 79% rename from audits/Hacken_Push Chain_[L1] Push Chain _ Blockchain Audit _ Apr2026_P-2025-1758_5_20260626 08_15.pdf rename to audits/Hacken_Push Chain_[L1] Push Chain _ Blockchain Audit _ Apr2026_P-2025-1758_6_20260629 17_10.pdf index f860c9ca4b533261127645d2b58cafd69dbea2c4..c1650543e03560f5bf06a3b9033a484f8846f0d4 100644 GIT binary patch delta 732134 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

`|HdIZ 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%0AS(m-2M@%cn>#=G_eJ-oduC5JbQxX=kiDSO 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%0D9!h`z!bY@HxjP$@^ zFxqq{0t9bh=Jdp8B#!i1F>u)bOD99($WO0ApehVWdS=TH#s^^(?0DfxGB2Ksf))q6mZYzx;;&cWxR7^Z&t3 z<6!>p+%%#8AI6XnFh}|vJ1A-T)5woZZT9#Fpthw4;g+5-3YL)0_x>}D#UsK6W#;Bg zml*>S21(6Kj~xS>1l+j6QPp!rISGdxGwTPBBOh6^O&}5!p?pGg1oyJV+DHTsm%$`t zKCEEMW}E)q^R?C+@5&2i>Fk_Ne_?$D%1CHCKm6$qEZ^)kb*_MM))){mIX7+kCiQghlzdjrQf6|>KuZhI9u z4h+}C74nUyX4SF*sN>}##v5yMwoh(NO>lcg#EAp z8thoxh?_;r=15-(55|x`9({7V$R=csiJ#Z9`ed#r?h(#fN^ZSz4*3j3vvqijvU^zl; zgIEa>tXEn}KpPCQf&jp?gC)YD0ZTP37vHmnzoOR~);IQl6lK5&DEmV{7r;^nA`yGV zxHCnr2f>Ou$Jx_A7RY-=4bY*AVfF8j7?I9(5n3xlPW3iYq%Qk2ZR0Ac>90blF7dJpv<_D!N(CE56yBYU<@QrL>%6qkeJ~XRRHY=K;>h_z=8o1Xbx>;n{_6dIyS`o1L1|7Wg+eTHOUTLgx&Szl?Q(ek&Dj} z3;IFo{=%w9NsvpiZ(48OExWgguAqAmaTo6s&c;XPJ(zQa6l3i(Fa17yX*?D z>m~O$uMfLdv-|3GaG3r%?yXiU$Yk_gQsO4HKHO1o(u>VTLMOmYy!!uB^;7Q#=|7|4{HA*_LF-fGd7gzy3YSZea*3 z*SmjkhCWg53f`Z793^K~I5NdDYIE5CNbZ~>jBWJW^&Q1UERu22hKEJ*^4e>M(;rvH zSp2Z>kBVB=)RNJYeLAOXzFem4oWvW9yWqkLnULN~BA(?ULbJENZ5fhFMurGt1-Ut& ziEp5^NzVX;&dyp4BAGXiQH zV@rMJud&!b%Qw7}59zh9l=*LO3^Z5e$9P}k4Yn+Q#$wpRi-9k(ULn=*i23acIFmq`jHWCPmi7gasIW(kUu zF0BA%>F>RQPP{PIFM@YWlUp3gghEXh+=tVb;jdbcB;xb<#9JraJJ=yfXj0~Rxq+cBU!5k{I_M1#o3UyRo3T7+R`3jMSM`%d-6lKGi%f$ci@g^6^n6bQp>)Sd7(A=G zLML`vj@;X)ta3(cHmuRa-7e4L>&Ay8&c%RW676@8+}-_^5a&Wt z;1;;+0$2O!$b-75WzeRKW4{tc%8xp46d(t4gYaPVuHOQ%+RS3A=hb8CV_ zw@e_sLEL%R`0Q3hKdz0Fd9lt@#P{ze@0t=!|GG-u2NhV;&Y@n7j4=Kj*MI|9h)Dcu zx?P(FZ!PsH%{a7ES;Uo~4}FmA>D2O~Ps!#V*}<<3<#VOKwq zT2(!ygKtsE)X1SjU#j*YUclHore1M{Gv^O|*(BlmazA+pTbP(`8MiWi|MU4gilK6{ zjgQsK;5D?Ufe%%xOJtHpz7oKmFm5}Rpp#|^5}TH-T|9i0L46AYOg48fM|6+Far@xp zWfA2z20TB0FZbuJf~#omellg81wwSOfseJgVbhqb-@FF)?)Lox{HwPnLuedQiLR!q zZWE%m(8c@+*aLR1s%APHONfRs$yg)!-YpRh5!$*-!VJe(DW4(9m>NJFi#b-ul!y_e zS%b=};zTZMhlZwBDcsZ0#b?n)LHk;^0xfY$lH!)D5C zqldi^hhx>mQj!?Y?)@_vMYc9Kmm_s5nj8Nu!6_y0w1|0u_@AXhb6Yv)v@w)~dnaqT z(5z;Rp{>TNwYWsUvP{O&a;BG6^_nJj)3xy5^R9@H^*3E~z{T&P(rJKnUiXZkyEH`v z%D_F=Ki^Iki%GpSSjna~770C|hqmIB{ei74*MnOf97n{~1Pssd2JhXD!Wk7#2C(4f;e_^t@Oe82$U0$RRlr;GTG)n*k8IY zh5%HY1Lg)wU-hzGm@(vYOt`CZ&l2#Rq1g2>W^IMvRTeu*Jq9s>Ba;7(vU3W~Bx=`o zY}=Y(V%wP5p4hhi#rjFg48{oKJ!apCk5rp*H->ZdX|;j<@v5(Ixg^1Ry~M$Uj57!Akw;Z4O|U`D z06IeyYGfVJ>=yQz-|iR|xLVC?WREdzw^8uZyHYOx^7@ij!b`i|IjtB7L8c&@o{n*hM3)c_A-_Eq^ z6xv-jyoCrHqblYH93*|^M?(H?dxY&V9ujHz%Mc0xM~Nug^O?ybox>luC9hB5S-Ufi z&q;(c5%Twq!CoFrn4*&z1|5kBW0~H(W(Y^qe01}sQp zhd(ETBDz2P+EP`fFm14ZR3zM}jKrd+#BG?FVhM>kP&ktqhmEbztcDW1wd}stP9w)i zM9eq>3b9P|kN2m8$UE@Ql_7bVqa3LM%L)Ad@rq9`A9U?&I)&RWkM2UbRu$CGWTc>i z`rUo!Ob|9+nn2AMXPdUKjQ zn+VC_dZgS;aHeN03Qq?g55E{X?n#sz!wt^{;Ep{5$ktg4H%y{+h4p_6Qr)hlv|}p? z(z3*x(%+i>+d4qdo35H!iS;;|lw7i@te_Wy#Z)v&PMa`rWJwI1vExuS6|GsAAqmz| z%S}H%;kPv3z;N8j4-Yg#t(FzwYr59Ua85ePI(QOkz|8SvB!8j=zrtNX?-rRpnRK@S z45-&2nEcM8oOPtk;Jc6=<%gnYO zMq0_bK1P#f#pj3%>T+1EzFtrh6Jt6H$*2@S_)2|_7d#f8f z2XLC*@>3-@GzvVrHuK-bs=ASRmdMq3%#ah4-RDU;$p@5WH;tD^Bh(sYrMO&1Z%jLo zP}!Q#x1&>!%#oyVja8@Dy-O`cNNr$_cH)z|g~D^wjrZS+8R)hBLCX>W#69xFrV*KN(pNThz$_Lw^7a2ZS*NWbCui4o1)%xw4bpEx*NHQX(U*}axxhJrW@+siJ-M+N=uvz7ar_cj38LHReI%@~ z;OHe;gpIe406Da=xsL{bJB)+#;0#V!I=q*EwgopMpz#u_v?~Jm045bQA~#^?;Tt_d z-z9(usjil8Y3K)|I6P<>G|OGWh9At6H}86F@O0hUQZHLWAKvz>tl888bw_s}t6;ww zw3Rx`M9caVD`=G~+I+Y=<;fIY{12pP4N;+K=7Z_oul&sP6w(SV>5}*P)$~z)ks3&& zUs@oh_=Sd<%q{YIo}Z?x)^kRTrg5U=0nhN`uYc&~gx-spS+J~)4cgvW(r`B*QULEV z$tV+SsPv~&uNyqLseVi7imXj1E)h-9MV4%rYW3M8H2$}aC+DGS3BHgjZ|+NIqW^*s z66jO8E)lZCf$c&m!bf!E)zjsY6*+I1FWOeRgv}Okwp(;;YtR9XGm5^$UnDlsUuD|N z48mKG8(13alw$Iw7ND7xCu0Wn5CA?+yhzLwAi>jA^^8m(gsw>NP>k*wQn@qUM4x`Y z_pFGJCYYv$=?FK1UIcU@WNlt4;|?4N0l$J{tTDp``O8%UofX&S@uNzEC6f??JIcnhd6`8n2xQdOM9co$nrIno!BBMVkLA@3 z*-1(5s%3Zll4CCcQ)c4UIsirI>{}_1%p#SAiWl6oAM4fCiD$+vC{6Gp!SA<0JZRd> zj1ka4O}WvmeB>Xv`f{!4T2P(0W}kO2ZTg;ZrXi_K+4N^IMWG^bibQQmS{Ti@7X_8; zo5Uav;r>AyLcnti?=RehB=?xN6^`*v;BWdQ_iB|~s;dwVnJswR1V9(|DV-;@R_6De z7RfF6W`!+y%hWFHH8M}e#oxEFO&p$#%kj6=!{s^!La!BUR6Zer%gGo(zl6{e<5Mmn z8j8#m4Uo#ju2xYIJc=te4}C3s`K`+Ic=2&>Ose^xwssJ4s(q+S6=kzKan`CQs+D#& z%bA0t#kR(EHX{e!-v|ZEbTk(101M+=$#?NjQ>m>i#}vYpoJ(>8X%gwhOw$lmg+#Lq zGDKuHgA?yUrQ5Az*m4R_dzar9>0O+7oNG_x-UTk>-nPr+SBrqOE_5p0TRm<HJ5 z$2X4`_*+w}TBTl9`zh;Sn}^1_!qoz3K@2fXb;X{~VG)G`NI1;M3MkMLqlbGthYUGKDgG5CBXi-Om@|g<0B)Oclh#3C*{8vbY~~R zsQjpY6r?Q%xI!QB9BJa<*E9W@Bgo%987LW=XPryq;oif}?J}fDvk~Ssq}cvgLd1+= zJ=CDLfGP^Bz;YvtKZ7Ifa|kSAYFt$3<1=Zj~hzL!SdMxmtXuH z<)(Ae>0P$K2jo`~Czl*XS1i)Zc6JA(?`;AnBDkd7NN}neNQ6^(tYv60TOUPEM}OGy z-+sQ51hn~MkUSc>KXXcgMH}p`BccH=MWlZmIr=jZioTLs>T~R?cK7F-VIBD#$}V_^ zZu$yR0a5YK->2>P5%91z#}Gauu0?4QlZG65_53ua9YtD|eC~C4Vueba8GNfHh()I@ z#Y*Y?l)KPxDu|J5wUU_ZpjKspS&pO9Qx2}WI8P8E_vLve6p%+K!Jp!)JXfUQl&@l} z=~d5cH|dF-$F9t({LXaV?CL9y-wuWxmZcN`-FDbU`hy74<$0FyCe+6-&gC-3<2W|O zjzDmJOnMY?92^5#I5C$r?)@27HnA%D8R3MS`X!&@M)HRexib6@ejNd9=~HPwi1Fr` zy1$cCN2H}cb}e}*oHNm)9>C*~N&NDE4u&f%W8R3u$z=DbESQDIvBmOWUMGa_mIwU= z|h$)F+5 zETo$RxH21^@9#0io?#RBy_*lU*IE<@F!vy*Iq#^1+Pj~I(uph*=F`J7V)gTtE< z`#e6-N;vCgMJ9QvDmA-F-9_T}tv}uHHtxz6urp5o)Dq2hiUXQZOIgxqIcJPO(j2(= zd_(WgR3Kie@6^Vwx{_HNfmz%^XJmb%%*3V{+5Z|E`hDK|M6dr?*bUc^6L^yVn(&#? z9p_`7ia1+L9$>DexWLVGZ2OP;OJm@V#${+jo__#fw5J!_la8vJ)}Hk)a~bbjfXerx ze6JD5zT2uqy2DJ}zT3=#V4#nSnf{}I-`6{$dz?LPXM*cj56w?^f_XMcH?|$)hgvv> zCo4Hg^2mbpK1azbEl@>;L^-Yi0ib&o+Vu&R%66dTTY zdI%NrFcLYDX3HN8g1M0eU;>6B7jQs-)z6og86IOQ=HqXIj>Zy|{^IS}CeX@mdqud+f#o)22wV zKV#W=oO4EUWzHr`8u_XpHWIWZIOb924>Kq0mpGcD{v%bc zanxk5`q_4hD>tE|m(1#T?hCN=pOaxmrMYR=5YXE5g0|g&yv}=}r3@CG_9JK|kRfjG zHD0sMiTsSAq9j>NjtDDjjOSN8h2oq zc%@BMrBLd#b4mJzOd7`;{l9Iaw9z*R#58zXC^#?<*0h@fIJN(mL&3q4w%Z8BpQJ<0 z3&z2mR?rPkmez9k-TPU6hoEeIJcJNOPy0;>Nt0%%4W*j)UrrZCT0j_*$p5LW1kSZQRqNL~gcT^jrbfbkhf}Tq0NQ*kZ5P*I%JX@9 zwyRL`d3KZb#qDnfrBX;u>6?6y}Qn%H3aRtbFD!2emHRj9JYM@*gvs1c*d;hK*QID zKdJ!O=Nu+6FHx9lfV-^3MGO_coT2gag@xKeDcp^(9-o=yQbllME3xvN6MS@taE68+ z=~*t4Y_M>fM-(x)86|a+ftpKoai^b$V{rP#Vl_Tnl;NjY2tiV2I3p`F43#lW_;NG4 za2kTE^Vf_Zm#jnd4fb8M-tA-L%os*am&OAqHn;T6cI5^$a(>=>ibg{Q?a0BHb9XBK z9OC#_7Y-e56BFTQw+%CcH0zQZano>)i6w1DpZyQqX?59o15U-p>D?TI_8E=Jo_+Uj zn$X9!-MRetANy&P(^;-%TBfwP%E!OQ&`2d#VFm6jS%0*XA5NL36~z$W$yKwtMxQ-PI`^;XD(_e8oWI8te_5eohM?Y6_!=Co<`#+V+!v@QTDD zO3GK^h3mM=1+*cs508ARhC9I5(V}%)Q63q{Ask=xe6r7L3a{#9Sm_Y%vO}bEv8~k- z)}m}282wm%OWX1d4xdoRFH2)wph$rLDZNUy8cHTZk&w`1ECF!uBj`X)s^!G*Bt@o9dmTK! zo>k)#B~NneXCN&))n=#kqyvvF($<3%*{-$F_Y84Nm%qwt26q#w@N*A9o#&PF=E+9n zoIEsCdK+Jo{jrVwwW%@Dhcb&B&m6qrT9rn@k%m`S5}jk*hhtZrkhH_4(+Ye^-l=Wb zU?N4zIyDHu*h8?CrTdG$8}&HMc@b6Rv%9$!CQ(i6OYoLwn8$&XQ}L%+v0S;cLQBJt z6P&sEjzLu`pc#U6( zM&CL`$Rxz^?mUG&EDl;3QexajctE0+C#H90EyNOla9~C8Uc$Z6Ey&u-b!ClA>VuF_ zx!bVBL4~KQ6%j_R&x$7`g?Z&53cR0@fl0Jl@nCpH%xoRwColq7zyw`~gcpt<=%F*$ zRfYQ4c^k=D1SQ=Mh6gqip~^n3prjNjnfnoBEokmGT z3bP#li53>h6GI5ho9pxhGD_OL_J@Fp^&84#SWZ!Mj-IZBqkvyD{fg!?Hb=b@ds$FWMD-vA zwkt2RwY@Tu!sPuNq7^6zFZDU4XNJl9Lrx`-5ytiJ9bg4O39LGw=ygTeW4AAZCaw6| zxYAq-D=mF#;gE8BX&F9DLlMb_+O)Un5zH`{Y9Q!oHir2EzhkzuVNwlp!re)0M~R#3 z%hcwfksn?&-#qKso@V?w11|SNz@Q#Z#mH;G!eQlUtI$n9@Bb^?p-0tQ`wZwl>=Yc3 zS|)#jwMqoML9n;SSk{(T3zJV@KXi0VWG{}%5|8dYfy))@u!Ltd_*V+*RN5o8SpZeV&+h*jU1@ji)eF*Bx5{L( z1Jnj5n5!)wdS2>l?zP(i8gk-noF2DERR(bV9XJ52ZS6bd@%s;3MwtyxMtzPG!f0Xj z(jQY&{ScSjl5{Y3+zfO+*oTM8h#d~OvlNrvUx;a&pgP6{!ptwB3pzk0Oj> zpPCucPqf(O8rcO67=EzWA0KX9_U14Nv2Sps6Y9M|Qr2MI<(e&S zMe|P+FG8Y4@58xKHhm-q*V&q=4i@ZN<{D~PpY zoh|}F&#`qZ*qkMtW8xn-4kPA_mh6$xAtRj|UE^&c5SN-iLd`N(fH^1l_!SMoC^cvs z8|0l5##MmO`Xh+JfZ8~SCBrvg3UIIW9 z*2McE4yw_@@N_*kewyq34w}(|EwUXM2mkwlw`J}5>ti1ZiikCLYEKZqNz4@5Lda8) z#Xjq6;oO~K=7tW|+^qPHBZS*5Q~85!gfB+Xt-)|*+CDTwHFk?xHXM}JYSGS5g983_ z#&vN2{;Cf@6cI3#V}3YcsD(fX(o29r)*4d^Ov{O&O6HvW&*fwNGE!8Qrsh$spuK{n z>Ba=akC{M7qe29;2w3)XnvyM(-t;0-MI#1x&%Y;M8yBEE6gZ6AkY_KCeOI-xegHWk zD=-=#D|zpTmYU~LY}RaFI)m0j+z4E7v3B3WUqrR|_8qZ%HNiZr;fbY$kC%X-Wu%I_ z=;`rAo}S88)?l>3Ji|g&KU#mc1XvuYf##~#5j05^2J^VbS`aC85Ez*+gwzaAz717C zx@$Z-w*-^~3ew_5-|48lvJA+Z!>Jt6u5nIN_D6E0;2bgObI3$Iu(i`aCkvNEi`wVp zGT%x)h0E28Z}K;nR2Fs>*5r(Ve=r_rb2^rwZ9VvB>@?XPus&y1t5Vim6p zDj9o+-Tvpp0e2+fxw_FAW4aCu1J72R$Zm1=?5r0fn3D&hZMhqa*FPrB8FBJe$$KPd9}I@Y@iw#xAuZ8*|QLDk8|hUc*!m3 z>Q!`%TO}O58=d06JFU2x*VXEPZEO=+b{7M(wCJQ!_gA%Ujr3;L*iSA}LF;zDV(yrk zRyB@EQ~iJ5Sd&{0`cVJ{jL$@d4wZ(M?pmxSyFWEul3pXZ0jiVMrw`e6A~)1=#1+rf zQSz?It?BU;6z3VvW5cRg^}XG51P#L!C6keJYrNCG-lUzaQ38|dek1X(%3LqUFUQAl zLnh{N*|uaTn{{xUo=;H2jf1&`^eF7i=j$44oo{!{O4sr+-DH4Z=#4&Ksx|l0kr10- zRyyG)vfqPa4qn(S4?Dw<10_2{Qv(4RJA+uSRE(%i79MoXS+LG-xp??6&M##&qxD5D zF^zJ^V|Y(IRSWa@K66m~=z4HZ=%Lo-6ho5djKrFDMs{qJ7EkM}q+0+oZ=j=tNDX@J?BkEu15ZkE3t%CtlF!jwOLF2M3D-5ir z9)jT|*#w*8O4A;e*#9lx)#JkH^e!sDp6#+YXN62QaGGHH%khxlUKisHb+1SJgN|xxl zoF$#YY|)@1-8<%^a)T=|DSbjW+vydcX{XaS6avUKJ}Em?4S;CAHqVvW#!av$Ju#JV zq0Ys*TC(`vCg*uHF>w5fRg!g}cGfpGy>sEi@p9(rp*)&2Zd&tBIDSCEAp-6>`MHXB zn@%zL8vqr3tkNWw84c4fD-=v=%NAnh6XERjV~xNp!UU4!5JUllfHq>foMcfUlO)`& zSdhF~RIca1^BR)bM>qoKH_Z^Ark;&%iJWND%Y^dSJtze6SyRmO%UeO}za1p9D}TCC zZfssH+Yk`36IImj<)zbWcxW(AJ0QyRx_KzWSx!eJ+Nm)jBYcj#I^-gJ zp1YcqIQTdkNeXoXHHnQwMizYwxiBW`Vv^|!H{Y!(ePIWUP~>p2*xlf`7MtZr96LIE@3>c8G|Du8TFbO10RReHV>?j zw-xLbwZt0V;G1b}33%EoAkHqaI!%cd9X&3h+R`XGUd#3sDcba#@Y>b)m&WRp@@}FZ z;2}76yd-vZUT%NU(ckj`JZ_QVY_S)h%se!PK?ReCqCB_`nkrLn_M)p@Z0$ko( zLO}Y3@9${C=-Z}2XhEWfU|V4DH920~yf%e8qcfaG? z%~VWbNOS(l^VHr)%+%8@LDwG;g31Kk_a8e1e1d}t-0w(C3>{Wu1W+h<>(V=MU?{Y| zLutyu*zycO0+2!aoNjSk>I&RY0Kmv>`fBh%Y|0*DO>CSxVGA=wj}ODKYb*Kj((`Zz z%|Bqr`LwcFHXkj_P1~<3^q8L}MGx$gbMn)4B)8rUj-cMRgF8=PtmAZXcp|b&iR=(| zLn!H1bV2-;x;O_P|E6gMia4&L-wkxhj0NbX*jgmXs-VTYIDtJ-Mz%*E0G-U4yL0m% z+|(nTtiqN#?@B9sVcVo74*f-rQ^~MOuDhwS7578@H?_CW3aZPKsWQHB8VSbhS64t* z{$JHnp|k-=J$QZ?%l)(&-@9KQBi7qv36UI9ZzEb4_*aWt_eW*>usq0@p0U!diqx~C z&H)@L_ShcPPwGos1B3)i0CusXvcIiu(xz=jvPrH^wVvB-z?c($GxbuT4{=V@j{Xj& z2Uk%*7&U&gl08;PYx3WkL}p-0XB7}qsjJ*?hlsYys}?mP?@in{mf?@Ln4j>83vx}X zlJ6yC#S%xJ+*<2ziA^gh%P9NBqhp}s6?r6EM3=yXT5)IoqZd~xfK}(>VqE>KH13GV zxj>~`<6!kK8p#_%=jT+6BgxlD<;@*rO9k+Th@F|n7Q1J4{F(>12LF-weqz%w^1=R{ z&~YT>h;NzjW4g6lI*bCUW4_qa>os?(sq>1v!?RNH+*F5eEyo$)T>NLmPm(72J)m;4^QFZr480gh@@Go5j9Ay7UQ!@3XpieB$>2Xm|2^n6#vzb0$cBt zey+ojtHh~5__~CH?bXDSZdaS0muPwG#khg)@&~UIOfVI}RD;P3k z9sYF*$1LCuutlPWTcO>FLwdHKSn17dL?U8YqJVfsAl6j!NtAq) zdiz?tV()c&DGA`^_^K(9yUhx`@v&V8;M(sDP^TXQ8juqaCIjHj2XnG@SDqIi)Orr; zm$7fB=jbd`&-kTM&2WvVM^zvW`+I>$k0X}g)AJDQs_Bz%sgz~uVCrWTY{IVe!ItQ= zL*dK8>t}TgL2HD_T{T{ zVy_ec3b(9zmZ8%zTG6o}`&)4E$GE>xGL*X7G2)oKW69OBq|8W zuApW2Ih#un$o}Il*=q1Z1y791!f2}5E7v;XF4u(Y3zderKk%wTK-nj6iX)s$w~>mL z`IZj$`M)dsa3{%2?9UY!{ET3{mPC{A3@HHqx@igG74#e>o`(@nWDn?|*L@&dUt(nt z+#gOv3@nmS=9<*B-$lcmc|!ph&`=8=O;CECuM}Ilfb__8N(xbADZ>P#q&Sh9l|# zZ6kTan#fho7skVr_~i5%(ZYGC`(PRGp|3uxdqIW`5_G2aw(VqLta+}6__{8p8!ybB zoX_2kA8qAm8ZlAb5WfQhhW@N%=yi;QL*E>WET9;q?$HL~LbFJWVCw-u8$G%k0?I)q zGwhcpOtN5^>eQ|0HX3T(tF=+_o zw2R>yYDol-KC-Y3t>*LLbJ?nn<3>^*$MYHTJ?}SM^C`ppn_X9(lXIfYYXhgAy4e!47p0F<+p0NV8dOLIW7J z8*)0zn9-9#<@U&>e~0V&R-M}SA$Pttt9Fi*t~+{(xIT1=m{RW@q+ahm$WiYts!`8t z#OG$8S5J~spjbnysWqY8xBTte3mjNwG{O-`l@dPttDMa(HO!-q1L(X%CFL-3=Wr8~ zI2ULSUJ2i%;zY$)y4&$4sP2zR|0PoY;e^4Tk%$l~)48;7wDupNj47>Y__E4=Nb6jn zOcETw-n*1@fl>Q>dgR~rGw8@=G-skXKZ5U>GtyCP>v_>l z2enM=f9*`0;dXKY#D*>#38OY8zR28&lfzfKRp^YLxLE|iv52nQMK<=FBisI(Zw~+j z6r_}W1d_yx5!(da@*b19wbv)Cr39hiaQVFZ?^{$wecVtktQhrW^%!!EAPYIxw=7;| zWK+7f(Ru=rAhCd!^+(}ghU#W?i|)9lCv$kGB#25Yqzyfv95pi(q^!4JBaGA)fagczrMxOBvTeq*V>dQ^?A0W(FZhx4}b zs{JwO@#rZ>I+9Zve?1sP2dLL*({K8ZM2dy689+GzR)zGF5C~B*TH|g)qrT};K2EEV z%o>zdHX0c;UBqHps@lT~Tg|lKR+tL-s23G-*F|Q@4(JDF3l%XjhW|cg)+-WG!O#0% z`ICk!VmsVO{~4TETzDCbW*}?UW>XF&>sHoNie=~`%eM7-=cw0Uta5(nH-%<8{wus} z$72WvID}9nwi_+Xt{`Z7^qIjiM=AU?vdZ(Mka=vRmUh}>^$b^${VYv&5S7n7#J&bS z$*b72tSQZ%XCoX@;0384L>IFUrozc9r3$Z^4uQ)+c}Cnk=8;1*+P~B=OGeRs0~=EB zA@S;bfP{%Xvn<_>Mlq@N1vTP=9p`gbA@KDE5T1{nuZg$XH+tQ}wwL2XGwySf;h6@6u9H zaiwEaa7MR%Rc)_(w%uTPr;bbE`!a$BB;*=!@`7;^^>jG}Obr_|x^Et|-f_LY+_iIV zuA7~88*i5j#p(ZfGrq+GsD|DzX-C=d#=RQSc>JS#w#}k<2|C(LtKV!|y(JhC+^tyh zRS{{=x+rPP$wJ4DBX9Z0rgc(_`P_wFNV>KV9r!hqg1I@&2Yid0-S+K2T^#}gjK}M8 zR&S?CJ8AE2N>JE-gCnnQ(AT|Z>aM!0e9U|P-6cI=O*lCjKVSEmtjKw+DM1&8q>UejD+uXX)YwrOw)&lL7?k=jTh58;U8)dqCuV~px z|8Z~uwePiJw3V#x6u$|eV1bEEDlB_Q=W;K3;j8EpHVAqsi$P$uoACBs_l}j9_Kgui ziIHNpcf0XR8SP^C^%1c6xh}ovt##`n6n9D8-09bG`zOmzONA_Cv{p+cDDn;-n?6TF zyV){APg=6>t>#!kCH{C+Ls?lJEv_<{Bbhb|2(8SVmkIhJ8Gu^tHqE}frOv%KH(b=A z*Jg=*{rQga&`zv&VXH801=F6_)ae{Gn3LG-VP#(&ZGa-&oej{XZ|4kEZVX=|+IFSr zwtpz5gte5q6!fA!my+t$NWK9^9SYWgkhjX$0KG@=lwyr9|LoPrX}1OnR$Y#$`~_)P zGvIC9&%6G+_e3N1BpZvbXLqr6we~w?M;dn%xnlY@IFaWmN2FsaOMF42LF>Ji&zoyo z;ri6x8f`k+6%3H@HrDbpuWQ$Pn3C@wzkE2K^F>e2n}oEwPpX#Bn_ic0<+)>R<7Pc= zj#eRp9^$5Z@!OFRk%p8E<3~#7G8c^OeD@l zQ_7l~w#u!G6Kp{beH00yBb)~*%cWfjQ=AAXmaz>EVFBRRr1!FxMD*F35{=k8HE$nN zYrY^fwbWfcmfxqJe|wXSa;o2#p-u}6X#b3Jr}MqGEUz42y&o=Tp^I0X#dqY>g14_M zVbP$q@BPJ=n93n@>old0e8%W59_KoR&jj^p$T#towoys|rh0e<=~_<7xbd&*?nmGH zPRvdVfhS<*+loLnH~UZ)EjK4@TLnIRp(0S>Cgt&9^dA1;Y@My*q0L3)uut%duF1UA z(*wD_g=#7!nB-E*S(q$TyG&=JJ`dM~CSdF>fEKZN|L?C4XZGx12VaWFcD;qM6$nku zKFaS-*9_Gjb(TlW%LM{iUOqXcKtdSXzYV}?DjVRoOhVz=zYp1I+uQ4M(yhKFtK#aW zVtsh+^UU5^<`uVdpx9DZFiOF3#awHWTHwJ-Y3SRE?5nvtKq2P9oI=6i=Up^5kCmpG zTq5S}9rbm!92{Gk-|;1x5i7A_Z9z0|naU!0CtJKodEcLSYT|2bus z^obojkoBdwZ2CTkv8)4kz8fO*h5UHXF!$g7J+v4kV%o(P7%UbS3keg6GRgmE{A6eQ zZ#u!GJ`y=NGY9+sIh{ZVjDzie;|bCxh#+x6xsq-s0@BQgA>-4Sh0!Qm2}mHVLBY+G zxzi#@A!EP^e8O6B$snOX0TU30DUou_1xChWA|lTz0V$hCDMLn(?-i+C{k;(Cv?#<` zojRE7OH0nn;l;ccv>0Q( zZ-(&*?7xkpp5XI1l!$FhXv0i<%L$EkQlj~H;~-G-vknb(8LM1z0A>W=xVg&NIuOyT zTrd5en+(*8ZRqyw(Hr&#F@qzZPP}aiqBzPk7@hx!- zNYsm<-vuduCuh2e0Zh;ND%fwx{3LWo;97n(Jt)6OouDT6iH&#!dV+U~vZHIO=GGg3i;spg0F0!r=S=;w7!U|qv=z6s zFKP>UH*-Vh=xAw*r5b3c1E6{EM(}N@Zf?cDPMsfWGbQ=+Z>>3ejKD->xuZ7FZDxFV zs#c;E__6Cq4`xLVh>!G%ne=J=(n8*DEW|ObL(Vdf)<#%EHbf_}ymL#+5B3vF>UD3- z@a3Y#Mdgf60FCfBxS5x{xknK*K#!dcAHhCPSyKD#gek7PGnsUGRON%bm(>vT^uc{v zz99O5FP{*RE%22=UMgc*Nr*-P^>k-})+d7JPnY-D?i~O15flux?pI3kd=>8n5iK(i zb$P6LWv~L|(Gjj)GaFYFB<1|{SZ)QXB$7H3O_-7aKw?Dbb`T1Pws}yZ8JsX884{B$ z$GUr6F@JM=z~lReA_+Beak6Z@hQGg?`4OV9bk6PVT!dd#4v0Cn1OiHCb{6w zwMq;{0qia2&%_iI!9M#v$F)d8vVqovMhNcG zC~24A8Tz`C5Fx^$d}4>;Adf>RrBo7Mp9C{Y;_08BzGVtj@ZZD5(h7Ekvi$BugY9%t|N9@k zEdVHYEvw@Gly(7Ii)GeIXtO-KvC^v;FSEU=Di{_mNUlYl?)J^AFamu({sI;`R!6cfrsNnnWl>ty(>kdoSGlx(pH9Jt z2Du2kvVN;zEyMIC6h)~uOj&Zf#of~Iq;N zV8V`su*R)N4rnZa8{WV*Ox7hu$TgOsIDUB}9U0)(3h5#;%H&F_MDZE1QFz2p+HF!a z@K=;1aWw4O$4jb?FvBty!72V@bkEE!ug9B%BF^nuN;UA45Gz>XC&lz634{^{a1|#B zyK2NtFMQz4(84$YWwgK?#ITTa#~)DcVno2x(sI~5YN!eXk~`z{=17qIge*rptOozZ z?CbR71Qa8vItDTcB$E2BPx8#lioXs&G}!2e7#K-yn6O3JCW+4C7fC1~I@StT(WDdM z+nCO>zdV@4T2RG6)8BuRaHh(o<@jv1XnE@mfoN$^{C1NBYJg=I0@ziz5Fx;qlG(n} zM75x`pwIlgOVPVf=P^gqwbqDz5HRI6h@*0cy=pPeJ4O8zq#5fpIaI!~%hT=P*Gs?e zoZoB*oUhc?ZUh#S6^c3ckD~DBms}88?GN9r`!8U!LW~qAq|<3+9u}1EP>K*e=Sl~- zXcR@t?A^_Qg3Q^HrGKd$yd_}1rNQ)k#9y!}Cx-QvV~7u6GRv}rc*aqU{nQhg~km+O)d%c}B zyqRHbdFh!IDPA>OU;Re+vX|>os_@lX*J@spx6h(fd&>Vj1~E7yECaYj-TkfSt|dn6 zeeje8AL)Mt*IfRQ_=I0}!Mxx4MR1wZ`BQ_h=6Ubfhcg4he7VKVR>#rga!rLz+(e*h z{oW?(WaRh&G{{$jOv1c$fO?otFBZbK3UjBWGUE*9G9~ z`E?=h^UA*OrFQ#Uf(G>Y1D-nfC(>r3ly-#qd7UF{js$&EfAlYuQp)RExJgPrDt*eO z{z4Sdn-*_Gu-@XTXqzDxSnjqijkVxv_0%?&zH7n9?gB(aJw`=cy6WI_miRHJ{jRcu z?7<_w=p|HVU#x7z;jGE9@@{R(iSxQ6xx8=>wHVnE5dAX~9}PhJ6LaU8ovnI zsZrm3-P`&xqHM-kOCiiE~j1=7Ve$&f`_{FBGdV9~s z%ZTeyjA4BT%Ntf$JR2SH?}47Hrcdo^@UATIznt_p9Z!I?E!%94=iR;6xtV!LwkB_e z6g$Kh4fGr6=zl<6PJ15$D{Hf=#P^HF^6uDyN!ns-H@p3zJ86@qbX^Z1-h2zX9A2;9 zbP}|4Bv7tx@RQV!KfAT3y>~(U_MoU6KdJVK<)St^agL+Z#5mR$9NnI3g-h+}dTrsy zTWYJkLodM6r`3qc{(iQ9Bfd+3ghH@oZ~sFjGbzd}w{ZIl(?nT@`A@x7%IjEsHRczVUT1#}3UCh_dE$wUnynsN@EL>qtt^|TLHXYzP6@co-(D<^q^o~x=(_QW z$B@w3hLKbOfO-=yI)nv2O!x(w49JEqe(UY3m8aZQaS|y=D8T90AIY?YT-erXAn|^D zTy_Au{yHy^f#-1U0j(U+ckJW9f1Gl|yn6)?vqBQ@7z5;9xLQSezjemH9NAeY109}x zB~}d_OkxZd35)4f+?L=w}f(uG6{j;ZS4dvCfy_2G6OrN7Zbe?SU|bukjeplG?%kKpe= zGBh>&q74eesUON<>=;R1@GZ#-Taz#(59*))RQ{Cj%Kvxygt0jphyADX{!!T%pc9K% zNLl3E4*y?OnVZ2PTF~i1vICL?3Q~*p5XNFwVERe4to$;>cy~6`qLm9xVP{S7qtHc{=Vw3XfoxiD^8s~rAruG?5qYDEAmy8&GYk`q3IntD)#Lxw1-swEb+%# z4z|j?Ytp8aFbaD|sbYsW;1(EfUAV{M@9dw@MwH}^*?AxJ&EEHxSz~lT1s3aLd1ov` z{d>z_j7xy+xR+4^+??j$Z}r$xLFl%$B7&#vs1N(UPZn>L@UCt2YgYP6*gJ;+dF(p! zD>U)dkfH_Vv0tL%1{zHtu%DhDCYsWp^;Ce*{V8grLSM13QA_mzb+vzyzUsZsaU$oH zBPxnZGQ(?S-Ta>uZ_0(lUowh_>Wb^p%tHnkSi`!@(ITHK;{D|~I!ZuZ@zZ;br)RfK zoeg87t+99<_S-qNugKr8g^_hNG*W8eGgetrwnY+f;%~#{k)=;1v}a=%yV#iEy<7#o zg*%2ukjum5U1dgqTIjHf{8Ou{FC%xvpS5Lar|^S>C9=db(~+%}c|B?r_OfVpY|#p; zQr(=GK?%;BpM`NpgSe2q{0123HRw1RMlhQ{kFBaImKli%k|o(qJ0d(w+MkDmwhOCr zQ(WbruQCF^mU0@iTd@O)4n=G22BTE8%e?hWkLd9+>ZJJr`52x-BEC>HH&7wTZ<%I4 ztDkwh24(VK8n)C?Y7;_Pl$bq(Z>?xV&Y&RuZ*&nz(>m@iZx_?dQGr+|sDI(xN%wy_ zK9`)hGn3E-L+uwE1~*SZF|7B5N6&VMl4|Q&L!t0>^xzPlo_Do+ND)||2IIl+jtmq($G$jfStT(7_mb@Q-mgN;iA;9^(p+zkou=GxZ<}8AQ!K_Is-4 zn_YYprTl{D_#pz7QO{v&_GBW-5U$us=I^X+wLzEx|oXokfkH)d|5(p=PN zFrNG{8M|i6O>N#-BAg^-!zoOu%2VJ;w}MGRR&eMtRv74Nr(3dq1%D=~$|r`Vl|G&} z^FaO#p-;sue{OEaXi{`WQ@#=(*bTLoQTKR`eoXPka(Z+s?(JNx-JM^EWVoBuCj`4c zr_NmhE>Xmn^Cq{D0%YvNnAVJj)cKN!mTM*ti2`Kwv86AwvQ`2}5T+1%mvs;wit)(Z zXpfUoLfs}3_Q7~ZpL!A-WqP&|Wu_Tk{DN#kc33xA*54o*9wzL~5K#WM9x&BVr~73g z#ladLO*24xAD*MMxVXXW=QuPriXIQFl3I)dMi(XLa&*0Ob8>7%fl7~);;MU0+S(EH z>!#c^E!$4-jm2rnMtLOE(lw%qevizFQs*(`KUS@!+&Imteid@_QnPKy!(l%$4m*s9 z!O<1a{mYb~#4w-?P{b%nS)?i{SM17oRu;{WQ%;J=D!RQoJ&R9_KWcNy2!W% zoKAO4r5%=aZrw+Da@e1#0A{bAGdMcGyr1?iP_b2i)P=)8@^jv7*&lE27$jiL_qQCa z_M2TzBd~U<3d+lB^K|CDuf)QX`l*vz73ud*j6tf4*HwS6l@8O zfh}F@(s^yJa?ki-QXv0{JxSlt)Yd;@3w9~{#mGOYVfGq_a?GiVD@GjKAPYdQ@akdZ zP_f$ei%MFHcpzG0uMz&u0SU@PtbuTS=)E#h=Oy(!T-6o%F$G<>)NpcE z`SLZlN6wu$-Q)A!o?pD_t#3SdIp#5vchxfg6rJ}|q_*;tOtw%UeFY%so(t6w_o;nc zv|3SFsixPQoaMS2U*-_)#&ifv_vvhUSciYv!2}dXXnCE1au2*#bYx0zs~y?VJD09* z_vl&vX}A*ZH5KQ_re$bNb>hv{wC%OkMY`vshTa9kV(p?MZPfP)TtdKZC9CPY3 zhdEblNQABKcGmhS3frUSHm=9o%Zkrx?$%CI!3*-39SOF9`qCaooU@9W8Q2V`J z0$n(^JI}br3syZQ#$~S`vV~gAkwe#p<{>x`4id$$I_2id7L_Q43tMY((l8@p?Q*SP z$Rtu6L%f;3I!!Ct(32?yyidEWpR;h^yvsfc5=@L8Dni7dWU9O(>EW3n6Dr zXaeTeky&NyCQW*~TrFczz3qKWr$4Y`K#CRm9uROU3;CQ*Ok*%WRU+rDwZNDRoMAAx zOG3enKuf&#q9j%Q&fh@$KBt7#4i`k!_1b5*#MGE@T+iCRq9F%_O$IbTQ$IZG`JLhQ z=paVfQcFvrMzK}@&4!77^%5cmA(UZ;Bx@_=r zqqM{YfmuD&Yuza*%YFU_)MxZmNz03`apo2T-uK=}%p}U{-D;CreRK|`#5%ildUs%?avl$Hk@&_>kGd&g z>LE1BRkFWI>Tb#Ai|RHJ$gD&^B4X~S(0wgL>9$sQg?j!CHE^L6SD;n}pu{E?LW#5y zS>|QJR{3OiB+JrBQ*mWfYJ>TJ^(6r`SGz^W6&@3_q&5z3OHgOSB|U$_&~+Z?iy@{( z5i19m6S2iaQz|)>_0K7rq$-&motMALQo6xFx*LWZUTdX&-mXDi-iB4&KTq-bM+emB zonO&%)j%LoNYvprP+M9?LYnTGa=Bm$jutiFIPWC$W=yreo3BAD4Q-=}QHB7Oz@;b4 z3+f&n<#Xaabn~;JeYHH&<^feZe7n`NAnM(pq7ZflZ5??6$9_dfU(Fa5&L!Byzen>n2dW-tGOzV0sR;w3|V z5%%`9Dg0L2ww-7*LE5tZw8EF#YpeGq@t4+Z%Bre79dN#Pi)!-v%mMgnw|1W}w@Y~4 z-XcvubSTc8O!E;}|Hhd!<4*Hz^oRGNe=G*U`C^{g@-xFwoHLz=AaWxgxu+*~;}XM- z&dpJ7Fuz4IT;v-)JgrE*#CJ|dzh#1CX_xTP>y%(T#MmYgPW!pweu|Iec8l+P|DT&> zUc>91U&H51yhYamiAh|+1AL3l?s1Lq8_FAOS-KI!Hu+?oCKD&+>-$rG=5mFUB6Xn@ zo6qE2N66P<+?*5lX85EtEN&I0<=m@;J3BwooG5W!)7Eb1e~2Cw;^Gei z+!9Kt7r2^v!OqE%6v8M4g?!;imD3Hg`Tss|Jwb)#L$DqJrxnSo!hDzcc>!;op5$_m zzvb!1`nYr-FOLsTC%>MC=&K}&vdOi?jLzCyDGoJX8G5s37&&yY`RxNY54am_*D`ha zR^EeUOzZR)Jhp0E!Pv`ht+qUlwnn`PIrOL9o~^DWOs;Zpl{@{$9zfmo?Yk^(j|NOt zv1_gNUu0GQM6j`@u4$WFc$}L~?&j2Dxl~F2=;P9MEFo8vEkAWQJ@;y2ey^Xj-M9%p zdp+>9jnmV{wKh2U!d~WNwW>3;(~V@jGxyc0yZ~-nty6PQ?|0L-ENGo zbzyRL(te*qyc<<_zLGz<((kxIna_#f;{3+hF#~O(G(Yiwb(MJyCcW9!OtD^&x*S=pbN0KFJh;8uw@1cPJ)bG@ z@POLQQ{HCJyVkPnRLorcg~IPv`dkz9viF+s7Bg`E&-sm2qJ80Z;bxLNmmKBD#Lnb( zMsb<(+5AW=vXz_Z%BKg&kVCs!5ns*aLfl;s;N*IyU8cTr3G*r2RlE2_&f~bJr3h3J z^9#A)DE^jKDU|53qvIuIWQoIJSvxB0x*hx9`0`DvXP+usELG_|MPHkmvPK3M zfbU8oDlugO+BV^uI*WiXEp^alP|MlJiqow}@Wl341Lq^?qqZ$IASIw}n!6{B@Ux~3 z&!%_%*s=<&^4N|Rgl(<8gJJQMW@no!a3j+0j1C?rX4N}wjYAHGAkL2!$7xS%14V75 z%`SJndAx5hmEHMVgGhgt;>WMgc&D@-z_qF)s<1})0uon)a9X}wC*lwl5W7Lmo5*E(lVMO&}#C%4lv4E?dD&N0qWjm4fXt##vIKE4=+H7`xgEm3DO(pI`lVgRV+gaB2=xJOgog}Jp?X~IK%Ma$>126|47Tm;v*&YL-I%VBpb>Uq8Se^N6&T-+uxK@E-RVj(9A5 z9^P6&!YZ0nM2I|yT)Zidh+?>*DUgU)0Dct$4WYUon9!1|ZqD8+Bm6V@ie!Pw&i&zh zf@f4|1BUe%JA;B9=0I-tZHTQ(-N>uoFXoU9z=UFt1C~5_n-L5aPc#cWIL1fG(>AC6 zBv}WWSBhGOQX__|0+9>_1f{lVG3inQJ$PXb{~&!2F`5r^p=eNYB_rq)p;}9eAP5&? zxK80?Y?sJ!ZVP?${`|7xN};@nk|KBubMhIaZ~qmG?{moGS>K4dv7i_fbf?}C3OPc( zN9@Gb)@H{Ush#_FThMbI*T2UB83u$6=OO|`oxx1-g9{(j#DpY(%sR#b)9UW(YwZSNJB{!ViBgr%Ff;jiq<#-4$pN zO(ar5J=#rb-DUwT;WUcwOmaufDk`2MpkfB;=u$E4|oq)ht)uNQa zu&VP<)gk-4ot}lC2>RnxAsd1U?und@o!iI zOB^HX$H*lg+f8$Iyaj$#$+gxXpL0|gSm(@lI|GVebzw4|#*rCK0z~u7OqgO7+%>nb zE|^q_BdCohtUc?3m02!$Do-qohd@3+k42BkQX3!lgihRm%6}jK!Xx>7p-Xv6f zk38BDBb=p_IFCy`T=d;)V@nTf^0?x&eR$?A-djp)9I_JYgL-DQxRw zj}AC0(Nv7#Fb`=(+W+@-mn16lE;gP1MYWK8&7G+Q(LkP9Q&^D$C?Zb^-b5bAwH@FS z*$Wrr$}HuwVG8D^{Dnw7sE=K=icK@z4on(VnO~*npEk?}@g|$7{40Wd8mURt2Kw*9 zy9ulv%r6|HLpE4iOSXPd7C-*Db|weuDd*t3{+_a2eb^L9c9XM{+Mi&GFc%{7Sy3x- z;-Ap<^M3Pu_Kolkz(Ks6U#;@Pp_!bo8TRSC6?BPoGgW&_>Z)f;IEEr;u$WZOiCCK}q8mM2QdK=tuCUPwWDzG>J9KXwW8fnKIS5ov_N-?O; z{U{puqukWSc-^7$R*V%OuDCKyo>kxmPJ2$e-Y3N66J@caS#XfAnU+cZ>cE8rm$QAh z5U&=l6Z$Y|;BnDGY&gPkVqg1a7oc6smP5T`dI}P=!L&ubg!tny z6uPDhj5GNPu477>(`~N# zz?)J7qF%m1*I4H)Il0LGajsGlJ0&v(@dawe<4C~4e{((~p>ict7BTlC(?UqC2TGYA z`VZ_H!(k=v3bSkDikMvsA+x{xhj5YxN00l6#0&am$3T%s#`nlKa-{TtZK29$Hg)5y z;p;Z8lI!>7A-miqM)y)2gO0B=F`J6k{t?v&wt#?@YelVzRWZEWcP%B>-7^k8+*zBx5NFY!a1mO z&SQgSn2!JD_i^fzO0^*zVpZqUt`dPKjL^&MZKs#VmW915e^05KCHP$?t}FBF?T2L* zZ&#hk9(|jgdXGChHgAVp?qRhA2VTMxHCM0NQL{+H=O`4U=?+oXnw;iR~BqjO(d1SM@XnN-7M`62eH;IxV(WjowfF{4UEAgL8Q>*(g>jP?C< zv-y_u)MsGEtN$@=?lrb&EL?#zv$1*M`+3xP=h!ventfb%~IEo)C zGXC9qp1%kFi=x2GpnH}cpOs04U3e9~GGNKbm>tP}h6~ z|2$K{U;F*Od*e}5+s$y{S-5gVD#MSdPfG|8p-wf-aeZ>|-%_fXZ7XV0r|rv6U4Vf6wTbd=P~8obKhhPnODHn2N;mRVwTW$B5;h0wKSInrSBr`%j+ z&ksqJ0O6(ggKPRF#&bq#QMO6OmBt8$PSR!}Y-YRl?%#uWjITL<=szj^UEPA4Pm{*1 zuU~VhttCgq)zrl)0o5UUQK+A9*B8gms-OK7ARQ4QihuF?z9ADAC8Yk#>h!V!rv~Bp z-_(x(#Rwe#P3^$rB4py=`X8z%gE$u>;m`OF?enK*BINq1_Y+zvihF$S=JDn4W7`tL^!L{L z*>|>}BdT*I7qq~?_q;Rxr#hsxB*j7P6BPPBv2D3X(6xuTlBoK;?1bEn82#_@di7|> zwrBY~$BUSk{?-*UR2l|Xwev-3E8QSr>Kaqa6SjQ;zg+V9>U)7>z!%58Z&1x(1mGgD zhMCohVziqvkim);em&(h$d>k9G5QpI0k^j#04 z-22|GUvWhfx^Lu^EnW4t129?BjsD#a98tQzSCoz03{uOmWmQrh`asONg#?JG1g<{F zVA~J(P<9>qYtf=h?+N9T%c(#}gMWGV?u+k3qBuRrq3Qv$luPGvNXJ^|^q9|`>8VXV zx+ZH?0OaC$J27pWzBy3Qg287V*_5$^nprdUVQ4TWHcj$5`*m#2 zme|MOz!jK2Fzd-fhJ{g!{$ffQ(A z(WwNNp^@++MQOs?2Bt5Xt2$8yr#dXW5g7B@<5yLGLL`;iJtT2M29O2QrI9yaaHiln z9d}OY%~`EERzIjzelY zmHx;D!3gSOLx4iW0?@?<7}4L{tb!pZJ6U5445Qf!!>m;br1^k61=wJ~B#TTcc8X*7 zLsw8$tgtwXZ)pntRT=ISm39a!4jU?5dZ_s8j8}$Vfh-;O*xRe^51yI z@%_IrcL@)ngZ!CbHjKywO=kBg#yIONyM>R9pDK5PlkA`n2Rez~dj~D*CI{i{{%(4c zfRESmE_}zTUxwcJhLz^d2X+=z;FvjNuxLlp!r^WjjCH%D#M$X3@u5u*TuR@tD9auj z1RTY{jshNp=b??k`^=in$J~bQu-G;yT-Q6)92IG(WPW$}C3DCgUhUqWctdwPXbesI zq+(nY>|Xx!Wk9N26(D(Uq*vxZqxzbxePb22s(9Die5nS#ZR+m6O1yx8f&TvE&xX-J zOg_v9NvqXDgbvvQSFCr%@Yzwk{|9BaTe{;ecnwe~H#JVzGO>dg30=|pl&MvMDAVm| z$VX8YTd-$|6}}G6c3l3Ylg8D854eaRfpL%zvc1jnhLWZdM?DzIl#FO(k@$1=Qt>3Q zxg5eokNfd{rR<)EpAdjp`PwEvxN`h_iuf8nS!b^_DNcut^Tmt}DT9 zYQ0aME}>ba9>tlRo8i9@9=X!f0TX238*gkygoQF7N_|@?$zurg^zY(ml%Qe0&N^8$N?IW?wMX^jv z5S(h{GN@WOI;K+sb60kXCCl%2R%#kORh1E1F5!v*)6!0Y2+-gzk%QY?+gWplq!zdI zh}?1>Qm{Q*6*B7it+8oT9#Sl_It%DP`ceE7@ujr8s$@x%xu|?gTP`}sIF-EMu+b*7 z!em+4q4GS}-~)H!^LZAN+OdI3Uom4oj*Hxe^~9P57iI_cK9+E94FJydAR7~aF@j6nJ2%+5x88BFG%cS3N?vv{08PPvl~+x8Oal!i~Yozj1pvoQ89y(*aD)$1Qhk ztp=q!P)-ewO?^@=vZGhGC_SU&VM_(5m-DOzQJnLOBGgn@UcvGm;h$ps|57zgtKSQ65XxTbpkpkE$iufz~7lYI5Z-gXhvLT zA{q&cwTN`TQ1W?AlBfMrPEg3{a42Ltp^Yq|4^5`G&-<+fNs)XLD%?J#v&ioB&;f%Nq#vsDxU) zr=nFDelOL^d5ARp9v3}nHZQ9vKefEC5rvT{v=U~Vg?z*$v9Jx5be>jkmd6+>gr4LppBwqY%(MN5a1VQYi872IzC@!k3X5!dfiF!&eV5?s_sF7=R%}7;oLcR7GCxef9A%H&Z&0o}$ZTHXbF4uD)?s-bQ>2Pqt`|nNk(1Vx zb6>FmrJtV7uS>TBk9Zi)mvp8aZog;pYz+Q&(Vw4i z-1PR4zrqyDj$@&b2;z?)h0f+V#2?@<%-0rMs&*OL9JtS{D)Vyu7KC8ew&=!KEN|pz zXGPGjy`RVQvm!8%GMIW_Zoshj5@LrjVfA`!$ZPMgpJvkb+V!AL(S#iE`Lr2oENH-P!l;}n?Mg%elE=9lU|Rc?G_v;%z<7&^q9e6H)FUl(HZf$i zE?jmTJ*saob=P50SN)=Be-HQHHct6i0KG#o*P&UxUv1AyBdyV%IO1pFetXejWDm#4Lq`bic;LFl8 z5acf(zrM2_ag2uD=twQuewUCr3xYx8D~Gzht9}@^#o@x3R5PJ9kj{V|+smC0AH4?< ztQ6+9Jk&m#Iv`l)^?=I5p@|eGVnuk`JA9^0u4v)Rq>{C$D_Lr@o7^(rEo=~|Z+Hxm zy0lf3N`tgv%x^R=Yz|x-rSTX8u6}|ooAeQ=p}rP0y|uG!+Dvq8))#TQ`*_F&Y{0kc zcQc!1*l(VM8KTprXzEUs3Ll3|$<;-EuT7SEBqTeMS%PA(6&U)RZj(X{i9sDKvVO^y zxzZJSB1KV#Mj^^r>!w2p{{_W|vo^}8$D~duDCNdDV9iM+ssu6WXOY(dkircygdbQ^ zCe?e8|N8oMR{Fr;coUNz6*deMD1(#Mbn$UxQbVZ-BDxElK$h+grFRxCVz(NKy~?D* zisEms75-v+Pq8 z91<_Ci!u?TRaVOen$K8(PZgm(;~yz&E3S9o$0wSUo|%AKvhnK+AYv>$K7os}xT(%W zBrmQ%u55hl$@LJ7r%BM$r3dB7T+_GAt?6Zw&*{xkH%=1V%scK3*eQD(clS8~&2;*5 z(w?e14b8W+ID{z6weV=U!`op{R}sM0W0P~djF7mG^m%W4cJ~i^y1U;yS@Sp&VCWr% zg#hXs_5Pyx_pVnBz%u3H_h%tyc?TZj^|L}Fw*c17YmPstVgWyy902tY#*uL(ODBQb zB{*ZOZ>(wCR3J-d0r^C_s>xiZ0=K(d+PuxT?LE!H z#n#+NY7nY6facfTjM_HTnwCSphKR(eGK#hP;u=ETwv^fTCf-8^tDJ6E(fI1Qxg^X( zSh)9YQuKQZPhH$a9C=AR`J%PEY~L~J44zi0%Wo-|Wf@-yETQhQ-ldoSd0EOO)Hpt1 z@!aDlvS$XSnDb}z)QsMBiS$W)icopnq-{sy#{=e?jcYrKK1<^NW+D^sWoD}8W?m3= zFwZz${^BFOkP7I*-Q0A)j3w08&iH~LA5E#q$evZ_{K&t^PH5g)LfqpHKZlm#rwADt z-!#WnvmnvNVbY=DBa=G|$DdbOs&PyN`9tqqZ-cJVtX zejM;s?Xmf^EUPSt1Np$0&E96?Z-E703gI-2=E04Q;6jTYQL8OJjz%K-`>9yy{cR#Sq14QTSyxke zejAICEhjh7w6>;YgKcXGY>{2v7pI+t&nmQ>4p5&Fz`ehojWm81u|bB5;3YV>hIR|p^& z5_e&E#O(_4oa3B4dLaulLem%L&s&VV#upv*$;6V3y$<*7(45*HyszuvM*+>ikIW4? z{}ChNc01v$TNIlgplw=6SeP`q5{9-nUI=~qf^tES&`@Vyro3%5C~ll>n%F)@bw6g1 zfaiIp@S=*ehsEBqyVYWLFFT%{3Vh=yS&ghv|of;I6C9#_9~Kz7wqoTFYN131U%f_HCIxP_3Z=dcgC`L zV$G~<0~aG)SXp$hd{~vQI((AWAiCT__mb-xh(gU-kfuz71eumDm_;8InE|4IY{-o+ zhBMaw%onV(uh9ro=F*EW0ya0Tv7|kPv`>N}!Vl7-5vK?$MQG6%29FHhn>ND_v80Ec zZpiv)H|v#hpzHSJQ#+^#Rz;5S5cOl9hyvH%m3=yNVOcc!2|1PhE$Eq2EL#X0bfTzV zp&NWS9dCQU|C|{|XUK)0GyyU)vVKk(EPF^Z1Z>mDKk!;At6lAv`RG+K{LjiN!=e4p zHfxMI;YlDAFP$X|Qiu+(dPJGfm0ieV8Z5-RQ+KnOD(6zg9K6h*bBf&A>I?#e2nZoH zBbYI%qVq_~p$cUKX*vgMMMtX=pyPkb<#JxNign9=Md2Zd;-)AwfEcT?-WB8jbZDBN z4jnJ8WBX$r24Im*D$YE>1>c+n3d@}n)f+YDy$4awn=6hpOw>20Kv;JL%*FGrQBI)b z?2Iv9h@^}?7kwum;!1XPyrjeLH70hyXhO)mZr>ECmH2_f38YhOqvLt^L5r()bnKjX z{KYuLS1dLBn)30+74# zA9R&KpV7-Qh7{;}I!}!4$#d3lH@w})`8NOyqvZ1B#}Nl$70uD-A+71WW7=~nk0*Lw z87uf*s%Q$gcG@$LXjD&ZD;?=+R+o$l>Y}McD;Sk%6aSzd{t28Lx7j%HYvX(Wb~nQ7!t zZygD4p{NX3Ps26>vbl;1dR|zn(NAv`3GAh)Olwdus6FE4e{bWetVEmN;G|6^2~bpm zs~3}M@LwhynKX{EDmld=qYY-g#HO-q-qcq?tY^H5wv%z9T_Lmaxp3os4km=PNWdPJf3uB0|H2zoE>P?SSs z5*!frI;dhc@~8S)M2XRgG*%L`8@r-4JlxZUyyH2eHDqRXM6S!eRs%bK*ILd8U50!5 zeCu9@FSwuUtORV|CIrFKxSt;t5$rAj5`M%CA9^GglJ3<&Z7duy{6`XJn+qL5pcISe zO~E7?DKr`0tl%UoKo)vSt{^{NpxIMMl@gaoaL2TOQ*0L+SN3xqRi!<(**AEV8%!(# zl5V{WgjRX-TiKxDIiHhen1^d={RWP?e@*LKG3>9J*Ec`}M*0wKgm={%3?IJ$T&Z~@ z3#;XR=n_1&oS9+IM!8FnrBMk>{v=~Z_^QX&!H~^##onDxu}Qi9mc0oQugB)87yYCX z2={0h@orDzvi<%;0<608u~!9Qi68QV{X=%23dp{rF2$oS{l)hgWsNBXzdu7r@K^ow zwyNM>_{E;oXBzvKJ+&#MYE1CVkTVuRs49Q(Po&Fu9)8VA2@ zi_kmzwNWDaDw5|Ya_MthY)22NX`1m&dy1R43d$0IMuk==dzMSeF>bQvd(mseo>)L1 zIBUDyypc5&Pl3^Es;Q+;9XHAGDyK=n4b_@m4OF>QUQWF8#(UliO|QCtA8wU|_m;NS zVs1jxDVzgw_1tV$cuM=}&MfI`KlYv;X0J}@%`}~MSmbJZe~gK^;!6B&NFlo_c(x0B zxj=wlc*n9%=c63BmcuWG=F(`rI~zQ$28IDHafS8`fD zI2d|bZYVfM+FZ%c_7w*+D`(qxD7YgqSk%*>@-*jg@I+9#-xY085kFQWAmT!4u94uz zz>RGik>IExfL{TGEER~|^y$R63z$2SXegByOm`RZ$ic6oUZd3$>1nm`LCM0BHF$xv zs&~n(sKBs`8*mFHd5c>UPXD|ZXOl34if=7GuW2m5NdxYQ^-DT888(-WAfh-gZ zOvzU~ghbrSQAtz%J*iT#(mC|xl`iI^pwpQar{pvH0ohH$znmbkh36ZW5bH~bW7KmR z7vx<>@7*vrH4a-97Cy+Q(|U2xQa$cdBE=aB-Xrkz(XHZ&z^c1~92Lk6nS^t429>7}bPW53&1)l1T&A#8Z27t8Zmx+;OC$jt*}&JGA_B_K0qRz3(?| z6Uu4gA>f(z@ghYX*2LSW^)k50iXD8)W2UW9`roIVp?(G^99Qxi1Tt~uiF@x-^tgKp zfVg+yse{SLA%YM;m*MSnX;3?-3|b+JnhG~vAPcp!QsC`?*2YJmA})xev7n^FQT#@fl%T2S%Gl_-72#|M9%zHGgn22bL5xC_~J(|MarsF#~Z>OS0It#>FwZsWey&GWqebgrOw;bNW->ByF*fz)kew92o*s8 zC!QYW?=|%fQHs>$);W1i=02nMHeKxA3&iW-!&N|miVM5J>q@W6aEMU^fKTSyzQ!s@ zd&7pUC5Fp@9h&gBl*4$KPU8NkLk4`;+#VokTp*23*RPNeXz8Afrsk7@=`LRG_+h=Q zsqi|`YRl`~jpFmPYPB_%C;Fm3C03_EZkw4x`92XO>QQSa>#CPp*+o-=V~!C6Int@n zGAot(Ri^zq3%Fe!evNwt@YipseM+-D%Nd%dXavU#|M{Gu*O^z>-n??P!4SgFp{~G6 z#A$>pZF?@|Sh|C~kyJf9OMQ7YyxFJ7ds7%5(@N2Qg)C>KA(;oLWPq3sDxk0zT%%vD zG&m%g!C|)Tgp{!zLhG~jzIn3U8vDPE1}*i8CW?4rd=$01pN8 zV?y$Q6ZI4Tfm446sGpxNCrd^r7z?1mL8$}%Zq=G4q@u{o&Ju-Y@j~Sj;)-CRj8Guu zfe=FuEzA?z`4xyG8yMd({@SRH2exgQ*$b=e1I$N{YVKXfhi#^EQ;C%r zQ;qm1gS5lb-WceQ>X|D?O3^lKa)><^wzIoq$cL#|E5 z%I~7#H4_stAv!v*>dTisHgdSfwh*&hhG_}T55x2TlCxo_G$cY8sE?dqv#Tf8ePke% z=htrX^B#$-L;uiW4X)(Ge^srFkdqi_M7?(L_}6e?0qfc@6I-mC1Y2v_cY5MM2dmB6 z>Dht|xauaX+F82%$QR5&{8z>7$Wmwv=|X8_`Y#(j&33yy19V5gs_BQTSOZqbK55$8*BJy98h^q#22p10{XZ{cZeDvEWR3U%TKe1Xd(M)4&zw7bDXJ4g2 zCr%*VZdi2|{r4n?O;O&c5n4flc6NtT0O<@mnE^ME)&tDrj{_5td%`0ZL3``Z-I}D; zg&IyBVUNd+#0Sv>P7#GyxdmrM52NN!0{3*JuR&*>O4|srS3ghkZ`b>DJLqcOLN)+2 zNp;i{$zCsb&rl)$!TxLGpXdDU;GRz(^~AOC`5PhtjKxxzX5k<3yM2=|Jp9qf5?4OU zjq0ta|LqK4Efsxr0UP&2+fb?=nzd%Z#T5>@&VZMiG^M~&0t!-L(dzf73k3|O9CjtB zJbMw581O3V!lH@zt5&ySX+gT67H=e=95jG%<7NIzh>Ey>sfO>bIOE`vg%1KsxL2>i zhXj%&3qHt10BxY<>KJe?9xPa>lrhXj`b_?CM_-eH5+qvg$eY`FCnL8!>KzX5pHu>f zgrk)(2}U)jCK$p)tGr!E74pR%R&W~8KD@5b^m%ALZ@2>3f9W%$s{n7U94jdKyN$7RuZWnM>QlTKctfq2x-*s7&(76C5}XXG`_=@Z&GogOky=150Dme(wo)TS&S-cgsd++eYuQ z0hOk?hQH41hN*cFBU9``V5(q-{ALq(7OA*z?@sC$_e}WqL_XIe5>B9im9-d_zt#5q zO+}12x{_|J2V2*f&YqR8(;vLH-s0Z5dG3pGr{)ZPqeo`}e2Q3Gp=nvWMIC&PtULhe zR(Jw;s8sgd!x8+sO?I3k6iicOqcAf+VJ;R%KUvE=_7bYrHa_Lex}?B24IN*JC>lR( z8`bBsra^>{>75m}5)mc9tqq?39}B*1(DFEBu4yKY!N8W7{qxn)|LCtD%cA+E|(;tv2AM`dmRQ?^4`9g%7_c5lXI0$V*H-#XaWfGIqm=Cn=npOVFIPWXC|KzWW(}KA5px8fk4`Ns$#$-3$5W^U`}pssKO z`nSCE-=Ct(mj=}~o2aNG`fzZhLA4!Mu-%$3SZ^)Rt+Dl%R=3((SW?|&pX{RE z7&TN^*fL>C-eB*5S8YYLnZK*8Yk&uS4?O=w)~1~gaj~kdhv!^MBF^sZNACd1%rw#0 z*Jj*VTVuB=ud}V;VQVgPS!u!J1L!*17Im`(Ozu;`dX)`ok%|8G==!mt)tue zef;D0SlO1f2LFE;d#C73qPA-{dSctQI<{@ww(TdjopjQ%ZM(yc*|BXqz4LzWckmzV zf9!o!993)7!5XXPeP445&>B>q3GRz2uIEZMg!@=T?gDq?d?ne}er-(=3Nz7>>V~ubWw}nYEd^aIqp>MXtOADPL!`dx%Z>>5a)s zUT4j}&=@I-%{-4llKGaNNa~DDr3`WMefXC(e8sCzb*CKh){&{HoF(uKPVDBMLtj6= zMn(`&&nzkP)x^^o{(5XFGC>ZLFmnVxW>&Z`oH45|hrX?)%&HWanJ`GmXjNj9(`z!& zpq!(0f&8KC3@wco9=!X>7c9bv-CUR|OHqyvkE*qWfSNsH*2tAa+$SQZMT_0c(qmWs zoE9uxWh1XCqB=lqT=E3>7bW5-{jA>Y)5mF9;gG;eWDDB)QQlg_NxwIF>uneqrF__9 zD=Ew)NelGiUx+Shc8*v367oG7x2#s1#JF0R+u)Lg`ZZ}VVTA3w`*4Tjc;vVS`HO zkK#=U*94BVXxiqBOw^yz#CXX2QONN3Mmn5B(c^^;u7?pZfP{6vv;NYOS}ERvzXf@H9CjdH*u| z8LE!19|GTM3^o6H%)SC882bKHdvB(ehf1mLZU2V((hK^>u#UtLFk)I{-sJJbG`Onu z_RhBkJb&?V#^0IT_C8MH*nIrEE(oY!72Gx_-Mx7IdiQTn3`Fpsj)>-*4D|eJMKuHi)C97mplFa> zvnvrKh6;47Px+c_D2rt#<3yLloR3r3*94zYTRKQAc(^(kaOsFHfamfn;0BA|Spt>` ztbgke3;1s)m8#{BDW5n?;uav{WE9I7*CYU34_T49u6s;))EB_i1xoFxlvpy|%H{-) zcrPK)ppM3R)CPb%>E^QW7_D2{Pv1PCop53_M6o?`r;!&R@wtinP*>OZeuc6y|CZC# z8Vd!WA(H;B1O%nug%^i*qXZ{3kdY;!=-Ga(KElOPBiiJHSf#2&#A}4eShGCDlL@V@ zNmyABl)JM$h=3iXLD!;D*5{|;3?rsW&{^{WDC`Qxe1MV~9BZr7*jtGHcno`?me2m+ zLy><~K_{*C%gZ0YrWA8t=T#D!4v0aGSdrA#gzQn=Fld;}8hm%b;j9IX>L~O7s&7@X z7%LO_hMhMe*>S)UJ4S5T*>ss0D7b%Win_%mn?wv7(GB-#Yfk$>NwF}MatXPoku&7B zg=l01s{uvfL++>Ut~Z-)Dm{-Y=bWL(XneeX1KF$&1a;EA_^a_E7&28n!HZSO%H$|< z*SkHkTsOkqoO0l&WvzaorfZdK5;knBk}oxi`n_TrO!4`k)}0L+86Jz-cI#aYP>SQh z_F*O=JQXJU*MhxJe7QAVm%Y7_lw0*UTRO(aAp`fjIc+-P^^vuX^Ng%b?(XbX+|R=D zY#rB@^Sn{IZvBT|E7D8H?WEa}6GgQQgYhV=;2-AMNc-3XLNEvGIL8xMoy;zp{mHxK zTmDFQ4lBeVz~lPU)@b~lO*HyFj<7q zUJo>B6)7M!_`qniCmC9$_74&Ni%g}R6#=^Wv2tQnZJ?D|(oz=N2-ulnXjGG4`P1(= z{(OJxkhx{M(FtF5l$m^#oq~Z%Mz~^(?U4?(TH@WOxnCabTkh=T%n^A7>Ay#=DgF3| zLvCfd^6*l&S8%B&6D-|K^R;r0YfZ_D8362Bla{4>zSH+}ulW(d99es7ZJCWyMDxf5 zS0A>qd`@zpQ1_up!f9~5g3~*F*q`$-RE=a8k7MHpqV__vCqW=I>j2FaSVQKJZ~8me z2090i)WPUH67Pg%kJ2u(-B~^eH{O*N6dM_qCrm8!=$yZOITF|z5JYkU2M!Rj^#W>H zmsC`SC54d*v2W#QLq(4*x{H8?Lu<@5La2#uYM%&MBck;9plU>uRN}^+sK5c`4<_2bdA{By-j84g4{W#X64<*t>$x(0wvuT@1B2EGIVx2onD#*2A~AV zn}|im0r_R5U+ub$-<^z-#aO;iI)e&MdnaR`?p;SQ$=_n}T%Sml#3)!`gS|lL+`!{- z>YVhDoUCZy5E8xr4+_h;J?X!?d0!QP4)p)9=mulv%m9Ue5c+@7>37Eg!Wjh+5M&wS z#otheT+KJs;WPtApTS=ONY2o${boWRO8~=QtC1Pqr2uPC@HWaa01yIV^|xNde_sYY z{_PYl{{ah)!NUDte&qkO3g^sF!iCrPUmo>aWY1{W1{3=KE#QBTPUOFcDof4x=nN+S z@EHm-V5BKRJ!%=#HGmi}R;CPNdPvH)pxSTg9=P$Qz|zi|`Dn=$Kd|Vn*|xHI{3C-X z^IyaJhagtH;&6_I!eC9_zm3m)K?Ju|Jc@30f%(HBA-IL zHGm@Dg&{B0`$1>{oVCIvl(7b^2QLMiba7f$P7y1%-rOFm(E-2TkgSVR84=`io$Jz$;%9n%j5hi= z!wQv>g`83NcV;R&tPJ)lNnW(PGbSY1M8MG>17|HvF?$nlShP*h(~hQ@(&5!&Wgv$z)W+p_v|WBvQtbdux`7nxq>e;9;M4WQVVXng{N%LWhk6e zcy@nSr|u9nPRA@;y6!41WEq--&00rrc2u#L~LwOcblC9pif6ahVo z64Oh*Md&QsUz^1Y%^leimQ1kfKGVheZKy6BC+1YQ%~A%f4wa4*IBNcX=pQEZ%@aiA zg_1%tK!ArIdtNTbMcP{X44UVIq*?_dqE3zMav&%Us{E^{py>TKWoWKU_sXyINpY&s zv(I!={Utpr!qbn%j%yf;4izqJF+fId_)_~)J?Vg5=sJOl!3=M)t6F& zcI^Zo_0!6$1E~w&op31~PNiwi((*Gr2$=NB?er!;P&bEArH-H<_-6V0fxz>21%U_S z6N91bP~TdWABw9{T>BuK*cBP61q7L0?>Q637UOg`kdy^33ol z1NX=a2WsKO$L(S`=H+<{_k8r2p|ZLB<@9Kot{e+Rb_Kp^olxs@ll!>zh$Ic!+#sg- zl7meinVNy6s;u`pr8)a5alp-CbJ zc-G1@J8oZS)|u4hx$6;H^YYT!6RWYCFkxfG?3l2xe;jh!zp*iPdDQke*Ca?B_<|!| z$i7V>Q^QNc-Z!}Ff00Zc<>Yk*I=U<(xooklDhk>5y4(pvV4 z;!YkXo`T?hgm>V4Jl(uMg2lJ4Y(@CNs%!;`G|`2BNRfC(<`Xoqv364G@}rL z+we$}3o12Z@FU*k1NNwWXYJmst5>2^cZ#gu;Kh|PLFaet18Gx0Wz01iwkJSXq0%Z4 zt2HnQB-OG52z|K43u7y@(9N|KI_Mv493&pqT)8S6em&P^^dWFdB|>V!QMZlah0~kK z|Ea)R5bh?t>n@E}pf1krWQJxEliOyxMINeW+PQZhA+7fzVYHHMaZ-zUvgAX-f>;ts z(o@E2b|8$<2KLP&4v&;4p#v6rEG1Hmw=I1MM=fa#0LHwVgTks!+fhqQ73+ zK_I*Wg?P!%kdw=tGlO{;_aPzS2!<45F3^cdpcI7QMXDhJy~kq8Nv4upgmz<#35ZgA zWGPwa7@HcIXL0qGO*s;7g@ZdxCUvt7TbmKa*hZz%f!o%ZVErxAveQ^8oz9@|_x=f+ zL5m8FH)CVd7~r9-Ip@}DV5-kDPEF>xq5kFt*1JafW?d;3nnm!qxybCeHXg&H3UZw` zYw^8l2+Z2&k-htl!=!H-8u3ZGdp6RZPKuE?QQUeQpiokz)i6XQj21?7V176Su8B@D$wyKWN4_E8k6+?8NOWK{7mhXK z1ttXUhX;5-WFJcR7)mlIAcM#T!d_@#8~Va*u1;et00KV?yckTwjqkRSeZv}AHQSA9 z4R;>I113%KTGSTLsZ!SD&Z|tE&(@nnp%|wKsLkM23?_kD5}nAQP65&+viWx!m{X14RcDY<3?RR@quGa7m?DXo%rh=0zyxq)iP? z30#3u9fs{X>0V?v956%^J6O*I1=|#LNqZmtsTrQhT_MMDrPnn1s&0dhfc5|8Q;i$930k?avpr1HuJr`b zNqUo*$HLYm`5Ev_3^!#Uly*Fg+)*1^h)cBZFIS#)X!j&7H)nc}3CC%-+UM`|P{dm~ zi-`PBwUa=!W>eyw5Y~sNOgi+>9RA32e&wPbH8izT`2;3a1qFTRVc4+5f>IYVAYWa5 z-F95Gbcr=(6uvYrmyjskq{c;w$gTyhdBeL;rn~ID8~H)czpK1y-mwktmP=)rAv=W{ zdWxp5O0qjKV2^SrJ_IiK8Ckj^OTC&04jy5mct?LJ;NUSmhg zXev%cvEVPzEFMxvzF3ykIDCQ0_vDU@403YQxFwu(&hYi0ha}}##d~z?z|OW2>)g$R zcwYE_pMAi$!w8~Ypx5W|zt7Xld&94Pd7D7L=dP+EjH7E_9|OdNyhX{mk?HD!ml;CF>b74OgG zPec*w728h_=9se%Zz^+73s*mW2`L8lZKXj8iGV%a%HOZ@TYzj^p!3J%VL11}+-qBK z#qz}nOoxjJnJHSIGB|<ITeJbepiW_dy0p?qG$(eplyB;Uc0W!AU; z^lowFeayI;^vE{%I&DN2aoN3WwB5KcFQ`f10-Qf@l_SO3og}#|+>dPn+jX`+ba;x6sp+ z>kH-^vc7EMgByUiyA_v=!s+{5h0%gKU1D9++VI)w>V7{?5$0XdylJq|yN(4P;c=E_ z1#`-AWyV3;Wq#v4D|?Z8ATfOaSv5g-j2mP+-Jl~=T61Lv{B&=yRq@!Shh3c)GEH|k zH5N=6oAL;AWJACu8{#!Lv-IA3ba?6oec$4`&uPBubKY_^jn#bcLms?>+xUgRTV+Wx zFt#cV{iOwa{lWQ%qgR@sv_|y~dwu0tqe2~U8jg4w#t}Z?Z+Iq=lKDfrq}?IU-3=|9 zu4qF;=i#jaD5Z@AV7xLz`H(->b-XzpI-_JpR2|=Q&y0I`-D+d31LE9TSSV6ztz#ZO zcl>B#Rgm$#P~WU!Hr&;6%y_YvYtH*<8O{n!$jpheCNrT}?#Aw2ibz^fyd9zO)e!4= z#P1`B@BWGEs3*f4py~Ai&KfWNp8I?3B5FC#9d>jz7v?Z`qa2`@XzlGZng+)5Mj?= zw>$Q`fA{G!V6b_?wq!Q@*UI*lIQHFh?s3P-fmYV5hM3r#Vw#2&j*-4P2E#F!JQDk_ z$8W5)cd>_(b)AFGF}&}Jia-Uf@Q@69s`*U@NQJ)P8@BNNaq5N(?b7Pfb-)#8 zb}UojZMjxfd{tCmSUjMq1ihfQ7Gco|Y>t=?5z(30nAw7SIJieBrD5ZT%03BRP&D8d zFo`2)7?d1k!d!GniB?NuP00bL;?@l*|4!)#Ua2e(1o`aEA!M;}mI<0Qi&}4Z->y-T z8`W})L^j-L88J-2ni>xG$B3$eR^=~zYA+)=MfP-X0C3b*$)(NfO1k>59#i0=SF?-- zhN4`QWc>k2Sc-c1kWTc#J`Me?Rt7yKuz2`wK|5P2Can^#Bf?ZaoXos2JP{L)!crW! z49+})%mns-7JcZyT<6vGtK4+t2SWFTky*Ng+=t>CbT|@cjgM~OFsx7OakDDT!7)Z) zZC9z4N68TA6g3+)_pRB(($#osN@t6c3(nk297}>%(K!m#GC_*wep{s38$SbYUM66Z zP!2^r!+IdoyC$laoUG*8K~t$M6tih;xLQiHl|tmIvNFO973RuIY$%Z?s;T}}q%|rh z>J+ZsfVP&W5*U7)_(g{Cb%vo~p}Wt=mR341j1DP5O(Y(VBpz>5R|d2ZcNOX+f)|ep zk4N*pMl42<1g`^MJ0c(9q%#omYgA+wEF01j z%pN~rkn(0$HGfz4m?V;y9u_P`o)}}JG|cIoiz8{LC~)Lm$?Ok_$3tg+w?Sy2q%L2N zYjH0q21$Mdh=58^cmzs+r;Q)%EHN~3H|;BEP zDbQK|&u@o!pNfs|uSBN!;OvVi@Ps*}nMSSg2KYfCi7a(6%eBDYWTsfPzX#pnGz)#T z+LHyEw5&|W=n^iwt+P4BTS+?N5gV=f*c{oQqSFz*DMr3H7!n&x>#AcTQr)M1vak~M zx`iog+8|EQF{{z*D<8PhuZ|?y$MYSZ_Ipi&cHBNC&A%0lHVxvv;`o4bCTSxMtFy<-d~ zg9!$s_x8dg%9D1sfomnF;{hp0hn^mv*k^}jLcQ#+<5Gb5Qn+bzC^<*j2lDgwC&!5| z7UDe3m?s==W)!2M>!1whWG4yB9+#pXq!Os(&MG_?uYm+Pl?xc>6cnAo6pFb6bYpIJ zCNmoKtdx*lb$uV0*Ue)B=#ZyIlBT5DA$Z|&?=sJ0xf(65-jX~-Yc$Y4+H$48QL%_a6}zxdJPHl5I~oUqbEiPnM^r%+oSVONJiYXEGb zDsVW9a3IXhDL)sf1G6Z%;w5qQ9SIh2vKh%y{R5cw3KCVrD|I#t(<~7L*Oc|QRQ>i5 zi!4@cIB9q+srWr<@@IgxYVt(>j+9Vw$N&mO5-Cq}nk;S9kq_W2$Ht9qOnVNDU;PBf zH@_`y>Wj=5rx}W;zNNKskaTx2X=>tGN47RL_2@E}1&Yq)JyU3Jtn|^bE~FYnMuD>8 zGzr)U-p%|52x_hI@WWyxO?G7#BV%%jLKw=%rA>botoP89mPy&QGQX5E-RPL z^Ai&Apc|rF-xg)-efIV$71(zeKBB-W);WfRE7ZC%3k7#`ora_}UNlRy2+n)^P�N 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 z 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

8kC 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

dVIC^A2qj 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

QOzPW>r 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≠*9LV7}}? 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

eudCRh3r1ivKY5`+z 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 +D4HtgJ$KJft zx5Yr8m1tA>EN`Sor?nQ7=Brd2t*cDQ{BaVO3jH_z#~+P3M%88mdZ*3ZIw!q8L*Z-s z&FN2(a(XAds6RjAOovR(t|OaIug1HdJMpfZ9k_>i3WjaoqP1u5*qxbN&$^Q4J9+eG zl1$HUKDj+CXS~jjkI&)Vf110kOxvhg-1_L}?s~oN&Xe@6KI(ZlvQK)mOFSnp zy6-C;93)xXL@zLcMvTx2r#v{c(QZxRPD;1&#+U);t{4JlDOgSYJKy5~9B8g?MMSPNCU&UUOwVvYmQsr)TdI5{-ua;V-AdS{iHI%??b`V20s9Ki_NiZ4|u2RH6 zJ0k{Pjte?nJy%WCM|s~wgy~gV)~arcmiFgD(&;aMx3!g>#BA=m7%^8uhDmdptN`DS zp_-PgZr1EC_rxb$X@S1Gb=CTnxqgGGasv~=v)IFH_?z@k&+?ueZA#4M1_u*^vL{M3ITb`Bf<&)d8S*rUez^*u)WwSK zrYNhcB8uFpbFyK-{FxY~IAOJBxN4qVuHe~9-Sy9?Q*=IK} z#>p3ojO~(?T!n6orj;#V4tzpTn04H4z{)846B{_H*%n6RyHv$#-p zQ`JE-M=}Dwg^!gqbzc`MmqmD}yb%C1$CgbQWvS9gUX&qZS2P^JEp-%>WZ_~^Oz${q zIR*)0j;ekkc62k<2{a|H{n+m`-T$26dmENx*>LY7vzxv<=5%PTg5=Zdr|9dddyBa| z{Ce&kT;pq^-5%n-;@-MN{W?zFZO6}OyE@OWO1U|a**jeC3jp(Ap6-~6>vnGK^TQi_ z{agFO9YjjcKXkVZx?sIgUjaI6nmPNf2O-0S?c7=F5w=*`0Yr!-6@NbD%6wR!Z!UeU z26)n&#*p5K$FZjY#l7#E1{Jr~_XZiKjWH(U%9EG$N>T-AAms_vS)h=!kfaTu(78aR zD}LzJRjM~3$m&ev@EGmMLeJ3ObY)s1lEN-i#Smr+Ss-dTrB%=efJY~7Lq>L~as-FY zxFW}RH-*nK0C9&Y?D8gmzx8T?7L~H_tG4#E-5>{^ni4=z&WUST66~7426|y_@MRQS z8iV9L(4E}`HrUVnbv{=K>9SyQzkUrMD-o}mTMD{02$3@P>oJILACTkM_*`4t!P+}c zYhwjQyMNHV63(7&3X3;RgA0amRvAHUG=_z-%?wLH0^h=7ksqIv4j&`on5vea2Jqps zY6lMa<)|%tc3*p7Y~HFEf8=;RVKDG3ph;31Jtp3W*dM1guM8r7=H% z!I@6j*bhyX$Rx2PER?{B2OpafU-(LbQ@h_dcTz~0Md%JKS zeSjmMyWrd60Q5gj+94n0rr^ZG64dv3S$1&XF?F{RMjiYj2Tyr17E>KucD5O+pC$lr zOWHiMEgmLE3Wp-d>TUX6?5kvpn#-;kgO0P z)Rzd_A1|Ez7 zme?`cm$H735I6TpVDg%g87@H_zG6iB$|O=m7JwsF6zC-N68KL_F^o`m>aP;qUE(WT zMv7(ur>`4-$zWLV$Bq=%bgZ3ZG3lzYnzMJhjv5!cd%t*tvv)Z^^!D%x4nx5f@NS2i zEGvfm+AeM?)qUev@Tx`Q2q-c9{yL#zAo+ywCB|w}*Hs^cYv^Y09U?((g|uf+w~p0Y zL}6D%1!IWQ!_F%A52eZ8g)`ir6{Z`M?NYb?Km81`Fsum-a#?oZ`J+1OTJ;z z##GblH{KU~<-|5yqgLLX>*K}FH@@mXD_78M#2WE0>rTIGV2vB#$Zd@R_)M+hFwv#L z<o{)(v9xs)da`OI!{+-pq4ZRI&X2J~%2CCUXr zVk43xA=X`HAaW5z`dFo#2NQU7_z=MH{rmpH~y%S3SadtOaY z^nWPI<^(jEpxUV4vf{fq;(IlX(Y;;X)S(G@s$rjf7+72~*Z4o~OQ@oHX;aAxC&3gp zu|l{zL-h!7gP{y0Wl|WJ!$ptGl*^kj*tiym+q;()%mDunE`GjQxK5w7_@Ujp1#K3j@Mz* zgXYmgEUnQ%db+7xxXwl>t3gd&AXZg{m-}fUX*f-Powce0(#9xkK3PKEMHQT&U(Kh0 z7cq5ex?*ur1dH^W6sFMhk`;hsE2ov0?^qDK}MFtGCbJF`^L&$_DR1;Y1m0Ik`cqP;23Kkgn_C$L$M| zA3C5R_WjDe_r(SUs zXxP)D+3euDB!4K*TC?qrII!MY+jNsL>6sGNPDJLSa-#i*?2uik+qmW!M)fdMR?<&JfG*qsKk+nhNjtPl%AxuTr#hZS!EiP zVxvhu>TnIF&%nQ&{Ge`y2yLAn;#FQshmumVgS1o1?Q5W;)LJX+#D9O$`Y|^de-F_1Sfo;73j};_Mv5f+N3! z$>!sLDB6D%361>RvJgh*Sw@C97@q+S00NQNAbP<)wYlu-3BjDO^Y!=2M1j8Kgd1r# z!cN00!N-L42s8V~Y$6HK)}drfYseEZHo`1d%fV)d4W!fY$ZJ_f4IKl;6b&*r6?Vj^&?*eJB0Rw9YTPQ>|6;ntyQe9eSfitPw9QWNKMr&E!7CQ(b9c)>uEeii*_ zw*4t>rLPWBXVMYr%iv*mYL~bM=_>m)PKJK3e?JWuxmHM(RmOv4P|!WQVkxPtsq3S| zKnHM%xO|{$i#;UJc4&u-=*oydV#@M&>59XzFZYGZOIt}*Ro)3z=X27Skvp3>wHdoA znXqT2lfdI{O1(xf+efPaSt>wuF^X~~wt!cvcH&GWcY+cBd-2Be!X8NWxu^3W{%vk|^g)my@V`kKKcO9A8O9SbioQNw`BgX7(JRV&Ysp zMZ8*KY7{+H-2Nox%7GvSBmSN$6=9>|D6xu4I2%QEVtiVI1--rf;x!PuUZe>75&y9|K{^DC+7;)+gF`*3oo|Rtcv`k$#NDBvubJm*6_OJE?PN^T1RTrlY09iZj_fsA9M5Vj@EJJORGvyVoo}7_r~}vdD#SF_L~61id--S+NjjCz>_U>yt3{z!%R zH2hAz5u1+|XFwWTqr?krZj8s21u z=?Np)OAcV4vU2J8BZ^?xkOfmrDBd`+zivu~vkW18(bW*j49aJNy$o_fj0h;UIK*(} zT$E8?(o(js0eJAXgsN{^0KeP`e0?;-OwD3lVtExY7!dkQs`&!vnlyi1()pF;=DrHJFKjynBy{>&Db(~>#{1;nMP!$McTv@T+KS^y;ggT z1hLNZyFj5u#fYEX_6fgO$`!q-k*@f}h(jC%K#H6kWXr%X#T_ z6z-C!SGU8Tb1aXXxCj?ooypRqcYR$B3vS$T{I#q=Yr5J)4e(nmi(UJqQ3 zfi@%6nrAWJBmXx#{huWon=;*c%jD?c%EA&ULurQzr6E(vYEX{5wW<-;S6%67ZnC8z z=q7=`!ZxzOxk2mtqgBqMW)nBV>%V5+QniUUY1*152Q=HSrmFHLmKNfBSs9EX?wX+% zLdKG;rIPq`KcBlyY|e4opKf2LDt-eW4OEXaM6jiHk3x6X)^zK@o%*C zWKm=l3==dMgosMG@X+oA&(YPUqiY2hR&N9X?xO`hK*q+M8dr~TrQsiGG6AxA*j`TN!! z;g67|wP>+Iy`Uu<6LO{~FEm3%O4(jh9qn|pZDzyd$93D5HRsv|HgJuC$)>-4NwtbJ zYTu!E!_%W)?$}kfH#yfNZ;N~SLiGm2gxT%XtOmKmpaz+5nHmD=u^eK}QQ9b8?B1Xn z7EfGb0?l9CQPrP6of;yk4XoRORh<=YTs119t^CHnt=v8vx4o_W?ltYcZIarW=`UB! zD1y|yJEVN5+L~4l3vl|M_MWJt>K^2O6>6z5l^XPK+icAZPBDMap_J>;L;)7=k){e# z+>y2{BD)xKLiCZn?4RtLvknuU$^a8Q?H?1rn>oJ&mpbNtgpNf);ra`^!}v4ttK5Y} z>D)C3IU!{#McUL=sxrJ)KpE4jiVZFEP7})_Q;@@ZCi@Ox^D`G|_Qw~fq^iuIdqVlTR{|8tnLz1C49-W*0$h z_v-ZqOuVk@a16$>Pov1*u;lG=_*<0Fd5)syrw7-ihU1-MgHeuaT5pM}D6pRDT+7fA zT%SII+xZQsaO+BCkJal1bwrG*VpppbGzJ+E#!Jr5cFpZnYS0XNc5*G&Wqs@FUNIr5 zkI4GIb_n8&jz3Ql@Ax$F;szXP% z{XR)K`tS2K0_FZFVJpEtZ1GnP6Bv8=+GgC9S0fOJedA-1{L&-xVUTvhHA%XOMbom& zv|t$K0GkYHmmP4>MuyN6?PXp6+@TSW`ZN1#4I--nXO>|7NroV@7bj_TX98{VX2%DA z$VCrgEt-_-iEhmucU-u&B;>U7NakP{#;V9;cX=`*_bTb_En6y(F_P_FQvgPYN_q=D!wU3A9&yXd{Dt4*#VqOU#i; zp~wssFRF|nu#SQK(;f8O(~MjVB$7Ma|F`x8#`9XAP#l<7)40Qbd*Zv+siVTR60iqL z{n}&jAmhJVnENxPM<~I~s?y5Dk-g)I{S?3i$^RByv5a*5Ui&}g?ie_tb?N%U;&|S7F|C8d*EtC9Wb>2ucpt2y*`K)*a7jr@kYlcr$ zn5diVWuu`#~+(0A1ef$IXf$^lXo6takWhr`OBG@ ztgwDW5q|66KQmB18aEUxrSIn!{~3l6O-XJ1m{9~|ckOF}6w9VfkeH;ZdQ8@)4dAT* zqa_Lym=ciM<9v?e`#zes+U_RF0gsS})KswW{Jq_6V~IjIlEH#30FMPkLi!N&sUm@I zWZH(>*;5p70ge)@B(UG%zVDSb>92RTinhKR>QySRZR@unJkSM?FhfK(iUEpQIXWt+ zm@xxwCuE6C@h)06|BwNjjTR;^?16^<&yS}6-6E4&NM5ihN`Ei4W5DmVwoHmWOsis< z%;Lj{i(2)9W=89Ki~PFvjKe=eCRUzrH$w6^WXkuPz1W6!i*4)edQ+i=SEuW3=rQ*F zvGjRQ^Y{X3>)_uT+|);4N*e`3QkD-8F4att30tlz41SIz^)PUmr0*}2LjnGlAG6*v5 z3uaDje#BqBqLHz;Q#qZ&%{~^o*|72rfq5#=-=p%*+d@d zRRyYIJWKqNfYiy#(xL#JBt=TrTO?_a(fMh&j`5#0ylYXa4c3WQU+(%JPpeFVT&pRQ z%Uv5<*6SSV+*iAI$ucfbPeW?2YtqT>NSD=q>xhaZG%sF!+kzz*!8P~q2A6-O*YHl) zx~qvMmecQ$uCKm8)Mr%x{V#+t>je%WB^wd}8jYEgoq&NriQxZ+5i$Z$R8#D}z)8Qc zgiI-n$zc5dznY}q5XAq{Bt<8K$4sewg=0wB)`C<{&>0i_UxGnsa(IrE^fx#P2zC~B z_7+QWcymzTO_WwomE;HzQgtMp{z@7A_4CZOg+J`M=1KqS4eHcFq?X96^=av=^=rgu zqBU4uphT9Y^)vCN76d=4rsXVlrxl{4;2PQ$SZ2o}^v~{oQo|lMD~cf6KjrNszi>ej zzlZ*zXtK<}f>?~?d90=dl7;gF2~q(FJ%nGY3fTn2anpnC#p^^xK8m_*Kp8jixMwgfi3cnh@7+zV~tfoP_ zC&*kMe;jo!b>$k`_C^vmx-b+FJmXgUMKx*}Sh30mKeViGo{`Tv*Mt+s}zEtn3c>NXxlkw$3gQFh2k*591*lrJ5$n-_vYUhz*21yg({(?7#&nlk9bEU|~VX*b2Dm#GNm zIx1vltUomS3ehw^tgn&XuPBTfC9x`EATrPd9;}P_UdzRmH1>pySLmA`)u_vau(N@R zsyx5I$?{yDQX<6x|GZ+GPd7@|J0La3C}gb<9N`SWMBJ-)y8qNcnGG5axJ-vvS7BmG zEbl~i?=ymTz$CFFQ;M=X^?TO=K50FGLky7<`K^Wr!k{pkgYA&o@%59EU(JDW>T>w$uNgnW4gAN&aH?AMJ* z#5^&TF;8Hql6BVsIXnNrZia3qlh8LzWb)6IXENlYD-~cEK!?;)V}yi%aS@(}E4$6` z0vYjaRi&HtoDr~OIwthC$_*BQ!dB>UfrF=J*cW$&0M{Cf?A^kA8=7^Fneqd=Lqz+s zmLmVL0y{UD9#RK@+D1kgtr1A&>UxTv2w+Bv#u86#SD>Q_6O%k33ep!bC8FlSMW}ph zgAke#v?LO{qBu>$N|O57f%KD$=MlEXS10|R2X$)%5tY`ny=R<8PBTBb06&y|%R)!y z=2#PwF2QaB9r|&SCw+=c$*u#O4PNi*;S6~t_{h4(6fDT#SdE(eV^1<;Oi38&;tRm+ z1?&J6AsN8hsfN|MINC5d1nc!3;)YI75;OFru5wldLk8gK#I52`2Rza@xpw90lvHwzF%K+mSiS`$WGsy+saef7LetT{-=O z2($w+wHj~Q+R{&V>Fe%pgNNWxcb~)qnY7t zzVOUW9D{GuvA{HQLfxYS{J+(TJkY-hACc(;2~tUB)-hkHG2$N_5^RMrX|wP?2m;+K z>0=C-b2i$AeuNaO{gD4YVTDcT3nMn{OLVol0J^c<}Pz2a(hKIZ* zdWha=&k>#7eYR$`YI zQQzP}v2$J&L8%8i5}(Fn38|FxNhQ|2y62Z}OraLhHKJbBWRVaTF$YGLkMeMR!e*Rk zSQF*#{SA%L@})T%vEf-*A~wCdpITjVtd4ysKQ!KQ&%W+|M?HsR%Qg^3W#e*lPp=8_ zgU3Um9o{~Zu90s=)F1!th>K=c-QQn?(Eekw8oU=_={t}@#VcYbhs0}f{oQan-~fY% z!!#;@6f^6$h9F$B0Tp>bjAi$u8Y;$k69CRIAHgNHLVHQ5p%=K zkrn(B%O-3cMMM!%#w{g4)-w(PR1aWh=Gzp2O;}G4*K_7wV6+RHo3n+_>Yi(!7!oAc z*a<5WYXULF6NLMAf4f((kkts1sCPoe*|L6!Al0XltSUV-TBvwB&3B%%hGx6eFq%0% zxEAcP4|7Du^@bo8l(lIyotGPt2{&@vGi6rvlY@7QK+Ws$EEo}}8@?acK4OYPJp@{_ z=}(cRAI{B1vcVVBMYMS$l@K>>z78&objDibBLNrDmmXGH$f$63@{FB`Fcrq!1SE92 z?d2|`;}@saR?O+Cq+`>=9N|z)BJTC^_Vw+4;9aaORiv(8+3iBlhHz3G4rb1)dB3Fkp>~ z73Lw8rJE69B{>5r{*CC1obO!w(}%UXG1LbxPy=4m+V3X`DFQ{@o+JC-T$Cy_kXa@Fo3+BF3W_isyZ{ z2%#l-l|bTv$y^oni3;RCAP>JRlL>`X(XoKRak3V_*hEYLdn+ zS)W#|p*U)^&Aj4)LI!i7Lg=xR@NQQe0gVKY9aVozL{i|L)+`aTp5ffVV=d>Aa<1tJ zJT)f{J#{M1q5__fV3B*^n0iZ~ZO(kF-UXi<{3@kD(YA=}&lNivkr+!*olID8v*3#3 zuyh`2XvCZgyBj35@sN_TIi&zPTrrrIlq@F(kuKT2#Bbk{-n^JgBEsM3rIjmQu*yF? zk_5t(vk}A5dAkwx*yq%Uz&@Jz-JqAUz zBkoq<8zK;>1*IjEJC)@7k&vh-qsxPX`KMI9(EWjQU`r##5JN(P zJ$c3s&COu^xwC#h!f^u-WkB3*e#6gI=a0A0LTcPEEHI@&adG(Bs@<^DRLi=j?%wVP zs?ql|3$!BXila}5FYfO1ACBVmH=yl>gi3gTROX1p7tJbyr+5QLAoeygyq&N|MsYI} z6=)J%DRd(y!BEJd{p6&uo+<910MI2Y!AS&l-aqrLm)C2w+t7=^SOiKCT)ON5PU%Vx z3WS$mv2D3x0?e&+gsY%0F3PZ*=Q~57-jF{v@$GLL^L#)gps4umh@SxZq{1WJoct=J51X4*g)G5-KQl1h~h4 z8JwJzGZ<37#+o3zj8|;Nu0DjAJ!`e$NWL*jYIc|hm3qm3l<069Gu8%|x|Gr^Y}NCF z6lc^2p0QEXqybwVLpdcq*FOi~rvBYECY29!mQT3Dj-y=yy^Oc|rP{~$vptc!nyUj%yFyCRM-zF7SEw(&EQ7sGLb!8QOx<2;Rqnj@wuD*t ztEz5zJLi9m&15aQQC{Pq9o6XU^hV>=8uiK;*9yn{C>P(k;Iu_^p@FfKb&nW)OOLq~ zdK}}tguLMcernWj?p=@WHC8;u@ibK;y;`biKYvp3(HV5!%E@aYjVX8Tjp#2^f5Ud> zrRMnf4);*`5O-^8<2!$FQ@Y6Lhqt(0>V|RFh}c$+13@TjS33)wtt@$v#xW-8CD~wS z&e1lw2E}bL-p}O{|A|YSGbdk7LCVuFOSqQ+`MrMjiA(S-Se*|@zdaG-4@L*! zM%SxUP&exSD{|o&-42KR%$1P3)8s!ZaHSIK0PfaY zbArB&%aJ_16hZ}+3(94BGQqyh4Sm_`Mf=df8Wv<_g?_XzJ+tJ9>y_=_s0CGZUfhQ) z-NUT_2S_P-SjT)+R*_yYyB>!;e{|su49j_u#ymA(z)}$EOEKXy1R9E#h;@g4~O@YBA=un58Lmo$-})YitpMZx>~Fb)gNJ}W{^f7tN-My zR|C{4U|sQA(k^q@?!L=c!$ik#qo0;W&nrNV{zb4FjY{0JJ3$qoO3*oZV_NStPu5f@ zo|SSx_#yekHfy=m#CFXy_~zC1WAN1Dv(`eAt-9OlayDOzKXt==`V%*gHO46-Hww7^ zgqQf%+xF8>d^E>f#(PL^?*nRNZ^H1Sm*%^1NZ^FDVTqc(adObpXh);Fy!~Q1_6KlJ zW2nTZy+s%sf9=zui@#f4`!N;6qmk}YO}p91v}PV>g|SUp*WhB%u|RKPjem3WQUT;A3m+!3{v1GMORqn&V>!7?nnYnN*G_LA51 ztgEo-L%p21G|0~Mqzyf$dp_tIUSJ0^h-PnH%Wru&^V|Bnbj81-5=Yx#LDJ*DwQXFB z@r~~qBF1pGYMaxvD~4^VogC_ZYr;N68>*O!~VH;c5UdZG?513uZ z050=?6xo)be5;vx)ME9YekN4h)KH(>%LDJDHZ;(~`w{?*sAeER-m8)OwLlaX>vvDh zQ0rTc!;g9`H`yn;7v6J%9_x*#_Y1?;PHDu&Pd)wG3EHWxt-x#Ab_50*;|Ka#g@c|l zXvPcDJLZe|TRgZvb+{E*j6nTW#63Ih_+u*tA^ccxB{WV^Dn555#ocUt+>At^sH<6j z0<=dTR9<;jkB3%RPQ%P`9uS?cnQ>$$8L9QI33K4xxrvj+MMvBEAmE(0UGXQXL0xj_ z-q@PaqcHhN$6f2yn@^+*lO9+5$iKry%PENg3{{uCuwP?K`sH%mQcV)6Lh#xus6>zQ^Ye5;1{LxIOz3Sd5I#s)K+AnB>n zuG)I+riD(Vgz>kn7|*Udp;j+u=3L7wQ5@zddET>6ag6wAm9~b6uME<-zIU%$y8&WF zB0k{uczx$&GFhL7=R(hv!3H;K)AcP1`KK8su|bP6Y^Vr^3{v7IAIsLFMbZ^Rfkh^& z%nE57pBoEek6NE~~>0+Qtf@H;5V$ z(l?DTu!B?f7X|V*u3^_te_}KA{5YX4`r#TsGdlN~DoR1Uu>RykXI^ME3kM;Tl?9_X zd9uu@pFOyt0frX8`lIBMXc)T0GjP<_x{8v=c6#oKh-?l89SH76Rb1tAWyG(-<=Hnw zM;toXHQq3U7X&R6$h|}STTD5bIGH_*bXQOV!j;?~q89Gg*FS%o(byU!syS_}c}uUo z-0-MYK~rsVs0X|^Zup?d48v(BGDL8zKTiyp8Gm+z0mE@wPs<1IY1r>+!SK*g7%A}h z7h9|cI&a4Rbb%Rf#&atv{);+;T-A>D2YrrjiFHbQi1ojH3SKBDRiwn85CJGF50zdI8_v(7wDvpUZkIQCc6EI;K~+ zv)OZ|G&KC&;$3f+++vAJ4{Z_b%*gE=c~1MX2Cv!L?JT`3bE)eBob=d{KcpXbM#b}} zB8g<9b@yN%VOZdP74EOrDJTgp`TH6KSwE50m@x#`qV)-v)VCuEsVl7hg5)@O+dGc< zmJ4t-9RV7IgR1b=a)NK!Kp7_^Sxrt;3rt@DvMQ0mR;kgn_S{N2q^1NGrvZ}CfrRjz zsx6*3ji3Q<_q}#_+##uAIkGRlwa!e)*@VYvLqEY?QQ&{EZxF`|aO{qx7tH_Q7;D)` zXad1+D#-5GJT@XMt_|)25y06>#N@eEUJ;TZ#{tGbLy8vb4#<8zj3e?8^rX%T9O2-R zQl4uXFx7z>QE0PqSCT*sMsufe=ySyhdfmc;!n%Oy(mpP(N7#fD4pBcd-GFzt;)v_xWBT9Okz&zbzG&A- z=RldyBJ0EMXh2l|oO~f(RJnzkL-~shcj8=v%*Rh@@Y_~;sHu80alvs7(#_2zNe3vw z1|QHCL41DpoUT4Sc`Emi^GV;nYO+Ps_c@cboYRwhP$Xv-o2SEN z0OO=*!7iWQf;lAI=J?n~u_Z)UzrH827Lc*wt;`Wx7>C0QPEvBV=$Edvk~Zy)uRr{d zXX8H>#QvF*5W9f8wYZsiHt~6!`}0+8&WZ={C<&atl2=kROaL*Pv{?otgc%NnsMXp{ z%S$dE_4q*+9yNowO)S@+gGvU`_qLv;-LZ>CNr>BM;*+EXIA`oWF>xMi<7SAN5g^4{ z3L!nMkgDXexN}v?g-Pn7$f9qBE_K`BrwvVx$C6&2j+&BYdWK@zEIB9Cha%D0m} z%MoNx1?_;*mr{lPwia!?moW*qhjD8aG7@v>R0JhlDe4q#`e((GxMXGa7K&MI_OYUJ zr5*kLNb!;9tvwLd8 z=%>_`f37fHGvFlM3TAzC*S_VdLcX6w8WVwpg4F62e)I{%UhypFw7`a_Nb9RgB z(Du6JlMhqXT2pJ#c!wE$gX8*D%n^(HBi+mA5rnaN&3gL?G)zqi`m}1VeF-M_mtjr7 zAt}-g6BN^mA3?_E;vl7&edQk0NFc`ks=~^KsE(42ZH<@ zHNoJx-{AVGd>tqc;*T5S^M}HK!}NsfOxV3k*4BiPu+Bv|chNa-FJO+>yf~_n%n!+I zek22Q98yfG@IH*VksG8~QgGH8WDJ7H5S4Bfup@^BOOUJ&a|Hw)(+dlQS<*GA3k9hB z72H0^Dp1;X&gWW&eM~LI>Y6UiFVQ81vlel+BoEDgLQG&(OcREZHzvk1%{uraqjoc1 zzz*Ypv-750Y`~@MfTp53J}ZNVx1GP~!nAVNO~t3PODMzAd+dEXnQi(p?s%Q;Q2#hw z1@-rNXQe?dVGejfJ=1@6kIyY@^MyU{$^r8!uIcaan6V|+0tRQemS#&$`j zcg7>33xrW%66^(MA9UuY6yaKcj0z|epjBIE@HzP)1PM=6A00oKqFyrkK;#wHK$0#? z0{=B`9&qO5;C39MZmJ+S`{Pw`2~f_mb%&rmAY4}=tOP8>Lz%ro&wmvf)h>qX>!T;gwvDgS`@@@}WilM0fX#hC49 zYtSRo{=WJ(%V8~LOFz9_IEg=ylyX#52G|X|3z`;ElqxkQYQ&YyHngM5!f(ny937U|JPxNQ~Q03&Xrk!rlQ>7teToB%5YMc;; zy8mkttji8#bu=Jhq3a>g0Nc{NR|=>?cn2JOeUGP?ec_P}O&Qh|#<%YB1^3MClta z5g#slfmC*R-<2SxaDZBdMH)Q$K)T=QQZcF)Kc}-&{)0BDisYfK9&-CHus$d*B}YG# z|93i^HnLF>48|<(yPjlF+1YbvJfNaaO+tKD?)&9)R-}eL0PQ4Nfu*D>WT>!^+H$KP z2GwQ@YLTf`xG*WT)zI5TJ!H`?#E#B7~jZzt=(qiAq`J=VALD80$WR1UDV(ebkM^|z`an>AGmaF2O(lFX0h z_w{C8c?ztqB8byyKCAa=wCNl-{1|pC2dBnn7oR=sWX$dp{6* zmMsk|9!~fux5`fVOOMX04O;wP)neo=K^zQbwxyl?jo-5KYrnk^t}c>17X8z1zChc( zVN+%VP|#EIN#JQx@~FX7Q@rEBNxs98%qfI>;QaqDiaf>?Upa8%6dW=5UnvY^@N_8# zy$}Q`**5TSDKTR32`P84L}U<*>>SJ~GUD*qK*>Z314V*_F}@bSG?GL+Wq6@L%NJ5& zBdKP+_*vRG36iX@fx}R-8*d?ru~nvz*hF`Z$^$*80?O~gn-k|}Q;_S?u^8-<7*L9B zvq)jl#nl-wE=auf=Z~13mYkvP16(u|8u!dX`Y+XbJ}i-Mk>fcgDh~`XG>LXaP1KEN z;MtNroDUF>57@MK*A2dkQW;Gc`R@0y)6|UsE0TB1zAQNk^@lQKc;^lrlFi8Lovr5_ z0#jIl(4!8C#tdO^@9Y|~e`;?}AG|i{kC8zC6yGPGPZ&%KEDM)>;x%`seLq(~9ss7h z>BSvmVt)XOf7~HrAxAk=NaHIJ~)b(Gs z4KSk2HB@jw8aLvqh%LM&ZqaVF=~Ca!_|2SBgJI%7Ow}WzHG2y>LP(r!I-MYfz^oBV za1S@h?A6EP%qC3v`9dS3|HIik24@oWeZom5nF+3FVjC0N$xJ-4ZQJG*Ol)4UZQHhO z+jchh^XzWzhushFTh*t!>vW&${?Mn+zs^s6k%4$`)`=*nTvI+Cp_w>u6$TC2sum9o zXKib>U!;GA01NUdR^+1rrJ&w$Q7DD`Ps|H6CC$=vG

cB{)g)F%1SP6B?#&l;n`y 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 wrC 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 Date: Wed, 12 Aug 2026 15:53:28 +0530 Subject: [PATCH 02/60] fix: gate TSS libp2p inbound to eligible universal validators (F-2026-18136) (#301) --- .../tss/coordinator/coordinator.go | 22 +++ .../tss/coordinator/coordinator_test.go | 81 ++++++++++ .../tss/networking/libp2p/config.go | 5 + .../tss/networking/libp2p/gater.go | 30 ++++ .../tss/networking/libp2p/network.go | 37 ++++- .../tss/networking/libp2p/network_test.go | 145 ++++++++++++++++++ universalClient/tss/tss.go | 61 ++++---- 7 files changed, 350 insertions(+), 31 deletions(-) create mode 100644 universalClient/tss/networking/libp2p/gater.go diff --git a/universalClient/tss/coordinator/coordinator.go b/universalClient/tss/coordinator/coordinator.go index f1cbe4a6d..55627b11d 100644 --- a/universalClient/tss/coordinator/coordinator.go +++ b/universalClient/tss/coordinator/coordinator.go @@ -179,6 +179,28 @@ func (c *Coordinator) GetPeerIDFromPartyID(_ context.Context, partyID string) (s return "", fmt.Errorf("partyID %s not found in validators", partyID) } +// IsKnownPeer reports whether peerID belongs to a Universal Validator that can +// participate in some TSS protocol (Active, Pending Join, or Pending Leave). +// Fails closed when the cache is empty or stale. +func (c *Coordinator) IsKnownPeer(peerID string) bool { + for _, v := range c.validatorsSnapshot() { + if v.NetworkInfo == nil || v.NetworkInfo.PeerId != peerID { + continue + } + if v.LifecycleInfo == nil { + return false + } + switch v.LifecycleInfo.CurrentStatus { + case types.UVStatus_UV_STATUS_ACTIVE, + types.UVStatus_UV_STATUS_PENDING_JOIN, + types.UVStatus_UV_STATUS_PENDING_LEAVE: + return true + } + return false + } + return false +} + // GetMultiAddrsFromPeerID gets the multiaddrs for a given peerID. func (c *Coordinator) GetMultiAddrsFromPeerID(_ context.Context, peerID string) ([]string, error) { for _, v := range c.validatorsSnapshot() { diff --git a/universalClient/tss/coordinator/coordinator_test.go b/universalClient/tss/coordinator/coordinator_test.go index 7e2b6c694..72daca8ec 100644 --- a/universalClient/tss/coordinator/coordinator_test.go +++ b/universalClient/tss/coordinator/coordinator_test.go @@ -1277,3 +1277,84 @@ func TestValidatorsSnapshot(t *testing.T) { assert.NotNil(t, coord.validatorsSnapshot()) }) } + +func TestIsKnownPeer(t *testing.T) { + uv := func(peerID string, status types.UVStatus) *types.UniversalValidator { + return &types.UniversalValidator{ + IdentifyInfo: &types.IdentityInfo{CoreValidatorAddress: "addr-" + peerID}, + NetworkInfo: &types.NetworkInfo{PeerId: peerID, MultiAddrs: []string{"/ip4/127.0.0.1/tcp/9001"}}, + LifecycleInfo: &types.LifecycleInfo{CurrentStatus: status}, + } + } + + setValidators := func(coord *Coordinator, vs []*types.UniversalValidator) { + coord.mu.Lock() + coord.allValidators = vs + coord.lastValidatorsRefreshAt = time.Now() + coord.mu.Unlock() + } + + coord, _, _ := setupTestCoordinator(t) + + t.Run("eligible statuses admitted", func(t *testing.T) { + setValidators(coord, []*types.UniversalValidator{ + uv("active", types.UVStatus_UV_STATUS_ACTIVE), + uv("joining", types.UVStatus_UV_STATUS_PENDING_JOIN), + uv("leaving", types.UVStatus_UV_STATUS_PENDING_LEAVE), + }) + assert.True(t, coord.IsKnownPeer("active")) + assert.True(t, coord.IsKnownPeer("joining")) + assert.True(t, coord.IsKnownPeer("leaving")) + }) + + t.Run("inactive and unspecified rejected", func(t *testing.T) { + setValidators(coord, []*types.UniversalValidator{ + uv("active", types.UVStatus_UV_STATUS_ACTIVE), + uv("inactive", types.UVStatus_UV_STATUS_INACTIVE), + uv("unspecified", types.UVStatus_UV_STATUS_UNSPECIFIED), + }) + assert.False(t, coord.IsKnownPeer("inactive")) + assert.False(t, coord.IsKnownPeer("unspecified")) + }) + + t.Run("unknown peer rejected", func(t *testing.T) { + setValidators(coord, []*types.UniversalValidator{ + uv("active", types.UVStatus_UV_STATUS_ACTIVE), + }) + assert.False(t, coord.IsKnownPeer("stranger")) + }) + + t.Run("nil lifecycle info rejected", func(t *testing.T) { + noLifecycle := uv("ghost", types.UVStatus_UV_STATUS_ACTIVE) + noLifecycle.LifecycleInfo = nil + setValidators(coord, []*types.UniversalValidator{ + uv("active", types.UVStatus_UV_STATUS_ACTIVE), + noLifecycle, + }) + assert.False(t, coord.IsKnownPeer("ghost")) + }) + + t.Run("bootstrap keygen peers admitted without any active validator", func(t *testing.T) { + // Fresh network: everyone is Pending Join. Strict filter must still + // admit them so keygen can start; Inactive stays rejected even here. + setValidators(coord, []*types.UniversalValidator{ + uv("joining", types.UVStatus_UV_STATUS_PENDING_JOIN), + uv("joining2", types.UVStatus_UV_STATUS_PENDING_JOIN), + uv("inactive", types.UVStatus_UV_STATUS_INACTIVE), + }) + assert.True(t, coord.IsKnownPeer("joining")) + assert.True(t, coord.IsKnownPeer("joining2")) + assert.False(t, coord.IsKnownPeer("inactive")) + assert.False(t, coord.IsKnownPeer("stranger")) + }) + + t.Run("stale cache fails closed", func(t *testing.T) { + setValidators(coord, []*types.UniversalValidator{ + uv("active", types.UVStatus_UV_STATUS_ACTIVE), + }) + coord.mu.Lock() + coord.lastValidatorsRefreshAt = time.Now().Add(-time.Hour) + coord.mu.Unlock() + assert.False(t, coord.IsKnownPeer("active")) + }) +} diff --git a/universalClient/tss/networking/libp2p/config.go b/universalClient/tss/networking/libp2p/config.go index 2acfa6328..e45e11d98 100644 --- a/universalClient/tss/networking/libp2p/config.go +++ b/universalClient/tss/networking/libp2p/config.go @@ -15,6 +15,11 @@ type Config struct { DialTimeout time.Duration // IOTimeout bounds stream read/write operations. IOTimeout time.Duration + // Authorizer reports whether a remote peer ID is allowed to connect and + // open TSS streams. When set, inbound connections from unauthorized peers + // are rejected at secured-connection admission and any stream that slips + // through is reset before reading. Nil disables gating (tests only). + Authorizer func(peerID string) bool } // setDefaults sets default values for unset fields. diff --git a/universalClient/tss/networking/libp2p/gater.go b/universalClient/tss/networking/libp2p/gater.go new file mode 100644 index 000000000..ac773e9ef --- /dev/null +++ b/universalClient/tss/networking/libp2p/gater.go @@ -0,0 +1,30 @@ +package libp2p + +import ( + "github.com/libp2p/go-libp2p/core/control" + "github.com/libp2p/go-libp2p/core/network" + "github.com/libp2p/go-libp2p/core/peer" + ma "github.com/multiformats/go-multiaddr" +) + +// validatorGater rejects inbound connections whose authenticated peer ID is +// not accepted by the authorizer. Outbound dials are not gated: this node only +// dials peers resolved from the validator set. +type validatorGater struct { + authorizer func(peerID string) bool +} + +func (g *validatorGater) InterceptPeerDial(peer.ID) bool { return true } +func (g *validatorGater) InterceptAddrDial(peer.ID, ma.Multiaddr) bool { return true } +func (g *validatorGater) InterceptAccept(network.ConnMultiaddrs) bool { return true } + +func (g *validatorGater) InterceptSecured(dir network.Direction, p peer.ID, _ network.ConnMultiaddrs) bool { + if dir == network.DirOutbound { + return true + } + return g.authorizer(p.String()) +} + +func (g *validatorGater) InterceptUpgraded(network.Conn) (bool, control.DisconnectReason) { + return true, 0 +} diff --git a/universalClient/tss/networking/libp2p/network.go b/universalClient/tss/networking/libp2p/network.go index 8710940c6..dec383032 100644 --- a/universalClient/tss/networking/libp2p/network.go +++ b/universalClient/tss/networking/libp2p/network.go @@ -31,6 +31,10 @@ import ( // observed DKLS Step() + coordinator.Message wrapping for our committee sizes. const MaxFrameSize = 1 * 1024 * 1024 // 1 MiB +// maxConcurrentReads bounds in-flight framed reads across all inbound TSS +// streams so slow peers cannot pin unbounded goroutines on blocking reads. +const maxConcurrentReads = 64 + // Network implements networking.Network using libp2p. type Network struct { cfg Config @@ -43,6 +47,8 @@ type Network struct { peerMu sync.RWMutex peers map[string]peer.AddrInfo + readSem chan struct{} + logger zerolog.Logger } @@ -58,10 +64,15 @@ func New(ctx context.Context, cfg Config, logger zerolog.Logger) (*Network, erro return nil, err } - host, err := libp2p.New( + opts := []libp2p.Option{ libp2p.Identity(priv), libp2p.ListenAddrStrings(cfg.ListenAddrs...), - ) + } + if cfg.Authorizer != nil { + opts = append(opts, libp2p.ConnectionGater(&validatorGater{authorizer: cfg.Authorizer})) + } + + host, err := libp2p.New(opts...) if err != nil { return nil, err } @@ -71,6 +82,7 @@ func New(ctx context.Context, cfg Config, logger zerolog.Logger) (*Network, erro host: host, protocolID: protocol.ID(cfg.ProtocolID), peers: make(map[string]peer.AddrInfo), + readSem: make(chan struct{}, maxConcurrentReads), logger: logger.With().Str("component", "networking_libp2p").Logger(), } @@ -194,6 +206,25 @@ func (n *Network) lookupPeer(peerID string) (peer.AddrInfo, error) { } func (n *Network) handleStream(stream network.Stream) { + remotePeer := stream.Conn().RemotePeer().String() + // Recheck authorization per stream: the gater only runs at connection + // admission, so this covers peers removed from the validator set while a + // connection is still open. + if n.cfg.Authorizer != nil && !n.cfg.Authorizer(remotePeer) { + n.logger.Warn().Str("peer_id", remotePeer).Msg("resetting stream from unauthorized peer") + _ = stream.Reset() + return + } + + select { + case n.readSem <- struct{}{}: + default: + n.logger.Warn().Str("peer_id", remotePeer).Msg("concurrent read limit reached, resetting stream") + _ = stream.Reset() + return + } + defer func() { <-n.readSem }() + defer stream.Close() if deadline := time.Now().Add(n.cfg.IOTimeout); true { @@ -214,7 +245,7 @@ func (n *Network) handleStream(stream network.Stream) { } // Call handler in a goroutine to avoid blocking - go handler(stream.Conn().RemotePeer().String(), data) + go handler(remotePeer, data) } func loadIdentity(base64Key string) (crypto.PrivKey, error) { diff --git a/universalClient/tss/networking/libp2p/network_test.go b/universalClient/tss/networking/libp2p/network_test.go index 8c9846684..f09aa1e7c 100644 --- a/universalClient/tss/networking/libp2p/network_test.go +++ b/universalClient/tss/networking/libp2p/network_test.go @@ -2,10 +2,15 @@ package libp2p import ( "bytes" + "context" "encoding/binary" + "fmt" "io" + "sync" "testing" + "time" + "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -78,3 +83,143 @@ func TestWriteFramed_AcceptsAtMaxFrameSize(t *testing.T) { assert.Equal(t, payload[len(payload)-1], got[len(got)-1]) } +// allowlist is a mutable peer-ID allowlist used as a test Authorizer. +type allowlist struct { + mu sync.RWMutex + peers map[string]bool +} + +func newAllowlist() *allowlist { + return &allowlist{peers: make(map[string]bool)} +} + +func (a *allowlist) allow(peerID string) { + a.mu.Lock() + a.peers[peerID] = true + a.mu.Unlock() +} + +func (a *allowlist) revoke(peerID string) { + a.mu.Lock() + delete(a.peers, peerID) + a.mu.Unlock() +} + +func (a *allowlist) authorized(peerID string) bool { + a.mu.RLock() + defer a.mu.RUnlock() + return a.peers[peerID] +} + +func newTestNetwork(t *testing.T, authorizer func(string) bool) *Network { + t.Helper() + n, err := New(context.Background(), Config{ + ListenAddrs: []string{"/ip4/127.0.0.1/tcp/0"}, + DialTimeout: 5 * time.Second, + IOTimeout: 5 * time.Second, + Authorizer: authorizer, + }, zerolog.New(io.Discard)) + require.NoError(t, err) + t.Cleanup(func() { _ = n.Close() }) + return n +} + +func connectPeer(t *testing.T, from *Network, to *Network) { + t.Helper() + require.NoError(t, from.EnsurePeer(to.ID(), to.ListenAddrs())) +} + +func collectMessages(t *testing.T, n *Network) <-chan string { + t.Helper() + msgs := make(chan string, 64) + require.NoError(t, n.RegisterHandler(func(peerID string, data []byte) { + msgs <- peerID + ":" + string(data) + })) + return msgs +} + +func TestNetwork_RejectsUnknownPeer(t *testing.T) { + acl := newAllowlist() + receiver := newTestNetwork(t, acl.authorized) + rogue := newTestNetwork(t, nil) + msgs := collectMessages(t, receiver) + + connectPeer(t, rogue, receiver) + err := rogue.Send(context.Background(), receiver.ID(), []byte("intrusion")) + require.Error(t, err, "unauthenticated peer must not reach the TSS protocol") + + select { + case m := <-msgs: + t.Fatalf("handler received message from unauthorized peer: %s", m) + case <-time.After(500 * time.Millisecond): + } +} + +func TestNetwork_AuthorizedPeerDeliversDuringUnauthenticatedFlood(t *testing.T) { + acl := newAllowlist() + receiver := newTestNetwork(t, acl.authorized) + validator := newTestNetwork(t, nil) + acl.allow(validator.ID()) + msgs := collectMessages(t, receiver) + + const rogues = 8 + var wg sync.WaitGroup + for i := range rogues { + rogue := newTestNetwork(t, nil) + connectPeer(t, rogue, receiver) + wg.Add(1) + go func(r *Network, i int) { + defer wg.Done() + for j := range 5 { + _ = r.Send(context.Background(), receiver.ID(), fmt.Appendf(nil, "flood-%d-%d", i, j)) + } + }(rogue, i) + } + + connectPeer(t, validator, receiver) + require.NoError(t, validator.Send(context.Background(), receiver.ID(), []byte("ack"))) + wg.Wait() + + select { + case m := <-msgs: + assert.Equal(t, validator.ID()+":ack", m) + case <-time.After(5 * time.Second): + t.Fatal("validator message not delivered during unauthenticated flood") + } + + select { + case m := <-msgs: + t.Fatalf("received unexpected message: %s", m) + case <-time.After(500 * time.Millisecond): + } +} + +func TestNetwork_ResetsStreamAfterPeerRevoked(t *testing.T) { + acl := newAllowlist() + receiver := newTestNetwork(t, acl.authorized) + validator := newTestNetwork(t, nil) + acl.allow(validator.ID()) + msgs := collectMessages(t, receiver) + + connectPeer(t, validator, receiver) + require.NoError(t, validator.Send(context.Background(), receiver.ID(), []byte("before"))) + select { + case m := <-msgs: + assert.Equal(t, validator.ID()+":before", m) + case <-time.After(5 * time.Second): + t.Fatal("message from authorized peer not delivered") + } + + // Revoke: the existing connection survives the gater, but handleStream + // must reset new streams from the now-unauthorized peer. + acl.revoke(validator.ID()) + _ = validator.Send(context.Background(), receiver.ID(), []byte("after")) + + select { + case m := <-msgs: + t.Fatalf("handler received message from revoked peer: %s", m) + case <-time.After(500 * time.Millisecond): + } +} + + diff --git a/universalClient/tss/tss.go b/universalClient/tss/tss.go index 38d70b16e..f54ca0650 100644 --- a/universalClient/tss/tss.go +++ b/universalClient/tss/tss.go @@ -285,34 +285,9 @@ func (n *Node) Start(ctx context.Context) error { n.logger.Debug().Msg("starting TSS node") - // Start libp2p network - net, err := libp2pnet.New(ctx, n.networkCfg, n.logger) - if err != nil { - return fmt.Errorf("failed to start libp2p network: %w", err) - } - n.network = net - - // Register global message handler - if err := net.RegisterHandler(n.onReceive); err != nil { - net.Close() - return fmt.Errorf("failed to register message handler: %w", err) - } - - // Recover IN_PROGRESS events on startup. Two-pass: - // 1. Rows whose event_data already carries signing_data → SIGNED - // (signature was persisted but status got clobbered by a race). - // 2. Remaining IN_PROGRESS → CONFIRMED (genuine mid-session crashes). - signedRecovered, confirmedReset, err := n.eventStore.RecoverInProgressEvents() - if err != nil { - n.logger.Warn().Err(err).Msg("failed to recover IN_PROGRESS events, continuing anyway") - } else if signedRecovered > 0 || confirmedReset > 0 { - n.logger.Info(). - Int64("signed_recovered", signedRecovered). - Int64("confirmed_reset", confirmedReset). - Msg("recovered IN_PROGRESS events on node startup") - } - - // Create coordinator with send function using node's Send method + // Create coordinator with send function using node's Send method. + // Created before the network so the connection gater and message handler + // never observe a nil coordinator or session manager. if n.coordinator == nil { coord := coordinator.NewCoordinator( n.eventStore, @@ -351,6 +326,36 @@ func (n *Node) Start(ctx context.Context) error { n.sessionManager = sessionMgr } + // Only Universal Validators may connect and open TSS streams + n.networkCfg.Authorizer = n.coordinator.IsKnownPeer + + // Start libp2p network + net, err := libp2pnet.New(ctx, n.networkCfg, n.logger) + if err != nil { + return fmt.Errorf("failed to start libp2p network: %w", err) + } + n.network = net + + // Register global message handler + if err := net.RegisterHandler(n.onReceive); err != nil { + net.Close() + return fmt.Errorf("failed to register message handler: %w", err) + } + + // Recover IN_PROGRESS events on startup. Two-pass: + // 1. Rows whose event_data already carries signing_data → SIGNED + // (signature was persisted but status got clobbered by a race). + // 2. Remaining IN_PROGRESS → CONFIRMED (genuine mid-session crashes). + signedRecovered, confirmedReset, err := n.eventStore.RecoverInProgressEvents() + if err != nil { + n.logger.Warn().Err(err).Msg("failed to recover IN_PROGRESS events, continuing anyway") + } else if signedRecovered > 0 || confirmedReset > 0 { + n.logger.Info(). + Int64("signed_recovered", signedRecovered). + Int64("confirmed_reset", confirmedReset). + Msg("recovered IN_PROGRESS events on node startup") + } + // Start coordinator n.coordinator.Start(ctx) From d28d09532d6124d86223820d089fe112e6646db3 Mon Sep 17 00:00:00 2001 From: Aman Gupta Date: Mon, 17 Aug 2026 12:52:52 +0530 Subject: [PATCH 03/60] fix: F-2026-18145 | [Dual Defense] Missing OP-Stack L1 Data-Fee Accounting Under-Reports Outbound Cost (#304) * fix: include OP-Stack L1 data fee in outbound GasFeeUsed accounting (F-2026-18145) * refactor: read L2+L1 gas fee from a single receipt call (F-2026-18145) * refactor: single GetReceipt method with GasFee helper, drop typed receipt fetch (F-2026-18145) * refactor: keep GetTransactionReceipt name, move gas-fee helper out of rpc_client (F-2026-18145) * fix: source gas price from tx, not receipt effectiveGasPrice, for fee accounting (F-2026-18145) * fix: use receipt effectiveGasPrice for gas fee, guard missing field, drop tx fetch (F-2026-18145) * test: add skipped live-RPC gas-fee check for Sepolia and Base Sepolia (F-2026-18145) * fix: error instead of zero gas fee when receipt fee cannot be determined (F-2026-18145) --- universalClient/chains/common/types.go | 5 +- universalClient/chains/evm/event_confirmer.go | 19 +- universalClient/chains/evm/l1fee_test.go | 172 ++++++++++++++++++ universalClient/chains/evm/rpc_client.go | 53 +++++- universalClient/chains/evm/tx_builder.go | 38 ++-- 5 files changed, 248 insertions(+), 39 deletions(-) create mode 100644 universalClient/chains/evm/l1fee_test.go diff --git a/universalClient/chains/common/types.go b/universalClient/chains/common/types.go index 98a3b85c9..aa44e5827 100644 --- a/universalClient/chains/common/types.go +++ b/universalClient/chains/common/types.go @@ -78,9 +78,10 @@ type TxBuilder interface { IsAlreadyExecuted(ctx context.Context, txID string) (executed bool, queryBlockTime int64, err error) // GetGasFeeUsed returns the gas fee used by a transaction on the destination chain. - // EVM: fetches receipt and returns gasUsed * effectiveGasPrice as decimal string. + // EVM: gasUsed * effectiveGasPrice + OP-Stack l1Fee, as a decimal string; errors + // when the fee cannot be determined so callers retry instead of recording an + // under-reported fee. // SVM: returns "0" (gas accounting is handled via vault gasFee reimbursement). - // Returns "0" if the transaction is not found. GetGasFeeUsed(ctx context.Context, txHash string) (string, error) // GetFundMigrationSigningRequest builds a native token transfer for fund migration, diff --git a/universalClient/chains/evm/event_confirmer.go b/universalClient/chains/evm/event_confirmer.go index c30039040..e43f15326 100644 --- a/universalClient/chains/evm/event_confirmer.go +++ b/universalClient/chains/evm/event_confirmer.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "math/big" "strings" "sync" "time" @@ -140,7 +139,7 @@ func (ec *EventConfirmer) processPendingEvents(ctx context.Context) error { // Get transaction receipt hash := ethcommon.HexToHash(txHash) receipt, err := ec.rpcClient.GetTransactionReceipt(ctx, hash) - if err != nil { + if err != nil || receipt == nil { // Transaction not found or not yet mined - skip continue } @@ -160,25 +159,23 @@ func (ec *EventConfirmer) processPendingEvents(ctx context.Context) error { // Check if transaction is confirmed based on confirmation type requiredConfirmations := ec.getRequiredConfirmations(event.ConfirmationType) - confirmations := latestBlock - receipt.BlockNumber.Uint64() + 1 + confirmations := latestBlock - receipt.BlockNumber + 1 if confirmations >= requiredConfirmations { var rowsAffected int64 // For outbound events, enrich with gas fee before confirming if event.Type == store.EventTypeOutbound { - tx, _, txErr := ec.rpcClient.GetTransactionByHash(ctx, hash) - if txErr != nil { + if receipt.EffectiveGasPrice == nil { + // Receipt omitted effectiveGasPrice; skip rather than record a + // gas fee missing its L2 execution component. Retried next poll. ec.logger.Warn(). - Err(txErr). Str("event_id", event.EventID). Str("tx_hash", txHash). - Msg("failed to fetch transaction for gas fee, skipping confirmation") + Msg("receipt missing effectiveGasPrice, skipping confirmation") continue } - gasUsed := new(big.Int).SetUint64(receipt.GasUsed) - gasPrice := tx.GasPrice() - gasFeeUsed := new(big.Int).Mul(gasUsed, gasPrice).String() + gasFeeUsedStr := gasFeeUsed(receipt.GasUsed, receipt.EffectiveGasPrice, receipt.L1Fee).String() // Unmarshal, set GasFeeUsed, re-marshal var outboundEvent chaincommon.OutboundEvent @@ -189,7 +186,7 @@ func (ec *EventConfirmer) processPendingEvents(ctx context.Context) error { Msg("failed to unmarshal outbound event data") continue } - outboundEvent.GasFeeUsed = gasFeeUsed + outboundEvent.GasFeeUsed = gasFeeUsedStr updatedData, marshalErr := json.Marshal(outboundEvent) if marshalErr != nil { diff --git a/universalClient/chains/evm/l1fee_test.go b/universalClient/chains/evm/l1fee_test.go new file mode 100644 index 000000000..e680bc581 --- /dev/null +++ b/universalClient/chains/evm/l1fee_test.go @@ -0,0 +1,172 @@ +package evm + +import ( + "context" + "math/big" + "net/http" + "net/http/httptest" + "os" + "strings" + "testing" + + ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// receiptRPC serves eth_chainId plus a single receipt for any receipt lookup. +func receiptRPC(t *testing.T, receiptJSON string) *RPCClient { + t.Helper() + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + body := make([]byte, r.ContentLength) + r.Body.Read(body) + switch { + case strings.Contains(string(body), "eth_chainId"): + w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":"0xaa36a7"}`)) // 11155111 + case strings.Contains(string(body), "eth_getTransactionReceipt"): + w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":` + receiptJSON + `}`)) + default: + w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":null}`)) + } + })) + t.Cleanup(server.Close) + + rc, err := NewRPCClient([]string{server.URL}, 11155111, zerolog.Nop()) + require.NoError(t, err) + t.Cleanup(func() { rc.Close() }) + return rc +} + +// receipt builds a minimal receipt JSON. gasUsed 0x5208 (21000), +// effectiveGasPrice 0x4a817c800 (20 gwei) unless withEffPrice is false; +// l1FeeField is "" for non-OP chains. +func receipt(l1FeeField string, withEffPrice bool) string { + eff := "" + if withEffPrice { + eff = `"effectiveGasPrice":"0x4a817c800",` + } + return `{"transactionHash":"0xabc","blockHash":"0x2222222222222222222222222222222222222222222222222222222222222222",` + + `"blockNumber":"0x1","transactionIndex":"0x0","cumulativeGasUsed":"0x5208",` + + `"gasUsed":"0x5208",` + eff + `"status":"0x1","contractAddress":null,` + + `"logs":[],"logsBloom":"0x` + strings.Repeat("0", 512) + `",` + l1FeeField + `"type":"0x0"}` +} + +// A nonzero l1Fee must be added to GasFeeUsed so the core refund (gasFee − +// GasFeeUsed) shrinks by exactly that amount; both come from one receipt read. +func TestGetGasFeeUsed(t *testing.T) { + execFee := new(big.Int).Mul(big.NewInt(21000), big.NewInt(20_000_000_000)) // gasUsed * effectiveGasPrice + tb := func(rc *RPCClient) *TxBuilder { + return &TxBuilder{rpcClient: rc, chainID: "eip155:11155111", chainIDInt: 11155111, logger: zerolog.Nop()} + } + + t.Run("OP destination adds l1Fee", func(t *testing.T) { + got, err := tb(receiptRPC(t, receipt(`"l1Fee":"0x5208",`, true))).GetGasFeeUsed(context.Background(), "0xabc") + require.NoError(t, err) + assert.Equal(t, new(big.Int).Add(execFee, big.NewInt(0x5208)).String(), got) + }) + + t.Run("non-OP destination is execution fee only", func(t *testing.T) { + got, err := tb(receiptRPC(t, receipt(``, true))).GetGasFeeUsed(context.Background(), "0xabc") + require.NoError(t, err) + assert.Equal(t, execFee.String(), got) + }) + + // Errors rather than "0": a zero fee here would make core refund the full + // gasFee, the same over-refund this fix removes. Callers retry instead. + t.Run("missing effectiveGasPrice errors", func(t *testing.T) { + _, err := tb(receiptRPC(t, receipt(`"l1Fee":"0x5208",`, false))).GetGasFeeUsed(context.Background(), "0xabc") + require.Error(t, err) + assert.Contains(t, err.Error(), "missing effectiveGasPrice") + }) + + t.Run("missing receipt errors", func(t *testing.T) { + _, err := tb(receiptRPC(t, `null`)).GetGasFeeUsed(context.Background(), "0xabc") + require.Error(t, err) + assert.Contains(t, err.Error(), "receipt not found") + }) +} + +// GetTransactionReceipt surfaces effectiveGasPrice (nil when absent) and l1Fee. +func TestGetTransactionReceipt_Fields(t *testing.T) { + hash := ethcommon.HexToHash("0xabc") + + t.Run("effectiveGasPrice and l1Fee parsed", func(t *testing.T) { + r, err := receiptRPC(t, receipt(`"l1Fee":"0x5208",`, true)).GetTransactionReceipt(context.Background(), hash) + require.NoError(t, err) + require.NotNil(t, r) + assert.Equal(t, int64(20_000_000_000), r.EffectiveGasPrice.Int64()) + assert.Equal(t, int64(0x5208), r.L1Fee.Int64()) + }) + + t.Run("nil effectiveGasPrice when absent", func(t *testing.T) { + r, err := receiptRPC(t, receipt(``, false)).GetTransactionReceipt(context.Background(), hash) + require.NoError(t, err) + require.NotNil(t, r) + assert.Nil(t, r.EffectiveGasPrice) + assert.Equal(t, int64(0), r.L1Fee.Int64()) + }) +} + +// TestLive_GasFeeUsed exercises the real fetch + fee computation against public +// RPCs for two known txs (one non-OP, one OP). Skipped by default; run with: +// +// RUN_LIVE_RPC_TESTS=1 go test ./universalClient/chains/evm/ -run TestLive_GasFeeUsed -v +func TestLive_GasFeeUsed(t *testing.T) { + if os.Getenv("RUN_LIVE_RPC_TESTS") != "1" { + t.Skip("set RUN_LIVE_RPC_TESTS=1 to run live RPC test") + } + + cases := []struct { + name string + rpcURL string + chainID int64 + txHash string + wantFee string // gasUsed*effectiveGasPrice + l1Fee + wantL1 string + }{ + { + name: "Ethereum Sepolia (non-OP, l1Fee=0)", + rpcURL: "https://ethereum-sepolia-rpc.publicnode.com", + chainID: 11155111, + txHash: "0x489fb72d961e9bd69983fdaa52f0c9113705330f2e4bf4ac3fc46e1fb2977f08", + wantFee: "170830319373250", + wantL1: "0", + }, + { + name: "Base Sepolia (OP, nonzero l1Fee)", + rpcURL: "https://sepolia.base.org", + chainID: 84532, + txHash: "0x7b961e5cfbb6f8ddced1a0694773290ddb0d32caaaf8494f850d7ad07ddc0c30", + wantFee: "1032488370864", + wantL1: "14015970864", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + rc, err := NewRPCClient([]string{tc.rpcURL}, tc.chainID, zerolog.Nop()) + require.NoError(t, err) + defer rc.Close() + + receipt, err := rc.GetTransactionReceipt(context.Background(), ethcommon.HexToHash(tc.txHash)) + require.NoError(t, err) + require.NotNil(t, receipt, "tx not found on chain") + require.NotNil(t, receipt.EffectiveGasPrice, "receipt missing effectiveGasPrice") + + fee := gasFeeUsed(receipt.GasUsed, receipt.EffectiveGasPrice, receipt.L1Fee) + t.Logf("gasUsed=%d effectiveGasPrice=%s l1Fee=%s => GasFeeUsed=%s", + receipt.GasUsed, receipt.EffectiveGasPrice, receipt.L1Fee, fee) + + assert.Equal(t, tc.wantL1, receipt.L1Fee.String(), "l1Fee") + assert.Equal(t, tc.wantFee, fee.String(), "GasFeeUsed") + + // Full path through the TxBuilder entrypoint. + tb := &TxBuilder{rpcClient: rc, chainIDInt: tc.chainID, logger: zerolog.Nop()} + got, err := tb.GetGasFeeUsed(context.Background(), tc.txHash) + require.NoError(t, err) + assert.Equal(t, tc.wantFee, got) + }) + } +} diff --git a/universalClient/chains/evm/rpc_client.go b/universalClient/chains/evm/rpc_client.go index b8c83d04d..ab9086673 100644 --- a/universalClient/chains/evm/rpc_client.go +++ b/universalClient/chains/evm/rpc_client.go @@ -10,6 +10,7 @@ import ( "github.com/ethereum/go-ethereum" ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" "github.com/rs/zerolog" @@ -193,15 +194,53 @@ func (rc *RPCClient) FilterLogs(ctx context.Context, query ethereum.FilterQuery) return logs, err } -// GetTransactionReceipt fetches a transaction receipt -func (rc *RPCClient) GetTransactionReceipt(ctx context.Context, txHash ethcommon.Hash) (*types.Receipt, error) { - var receipt *types.Receipt +// Receipt holds the transaction-receipt fields the universal client needs, +// including the OP-Stack L1 data fee that go-ethereum's typed receipt omits. +type Receipt struct { + Status uint64 + BlockNumber uint64 + GasUsed uint64 + EffectiveGasPrice *big.Int // nil if the receipt omits the field (pre-London / non-compliant RPC) + L1Fee *big.Int // OP-Stack L1 data fee; 0 on non-OP chains +} + +// GetTransactionReceipt fetches a transaction receipt in a single raw call, +// reading the OP-Stack l1Fee alongside the standard fields. Returns (nil, nil) +// if the tx is not found (receipt is null). +func (rc *RPCClient) GetTransactionReceipt(ctx context.Context, txHash ethcommon.Hash) (*Receipt, error) { + var raw struct { + Status *hexutil.Uint64 `json:"status"` + BlockNumber *hexutil.Big `json:"blockNumber"` + GasUsed *hexutil.Uint64 `json:"gasUsed"` + EffectiveGasPrice *hexutil.Big `json:"effectiveGasPrice"` + L1Fee *hexutil.Big `json:"l1Fee"` + } err := rc.executeWithFailover(ctx, "get_transaction_receipt", func(client *ethclient.Client) error { - var innerErr error - receipt, innerErr = client.TransactionReceipt(ctx, txHash) - return innerErr + return client.Client().CallContext(ctx, &raw, "eth_getTransactionReceipt", txHash) }) - return receipt, err + if err != nil { + return nil, err + } + if raw.GasUsed == nil { + return nil, nil // not found + } + r := &Receipt{ + GasUsed: uint64(*raw.GasUsed), + L1Fee: big.NewInt(0), + } + if raw.Status != nil { + r.Status = uint64(*raw.Status) + } + if raw.BlockNumber != nil { + r.BlockNumber = (*big.Int)(raw.BlockNumber).Uint64() + } + if raw.EffectiveGasPrice != nil { + r.EffectiveGasPrice = (*big.Int)(raw.EffectiveGasPrice) + } + if raw.L1Fee != nil { + r.L1Fee = (*big.Int)(raw.L1Fee) + } + return r, nil } // GetTransactionByHash returns a transaction by its hash. diff --git a/universalClient/chains/evm/tx_builder.go b/universalClient/chains/evm/tx_builder.go index f55873ff2..f5a0114ea 100644 --- a/universalClient/chains/evm/tx_builder.go +++ b/universalClient/chains/evm/tx_builder.go @@ -250,11 +250,11 @@ func (tb *TxBuilder) BroadcastOutboundSigningRequest( func (tb *TxBuilder) VerifyBroadcastedTx(ctx context.Context, txHash string) (found bool, blockHeight uint64, confirmations uint64, status uint8, err error) { hash := ethcommon.HexToHash(txHash) receipt, err := tb.rpcClient.GetTransactionReceipt(ctx, hash) - if err != nil { + if err != nil || receipt == nil { return false, 0, 0, 0, nil } - receiptBlock := receipt.BlockNumber.Uint64() + receiptBlock := receipt.BlockNumber var confs uint64 latestBlock, err := tb.rpcClient.GetLatestBlock(ctx) @@ -449,29 +449,29 @@ func (tb *TxBuilder) IsAlreadyExecuted(ctx context.Context, txID string) (bool, } -// GetGasFeeUsed returns the gas fee used by a transaction on the EVM chain. -// Fetches the receipt for gasUsed and the transaction for gasPrice, then returns -// gasUsed * gasPrice as a decimal string. Returns "0" if not found. +// GetGasFeeUsed returns the gas fee used by a transaction on the EVM chain: +// L2 execution (gasUsed * effectiveGasPrice) plus the OP-Stack L1 data fee +// (0 on non-OP chains). Errors when the fee cannot be determined so callers +// retry rather than record an under-reported fee. func (tb *TxBuilder) GetGasFeeUsed(ctx context.Context, txHash string) (string, error) { - hash := ethcommon.HexToHash(txHash) - receipt, err := tb.rpcClient.GetTransactionReceipt(ctx, hash) + receipt, err := tb.rpcClient.GetTransactionReceipt(ctx, ethcommon.HexToHash(txHash)) if err != nil { - return "0", nil + return "", fmt.Errorf("failed to fetch receipt for %s: %w", txHash, err) } - - tx, _, err := tb.rpcClient.GetTransactionByHash(ctx, hash) - if err != nil { - return "0", nil + if receipt == nil { + return "", fmt.Errorf("receipt not found for %s", txHash) } - - gasUsed := new(big.Int).SetUint64(receipt.GasUsed) - gasPrice := tx.GasPrice() - if gasPrice == nil || gasPrice.Sign() == 0 { - return "0", nil + if receipt.EffectiveGasPrice == nil { + return "", fmt.Errorf("receipt for %s missing effectiveGasPrice", txHash) } + return gasFeeUsed(receipt.GasUsed, receipt.EffectiveGasPrice, receipt.L1Fee).String(), nil +} - gasFeeUsed := new(big.Int).Mul(gasUsed, gasPrice) - return gasFeeUsed.String(), nil +// gasFeeUsed returns the full destination cost of an included tx: L2 execution +// (gasUsed * effectiveGasPrice) plus the OP-Stack L1 data fee. +func gasFeeUsed(gasUsed uint64, gasPrice, l1Fee *big.Int) *big.Int { + fee := new(big.Int).Mul(new(big.Int).SetUint64(gasUsed), gasPrice) + return fee.Add(fee, l1Fee) } // GetFundMigrationSigningRequest builds a native token transfer for fund migration, From 8af9cd299508dd4ff01a6073bb21d7e7bbf2f543 Mon Sep 17 00:00:00 2001 From: Aman Gupta Date: Tue, 18 Aug 2026 15:29:10 +0530 Subject: [PATCH 04/60] fix: F-2026-18190 | [Dual Defense] Obsolete TSS Keyshares Survive Quorum Change Without Deletion or Retirement (#305) * fix: delete keyshares superseded by quorum change or key refresh (F-2026-18190) * fix: resolve keyshare pubkeys per share instead of paging full key history (F-2026-18190) * refactor: rename keysharegc to keysharesweeper, sweep hourly (F-2026-18190) * refactor: move keyshare sweeper into keyshare package, guard Start with sync.Once (F-2026-18190) * refactor: sweep keyshares daily and run once at start (F-2026-18190) * test: cover GetKeyByID and guard nil query response (F-2026-18190) * refactor: drop redundant pending-process and fund-migration guards from keyshare sweeper (F-2026-18190) --- universalClient/pushcore/pushCore.go | 21 ++ universalClient/pushcore/pushCore_test.go | 75 ++++++- universalClient/tss/keyshare/manager.go | 53 +++++ universalClient/tss/keyshare/manager_test.go | 130 +++++++++++ universalClient/tss/keyshare/sweeper.go | 157 ++++++++++++++ universalClient/tss/keyshare/sweeper_test.go | 213 +++++++++++++++++++ universalClient/tss/tss.go | 10 + 7 files changed, 654 insertions(+), 5 deletions(-) create mode 100644 universalClient/tss/keyshare/sweeper.go create mode 100644 universalClient/tss/keyshare/sweeper_test.go diff --git a/universalClient/pushcore/pushCore.go b/universalClient/pushcore/pushCore.go index 647b548ea..ccf5aca04 100644 --- a/universalClient/pushcore/pushCore.go +++ b/universalClient/pushcore/pushCore.go @@ -211,6 +211,27 @@ func (c *Client) GetCurrentKey(ctx context.Context) (*utsstypes.TssKey, error) { ) } +// GetKeyByID retrieves a single TSS key from the on-chain key history. +// Returns an error if the key ID is not in the history. +func (c *Client) GetKeyByID(ctx context.Context, keyID string) (*utsstypes.TssKey, error) { + return retryWithRoundRobin( + len(c.utssClients), + &c.rr, + func(idx int) (*utsstypes.TssKey, error) { + resp, err := c.utssClients[idx].KeyById(ctx, &utsstypes.QueryKeyByIdRequest{KeyId: keyID}) + if err != nil { + return nil, err + } + if resp == nil || resp.Key == nil { + return nil, fmt.Errorf("pushcore: TSS key %s not found", keyID) + } + return resp.Key, nil + }, + "GetKeyByID", + c.logger, + ) +} + // GetGasPrice retrieves the median gas price for a specific chain from the on-chain oracle. func (c *Client) GetGasPrice(ctx context.Context, chainID string) (*big.Int, error) { if chainID == "" { diff --git a/universalClient/pushcore/pushCore_test.go b/universalClient/pushcore/pushCore_test.go index 323372b4e..e7b88e045 100644 --- a/universalClient/pushcore/pushCore_test.go +++ b/universalClient/pushcore/pushCore_test.go @@ -2,6 +2,7 @@ package pushcore import ( "context" + "errors" "math/big" "testing" @@ -965,10 +966,11 @@ func (m *mockUValidatorQueryClient) UniversalValidator(ctx context.Context, req type mockUTSSQueryClient struct { utsstypes.QueryClient - currentKeyResp *utsstypes.QueryCurrentKeyResponse - pendingTssEventsResp *utsstypes.QueryAllPendingTssEventsResponse - pendingFundMigrationsResp *utsstypes.QueryPendingFundMigrationsResponse - err error + currentKeyResp *utsstypes.QueryCurrentKeyResponse + keyByIdResp *utsstypes.QueryKeyByIdResponse + pendingTssEventsResp *utsstypes.QueryAllPendingTssEventsResponse + pendingFundMigrationsResp *utsstypes.QueryPendingFundMigrationsResponse + err error } func (m *mockUTSSQueryClient) CurrentKey(ctx context.Context, req *utsstypes.QueryCurrentKeyRequest, opts ...grpc.CallOption) (*utsstypes.QueryCurrentKeyResponse, error) { @@ -993,7 +995,10 @@ func (m *mockUTSSQueryClient) PendingFundMigrations(ctx context.Context, req *ut } func (m *mockUTSSQueryClient) KeyById(ctx context.Context, req *utsstypes.QueryKeyByIdRequest, opts ...grpc.CallOption) (*utsstypes.QueryKeyByIdResponse, error) { - return nil, nil + if m.err != nil { + return nil, m.err + } + return m.keyByIdResp, nil } type mockTxServiceClient struct { @@ -1084,3 +1089,63 @@ func (m *mockAuthAccountQueryClient) Account(ctx context.Context, req *authtypes } return m.accountResp, nil } + +func TestClient_GetKeyByID(t *testing.T) { + logger := zerolog.Nop() + + t.Run("no endpoints configured", func(t *testing.T) { + client := &Client{logger: logger, utssClients: []utsstypes.QueryClient{}} + + key, err := client.GetKeyByID(context.Background(), "key-123") + require.Error(t, err) + assert.Contains(t, err.Error(), "no endpoints configured") + assert.Nil(t, key) + }) + + t.Run("successful query returns key", func(t *testing.T) { + mockClient := &mockUTSSQueryClient{ + keyByIdResp: &utsstypes.QueryKeyByIdResponse{ + Key: &utsstypes.TssKey{KeyId: "key-123", TssPubkey: "0xpub"}, + }, + } + client := &Client{logger: logger, utssClients: []utsstypes.QueryClient{mockClient}} + + key, err := client.GetKeyByID(context.Background(), "key-123") + require.NoError(t, err) + require.NotNil(t, key) + assert.Equal(t, "key-123", key.KeyId) + assert.Equal(t, "0xpub", key.TssPubkey) + }) + + t.Run("unknown key id errors", func(t *testing.T) { + mockClient := &mockUTSSQueryClient{ + keyByIdResp: &utsstypes.QueryKeyByIdResponse{Key: nil}, + } + client := &Client{logger: logger, utssClients: []utsstypes.QueryClient{mockClient}} + + key, err := client.GetKeyByID(context.Background(), "missing") + require.Error(t, err) + assert.Contains(t, err.Error(), "not found") + assert.Nil(t, key) + }) + + // A nil response with a nil error must not panic. + t.Run("nil response errors", func(t *testing.T) { + mockClient := &mockUTSSQueryClient{keyByIdResp: nil} + client := &Client{logger: logger, utssClients: []utsstypes.QueryClient{mockClient}} + + key, err := client.GetKeyByID(context.Background(), "key-123") + require.Error(t, err) + assert.Contains(t, err.Error(), "not found") + assert.Nil(t, key) + }) + + t.Run("query error propagates", func(t *testing.T) { + mockClient := &mockUTSSQueryClient{err: errors.New("rpc down")} + client := &Client{logger: logger, utssClients: []utsstypes.QueryClient{mockClient}} + + key, err := client.GetKeyByID(context.Background(), "key-123") + require.Error(t, err) + assert.Nil(t, key) + }) +} diff --git a/universalClient/tss/keyshare/manager.go b/universalClient/tss/keyshare/manager.go index 0e5574261..0e599428e 100644 --- a/universalClient/tss/keyshare/manager.go +++ b/universalClient/tss/keyshare/manager.go @@ -144,6 +144,59 @@ func (m *Manager) Exists(id string) (bool, error) { return true, nil } +// List returns the IDs of all stored keyshares. +func (m *Manager) List() ([]string, error) { + entries, err := os.ReadDir(m.keysharesDir) + if err != nil { + if os.IsNotExist(err) { + return nil, nil + } + return nil, fmt.Errorf("failed to read keyshares directory: %w", err) + } + + ids := make([]string, 0, len(entries)) + for _, e := range entries { + if !e.IsDir() { + ids = append(ids, e.Name()) + } + } + return ids, nil +} + +// Delete removes a stored keyshare. It overwrites the file with random bytes +// before unlinking; on SSD/COW filesystems that is best-effort, so the real +// protection remains the at-rest encryption. Deleting a missing ID is a no-op. +func (m *Manager) Delete(id string) error { + if id == "" { + return ErrInvalidID + } + + if strings.Contains(id, "/") || strings.Contains(id, "\\") || strings.Contains(id, "..") { + return fmt.Errorf("%w: id contains invalid characters", ErrInvalidID) + } + + filePath := filepath.Join(m.keysharesDir, id) + info, err := os.Stat(filePath) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return fmt.Errorf("failed to stat keyshare file: %w", err) + } + + if info.Mode().IsRegular() && info.Size() > 0 { + scratch := make([]byte, info.Size()) + if _, rerr := rand.Read(scratch); rerr == nil { + _ = os.WriteFile(filePath, scratch, filePerms) + } + } + + if err := os.Remove(filePath); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("failed to remove keyshare file: %w", err) + } + return nil +} + // encrypt encrypts keyshare data using AES-256-GCM with a password-derived key. // Returns encrypted data in format: [salt(32) || nonce(12) || ciphertext || tag(16)] func (m *Manager) encrypt(keyshareData []byte) ([]byte, error) { diff --git a/universalClient/tss/keyshare/manager_test.go b/universalClient/tss/keyshare/manager_test.go index 348d0d3d7..f7c9c37af 100644 --- a/universalClient/tss/keyshare/manager_test.go +++ b/universalClient/tss/keyshare/manager_test.go @@ -516,3 +516,133 @@ func TestManager_EncryptDecrypt(t *testing.T) { } }) } + +func TestList(t *testing.T) { + t.Run("empty directory", func(t *testing.T) { + mgr, err := NewManager(t.TempDir(), "pw") + if err != nil { + t.Fatalf("NewManager() error = %v", err) + } + ids, err := mgr.List() + if err != nil { + t.Fatalf("List() error = %v", err) + } + if len(ids) != 0 { + t.Errorf("List() = %v, want empty", ids) + } + }) + + t.Run("returns stored ids", func(t *testing.T) { + mgr, err := NewManager(t.TempDir(), "pw") + if err != nil { + t.Fatalf("NewManager() error = %v", err) + } + for _, id := range []string{"key-a", "key-b"} { + if err := mgr.Store([]byte("share-"+id), id); err != nil { + t.Fatalf("Store(%s) error = %v", id, err) + } + } + ids, err := mgr.List() + if err != nil { + t.Fatalf("List() error = %v", err) + } + if len(ids) != 2 { + t.Fatalf("List() returned %d ids, want 2", len(ids)) + } + found := map[string]bool{} + for _, id := range ids { + found[id] = true + } + if !found["key-a"] || !found["key-b"] { + t.Errorf("List() = %v, want key-a and key-b", ids) + } + }) + + t.Run("ignores subdirectories", func(t *testing.T) { + tmpDir := t.TempDir() + mgr, err := NewManager(tmpDir, "pw") + if err != nil { + t.Fatalf("NewManager() error = %v", err) + } + if err := os.MkdirAll(filepath.Join(mgr.keysharesDir, "nested"), dirPerms); err != nil { + t.Fatalf("MkdirAll() error = %v", err) + } + ids, err := mgr.List() + if err != nil { + t.Fatalf("List() error = %v", err) + } + if len(ids) != 0 { + t.Errorf("List() = %v, want empty (dirs ignored)", ids) + } + }) +} + +func TestDelete(t *testing.T) { + t.Run("removes stored keyshare", func(t *testing.T) { + mgr, err := NewManager(t.TempDir(), "pw") + if err != nil { + t.Fatalf("NewManager() error = %v", err) + } + if err := mgr.Store([]byte("secret-share"), "key-1"); err != nil { + t.Fatalf("Store() error = %v", err) + } + if err := mgr.Delete("key-1"); err != nil { + t.Fatalf("Delete() error = %v", err) + } + if _, err := mgr.Get("key-1"); !errors.Is(err, ErrKeyshareNotFound) { + t.Errorf("Get() after Delete error = %v, want ErrKeyshareNotFound", err) + } + exists, err := mgr.Exists("key-1") + if err != nil { + t.Fatalf("Exists() error = %v", err) + } + if exists { + t.Error("Exists() = true after Delete, want false") + } + }) + + t.Run("missing id is a no-op", func(t *testing.T) { + mgr, err := NewManager(t.TempDir(), "pw") + if err != nil { + t.Fatalf("NewManager() error = %v", err) + } + if err := mgr.Delete("never-stored"); err != nil { + t.Errorf("Delete() on missing id error = %v, want nil", err) + } + }) + + t.Run("rejects invalid ids", func(t *testing.T) { + mgr, err := NewManager(t.TempDir(), "pw") + if err != nil { + t.Fatalf("NewManager() error = %v", err) + } + for _, id := range []string{"", "../escape", "sub/dir", "back\\slash"} { + if err := mgr.Delete(id); !errors.Is(err, ErrInvalidID) { + t.Errorf("Delete(%q) error = %v, want ErrInvalidID", id, err) + } + } + }) + + t.Run("leaves other keyshares intact", func(t *testing.T) { + mgr, err := NewManager(t.TempDir(), "pw") + if err != nil { + t.Fatalf("NewManager() error = %v", err) + } + if err := mgr.Store([]byte("share-a"), "key-a"); err != nil { + t.Fatalf("Store() error = %v", err) + } + if err := mgr.Store([]byte("share-b"), "key-b"); err != nil { + t.Fatalf("Store() error = %v", err) + } + if err := mgr.Delete("key-a"); err != nil { + t.Fatalf("Delete() error = %v", err) + } + got, err := mgr.Get("key-b") + if err != nil { + t.Fatalf("Get(key-b) error = %v", err) + } + if string(got) != "share-b" { + t.Errorf("Get(key-b) = %q, want %q", got, "share-b") + } + }) +} diff --git a/universalClient/tss/keyshare/sweeper.go b/universalClient/tss/keyshare/sweeper.go new file mode 100644 index 000000000..06e4c2e91 --- /dev/null +++ b/universalClient/tss/keyshare/sweeper.go @@ -0,0 +1,157 @@ +package keyshare + +import ( + "context" + "sync" + "time" + + "github.com/rs/zerolog" + + utsstypes "github.com/pushchain/push-chain-node/x/utss/types" +) + +// Quorum change and key refresh are rare, and a retained share is only a +// concern over the long run, so sweeping daily is ample. +const defaultCheckInterval = 24 * time.Hour + +// PushCoreClient is the subset of pushcore.Client the sweeper depends on. +// Defined as an interface so tests can inject a mock. *pushcore.Client satisfies it. +type PushCoreClient interface { + GetCurrentKey(ctx context.Context) (*utsstypes.TssKey, error) + GetKeyByID(ctx context.Context, keyID string) (*utsstypes.TssKey, error) +} + +// KeyshareStore is the subset of keyshare.Manager the sweeper depends on. +type KeyshareStore interface { + List() ([]string, error) + Delete(id string) error +} + +// Config holds configuration for the keyshare sweeper. +type Config struct { + Keyshares KeyshareStore + PushCore PushCoreClient + CheckInterval time.Duration + Logger zerolog.Logger +} + +// Sweeper deletes local keyshares that chain state proves are redundant. +// +// A keyshare is deleted only when every one of these holds: +// - it is not the current key ID; +// - its TSS pubkey equals the current key's pubkey, i.e. a quorum change or +// key refresh superseded it while preserving the vault key. +// +// Those two conditions are sufficient. The current key only changes when a key +// process finalizes, so while one is in flight the predecessor is still current +// and therefore never a deletion candidate. Fund migrations only exist across a +// pubkey rotation, so they can only reference a key this sweeper already keeps. +// +// Shares whose pubkey differs from the current one are kept: they belong to a +// rotated-away key that fund migration still needs to sweep its vault. Retiring +// those is an explicit operator action, since a chain that was never migrated is +// indistinguishable from one with nothing to migrate. +// +// Every chain-state lookup fails closed: on error the sweep is skipped and +// retried next tick rather than deleting on incomplete information. Pubkeys are +// resolved per held share rather than from the full key history, which grows +// unbounded and would need paging. +type Sweeper struct { + keyshares KeyshareStore + pushCore PushCoreClient + checkInterval time.Duration + logger zerolog.Logger + startOnce sync.Once +} + +// NewSweeper creates a new keyshare sweeper. +func NewSweeper(cfg Config) *Sweeper { + interval := cfg.CheckInterval + if interval == 0 { + interval = defaultCheckInterval + } + return &Sweeper{ + keyshares: cfg.Keyshares, + pushCore: cfg.PushCore, + checkInterval: interval, + logger: cfg.Logger.With().Str("component", "keyshare_sweeper").Logger(), + } +} + +// Start begins the background sweep loop. Repeat calls are no-ops, so a +// restarted node cannot end up with two sweepers deleting concurrently. +func (s *Sweeper) Start(ctx context.Context) { + s.startOnce.Do(func() { + go s.run(ctx) + }) +} + +func (s *Sweeper) run(ctx context.Context) { + ticker := time.NewTicker(s.checkInterval) + defer ticker.Stop() + + // Sweep on start: with a long interval, a node restarted more often than + // that would otherwise never sweep. + s.sweep(ctx) + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + s.sweep(ctx) + } + } +} + +func (s *Sweeper) sweep(ctx context.Context) { + if s.keyshares == nil || s.pushCore == nil { + return + } + + localIDs, err := s.keyshares.List() + if err != nil { + s.logger.Warn().Err(err).Msg("failed to list keyshares, skipping sweep") + return + } + if len(localIDs) <= 1 { + return + } + + current, err := s.pushCore.GetCurrentKey(ctx) + if err != nil { + s.logger.Debug().Err(err).Msg("failed to get current TSS key, skipping sweep") + return + } + if current == nil || current.KeyId == "" || current.TssPubkey == "" { + return + } + + deleted := 0 + for _, id := range localIDs { + if id == current.KeyId { + continue + } + // Look up only the shares we hold; the on-chain key history is unbounded. + // Any lookup failure (unknown ID or transport error) keeps the share. + key, err := s.pushCore.GetKeyByID(ctx, id) + if err != nil || key == nil { + s.logger.Debug().Err(err).Str("key_id", id).Msg("cannot resolve keyshare on chain, keeping") + continue + } + if key.TssPubkey != current.TssPubkey { + continue + } + if err := s.keyshares.Delete(id); err != nil { + s.logger.Warn().Err(err).Str("key_id", id).Msg("failed to delete superseded keyshare") + continue + } + deleted++ + s.logger.Info().Str("key_id", id).Str("current_key_id", current.KeyId). + Msg("deleted superseded keyshare") + } + + if deleted > 0 { + s.logger.Info().Int("deleted", deleted).Msg("keyshare sweep complete") + } +} diff --git a/universalClient/tss/keyshare/sweeper_test.go b/universalClient/tss/keyshare/sweeper_test.go new file mode 100644 index 000000000..d537812dc --- /dev/null +++ b/universalClient/tss/keyshare/sweeper_test.go @@ -0,0 +1,213 @@ +package keyshare + +import ( + "context" + "errors" + "sync" + "testing" + "time" + + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + utsstypes "github.com/pushchain/push-chain-node/x/utss/types" +) + +const ( + pubkeyA = "0xAAA" + pubkeyB = "0xBBB" +) + +type mockStore struct { + mu sync.Mutex + ids []string + deleted []string + listErr error + delErr error +} + +func (m *mockStore) List() ([]string, error) { + m.mu.Lock() + defer m.mu.Unlock() + if m.listErr != nil { + return nil, m.listErr + } + return append([]string(nil), m.ids...), nil +} + +// Delete drops the id so a repeat sweep cannot delete it twice, matching the +// real Manager. +func (m *mockStore) Delete(id string) error { + m.mu.Lock() + defer m.mu.Unlock() + if m.delErr != nil { + return m.delErr + } + remaining := m.ids[:0] + for _, existing := range m.ids { + if existing != id { + remaining = append(remaining, existing) + } + } + m.ids = remaining + m.deleted = append(m.deleted, id) + return nil +} + +func (m *mockStore) deletedIDs() []string { + m.mu.Lock() + defer m.mu.Unlock() + return append([]string(nil), m.deleted...) +} + +type mockCore struct { + current *utsstypes.TssKey + keys map[string]*utsstypes.TssKey + + currentErr, keysErr error +} + +func (m *mockCore) GetCurrentKey(context.Context) (*utsstypes.TssKey, error) { + return m.current, m.currentErr +} +func (m *mockCore) GetKeyByID(_ context.Context, keyID string) (*utsstypes.TssKey, error) { + if m.keysErr != nil { + return nil, m.keysErr + } + k, ok := m.keys[keyID] + if !ok { + return nil, errors.New("key not found") + } + return k, nil +} +func key(id, pubkey string) *utsstypes.TssKey { + return &utsstypes.TssKey{KeyId: id, TssPubkey: pubkey} +} + +// baseCore: K1 and K2 share pubkeyA (quorum change / refresh); K0 is a rotated +// away key on pubkeyB. K2 is current. +func baseCore() *mockCore { + return &mockCore{ + current: key("K2", pubkeyA), + keys: map[string]*utsstypes.TssKey{ + "K0": key("K0", pubkeyB), + "K1": key("K1", pubkeyA), + "K2": key("K2", pubkeyA), + }, + } +} + +func sweepWith(t *testing.T, store *mockStore, core *mockCore) *mockStore { + t.Helper() + NewSweeper(Config{Keyshares: store, PushCore: core, Logger: zerolog.Nop()}).sweep(context.Background()) + return store +} + +func TestSweep_DeletesSupersededSamePubkeyShare(t *testing.T) { + store := sweepWith(t, &mockStore{ids: []string{"K1", "K2"}}, baseCore()) + assert.Equal(t, []string{"K1"}, store.deleted) +} + +func TestSweep_KeepsCurrentKey(t *testing.T) { + store := sweepWith(t, &mockStore{ids: []string{"K1", "K2"}}, baseCore()) + assert.NotContains(t, store.deleted, "K2") +} + +// A rotated-away key (different pubkey) may still be needed to sign fund +// migration out of the retired vault, so it must survive. +func TestSweep_KeepsRotatedAwayPubkeyShare(t *testing.T) { + store := sweepWith(t, &mockStore{ids: []string{"K0", "K1", "K2"}}, baseCore()) + assert.NotContains(t, store.deleted, "K0") + assert.Equal(t, []string{"K1"}, store.deleted) +} + +// A share the chain doesn't know about is never deleted. +func TestSweep_KeepsUnknownKeyID(t *testing.T) { + store := sweepWith(t, &mockStore{ids: []string{"mystery", "K2"}}, baseCore()) + assert.Empty(t, store.deleted) +} + +func TestSweep_FailsClosedOnRPCError(t *testing.T) { + cases := map[string]func(*mockCore){ + "current key": func(c *mockCore) { c.currentErr = errors.New("boom") }, + "key lookup": func(c *mockCore) { c.keysErr = errors.New("boom") }, + } + for name, breakIt := range cases { + t.Run(name, func(t *testing.T) { + core := baseCore() + breakIt(core) + store := sweepWith(t, &mockStore{ids: []string{"K1", "K2"}}, core) + assert.Empty(t, store.deleted, "must not delete on incomplete chain state") + }) + } +} + +func TestSweep_NoopWhenSingleOrNoShare(t *testing.T) { + core := baseCore() + core.currentErr = errors.New("should not be called") + store := sweepWith(t, &mockStore{ids: []string{"K2"}}, core) + assert.Empty(t, store.deleted) +} + +func TestSweep_ContinuesAfterDeleteError(t *testing.T) { + store := &mockStore{ids: []string{"K1", "K2"}, delErr: errors.New("disk error")} + NewSweeper(Config{Keyshares: store, PushCore: baseCore(), Logger: zerolog.Nop()}). + sweep(context.Background()) + assert.Empty(t, store.deleted) +} + +func TestNewSweeper_DefaultInterval(t *testing.T) { + s := NewSweeper(Config{Keyshares: &mockStore{}, PushCore: baseCore(), Logger: zerolog.Nop()}) + require.Equal(t, defaultCheckInterval, s.checkInterval) +} + +// One unresolvable share must not block collection of the others; only the +// shares we hold are looked up, so the unbounded key history is never paged. +func TestSweep_StrayShareDoesNotBlockOthers(t *testing.T) { + store := sweepWith(t, &mockStore{ids: []string{"stray", "K1", "K2"}}, baseCore()) + assert.Equal(t, []string{"K1"}, store.deletedIDs()) +} + +// Start must be idempotent: a second call cannot spawn a concurrent sweeper. +func TestSweeper_StartIsIdempotent(t *testing.T) { + store := &mockStore{ids: []string{"K1", "K2"}} + s := NewSweeper(Config{ + Keyshares: store, + PushCore: baseCore(), + CheckInterval: 10 * time.Millisecond, + Logger: zerolog.Nop(), + }) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + for range 5 { + s.Start(ctx) + } + + // One loop deletes K1 exactly once; duplicates would retry the deleted id. + time.Sleep(60 * time.Millisecond) + cancel() + assert.Equal(t, []string{"K1"}, store.deletedIDs()) +} + +// The interval is long, so the first sweep must happen at start rather than +// after a full period — otherwise a frequently restarted node never sweeps. +func TestSweeper_SweepsOnStart(t *testing.T) { + store := &mockStore{ids: []string{"K1", "K2"}} + s := NewSweeper(Config{ + Keyshares: store, + PushCore: baseCore(), + CheckInterval: time.Hour, // far longer than the test waits + Logger: zerolog.Nop(), + }) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + s.Start(ctx) + + assert.Eventually(t, func() bool { + return len(store.deletedIDs()) == 1 + }, 2*time.Second, 10*time.Millisecond, "expected a sweep at start") + assert.Equal(t, []string{"K1"}, store.deletedIDs()) +} diff --git a/universalClient/tss/tss.go b/universalClient/tss/tss.go index f54ca0650..215f5dfb3 100644 --- a/universalClient/tss/tss.go +++ b/universalClient/tss/tss.go @@ -105,6 +105,7 @@ type Node struct { txBroadcaster *txbroadcaster.Broadcaster txResolver *txresolver.Resolver expirySweeper *expirysweeper.Sweeper + keyshareSweeper *keyshare.Sweeper // Network configuration (used during Start) networkCfg libp2pnet.Config @@ -269,6 +270,12 @@ func NewNode(ctx context.Context, cfg Config) (*Node, error) { Logger: logger, }) + node.keyshareSweeper = keyshare.NewSweeper(keyshare.Config{ + Keyshares: mgr, + PushCore: cfg.PushCore, + Logger: logger, + }) + return node, nil } @@ -371,6 +378,9 @@ func (n *Node) Start(ctx context.Context) error { // Start expiry sweeper (CONFIRMED past expiry → REVERTED) n.expirySweeper.Start(ctx) + // Start keyshare GC (delete shares superseded by quorum change / key refresh) + n.keyshareSweeper.Start(ctx) + n.logger.Info(). Str("peer_id", net.ID()). Strs("addrs", net.ListenAddrs()). From 49de1077b819fe824db99ad2cd150149d83fab58 Mon Sep 17 00:00:00 2001 From: Aman Gupta Date: Tue, 18 Aug 2026 16:01:36 +0530 Subject: [PATCH 05/60] fix: F-2026-18191 | [Dual Defense] Byzantine TSS Coordinator Can Freeze Outbound Withdrawals via Gap Nonce (#306) * fix: bound coordinator-assigned nonce above finalized to block gap-nonce freeze (F-2026-18191) * fix: anchor nonce ceiling to pending nonce so congestion cannot false-reject (F-2026-18191) * test: cover nonceBounds pending fallback branches (F-2026-18191) --- .../tss/sessionmanager/sessionmanager.go | 66 +++++++++-- .../tss/sessionmanager/sessionmanager_test.go | 110 ++++++++++++++++++ 2 files changed, 164 insertions(+), 12 deletions(-) diff --git a/universalClient/tss/sessionmanager/sessionmanager.go b/universalClient/tss/sessionmanager/sessionmanager.go index 8257d7a0b..c48162260 100644 --- a/universalClient/tss/sessionmanager/sessionmanager.go +++ b/universalClient/tss/sessionmanager/sessionmanager.go @@ -940,16 +940,15 @@ func (sm *SessionManager) verifyOutboundSigningRequest(ctx context.Context, even return nil } - // Guard against stale / replayed nonces: reject if coordinator's nonce is below the - // last finalized nonce on chain (i.e. that nonce has already been committed). + // Bound the coordinator's nonce on both sides: below finalized it is already + // committed, and far above it would never mine, freezing the outbound. // We only hard-reject on a definitive answer — warn and skip if we can't determine it. if tssAddr, addrErr := sm.getTSSAddress(ctx); addrErr != nil { sm.logger.Warn().Err(addrErr).Str("chain", chainID).Msg("cannot get TSS address for nonce check, skipping") - } else if finalizedNonce, nonceErr := builder.GetNextNonce(ctx, tssAddr, true /* useFinalized */); nonceErr != nil { + } else if finalizedNonce, ceilingBase, nonceErr := nonceBounds(ctx, builder, tssAddr); nonceErr != nil { sm.logger.Warn().Err(nonceErr).Str("chain", chainID).Msg("cannot get finalized nonce for check, skipping") - } else if req.Nonce < finalizedNonce { - return fmt.Errorf("coordinator assigned nonce %d is below chain finalized nonce %d for %s — nonce already used on chain", - req.Nonce, finalizedNonce, chainID) + } else if err := checkNonceInRange(req.Nonce, finalizedNonce, ceilingBase, chainID); err != nil { + return err } // Use coordinator's nonce so our computed hash matches @@ -977,6 +976,51 @@ func (sm *SessionManager) verifyOutboundSigningRequest(ctx context.Context, even return nil } +// maxNonceGap bounds how far above the ceiling base a coordinator may assign. +// An honest coordinator starts at the pending nonce and increments at most +// coordinator.PerChainCap times per poll, so pending+PerChainCap is the true +// ceiling; 2x absorbs nonce skew between our RPC view and the coordinator's. +// +// The base is the pending nonce, not the finalized one: a BROADCASTED event no +// longer counts toward the in-flight cap but still holds a nonce in the +// mempool, so pending-minus-finalized grows while a chain is congested. Anchored +// to finalized, any fixed gap would eventually reject honest coordinators. +const maxNonceGap = 2 * coordinator.PerChainCap + +// checkNonceInRange rejects a coordinator-assigned nonce that is already +// committed on chain, or so far ahead it would never mine — which would freeze +// the outbound with its PRC20 already burned at the gateway. +// ceilingBase is the pending nonce where available, else the finalized nonce. +func checkNonceInRange(assigned, finalized, ceilingBase uint64, target string) error { + if assigned < finalized { + return fmt.Errorf("coordinator assigned nonce %d is below chain finalized nonce %d for %s — nonce already used on chain", + assigned, finalized, target) + } + if ceilingBase < finalized { + ceilingBase = finalized + } + if assigned > ceilingBase+maxNonceGap { + return fmt.Errorf("coordinator assigned nonce %d exceeds nonce %d by more than %d for %s — gap nonce would never mine", + assigned, ceilingBase, maxNonceGap, target) + } + return nil +} + +// nonceBounds returns the finalized nonce and the ceiling base for signer. +// A failed pending lookup falls back to the finalized nonce, which is stricter +// but never wrong; a failed finalized lookup is reported so the caller skips. +func nonceBounds(ctx context.Context, builder common.TxBuilder, signer string) (finalized, ceilingBase uint64, err error) { + finalized, err = builder.GetNextNonce(ctx, signer, true /* useFinalized */) + if err != nil { + return 0, 0, err + } + pending, pErr := builder.GetNextNonce(ctx, signer, false /* pending */) + if pErr != nil || pending < finalized { + return finalized, finalized, nil + } + return finalized, pending, nil +} + // verifyFundMigrationSigningRequest validates the coordinator's fund migration signing request. // It independently rebuilds the signing hash from the event data and compares it with the coordinator's hash. func (sm *SessionManager) verifyFundMigrationSigningRequest(ctx context.Context, event *store.Event, req *common.UnsignedSigningReq) error { @@ -1020,13 +1064,11 @@ func (sm *SessionManager) verifyFundMigrationSigningRequest(ctx context.Context, return nil } - // Guard against stale / replayed nonces: reject if coordinator's nonce is below the - // last finalized nonce on chain for the old TSS address. - if finalizedNonce, nonceErr := builder.GetNextNonce(ctx, oldTSSAddr, true /* useFinalized */); nonceErr != nil { + // Bound the coordinator's nonce on both sides for the old TSS address. + if finalizedNonce, ceilingBase, nonceErr := nonceBounds(ctx, builder, oldTSSAddr); nonceErr != nil { sm.logger.Warn().Err(nonceErr).Str("chain", migrationData.Chain).Msg("cannot get finalized nonce for old TSS, skipping nonce check") - } else if req.Nonce < finalizedNonce { - return fmt.Errorf("coordinator assigned nonce %d is below chain finalized nonce %d for old TSS %s — nonce already used on chain", - req.Nonce, finalizedNonce, oldTSSAddr) + } else if err := checkNonceInRange(req.Nonce, finalizedNonce, ceilingBase, oldTSSAddr); err != nil { + return err } // Rebuild fund migration signing request with coordinator's nonce. diff --git a/universalClient/tss/sessionmanager/sessionmanager_test.go b/universalClient/tss/sessionmanager/sessionmanager_test.go index 0e3d74ba9..0a76ab6d8 100644 --- a/universalClient/tss/sessionmanager/sessionmanager_test.go +++ b/universalClient/tss/sessionmanager/sessionmanager_test.go @@ -5,6 +5,7 @@ import ( "context" "encoding/hex" "encoding/json" + "errors" "fmt" "math/big" "reflect" @@ -1343,3 +1344,112 @@ func TestExtractSignedDataFromEvent_CorruptDataIsObservable(t *testing.T) { assert.Equal(t, uint64(42), signed.Nonce) }) } + +// A coordinator-assigned nonce must sit within [finalized, ceilingBase+maxNonceGap]. +// Below is already committed; far above never mines and freezes the outbound +// with its PRC20 already burned at the gateway. +func TestCheckNonceInRange(t *testing.T) { + const finalized = uint64(100) + + t.Run("equal to finalized is accepted", func(t *testing.T) { + require.NoError(t, checkNonceInRange(finalized, finalized, finalized, "eip155:1")) + }) + + t.Run("within the cap above pending is accepted", func(t *testing.T) { + require.NoError(t, checkNonceInRange(finalized+coordinator.PerChainCap, finalized, finalized, "eip155:1")) + }) + + t.Run("exactly at the gap limit is accepted", func(t *testing.T) { + require.NoError(t, checkNonceInRange(finalized+maxNonceGap, finalized, finalized, "eip155:1")) + }) + + // While a chain is congested, BROADCASTED events free the in-flight cap but + // still hold mempool nonces, so pending runs far ahead of finalized. Honest + // coordinators assign from pending and must not be rejected. + t.Run("congestion: nonce far above finalized but near pending is accepted", func(t *testing.T) { + pending := finalized + 500 + require.NoError(t, checkNonceInRange(pending+coordinator.PerChainCap, finalized, pending, "eip155:1")) + }) + + t.Run("below finalized is rejected", func(t *testing.T) { + err := checkNonceInRange(finalized-1, finalized, finalized, "eip155:1") + require.Error(t, err) + assert.Contains(t, err.Error(), "already used on chain") + }) + + t.Run("one past the gap limit is rejected", func(t *testing.T) { + err := checkNonceInRange(finalized+maxNonceGap+1, finalized, finalized, "eip155:1") + require.Error(t, err) + assert.Contains(t, err.Error(), "would never mine") + }) + + t.Run("far-future gap nonce is rejected even when congested", func(t *testing.T) { + pending := finalized + 500 + err := checkNonceInRange(finalized+(1<<32), finalized, pending, "eip155:1") + require.Error(t, err) + assert.Contains(t, err.Error(), "would never mine") + }) + + // A stale pending lookup must never widen the window below finalized. + t.Run("ceiling base below finalized falls back to finalized", func(t *testing.T) { + err := checkNonceInRange(finalized+maxNonceGap+1, finalized, finalized-50, "eip155:1") + require.Error(t, err) + assert.Contains(t, err.Error(), "would never mine") + }) + + // SVM reports 0 from GetNextNonce and signs nonce 0; it must not be rejected. + t.Run("zero nonce on a nonce-less chain is accepted", func(t *testing.T) { + require.NoError(t, checkNonceInRange(0, 0, 0, "solana:devnet")) + }) +} + +// nonceBuilder is a partial TxBuilder: only GetNextNonce is implemented, so any +// other call panics loudly rather than silently returning a zero value. +type nonceBuilder struct { + common.TxBuilder + finalized, pending uint64 + finalizedErr, pendingErr error +} + +func (b *nonceBuilder) GetNextNonce(_ context.Context, _ string, useFinalized bool) (uint64, error) { + if useFinalized { + return b.finalized, b.finalizedErr + } + return b.pending, b.pendingErr +} + +func TestNonceBounds(t *testing.T) { + ctx := context.Background() + + t.Run("pending above finalized becomes the ceiling base", func(t *testing.T) { + fin, base, err := nonceBounds(ctx, &nonceBuilder{finalized: 100, pending: 140}, "0xtss") + require.NoError(t, err) + assert.Equal(t, uint64(100), fin) + assert.Equal(t, uint64(140), base) + }) + + // Falling back to finalized is stricter, never wrong. + t.Run("pending lookup failure falls back to finalized", func(t *testing.T) { + fin, base, err := nonceBounds(ctx, &nonceBuilder{ + finalized: 100, + pendingErr: errors.New("rpc down"), + }, "0xtss") + require.NoError(t, err) + assert.Equal(t, uint64(100), fin) + assert.Equal(t, uint64(100), base) + }) + + // A stale pending read must never lower the ceiling below finalized. + t.Run("pending below finalized falls back to finalized", func(t *testing.T) { + fin, base, err := nonceBounds(ctx, &nonceBuilder{finalized: 100, pending: 60}, "0xtss") + require.NoError(t, err) + assert.Equal(t, uint64(100), fin) + assert.Equal(t, uint64(100), base) + }) + + // Callers skip the nonce check entirely when finalized is unavailable. + t.Run("finalized lookup failure errors", func(t *testing.T) { + _, _, err := nonceBounds(ctx, &nonceBuilder{finalizedErr: errors.New("rpc down")}, "0xtss") + require.Error(t, err) + }) +} From 6ae6dcbcb5112c6c472214222ce461ae6e3dc2c4 Mon Sep 17 00:00:00 2001 From: Aman Gupta Date: Wed, 19 Aug 2026 15:58:33 +0530 Subject: [PATCH 06/60] fix: F-2026-18199 | [Dual Defense] TSS Setup Message Signs an Unverified Hash (Payload vs SigningHash Split) (#309) * fix: bind verified signing hash to the DKLS setup message before ACK (F-2026-18199) * fix: bind setup participants for keygen, keyrefresh and quorumchange too (F-2026-18199) * refactor: consolidate setup binding into one entry point and one helper file (F-2026-18199) * test: move setup decoder tests to utils_test and cover both symmetrically (F-2026-18199) * test: end-to-end proof that a mismatched payload hash refuses session and emits no shares (F-2026-18199) * fix: bind setup threshold by parsing the setup TLV, closing the downgrade gap (F-2026-18199) --- universalClient/tss/dkls/utils.go | 84 +++++++ universalClient/tss/dkls/utils_test.go | 162 +++++++++++++ .../tss/sessionmanager/sessionmanager.go | 86 +++++++ .../tss/sessionmanager/sessionmanager_test.go | 226 +++++++++++++++++- 4 files changed, 551 insertions(+), 7 deletions(-) diff --git a/universalClient/tss/dkls/utils.go b/universalClient/tss/dkls/utils.go index c77c62415..4a42b8137 100644 --- a/universalClient/tss/dkls/utils.go +++ b/universalClient/tss/dkls/utils.go @@ -2,6 +2,10 @@ package dkls import ( "crypto/sha256" + "encoding/binary" + "fmt" + + session "go-wrapper/go-dkls/sessions" ) // deriveKeyID derives a key ID bytes from a string key ID. @@ -22,3 +26,83 @@ func encodeParticipantIDs(participants []string) []byte { } return ids } + +// --- Setup decoding ------------------------------------------------------- +// The coordinator supplies the setup blob and the values a follower validates +// separately. DKLS runs on the blob, so these expose what it actually contains +// and let callers bind the two. Both return an error on a malformed blob. + +// SetupMessageHash returns the message hash embedded in a sign setup blob. +// DklsSignSessionFromSetup signs over the setup, not over any hash passed +// alongside it, so callers must confirm the two agree. +func SetupMessageHash(setupData []byte) ([]byte, error) { + if len(setupData) == 0 { + return nil, fmt.Errorf("setupData is required") + } + return session.DklsDecodeMessage(setupData) +} + +// SetupParticipants returns the participant list embedded in a DKLS setup blob, +// in index order. The setup is what actually drives the session, so callers must +// confirm it matches the participants they validated. Otherwise a coordinator +// can present one list for validation and run the session over another. +// +// Party names decode by index and come back empty past the end, which is how the +// list terminates. +func SetupParticipants(setupData []byte) ([]string, error) { + if len(setupData) == 0 { + return nil, fmt.Errorf("setupData is required") + } + var participants []string + for i := 0; ; i++ { + name, err := session.DklsDecodePartyName(setupData, i) + if err != nil { + return nil, fmt.Errorf("failed to decode party name at index %d: %w", i, err) + } + if len(name) == 0 { + break + } + participants = append(participants, string(name)) + } + return participants, nil +} + +// Setup blobs are a tag-length-value list after a fixed header. The wrapper +// exposes decoders for the key ID, message and party names but not the +// threshold, so that one is read here. Values are laid out as: +// +// tag uint16 little endian +// length uint16 little endian, stored as length-1 +// value length bytes +// +// The header is MESSAGE_ID_SIZE(32) + 2 + 2. This mirrors the library's internal +// encoding, so TestSetupThreshold pins it: if the format changes, that test +// fails rather than this silently reading the wrong byte. +const ( + setupHeaderSize = 36 + setupTagThreshold = 1 +) + +// SetupThreshold returns the threshold embedded in a keygen, keyrefresh or +// quorumchange setup blob. Sign setups carry no threshold and return an error. +func SetupThreshold(setupData []byte) (int, error) { + if len(setupData) < setupHeaderSize { + return 0, fmt.Errorf("setup message too short to contain a threshold") + } + for offset := setupHeaderSize; offset+4 <= len(setupData); { + tag := binary.LittleEndian.Uint16(setupData[offset : offset+2]) + length := int(binary.LittleEndian.Uint16(setupData[offset+2:offset+4])) + 1 + valueStart := offset + 4 + if valueStart+length > len(setupData) { + return 0, fmt.Errorf("setup message is malformed: tag %d claims %d bytes past the end", tag, length) + } + if tag == setupTagThreshold { + if length != 1 { + return 0, fmt.Errorf("threshold tag has unexpected length %d", length) + } + return int(setupData[valueStart]), nil + } + offset = valueStart + length + } + return 0, fmt.Errorf("setup message carries no threshold") +} diff --git a/universalClient/tss/dkls/utils_test.go b/universalClient/tss/dkls/utils_test.go index df57610e8..29f432859 100644 --- a/universalClient/tss/dkls/utils_test.go +++ b/universalClient/tss/dkls/utils_test.go @@ -1,8 +1,11 @@ package dkls import ( + "bytes" "crypto/sha256" "testing" + + session "go-wrapper/go-dkls/sessions" ) func TestDeriveKeyID(t *testing.T) { @@ -58,3 +61,162 @@ func TestEncodeParticipantIDs(t *testing.T) { }) } } + +// The setup blob is what DKLS actually runs on, so these decoders are what let a +// follower bind it to the values it validated separately. Both must report what +// the blob really contains, and must error rather than guess on a malformed one. + +func TestSetupMessageHash(t *testing.T) { + participantIDs := encodeParticipantIDs([]string{"party1", "party2"}) + keyID := make([]byte, 32) + + legitHash := make([]byte, 32) + copy(legitHash, "legitimate-outbound-hash-32bytes") + attackerHash := make([]byte, 32) + copy(attackerHash, "attacker-chosen-vault-call-digest") + + t.Run("returns the hash embedded in the setup", func(t *testing.T) { + setup, err := session.DklsSignSetupMsgNew(keyID, nil, legitHash, participantIDs) + if err != nil { + t.Fatalf("failed to build sign setup: %v", err) + } + got, err := SetupMessageHash(setup) + if err != nil { + t.Fatalf("SetupMessageHash() error = %v", err) + } + if !bytes.Equal(got, legitHash) { + t.Errorf("SetupMessageHash() = %x, want %x", got, legitHash) + } + }) + + // A substituted setup must report the hash it really signs, which is what + // makes the mismatch detectable. + t.Run("substituted setup reports the attacker hash", func(t *testing.T) { + setup, err := session.DklsSignSetupMsgNew(keyID, nil, attackerHash, participantIDs) + if err != nil { + t.Fatalf("failed to build sign setup: %v", err) + } + got, err := SetupMessageHash(setup) + if err != nil { + t.Fatalf("SetupMessageHash() error = %v", err) + } + if bytes.Equal(got, legitHash) { + t.Fatal("substituted setup must not report the legitimate hash") + } + if !bytes.Equal(got, attackerHash) { + t.Errorf("SetupMessageHash() = %x, want %x", got, attackerHash) + } + }) + + t.Run("errors on empty and malformed setup", func(t *testing.T) { + if _, err := SetupMessageHash(nil); err == nil { + t.Error("SetupMessageHash(nil) should error") + } + if _, err := SetupMessageHash([]byte("not-a-dkls-setup")); err == nil { + t.Error("SetupMessageHash(malformed) should error") + } + }) +} + +func TestSetupParticipants(t *testing.T) { + t.Run("returns the participants in index order", func(t *testing.T) { + want := []string{"alice", "bob", "carol"} + setup, err := session.DklsKeygenSetupMsgNew(2, nil, encodeParticipantIDs(want)) + if err != nil { + t.Fatalf("failed to build keygen setup: %v", err) + } + got, err := SetupParticipants(setup) + if err != nil { + t.Fatalf("SetupParticipants() error = %v", err) + } + if len(got) != len(want) { + t.Fatalf("SetupParticipants() = %v, want %v", got, want) + } + for i := range want { + if got[i] != want[i] { + t.Errorf("participant %d = %q, want %q", i, got[i], want[i]) + } + } + }) + + // Enumeration terminates on the first empty name rather than an error, which + // is the contract this relies on to find the end of the list. + t.Run("terminates at the end of a two party list", func(t *testing.T) { + want := []string{"first", "second"} + setup, err := session.DklsKeygenSetupMsgNew(2, nil, encodeParticipantIDs(want)) + if err != nil { + t.Fatalf("failed to build keygen setup: %v", err) + } + got, err := SetupParticipants(setup) + if err != nil { + t.Fatalf("SetupParticipants() error = %v", err) + } + if len(got) != 2 || got[0] != "first" || got[1] != "second" { + t.Errorf("SetupParticipants() = %v, want %v", got, want) + } + }) + + t.Run("errors on empty and malformed setup", func(t *testing.T) { + if _, err := SetupParticipants(nil); err == nil { + t.Error("SetupParticipants(nil) should error") + } + if _, err := SetupParticipants([]byte("not-a-dkls-setup")); err == nil { + t.Error("SetupParticipants(malformed) should error") + } + }) +} + +// Pins the setup TLV layout this package parses directly. If the library +// changes its encoding, this fails loudly instead of SetupThreshold silently +// reading the wrong byte. +func TestSetupThreshold(t *testing.T) { + participants := []string{"alice", "bob", "carol"} + + t.Run("reads the embedded keygen threshold", func(t *testing.T) { + for _, want := range []int{2, 3} { + setup, err := session.DklsKeygenSetupMsgNew(want, nil, encodeParticipantIDs(participants)) + if err != nil { + t.Fatalf("failed to build keygen setup with threshold %d: %v", want, err) + } + got, err := SetupThreshold(setup) + if err != nil { + t.Fatalf("SetupThreshold() error = %v", err) + } + if got != want { + t.Errorf("SetupThreshold() = %d, want %d", got, want) + } + } + }) + + // A downgraded setup must report the weaker threshold it really carries, + // which is what makes the mismatch detectable. + t.Run("downgraded setup reports the weaker threshold", func(t *testing.T) { + setup, err := session.DklsKeygenSetupMsgNew(2, nil, encodeParticipantIDs(participants)) + if err != nil { + t.Fatalf("failed to build keygen setup: %v", err) + } + got, err := SetupThreshold(setup) + if err != nil { + t.Fatalf("SetupThreshold() error = %v", err) + } + if got == 3 { + t.Fatal("downgraded setup must not report the expected threshold") + } + if got != 2 { + t.Errorf("SetupThreshold() = %d, want 2", got) + } + }) + + t.Run("errors on short, malformed and thresholdless setups", func(t *testing.T) { + if _, err := SetupThreshold(nil); err == nil { + t.Error("SetupThreshold(nil) should error") + } + if _, err := SetupThreshold([]byte("too-short")); err == nil { + t.Error("SetupThreshold(short) should error") + } + // Header present but no tags at all. + if _, err := SetupThreshold(make([]byte, setupHeaderSize)); err == nil { + t.Error("SetupThreshold(no tags) should error") + } + }) +} diff --git a/universalClient/tss/sessionmanager/sessionmanager.go b/universalClient/tss/sessionmanager/sessionmanager.go index c48162260..47db05ec9 100644 --- a/universalClient/tss/sessionmanager/sessionmanager.go +++ b/universalClient/tss/sessionmanager/sessionmanager.go @@ -8,6 +8,7 @@ import ( "encoding/json" "fmt" "math/big" + "slices" "sync" "time" @@ -196,6 +197,18 @@ func (sm *SessionManager) handleSetupMessage(ctx context.Context, senderPeerID s } } + // 6c. Everything validated above came from message fields, but the DKLS + // session runs on msg.Payload, and the two arrive unbound. Require the setup + // blob to carry exactly what we approved, before the ACK, so no shares are + // ever produced for a setup we did not verify. + if err := verifySetupMatchesValidated(msg, event.Type); err != nil { + sm.logger.Error().Err(err). + Str("event_id", msg.EventID). + Str("coordinator", senderPeerID). + Msg("setup message does not match the validated request - rejecting") + return err + } + // 7. Create session based on protocol type session, err := sm.createSession(ctx, event, msg) if err != nil { @@ -976,6 +989,79 @@ func (sm *SessionManager) verifyOutboundSigningRequest(ctx context.Context, even return nil } +// verifySetupMatchesValidated requires the coordinator's DKLS setup blob to +// carry exactly the values the follower validated from the message fields. +// DKLS runs on the blob, so without this the validated values are decorative: +// a coordinator can present legitimate ones for checking and embed different +// ones in Payload. +// +// Participants are checked for every protocol. Sign types additionally bind the +// signing hash; key-lifecycle types additionally bind the threshold, which the +// session constructors accept but ignore, so the embedded value is what the +// protocol actually runs with. +func verifySetupMatchesValidated(msg *coordinator.Message, eventType string) error { + if err := setupBindsParticipants(msg.Payload, msg.Participants); err != nil { + return err + } + if eventType == store.EventTypeSignOutbound || eventType == store.EventTypeSignFundMigrate { + if msg.UnsignedSigningReq == nil { + return fmt.Errorf("sign setup has no signing request to bind against") + } + return setupBindsHash(msg.Payload, msg.UnsignedSigningReq.SigningHash) + } + return setupBindsThreshold(msg.Payload, msg.Participants) +} + +// setupBindsThreshold requires the setup blob to embed the threshold the +// follower derives from the validated participants. Without it a coordinator can +// embed a lower one and elicit help producing a weaker key than was agreed. +func setupBindsThreshold(setupData []byte, validated []string) error { + expected := coordinator.CalculateThreshold(len(validated)) + embedded, err := dkls.SetupThreshold(setupData) + if err != nil { + return fmt.Errorf("cannot decode setup message threshold: %w", err) + } + if embedded != expected { + return fmt.Errorf("setup message threshold %d does not match expected %d for %d participants", + embedded, expected, len(validated)) + } + return nil +} + +// setupBindsHash requires the setup blob to embed exactly the verified hash. +func setupBindsHash(setupData, verifiedHash []byte) error { + if len(verifiedHash) == 0 { + return fmt.Errorf("no verified signing hash to bind setup message to") + } + embedded, err := dkls.SetupMessageHash(setupData) + if err != nil { + return fmt.Errorf("cannot decode setup message to check signing hash: %w", err) + } + if !bytes.Equal(embedded, verifiedHash) { + return fmt.Errorf("setup message signs hash %s but verified hash is %s", + hex.EncodeToString(embedded), hex.EncodeToString(verifiedHash)) + } + return nil +} + +// setupBindsParticipants requires the setup blob to embed exactly the validated +// participants, in the same order. Index order is part of the protocol, so a +// reorder is as consequential as a substitution. +func setupBindsParticipants(setupData []byte, validated []string) error { + if len(validated) == 0 { + return fmt.Errorf("no validated participants to bind setup message to") + } + embedded, err := dkls.SetupParticipants(setupData) + if err != nil { + return fmt.Errorf("cannot decode setup message participants: %w", err) + } + if !slices.Equal(embedded, validated) { + return fmt.Errorf("setup message participants %v do not match validated participants %v", + embedded, validated) + } + return nil +} + // maxNonceGap bounds how far above the ceiling base a coordinator may assign. // An honest coordinator starts at the pending nonce and increments at most // coordinator.PerChainCap times per poll, so pending+PerChainCap is the true diff --git a/universalClient/tss/sessionmanager/sessionmanager_test.go b/universalClient/tss/sessionmanager/sessionmanager_test.go index 0a76ab6d8..73f0dd2a9 100644 --- a/universalClient/tss/sessionmanager/sessionmanager_test.go +++ b/universalClient/tss/sessionmanager/sessionmanager_test.go @@ -9,10 +9,13 @@ import ( "fmt" "math/big" "reflect" + "strings" "testing" "time" "unsafe" + session "go-wrapper/go-dkls/sessions" + "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -467,18 +470,17 @@ func TestSessionManager_Integration(t *testing.T) { Type: "setup", EventID: event.EventID, Participants: []string{"validator1", "validator2", "validator3"}, - Payload: []byte("invalid setup data"), // Will fail when creating session + Payload: []byte("invalid setup data"), // rejected before a session is created } - // This will fail at session creation or GetLatestBlockNum, but validation should pass + // Rejected at the setup-binding check (the payload is not a decodable DKLS + // setup), or earlier at GetLatestBlockNum. Either way validation must not + // let an unbound payload reach session creation. err := sm.HandleIncomingMessage(ctx, "peer1", &msg) - // We expect an error because we can't create a real DKLS session with invalid data - // or because GetLatestBlockNum fails assert.Error(t, err) - // Error should be about session creation, DKLS library, or no endpoints assert.True(t, - containsAny(err.Error(), []string{"failed to create session", "DKLS", "dkls", "session", "no endpoints"}), - "error should be about session creation or endpoints, got: %s", err.Error()) + containsAny(err.Error(), []string{"failed to create session", "DKLS", "dkls", "session", "setup message", "no endpoints"}), + "error should be about setup binding, session creation or endpoints, got: %s", err.Error()) } func TestVerifySigningRequest_OutboundDisabled(t *testing.T) { @@ -1453,3 +1455,213 @@ func TestNonceBounds(t *testing.T) { require.Error(t, err) }) } + +// A follower verifies UnsignedSigningReq.SigningHash, but DKLS signs the hash +// embedded in Message.Payload, and the two arrive unbound. Without this check a +// coordinator can present a legitimate hash for verification and embed an +// attacker-chosen one in the setup, harvesting honest shares over it. +func TestSetupBindsHash(t *testing.T) { + participantIDs := []byte("party1\x00party2") + keyID := make([]byte, 32) + + legitHash := make([]byte, 32) + copy(legitHash, "legitimate-outbound-hash-32bytes") + attackerHash := make([]byte, 32) + copy(attackerHash, "attacker-chosen-vault-call-digest") + + legitSetup, err := session.DklsSignSetupMsgNew(keyID, nil, legitHash, participantIDs) + require.NoError(t, err) + attackerSetup, err := session.DklsSignSetupMsgNew(keyID, nil, attackerHash, participantIDs) + require.NoError(t, err) + + t.Run("accepts setup that signs the verified hash", func(t *testing.T) { + require.NoError(t, setupBindsHash(legitSetup, legitHash)) + }) + + // The reported attack. + t.Run("rejects setup embedding a different hash", func(t *testing.T) { + err := setupBindsHash(attackerSetup, legitHash) + require.Error(t, err) + assert.Contains(t, err.Error(), "setup message signs hash") + }) + + t.Run("rejects undecodable setup", func(t *testing.T) { + require.Error(t, setupBindsHash([]byte("not-a-dkls-setup"), legitHash)) + require.Error(t, setupBindsHash(nil, legitHash)) + }) + + t.Run("rejects missing verified hash", func(t *testing.T) { + err := setupBindsHash(legitSetup, nil) + require.Error(t, err) + assert.Contains(t, err.Error(), "no verified signing hash") + }) +} + +// Keygen, keyrefresh and quorumchange have the same split as the sign path: we +// validate msg.Participants, but the session runs on the list embedded in +// Payload. The threshold cannot be bound this way, see verifySetupBindsParticipants. +func TestSetupBindsParticipants(t *testing.T) { + validated := []string{"validator1", "validator2", "validator3"} + encode := func(ids []string) []byte { + return []byte(strings.Join(ids, "\x00")) + } + + legitSetup, err := session.DklsKeygenSetupMsgNew(2, nil, encode(validated)) + require.NoError(t, err) + + t.Run("accepts setup with the validated participants", func(t *testing.T) { + require.NoError(t, setupBindsParticipants(legitSetup, validated)) + }) + + t.Run("rejects setup with a substituted participant", func(t *testing.T) { + swapped, err := session.DklsKeygenSetupMsgNew(2, nil, + encode([]string{"validator1", "validator2", "attacker"})) + require.NoError(t, err) + err = setupBindsParticipants(swapped, validated) + require.Error(t, err) + assert.Contains(t, err.Error(), "do not match validated participants") + }) + + t.Run("rejects setup with a dropped participant", func(t *testing.T) { + fewer, err := session.DklsKeygenSetupMsgNew(2, nil, + encode([]string{"validator1", "validator2"})) + require.NoError(t, err) + require.Error(t, setupBindsParticipants(fewer, validated)) + }) + + // Index order is part of the protocol, so a reorder is as consequential as + // a substitution. + t.Run("rejects reordered participants", func(t *testing.T) { + reordered, err := session.DklsKeygenSetupMsgNew(2, nil, + encode([]string{"validator3", "validator2", "validator1"})) + require.NoError(t, err) + require.Error(t, setupBindsParticipants(reordered, validated)) + }) + + t.Run("rejects undecodable setup and missing validated list", func(t *testing.T) { + require.Error(t, setupBindsParticipants([]byte("not-a-setup"), validated)) + require.Error(t, setupBindsParticipants(nil, validated)) + require.Error(t, setupBindsParticipants(legitSetup, nil)) + }) +} + +// The regression the finding asks for: a Payload whose embedded hash differs +// from the verified SigningHash must refuse session creation and produce no +// shares. Driven through handleSetupMessage so it covers the wiring, not just +// the comparison helper. +func TestHandleSetupMessage_RejectsPayloadHashMismatch(t *testing.T) { + _, coord, evtStore, keyshareMgr, _, testDB := setupTestSessionManager(t) + ctx := context.Background() + + // Sign-eligible validators are the ACTIVE ones in the fixture. + participants := []string{"validator1", "validator2"} + participantIDs := []byte(strings.Join(participants, "\x00")) + keyID := make([]byte, 32) + + legitHash := make([]byte, 32) + copy(legitHash, "legitimate-outbound-hash-32bytes") + attackerHash := make([]byte, 32) + copy(attackerHash, "attacker-chosen-vault-call-digest") + + newRecordingSM := func() (*SessionManager, *int) { + sends := 0 + sm := NewSessionManager( + evtStore, coord, keyshareMgr, nil, nil, + func(context.Context, string, []byte) error { sends++; return nil }, + "validator1", 3*time.Minute, 30*time.Second, 60, zerolog.Nop(), nil, + ) + return sm, &sends + } + + newEvent := func(t *testing.T, id string) { + t.Helper() + require.NoError(t, testDB.Create(&store.Event{ + EventID: id, BlockHeight: 100, + Type: store.EventTypeSignOutbound, + Status: store.StatusConfirmed, + EventData: []byte(`{"destination_chain":"eip155:11155111"}`), + }).Error) + } + + t.Run("substituted payload hash is refused, no session, no shares", func(t *testing.T) { + newEvent(t, "sign-mismatch") + sm, sends := newRecordingSM() + + // Coordinator shows the legitimate hash but ships a setup over its own. + attackerSetup, err := session.DklsSignSetupMsgNew(keyID, nil, attackerHash, participantIDs) + require.NoError(t, err) + + err = sm.HandleIncomingMessage(ctx, "peer1", &coordinator.Message{ + Type: coordinator.MessageTypeSetup, + EventID: "sign-mismatch", + Participants: participants, + Payload: attackerSetup, + UnsignedSigningReq: &common.UnsignedSigningReq{SigningHash: legitHash, Nonce: 1}, + }) + + require.Error(t, err) + assert.Contains(t, err.Error(), "setup message signs hash") + + sm.mu.RLock() + sessionCount := len(sm.sessions) + sm.mu.RUnlock() + assert.Zero(t, sessionCount, "no session may be created for a mismatched setup") + assert.Zero(t, *sends, "no ACK or share may be emitted for a mismatched setup") + }) + + // Positive control: with the same wiring, a setup over the verified hash must + // get past the binding check, so the rejection above is the binding and not + // some earlier validation failing. + t.Run("matching payload hash passes the binding check", func(t *testing.T) { + newEvent(t, "sign-match") + sm, _ := newRecordingSM() + + legitSetup, err := session.DklsSignSetupMsgNew(keyID, nil, legitHash, participantIDs) + require.NoError(t, err) + + err = sm.HandleIncomingMessage(ctx, "peer1", &coordinator.Message{ + Type: coordinator.MessageTypeSetup, + EventID: "sign-match", + Participants: participants, + Payload: legitSetup, + UnsignedSigningReq: &common.UnsignedSigningReq{SigningHash: legitHash, Nonce: 1}, + }) + + if err != nil { + assert.NotContains(t, err.Error(), "setup message signs hash", + "matching setup must not be rejected by the hash binding") + assert.NotContains(t, err.Error(), "do not match validated participants", + "matching setup must not be rejected by the participant binding") + } + }) +} + +// The session constructors accept a threshold and ignore it, so the setup blob's +// embedded threshold is what the protocol runs with. A coordinator embedding a +// lower one would elicit help producing a weaker key than the participants +// agreed to, which is worse than a bad signature since it persists. +func TestSetupBindsThreshold(t *testing.T) { + validated := []string{"validator1", "validator2", "validator3"} + expected := coordinator.CalculateThreshold(len(validated)) + encode := func(ids []string) []byte { return []byte(strings.Join(ids, "\x00")) } + + t.Run("accepts the expected threshold", func(t *testing.T) { + setup, err := session.DklsKeygenSetupMsgNew(expected, nil, encode(validated)) + require.NoError(t, err) + require.NoError(t, setupBindsThreshold(setup, validated)) + }) + + t.Run("rejects a downgraded threshold", func(t *testing.T) { + require.Greater(t, expected, 1, "fixture must allow a strictly lower threshold") + downgraded, err := session.DklsKeygenSetupMsgNew(expected-1, nil, encode(validated)) + require.NoError(t, err) + err = setupBindsThreshold(downgraded, validated) + require.Error(t, err) + assert.Contains(t, err.Error(), "does not match expected") + }) + + t.Run("rejects undecodable setup", func(t *testing.T) { + require.Error(t, setupBindsThreshold([]byte("not-a-setup"), validated)) + require.Error(t, setupBindsThreshold(nil, validated)) + }) +} From 7db9b89ec8c7f9289597dafff824603ae9c7ddf2 Mon Sep 17 00:00:00 2001 From: Aman Gupta Date: Wed, 19 Aug 2026 16:09:45 +0530 Subject: [PATCH 07/60] test: F-2026-18199 | [Dual Defense] TSS Setup Message Signs an Unverified Hash (Payload vs SigningHash Split) (#312) * test: pin quorumchange threshold encoding and accept u16 threshold (F-2026-18199) * test: cover setup decoders across keygen, refresh, quorumchange and sign (F-2026-18199) --- universalClient/tss/dkls/utils.go | 11 ++- universalClient/tss/dkls/utils_test.go | 103 +++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 2 deletions(-) diff --git a/universalClient/tss/dkls/utils.go b/universalClient/tss/dkls/utils.go index 4a42b8137..ae2fecc98 100644 --- a/universalClient/tss/dkls/utils.go +++ b/universalClient/tss/dkls/utils.go @@ -97,10 +97,17 @@ func SetupThreshold(setupData []byte) (int, error) { return 0, fmt.Errorf("setup message is malformed: tag %d claims %d bytes past the end", tag, length) } if tag == setupTagThreshold { - if length != 1 { + // keygen and quorumchange store the threshold as a u8; the weighted + // keygen variant uses a u16 under the same tag. Accept either so a + // library upgrade widening it does not reject every setup. + switch length { + case 1: + return int(setupData[valueStart]), nil + case 2: + return int(binary.LittleEndian.Uint16(setupData[valueStart : valueStart+2])), nil + default: return 0, fmt.Errorf("threshold tag has unexpected length %d", length) } - return int(setupData[valueStart]), nil } offset = valueStart + length } diff --git a/universalClient/tss/dkls/utils_test.go b/universalClient/tss/dkls/utils_test.go index 29f432859..c3985b7f3 100644 --- a/universalClient/tss/dkls/utils_test.go +++ b/universalClient/tss/dkls/utils_test.go @@ -220,3 +220,106 @@ func TestSetupThreshold(t *testing.T) { } }) } + +// Cross-protocol matrix over the three setup shapes the coordinator builds: +// keygen (shared with keyrefresh), quorumchange, and sign (shared with fund +// migration). Pins what each decoder returns for each shape, so a rebuilt DKLS +// library that changes the encoding fails here rather than in production. +func TestSetupDecoders_AllProtocols(t *testing.T) { + participants := []string{"party1", "party2", "party3"} + ids := encodeParticipantIDs(participants) + const threshold = 2 + + messageHash := make([]byte, 32) + copy(messageHash, "outbound-signing-hash-32-bytes!!") + + // keygen, also used verbatim for keyrefresh + keygenSetup, err := session.DklsKeygenSetupMsgNew(threshold, nil, ids) + if err != nil { + t.Fatalf("failed to build keygen setup: %v", err) + } + + // sign, also used for fund migration + signSetup, err := session.DklsSignSetupMsgNew(make([]byte, 32), nil, messageHash, ids) + if err != nil { + t.Fatalf("failed to build sign setup: %v", err) + } + + // quorumchange needs a real keyshare, so run a keygen to completion first + sessions := map[string]Session{} + for _, p := range participants { + sess, err := NewKeygenSession(keygenSetup, "matrix", p, participants, threshold) + if err != nil { + t.Fatalf("failed to create keygen session for %s: %v", p, err) + } + sessions[p] = sess + } + keyshare := runToCompletion(t, sessions)["party1"].Keyshare + handle, err := session.DklsKeyshareFromBytes(keyshare) + if err != nil { + t.Fatalf("failed to load keyshare: %v", err) + } + defer session.DklsKeyshareFree(handle) + + qcSetup, err := session.DklsQcSetupMsgNew(handle, threshold, participants, []int{0, 1, 2}, []int{0, 1, 2}) + if err != nil { + t.Fatalf("failed to build QC setup: %v", err) + } + + shapes := []struct { + name string + setup []byte + wantHash []byte // nil means the shape carries no message + hasThreshold bool + }{ + {"keygen and keyrefresh", keygenSetup, nil, true}, + {"quorumchange", qcSetup, nil, true}, + {"sign and fund migration", signSetup, messageHash, false}, + } + + for _, sh := range shapes { + t.Run(sh.name, func(t *testing.T) { + // Participants are bound for every protocol, so every shape must decode them. + got, err := SetupParticipants(sh.setup) + if err != nil { + t.Fatalf("SetupParticipants() error = %v", err) + } + if len(got) != len(participants) { + t.Fatalf("SetupParticipants() = %v, want %v", got, participants) + } + for i := range participants { + if got[i] != participants[i] { + t.Errorf("participant %d = %q, want %q", i, got[i], participants[i]) + } + } + + gotThreshold, thresholdErr := SetupThreshold(sh.setup) + if sh.hasThreshold { + if thresholdErr != nil { + t.Fatalf("SetupThreshold() error = %v", thresholdErr) + } + if gotThreshold != threshold { + t.Errorf("SetupThreshold() = %d, want %d", gotThreshold, threshold) + } + } else if thresholdErr == nil { + // Sign setups carry no threshold. Erroring is what stops a bogus + // value being read out of unrelated bytes. + t.Errorf("SetupThreshold() on a sign setup returned %d, want an error", gotThreshold) + } + + gotHash, hashErr := SetupMessageHash(sh.setup) + if hashErr != nil { + t.Fatalf("SetupMessageHash() error = %v", hashErr) + } + if sh.wantHash == nil { + // Shapes without a message report an empty hash rather than an + // error, so a non-sign setup can never satisfy the hash binding. + if len(gotHash) != 0 { + t.Errorf("SetupMessageHash() = %x, want empty", gotHash) + } + } else if !bytes.Equal(gotHash, sh.wantHash) { + t.Errorf("SetupMessageHash() = %x, want %x", gotHash, sh.wantHash) + } + }) + } +} From 3bddedc04a137fc13d35671542cdad6050f25291 Mon Sep 17 00:00:00 2001 From: Aman Gupta Date: Wed, 19 Aug 2026 17:32:05 +0530 Subject: [PATCH 08/60] fix: F-2026-18198 | [Dual Defense] Solana Inbound Event Forgery Enables Unbacked Synthetic Minting (#308) * fix: only accept solana gateway events emitted by the gateway program (F-2026-18198) * test: end-to-end proof that forged solana gateway events are not stored (F-2026-18198) * fix: detect truncated solana log buffer so dropped gateway events are not silent (F-2026-18198) * fix: identify which solana gateway event was dropped, not just that logs were cut (F-2026-18198) * chore: drop svm event observation doc from branch * revert: drop instruction discriminator reads from svm event listener * test: unbalanced solana invoke logs must not underflow the attribution stack (F-2026-18198) --- universalClient/chains/svm/event_listener.go | 114 ++++++- .../chains/svm/event_listener_test.go | 315 ++++++++++++++++++ 2 files changed, 428 insertions(+), 1 deletion(-) diff --git a/universalClient/chains/svm/event_listener.go b/universalClient/chains/svm/event_listener.go index aff9f0278..6c2292584 100644 --- a/universalClient/chains/svm/event_listener.go +++ b/universalClient/chains/svm/event_listener.go @@ -295,9 +295,29 @@ func (el *EventListener) processSignatureBatch( continue } - // Process each log in the transaction + // Process each log in the transaction. + // getSignaturesForAddress returns any tx that merely references the + // gateway in accountKeys, and a discriminator is a schema tag rather than + // an authenticator. So track the invocation stack and accept a + // "Program data:" line only while the gateway is the executing program; + // otherwise any program could emit a forged gateway event. if tx != nil && tx.Meta != nil && len(tx.Meta.LogMessages) > 0 { + // Surface truncation loudly: a gateway event may have been dropped and + // is unrecoverable from RPC, so the deposit needs manual reconciliation. + // Visible logs are still processed, since events before the cut are real. + if logsTruncated(tx.Meta.LogMessages) { + el.logger.Error(). + Str("signature", sig.Signature.String()). + Uint64("slot", sig.Slot). + Msg("solana log buffer truncated; a gateway event may have been dropped and needs manual review") + } + + fromGateway := gatewayEmittedLogs(tx.Meta.LogMessages, el.gatewayAddress) for logIndex, log := range tx.Meta.LogMessages { + if !fromGateway[logIndex] { + continue + } + // Determine event type based on discriminator eventType := el.determineEventType(log) if eventType == "" { @@ -395,6 +415,98 @@ func (el *EventListener) getPollingInterval() time.Duration { return 5 * time.Second // default } +// invokedProgram returns the program ID from a "Program invoke []" +// runtime log. Programs cannot emit these: sol_log and sol_log_data are always +// prefixed with "Program log: " / "Program data: ", so the invoke and exit lines +// are runtime-generated and safe to build an attribution stack from. +func invokedProgram(log string) (string, bool) { + const prefix = "Program " + if !strings.HasPrefix(log, prefix) { + return "", false + } + rest := log[len(prefix):] + idx := strings.Index(rest, " invoke [") + if idx <= 0 { + return "", false + } + programID := rest[:idx] + if _, err := solana.PublicKeyFromBase58(programID); err != nil { + return "", false + } + return programID, true +} + +// gatewayEmittedLogs returns the indexes of "Program data:" lines emitted while +// gatewayAddress was the executing program, walking the invoke/exit stack. +// Lines emitted by any other program are excluded: a discriminator identifies an +// encoding schema, not the emitter, so without this any program could log a +// well-formed gateway event and have it observed as a real deposit. +func gatewayEmittedLogs(logs []string, gatewayAddress string) map[int]bool { + emitted := make(map[int]bool) + var stack []string + for i, log := range logs { + if programID, ok := invokedProgram(log); ok { + stack = append(stack, programID) + continue + } + if isProgramExit(log) { + if len(stack) > 0 { + stack = stack[:len(stack)-1] + } + continue + } + if !strings.HasPrefix(log, "Program data: ") { + continue + } + if len(stack) > 0 && stack[len(stack)-1] == gatewayAddress { + emitted[i] = true + } + } + return emitted +} + +// logsTruncated reports whether the runtime dropped part of this transaction's +// log buffer. Programs can only emit "Program log:" and "Program data:" lines, +// so a bare line is runtime-generated and cannot be spoofed. Matching on the +// word rather than one exact literal keeps this working if the wording changes. +// +// It matters because gateway events are emitted with sol_log_data: once the +// buffer overflows the event line is gone, and no RPC call can recover it. The +// deposit would otherwise be missed in silence. +func logsTruncated(logs []string) bool { + for _, log := range logs { + if strings.HasPrefix(log, "Program ") { + continue + } + if strings.Contains(strings.ToLower(log), "truncated") { + return true + } + } + return false +} + +// isProgramExit reports whether log ends an invocation frame, i.e. +// "Program success" or "Program failed: ...". +// The program ID must parse as a pubkey: otherwise a program logging "success" +// emits "Program log: success", which would pop a frame it does not own and let +// a later log be attributed to its caller. +func isProgramExit(log string) bool { + const prefix = "Program " + if !strings.HasPrefix(log, prefix) { + return false + } + rest := log[len(prefix):] + sp := strings.IndexByte(rest, ' ') + if sp <= 0 { + return false + } + if _, err := solana.PublicKeyFromBase58(rest[:sp]); err != nil { + return false + } + tail := rest[sp+1:] + return tail == "success" || strings.HasPrefix(tail, "failed") +} + // determineEventType determines the event type based on the log discriminator func (el *EventListener) determineEventType(log string) string { if !strings.HasPrefix(log, "Program data: ") { diff --git a/universalClient/chains/svm/event_listener_test.go b/universalClient/chains/svm/event_listener_test.go index 62065a9b2..84c20e36f 100644 --- a/universalClient/chains/svm/event_listener_test.go +++ b/universalClient/chains/svm/event_listener_test.go @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/pushchain/push-chain-node/universalClient/chains/common" "github.com/pushchain/push-chain-node/universalClient/db" uregistrytypes "github.com/pushchain/push-chain-node/x/uregistry/types" ) @@ -676,3 +677,317 @@ func TestEventListener_StartWhileRunning(t *testing.T) { cancel() el.wg.Wait() } + +const ( + testGatewayProgram = "CFVSincHYbETh2k7w6u1ENEkjbSLtveRCEBupKidw2VS" + testAttackerProgram = "AttackerProgram1111111111111111111111111111" +) + +// getSignaturesForAddress returns any tx that merely references the gateway in +// accountKeys, and a discriminator is a schema tag, not an authenticator. So a +// gateway-shaped log must only be trusted when the gateway is the executing +// program. Otherwise any program can forge a deposit that every honest UV +// deterministically votes for. +func TestGatewayEmittedLogs(t *testing.T) { + const data = "Program data: q83vEjRWeJA=" + + t.Run("accepts log emitted by the gateway", func(t *testing.T) { + logs := []string{ + "Program " + testGatewayProgram + " invoke [1]", + data, + "Program " + testGatewayProgram + " success", + } + assert.Equal(t, map[int]bool{1: true}, gatewayEmittedLogs(logs, testGatewayProgram)) + }) + + // The reported attack: attacker program lists the gateway as an unused + // read-only account and emits a correctly encoded gateway event. + t.Run("rejects forged log from an attacker program", func(t *testing.T) { + logs := []string{ + "Program " + testAttackerProgram + " invoke [1]", + data, + "Program " + testAttackerProgram + " success", + } + assert.Empty(t, gatewayEmittedLogs(logs, testGatewayProgram)) + }) + + t.Run("accepts gateway frame reached via CPI", func(t *testing.T) { + logs := []string{ + "Program " + testAttackerProgram + " invoke [1]", + "Program " + testGatewayProgram + " invoke [2]", + data, + "Program " + testGatewayProgram + " success", + "Program " + testAttackerProgram + " success", + } + assert.Equal(t, map[int]bool{2: true}, gatewayEmittedLogs(logs, testGatewayProgram)) + }) + + // After the gateway frame exits, control is back with the caller, so a log + // there is not the gateway's. + t.Run("rejects log emitted after the gateway frame exits", func(t *testing.T) { + logs := []string{ + "Program " + testAttackerProgram + " invoke [1]", + "Program " + testGatewayProgram + " invoke [2]", + "Program " + testGatewayProgram + " success", + data, + "Program " + testAttackerProgram + " success", + } + assert.Empty(t, gatewayEmittedLogs(logs, testGatewayProgram)) + }) + + t.Run("rejects log from a failed gateway invocation's caller", func(t *testing.T) { + logs := []string{ + "Program " + testGatewayProgram + " invoke [1]", + "Program " + testGatewayProgram + " failed: custom program error: 0x1", + data, + } + assert.Empty(t, gatewayEmittedLogs(logs, testGatewayProgram)) + }) + + // A program can only emit "Program log: ..." or "Program data: ...", so it + // cannot fake an invoke line to push a gateway frame onto the stack. + t.Run("cannot spoof an invoke line via program log", func(t *testing.T) { + logs := []string{ + "Program " + testAttackerProgram + " invoke [1]", + "Program log: Program " + testGatewayProgram + " invoke [1]", + data, + "Program " + testAttackerProgram + " success", + } + assert.Empty(t, gatewayEmittedLogs(logs, testGatewayProgram)) + }) + + // A callee that logs "success" emits "Program log: success". If that were + // treated as a frame exit it would pop its own frame and the next data log + // would be attributed to its caller, the gateway. + t.Run("callee cannot pop its frame by logging success", func(t *testing.T) { + logs := []string{ + "Program " + testGatewayProgram + " invoke [1]", + "Program " + testAttackerProgram + " invoke [2]", + "Program log: success", + data, + "Program " + testAttackerProgram + " success", + "Program " + testGatewayProgram + " success", + } + assert.Empty(t, gatewayEmittedLogs(logs, testGatewayProgram), + "data logged inside the callee must not be attributed to the gateway") + }) + + t.Run("no logs or no invocation yields nothing", func(t *testing.T) { + assert.Empty(t, gatewayEmittedLogs(nil, testGatewayProgram)) + assert.Empty(t, gatewayEmittedLogs([]string{data}, testGatewayProgram)) + }) + + // Unbalanced logs are reachable: truncation can cut a frame's exit line, and + // the parser must not underflow or start attributing to a frame nobody owns. + t.Run("more exits than invokes does not underflow", func(t *testing.T) { + assert.Empty(t, gatewayEmittedLogs([]string{ + "Program " + testGatewayProgram + " success", + "Program " + testGatewayProgram + " success", + data, + }, testGatewayProgram)) + + assert.Empty(t, gatewayEmittedLogs([]string{ + "Program " + testGatewayProgram + " invoke [1]", + "Program " + testGatewayProgram + " success", + "Program " + testGatewayProgram + " success", + data, + }, testGatewayProgram)) + }) + + t.Run("attacker exits cannot expose an outer gateway frame", func(t *testing.T) { + // Gateway CPIs into the attacker, who emits surplus exits hoping to pop + // back to the gateway frame and have its own data log attributed to it. + assert.Empty(t, gatewayEmittedLogs([]string{ + "Program " + testGatewayProgram + " invoke [1]", + "Program " + testAttackerProgram + " invoke [2]", + "Program " + testAttackerProgram + " success", + "Program " + testGatewayProgram + " success", + data, + }, testGatewayProgram)) + }) + + t.Run("gateway frame left open still attributes", func(t *testing.T) { + // What a mid-frame truncation looks like: the exit line never arrives. + assert.Equal(t, map[int]bool{1: true}, gatewayEmittedLogs([]string{ + "Program " + testGatewayProgram + " invoke [1]", + data, + }, testGatewayProgram)) + }) + + t.Run("unrelated runtime lines do not disturb the stack", func(t *testing.T) { + logs := []string{ + "Program " + testGatewayProgram + " invoke [1]", + "Program log: Instruction: SendFunds", + "Program return: " + testGatewayProgram + " AQID", + "Program " + testGatewayProgram + " consumed 12345 of 200000 compute units", + data, + "Program " + testGatewayProgram + " success", + } + assert.Equal(t, map[int]bool{4: true}, gatewayEmittedLogs(logs, testGatewayProgram)) + }) +} + +func TestInvokedProgramAndExit(t *testing.T) { + id, ok := invokedProgram("Program " + testGatewayProgram + " invoke [1]") + assert.True(t, ok) + assert.Equal(t, testGatewayProgram, id) + + _, ok = invokedProgram("Program log: hello") + assert.False(t, ok) + _, ok = invokedProgram("Program data: AQID") + assert.False(t, ok) + + assert.True(t, isProgramExit("Program "+testGatewayProgram+" success")) + assert.True(t, isProgramExit("Program "+testGatewayProgram+" failed: custom program error: 0x1")) + assert.False(t, isProgramExit("Program log: success")) + assert.False(t, isProgramExit("Program data: AQID")) + assert.False(t, isProgramExit("Program "+testGatewayProgram+" consumed 1 of 2 compute units")) +} + +// forgeryRPC serves one transaction whose logs the test controls. +type forgeryRPC struct { + slot uint64 + sig solana.Signature + logs []string +} + +func (m *forgeryRPC) GetLatestSlot(context.Context) (uint64, error) { return m.slot, nil } + +func (m *forgeryRPC) GetSignaturesForAddress(context.Context, solana.PublicKey, solana.Signature) ([]*solanarpc.TransactionSignature, error) { + return []*solanarpc.TransactionSignature{{Signature: m.sig, Slot: m.slot}}, nil +} + +func (m *forgeryRPC) GetTransaction(context.Context, solana.Signature) (*solanarpc.GetTransactionResult, error) { + return &solanarpc.GetTransactionResult{ + Slot: m.slot, + Meta: &solanarpc.TransactionMeta{LogMessages: m.logs}, + }, nil +} + +// End-to-end proof that the listener drops a forged event. The same valid +// send_funds payload is served twice: emitted by an attacker program it must be +// ignored, emitted by the gateway it must be stored. Running both with one +// payload shows attribution is what rejects it, not a decode failure. +func TestProcessSignatureBatch_RejectsForgedGatewayEvent(t *testing.T) { + discriminator := "0000000000000000" // buildSendFundsPayload zeroes the discriminator + payload := buildSendFundsPayload( + [32]byte{1}, [20]byte{2}, [32]byte{3}, 1_000_000, + nil, [32]byte{4}, 0, nil, false, + ) + dataLog := "Program data: " + base64.StdEncoding.EncodeToString(payload) + + run := func(t *testing.T, logs []string) int { + t.Helper() + database, err := db.OpenInMemoryDB(true) + require.NoError(t, err) + t.Cleanup(func() { database.Close() }) + + methods := []*uregistrytypes.GatewayMethods{ + {Name: EventTypeSendFunds, EventIdentifier: discriminator}, + } + rpc := &forgeryRPC{slot: 100, sig: mkSig(7), logs: logs} + el, err := NewEventListener(rpc, testGatewayProgram, "solana:test", methods, database, 10, nil, zerolog.Nop()) + require.NoError(t, err) + + _, err = el.processSignatureBatch(context.Background(), []*solanarpc.TransactionSignature{ + {Signature: mkSig(7), Slot: 100}, + }, 0, 200) + require.NoError(t, err) + + events, err := common.NewChainStore(database).GetPendingEvents(100) + require.NoError(t, err) + return len(events) + } + + t.Run("forged by attacker program is not stored", func(t *testing.T) { + stored := run(t, []string{ + "Program " + testAttackerProgram + " invoke [1]", + dataLog, + "Program " + testAttackerProgram + " success", + }) + assert.Zero(t, stored, "forged gateway event must not become an inbound") + }) + + t.Run("same payload from the gateway is stored", func(t *testing.T) { + stored := run(t, []string{ + "Program " + testGatewayProgram + " invoke [1]", + dataLog, + "Program " + testGatewayProgram + " success", + }) + assert.Equal(t, 1, stored, "genuine gateway event must be observed") + }) +} + +// Gateway events are emitted with sol_log_data, so once the runtime truncates +// the log buffer the event line is gone and no RPC call recovers it. Detect it +// so a missed deposit is alertable instead of silent. +func TestLogsTruncated(t *testing.T) { + t.Run("detects the runtime marker", func(t *testing.T) { + assert.True(t, logsTruncated([]string{ + "Program " + testGatewayProgram + " invoke [1]", + "Program log: Instruction: SendFunds", + "Log truncated", + })) + }) + + t.Run("matches wording variants and case", func(t *testing.T) { + assert.True(t, logsTruncated([]string{"log truncated"})) + assert.True(t, logsTruncated([]string{"Log Truncated"})) + }) + + t.Run("normal logs are not flagged", func(t *testing.T) { + assert.False(t, logsTruncated([]string{ + "Program " + testGatewayProgram + " invoke [1]", + "Program log: Instruction: SendFunds", + "Program data: q83vEjRWeJA=", + "Program " + testGatewayProgram + " success", + })) + assert.False(t, logsTruncated(nil)) + }) + + // A program cannot emit a bare line, so it cannot fake the marker. Its own + // output is always prefixed and must not trip detection. + t.Run("program cannot spoof the marker", func(t *testing.T) { + assert.False(t, logsTruncated([]string{ + "Program " + testAttackerProgram + " invoke [1]", + "Program log: Log truncated", + "Program data: dHJ1bmNhdGVk", + "Program " + testAttackerProgram + " success", + })) + }) +} + +// Truncation must not discard the events that did survive: anything logged +// before the cut is genuine and attributable. +func TestProcessSignatureBatch_TruncatedLogsStillStoreVisibleEvents(t *testing.T) { + discriminator := "0000000000000000" + payload := buildSendFundsPayload( + [32]byte{1}, [20]byte{2}, [32]byte{3}, 1_000_000, + nil, [32]byte{4}, 0, nil, false, + ) + + database, err := db.OpenInMemoryDB(true) + require.NoError(t, err) + defer database.Close() + + methods := []*uregistrytypes.GatewayMethods{ + {Name: EventTypeSendFunds, EventIdentifier: discriminator}, + } + rpc := &forgeryRPC{slot: 100, sig: mkSig(9), logs: []string{ + "Program " + testGatewayProgram + " invoke [1]", + "Program data: " + base64.StdEncoding.EncodeToString(payload), + "Program " + testGatewayProgram + " success", + "Log truncated", + }} + el, err := NewEventListener(rpc, testGatewayProgram, "solana:test", methods, database, 10, nil, zerolog.Nop()) + require.NoError(t, err) + + _, err = el.processSignatureBatch(context.Background(), []*solanarpc.TransactionSignature{ + {Signature: mkSig(9), Slot: 100}, + }, 0, 200) + require.NoError(t, err) + + events, err := common.NewChainStore(database).GetPendingEvents(100) + require.NoError(t, err) + assert.Len(t, events, 1, "events logged before the cut must still be stored") +} From f176bfaf6e7090ada7dcf0e5b9d4a87055247b0e Mon Sep 17 00:00:00 2001 From: Aman Gupta Date: Wed, 19 Aug 2026 18:29:08 +0530 Subject: [PATCH 09/60] fix: treat receipt RPC failure as an error instead of tx-not-found (F-2026-18826) (#313) --- universalClient/chains/common/types.go | 4 +- universalClient/chains/evm/tx_builder.go | 10 +- universalClient/chains/evm/tx_builder_test.go | 128 ++++++++++++++++++ universalClient/chains/svm/tx_builder.go | 8 +- universalClient/chains/svm/tx_builder_test.go | 73 ++++++++++ .../tss/txresolver/resolver_test.go | 33 +++++ 6 files changed, 251 insertions(+), 5 deletions(-) diff --git a/universalClient/chains/common/types.go b/universalClient/chains/common/types.go index aa44e5827..c67343625 100644 --- a/universalClient/chains/common/types.go +++ b/universalClient/chains/common/types.go @@ -60,7 +60,9 @@ type TxBuilder interface { // VerifyBroadcastedTx checks the status of a broadcasted transaction on the destination chain. // Returns (found, blockHeight, confirmations, status, error): - // - found=false: tx not found or not yet mined + // - err != nil: the chain could not be queried. Callers must retry and must not + // treat this as evidence about whether the tx executed. + // - found=false, err=nil: the chain answered and the tx is not there. // - found=true: tx exists on-chain // - blockHeight: the block in which the tx was mined // - confirmations: number of blocks since the tx was mined (0 = just mined) diff --git a/universalClient/chains/evm/tx_builder.go b/universalClient/chains/evm/tx_builder.go index f5a0114ea..7e74cd131 100644 --- a/universalClient/chains/evm/tx_builder.go +++ b/universalClient/chains/evm/tx_builder.go @@ -250,15 +250,19 @@ func (tb *TxBuilder) BroadcastOutboundSigningRequest( func (tb *TxBuilder) VerifyBroadcastedTx(ctx context.Context, txHash string) (found bool, blockHeight uint64, confirmations uint64, status uint8, err error) { hash := ethcommon.HexToHash(txHash) receipt, err := tb.rpcClient.GetTransactionReceipt(ctx, hash) - if err != nil || receipt == nil { + if err != nil { + // Reporting a not-found verdict here would let the resolver vote failure against a tx that already executed. + return false, 0, 0, 0, err + } + if receipt == nil { return false, 0, 0, 0, nil } receiptBlock := receipt.BlockNumber var confs uint64 - latestBlock, err := tb.rpcClient.GetLatestBlock(ctx) - if err == nil && latestBlock >= receiptBlock { + latestBlock, blockErr := tb.rpcClient.GetLatestBlock(ctx) + if blockErr == nil && latestBlock >= receiptBlock { confs = latestBlock - receiptBlock + 1 } diff --git a/universalClient/chains/evm/tx_builder_test.go b/universalClient/chains/evm/tx_builder_test.go index bd9d545a0..d5c2fa589 100644 --- a/universalClient/chains/evm/tx_builder_test.go +++ b/universalClient/chains/evm/tx_builder_test.go @@ -3,7 +3,11 @@ package evm import ( "context" "encoding/hex" + "io" "math/big" + "net/http" + "net/http/httptest" + "strings" "testing" "time" @@ -938,6 +942,130 @@ func TestSimulateBSC_RescueFunds_ERC20(t *testing.T) { // BSC simulation tests above). // --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// VerifyBroadcastedTx +// --------------------------------------------------------------------------- + +const testReceiptTxHash = "0x1111111111111111111111111111111111111111111111111111111111111111" + +// newReceiptRPCBuilder drives the real RPCClient against a local JSON-RPC +// server, so these exercise the same path production takes rather than a mock +// that can return errors the real client never produces. +func newReceiptRPCBuilder(t *testing.T, respond func(method string, w http.ResponseWriter)) *TxBuilder { + t.Helper() + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + body, _ := io.ReadAll(r.Body) + w.Header().Set("Content-Type", "application/json") + switch { + case strings.Contains(string(body), "eth_getTransactionReceipt"): + respond("eth_getTransactionReceipt", w) + case strings.Contains(string(body), "eth_blockNumber"): + respond("eth_blockNumber", w) + default: + _, _ = w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":"0x1"}`)) + } + })) + t.Cleanup(server.Close) + + rpcClient, err := NewRPCClient([]string{server.URL}, 1, zerolog.Nop()) + require.NoError(t, err) + t.Cleanup(rpcClient.Close) + + return &TxBuilder{rpcClient: rpcClient, chainID: "eip155:1", chainIDInt: 1, logger: zerolog.Nop()} +} + +// A receipt RPC failure is not evidence about the transaction. Reported as +// not-found it combines with a consumed nonce to look like "never executed", +// and the resolver votes failure against an outbound the destination already paid. +func TestVerifyBroadcastedTx_ReceiptRPCFailureIsNotAVerdict(t *testing.T) { + t.Run("json-rpc error", func(t *testing.T) { + tb := newReceiptRPCBuilder(t, func(_ string, w http.ResponseWriter) { + _, _ = w.Write([]byte(`{"jsonrpc":"2.0","id":1,"error":{"code":-32005,"message":"rate limited"}}`)) + }) + + found, _, _, _, err := tb.VerifyBroadcastedTx(context.Background(), testReceiptTxHash) + require.Error(t, err, "an unreachable chain must not be reported as a verdict") + assert.False(t, found) + }) + + t.Run("transport failure", func(t *testing.T) { + tb := newReceiptRPCBuilder(t, func(_ string, w http.ResponseWriter) { + w.WriteHeader(http.StatusInternalServerError) + }) + + found, _, _, _, err := tb.VerifyBroadcastedTx(context.Background(), testReceiptTxHash) + require.Error(t, err) + assert.False(t, found) + }) + + t.Run("null receipt is a real not-found", func(t *testing.T) { + tb := newReceiptRPCBuilder(t, func(_ string, w http.ResponseWriter) { + _, _ = w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":null}`)) + }) + + found, _, _, _, err := tb.VerifyBroadcastedTx(context.Background(), testReceiptTxHash) + require.NoError(t, err, "the chain answered, so this is a verdict and not a failure") + assert.False(t, found) + }) +} + +func TestVerifyBroadcastedTx_ReceiptFound(t *testing.T) { + const receipt = `{"jsonrpc":"2.0","id":1,"result":{"status":"0x1","blockNumber":"0x64","gasUsed":"0x5208","effectiveGasPrice":"0x3b9aca00"}}` + + t.Run("reports block height, confirmations and status", func(t *testing.T) { + tb := newReceiptRPCBuilder(t, func(method string, w http.ResponseWriter) { + if method == "eth_blockNumber" { + _, _ = w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":"0x6e"}`)) // 110 + return + } + _, _ = w.Write([]byte(receipt)) + }) + + found, blockHeight, confs, status, err := tb.VerifyBroadcastedTx(context.Background(), testReceiptTxHash) + require.NoError(t, err) + assert.True(t, found) + assert.Equal(t, uint64(100), blockHeight) + assert.Equal(t, uint64(11), confs) + assert.Equal(t, uint8(1), status) + }) + + // The block number is only needed for the confirmation count. Failing it must + // not discard the receipt we already have: zero confirmations makes the + // resolver wait, which is the correct outcome. + t.Run("block number failure keeps the tx found with zero confirmations", func(t *testing.T) { + tb := newReceiptRPCBuilder(t, func(method string, w http.ResponseWriter) { + if method == "eth_blockNumber" { + w.WriteHeader(http.StatusInternalServerError) + return + } + _, _ = w.Write([]byte(receipt)) + }) + + found, blockHeight, confs, status, err := tb.VerifyBroadcastedTx(context.Background(), testReceiptTxHash) + require.NoError(t, err) + assert.True(t, found) + assert.Equal(t, uint64(100), blockHeight) + assert.Zero(t, confs) + assert.Equal(t, uint8(1), status) + }) + + t.Run("reverted receipt reports status zero", func(t *testing.T) { + tb := newReceiptRPCBuilder(t, func(method string, w http.ResponseWriter) { + if method == "eth_blockNumber" { + _, _ = w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":"0x6e"}`)) + return + } + _, _ = w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":{"status":"0x0","blockNumber":"0x64","gasUsed":"0x5208"}}`)) + }) + + found, _, _, status, err := tb.VerifyBroadcastedTx(context.Background(), testReceiptTxHash) + require.NoError(t, err) + assert.True(t, found) + assert.Equal(t, uint8(0), status) + }) +} + // --------------------------------------------------------------------------- // parseGasLimit — additional edge-case coverage // --------------------------------------------------------------------------- diff --git a/universalClient/chains/svm/tx_builder.go b/universalClient/chains/svm/tx_builder.go index 784ac1d3a..ee77d59ea 100644 --- a/universalClient/chains/svm/tx_builder.go +++ b/universalClient/chains/svm/tx_builder.go @@ -30,6 +30,7 @@ import ( "encoding/binary" "encoding/hex" "encoding/json" + "errors" "fmt" "math/big" "os" @@ -478,9 +479,14 @@ func (tb *TxBuilder) VerifyBroadcastedTx(ctx context.Context, txHash string) (fo } tx, txErr := tb.rpcClient.GetTransaction(ctx, sig) - if txErr != nil { + // solana-go reports a genuinely absent tx as ErrNotFound, so that one is a verdict. + if errors.Is(txErr, rpc.ErrNotFound) { return false, 0, 0, 0, nil } + if txErr != nil { + // Reporting a not-found verdict here would let the resolver vote failure against a tx that already executed. + return false, 0, 0, 0, txErr + } if tx == nil { return false, 0, 0, 0, nil diff --git a/universalClient/chains/svm/tx_builder_test.go b/universalClient/chains/svm/tx_builder_test.go index 26842fc58..f94145e17 100644 --- a/universalClient/chains/svm/tx_builder_test.go +++ b/universalClient/chains/svm/tx_builder_test.go @@ -8,6 +8,9 @@ import ( "encoding/binary" "encoding/hex" "fmt" + "io" + "net/http" + "net/http/httptest" "os" "path/filepath" "strings" @@ -2718,3 +2721,73 @@ func TestSimulate_RefRoute_Execute(t *testing.T) { require.NoError(t, err) requireSimulationSuccess(t, storeSim) } + +// --------------------------------------------------------------------------- +// VerifyBroadcastedTx +// --------------------------------------------------------------------------- + +const testVerifySignature = "5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW" + +// newVerifyRPCBuilder drives the real RPCClient against a local JSON-RPC server. +// getHealth must answer for NewRPCClient to keep the endpoint; the genesis hash +// check is skipped by passing an empty expected hash. +func newVerifyRPCBuilder(t *testing.T, respond func(method string, w http.ResponseWriter)) *TxBuilder { + t.Helper() + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + body, _ := io.ReadAll(r.Body) + w.Header().Set("Content-Type", "application/json") + switch { + case strings.Contains(string(body), `"getHealth"`): + _, _ = w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":"ok"}`)) + case strings.Contains(string(body), `"getTransaction"`): + respond("getTransaction", w) + case strings.Contains(string(body), `"getSlot"`): + respond("getSlot", w) + default: + _, _ = w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":null}`)) + } + })) + t.Cleanup(server.Close) + + rpcClient, err := NewRPCClient([]string{server.URL}, "", zerolog.Nop()) + require.NoError(t, err) + + return &TxBuilder{rpcClient: rpcClient, chainID: "solana:test", logger: zerolog.Nop()} +} + +// solana-go collapses both cases onto the error return: a genuinely absent tx +// comes back as ErrNotFound, everything else is a real RPC failure. Only the +// first is a verdict; treating the second as one lets the resolver vote failure +// against a tx that already executed. +func TestVerifyBroadcastedTx_NotFoundVersusRPCFailure(t *testing.T) { + t.Run("absent tx is a verdict, not an error", func(t *testing.T) { + tb := newVerifyRPCBuilder(t, func(_ string, w http.ResponseWriter) { + _, _ = w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":null}`)) + }) + + found, _, _, _, err := tb.VerifyBroadcastedTx(context.Background(), testVerifySignature) + require.NoError(t, err, "an absent tx must resolve, not retry forever") + assert.False(t, found) + }) + + t.Run("rpc failure surfaces as an error", func(t *testing.T) { + tb := newVerifyRPCBuilder(t, func(_ string, w http.ResponseWriter) { + _, _ = w.Write([]byte(`{"jsonrpc":"2.0","id":1,"error":{"code":-32005,"message":"rate limited"}}`)) + }) + + found, _, _, _, err := tb.VerifyBroadcastedTx(context.Background(), testVerifySignature) + require.Error(t, err, "an unreachable chain must not be reported as a verdict") + assert.False(t, found) + }) + + t.Run("malformed signature is a verdict", func(t *testing.T) { + tb := newVerifyRPCBuilder(t, func(_ string, w http.ResponseWriter) { + _, _ = w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":null}`)) + }) + + found, _, _, _, err := tb.VerifyBroadcastedTx(context.Background(), "not-a-signature") + require.NoError(t, err) + assert.False(t, found) + }) +} diff --git a/universalClient/tss/txresolver/resolver_test.go b/universalClient/tss/txresolver/resolver_test.go index 1e7c457be..752b03c2d 100644 --- a/universalClient/tss/txresolver/resolver_test.go +++ b/universalClient/tss/txresolver/resolver_test.go @@ -965,6 +965,39 @@ func TestResolveOutboundEVM_NotFound_NonceConsumed_Reverts(t *testing.T) { builder.AssertCalled(t, "GetNextNonce", mock.Anything, testEVMTSSAddr, true) } +// A receipt RPC failure combined with a consumed nonce is the dangerous +// combination: the nonce alone reads as "another tx took our slot, ours never +// executed", so a failure vote goes out against an outbound the destination has +// already paid. The error must stop the resolver before the nonce is consulted. +func TestResolveOutboundEVM_ReceiptError_NonceConsumed_DoesNotVoteFailure(t *testing.T) { + evtStore, db := setupTestDB(t) + builder := &mockTxBuilder{} + client := &mockChainClient{builder: builder} + ch := newTestChains(t, "eip155:1", uregistrytypes.VmType_EVM, client) + + eventData := makeOutboundEventDataWithNonce("tx-100", "utx-200", "eip155:1", 5) + insertBroadcastedEvent(t, db, "ev-18826", "eip155:1", "eip155:1:0xalreadypaid", eventData) + + builder.On("VerifyBroadcastedTx", mock.Anything, "0xalreadypaid"). + Return(false, uint64(0), uint64(0), uint8(0), assert.AnError).Once() + // Finalized nonce 7 > signed nonce 5, so the nonce check would say "consumed". + builder.On("GetNextNonce", mock.Anything, testEVMTSSAddr, true).Return(uint64(7), nil) + + resolver := newResolverWithTSSAddress(evtStore, ch, testEVMTSSAddr) + resolver.processBroadcasted(context.Background()) + + require.Equal(t, store.StatusBroadcasted, getEvent(t, db, "ev-18826").Status) + builder.AssertNotCalled(t, "GetNextNonce", mock.Anything, mock.Anything, mock.Anything) + + // RPC recovers and the receipt shows the destination did execute successfully. + builder.On("VerifyBroadcastedTx", mock.Anything, "0xalreadypaid"). + Return(true, uint64(500), uint64(20), uint8(1), nil).Once() + + resolver.processBroadcasted(context.Background()) + + require.Equal(t, store.StatusCompleted, getEvent(t, db, "ev-18826").Status) +} + func TestResolveOutboundEVM_NotFound_NonceUnconsumed_RewindsToSigned(t *testing.T) { // Tx not found AND signed nonce >= finalized nonce → tx may still land // (or was dropped from mempool). Rewind to SIGNED so the broadcaster From e6ba904209ee9ac4228c86372ab8df5d5f2ae672 Mon Sep 17 00:00:00 2001 From: Aman Gupta Date: Fri, 21 Aug 2026 13:25:47 +0530 Subject: [PATCH 10/60] fix: F-2026-18804 | [Dual Defense] EVM ABI readDynamicBytes Uint64 Overflow Panics Can Kill puniversald (#322) * fix: reject overflowing ABI offsets and contain decode panics in the evm log parser (F-2026-18804) * chore: gofmt evm event parser * fix: contain decode panics in the svm log parser too (F-2026-18804) * Revert "fix: contain decode panics in the svm log parser too (F-2026-18804)" This reverts commit 0eaf1a981316f44c5d1d15a8ea21d495bb7d6524. --- universalClient/chains/evm/event_parser.go | 41 +++++-- .../chains/evm/event_parser_test.go | 100 ++++++++++++++++++ 2 files changed, 133 insertions(+), 8 deletions(-) diff --git a/universalClient/chains/evm/event_parser.go b/universalClient/chains/evm/event_parser.go index 3cde0c7ea..2c3382327 100644 --- a/universalClient/chains/evm/event_parser.go +++ b/universalClient/chains/evm/event_parser.go @@ -29,7 +29,27 @@ const ( // ParseEvent parses a log into a store.Event based on the event type. // eventType should be one of: sendFunds, executeUniversalTx, revertUniversalTx. -func ParseEvent(log *types.Log, eventType string, chainID string, logger zerolog.Logger) *store.Event { +// +// A panic in the decoders is contained here rather than allowed to unwind. Log +// data is supplied by an RPC and the listener runs on a background goroutine, so +// an unrecovered panic would take down every chain and the TSS node with it. A +// log we cannot decode is skipped like any other undecodable one. +func ParseEvent(log *types.Log, eventType string, chainID string, logger zerolog.Logger) (event *store.Event) { + defer func() { + if r := recover(); r != nil { + event = nil + logger.Error(). + Interface("panic", r). + Str("event_type", eventType). + Str("tx_hash", log.TxHash.Hex()). + Uint("log_index", log.Index). + Msg("panic while decoding log; skipping it") + } + }() + return parseEvent(log, eventType, chainID, logger) +} + +func parseEvent(log *types.Log, eventType string, chainID string, logger zerolog.Logger) *store.Event { if len(log.Topics) == 0 { return nil } @@ -175,17 +195,24 @@ func parseUniversalTxEvent(event *store.Event, log *types.Log, chainID string, l } // readDynamicBytes decodes ABI-encoded dynamic bytes at the given absolute offset in data. +// +// Both absOff and the length word are attacker-controlled: they come from the +// log data an RPC returns. Bounds are therefore checked by subtracting from the +// buffer length rather than adding to the offset — absOff+32 and dataStart+byteLen +// each wrap on a near-2^64 word and would pass an additive guard, then panic on +// the slice. func readDynamicBytes(data []byte, absOff uint64) (string, bool) { - if absOff+32 > uint64(len(data)) { + n := uint64(len(data)) + if absOff > n || n-absOff < 32 { return "", false } byteLen := new(big.Int).SetBytes(data[absOff : absOff+32]).Uint64() - dataStart := absOff + 32 - dataEnd := dataStart + byteLen - if dataEnd > uint64(len(data)) { + + dataStart := absOff + 32 // safe: absOff+32 <= n was just established + if n-dataStart < byteLen { return "", false } - return "0x" + hex.EncodeToString(data[dataStart:dataEnd]), true + return "0x" + hex.EncodeToString(data[dataStart:dataStart+byteLen]), true } // readWord returns the i-th 32-byte word from data, or nil if out of bounds. @@ -278,5 +305,3 @@ func parseUniversalTx(event *store.Event, log *types.Log, dataOffset uint64, pay finalizeEvent(event, payload, logger) } - - diff --git a/universalClient/chains/evm/event_parser_test.go b/universalClient/chains/evm/event_parser_test.go index 1d211cc55..d3a71e798 100644 --- a/universalClient/chains/evm/event_parser_test.go +++ b/universalClient/chains/evm/event_parser_test.go @@ -3,6 +3,7 @@ package evm import ( "encoding/hex" "encoding/json" + "math" "math/big" "testing" @@ -656,3 +657,102 @@ func TestFinalizeEvent(t *testing.T) { assert.Equal(t, "1000", decoded.Amount) }) } + +// abiWord returns a 32-byte big-endian word holding v, for building hostile log data. +func abiWord(v *big.Int) []byte { + w := make([]byte, 32) + v.FillBytes(w) + return w +} + +// Both the offset and the length word come from the RPC, so both can be chosen +// to overflow uint64. Addition-based bounds wrap and pass, then the slice panics +// — and the listener has no caller between here and the goroutine root, so that +// panic would end the process. +func TestReadDynamicBytes_OverflowIsRejectedNotPanicked(t *testing.T) { + maxU64 := new(big.Int).SetUint64(math.MaxUint64) + + t.Run("offset near 2^64 does not wrap past the bounds check", func(t *testing.T) { + data := make([]byte, 128) + for _, off := range []uint64{ + math.MaxUint64, // absOff + 32 wraps to 31 + math.MaxUint64 - 16, // wraps to 15 + math.MaxUint64 - 31, // wraps to 0 + math.MaxUint64 - 32, // wraps to exactly 0 after the +32 + } { + _, ok := readDynamicBytes(data, off) + assert.False(t, ok, "offset %d must be rejected", off) + } + }) + + t.Run("length near 2^64 does not wrap the end below the start", func(t *testing.T) { + // Word at offset 0 is the length; make it enormous so dataStart+byteLen wraps. + data := make([]byte, 128) + copy(data[0:32], abiWord(maxU64)) + + _, ok := readDynamicBytes(data, 0) + assert.False(t, ok, "a length that wraps the end must be rejected") + }) + + t.Run("length just past the buffer is rejected without wrapping", func(t *testing.T) { + data := make([]byte, 128) + copy(data[0:32], abiWord(big.NewInt(97))) // 32 header + 97 > 128 + _, ok := readDynamicBytes(data, 0) + assert.False(t, ok) + }) + + t.Run("well formed input still decodes", func(t *testing.T) { + data := make([]byte, 128) + copy(data[0:32], abiWord(big.NewInt(4))) + copy(data[32:36], []byte{0xDE, 0xAD, 0xBE, 0xEF}) + + got, ok := readDynamicBytes(data, 0) + require.True(t, ok) + assert.Equal(t, "0xdeadbeef", got) + }) + + t.Run("zero length decodes to empty", func(t *testing.T) { + data := make([]byte, 64) + got, ok := readDynamicBytes(data, 0) + require.True(t, ok) + assert.Equal(t, "0x", got) + }) + + t.Run("exactly filling the buffer decodes", func(t *testing.T) { + data := make([]byte, 64) + copy(data[0:32], abiWord(big.NewInt(32))) + copy(data[32:64], abiWord(big.NewInt(1))) + + _, ok := readDynamicBytes(data, 32+32-32) // offset 32 is past the end for a 64-byte buffer + assert.False(t, ok) + + got, ok := readDynamicBytes(data, 0) + require.True(t, ok) + assert.Len(t, got, 2+64) + }) +} + +// End to end: a log carrying an overflowing payload offset must be skipped, not +// crash the listener goroutine. +func TestParseEvent_HostileLogDoesNotPanic(t *testing.T) { + // 5 words of data so the length guard passes, with word 2 (the payload + // offset) set to a value that overflows when 32 is added to it. + data := make([]byte, 32*5) + copy(data[2*32:3*32], abiWord(new(big.Int).SetUint64(math.MaxUint64))) + + log := &types.Log{ + Topics: []ethcommon.Hash{ + ethcommon.HexToHash("0x01"), + ethcommon.HexToHash("0x02"), + ethcommon.HexToHash("0x03"), + }, + Data: data, + TxHash: ethcommon.HexToHash("0xabc"), + Index: 7, + Address: ethcommon.HexToAddress("0xdead"), + } + + require.NotPanics(t, func() { + ParseEvent(log, EventTypeSendFunds, "eip155:1", zerolog.Nop()) + }) +} From f0083a3f75b00d8bed133e209e95a798ebd41b06 Mon Sep 17 00:00:00 2001 From: Aman Gupta Date: Fri, 21 Aug 2026 16:04:33 +0530 Subject: [PATCH 11/60] fix: F-2026-18797 | [Dual Defense] Universal Client Chain Registry Lifecycle: Stale-Remove Deadlock and Unclosed DB Handles (#325) * fix: remove stale chains outside the read lock and close per-chain databases (F-2026-18797) * fix: apply the same database ownership rule to the push chain path (F-2026-18797) --- universalClient/chains/chains.go | 88 +++++++++++-- universalClient/chains/chains_test.go | 173 +++++++++++++++++++++++++- 2 files changed, 250 insertions(+), 11 deletions(-) diff --git a/universalClient/chains/chains.go b/universalClient/chains/chains.go index bb0b102bc..7fdda147a 100644 --- a/universalClient/chains/chains.go +++ b/universalClient/chains/chains.go @@ -29,8 +29,15 @@ type Chains struct { // Chain client management chains map[string]common.ChainClient // key: CAIP-2 chain ID chainConfigs map[string]*uregistrytypes.ChainConfig // key: CAIP-2 chain ID - chainsMu sync.RWMutex - pushChainID string // Push chain ID (always present) + // Handle opened for each live chain, kept so removal can close it. Every + // getChainDB call opens a new pool, so a handle dropped without closing keeps + // its file descriptors until the process exits. + chainDBs map[string]*db.DB // key: CAIP-2 chain ID + chainsMu sync.RWMutex + pushChainID string // Push chain ID (always present) + + // Database opener, swapped in tests to observe handle lifecycle. + openDB func(dir, filename string, migrateSchema bool) (*db.DB, error) // Background control muRunning sync.Mutex @@ -58,6 +65,8 @@ func NewChains( logger: logger.With().Str("component", "chains").Logger(), chains: make(map[string]common.ChainClient), chainConfigs: make(map[string]*uregistrytypes.ChainConfig), + chainDBs: make(map[string]*db.DB), + openDB: db.OpenFileDB, pushChainID: cfg.PushChainID, } } @@ -199,19 +208,33 @@ func (c *Chains) fetchAndUpdate(parent context.Context) error { } } - // Remove stale chains (never remove Push chain) + c.removeStaleChains(seenChains) + + return nil +} + +// removeStaleChains drops chains the registry no longer lists, never the Push chain. +// +// The ids are collected under the read lock and removed after releasing it. +// removeChain takes the write lock and sync.RWMutex is not reentrant, so removing +// from inside the loop would park the refresh goroutine forever while it still +// holds the read lock, taking every later reader of the registry down with it. +func (c *Chains) removeStaleChains(seenChains map[string]bool) { c.chainsMu.RLock() + var stale []string for chainID := range c.chains { if chainID != c.pushChainID && !seenChains[chainID] { - c.logger.Info().Str("chain", chainID).Msg("removing chain no longer in config") - if err := c.removeChain(chainID); err != nil { - c.logger.Error().Err(err).Str("chain", chainID).Msg("failed to remove chain") - } + stale = append(stale, chainID) } } c.chainsMu.RUnlock() - return nil + for _, chainID := range stale { + c.logger.Info().Str("chain", chainID).Msg("removing chain no longer in config") + if err := c.removeChain(chainID); err != nil { + c.logger.Error().Err(err).Str("chain", chainID).Msg("failed to remove chain") + } + } } // chainAction represents the action to take for a chain config @@ -271,6 +294,19 @@ func (c *Chains) addChain(ctx context.Context, cfg *uregistrytypes.ChainConfig) return fmt.Errorf("failed to get database for chain %s: %w", cfg.Chain, err) } + // Ownership passes to the registry only once the client is live. Until then + // close it on the way out, or a chain that cannot start leaks a handle on + // every refresh tick for as long as the misconfiguration lasts. + adopted := false + defer func() { + if adopted { + return + } + if cerr := chainDB.Close(); cerr != nil { + c.logger.Warn().Err(cerr).Str("chain", cfg.Chain).Msg("failed to close database after unsuccessful chain add") + } + }() + // Get chain-specific config chainConfig := c.config.GetChainConfig(cfg.Chain) @@ -298,7 +334,9 @@ func (c *Chains) addChain(ctx context.Context, cfg *uregistrytypes.ChainConfig) c.chainsMu.Lock() c.chains[cfg.Chain] = client c.chainConfigs[cfg.Chain] = cfg + c.chainDBs[cfg.Chain] = chainDB c.chainsMu.Unlock() + adopted = true c.logger.Info(). Str("chain", cfg.Chain). @@ -324,6 +362,14 @@ func (c *Chains) removeChain(chainID string) error { Msg("error stopping chain client during removal") } + // After Stop, so nothing is still reading through it. + if database, ok := c.chainDBs[chainID]; ok { + if err := database.Close(); err != nil { + c.logger.Error().Err(err).Str("chain", chainID).Msg("error closing chain database during removal") + } + delete(c.chainDBs, chainID) + } + delete(c.chains, chainID) delete(c.chainConfigs, chainID) @@ -350,9 +396,19 @@ func (c *Chains) StopAll() { } } + for chainID, database := range c.chainDBs { + if err := database.Close(); err != nil { + c.logger.Error(). + Err(err). + Str("chain", chainID). + Msg("error closing chain database") + } + } + // Clear the registry c.chains = make(map[string]common.ChainClient) c.chainConfigs = make(map[string]*uregistrytypes.ChainConfig) + c.chainDBs = make(map[string]*db.DB) } // GetClient returns the chain client for the specified chain ID @@ -413,7 +469,7 @@ func (c *Chains) getChainDB(chainID string) (*db.DB, error) { // Derive database base directory from NodeHome baseDir := filepath.Join(c.config.NodeHome, config.DatabasesSubdir) - database, err := db.OpenFileDB(baseDir, dbFilename, true) + database, err := c.openDB(baseDir, dbFilename, true) if err != nil { return nil, fmt.Errorf("failed to create database for chain %s: %w", chainID, err) } @@ -446,6 +502,18 @@ func (c *Chains) ensurePushChain(ctx context.Context) error { return fmt.Errorf("failed to get database for push chain: %w", err) } + // Same ownership rule as addChain: the registry adopts the handle only once + // the client is live, and closes it on the way out of every other path. + adopted := false + defer func() { + if adopted { + return + } + if cerr := pushDB.Close(); cerr != nil { + c.logger.Warn().Err(cerr).Str("chain", c.pushChainID).Msg("failed to close database after unsuccessful push chain add") + } + }() + // Create a minimal chain config for push chain // Push chain doesn't need gateway or other configs pushConfig := &uregistrytypes.ChainConfig{ @@ -482,7 +550,9 @@ func (c *Chains) ensurePushChain(ctx context.Context) error { c.chainsMu.Lock() c.chains[c.pushChainID] = client c.chainConfigs[c.pushChainID] = pushConfig + c.chainDBs[c.pushChainID] = pushDB c.chainsMu.Unlock() + adopted = true c.logger.Info(). Str("chain", c.pushChainID). diff --git a/universalClient/chains/chains_test.go b/universalClient/chains/chains_test.go index 8fcb47063..3d169fff4 100644 --- a/universalClient/chains/chains_test.go +++ b/universalClient/chains/chains_test.go @@ -12,6 +12,7 @@ import ( "github.com/pushchain/push-chain-node/universalClient/chains/common" "github.com/pushchain/push-chain-node/universalClient/config" + "github.com/pushchain/push-chain-node/universalClient/db" uregistrytypes "github.com/pushchain/push-chain-node/x/uregistry/types" ) @@ -1665,8 +1666,8 @@ func TestNewChains_ConfigPreserved(t *testing.T) { t.Run("preserves all config fields", func(t *testing.T) { logger := zerolog.Nop() cfg := &config.Config{ - PushChainID: "push:1", - NodeHome: "/tmp/test", + PushChainID: "push:1", + NodeHome: "/tmp/test", ConfigRefreshIntervalSeconds: 30, } @@ -1739,3 +1740,171 @@ func TestDetermineChainAction_PushChainID(t *testing.T) { assert.Equal(t, chainActionAdd, action) }) } + +// dbIsOpen reports whether the handle still answers queries. A closed *db.DB +// errors on use, which is how these tests tell a released handle from a leaked one. +func dbIsOpen(t *testing.T, database *db.DB) bool { + t.Helper() + sqlDB, err := database.Client().DB() + if err != nil { + return false + } + return sqlDB.Ping() == nil +} + +// A chain that drops out of the registry is removed under the write lock, so the +// stale sweep must not still be holding the read lock when it calls removeChain. +// sync.RWMutex is not reentrant: doing so parks the refresh goroutine forever +// and every later reader of the registry blocks behind it. +func TestFetchAndUpdate_StaleRemovalDoesNotDeadlock(t *testing.T) { + c := newTestChains() + c.chains["eip155:1"] = &mockChainClient{} + c.chainConfigs["eip155:1"] = &uregistrytypes.ChainConfig{Chain: "eip155:1"} + + // Drive the stale sweep directly: the chain is absent from seenChains, which + // is what a delisted chain looks like on the next config fetch. + done := make(chan struct{}) + go func() { + defer close(done) + c.removeStaleChains(map[string]bool{c.pushChainID: true}) + }() + + select { + case <-done: + case <-time.After(5 * time.Second): + t.Fatal("stale removal deadlocked: removeChain was called while the read lock was held") + } + + // The registry must be usable afterwards, not left with a held lock. + acquired := make(chan struct{}) + go func() { + c.chainsMu.Lock() + c.chainsMu.Unlock() + close(acquired) + }() + select { + case <-acquired: + case <-time.After(5 * time.Second): + t.Fatal("chainsMu still held after the stale sweep") + } + + _, err := c.GetClient("eip155:1") + assert.Error(t, err, "the delisted chain should be gone") +} + +// Every getChainDB call opens a fresh pool, so a handle that is dropped rather +// than closed keeps its descriptors for the life of the process. A chain that +// cannot start is retried on every refresh tick, which turns that into growth. +func TestAddChain_ClosesDatabaseWhenTheChainCannotStart(t *testing.T) { + c := newTestChains() + c.config.NodeHome = t.TempDir() + + // An unsupported VM type fails after the database has been opened. + cfg := &uregistrytypes.ChainConfig{ + Chain: "eip155:99", + VmType: uregistrytypes.VmType(9999), + Enabled: &uregistrytypes.ChainEnabled{IsInboundEnabled: true}, + } + + // Capture every handle addChain opens so we can assert each was released. + var opened []*db.DB + realOpen := c.openDB + c.openDB = func(dir, filename string, migrate bool) (*db.DB, error) { + database, err := realOpen(dir, filename, migrate) + if err == nil { + opened = append(opened, database) + } + return database, err + } + + for i := 0; i < 5; i++ { // five refresh ticks with the same broken config + err := c.addChain(context.Background(), cfg) + require.Error(t, err) + } + + require.Len(t, opened, 5, "each attempt opens its own handle") + for i, database := range opened { + assert.False(t, dbIsOpen(t, database), + "handle from attempt %d leaked; a persistent misconfiguration would grow one per tick", i) + } + assert.NotContains(t, c.chains, "eip155:99") +} + +// Removal has to release the handle too, not just drop the map entry. +func TestRemoveChain_ClosesTheDatabase(t *testing.T) { + c := newTestChains() + database, err := db.OpenFileDB(t.TempDir(), "eip155_1.db", true) + require.NoError(t, err) + + c.chains["eip155:1"] = &mockChainClient{} + c.chainConfigs["eip155:1"] = &uregistrytypes.ChainConfig{Chain: "eip155:1"} + c.chainDBs["eip155:1"] = database + require.True(t, dbIsOpen(t, database)) + + require.NoError(t, c.removeChain("eip155:1")) + + assert.False(t, dbIsOpen(t, database), "removal must close the handle") + assert.NotContains(t, c.chainDBs, "eip155:1") +} + +func TestStopAll_ClosesEveryDatabase(t *testing.T) { + c := newTestChains() + dir := t.TempDir() + + var opened []*db.DB + for _, id := range []string{"eip155:1", "eip155:2"} { + database, err := db.OpenFileDB(dir, sanitizeChainID(id)+".db", true) + require.NoError(t, err) + c.chains[id] = &mockChainClient{} + c.chainDBs[id] = database + opened = append(opened, database) + } + + c.StopAll() + + for i, database := range opened { + assert.False(t, dbIsOpen(t, database), "handle %d must be closed", i) + } + assert.Empty(t, c.chainDBs) +} + +// ensurePushChain opens its own handle rather than going through addChain, so the +// same ownership rule has to hold there: released on failure, and registered on +// success so shutdown can close it. +func TestEnsurePushChain_HandleOwnership(t *testing.T) { + t.Run("failure to construct the client releases the handle", func(t *testing.T) { + c := newTestChains() + c.config.NodeHome = t.TempDir() + c.pushCore = nil // push.NewClient rejects a nil core + + var opened []*db.DB + realOpen := c.openDB + c.openDB = func(dir, filename string, migrate bool) (*db.DB, error) { + database, err := realOpen(dir, filename, migrate) + if err == nil { + opened = append(opened, database) + } + return database, err + } + + err := c.ensurePushChain(context.Background()) + require.Error(t, err) + require.Len(t, opened, 1) + assert.False(t, dbIsOpen(t, opened[0]), "handle must be released when the push client cannot be built") + assert.NotContains(t, c.chainDBs, c.pushChainID) + }) + + // On success the handle must be registered, or shutdown silently leaves the + // push chain's database open. + t.Run("shutdown closes a registered push handle", func(t *testing.T) { + c := newTestChains() + database, err := db.OpenFileDB(t.TempDir(), "push.db", true) + require.NoError(t, err) + + c.chains[c.pushChainID] = &mockChainClient{} + c.chainDBs[c.pushChainID] = database + + c.StopAll() + assert.False(t, dbIsOpen(t, database), "push chain handle must be closed on shutdown") + }) +} From b93ea41c642d93bf12f6ec9e3f6fd235ff8464cb Mon Sep 17 00:00:00 2001 From: Aman Gupta Date: Fri, 21 Aug 2026 16:33:51 +0530 Subject: [PATCH 12/60] fix: F-2026-18802 | [Dual Defense] EVM FilterLogs Fixed 9000-Block Chunks Stall Cursor With No Adaptive Shrink (#324) * fix: shrink the log query span on rejection and commit partial block progress (F-2026-18802) * test: assert exact block coverage across span shrinks and range boundaries (F-2026-18802) --- universalClient/chains/evm/event_listener.go | 90 +++++-- .../chains/evm/event_listener_test.go | 253 ++++++++++++++++++ 2 files changed, 316 insertions(+), 27 deletions(-) diff --git a/universalClient/chains/evm/event_listener.go b/universalClient/chains/evm/event_listener.go index 294ddcc38..8bd5c0457 100644 --- a/universalClient/chains/evm/event_listener.go +++ b/universalClient/chains/evm/event_listener.go @@ -208,58 +208,94 @@ func (el *EventListener) processNewBlocks( } // Process blocks in range - if err := el.processBlockRange(ctx, *currentBlock, latestBlock, topics); err != nil { - return fmt.Errorf("failed to process block range: %w", err) + nextBlock, rangeErr := el.processBlockRange(ctx, *currentBlock, latestBlock, topics) + + // Commit whatever was covered even when a later chunk failed. Holding the + // cursor back would re-read the blocks already handled on every tick, so one + // unreadable window would sit in front of everything behind it indefinitely. + if nextBlock > *currentBlock { + if err := el.updateLastProcessedBlock(nextBlock - 1); err != nil { + el.logger.Error().Err(err).Msg("failed to update last processed block") + // Don't return error - continue processing + } + *currentBlock = nextBlock } - // Update last processed block in database - if err := el.updateLastProcessedBlock(latestBlock); err != nil { - el.logger.Error().Err(err).Msg("failed to update last processed block") - // Don't return error - continue processing + if rangeErr != nil { + return fmt.Errorf("failed to process block range: %w", rangeErr) } - - // Move to next block - *currentBlock = latestBlock + 1 return nil } -// processBlockRange processes events in a range of blocks +// Block span for a single eth_getLogs call. Providers cap the result set rather +// than the block count, so a dense window can be rejected at a span that is +// normally fine. maxBlockRange is the optimistic starting point and minBlockRange +// the floor we stop shrinking at. +const ( + maxBlockRange uint64 = 9000 // Safe under the 10000 RPC limit + minBlockRange uint64 = 100 +) + +// processBlockRange processes events in a range of blocks, returning the first +// block it did not cover. That is fromBlock when nothing was processed and +// toBlock+1 when everything was, so the caller can commit partial progress +// whether or not an error is also returned. +// +// A rejected query is retried over a smaller span rather than abandoned: the +// limit is on results, so halving until the window fits gets past a dense range +// that a fixed span cannot. Shrinking is linear rather than a recursive split, +// which would issue exponentially many calls against a range that keeps failing. func (el *EventListener) processBlockRange( ctx context.Context, fromBlock, toBlock uint64, topics []ethcommon.Hash, -) error { - const maxBlockRange uint64 = 9000 // Safe under the 10000 RPC limit +) (uint64, error) { + span := maxBlockRange + nextFrom := fromBlock - currentFrom := fromBlock - - // Process in chunks if the range is too large - for currentFrom <= toBlock { - currentTo := currentFrom + maxBlockRange - 1 - if currentTo > toBlock { + for nextFrom <= toBlock { + currentTo := nextFrom + span - 1 + if currentTo > toBlock || currentTo < nextFrom { // second test catches overflow currentTo = toBlock } - // Log chunk processing for large ranges - blockRange := currentTo - currentFrom + 1 + blockRange := currentTo - nextFrom + 1 if blockRange > 1000 { el.logger.Debug(). - Uint64("from_block", currentFrom). + Uint64("from_block", nextFrom). Uint64("to_block", currentTo). Uint64("range_size", blockRange). Msg("processing block chunk") } - // Process chunk - if err := el.processBlockChunk(ctx, currentFrom, currentTo, topics); err != nil { - return fmt.Errorf("failed to process chunk %d-%d: %w", currentFrom, currentTo, err) + if err := el.processBlockChunk(ctx, nextFrom, currentTo, topics); err != nil { + // Halve what was actually attempted, not the nominal span: near the end + // of a range the span is clamped to toBlock, so shrinking the span alone + // would resend the identical query until it dropped below the remainder. + if blockRange > minBlockRange { + span = blockRange / 2 + if span < minBlockRange { + span = minBlockRange + } + el.logger.Warn(). + Err(err). + Uint64("from_block", nextFrom). + Uint64("to_block", currentTo). + Uint64("retry_span", span). + Msg("log query failed, retrying the same start over a smaller span") + continue + } + + // At the floor the span is no longer the problem. Report the failure + // and leave the cursor here: skipping ahead would drop any deposits in + // these blocks permanently, which is worse than waiting for the RPC. + return nextFrom, fmt.Errorf("failed to process chunk %d-%d at minimum span: %w", nextFrom, currentTo, err) } - // Move to next chunk - currentFrom = currentTo + 1 + nextFrom = currentTo + 1 } - return nil + return nextFrom, nil } // processBlockChunk processes a single chunk of blocks diff --git a/universalClient/chains/evm/event_listener_test.go b/universalClient/chains/evm/event_listener_test.go index 71d76420d..d9e493d33 100644 --- a/universalClient/chains/evm/event_listener_test.go +++ b/universalClient/chains/evm/event_listener_test.go @@ -2,6 +2,14 @@ package evm import ( "context" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "sort" + "strconv" + "strings" + "sync" "testing" "time" @@ -416,3 +424,248 @@ func TestEventListener_ContextCancellationStopsGoroutine(t *testing.T) { el.Stop() assert.False(t, el.IsRunning()) } + +// logQueryServer serves eth_getLogs, rejecting any query whose block span exceeds +// maxSpan the way a provider rejects an over-large result set, and recording the +// spans it was asked for so tests can assert how the client adapted. +type logQueryServer struct { + maxSpan uint64 + failFrom uint64 // when non-zero, reject any query overlapping this block onwards + mu sync.Mutex + asked [][2]uint64 + served [][2]uint64 // only the queries that actually returned logs +} + +func (s *logQueryServer) record(from, to uint64) { + s.mu.Lock() + defer s.mu.Unlock() + s.asked = append(s.asked, [2]uint64{from, to}) +} + +func (s *logQueryServer) spans() [][2]uint64 { + s.mu.Lock() + defer s.mu.Unlock() + return append([][2]uint64(nil), s.asked...) +} + +func (s *logQueryServer) servedSpans() [][2]uint64 { + s.mu.Lock() + defer s.mu.Unlock() + return append([][2]uint64(nil), s.served...) +} + +func (s *logQueryServer) recordServed(from, to uint64) { + s.mu.Lock() + defer s.mu.Unlock() + s.served = append(s.served, [2]uint64{from, to}) +} + +func (s *logQueryServer) start(t *testing.T) *RPCClient { + t.Helper() + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + body, _ := io.ReadAll(r.Body) + w.Header().Set("Content-Type", "application/json") + + if !strings.Contains(string(body), "eth_getLogs") { + _, _ = w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":"0x1"}`)) + return + } + + var req struct { + Params []struct { + FromBlock string `json:"fromBlock"` + ToBlock string `json:"toBlock"` + } `json:"params"` + } + _ = json.Unmarshal(body, &req) + from, _ := strconv.ParseUint(strings.TrimPrefix(req.Params[0].FromBlock, "0x"), 16, 64) + to, _ := strconv.ParseUint(strings.TrimPrefix(req.Params[0].ToBlock, "0x"), 16, 64) + s.record(from, to) + + overSpan := to-from+1 > s.maxSpan + stuck := s.failFrom != 0 && to >= s.failFrom + if overSpan || stuck { + _, _ = w.Write([]byte(`{"jsonrpc":"2.0","id":1,"error":{"code":-32005,"message":"query returned more than 10000 results"}}`)) + return + } + s.recordServed(from, to) + _, _ = w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":[]}`)) + })) + t.Cleanup(srv.Close) + + rpcClient, err := NewRPCClient([]string{srv.URL}, 1, zerolog.Nop()) + require.NoError(t, err) + t.Cleanup(rpcClient.Close) + return rpcClient +} + +func newRangeListener(t *testing.T, rpcClient *RPCClient) *EventListener { + t.Helper() + el, err := NewEventListener(rpcClient, "0x1111111111111111111111111111111111111111", + "0x2222222222222222222222222222222222222222", "eip155:1", nil, nil, testDB(t), 10, nil, zerolog.Nop()) + require.NoError(t, err) + return el +} + +// Providers cap the result set, not the block count, so a dense window is +// rejected at a span that is normally fine. A fixed span retries the same +// rejected query forever and the cursor never moves past it. +func TestProcessBlockRange_ShrinksSpanUntilTheQueryFits(t *testing.T) { + srv := &logQueryServer{maxSpan: 1000} // anything wider than 1000 blocks is rejected + el := newRangeListener(t, srv.start(t)) + + next, err := el.processBlockRange(context.Background(), 1, 2000, nil) + require.NoError(t, err) + assert.Equal(t, uint64(2001), next, "the whole range must end up covered") + + spans := srv.spans() + require.NotEmpty(t, spans) + + // The first attempt is optimistic, and every retry restarts at the same block + // rather than skipping the blocks that were rejected. + assert.Equal(t, uint64(1), spans[0][0]) + assert.Equal(t, uint64(2000), spans[0][1], "first attempt spans the whole range") + + var widths []uint64 + for _, s := range spans { + if s[0] == 1 { + widths = append(widths, s[1]-s[0]+1) + } + } + require.Greater(t, len(widths), 1, "must retry the same start over a smaller span") + for i := 1; i < len(widths); i++ { + assert.Less(t, widths[i], widths[i-1], "each retry must be narrower") + } +} + +// A window that cannot be read even at the floor must not be stepped over: +// the blocks may contain deposits, and skipping them loses those permanently. +func TestProcessBlockRange_DoesNotSkipAnUnreadableWindow(t *testing.T) { + srv := &logQueryServer{maxSpan: maxBlockRange, failFrom: 1} // every query fails + el := newRangeListener(t, srv.start(t)) + + next, err := el.processBlockRange(context.Background(), 1, 500, nil) + require.Error(t, err) + assert.Contains(t, err.Error(), "minimum span") + assert.Equal(t, uint64(1), next, "cursor must stay put, not advance past unread blocks") +} + +// Work already done must be committed. Holding the cursor at the start would +// re-read the earlier chunks on every tick, so one bad window would sit in front +// of everything behind it. +func TestProcessBlockRange_ReportsPartialProgressOnFailure(t *testing.T) { + // First 9000 blocks are readable; anything from 9001 always fails. + srv := &logQueryServer{maxSpan: maxBlockRange, failFrom: 9001} + el := newRangeListener(t, srv.start(t)) + + next, err := el.processBlockRange(context.Background(), 1, 20000, nil) + require.Error(t, err) + assert.Equal(t, uint64(9001), next, "must report the first block it could not cover") +} + +func TestProcessBlockRange_SinglePassWhenNothingIsRejected(t *testing.T) { + srv := &logQueryServer{maxSpan: maxBlockRange} + el := newRangeListener(t, srv.start(t)) + + next, err := el.processBlockRange(context.Background(), 1, 500, nil) + require.NoError(t, err) + assert.Equal(t, uint64(501), next) + assert.Len(t, srv.spans(), 1, "a range that fits must not be split") +} + +// assertExactCoverage checks that the served queries tile [from,to] with no gap +// and no block fetched twice. A gap is a block whose logs are never read, which +// for an inbound is a deposit nobody observes. +func assertExactCoverage(t *testing.T, served [][2]uint64, from, to uint64) { + t.Helper() + + sort.Slice(served, func(i, j int) bool { return served[i][0] < served[j][0] }) + + require.NotEmpty(t, served, "nothing was fetched for %d-%d", from, to) + assert.Equal(t, from, served[0][0], "coverage must start at the first block") + assert.Equal(t, to, served[len(served)-1][1], "coverage must end at the last block") + + for i := 1; i < len(served); i++ { + prevEnd, thisStart := served[i-1][1], served[i][0] + assert.Equal(t, prevEnd+1, thisStart, + "chunk %d starts at %d but the previous ended at %d", i, thisStart, prevEnd) + } + + var covered uint64 + for _, c := range served { + require.LessOrEqual(t, c[0], c[1], "chunk %d-%d is inverted", c[0], c[1]) + covered += c[1] - c[0] + 1 + } + assert.Equal(t, to-from+1, covered, "total blocks covered must equal the range size") +} + +// Every block in the range must be fetched exactly once, whatever the span ends +// up being. Off-by-one at a chunk boundary would silently skip a block. +func TestProcessBlockRange_CoversEveryBlockExactlyOnce(t *testing.T) { + cases := []struct { + name string + from, to uint64 + serverSpan uint64 // widest query the server will accept + }{ + {"single block", 1, 1, maxBlockRange}, + {"single block at zero", 0, 0, maxBlockRange}, + {"range starting at zero", 0, 500, maxBlockRange}, + {"exactly one full span", 1, maxBlockRange, maxBlockRange}, + {"one block past a full span", 1, maxBlockRange + 1, maxBlockRange}, + {"one block short of a full span", 1, maxBlockRange - 1, maxBlockRange}, + {"several full spans", 1, maxBlockRange * 3, maxBlockRange}, + {"several spans plus a remainder", 1, maxBlockRange*2 + 137, maxBlockRange}, + {"forced shrink, divisible", 1, 2000, 1000}, + {"forced shrink, not divisible", 1, 2500, 333}, + {"forced shrink to the floor", 1, 1000, minBlockRange}, + {"shrink with an odd start", 4097, 9999, 700}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + srv := &logQueryServer{maxSpan: tc.serverSpan} + el := newRangeListener(t, srv.start(t)) + + next, err := el.processBlockRange(context.Background(), tc.from, tc.to, nil) + require.NoError(t, err) + assert.Equal(t, tc.to+1, next, "must report the range as fully covered") + + assertExactCoverage(t, srv.servedSpans(), tc.from, tc.to) + }) + } +} + +// After a shrink the walk continues at the smaller span. The blocks either side +// of the failure boundary must still be covered exactly once. +func TestProcessBlockRange_NoGapAroundAShrink(t *testing.T) { + srv := &logQueryServer{maxSpan: 750} + el := newRangeListener(t, srv.start(t)) + + next, err := el.processBlockRange(context.Background(), 100, 3100, nil) + require.NoError(t, err) + assert.Equal(t, uint64(3101), next) + + assertExactCoverage(t, srv.servedSpans(), 100, 3100) +} + +// Across successive polls the caller resumes from the block the previous call +// reported, so a partial range must hand back a boundary that leaves no hole. +func TestProcessBlockRange_ResumeAfterPartialLeavesNoGap(t *testing.T) { + // Blocks from 5001 are unreadable, so the first call stops there. + srv := &logQueryServer{maxSpan: maxBlockRange, failFrom: 5001} + el := newRangeListener(t, srv.start(t)) + + next, err := el.processBlockRange(context.Background(), 1, 8000, nil) + require.Error(t, err) + assertExactCoverage(t, srv.servedSpans(), 1, next-1) + + // The obstruction clears and the caller resumes from where it stopped. + srv2 := &logQueryServer{maxSpan: maxBlockRange} + el2 := newRangeListener(t, srv2.start(t)) + + final, err := el2.processBlockRange(context.Background(), next, 8000, nil) + require.NoError(t, err) + assert.Equal(t, uint64(8001), final) + assertExactCoverage(t, srv2.servedSpans(), next, 8000) +} From 757157dea31cc7c3a8ae33a40c790597a835c759 Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Mon, 24 Aug 2026 07:20:03 +0530 Subject: [PATCH 13/60] fix: F-2026-18201 | [Dual Defense] Staking Precompile and Vesting Underflow StateDB Balance Enabling Native Mint and Drain (#315) Rejects all three cosmos vesting MsgCreate* types at the top level, not just inside authz.MsgExec. --- app/ante/ante_cosmos.go | 10 + app/ante/blocked_msgs.go | 91 ++++++++ app/ante/blocked_msgs_test.go | 147 +++++++++++++ test/integration/ante/vesting_blocked_test.go | 200 ++++++++++++++++++ 4 files changed, 448 insertions(+) create mode 100644 app/ante/blocked_msgs.go create mode 100644 app/ante/blocked_msgs_test.go create mode 100644 test/integration/ante/vesting_blocked_test.go diff --git a/app/ante/ante_cosmos.go b/app/ante/ante_cosmos.go index 08be3f011..1f4768e55 100755 --- a/app/ante/ante_cosmos.go +++ b/app/ante/ante_cosmos.go @@ -25,6 +25,16 @@ func NewCosmosAnteHandler(ctx sdk.Context, options HandlerOptions) sdk.AnteHandl sdk.MsgTypeURL(&evmtypes.MsgEthereumTx{}), sdk.MsgTypeURL(&sdkvesting.MsgCreateVestingAccount{}), ), + // Vesting accounts can delegate locked coins, but the EVM state view only + // tracks spendable balance. Delegating more than the spendable balance makes + // the StateDB subtract more than it holds, which reconciles back to bank as a + // mint (or a burn for the victim). Block vesting-account creation outright so + // the precondition cannot be created permissionlessly. + NewBlockedMsgsDecorator( + sdk.MsgTypeURL(&sdkvesting.MsgCreateVestingAccount{}), + sdk.MsgTypeURL(&sdkvesting.MsgCreatePermanentLockedAccount{}), + sdk.MsgTypeURL(&sdkvesting.MsgCreatePeriodicVestingAccount{}), + ), ante.NewSetUpContextDecorator(), wasmkeeper.NewLimitSimulationGasDecorator(options.WasmConfig.SimulationGasLimit), // after setup context to enforce limits early diff --git a/app/ante/blocked_msgs.go b/app/ante/blocked_msgs.go new file mode 100644 index 000000000..a1cae66b3 --- /dev/null +++ b/app/ante/blocked_msgs.go @@ -0,0 +1,91 @@ +package ante + +import ( + "fmt" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + errortypes "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/x/authz" +) + +// maxNestedBlockedMsgs caps how deep the decorator recurses into nested +// authz.MsgExec messages while looking for blocked msg types. +const maxNestedBlockedMsgs = 7 + +// BlockedMsgsDecorator rejects a fixed set of msg type URLs anywhere in a tx: +// at the top level, and nested inside authz.MsgExec (arbitrarily deep, up to +// maxNestedBlockedMsgs). +// +// It complements cosmosante.NewAuthzLimiterDecorator, which only blocks msgs +// carried *inside* an authz message and lets the same msg through when it is +// submitted directly. +type BlockedMsgsDecorator struct { + // blockedMsgTypes is the set of msg type URLs to reject. + blockedMsgTypes map[string]struct{} +} + +// NewBlockedMsgsDecorator creates a decorator that rejects the given msg type +// URLs regardless of where they appear in the tx. +func NewBlockedMsgsDecorator(blockedMsgTypes ...string) BlockedMsgsDecorator { + blocked := make(map[string]struct{}, len(blockedMsgTypes)) + for _, msgType := range blockedMsgTypes { + blocked[msgType] = struct{}{} + } + + return BlockedMsgsDecorator{blockedMsgTypes: blocked} +} + +func (bmd BlockedMsgsDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { + if err := bmd.checkBlockedMsgs(tx.GetMsgs(), 1); err != nil { + return ctx, errorsmod.Wrapf(errortypes.ErrUnauthorized, "%s", err.Error()) + } + + return next(ctx, tx, simulate) +} + +// checkBlockedMsgs walks the msgs and returns an error on the first blocked msg +// type it finds. authz.MsgExec is unwrapped so a blocked msg cannot be smuggled +// through the authz module; authz.MsgGrant is checked so a grant for a blocked +// msg type cannot be created either. +func (bmd BlockedMsgsDecorator) checkBlockedMsgs(msgs []sdk.Msg, nestedLvl int) error { + if nestedLvl >= maxNestedBlockedMsgs { + return fmt.Errorf("found more nested msgs than permitted; got: %d, expected: <%d", nestedLvl, maxNestedBlockedMsgs) + } + + for _, msg := range msgs { + switch msg := msg.(type) { + case *authz.MsgExec: + innerMsgs, err := msg.GetMessages() + if err != nil { + return err + } + if err := bmd.checkBlockedMsgs(innerMsgs, nestedLvl+1); err != nil { + return err + } + case *authz.MsgGrant: + authorization, err := msg.GetAuthorization() + if err != nil { + return err + } + if err := bmd.rejectIfBlocked(authorization.MsgTypeURL()); err != nil { + return err + } + default: + if err := bmd.rejectIfBlocked(sdk.MsgTypeURL(msg)); err != nil { + return err + } + } + } + + return nil +} + +func (bmd BlockedMsgsDecorator) rejectIfBlocked(msgTypeURL string) error { + if _, blocked := bmd.blockedMsgTypes[msgTypeURL]; blocked { + return fmt.Errorf("found blocked msg type: %s", msgTypeURL) + } + + return nil +} diff --git a/app/ante/blocked_msgs_test.go b/app/ante/blocked_msgs_test.go new file mode 100644 index 000000000..b0f3af2b3 --- /dev/null +++ b/app/ante/blocked_msgs_test.go @@ -0,0 +1,147 @@ +package ante_test + +import ( + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + sdkvesting "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + "github.com/cosmos/cosmos-sdk/x/authz" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/stretchr/testify/require" + + "github.com/pushchain/push-chain-node/app/ante" +) + +// blockedVestingMsgURLs mirrors the list wired into NewCosmosAnteHandler. +var blockedVestingMsgURLs = []string{ + sdk.MsgTypeURL(&sdkvesting.MsgCreateVestingAccount{}), + sdk.MsgTypeURL(&sdkvesting.MsgCreatePermanentLockedAccount{}), + sdk.MsgTypeURL(&sdkvesting.MsgCreatePeriodicVestingAccount{}), +} + +func vestingTestAddrs() (from, to sdk.AccAddress) { + return sdk.AccAddress([]byte("from________________")), sdk.AccAddress([]byte("to__________________")) +} + +// nestMsgExec wraps msgs in `depth` levels of authz.MsgExec. +func nestMsgExec(grantee sdk.AccAddress, depth int, msgs []sdk.Msg) sdk.Msg { + inner := msgs + var out sdk.Msg + for i := 0; i < depth; i++ { + exec := authz.NewMsgExec(grantee, inner) + out = &exec + inner = []sdk.Msg{out} + } + return out +} + +// TestBlockedMsgsDecorator_VestingMsgs asserts that all three vesting-account +// creation msgs are rejected at the TOP LEVEL of a tx (F-2026-18201). Before +// this decorator only MsgCreateVestingAccount was blocked, and only when nested +// inside an authz.MsgExec, so a plain top-level tx created the vesting account +// that the staking-precompile underflow attack needs. +func TestBlockedMsgsDecorator_VestingMsgs(t *testing.T) { + from, to := vestingTestAddrs() + amount := sdk.NewCoins(sdk.NewInt64Coin("upc", 1_000_000)) + future := time.Date(9000, 1, 1, 0, 0, 0, 0, time.UTC) + + createVesting := sdkvesting.NewMsgCreateVestingAccount(from, to, amount, future.Unix(), false) + createPermanentLocked := sdkvesting.NewMsgCreatePermanentLockedAccount(from, to, amount) + createPeriodicVesting := sdkvesting.NewMsgCreatePeriodicVestingAccount(from, to, 0, []sdkvesting.Period{ + {Length: 3600, Amount: amount}, + }) + send := banktypes.NewMsgSend(from, to, amount) + + decorator := ante.NewBlockedMsgsDecorator(blockedVestingMsgURLs...) + + testCases := []struct { + name string + msgs []sdk.Msg + expFail bool + }{ + {"allowed msg passes", []sdk.Msg{send}, false}, + {"top-level MsgCreateVestingAccount", []sdk.Msg{createVesting}, true}, + {"top-level MsgCreatePermanentLockedAccount", []sdk.Msg{createPermanentLocked}, true}, + {"top-level MsgCreatePeriodicVestingAccount", []sdk.Msg{createPeriodicVesting}, true}, + {"blocked msg alongside allowed msgs", []sdk.Msg{send, createPermanentLocked, send}, true}, + { + "blocked msg inside authz.MsgExec", + []sdk.Msg{nestMsgExec(from, 1, []sdk.Msg{createPermanentLocked})}, + true, + }, + { + "blocked msg inside deeply nested authz.MsgExec", + []sdk.Msg{nestMsgExec(from, 4, []sdk.Msg{createPeriodicVesting})}, + true, + }, + { + "allowed msg inside authz.MsgExec passes", + []sdk.Msg{nestMsgExec(from, 2, []sdk.Msg{send})}, + false, + }, + { + "nesting deeper than the cap is rejected", + []sdk.Msg{nestMsgExec(from, 8, []sdk.Msg{send})}, + true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + tx := mockFeeTx{msgs: tc.msgs} + + called := false + next := func(ctx sdk.Context, _ sdk.Tx, _ bool) (sdk.Context, error) { + called = true + return ctx, nil + } + + _, err := decorator.AnteHandle(sdk.Context{}, tx, false, next) + if tc.expFail { + require.Error(t, err) + require.ErrorIs(t, err, sdkerrors.ErrUnauthorized) + require.False(t, called, "blocked tx must not reach the next decorator") + return + } + + require.NoError(t, err) + require.True(t, called, "allowed tx must reach the next decorator") + }) + } +} + +// TestBlockedMsgsDecorator_AuthzGrant asserts that an authz grant for a blocked +// vesting msg type cannot be created either, so the block cannot be sidestepped +// by pre-authorizing a grantee. +func TestBlockedMsgsDecorator_AuthzGrant(t *testing.T) { + from, to := vestingTestAddrs() + future := time.Date(9000, 1, 1, 0, 0, 0, 0, time.UTC) + + decorator := ante.NewBlockedMsgsDecorator(blockedVestingMsgURLs...) + + for _, msgURL := range blockedVestingMsgURLs { + t.Run(msgURL, func(t *testing.T) { + grant, err := authz.NewMsgGrant(from, to, authz.NewGenericAuthorization(msgURL), &future) + require.NoError(t, err) + + _, err = decorator.AnteHandle(sdk.Context{}, mockFeeTx{msgs: []sdk.Msg{grant}}, false, noopAnteNext) + require.Error(t, err) + require.ErrorIs(t, err, sdkerrors.ErrUnauthorized) + }) + } + + t.Run("grant for an allowed msg type passes", func(t *testing.T) { + grant, err := authz.NewMsgGrant(from, to, + authz.NewGenericAuthorization(sdk.MsgTypeURL(&banktypes.MsgSend{})), &future) + require.NoError(t, err) + + _, err = decorator.AnteHandle(sdk.Context{}, mockFeeTx{msgs: []sdk.Msg{grant}}, false, noopAnteNext) + require.NoError(t, err) + }) +} + +func noopAnteNext(ctx sdk.Context, _ sdk.Tx, _ bool) (sdk.Context, error) { + return ctx, nil +} diff --git a/test/integration/ante/vesting_blocked_test.go b/test/integration/ante/vesting_blocked_test.go new file mode 100644 index 000000000..92deba343 --- /dev/null +++ b/test/integration/ante/vesting_blocked_test.go @@ -0,0 +1,200 @@ +package ante_test + +import ( + "testing" + "time" + + abci "github.com/cometbft/cometbft/abci/types" + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + cmttypes "github.com/cometbft/cometbft/types" + "github.com/stretchr/testify/require" + + sdkmath "cosmossdk.io/math" + + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/testutil/mock" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + sdkvesting "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + "github.com/pushchain/push-chain-node/app" +) + +// testChainID must be a chain ID app.EVMAppOptions knows about, otherwise +// NewChainApp panics while configuring the EVM coin info. +var testChainID = app.ChainID + +// setupVestingAnteApp boots a chain app with a single validator and one funded +// genesis account whose private key we keep, so we can sign real txs and push +// them through the full baseapp -> ante pipeline. +func setupVestingAnteApp(t *testing.T) (*app.ChainApp, cryptotypes.PrivKey, sdk.AccAddress) { + t.Helper() + + privVal := mock.NewPV() + valPubKey, err := privVal.GetPubKey() + require.NoError(t, err) + + valSet := cmttypes.NewValidatorSet([]*cmttypes.Validator{cmttypes.NewValidator(valPubKey, 1)}) + + senderPrivKey := secp256k1.GenPrivKey() + senderAcc := authtypes.NewBaseAccount(senderPrivKey.PubKey().Address().Bytes(), senderPrivKey.PubKey(), 0, 0) + senderAddr := senderAcc.GetAddress() + + balance := banktypes.Balance{ + Address: senderAddr.String(), + Coins: sdk.NewCoins( + sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(100_000_000_000_000)), + // Enough of the EVM denom to actually pay the fee, so that the tx is + // only ever rejected because of the msg type and not because it is + // underfunded. + sdk.NewCoin(app.BaseDenom, sdkmath.NewInt(1).MulRaw(1e18).MulRaw(100)), + ), + } + + chainApp := app.SetupWithGenesisValSet( + t, valSet, []authtypes.GenesisAccount{senderAcc}, testChainID, nil, balance, + ) + + return chainApp, senderPrivKey, senderAddr +} + +// disableInflation zeroes out the mint module so that the only thing that can +// change total supply across the test block is the tx under test, not block +// inflation. Written through an uncached context so it survives into +// FinalizeBlock. +func disableInflation(t *testing.T, chainApp *app.ChainApp) { + t.Helper() + + ctx := chainApp.BaseApp.NewUncachedContext(false, cmtproto.Header{}) + + params, err := chainApp.MintKeeper.Params.Get(ctx) + require.NoError(t, err) + params.InflationMin = sdkmath.LegacyZeroDec() + params.InflationMax = sdkmath.LegacyZeroDec() + params.InflationRateChange = sdkmath.LegacyZeroDec() + require.NoError(t, chainApp.MintKeeper.Params.Set(ctx, params)) + + minter, err := chainApp.MintKeeper.Minter.Get(ctx) + require.NoError(t, err) + minter.Inflation = sdkmath.LegacyZeroDec() + minter.AnnualProvisions = sdkmath.LegacyZeroDec() + require.NoError(t, chainApp.MintKeeper.Minter.Set(ctx, minter)) +} + +func totalSupply(t *testing.T, chainApp *app.ChainApp, ctx sdk.Context) sdk.Coins { + t.Helper() + + supply := sdk.NewCoins() + chainApp.BankKeeper.IterateTotalSupply(ctx, func(coin sdk.Coin) bool { + supply = supply.Add(coin) + return false + }) + + return supply +} + +// TestVestingAccountCreationBlockedEndToEnd is the chain-level regression test +// for F-2026-18201. +// +// The staking-precompile underflow attack needs a vesting account: the EVM state +// view tracks only SPENDABLE balance, while Cosmos lets a vesting account +// DELEGATE locked coins. Delegating more than the spendable balance makes the +// StateDB subtract more than it holds; x/vm/keeper/statedb.go then reconciles +// that bogus view back into bank by MINTING the difference (or by BURNING a +// victim's real coins on the wrap-transfer variant). +// +// Vesting-account creation used to be permissionless: NewAuthzLimiterDecorator +// blocked MsgCreateVestingAccount only INSIDE an authz.MsgExec, so a plain +// top-level tx went straight through - and MsgCreatePermanentLockedAccount / +// MsgCreatePeriodicVestingAccount were not blocked anywhere at all. This test +// submits each of the three as a real signed tx and asserts that it is rejected, +// that no vesting account is created, and that neither the sender's balance nor +// total native supply moves. +func TestVestingAccountCreationBlockedEndToEnd(t *testing.T) { + // The vesting amount is denominated in the EVM/staking denom, which is what + // makes the account a usable attack primitive in the first place. + amount := sdk.NewCoins(sdk.NewCoin(app.BaseDenom, sdkmath.NewInt(1).MulRaw(1e18))) + future := time.Now().Add(365 * 24 * time.Hour).Unix() + + // Comfortably above the dynamic min gas price so the tx is not rejected by + // the fee decorators instead of the blocked-msgs decorator. + fees := sdk.NewCoins(sdk.NewCoin(app.BaseDenom, + sdkmath.NewInt(1e10).MulRaw(int64(simtestutil.DefaultGenTxGas)))) + + testCases := []struct { + name string + msg func(from, to sdk.AccAddress) sdk.Msg + }{ + { + "MsgCreateVestingAccount", + func(from, to sdk.AccAddress) sdk.Msg { + return sdkvesting.NewMsgCreateVestingAccount(from, to, amount, future, false) + }, + }, + { + "MsgCreatePermanentLockedAccount", + func(from, to sdk.AccAddress) sdk.Msg { + return sdkvesting.NewMsgCreatePermanentLockedAccount(from, to, amount) + }, + }, + { + "MsgCreatePeriodicVestingAccount", + func(from, to sdk.AccAddress) sdk.Msg { + return sdkvesting.NewMsgCreatePeriodicVestingAccount(from, to, time.Now().Unix(), + []sdkvesting.Period{{Length: 3600, Amount: amount}}) + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + chainApp, senderPriv, senderAddr := setupVestingAnteApp(t) + disableInflation(t, chainApp) + victimAddr := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address().Bytes()) + + ctx := chainApp.BaseApp.NewContext(true) + senderAccount := chainApp.AccountKeeper.GetAccount(ctx, senderAddr) + require.NotNil(t, senderAccount) + + supplyBefore := totalSupply(t, chainApp, ctx) + senderBalanceBefore := chainApp.BankKeeper.GetAllBalances(ctx, senderAddr) + victimBalanceBefore := chainApp.BankKeeper.GetAllBalances(ctx, victimAddr) + + res, err := app.SignAndDeliverWithoutCommit( + t, + chainApp.TxConfig(), + chainApp.BaseApp, + []sdk.Msg{tc.msg(senderAddr, victimAddr)}, + fees, + testChainID, + []uint64{senderAccount.GetAccountNumber()}, + []uint64{senderAccount.GetSequence()}, + time.Now(), + senderPriv, + ) + require.NoError(t, err, "block must still be produced") + require.Len(t, res.TxResults, 1) + + txRes := res.TxResults[0] + require.NotEqual(t, abci.CodeTypeOK, txRes.Code, + "vesting account creation must be rejected in ante, got success: %s", txRes.Log) + require.Contains(t, txRes.Log, "found blocked msg type", + "tx must be rejected by the blocked-msgs decorator, got: %s", txRes.Log) + + // The tx failed in ante, so nothing it would have done may be visible. + ctxAfter := chainApp.BaseApp.NewContext(true) + + require.Nil(t, chainApp.AccountKeeper.GetAccount(ctxAfter, victimAddr), + "no vesting account may be created") + require.Equal(t, victimBalanceBefore, chainApp.BankKeeper.GetAllBalances(ctxAfter, victimAddr), + "victim spendable balance must be unchanged") + require.Equal(t, senderBalanceBefore, chainApp.BankKeeper.GetAllBalances(ctxAfter, senderAddr), + "sender spendable balance must be unchanged") + require.Equal(t, supplyBefore, totalSupply(t, chainApp, ctxAfter), + "total native supply must be unchanged across the tx") + }) + } +} From 6a1403ef3fa4b5e33b4b852f89a88b13e820a0cb Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Mon, 24 Aug 2026 07:34:56 +0530 Subject: [PATCH 14/60] fix: F-2026-18200 | [Dual Defense] Variable-Length Signer Addresses Enable Universal Executor Module Impersonation (#316) * fix: F-2026-18200 | bind gasless signer to signing key and reject non-20-byte addresses Ante now enforces pubKey.Address() == signer for new gasless accounts, and GetAddressPair / MustConvertCosmosToHex reject anything that is not exactly 20 bytes instead of truncating onto a module address. * fix: drop signature gas consumption from the gasless new-account path Gasless txs skip fee deduction entirely, so charging gas has no economic effect. The TxSigLimit cap alone bounds the multisig work; reverts the NewAccountInitDecorator signature change and the SigGasConsumer wiring. --- app/ante/account_init_decorator.go | 45 ++- app/ante/account_init_signer_binding_test.go | 298 ++++++++++++++++++ app/ante/ante_cosmos.go | 3 +- .../uexecutor/chain_enabled_test.go | 2 +- .../uexecutor/execute_payload_test.go | 16 +- utils/address.go | 25 +- utils/address_test.go | 92 ++++++ x/uexecutor/types/msg_execute_payload.go | 15 +- x/uexecutor/types/msg_migrate_uea.go | 15 +- x/uexecutor/types/msg_signer_length_test.go | 107 +++++++ 10 files changed, 602 insertions(+), 16 deletions(-) create mode 100644 app/ante/account_init_signer_binding_test.go create mode 100644 utils/address_test.go create mode 100644 x/uexecutor/types/msg_signer_length_test.go diff --git a/app/ante/account_init_decorator.go b/app/ante/account_init_decorator.go index 29e6d4eaa..b117c938c 100644 --- a/app/ante/account_init_decorator.go +++ b/app/ante/account_init_decorator.go @@ -1,6 +1,7 @@ package ante import ( + "bytes" "fmt" sdk "github.com/cosmos/cosmos-sdk/types" @@ -12,6 +13,7 @@ import ( codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/types/tx/signing" + "github.com/cosmos/cosmos-sdk/x/auth/ante" authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" txpolicy "github.com/pushchain/push-chain-node/app/txpolicy" ) @@ -55,7 +57,7 @@ func (aid AccountInitDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate "address", sdk.AccAddress(newAccAddr).String(), "simulate", simulate, ) - // if account does not exist on chain, bypass rest of ante chain (especially gas and signature verification) here. + // if account does not exist on chain, bypass rest of ante chain here. // Perform signature verification on account number e and sequence number e instead. if err := aid.verifySignatureForNewAccount(ctx, tx, simulate); err != nil { ctx.Logger().Debug("account init decorator: signature verification failed for new account", @@ -103,13 +105,52 @@ func (aid AccountInitDecorator) verifySignatureForNewAccount(ctx sdk.Context, tx return errorsmod.Wrapf(sdkerrors.ErrUnauthorized, "invalid number of signer; expected: %d, got %d", len(signers), len(sigs)) } - newAccAddr := sdk.AccAddress(signers[0]) + params := aid.ak.GetParams(ctx) + + // Enforce the signature count limit before doing any verification work. + // This decorator short-circuits the ante chain for new accounts, so + // ante.ValidateSigCountDecorator never runs for them; without this hard cap + // a gasless tx could carry an arbitrarily large multisig key and force the + // node to verify every sub-signature. Gas is deliberately NOT consumed here: + // gasless txs skip fee deduction entirely, so charging gas would cost an + // attacker nothing - the count cap is what actually bounds the work. + sigCount := 0 for _, sig := range sigs { + if sig.PubKey == nil { + return errorsmod.Wrap(sdkerrors.ErrInvalidPubKey, "pubkey is not provided in signature") + } + sigCount += ante.CountSubKeys(sig.PubKey) + if uint64(sigCount) > params.TxSigLimit { + return errorsmod.Wrapf(sdkerrors.ErrTooManySignatures, + "signatures: %d, limit: %d", sigCount, params.TxSigLimit) + } + } + + newAccAddr := sdk.AccAddress(signers[0]) + for i, sig := range sigs { pubKey := sig.PubKey if pubKey == nil { return errorsmod.Wrap(sdkerrors.ErrInvalidPubKey, "pubkey is not provided in signature") } + // Bind the declared signer to the key that actually signed the tx. + // + // VerifySignature below only proves "this key signed this tx"; it says + // nothing about WHO the tx claims to be from. Because this decorator + // short-circuits the ante chain for new accounts, the SDK's + // SetPubKeyDecorator - which owns this check - never runs, so a tx could + // declare an arbitrary signer while being signed by an unrelated key. + // Bech32 account addresses may be up to 255 bytes, and downstream + // conversion to a 20-byte EVM address keeps only the rightmost bytes, so + // a crafted longer signer could alias a module address. + // + // Guards mirror x/auth/ante/sigverify.go exactly so simulation and gas + // estimation keep working. + if !simulate && ctx.IsSigverifyTx() && !bytes.Equal(pubKey.Address().Bytes(), signers[i]) { + return errorsmod.Wrapf(sdkerrors.ErrInvalidPubKey, + "pubKey does not match signer address %s with signer index: %d", sdk.AccAddress(signers[i]).String(), i) + } + // retrieve signer data chainID := ctx.ChainID() var accSequence uint64 = 0 diff --git a/app/ante/account_init_signer_binding_test.go b/app/ante/account_init_signer_binding_test.go new file mode 100644 index 000000000..4173e17f8 --- /dev/null +++ b/app/ante/account_init_signer_binding_test.go @@ -0,0 +1,298 @@ +package ante_test + +import ( + "context" + "fmt" + "testing" + + kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + + clienttx "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/std" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + + "github.com/pushchain/push-chain-node/app/ante" + appparams "github.com/pushchain/push-chain-node/app/params" + uexecutortypes "github.com/pushchain/push-chain-node/x/uexecutor/types" +) + +// uexecutorModuleEVMAddr is the EVM address of the uexecutor module account - +// sha256("uexecutor")[:20]. The UEA contract trusts calls coming from it +// unconditionally, which is what makes aliasing onto it so damaging. +const uexecutorModuleEVMAddr = "0x14191Ea54B4c176fCf86f51b0FAc7CB1E71Df7d7" + +const anteTestChainID = "push_9000-1" + +// newSignerBindingEncodingConfig returns an encoding config able to build and +// sign real uexecutor transactions. +func newSignerBindingEncodingConfig(t *testing.T) appparams.EncodingConfig { + t.Helper() + encCfg := appparams.MakeEncodingConfig() + std.RegisterInterfaces(encCfg.InterfaceRegistry) + authtypes.RegisterInterfaces(encCfg.InterfaceRegistry) + uexecutortypes.RegisterInterfaces(encCfg.InterfaceRegistry) + return encCfg +} + +// aliasedSigner returns a `length`-byte address whose RIGHTMOST 20 bytes are the +// uexecutor module account. common.BytesToAddress keeps exactly those bytes, so +// every such address collapses onto the module's EVM address. +func aliasedSigner(t *testing.T, length int) sdk.AccAddress { + t.Helper() + require.Greater(t, length, common.AddressLength) + + moduleAddr := authtypes.NewModuleAddress(uexecutortypes.ModuleName) + require.Len(t, moduleAddr, common.AddressLength) + require.Equal(t, uexecutorModuleEVMAddr, common.BytesToAddress(moduleAddr).Hex()) + + prefix := make([]byte, length-common.AddressLength) + prefix[0] = 0x01 + addr := sdk.AccAddress(append(prefix, moduleAddr...)) + require.Len(t, addr, length) + + // The whole point of the finding: this longer address truncates onto the + // module's EVM address downstream. + require.Equal(t, uexecutorModuleEVMAddr, common.BytesToAddress(addr).Hex()) + return addr +} + +// gaslessMsgFor builds one of the two user-facing gasless messages with the +// given declared signer. +func gaslessMsgFor(t *testing.T, msgType string, signer sdk.AccAddress) sdk.Msg { + t.Helper() + ua := &uexecutortypes.UniversalAccountId{ + ChainNamespace: "eip155", + ChainId: "11155111", + Owner: "0x000000000000000000000000000000000000dead", + } + + switch msgType { + case "MsgExecutePayload": + return &uexecutortypes.MsgExecutePayload{ + Signer: signer.String(), + UniversalAccountId: ua, + UniversalPayload: &uexecutortypes.UniversalPayload{ + To: "0x000000000000000000000000000000000000dead", + Data: "0xabcdef", + }, + VerificationData: "0xabcdef", + } + case "MsgMigrateUEA": + return &uexecutortypes.MsgMigrateUEA{ + Signer: signer.String(), + UniversalAccountId: ua, + MigrationPayload: &uexecutortypes.MigrationPayload{ + Migration: "0x000000000000000000000000000000000000beef", + Nonce: "0", + Deadline: "1", + }, + Signature: "0xabcdef", + } + default: + t.Fatalf("unknown msg type %q", msgType) + return nil + } +} + +// buildSignedTx returns a tx carrying msg whose declared signer is +// `declaredSigner` but which is signed by `priv` - the two need not be related, +// which is exactly the confusion the fix has to reject. +func buildSignedTx(t *testing.T, encCfg appparams.EncodingConfig, msg sdk.Msg, declaredSigner sdk.AccAddress, priv cryptotypes.PrivKey) sdk.Tx { + t.Helper() + + txb := encCfg.TxConfig.NewTxBuilder() + require.NoError(t, txb.SetMsgs(msg)) + txb.SetGasLimit(300_000) + + require.NoError(t, txb.SetSignatures(signing.SignatureV2{ + PubKey: priv.PubKey(), + Data: &signing.SingleSignatureData{SignMode: signing.SignMode_SIGN_MODE_DIRECT}, + Sequence: 0, + })) + + // The gasless new-account path signs over account number 0 / sequence 0, + // since the account does not exist on chain yet. + signerData := authsigning.SignerData{ + Address: declaredSigner.String(), + ChainID: anteTestChainID, + AccountNumber: 0, + Sequence: 0, + PubKey: priv.PubKey(), + } + + sigV2, err := clienttx.SignWithPrivKey( + context.Background(), signing.SignMode_SIGN_MODE_DIRECT, signerData, + txb, priv, encCfg.TxConfig, 0, + ) + require.NoError(t, err) + require.NoError(t, txb.SetSignatures(sigV2)) + + return txb.GetTx() +} + +func newSignerBindingDecorator(t *testing.T, encCfg appparams.EncodingConfig) (ante.AccountInitDecorator, *mockAccountKeeperAnte) { + t.Helper() + ak := newMockAccountKeeperAnte(sdk.AccAddress([]byte("feeCollector"))) + return ante.NewAccountInitDecorator(ak, encCfg.TxConfig.SignModeHandler()), ak +} + +// TestAccountInitDecorator_RejectsAliasedModuleSigner is the regression test for +// F-2026-18200: a gasless tx may not declare an over-long signer that truncates +// onto the uexecutor module address while being signed by an unrelated key. +// +// Hacken's PoC only used the 21-byte case; truncation works for ANY length > 20, +// so 21, 22 and 32 bytes are all covered, against both gasless messages. +func TestAccountInitDecorator_RejectsAliasedModuleSigner(t *testing.T) { + encCfg := newSignerBindingEncodingConfig(t) + + for _, msgType := range []string{"MsgExecutePayload", "MsgMigrateUEA"} { + for _, length := range []int{21, 22, 32} { + t.Run(fmt.Sprintf("%s/%dbytes", msgType, length), func(t *testing.T) { + attackerKey := secp256k1.GenPrivKey() + declaredSigner := aliasedSigner(t, length) + msg := gaslessMsgFor(t, msgType, declaredSigner) + tx := buildSignedTx(t, encCfg, msg, declaredSigner, attackerKey) + + aid, ak := newSignerBindingDecorator(t, encCfg) + ctx := newAnteTestCtx(t, false).WithChainID(anteTestChainID) + + nextCalled := false + _, err := aid.AnteHandle(ctx, tx, false, func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { + nextCalled = true + return ctx, nil + }) + + require.Error(t, err, "aliased signer must not pass the ante chain") + require.True(t, sdkerrors.ErrInvalidPubKey.Is(err), "expected ErrInvalidPubKey, got: %v", err) + require.False(t, nextCalled, "the message must never reach execution") + require.False(t, ak.HasAccount(context.Background(), declaredSigner), + "no account may be persisted for a rejected signer") + }) + } + } +} + +// TestAccountInitDecorator_RejectsMismatchedSigner covers the general case: a +// well-formed 20-byte signer that is simply not the address of the signing key. +func TestAccountInitDecorator_RejectsMismatchedSigner(t *testing.T) { + encCfg := newSignerBindingEncodingConfig(t) + + attackerKey := secp256k1.GenPrivKey() + victimKey := secp256k1.GenPrivKey() + declaredSigner := sdk.AccAddress(victimKey.PubKey().Address()) + + msg := gaslessMsgFor(t, "MsgExecutePayload", declaredSigner) + tx := buildSignedTx(t, encCfg, msg, declaredSigner, attackerKey) + + aid, ak := newSignerBindingDecorator(t, encCfg) + ctx := newAnteTestCtx(t, false).WithChainID(anteTestChainID) + + _, err := aid.AnteHandle(ctx, tx, false, emptyNext) + require.Error(t, err) + require.True(t, sdkerrors.ErrInvalidPubKey.Is(err), "expected ErrInvalidPubKey, got: %v", err) + require.False(t, ak.HasAccount(context.Background(), declaredSigner)) +} + +// TestAccountInitDecorator_AcceptsMatchingSigner is the positive control: a +// normal 20-byte signer whose key matches still creates the account and passes. +func TestAccountInitDecorator_AcceptsMatchingSigner(t *testing.T) { + encCfg := newSignerBindingEncodingConfig(t) + + for _, msgType := range []string{"MsgExecutePayload", "MsgMigrateUEA"} { + t.Run(msgType, func(t *testing.T) { + key := secp256k1.GenPrivKey() + signer := sdk.AccAddress(key.PubKey().Address()) + require.Len(t, signer, common.AddressLength) + + msg := gaslessMsgFor(t, msgType, signer) + tx := buildSignedTx(t, encCfg, msg, signer, key) + + aid, ak := newSignerBindingDecorator(t, encCfg) + ctx := newAnteTestCtx(t, false).WithChainID(anteTestChainID) + + _, err := aid.AnteHandle(ctx, tx, false, emptyNext) + require.NoError(t, err) + + acc := ak.GetAccount(context.Background(), signer) + require.NotNil(t, acc, "the account must be created for a legitimate gasless tx") + require.Equal(t, uint64(1), acc.GetSequence()) + }) + } +} + +// TestAccountInitDecorator_SimulationUnaffected checks that the new binding +// check keeps the SDK's `!simulate` guard, so simulation and gas estimation - +// which carry no usable signature - keep working. +func TestAccountInitDecorator_SimulationUnaffected(t *testing.T) { + encCfg := newSignerBindingEncodingConfig(t) + + attackerKey := secp256k1.GenPrivKey() + victimKey := secp256k1.GenPrivKey() + + for name, declaredSigner := range map[string]sdk.AccAddress{ + "matching_signer": sdk.AccAddress(attackerKey.PubKey().Address()), + "mismatched_signer": sdk.AccAddress(victimKey.PubKey().Address()), + } { + t.Run(name, func(t *testing.T) { + msg := gaslessMsgFor(t, "MsgExecutePayload", declaredSigner) + tx := buildSignedTx(t, encCfg, msg, declaredSigner, attackerKey) + + aid, _ := newSignerBindingDecorator(t, encCfg) + ctx := newAnteTestCtx(t, false).WithChainID(anteTestChainID) + + _, err := aid.AnteHandle(ctx, tx, true /* simulate */, emptyNext) + require.NoError(t, err, "simulation must not be affected by the binding check") + }) + } +} + +// TestAccountInitDecorator_EnforcesSignatureLimit covers F-2026-18186: the +// new-account path short-circuits the ante chain, so it has to enforce the +// signature count limit itself instead of verifying an unbounded multisig for +// free. +func TestAccountInitDecorator_EnforcesSignatureLimit(t *testing.T) { + encCfg := newSignerBindingEncodingConfig(t) + + params := authtypes.DefaultParams() + numKeys := int(params.TxSigLimit) + 1 + + pubKeys := make([]cryptotypes.PubKey, numKeys) + sigs := make([]signing.SignatureData, numKeys) + bitArray := cryptotypes.NewCompactBitArray(numKeys) + for i := 0; i < numKeys; i++ { + pubKeys[i] = secp256k1.GenPrivKey().PubKey() + sigs[i] = &signing.SingleSignatureData{ + SignMode: signing.SignMode_SIGN_MODE_DIRECT, + Signature: []byte("not-checked-the-limit-trips-first"), + } + bitArray.SetIndex(i, true) + } + + multisigPk := kmultisig.NewLegacyAminoPubKey(numKeys, pubKeys) + signer := sdk.AccAddress(multisigPk.Address()) + + txb := encCfg.TxConfig.NewTxBuilder() + require.NoError(t, txb.SetMsgs(gaslessMsgFor(t, "MsgExecutePayload", signer))) + txb.SetGasLimit(300_000) + require.NoError(t, txb.SetSignatures(signing.SignatureV2{ + PubKey: multisigPk, + Data: &signing.MultiSignatureData{BitArray: bitArray, Signatures: sigs}, + Sequence: 0, + })) + + aid, ak := newSignerBindingDecorator(t, encCfg) + ctx := newAnteTestCtx(t, false).WithChainID(anteTestChainID) + + _, err := aid.AnteHandle(ctx, txb.GetTx(), false, emptyNext) + require.Error(t, err) + require.True(t, sdkerrors.ErrTooManySignatures.Is(err), "expected ErrTooManySignatures, got: %v", err) + require.False(t, ak.HasAccount(context.Background(), signer)) +} diff --git a/app/ante/ante_cosmos.go b/app/ante/ante_cosmos.go index 1f4768e55..509d6e9ca 100755 --- a/app/ante/ante_cosmos.go +++ b/app/ante/ante_cosmos.go @@ -53,7 +53,8 @@ func NewCosmosAnteHandler(ctx sdk.Context, options HandlerOptions) sdk.AnteHandl // NewAccountInitDecorator must be called before all signature verification decorators and SetPubKeyDecorator // - this // 1. generates the account for the new accounts only for gasless transactions, - // 2. verifies the sig, and + // 2. binds the declared signer to the signing key, enforces the signature + // count limit and verifies the sig, and // 3. bypasses the rest of the ante chain NewAccountInitDecorator(options.AccountKeeper, options.SignModeHandler), // SetPubKeyDecorator must be called before all signature verification decorators diff --git a/test/integration/uexecutor/chain_enabled_test.go b/test/integration/uexecutor/chain_enabled_test.go index ee65cecff..84d5e5df4 100644 --- a/test/integration/uexecutor/chain_enabled_test.go +++ b/test/integration/uexecutor/chain_enabled_test.go @@ -220,7 +220,7 @@ func TestExecutePayload_ChainEnabled(t *testing.T) { ms := uexecutorkeeper.NewMsgServerImpl(testApp.UexecutorKeeper) _, err := ms.ExecutePayload(ctx, &uexecutortypes.MsgExecutePayload{ - Signer: "cosmos1xpurwdecvsenyvpkxvmnge3cv93nyd34xuersef38pjnxen9xfsk2dnz8yek2drrv56qmn2ak9", + Signer: testSigner, UniversalAccountId: &uexecutortypes.UniversalAccountId{ ChainNamespace: "eip155", ChainId: "11155111", diff --git a/test/integration/uexecutor/execute_payload_test.go b/test/integration/uexecutor/execute_payload_test.go index 3a1cf313c..88e61514f 100644 --- a/test/integration/uexecutor/execute_payload_test.go +++ b/test/integration/uexecutor/execute_payload_test.go @@ -15,6 +15,12 @@ import ( "github.com/stretchr/testify/require" ) +// testSigner is the bech32 form of the 20-byte account that these fixtures have +// always resolved to on the EVM side. It replaces an older literal that decoded +// to 42 bytes - GetAddressPair used to truncate it down to exactly these bytes, +// and now rejects it outright. +const testSigner = "cosmos18pjnzwr9xdnx2vnpv5mxywfnv56xxef5cludl5" + func TestExecutePayload(t *testing.T) { app, ctx, _ := utils.SetAppWithValidators(t) @@ -100,7 +106,7 @@ func TestExecutePayload(t *testing.T) { require.NoError(t, err) msg := &uexecutortypes.MsgExecutePayload{ - Signer: "cosmos1xpurwdecvsenyvpkxvmnge3cv93nyd34xuersef38pjnxen9xfsk2dnz8yek2drrv56qmn2ak9", + Signer: testSigner, UniversalAccountId: validUA, UniversalPayload: validUP, VerificationData: "0x91987784d56359fa91c3e3e0332f4f0cffedf9c081eb12874a63b41d5b5e5c660dc827947c2ae26e658d0551ad4b2d2aa073d62691429a0ae239d2cc58055bf11c", @@ -130,7 +136,7 @@ func TestExecutePayload(t *testing.T) { } msg := &uexecutortypes.MsgExecutePayload{ - Signer: "cosmos1xpurwdecvsenyvpkxvmnge3cv93nyd34xuersef38pjnxen9xfsk2dnz8yek2drrv56qmn2ak9", + Signer: testSigner, UniversalAccountId: validUA, UniversalPayload: validUP, } @@ -160,7 +166,7 @@ func TestExecutePayload(t *testing.T) { } msg := &uexecutortypes.MsgExecutePayload{ - Signer: "cosmos1xpurwdecvsenyvpkxvmnge3cv93nyd34xuersef38pjnxen9xfsk2dnz8yek2drrv56qmn2ak9", + Signer: testSigner, UniversalAccountId: validUA, UniversalPayload: validUP, VerificationData: "0xZZZZ", @@ -261,7 +267,7 @@ func TestExecutePayload_AutoDeployOnPreFundedAddress(t *testing.T) { // Submit MsgExecutePayload directly — no standalone DeployUEAV2 call beforehand. msg := &uexecutortypes.MsgExecutePayload{ - Signer: "cosmos1xpurwdecvsenyvpkxvmnge3cv93nyd34xuersef38pjnxen9xfsk2dnz8yek2drrv56qmn2ak9", + Signer: testSigner, UniversalAccountId: validUA, UniversalPayload: validUP, VerificationData: "0x91987784d56359fa91c3e3e0332f4f0cffedf9c081eb12874a63b41d5b5e5c660dc827947c2ae26e658d0551ad4b2d2aa073d62691429a0ae239d2cc58055bf11c", @@ -333,7 +339,7 @@ func TestExecutePayload_RejectWhenUndeployedAndUnfunded(t *testing.T) { } msg := &uexecutortypes.MsgExecutePayload{ - Signer: "cosmos1xpurwdecvsenyvpkxvmnge3cv93nyd34xuersef38pjnxen9xfsk2dnz8yek2drrv56qmn2ak9", + Signer: testSigner, UniversalAccountId: validUA, UniversalPayload: validUP, VerificationData: "0x1234", diff --git a/utils/address.go b/utils/address.go index 0a9ea1f64..cf52c362f 100644 --- a/utils/address.go +++ b/utils/address.go @@ -58,22 +58,41 @@ func ConvertAnyAddressesToBytes[T ByteType](addr ...string) ([]T, error) { return res, nil } -// get address pair returns both the cosmos and the 0x addresses, or an error +// GetAddressPair returns both the cosmos and the 0x addresses, or an error. +// +// The address MUST decode to exactly 20 bytes. The Cosmos SDK accepts bech32 +// account addresses of up to 255 bytes, while common.BytesToAddress silently +// keeps only the RIGHTMOST 20 bytes. A longer address would therefore collapse +// onto an unrelated EVM address - e.g. 0x01 || +// truncates to the uexecutor module itself - so reject it instead of +// truncating. func GetAddressPair(addr string) (sdk.AccAddress, common.Address, error) { bz, err := ConvertAnyAddressToBytes(addr) if err != nil { return nil, common.Address{}, err } + if len(bz) != common.AddressLength { + return nil, common.Address{}, fmt.Errorf( + "invalid address length for %q: got %d bytes, want %d", addr, len(bz), common.AddressLength) + } + return sdk.AccAddress(bz), common.BytesToAddress(bz), nil } +// MustConvertCosmosToHex returns the 0x form of addr, or an empty string when +// addr cannot be represented as a 20-byte EVM address. +// +// It never panics and never truncates: the previous common.Address(bz) +// conversion panicked for inputs shorter than 20 bytes and silently kept the +// LEFTMOST 20 bytes for longer ones - the opposite end from +// common.BytesToAddress used elsewhere in this file. func MustConvertCosmosToHex(addr string) string { bz, err := ConvertAnyAddressToBytes(addr) - if err != nil { + if err != nil || len(bz) != common.AddressLength { return "" } - return common.Address(bz).Hex() + return common.BytesToAddress(bz).Hex() } // create an enum for COSMOS, 0x, or EITHER diff --git a/utils/address_test.go b/utils/address_test.go new file mode 100644 index 000000000..7695564cf --- /dev/null +++ b/utils/address_test.go @@ -0,0 +1,92 @@ +package utils_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + + "github.com/pushchain/push-chain-node/utils" +) + +// uexecutorModuleEVMAddr is sha256("uexecutor")[:20] rendered as an EVM address. +const uexecutorModuleEVMAddr = "0x14191Ea54B4c176fCf86f51b0FAc7CB1E71Df7d7" + +// bech32OfLength returns a bech32 account address that decodes to exactly n bytes. +func bech32OfLength(n int) string { + bz := make([]byte, n) + for i := range bz { + bz[i] = byte(i + 1) + } + return sdk.AccAddress(bz).String() +} + +// TestGetAddressPair_RejectsNon20ByteAddresses is the regression test for +// F-2026-18200 remediation 2: anything that does not decode to exactly 20 bytes +// must be rejected rather than silently truncated. +func TestGetAddressPair_RejectsNon20ByteAddresses(t *testing.T) { + for _, length := range []int{19, 21, 22, 32} { + addr := bech32OfLength(length) + _, _, err := utils.GetAddressPair(addr) + require.Error(t, err, "%d-byte address must be rejected", length) + require.Contains(t, err.Error(), "invalid address length") + } +} + +func TestGetAddressPair_Accepts20ByteAddresses(t *testing.T) { + bz := make([]byte, common.AddressLength) + for i := range bz { + bz[i] = byte(i + 1) + } + + cosmosAddr, evmAddr, err := utils.GetAddressPair(sdk.AccAddress(bz).String()) + require.NoError(t, err) + require.Equal(t, sdk.AccAddress(bz), cosmosAddr) + require.Equal(t, common.BytesToAddress(bz), evmAddr) + + // The 0x form must round-trip as well. + cosmosAddr, evmAddr, err = utils.GetAddressPair(common.BytesToAddress(bz).Hex()) + require.NoError(t, err) + require.Equal(t, sdk.AccAddress(bz), cosmosAddr) + require.Equal(t, common.BytesToAddress(bz), evmAddr) +} + +// TestGetAddressPair_ModuleAliasRejected documents the exact attack: an over-long +// address whose rightmost 20 bytes are the uexecutor module account truncates +// onto the module's EVM address, which the UEA trusts unconditionally. +func TestGetAddressPair_ModuleAliasRejected(t *testing.T) { + moduleAddr := authtypes.NewModuleAddress("uexecutor") + require.Len(t, moduleAddr, common.AddressLength) + require.Equal(t, uexecutorModuleEVMAddr, common.BytesToAddress(moduleAddr).Hex()) + + for _, prefixLen := range []int{1, 2, 12} { + aliased := sdk.AccAddress(append(make([]byte, prefixLen), moduleAddr...)) + // Without the length check this collapses onto the module address. + require.Equal(t, uexecutorModuleEVMAddr, common.BytesToAddress(aliased).Hex()) + + _, evmAddr, err := utils.GetAddressPair(aliased.String()) + require.Error(t, err, "aliased %d-byte address must be rejected", len(aliased)) + require.Equal(t, common.Address{}, evmAddr) + } +} + +// TestMustConvertCosmosToHex checks the second truncation site: it must neither +// panic on short input nor keep the leftmost 20 bytes of a long one. +func TestMustConvertCosmosToHex(t *testing.T) { + bz := make([]byte, common.AddressLength) + for i := range bz { + bz[i] = byte(i + 1) + } + require.Equal(t, common.BytesToAddress(bz).Hex(), utils.MustConvertCosmosToHex(sdk.AccAddress(bz).String())) + + for _, length := range []int{19, 21, 22, 32} { + require.NotPanics(t, func() { + require.Empty(t, utils.MustConvertCosmosToHex(bech32OfLength(length)), + "%d-byte address must not be converted", length) + }) + } + + require.Empty(t, utils.MustConvertCosmosToHex("not-a-bech32-address")) +} diff --git a/x/uexecutor/types/msg_execute_payload.go b/x/uexecutor/types/msg_execute_payload.go index 656499f7d..45281fafc 100644 --- a/x/uexecutor/types/msg_execute_payload.go +++ b/x/uexecutor/types/msg_execute_payload.go @@ -7,6 +7,7 @@ import ( "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/ethereum/go-ethereum/common" ) var ( @@ -47,10 +48,20 @@ func (msg *MsgExecutePayload) GetSigners() []sdk.AccAddress { // ValidateBasic does a sanity check on the provided data. func (msg *MsgExecutePayload) ValidateBasic() error { - // Validate signer - if _, err := sdk.AccAddressFromBech32(msg.Signer); err != nil { + // Validate signer. + // The length check is deliberate: bech32 account addresses may carry up to + // 255 bytes, and this signer is later converted to a 20-byte EVM address + // that keeps only the rightmost bytes. A longer signer would therefore + // collapse onto an unrelated EVM address, including module addresses that + // the UEA trusts. Reject it here, at CheckTx, before the ante chain runs. + signerBz, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { return errors.Wrap(err, "invalid signer address") } + if len(signerBz) != common.AddressLength { + return errors.Wrapf(sdkerrors.ErrInvalidAddress, + "invalid signer address length: got %d bytes, want %d", len(signerBz), common.AddressLength) + } // Validate universalAccountId if msg.UniversalAccountId == nil { diff --git a/x/uexecutor/types/msg_migrate_uea.go b/x/uexecutor/types/msg_migrate_uea.go index 45178c6a7..25ec7db32 100644 --- a/x/uexecutor/types/msg_migrate_uea.go +++ b/x/uexecutor/types/msg_migrate_uea.go @@ -4,6 +4,7 @@ import ( "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/ethereum/go-ethereum/common" ) var ( @@ -44,10 +45,20 @@ func (msg *MsgMigrateUEA) GetSigners() []sdk.AccAddress { // ValidateBasic does a sanity check on the provided data. func (msg *MsgMigrateUEA) ValidateBasic() error { - // Validate signer - if _, err := sdk.AccAddressFromBech32(msg.Signer); err != nil { + // Validate signer. + // The length check is deliberate: bech32 account addresses may carry up to + // 255 bytes, and this signer is later converted to a 20-byte EVM address + // that keeps only the rightmost bytes. A longer signer would therefore + // collapse onto an unrelated EVM address, including module addresses that + // the UEA trusts. Reject it here, at CheckTx, before the ante chain runs. + signerBz, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { return errors.Wrap(err, "invalid signer address") } + if len(signerBz) != common.AddressLength { + return errors.Wrapf(sdkerrors.ErrInvalidAddress, + "invalid signer address length: got %d bytes, want %d", len(signerBz), common.AddressLength) + } // Validate universalAccountId if msg.UniversalAccountId == nil { diff --git a/x/uexecutor/types/msg_signer_length_test.go b/x/uexecutor/types/msg_signer_length_test.go new file mode 100644 index 000000000..8b5f5c0a1 --- /dev/null +++ b/x/uexecutor/types/msg_signer_length_test.go @@ -0,0 +1,107 @@ +package types_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + + "github.com/pushchain/push-chain-node/x/uexecutor/types" +) + +// uexecutorModuleEVMAddr is sha256("uexecutor")[:20] rendered as an EVM address. +// The UEA contract trusts calls from it unconditionally. +const uexecutorModuleEVMAddr = "0x14191Ea54B4c176fCf86f51b0FAc7CB1E71Df7d7" + +// aliasedModuleSigner returns a bech32 signer of the given byte length whose +// rightmost 20 bytes are the uexecutor module account, so that the downstream +// conversion to a 20-byte EVM address collapses onto the module itself. +func aliasedModuleSigner(t *testing.T, length int) string { + t.Helper() + moduleAddr := authtypes.NewModuleAddress(types.ModuleName) + require.Len(t, moduleAddr, common.AddressLength) + require.Equal(t, uexecutorModuleEVMAddr, common.BytesToAddress(moduleAddr).Hex()) + + prefix := make([]byte, length-common.AddressLength) + prefix[0] = 0x01 + addr := sdk.AccAddress(append(prefix, moduleAddr...)) + require.Equal(t, uexecutorModuleEVMAddr, common.BytesToAddress(addr).Hex()) + return addr.String() +} + +// TestGaslessMsgs_RejectOverlongSigner is the CheckTx-time guard for +// F-2026-18200: both gasless messages must reject a signer that does not decode +// to exactly 20 bytes, before the ante chain ever runs. +func TestGaslessMsgs_RejectOverlongSigner(t *testing.T) { + validUA := &types.UniversalAccountId{ + ChainNamespace: "eip155", + ChainId: "11155111", + Owner: "0x000000000000000000000000000000000000dead", + } + + for _, length := range []int{21, 22, 32} { + signer := aliasedModuleSigner(t, length) + + execMsg := &types.MsgExecutePayload{ + Signer: signer, + UniversalAccountId: validUA, + UniversalPayload: &types.UniversalPayload{ + To: "0x000000000000000000000000000000000000dead", + Data: "0xabcdef", + }, + VerificationData: "abcdef", + } + err := execMsg.ValidateBasic() + require.Error(t, err, "MsgExecutePayload must reject a %d-byte signer", length) + require.Contains(t, err.Error(), "invalid signer address length") + + migrateMsg := &types.MsgMigrateUEA{ + Signer: signer, + UniversalAccountId: validUA, + MigrationPayload: &types.MigrationPayload{ + Migration: "0x000000000000000000000000000000000000beef", + Nonce: "0", + Deadline: "1", + }, + Signature: "abcdef", + } + err = migrateMsg.ValidateBasic() + require.Error(t, err, "MsgMigrateUEA must reject a %d-byte signer", length) + require.Contains(t, err.Error(), "invalid signer address length") + } +} + +// TestGaslessMsgs_Accept20ByteSigner is the positive control. +func TestGaslessMsgs_Accept20ByteSigner(t *testing.T) { + signer := sdk.AccAddress(make([]byte, common.AddressLength)).String() + validUA := &types.UniversalAccountId{ + ChainNamespace: "eip155", + ChainId: "11155111", + Owner: "0x000000000000000000000000000000000000dead", + } + + execMsg := &types.MsgExecutePayload{ + Signer: signer, + UniversalAccountId: validUA, + UniversalPayload: &types.UniversalPayload{ + To: "0x000000000000000000000000000000000000dead", + Data: "0xabcdef", + }, + VerificationData: "abcdef", + } + require.NoError(t, execMsg.ValidateBasic()) + + migrateMsg := &types.MsgMigrateUEA{ + Signer: signer, + UniversalAccountId: validUA, + MigrationPayload: &types.MigrationPayload{ + Migration: "0x000000000000000000000000000000000000beef", + Nonce: "0", + Deadline: "1", + }, + Signature: "abcdef", + } + require.NoError(t, migrateMsg.ValidateBasic()) +} From 8e28eed60775de15f75769c4648e3a2274a99b88 Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Mon, 24 Aug 2026 08:06:04 +0530 Subject: [PATCH 15/60] fix: F-2026-18197 | [Dual Defense] Nested Message Dispatch Bypasses EVM Ante for MsgEthereumTx (#317) * fix: remove x/group module and drop wasm stargate capability Both are generic nested-message dispatchers that reach the message router after the ante handler has run, letting an MsgEthereumTx skip the EVM ante (F-2026-18197). Neither is used by Push. Adds a remove-group upgrade handler that prunes the group store. * chore: drop the remove-group upgrade handler audit-fixes targets mainnet, which starts from a fresh genesis where x/group is never mounted, so no store deletion is required. The StoreUpgrades handler belongs on the testnet branch, where donut has an existing group store. --- app/app.go | 20 --- app/nested_dispatch_test.go | 75 ++++++++++ app/txpolicy/gasless_test.go | 54 ++++++++ app/wasm.go | 11 +- .../uexecutor/gasless_module_sender_test.go | 130 ++++++++++++++++++ 5 files changed, 267 insertions(+), 23 deletions(-) create mode 100644 app/nested_dispatch_test.go create mode 100644 app/txpolicy/gasless_test.go create mode 100644 test/integration/uexecutor/gasless_module_sender_test.go diff --git a/app/app.go b/app/app.go index 19f459b9f..881a79cfd 100644 --- a/app/app.go +++ b/app/app.go @@ -91,9 +91,6 @@ import ( govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" - "github.com/cosmos/cosmos-sdk/x/group" - groupkeeper "github.com/cosmos/cosmos-sdk/x/group/keeper" - groupmodule "github.com/cosmos/cosmos-sdk/x/group/module" "github.com/cosmos/cosmos-sdk/x/mint" mintkeeper "github.com/cosmos/cosmos-sdk/x/mint/keeper" minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" @@ -195,7 +192,6 @@ var ( capabilities = []string{ "iterator", "staking", - "stargate", "cosmwasm_1_1", "cosmwasm_1_2", "cosmwasm_1_3", "cosmwasm_1_4", "token_factory", } @@ -312,7 +308,6 @@ type ChainApp struct { AuthzKeeper authzkeeper.Keeper EvidenceKeeper evidencekeeper.Keeper FeeGrantKeeper feegrantkeeper.Keeper - GroupKeeper groupkeeper.Keeper NFTKeeper nftkeeper.Keeper ConsensusParamsKeeper consensusparamkeeper.Keeper CircuitKeeper circuitkeeper.Keeper @@ -433,7 +428,6 @@ func NewChainApp( circuittypes.StoreKey, authzkeeper.StoreKey, nftkeeper.StoreKey, - group.StoreKey, // non sdk store keys ibcexported.StoreKey, ibctransfertypes.StoreKey, @@ -589,17 +583,6 @@ func NewChainApp( app.AccountKeeper, ) - groupConfig := group.DefaultConfig() - groupConfig.MaxMetadataLen = 10000 - app.GroupKeeper = groupkeeper.NewKeeper( - keys[group.StoreKey], - // runtime.NewKVStoreService(keys[group.StoreKey]), - appCodec, - app.MsgServiceRouter(), - app.AccountKeeper, - groupConfig, - ) - // get skipUpgradeHeights from the app options skipUpgradeHeights := map[int64]bool{} for _, h := range cast.ToIntSlice(appOpts.Get(server.FlagUnsafeSkipUpgrades)) { @@ -1026,7 +1009,6 @@ func NewChainApp( evidence.NewAppModule(app.EvidenceKeeper), params.NewAppModule(app.ParamsKeeper), authzmodule.NewAppModule(appCodec, app.AuthzKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), - groupmodule.NewAppModule(appCodec, app.GroupKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), nftmodule.NewAppModule(appCodec, app.NFTKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), consensus.NewAppModule(appCodec, app.ConsensusParamsKeeper), circuit.NewAppModule(appCodec, app.CircuitKeeper), @@ -1111,7 +1093,6 @@ func NewChainApp( stakingtypes.ModuleName, genutiltypes.ModuleName, feegrant.ModuleName, - group.ModuleName, // additional non simd modules evmtypes.ModuleName, erc20types.ModuleName, feemarkettypes.ModuleName, ibctransfertypes.ModuleName, @@ -1157,7 +1138,6 @@ func NewChainApp( authz.ModuleName, feegrant.ModuleName, nft.ModuleName, - group.ModuleName, paramstypes.ModuleName, upgradetypes.ModuleName, vestingtypes.ModuleName, diff --git a/app/nested_dispatch_test.go b/app/nested_dispatch_test.go new file mode 100644 index 000000000..0ac59d9a1 --- /dev/null +++ b/app/nested_dispatch_test.go @@ -0,0 +1,75 @@ +package app + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +// Regression tests for F-2026-18197 (nested message dispatch bypasses the EVM ante). +// +// Ethereum signature, nonce and gas checks live only in the EVM ante handler; +// x/vm's Keeper.EthereumTx assumes the ante already ran. Any module that unpacks +// and re-dispatches an embedded sdk.Msg therefore reaches the EVM executor with +// none of those checks applied. Push had two such dispatchers wired: x/group +// (MsgSubmitProposal/MsgExec) and the CosmWasm "stargate" capability +// (CosmosMsg::Any). Both are removed; these tests keep them removed. + +// groupMsgTypeURLs are the x/group entry points that unpack and dispatch a +// nested sdk.Msg. They must not resolve or route. +var groupMsgTypeURLs = []string{ + "/cosmos.group.v1.MsgSubmitProposal", + "/cosmos.group.v1.MsgExec", + "/cosmos.group.v1.MsgCreateGroup", + "/cosmos.group.v1.MsgCreateGroupWithPolicy", + "/cosmos.group.v1.MsgCreateGroupPolicy", +} + +// TestGroupModuleNotWired asserts x/group is gone from every wiring point: the +// module manager, the store keys, the message router and the interface registry. +func TestGroupModuleNotWired(t *testing.T) { + // setup() constructs the app without InitChain, which is all these + // assertions need: the module manager, store keys, message routes and + // interface registry are populated by then. Setup() is avoided on purpose - + // it passes the "testing" chain ID and only works once another test has + // already initialised the global EVM configurator. + gapp, _ := setup(t, ChainID, false, 0) + + t.Run("not in module manager", func(t *testing.T) { + _, ok := gapp.ModuleManager.Modules["group"] + require.False(t, ok, "x/group must not be registered in the module manager") + }) + + t.Run("no store key", func(t *testing.T) { + require.Nil(t, gapp.GetKey("group"), "x/group must not have a KV store key") + }) + + t.Run("msgs unroutable", func(t *testing.T) { + for _, typeURL := range groupMsgTypeURLs { + require.Nil(t, gapp.MsgServiceRouter().HandlerByTypeURL(typeURL), + "%s must have no handler on the msg service router", typeURL) + } + }) + + t.Run("msgs unresolvable", func(t *testing.T) { + for _, typeURL := range groupMsgTypeURLs { + _, err := gapp.InterfaceRegistry().Resolve(typeURL) + require.Error(t, err, + "%s must not resolve in the interface registry (tx decoding must fail)", typeURL) + } + }) +} + +// TestWasmStargateCapabilityDisabled asserts the "stargate" wasmvm capability is +// off for both wasm VMs. With it enabled, an uploaded contract may emit an +// arbitrary encoded sdk.Msg (CosmosMsg::Any / Stargate) that the wasm message +// handler forwards straight to the message router, after ante has already run. +func TestWasmStargateCapabilityDisabled(t *testing.T) { + t.Run("x/wasm", func(t *testing.T) { + require.NotContains(t, AllCapabilities(), "stargate") + }) + + t.Run("08-wasm light client", func(t *testing.T) { + require.NotContains(t, capabilities, "stargate") + }) +} diff --git a/app/txpolicy/gasless_test.go b/app/txpolicy/gasless_test.go new file mode 100644 index 000000000..78f2c57e0 --- /dev/null +++ b/app/txpolicy/gasless_test.go @@ -0,0 +1,54 @@ +package txpolicy_test + +import ( + "testing" + + protov2 "google.golang.org/protobuf/proto" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/authz" + evmtypes "github.com/cosmos/evm/x/vm/types" + "github.com/stretchr/testify/require" + + "github.com/pushchain/push-chain-node/app/txpolicy" + uexecutortypes "github.com/pushchain/push-chain-node/x/uexecutor/types" +) + +// msgsOnlyTx is the minimal sdk.Tx IsGaslessTx needs. +type msgsOnlyTx struct{ msgs []sdk.Msg } + +func (t msgsOnlyTx) GetMsgs() []sdk.Msg { return t.msgs } +func (t msgsOnlyTx) GetMsgsV2() ([]protov2.Message, error) { return nil, nil } + +// TestGaslessMsgTypesExcludeEthereumTx is one half of the F-2026-18197 invariant +// guard (see test/integration/uexecutor/gasless_module_sender_test.go for the +// other half). +// +// x/vm's Keeper.EthereumTx now rejects any MsgEthereumTx whose From is not the +// ECDSA signer of the raw transaction. A module account is derived from a name +// and has no key pair, so a module-signed MsgEthereumTx could never pass that +// check. Push's gasless flows are safe precisely because none of them is a +// MsgEthereumTx - they reach the EVM through CallEVM / DerivedEVMCall, which +// call ApplyMessageWithConfig directly. If a MsgEthereumTx were ever added to +// the gasless set, that flow would break 100% of the time; this test fails first. +func TestGaslessMsgTypesExcludeEthereumTx(t *testing.T) { + t.Run("MsgEthereumTx is not gasless", func(t *testing.T) { + tx := msgsOnlyTx{msgs: []sdk.Msg{&evmtypes.MsgEthereumTx{}}} + require.False(t, txpolicy.IsGaslessTx(tx), + "MsgEthereumTx must never be a gasless message type") + }) + + t.Run("MsgEthereumTx nested in authz is not gasless", func(t *testing.T) { + inner, err := codectypes.NewAnyWithValue(&evmtypes.MsgEthereumTx{}) + require.NoError(t, err) + tx := msgsOnlyTx{msgs: []sdk.Msg{&authz.MsgExec{Msgs: []*codectypes.Any{inner}}}} + require.False(t, txpolicy.IsGaslessTx(tx), + "MsgEthereumTx nested in authz.MsgExec must never be a gasless message type") + }) + + t.Run("MsgExecutePayload stays gasless", func(t *testing.T) { + tx := msgsOnlyTx{msgs: []sdk.Msg{&uexecutortypes.MsgExecutePayload{}}} + require.True(t, txpolicy.IsGaslessTx(tx)) + }) +} diff --git a/app/wasm.go b/app/wasm.go index 70f811bc1..1facc0d0b 100755 --- a/app/wasm.go +++ b/app/wasm.go @@ -1,13 +1,18 @@ package app -// AllCapabilities returns all capabilities available with the current wasmvm +// AllCapabilities returns the wasmvm capabilities enabled on this chain. // See https://github.com/CosmWasm/cosmwasm/blob/main/docs/CAPABILITIES-BUILT-IN.md -// This functionality is going to be moved upstream: https://github.com/CosmWasm/wasmvm/issues/425 +// +// NOTE: "stargate" is deliberately NOT enabled. It lets a contract emit an +// arbitrary encoded sdk.Msg (CosmosMsg::Any / Stargate), which reaches the +// message router without the tx ever passing through the ante handler. That is +// the nested-dispatch vector reported as F-2026-18197: an MsgEthereumTx routed +// that way skips the EVM ante entirely (signature, nonce and gas checks). No +// contract deployed on Push requires it. func AllCapabilities() []string { return []string{ "iterator", "staking", - "stargate", "cosmwasm_1_1", "cosmwasm_1_2", "cosmwasm_1_3", diff --git a/test/integration/uexecutor/gasless_module_sender_test.go b/test/integration/uexecutor/gasless_module_sender_test.go new file mode 100644 index 000000000..0cbe3078a --- /dev/null +++ b/test/integration/uexecutor/gasless_module_sender_test.go @@ -0,0 +1,130 @@ +package integrationtest + +import ( + "testing" + + "cosmossdk.io/math" + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + + utils "github.com/pushchain/push-chain-node/test/utils" + "github.com/pushchain/push-chain-node/types" + uexecutorkeeper "github.com/pushchain/push-chain-node/x/uexecutor/keeper" + uexecutortypes "github.com/pushchain/push-chain-node/x/uexecutor/types" + uregistrytypes "github.com/pushchain/push-chain-node/x/uregistry/types" +) + +// TestGaslessExecutePayloadWithModuleSender is the invariant guard for +// F-2026-18197. +// +// The fix hardens x/vm's Keeper.EthereumTx to require that msg.From is the +// ECDSA signer of the raw transaction, so that an MsgEthereumTx smuggled in via +// a nested-message dispatcher can no longer execute as somebody else. Push's +// gasless / module-sender flows must be completely unaffected by that, and they +// are - because they never reach that msg server. MsgExecutePayload runs the +// payload through CallEVM / DerivedEVMCall, which go straight to +// ApplyMessageWithConfig; no MsgEthereumTx is ever constructed. +// +// This test pins that down end to end: a gasless MsgExecutePayload, whose EVM +// caller is the uexecutor module account, still executes successfully. +func TestGaslessExecutePayloadWithModuleSender(t *testing.T) { + app, ctx, _ := utils.SetAppWithValidators(t) + + // The uexecutor module account is derived from a name, not from a key pair. + // It can never produce an ECDSA signature, so if a module operation ever + // routed through MsgEthereumTx the new VerifySender check would reject it + // 100% of the time. That is why module-driven EVM calls must keep using the + // ApplyMessage* path, and why this test exists. + moduleAcc := app.AccountKeeper.GetModuleAccount(ctx, uexecutortypes.ModuleName) + require.NotNil(t, moduleAcc) + require.Nil(t, moduleAcc.GetPubKey(), + "the uexecutor module account must have no public key - it cannot sign an MsgEthereumTx") + + app.UregistryKeeper.AddChainConfig(ctx, &uregistrytypes.ChainConfig{ + Chain: "eip155:11155111", + VmType: uregistrytypes.VmType_EVM, + PublicRpcUrl: "https://sepolia.drpc.org", + GatewayAddress: "0x28E0F09bE2321c1420Dc60Ee146aACbD68B335Fe", + BlockConfirmation: &uregistrytypes.BlockConfirmation{ + FastInbound: 5, + StandardInbound: 12, + }, + GatewayMethods: []*uregistrytypes.GatewayMethods{{ + Name: "addFunds", + Identifier: "", + EventIdentifier: "0xb28f49668e7e76dc96d7aabe5b7f63fecfbd1c3574774c05e8204e749fd96fbd", + }}, + Enabled: &uregistrytypes.ChainEnabled{ + IsInboundEnabled: true, + IsOutboundEnabled: true, + }, + }) + + params := app.FeeMarketKeeper.GetParams(ctx) + params.BaseFee = math.LegacyNewDec(1000000000) + app.FeeMarketKeeper.SetParams(ctx, params) + + ms := uexecutorkeeper.NewMsgServerImpl(app.UexecutorKeeper) + + universalAccount := &uexecutortypes.UniversalAccountId{ + ChainNamespace: "eip155", + ChainId: "11155111", + Owner: "0x778d3206374f8ac265728e18e3fe2ae6b93e4ce4", + } + payload := &uexecutortypes.UniversalPayload{ + To: "0x527F3692F5C53CfA83F7689885995606F93b6164", + Value: "0", + Data: "0x2ba2ed980000000000000000000000000000000000000000000000000000000000000312", + GasLimit: "21000000", + MaxFeePerGas: "1000000000", + MaxPriorityFeePerGas: "200000000", + Nonce: "1", + Deadline: "0", + VType: uexecutortypes.VerificationType(0), + } + + evmFrom := common.HexToAddress("0x1000000000000000000000000000000000000001") + + err := app.BankKeeper.MintCoins( + ctx, + uexecutortypes.ModuleName, + sdk.NewCoins(sdk.NewCoin(types.BaseDenom, sdkmath.NewInt(2_000_000_000_000_000))), + ) + require.NoError(t, err) + + err = app.BankKeeper.SendCoinsFromModuleToAccount( + ctx, + uexecutortypes.ModuleName, + sdk.AccAddress(evmFrom.Bytes()), + sdk.NewCoins(sdk.NewCoin(types.BaseDenom, sdkmath.NewInt(1_000_000_000_000_000))), + ) + require.NoError(t, err) + + _, err = app.UexecutorKeeper.DeployUEAV2(ctx, evmFrom, universalAccount) + require.NoError(t, err) + + ueaAddr, _, err := app.UexecutorKeeper.CallFactoryToGetUEAAddressForOrigin( + ctx, evmFrom, utils.GetDefaultAddresses().FactoryAddr, universalAccount, + ) + require.NoError(t, err) + + err = app.BankKeeper.SendCoinsFromModuleToAccount( + ctx, + uexecutortypes.ModuleName, + sdk.AccAddress(ueaAddr.Bytes()), + sdk.NewCoins(sdk.NewCoin(types.BaseDenom, sdkmath.NewInt(1_000_000_000_000_000))), + ) + require.NoError(t, err) + + // The gasless message itself: signer is a relayer, the EVM caller is the + // uexecutor module. This must still succeed after the x/vm change. + _, err = ms.ExecutePayload(ctx, &uexecutortypes.MsgExecutePayload{ + Signer: "cosmos1xpurwdecvsenyvpkxvmnge3cv93nyd34xuersef38pjnxen9xfsk2dnz8yek2drrv56qmn2ak9", + UniversalAccountId: universalAccount, + UniversalPayload: payload, + VerificationData: "0x91987784d56359fa91c3e3e0332f4f0cffedf9c081eb12874a63b41d5b5e5c660dc827947c2ae26e658d0551ad4b2d2aa073d62691429a0ae239d2cc58055bf11c", + }) + require.NoError(t, err, "gasless module-sender MsgExecutePayload must still execute end to end") +} From 3deb6d7f4bcd92356883a386230a284177cdad37 Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Mon, 24 Aug 2026 08:24:37 +0530 Subject: [PATCH 16/60] fix: F-2026-18195 | [Dual Defense] isCEA Smart-Contract Inbound Lifecycle Gaps Strand Bridged Principal (#319) * fix: attach outbounds on isCEA contract callback success (F-2026-18195) Attach inside the callback CacheContext so a nested UniversalGatewayPC burn and its OutboundTx/PendingOutbounds rows commit atomically; a failed attach discards the cache and records a FAILED PcTx. * test: cover isCEA contract callback outbound attach and rollback (F-2026-18195) --- .../inbound_cea_contract_outbound_test.go | 478 ++++++++++++++++++ .../execute_inbound_funds_and_payload.go | 19 +- .../keeper/execute_inbound_gas_and_payload.go | 16 +- 3 files changed, 511 insertions(+), 2 deletions(-) create mode 100644 test/integration/uexecutor/inbound_cea_contract_outbound_test.go diff --git a/test/integration/uexecutor/inbound_cea_contract_outbound_test.go b/test/integration/uexecutor/inbound_cea_contract_outbound_test.go new file mode 100644 index 000000000..28a79b7f4 --- /dev/null +++ b/test/integration/uexecutor/inbound_cea_contract_outbound_test.go @@ -0,0 +1,478 @@ +package integrationtest + +import ( + "fmt" + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + authz "github.com/cosmos/cosmos-sdk/x/authz" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + + "github.com/pushchain/push-chain-node/app" + utils "github.com/pushchain/push-chain-node/test/utils" + uexecutorkeeper "github.com/pushchain/push-chain-node/x/uexecutor/keeper" + uexecutortypes "github.com/pushchain/push-chain-node/x/uexecutor/types" + uregistrytypes "github.com/pushchain/push-chain-node/x/uregistry/types" + uvalidatortypes "github.com/pushchain/push-chain-node/x/uvalidator/types" +) + +// F-2026-18195 (defect 2). On the isCEA smart-contract branch the callback may +// itself call UniversalGatewayPC: that burns the PRC20 and emits a +// UniversalTxOutbound log. DerivedEVMCall skips PostTxProcessing, so the EVM +// hook never sees those logs — the handler itself has to attach them. Before +// the fix it returned right after recording the PcTx, leaving burned supply +// with no OutboundTx and no PendingOutbounds row (and a SUCCESS PcTx, so +// neither rescue nor remint were eligible). +// +// The attach now runs inside the same CacheContext as the callback, before +// writeCache(), so the burn and the outbound rows commit together or not at all. + +// gatewayCallingRecipientAddr hosts the mock recipient that re-enters +// UniversalGatewayPC during its callback. 0xD0-0xFF is outside the reserved +// system-contract ranges (see x/uregistry/types/constants.go). +var gatewayCallingRecipientAddr = common.HexToAddress("0x00000000000000000000000000000000000000D5") + +// gatewayWithdrawCalldata is the ABI-encoded UniversalGatewayPC withdraw call +// used by TestInboundInitiatedOutbound — recipient 0x1234..5678, PRC20 0x..0e06, +// amount 1000000. The test gateway answers it by emitting UniversalTxOutbound. +const gatewayWithdrawCalldata = "b3ca1fbc" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "00000000000000000000000000000000000000000000000000000000000000c0" + + "0000000000000000000000000000000000000000000000000000000000000e06" + + "00000000000000000000000000000000000000000000000000000000000f4240" + + "000000000000000000000000000000000000000000000000000000000007a120" + + "0000000000000000000000000000000000000000000000000000000000000100" + + "0000000000000000000000001234567890abcdef1234567890abcdef12345678" + + "0000000000000000000000000000000000000000000000000000000000000014" + + "1234567890abcdef1234567890abcdef12345678000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + +// expectedOutboundRecipient / expectedOutboundPRC20 mirror gatewayWithdrawCalldata. +const ( + expectedOutboundRecipient = "0x1234567890abcdef1234567890abcdef12345678" + expectedOutboundPRC20 = "0x0000000000000000000000000000000000000e06" + expectedOutboundAmount = "1000000" +) + +// gatewayCallingRecipientCode assembles runtime bytecode for a recipient that, +// on any call: +// +// 1. SSTOREs 1 into slot 0 — a witness that the callback body ran AND committed; +// 2. CALLs UniversalGatewayPC (0x..C1) with gatewayWithdrawCalldata, so the +// callback emits a UniversalTxOutbound log from the gateway address; +// 3. bubbles a gateway failure up as a REVERT. +// +// Assembly (all self-references are computed, not hard-coded): +// +// PUSH1 0x01; PUSH1 0x00; SSTORE storage[0] = 1 +// PUSH2 len; PUSH2 off; PUSH1 0x00; CODECOPY mem[0:len] = code[off:off+len] +// PUSH1 0x00; PUSH1 0x00 retSize, retOffset +// PUSH2 len; PUSH1 0x00 argsSize, argsOffset +// PUSH1 0x00; PUSH1 0xC1; GAS; CALL value, gateway, gas +// PUSH1 ok; JUMPI taken when CALL succeeded +// PUSH1 0x00; PUSH1 0x00; REVERT gateway call failed +// JUMPDEST; STOP +// +func gatewayCallingRecipientCode(t *testing.T) string { + t.Helper() + + blobLen := len(gatewayWithdrawCalldata) / 2 + + // The prologue below is a fixed 39 bytes; the blob is appended right after + // it, and the success JUMPDEST is its second-to-last byte. + const prologueLen = 39 + const okJumpDest = prologueLen - 2 + + prologue := "6001600055" + // PUSH1 1, PUSH1 0, SSTORE + fmt.Sprintf("61%04x", blobLen) + // PUSH2 blobLen (CODECOPY size) + fmt.Sprintf("61%04x", prologueLen) + // PUSH2 prologueLen (CODECOPY code offset) + "6000" + // PUSH1 0 (CODECOPY dest offset) + "39" + // CODECOPY + "6000" + // PUSH1 0 retSize + "6000" + // PUSH1 0 retOffset + fmt.Sprintf("61%04x", blobLen) + // PUSH2 blobLen argsSize + "6000" + // PUSH1 0 argsOffset + "6000" + // PUSH1 0 value + "60c1" + // PUSH1 0xC1 UniversalGatewayPC + "5a" + // GAS + "f1" + // CALL + fmt.Sprintf("60%02x", okJumpDest) + // PUSH1 okJumpDest + "57" + // JUMPI + "6000" + // PUSH1 0 revert offset + "6000" + // PUSH1 0 revert size + "fd" + // REVERT + "5b" + // JUMPDEST (okJumpDest) + "00" // STOP + + require.Equal(t, prologueLen, len(prologue)/2, "prologue length drifted; okJumpDest/CODECOPY offset are stale") + + return prologue + gatewayWithdrawCalldata +} + +// deployGatewayCallingRecipient installs the contract above and funds it with +// upc so DeductGasFeesFromReceipt succeeds and execution reaches the attach. +func deployGatewayCallingRecipient(t *testing.T, chainApp *app.ChainApp, ctx sdk.Context) common.Address { + t.Helper() + + addr := utils.DeployContract(t, chainApp, ctx, gatewayCallingRecipientAddr, gatewayCallingRecipientCode(t)) + + fundCoins := sdk.NewCoins(sdk.NewInt64Coin("upc", 1_000_000_000)) + require.NoError(t, chainApp.BankKeeper.MintCoins(ctx, utils.MintModule, fundCoins)) + require.NoError(t, chainApp.BankKeeper.SendCoinsFromModuleToAccount( + ctx, utils.MintModule, sdk.AccAddress(addr.Bytes()), fundCoins)) + + return addr +} + +// setupCEAContractOutboundTest mirrors setupInboundCEASmartContractTest but the +// recipient re-enters the gateway, and chain outbound can be disabled to force +// the attach to fail. +func setupCEAContractOutboundTest( + t *testing.T, + numVals int, + outboundEnabled bool, +) (*app.ChainApp, sdk.Context, []string, []stakingtypes.Validator, common.Address) { + t.Helper() + + chainApp, ctx, _, validators := utils.SetAppWithMultipleValidators(t, numVals) + + chainConfigTest := uregistrytypes.ChainConfig{ + Chain: "eip155:11155111", + VmType: uregistrytypes.VmType_EVM, + PublicRpcUrl: "https://sepolia.drpc.org", + GatewayAddress: "0x28E0F09bE2321c1420Dc60Ee146aACbD68B335Fe", + BlockConfirmation: &uregistrytypes.BlockConfirmation{ + FastInbound: 5, + StandardInbound: 12, + }, + GatewayMethods: []*uregistrytypes.GatewayMethods{{ + Name: "addFunds", + Identifier: "", + EventIdentifier: "0xb28f49668e7e76dc96d7aabe5b7f63fecfbd1c3574774c05e8204e749fd96fbd", + ConfirmationType: 5, + }}, + Enabled: &uregistrytypes.ChainEnabled{ + IsInboundEnabled: true, + IsOutboundEnabled: outboundEnabled, + }, + } + + prc20Address := utils.GetDefaultAddresses().PRC20USDCAddr + usdcAddress := utils.GetDefaultAddresses().ExternalUSDCAddr + + tokenConfigTest := uregistrytypes.TokenConfig{ + Chain: "eip155:11155111", + Address: usdcAddress.String(), + Name: "USD Coin", + Symbol: "USDC", + Decimals: 6, + Enabled: true, + LiquidityCap: "1000000000000000000000000", + TokenType: 1, + NativeRepresentation: &uregistrytypes.NativeRepresentation{ + Denom: "", + ContractAddress: prc20Address.String(), + }, + } + + chainApp.UregistryKeeper.AddChainConfig(ctx, &chainConfigTest) + chainApp.UregistryKeeper.AddTokenConfig(ctx, &tokenConfigTest) + + universalVals := make([]string, len(validators)) + for i, val := range validators { + network := uvalidatortypes.NetworkInfo{PeerId: fmt.Sprintf("temp%d", i+1), MultiAddrs: []string{"temp"}} + require.NoError(t, chainApp.UvalidatorKeeper.AddUniversalValidator(ctx, val.OperatorAddress, network)) + universalVals[i] = sdk.AccAddress([]byte(fmt.Sprintf("universal-validator-%d", i))).String() + } + + for i, val := range validators { + accAddr, err := sdk.ValAddressFromBech32(val.OperatorAddress) + require.NoError(t, err) + + coreValAddr := sdk.AccAddress(accAddr) + uniValAddr := sdk.MustAccAddressFromBech32(universalVals[i]) + + auth := authz.NewGenericAuthorization(sdk.MsgTypeURL(&uexecutortypes.MsgVoteInbound{})) + exp := ctx.BlockTime().Add(time.Hour) + require.NoError(t, chainApp.AuthzKeeper.SaveGrant(ctx, uniValAddr, coreValAddr, auth, &exp)) + } + + recipient := deployGatewayCallingRecipient(t, chainApp, ctx) + + return chainApp, ctx, universalVals, validators, recipient +} + +// ceaContractInbound builds an isCEA inbound targeting a contract recipient. +func ceaContractInbound( + txHash string, + recipient common.Address, + txType uexecutortypes.TxType, + amount string, +) *uexecutortypes.Inbound { + testAddress := utils.GetDefaultAddresses().DefaultTestAddr + usdcAddress := utils.GetDefaultAddresses().ExternalUSDCAddr + + return &uexecutortypes.Inbound{ + SourceChain: "eip155:11155111", + TxHash: txHash, + Sender: testAddress, + Recipient: recipient.String(), + Amount: amount, + AssetAddr: usdcAddress.String(), + LogIndex: "1", + TxType: txType, + UniversalPayload: &uexecutortypes.UniversalPayload{ + To: recipient.String(), + Value: "0", + Data: "0xdeadbeef", + GasLimit: "21000000", + MaxFeePerGas: "1000000000", + MaxPriorityFeePerGas: "200000000", + Nonce: "1", + Deadline: "9999999999", + VType: uexecutortypes.VerificationType(1), + }, + VerificationData: "", + IsCEA: true, + RevertInstructions: &uexecutortypes.RevertInstructions{ + FundRecipient: testAddress, + }, + } +} + +func reachInboundQuorum( + t *testing.T, + ctx sdk.Context, + chainApp *app.ChainApp, + universalVals []string, + coreVals []stakingtypes.Validator, + inbound *uexecutortypes.Inbound, +) { + t.Helper() + + for i := 0; i < 3; i++ { + valAddr, err := sdk.ValAddressFromBech32(coreVals[i].OperatorAddress) + require.NoError(t, err) + require.NoError(t, utils.ExecVoteInbound(t, ctx, chainApp, universalVals[i], sdk.AccAddress(valAddr).String(), inbound)) + } +} + +// lastPcTx returns the executeUniversalTx PcTx, which is always the final one +// recorded on the smart-contract branch. +func lastPcTx(t *testing.T, utx uexecutortypes.UniversalTx) *uexecutortypes.PCTx { + t.Helper() + require.NotEmpty(t, utx.PcTx, "at least one PcTx must be recorded") + return utx.PcTx[len(utx.PcTx)-1] +} + +func TestInboundCEAContractCallbackOutbound(t *testing.T) { + slot := common.Hash{} + + // --- FUNDS_AND_PAYLOAD: the defect and its fix ------------------------- + + t.Run("FUNDS_AND_PAYLOAD contract callback gateway burn creates OutboundTx and PendingOutbounds", func(t *testing.T) { + chainApp, ctx, vals, coreVals, recipient := setupCEAContractOutboundTest(t, 4, true) + + inbound := ceaContractInbound("0xsc-outbound-funds-01", recipient, uexecutortypes.TxType_FUNDS_AND_PAYLOAD, "1000000") + reachInboundQuorum(t, ctx, chainApp, vals, coreVals, inbound) + + utxKey := uexecutortypes.GetInboundUniversalTxKey(*inbound) + utx, found, err := chainApp.UexecutorKeeper.GetUniversalTx(ctx, utxKey) + require.NoError(t, err) + require.True(t, found) + + callPcTx := lastPcTx(t, utx) + require.Equal(t, "SUCCESS", callPcTx.Status, "executeUniversalTx should succeed: %s", callPcTx.ErrorMsg) + + // The callback committed (proves writeCache ran). + require.Equal(t, common.BigToHash(common.Big1), chainApp.EVMKeeper.GetState(ctx, recipient, slot), + "recipient slot 0 must be 1 (callback committed)") + + // THE PRIMARY ASSERTION: the nested gateway burn produced an outbound. + require.Len(t, utx.OutboundTx, 1, "the gateway call inside the callback must produce exactly one OutboundTx") + + out := utx.OutboundTx[0] + require.Equal(t, "eip155:11155111", out.DestinationChain) + require.Equal(t, expectedOutboundRecipient, out.Recipient) + require.Equal(t, expectedOutboundAmount, out.Amount) + require.Equal(t, expectedOutboundPRC20, out.Prc20AssetAddr) + require.Equal(t, uexecutortypes.Status_PENDING, out.OutboundStatus) + require.NotEqual(t, uexecutortypes.TxType_INBOUND_REVERT, out.TxType, + "the isCEA route must never auto-revert; this outbound comes from the callback") + + // ... and a PendingOutbounds row, so it is actually signed and delivered. + entry, err := chainApp.UexecutorKeeper.PendingOutbounds.Get(ctx, out.Id) + require.NoError(t, err, "outbound must be indexed in PendingOutbounds") + require.Equal(t, out.Id, entry.OutboundId) + require.Equal(t, utxKey, entry.UniversalTxId) + }) + + t.Run("FUNDS_AND_PAYLOAD attach failure rolls the callback back and records FAILED PcTx", func(t *testing.T) { + // Outbound disabled for the destination chain → BuildOutboundsFromReceipt + // errors, which is the attach failure we need to exercise. + chainApp, ctx, vals, coreVals, recipient := setupCEAContractOutboundTest(t, 4, false) + + recipientAcc := sdk.AccAddress(recipient.Bytes()) + balanceBefore := chainApp.BankKeeper.GetBalance(ctx, recipientAcc, "upc") + + inbound := ceaContractInbound("0xsc-outbound-funds-02", recipient, uexecutortypes.TxType_FUNDS_AND_PAYLOAD, "1000000") + reachInboundQuorum(t, ctx, chainApp, vals, coreVals, inbound) + + utxKey := uexecutortypes.GetInboundUniversalTxKey(*inbound) + utx, found, err := chainApp.UexecutorKeeper.GetUniversalTx(ctx, utxKey) + require.NoError(t, err) + require.True(t, found) + + callPcTx := lastPcTx(t, utx) + require.Equal(t, "FAILED", callPcTx.Status, "attach failure must surface on the PcTx, not be swallowed") + require.Contains(t, callPcTx.ErrorMsg, "outbound attach failed") + require.Contains(t, callPcTx.ErrorMsg, "outbound is disabled for chain") + + // The whole callback — including the gateway burn — was rolled back. + require.Equal(t, common.Hash{}, chainApp.EVMKeeper.GetState(ctx, recipient, slot), + "recipient slot 0 must stay 0 (callback rolled back with the attach failure)") + require.Empty(t, utx.OutboundTx, "no outbound may be recorded when the attach failed") + + querier := uexecutorkeeper.NewQuerier(chainApp.UexecutorKeeper) + resp, err := querier.AllPendingOutbounds(ctx, &uexecutortypes.QueryAllPendingOutboundsRequest{}) + require.NoError(t, err) + require.Empty(t, resp.Entries, "no PendingOutbounds row may survive a rolled-back callback") + + // No gas fee was collected either — the cache holding it was discarded. + require.Equal(t, balanceBefore.Amount, chainApp.BankKeeper.GetBalance(ctx, recipientAcc, "upc").Amount, + "no fee may be collected when the cache is discarded") + + // The deposit happens before the cache scope and stays committed, so the + // principal is still with the recipient the sender nominated. + require.Equal(t, "SUCCESS", utx.PcTx[0].Status, "deposit is outside the cache scope and stays committed") + }) + + // --- GAS_AND_PAYLOAD: the same branch in the sibling handler ----------- + // + // Amount is 0 so the handler skips gasAndPayloadDepositAutoSwap, which + // needs a live Uniswap quoter/router that the integration harness does not + // deploy. isSmartContract is set from the recipient's code hash regardless + // of amount, so the contract branch under test is still exercised. + + t.Run("GAS_AND_PAYLOAD contract callback gateway burn creates OutboundTx and PendingOutbounds", func(t *testing.T) { + chainApp, ctx, vals, coreVals, recipient := setupCEAContractOutboundTest(t, 4, true) + + inbound := ceaContractInbound("0xsc-outbound-gas-01", recipient, uexecutortypes.TxType_GAS_AND_PAYLOAD, "0") + reachInboundQuorum(t, ctx, chainApp, vals, coreVals, inbound) + + utxKey := uexecutortypes.GetInboundUniversalTxKey(*inbound) + utx, found, err := chainApp.UexecutorKeeper.GetUniversalTx(ctx, utxKey) + require.NoError(t, err) + require.True(t, found) + + callPcTx := lastPcTx(t, utx) + require.Equal(t, "SUCCESS", callPcTx.Status, "executeUniversalTx should succeed: %s", callPcTx.ErrorMsg) + + require.Equal(t, common.BigToHash(common.Big1), chainApp.EVMKeeper.GetState(ctx, recipient, slot), + "recipient slot 0 must be 1 (callback committed)") + + require.Len(t, utx.OutboundTx, 1, "the gateway call inside the callback must produce exactly one OutboundTx") + + out := utx.OutboundTx[0] + require.Equal(t, "eip155:11155111", out.DestinationChain) + require.Equal(t, expectedOutboundRecipient, out.Recipient) + require.Equal(t, expectedOutboundAmount, out.Amount) + require.Equal(t, expectedOutboundPRC20, out.Prc20AssetAddr) + require.Equal(t, uexecutortypes.Status_PENDING, out.OutboundStatus) + + entry, err := chainApp.UexecutorKeeper.PendingOutbounds.Get(ctx, out.Id) + require.NoError(t, err, "outbound must be indexed in PendingOutbounds") + require.Equal(t, utxKey, entry.UniversalTxId) + }) + + t.Run("GAS_AND_PAYLOAD attach failure rolls the callback back and records FAILED PcTx", func(t *testing.T) { + chainApp, ctx, vals, coreVals, recipient := setupCEAContractOutboundTest(t, 4, false) + + recipientAcc := sdk.AccAddress(recipient.Bytes()) + balanceBefore := chainApp.BankKeeper.GetBalance(ctx, recipientAcc, "upc") + + inbound := ceaContractInbound("0xsc-outbound-gas-02", recipient, uexecutortypes.TxType_GAS_AND_PAYLOAD, "0") + reachInboundQuorum(t, ctx, chainApp, vals, coreVals, inbound) + + utxKey := uexecutortypes.GetInboundUniversalTxKey(*inbound) + utx, found, err := chainApp.UexecutorKeeper.GetUniversalTx(ctx, utxKey) + require.NoError(t, err) + require.True(t, found) + + callPcTx := lastPcTx(t, utx) + require.Equal(t, "FAILED", callPcTx.Status, "attach failure must surface on the PcTx, not be swallowed") + require.Contains(t, callPcTx.ErrorMsg, "outbound attach failed") + require.Contains(t, callPcTx.ErrorMsg, "outbound is disabled for chain") + + require.Equal(t, common.Hash{}, chainApp.EVMKeeper.GetState(ctx, recipient, slot), + "recipient slot 0 must stay 0 (callback rolled back with the attach failure)") + require.Empty(t, utx.OutboundTx, "no outbound may be recorded when the attach failed") + + querier := uexecutorkeeper.NewQuerier(chainApp.UexecutorKeeper) + resp, err := querier.AllPendingOutbounds(ctx, &uexecutortypes.QueryAllPendingOutboundsRequest{}) + require.NoError(t, err) + require.Empty(t, resp.Entries, "no PendingOutbounds row may survive a rolled-back callback") + + require.Equal(t, balanceBefore.Amount, chainApp.BankKeeper.GetBalance(ctx, recipientAcc, "upc").Amount, + "no fee may be collected when the cache is discarded") + }) + + // --- regression: the UEA branch is untouched -------------------------- + + t.Run("UEA branch still attaches its outbound and indexes it", func(t *testing.T) { + chainApp, ctx, vals, inbound, coreVals, _ := setupInboundInitiatedOutboundTest(t, 4) + reachInboundQuorum(t, ctx, chainApp, vals, coreVals, inbound) + + utxKey := uexecutortypes.GetInboundUniversalTxKey(*inbound) + utx, found, err := chainApp.UexecutorKeeper.GetUniversalTx(ctx, utxKey) + require.NoError(t, err) + require.True(t, found) + + require.Len(t, utx.OutboundTx, 1, "the UEA payload's gateway call must still produce exactly one OutboundTx") + + out := utx.OutboundTx[0] + require.Equal(t, expectedOutboundRecipient, out.Recipient) + require.Equal(t, expectedOutboundAmount, out.Amount) + require.Equal(t, uexecutortypes.Status_PENDING, out.OutboundStatus) + + entry, err := chainApp.UexecutorKeeper.PendingOutbounds.Get(ctx, out.Id) + require.NoError(t, err, "UEA-branch outbound must still be indexed in PendingOutbounds") + require.Equal(t, utxKey, entry.UniversalTxId) + }) + + // --- regression: callbacks that emit nothing are untouched ------------- + + t.Run("contract callback without a gateway call still succeeds with no outbound rows", func(t *testing.T) { + chainApp, ctx, vals, coreVals, _ := setupCEAContractOutboundTest(t, 4, true) + + // Plain STOP recipient: the callback runs, emits no logs at all. + plain := deployMockRecipientContract(t, chainApp, ctx) + fundCoins := sdk.NewCoins(sdk.NewInt64Coin("upc", 1_000_000_000)) + require.NoError(t, chainApp.BankKeeper.MintCoins(ctx, utils.MintModule, fundCoins)) + require.NoError(t, chainApp.BankKeeper.SendCoinsFromModuleToAccount( + ctx, utils.MintModule, sdk.AccAddress(plain.Bytes()), fundCoins)) + + inbound := ceaContractInbound("0xsc-outbound-noop-01", plain, uexecutortypes.TxType_FUNDS_AND_PAYLOAD, "1000000") + reachInboundQuorum(t, ctx, chainApp, vals, coreVals, inbound) + + utxKey := uexecutortypes.GetInboundUniversalTxKey(*inbound) + utx, found, err := chainApp.UexecutorKeeper.GetUniversalTx(ctx, utxKey) + require.NoError(t, err) + require.True(t, found) + + require.Equal(t, "SUCCESS", utx.PcTx[0].Status, "deposit should still succeed") + callPcTx := lastPcTx(t, utx) + require.Equal(t, "SUCCESS", callPcTx.Status, "callback should still succeed: %s", callPcTx.ErrorMsg) + require.Empty(t, callPcTx.ErrorMsg) + + require.Empty(t, utx.OutboundTx, "a callback that emits nothing must not gain an outbound") + + querier := uexecutorkeeper.NewQuerier(chainApp.UexecutorKeeper) + resp, err := querier.AllPendingOutbounds(ctx, &uexecutortypes.QueryAllPendingOutboundsRequest{}) + require.NoError(t, err) + require.Empty(t, resp.Entries, "no spurious PendingOutbounds row") + }) +} diff --git a/x/uexecutor/keeper/execute_inbound_funds_and_payload.go b/x/uexecutor/keeper/execute_inbound_funds_and_payload.go index 6f7967837..6ac8f24c7 100644 --- a/x/uexecutor/keeper/execute_inbound_funds_and_payload.go +++ b/x/uexecutor/keeper/execute_inbound_funds_and_payload.go @@ -212,6 +212,7 @@ func (k Keeper) ExecuteInboundFundsAndPayload(ctx context.Context, utx types.Uni var contractReceipt *evmtypes.MsgEthereumTxResponse var contractErr error var feeErr error + var attachErr error if tcErr != nil { contractErr = fmt.Errorf("token config lookup failed: %w", tcErr) @@ -250,7 +251,21 @@ func (k Keeper) ExecuteInboundFundsAndPayload(ctx context.Context, utx types.Uni if contractErr == nil { feeErr = k.DeductGasFeesFromReceipt(cacheCtx, cacheCtx, ueaAddr, contractReceipt, utx.InboundTx.UniversalPayload) if feeErr == nil { - writeCache() + // A successful callback may itself have called + // UniversalGatewayPC, burning PRC20 and emitting + // UniversalTxOutbound. DerivedEVMCall skips + // PostTxProcessing, so nothing else picks those logs up: + // without this attach the supply is burned and no + // OutboundTx / PendingOutbounds row is ever created. + // Attaching inside cacheCtx keeps the burn and the + // outbound rows atomic - if the attach fails the cache + // is discarded, rolling the burn back with it. + if contractReceipt != nil { + attachErr = k.AttachOutboundsToExistingUniversalTx(cacheCtx, contractReceipt, utx) + } + if attachErr == nil { + writeCache() + } } } } @@ -270,6 +285,8 @@ func (k Keeper) ExecuteInboundFundsAndPayload(ctx context.Context, utx types.Uni callPcTx.ErrorMsg = contractErr.Error() case feeErr != nil: callPcTx.ErrorMsg = fmt.Sprintf("gas fee deduction failed: %s", feeErr.Error()) + case attachErr != nil: + callPcTx.ErrorMsg = fmt.Sprintf("outbound attach failed: %s", attachErr.Error()) default: callPcTx.Status = "SUCCESS" } diff --git a/x/uexecutor/keeper/execute_inbound_gas_and_payload.go b/x/uexecutor/keeper/execute_inbound_gas_and_payload.go index baa7284d3..db6a32f7e 100644 --- a/x/uexecutor/keeper/execute_inbound_gas_and_payload.go +++ b/x/uexecutor/keeper/execute_inbound_gas_and_payload.go @@ -248,10 +248,22 @@ func (k Keeper) ExecuteInboundGasAndPayload(ctx context.Context, utx types.Unive ) var feeErr error + var attachErr error if contractErr == nil && contractReceipt != nil { feeErr = k.DeductGasFeesFromReceipt(cacheCtx, cacheCtx, ueaAddr, contractReceipt, utx.InboundTx.UniversalPayload) if feeErr == nil { - writeCache() + // A successful callback may itself have called + // UniversalGatewayPC, burning PRC20 and emitting + // UniversalTxOutbound. DerivedEVMCall skips PostTxProcessing, + // so nothing else picks those logs up: without this attach the + // supply is burned and no OutboundTx / PendingOutbounds row is + // ever created. Attaching inside cacheCtx keeps the burn and the + // outbound rows atomic - if the attach fails the cache is + // discarded, rolling the burn back with it. + attachErr = k.AttachOutboundsToExistingUniversalTx(cacheCtx, contractReceipt, utx) + if attachErr == nil { + writeCache() + } } } @@ -271,6 +283,8 @@ func (k Keeper) ExecuteInboundGasAndPayload(ctx context.Context, utx types.Unive // EVM call returned nil receipt without error — leave Status FAILED, no message. case feeErr != nil: callPcTx.ErrorMsg = fmt.Sprintf("gas fee deduction failed: %s", feeErr.Error()) + case attachErr != nil: + callPcTx.ErrorMsg = fmt.Sprintf("outbound attach failed: %s", attachErr.Error()) default: callPcTx.Status = "SUCCESS" } From c63e0af777066da151ff0d3013320231b664ce0d Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Mon, 24 Aug 2026 08:33:40 +0530 Subject: [PATCH 17/60] =?UTF-8?q?fix:=20F-2026-18829=20|=20[Dual=20Defense?= =?UTF-8?q?]=20UEA=5FSVM=20Hardcodes=20Ed25519=20Verifier=200x=E2=80=A600c?= =?UTF-8?q?a=20After=20Runtime=20Moved=20to=200xEC=E2=80=A601=20(#320)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: activate ed25519 verifier at 0xEC..01 in genesis and drop legacy 0x..00ca Nothing is registered at 0x..00ca, so declaring it active panics the EVM precompile lookup, and the real verifier was never activated. * feat: add usigverifier-precompile-fix upgrade handler Drops the legacy ed25519 verifier address from EVM ActiveStaticPrecompiles and adds 0xEC..01 if missing, for chains already past genesis. * chore: fix ed25519 precompile address in README and drop unused UEA_SVM_BYTECODE * chore: drop usigverifier-precompile-fix upgrade handler audit-fixes targets mainnet, which starts from a fresh genesis, so the genesis scripts already carry the correct active_static_precompiles list and there are no live params to migrate. The upgrade handler belongs on the testnet branch instead, where donut has live params still declaring the legacy 0x...00ca address. Moves TestGenesisScriptsActivateCurrentVerifier out of the handler's test file into precompiles/usigverifier so the genesis half of the fix stays covered. * fix: drop the stale utxhashverifier address from genesis lists 0x..00CB has no implementation anywhere and remove-utxverifier strips it from live chains, so leaving it in genesis re-introduced it on every fresh chain. Confirmed absent from live donut params, so this is script-only cleanup. --- .../scripts/setup-genesis-auto.sh | 2 +- local-native/scripts/setup-genesis-auto.sh | 2 +- .../usigverifier/genesis_scripts_test.go | 63 +++++++++++++++++++ scripts/test_node.sh | 2 +- test/utils/bytecode.go | 2 - testnet/core/setup/setup_genesis_validator.sh | 2 +- x/uexecutor/README.md | 2 +- 7 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 precompiles/usigverifier/genesis_scripts_test.go diff --git a/local-multi-validator/scripts/setup-genesis-auto.sh b/local-multi-validator/scripts/setup-genesis-auto.sh index baadd4cb7..db8701666 100755 --- a/local-multi-validator/scripts/setup-genesis-auto.sh +++ b/local-multi-validator/scripts/setup-genesis-auto.sh @@ -231,7 +231,7 @@ update_genesis '.app_state["gov"]["params"]["expedited_voting_period"]="150s"' # EVM update_genesis `printf '.app_state["evm"]["params"]["evm_denom"]="%s"' $DENOM` -update_genesis '.app_state["evm"]["params"]["active_static_precompiles"]=["0x00000000000000000000000000000000000000CB","0x00000000000000000000000000000000000000ca","0x0000000000000000000000000000000000000100","0x0000000000000000000000000000000000000400","0x0000000000000000000000000000000000000800","0x0000000000000000000000000000000000000801","0x0000000000000000000000000000000000000802","0x0000000000000000000000000000000000000803","0x0000000000000000000000000000000000000804","0x0000000000000000000000000000000000000805"]' +update_genesis '.app_state["evm"]["params"]["active_static_precompiles"]=["0x0000000000000000000000000000000000000100","0x0000000000000000000000000000000000000400","0x0000000000000000000000000000000000000800","0x0000000000000000000000000000000000000801","0x0000000000000000000000000000000000000802","0x0000000000000000000000000000000000000803","0x0000000000000000000000000000000000000804","0x0000000000000000000000000000000000000805","0xEC00000000000000000000000000000000000001"]' # EVM Chain config update_genesis `printf '.app_state["evm"]["params"]["chain_config"]["chain_id"]=%s' $EVM_CHAIN_ID` diff --git a/local-native/scripts/setup-genesis-auto.sh b/local-native/scripts/setup-genesis-auto.sh index 6fb6eb72e..7560bea8d 100755 --- a/local-native/scripts/setup-genesis-auto.sh +++ b/local-native/scripts/setup-genesis-auto.sh @@ -113,7 +113,7 @@ update_genesis '.app_state["gov"]["params"]["max_deposit_period"]="300s"' update_genesis '.app_state["gov"]["params"]["voting_period"]="300s"' update_genesis '.app_state["gov"]["params"]["expedited_voting_period"]="60s"' update_genesis ".app_state[\"evm\"][\"params\"][\"evm_denom\"]=\"$DENOM\"" -update_genesis '.app_state["evm"]["params"]["active_static_precompiles"]=["0x00000000000000000000000000000000000000CB","0x00000000000000000000000000000000000000ca","0x0000000000000000000000000000000000000100","0x0000000000000000000000000000000000000400","0x0000000000000000000000000000000000000800","0x0000000000000000000000000000000000000801","0x0000000000000000000000000000000000000802","0x0000000000000000000000000000000000000803","0x0000000000000000000000000000000000000804","0x0000000000000000000000000000000000000805"]' +update_genesis '.app_state["evm"]["params"]["active_static_precompiles"]=["0x0000000000000000000000000000000000000100","0x0000000000000000000000000000000000000400","0x0000000000000000000000000000000000000800","0x0000000000000000000000000000000000000801","0x0000000000000000000000000000000000000802","0x0000000000000000000000000000000000000803","0x0000000000000000000000000000000000000804","0x0000000000000000000000000000000000000805","0xEC00000000000000000000000000000000000001"]' update_genesis ".app_state[\"staking\"][\"params\"][\"bond_denom\"]=\"$DENOM\"" update_genesis ".app_state[\"mint\"][\"params\"][\"mint_denom\"]=\"$DENOM\"" update_genesis '.consensus["params"]["abci"]["vote_extensions_enable_height"]="2"' diff --git a/precompiles/usigverifier/genesis_scripts_test.go b/precompiles/usigverifier/genesis_scripts_test.go new file mode 100644 index 000000000..83468826d --- /dev/null +++ b/precompiles/usigverifier/genesis_scripts_test.go @@ -0,0 +1,63 @@ +package usigverifier_test + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + usigverifierprecompile "github.com/pushchain/push-chain-node/precompiles/usigverifier" +) + +// legacyUSigVerifierAddress is where the Ed25519 signature verifier precompile +// used to live. Nothing is registered at it any more, so a genesis that still +// declares it active routes calls to an unimplemented address, which panics +// (recovered by baseapp, so the tx just fails). +const legacyUSigVerifierAddress = "0x00000000000000000000000000000000000000ca" + +// legacyUtxHashVerifierAddress is the other stale entry: the utxhashverifier +// precompile has no implementation anywhere in the tree, and the +// remove-utxverifier upgrade strips it from live chains. Leaving it in genesis +// would re-introduce on every fresh chain exactly the address that upgrade +// exists to remove. +const legacyUtxHashVerifierAddress = "0x00000000000000000000000000000000000000cb" + +// TestGenesisScriptsActivateCurrentVerifier guards the genesis half of +// F-2026-18829: a fresh chain must activate the address the verifier is actually +// registered at, and must not declare the legacy one. +func TestGenesisScriptsActivateCurrentVerifier(t *testing.T) { + repoRoot := filepath.Join("..", "..") + + scripts := []string{ + "scripts/test_node.sh", + "local-native/scripts/setup-genesis-auto.sh", + "local-multi-validator/scripts/setup-genesis-auto.sh", + "testnet/core/setup/setup_genesis_validator.sh", + } + + for _, script := range scripts { + t.Run(script, func(t *testing.T) { + raw, err := os.ReadFile(filepath.Join(repoRoot, script)) + require.NoError(t, err) + + var line string + for _, l := range strings.Split(string(raw), "\n") { + if strings.Contains(l, "active_static_precompiles") { + line = l + break + } + } + require.NotEmpty(t, line, "no active_static_precompiles assignment found") + + require.NotContains(t, strings.ToLower(line), strings.ToLower(legacyUSigVerifierAddress), + "genesis must not declare the legacy verifier address, nothing is registered at it") + require.NotContains(t, strings.ToLower(line), strings.ToLower(legacyUtxHashVerifierAddress), + "genesis must not declare the utxhashverifier address, nothing is registered at it") + require.Contains(t, strings.ToLower(line), + strings.ToLower(usigverifierprecompile.USigVerifierPrecompileAddress), + "genesis must activate the verifier address the node registers") + }) + } +} diff --git a/scripts/test_node.sh b/scripts/test_node.sh index c6881ae42..e2d07a3de 100755 --- a/scripts/test_node.sh +++ b/scripts/test_node.sh @@ -112,7 +112,7 @@ from_scratch () { update_test_genesis '.app_state["gov"]["params"]["expedited_voting_period"]="15s"' update_test_genesis `printf '.app_state["evm"]["params"]["evm_denom"]="%s"' $DENOM` - update_test_genesis '.app_state["evm"]["params"]["active_static_precompiles"]=["0x00000000000000000000000000000000000000CB","0x00000000000000000000000000000000000000ca","0x0000000000000000000000000000000000000100","0x0000000000000000000000000000000000000400","0x0000000000000000000000000000000000000800","0x0000000000000000000000000000000000000801","0x0000000000000000000000000000000000000802","0x0000000000000000000000000000000000000803","0x0000000000000000000000000000000000000804","0x0000000000000000000000000000000000000805"]' + update_test_genesis '.app_state["evm"]["params"]["active_static_precompiles"]=["0x0000000000000000000000000000000000000100","0x0000000000000000000000000000000000000400","0x0000000000000000000000000000000000000800","0x0000000000000000000000000000000000000801","0x0000000000000000000000000000000000000802","0x0000000000000000000000000000000000000803","0x0000000000000000000000000000000000000804","0x0000000000000000000000000000000000000805","0xEC00000000000000000000000000000000000001"]' update_test_genesis '.app_state["erc20"]["params"]["native_precompiles"]=["0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"]' # https://eips.ethereum.org/EIPS/eip-7528 update_test_genesis `printf '.app_state["erc20"]["token_pairs"]=[{contract_owner:1,erc20_address:"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",denom:"%s",enabled:true}]' $DENOM` update_test_genesis '.app_state["feemarket"]["params"]["no_base_fee"]=false' diff --git a/test/utils/bytecode.go b/test/utils/bytecode.go index 3c054680e..5526ddfc0 100644 --- a/test/utils/bytecode.go +++ b/test/utils/bytecode.go @@ -2,8 +2,6 @@ package utils const UEA_EVM_BYTECODE = "6080604052600436101561001a575b3615610018575f80fd5b005b5f3560e01c80631db61b54146100c95780635378c7aa146100c45780636eaf7ba3146100bf5780637d644a61146100ba5780638bcb651e146100b5578063a84813a4146100b0578063affed0e0146100ab578063c6f1b7e7146100a6578063f698da25146100a1578063f85135cc1461009c5763ffa1ad740361000e576108ac565b6107b3565b61068d565b610641565b610606565b610549565b610447565b6103f6565b610390565b61032f565b34610121575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101215760206040517f2aef22f9d7df5f9d21c56d14029233f3fdaa91917727e1eb68e504d27072d6cd8152f35b5f80fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6060810190811067ffffffffffffffff82111761016e57604052565b610125565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761016e57604052565b604051906101c461012083610173565b565b73ffffffffffffffffffffffffffffffffffffffff81160361012157565b602435906101c4826101c6565b35906101c4826101c6565b67ffffffffffffffff811161016e57601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b929192610242826101fc565b916102506040519384610173565b829481845281830111610121578281602093845f960137010152565b9080601f830112156101215781602061028793359101610236565b90565b3590600282101561012157565b91909161012081840312610121576102ad6101b4565b926102b7826101f1565b84526020820135602085015260408201359167ffffffffffffffff8311610121576102ea6101009261032794830161026c565b6040860152606081013560608601526080810135608086015260a081013560a086015260c081013560c086015260e081013560e08601520161028a565b610100830152565b346101215760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101215760043567ffffffffffffffff8111610121576103886103836020923690600401610297565b610935565b604051908152f35b346101215760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101215760243567ffffffffffffffff8111610121576103ec6103e4602092369060040161026c565b600435610af9565b6040519015158152f35b34610121575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261012157602073ffffffffffffffffffffffffffffffffffffffff60065416604051908152f35b346101215760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101215760043567ffffffffffffffff81116101215760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc8236030112610121576040516104c081610152565b816004013567ffffffffffffffff8111610121576104e4906004369185010161026c565b8152602482013567ffffffffffffffff81116101215761050a906004369185010161026c565b6020820152604482013567ffffffffffffffff811161012157610018926004610536923692010161026c565b60408201526105436101e4565b90610ed6565b346101215760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101215760043567ffffffffffffffff8111610121576101207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc8236030112610121576024359067ffffffffffffffff821161012157366023830112156101215781600401359067ffffffffffffffff8211610121573660248385010111610121576024610018930190600401611101565b34610121575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610121576020600554604051908152f35b34610121575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101215760206040517314191ea54b4c176fcf86f51b0fac7cb1e71df7d78152f35b34610121575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610121576020610388611496565b5f5b8381106106d65750505f910152565b81810151838201526020016106c7565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f602093610722815180928187528780880191016106c5565b0116010190565b906102879160208152604061078061074d84516060602086015260808501906106e6565b60208501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe085830301848601526106e6565b9201519060607fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0828503019101526106e6565b34610121575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610121576060604080516107f081610152565b828152826020820152015261085960405161080a81610152565b6040516108218161081a816112d3565b0382610173565b81526040516108338161081a81611390565b60208201526040516108488161081a81611413565b604082015260405191829182610729565b0390f35b6040519061086c602083610173565b5f8252565b60405190610880604083610173565b600582527f312e302e300000000000000000000000000000000000000000000000000000006020830152565b34610121575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610121576108596108e5610871565b6040519182916020835260208301906106e6565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b6002111561093057565b6108f9565b805173ffffffffffffffffffffffffffffffffffffffff1690610a4e6020820151610a2260408401516020815191012093606081015190608081015160a08201516005549161010060e08501519401519461098f86610926565b61099886610926565b604051998a9860208a019c8d9793610120979360ff97939b9a96929b73ffffffffffffffffffffffffffffffffffffffff6101408c019d7f1d8b43e5066bd20bfdacf7b8f4790c0309403b18434e3699ce3c5e57502ed8c48d521660208c015260408b015260608a0152608089015260a088015260c087015260e086015261010085015216910152565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282610173565b519020610a22610aa2610a5f611496565b9260405192839160208301958690916042927f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201520190565b51902090565b90600182811c92168015610aef575b6020831014610ac257565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b91607f1691610ab7565b610b0f91610b0691611858565b9092919261189c565b73ffffffffffffffffffffffffffffffffffffffff610b2f600354610aa8565b6003601f8211610ba8575b547fffffffffffffffffffffffffffffffffffffffff00000000000000000000000081169160148110610b73575b505060601c91161490565b7fffffffffffffffffffffffffffffffffffffffff0000000000000000000000009250829060140360031b1b16165f80610b68565b5060035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b610b3a565b818110610bde575050565b5f8155600101610bd3565b90601f8211610bf6575050565b6101c49160015f5260205f20906020601f840160051c83019310610c22575b601f0160051c0190610bd3565b9091508190610c15565b9190601f8111610c3b57505050565b6101c4925f5260205f20906020601f840160051c83019310610c2257601f0160051c0190610bd3565b90815167ffffffffffffffff811161016e57610c8c81610c85600254610aa8565b6002610c2c565b602092601f8211600114610cea57610cda929382915f92610cdf575b50507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b600255565b015190505f80610ca8565b60025f527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08216937f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace915f5b868110610d8b5750836001959610610d54575b505050811b01600255565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690555f8080610d49565b91926020600181928685015181550194019201610d36565b90815167ffffffffffffffff811161016e57610dcb81610dc4600354610aa8565b6003610c2c565b602092601f8211600114610e1d57610e18929382915f92610cdf5750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b600355565b60035f527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08216937fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b915f5b868110610ebe5750836001959610610e87575b505050811b01600355565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690555f8080610e7c565b91926020600181928685015181550194019201610e69565b919060045460ff81166110d9577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff006001911617600455825192835167ffffffffffffffff811161016e57610f3481610f2f600154610aa8565b610be9565b6020601f8211600114610ffa5773ffffffffffffffffffffffffffffffffffffffff9392610fa083610fb8946040946101c4999a5f92610cdf5750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b6001555b610fb16020820151610c64565b0151610da3565b1673ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffff00000000000000000000000000000000000000006006541617600655565b60015f527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08216957fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6965f5b8181106110c15750836101c4979860409473ffffffffffffffffffffffffffffffffffffffff989794610fb8976001951061108a575b505050811b01600155610fa4565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690555f808061107c565b83830151895560019098019760209384019301611046565b7f69783db7000000000000000000000000000000000000000000000000000000005f5260045ffd5b919060025f54146112ab5760025f557314191ea54b4c176fcf86f51b0fac7cb1e71df7d73303611254575b5050611139903690610297565b60e0810151801515908161124a575b506112225761116161115c60055460010190565b600555565b604081015161116f81611963565b156111fc575061117e90611c11565b905b156111c557507f3c72595b43537fb9a702f683573f82d3b3ad19487fd74cbb94728a41ec76d1cb6111b96005546040519182918261164d565b0390a16101c460015f55565b8051156111d457805190602001fd5b7facfdb444000000000000000000000000000000000000000000000000000000005f5260045ffd5b611205906119bc565b156112195761121390611a9a565b90611180565b61121390611a3f565b7ff87d9271000000000000000000000000000000000000000000000000000000005f5260045ffd5b905042115f611148565b90611271611277926112696103833687610297565b923691610236565b90610af9565b15611283575f8061112c565b7fc7dbd31d000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f3ee5aeb5000000000000000000000000000000000000000000000000000000005f5260045ffd5b6001545f92916112e282610aa8565b808252916001811690811561135657506001146112fd575050565b60015f9081529293509091907fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf65b83831061133c575060209250010190565b60018160209294939454838587010152019101919061132b565b60209495507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0091509291921683830152151560051b010190565b6002545f929161139f82610aa8565b808252916001811690811561135657506001146113ba575050565b60025f9081529293509091907f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace5b8383106113f9575060209250010190565b6001816020929493945483858701015201910191906113e8565b6003545f929161142282610aa8565b8082529160018116908115611356575060011461143d575050565b60035f9081529293509091907fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b5b83831061147c575060209250010190565b60018160209294939454838587010152019101919061146b565b6040516114a68161081a81611390565b80515f9181156115c957905f915b81831061153557505050610a22610aa26114cc610871565b60208151910120604051928391602083019530918791606091949373ffffffffffffffffffffffffffffffffffffffff9160808501967f2aef22f9d7df5f9d21c56d14029233f3fdaa91917727e1eb68e504d27072d6cd86526020860152604085015216910152565b9091926115b46001916115ae6115a86115a261158361157d6115578b8a611749565b517fff000000000000000000000000000000000000000000000000000000000000001690565b60f81c90565b9361159d60ff8616603081101590816115bd575b5061175f565b6117f1565b9261180c565b60ff1690565b9061184b565b930191906114b4565b6039915011155f611597565b60846040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602160248201527f456d70747920737472696e672063616e6e6f7420626520636f6e76657274656460448201527f2e000000000000000000000000000000000000000000000000000000000000006064820152fd5b9190604083525f6003549061166182610aa8565b918260408701526001811690815f146116dc5750600114611686575b60209150930152565b5060035f9081527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b5b8282106116c5575060209150840160600161167d565b8054828701606001526020909101906001016116af565b60209390849350604092507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166060880152151560051b8601010161167d565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b90815181101561175a570160200190565b61171c565b1561176657565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f4e6f6e2d64696769742063686172616374657220666f756e642e0000000000006044820152fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b90600a820291808304600a149015171561180757565b6117c4565b60ff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd09116019060ff821161180757565b906004820180921161180757565b9190820180921161180757565b8151919060418303611888576118819250602082015190606060408401519301515f1a90611d65565b9192909190565b50505f9160029190565b6004111561093057565b6118a581611892565b806118ae575050565b6118b781611892565b600181036118e7577ff645eedf000000000000000000000000000000000000000000000000000000005f5260045ffd5b6118f081611892565b6002810361192457507ffce698f7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b80611930600392611892565b146119385750565b7fd78bce0c000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b60048151106119b757602001517fffffffff00000000000000000000000000000000000000000000000000000000167f2cc2842d000000000000000000000000000000000000000000000000000000001490565b505f90565b60048151106119b757602001517fffffffff00000000000000000000000000000000000000000000000000000000167fcac656d6000000000000000000000000000000000000000000000000000000001490565b3d15611a3a573d90611a21826101fc565b91611a2f6040519384610173565b82523d5f602084013e565b606090565b5f809173ffffffffffffffffffffffffffffffffffffffff8151166040602083015192015191602083519301915af1611a76611a10565b9091565b908160209103126101215751610287816101c6565b6040513d5f823e3d90fd5b80513073ffffffffffffffffffffffffffffffffffffffff90911603611ba15760200151611ba15760046020611b01611ae860065473ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff1690565b604051928380927ff8ba7e030000000000000000000000000000000000000000000000000000000082525afa908115611bf8575f91611bc9575b5073ffffffffffffffffffffffffffffffffffffffff811615611ba1575f809160405160208101907f52fa5c2200000000000000000000000000000000000000000000000000000000825260048152611b95602482610173565b51915af4611a76611a10565b7fae962d4e000000000000000000000000000000000000000000000000000000005f5260045ffd5b611beb915060203d602011611bf1575b611be38183610173565b810190611a7a565b5f611b3b565b503d611bd9565b611a8f565b805182101561175a5760209160051b010190565b604001519081517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc810190811161180757611c64611c4e826101fc565b91611c5c6040519384610173565b8083526101fc565b917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06020830193013684375f5b8251811015611cc55780611cb2611557611cac60019461183d565b88611749565b5f1a611cbe8286611749565b5301611c91565b509250906020611cd9928051010190611df4565b5f5b8151811015611d58575f80611d0e611cf38486611bfd565b515173ffffffffffffffffffffffffffffffffffffffff1690565b6020611d1a8587611bfd565b510151906040611d2a8688611bfd565b51015191602083519301915af1611d3f611a10565b908015611d50575050600101611cdb565b939092509050565b505060019061028761085d565b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411611de9579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15611bf8575f5173ffffffffffffffffffffffffffffffffffffffff811615611ddf57905f905f90565b505f906001905f90565b5050505f9160039190565b6020818303126101215780519067ffffffffffffffff821161012157019080601f830112156101215781519167ffffffffffffffff831161016e578260051b9160405193611e456020850186610173565b8452602080850193830101918183116101215760208101935b838510611e6d57505050505090565b845167ffffffffffffffff811161012157820160607fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe082860301126101215760405190611eb982610152565b6020810151611ec7816101c6565b825260408101516020830152606081015167ffffffffffffffff81116101215760209101019184601f8401121561012157825191611f04836101fc565b611f116040519182610173565b838152866020858701011161012157611f346020959486958680850191016106c5565b6040820152815201940193611e5e56fea26469706673582212202c80640fc4c48d71a0d3494f62b95ab7ca574ca348f3f949610dec582ff771d464736f6c634300081a0033" -const UEA_SVM_BYTECODE = "6080604052600436101561001a575b3615610018575f80fd5b005b5f3560e01c8063196359b3146100d95780635378c7aa146100d45780636eaf7ba3146100cf5780637d644a61146100ca5780638bcb651e146100c557806397b3a757146100c0578063a84813a4146100bb578063affed0e0146100b6578063c6f1b7e7146100b1578063f698da25146100ac578063f85135cc146100a75763ffa1ad740361000e576108f5565b6107fc565b6106d6565b61068a565b61064f565b610592565b610559565b610457565b610406565b6103a0565b61033f565b34610131575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101315760206040517f3aefc31558906b9b2c54de94f82a9b2455c24b4ba2b642ebb545ea2cc64a1e4b8152f35b5f80fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6060810190811067ffffffffffffffff82111761017e57604052565b610135565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761017e57604052565b604051906101d461012083610183565b565b73ffffffffffffffffffffffffffffffffffffffff81160361013157565b602435906101d4826101d6565b35906101d4826101d6565b67ffffffffffffffff811161017e57601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b9291926102528261020c565b916102606040519384610183565b829481845281830111610131578281602093845f960137010152565b9080601f830112156101315781602061029793359101610246565b90565b3590600282101561013157565b91909161012081840312610131576102bd6101c4565b926102c782610201565b84526020820135602085015260408201359167ffffffffffffffff8311610131576102fa6101009261033794830161027c565b6040860152606081013560608601526080810135608086015260a081013560a086015260c081013560c086015260e081013560e08601520161029a565b610100830152565b346101315760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101315760043567ffffffffffffffff81116101315761039861039360209236906004016102a7565b610979565b604051908152f35b346101315760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101315760243567ffffffffffffffff8111610131576103fc6103f4602092369060040161027c565b6004356114cf565b6040519015158152f35b34610131575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261013157602073ffffffffffffffffffffffffffffffffffffffff60065416604051908152f35b346101315760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101315760043567ffffffffffffffff81116101315760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc8236030112610131576040516104d081610162565b816004013567ffffffffffffffff8111610131576104f4906004369185010161027c565b8152602482013567ffffffffffffffff81116101315761051a906004369185010161027c565b6020820152604482013567ffffffffffffffff811161013157610018926004610546923692010161027c565b60408201526105536101f4565b90610e40565b34610131575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261013157602060405160ca8152f35b346101315760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101315760043567ffffffffffffffff8111610131576101207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc8236030112610131576024359067ffffffffffffffff821161013157366023830112156101315781600401359067ffffffffffffffff821161013157366024838501011161013157602461001893019060040161106b565b34610131575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610131576020600554604051908152f35b34610131575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101315760206040517314191ea54b4c176fcf86f51b0fac7cb1e71df7d78152f35b34610131575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610131576020610398611400565b5f5b83811061071f5750505f910152565b8181015183820152602001610710565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f60209361076b8151809281875287808801910161070e565b0116010190565b90610297916020815260406107c9610796845160606020860152608085019061072f565b60208501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0858303018486015261072f565b9201519060607fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08285030191015261072f565b34610131575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101315760606040805161083981610162565b82815282602082015201526108a260405161085381610162565b60405161086a816108638161123d565b0382610183565b815260405161087c81610863816112fa565b6020820152604051610891816108638161137d565b604082015260405191829182610772565b0390f35b604051906108b5602083610183565b5f8252565b604051906108c9604083610183565b600582527f312e302e300000000000000000000000000000000000000000000000000000006020830152565b34610131575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610131576108a261092e6108ba565b60405191829160208352602083019061072f565b6002111561094c57565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b805173ffffffffffffffffffffffffffffffffffffffff1690610a926020820151610a6660408401516020815191012093606081015190608081015160a08201516005549161010060e0850151940151946109d386610942565b6109dc86610942565b604051998a9860208a019c8d9793610120979360ff97939b9a96929b73ffffffffffffffffffffffffffffffffffffffff6101408c019d7f1d8b43e5066bd20bfdacf7b8f4790c0309403b18434e3699ce3c5e57502ed8c48d521660208c015260408b015260608a0152608089015260a088015260c087015260e086015261010085015216910152565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282610183565b519020610a66610ae6610aa3611400565b9260405192839160208301958690916042927f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201520190565b51902090565b90600182811c92168015610b33575b6020831014610b0657565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b91607f1691610afb565b818110610b48575050565b5f8155600101610b3d565b90601f8211610b60575050565b6101d49160015f5260205f20906020601f840160051c83019310610b8c575b601f0160051c0190610b3d565b9091508190610b7f565b9190601f8111610ba557505050565b6101d4925f5260205f20906020601f840160051c83019310610b8c57601f0160051c0190610b3d565b90815167ffffffffffffffff811161017e57610bf681610bef600254610aec565b6002610b96565b602092601f8211600114610c5457610c44929382915f92610c49575b50507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b600255565b015190505f80610c12565b60025f527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08216937f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace915f5b868110610cf55750836001959610610cbe575b505050811b01600255565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690555f8080610cb3565b91926020600181928685015181550194019201610ca0565b90815167ffffffffffffffff811161017e57610d3581610d2e600354610aec565b6003610b96565b602092601f8211600114610d8757610d82929382915f92610c495750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b600355565b60035f527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08216937fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b915f5b868110610e285750836001959610610df1575b505050811b01600355565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690555f8080610de6565b91926020600181928685015181550194019201610dd3565b919060045460ff8116611043577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff006001911617600455825192835167ffffffffffffffff811161017e57610e9e81610e99600154610aec565b610b53565b6020601f8211600114610f645773ffffffffffffffffffffffffffffffffffffffff9392610f0a83610f22946040946101d4999a5f92610c495750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b6001555b610f1b6020820151610bce565b0151610d0d565b1673ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffff00000000000000000000000000000000000000006006541617600655565b60015f527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08216957fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6965f5b81811061102b5750836101d4979860409473ffffffffffffffffffffffffffffffffffffffff989794610f229760019510610ff4575b505050811b01600155610f0e565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690555f8080610fe6565b83830151895560019098019760209384019301610fb0565b7f69783db7000000000000000000000000000000000000000000000000000000005f5260045ffd5b919060025f54146112155760025f557314191ea54b4c176fcf86f51b0fac7cb1e71df7d733036111be575b50506110a39036906102a7565b60e081015180151590816111b4575b5061118c576110cb6110c660055460010190565b600555565b60408101516110d98161166d565b1561116657506110e89061191e565b905b1561112f57507f3c72595b43537fb9a702f683573f82d3b3ad19487fd74cbb94728a41ec76d1cb6111236005546040519182918261159e565b0390a16101d460015f55565b80511561113e57805190602001fd5b7facfdb444000000000000000000000000000000000000000000000000000000005f5260045ffd5b61116f906116c6565b156111835761117d90611775565b906110ea565b61117d9061171a565b7ff87d9271000000000000000000000000000000000000000000000000000000005f5260045ffd5b905042115f6110b2565b906111db6111e1926111d361039336876102a7565b923691610246565b906114cf565b156111ed575f80611096565b7fa764e90c000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f3ee5aeb5000000000000000000000000000000000000000000000000000000005f5260045ffd5b6001545f929161124c82610aec565b80825291600181169081156112c05750600114611267575050565b60015f9081529293509091907fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf65b8383106112a6575060209250010190565b600181602092949394548385870101520191019190611295565b60209495507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0091509291921683830152151560051b010190565b6002545f929161130982610aec565b80825291600181169081156112c05750600114611324575050565b60025f9081529293509091907f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace5b838310611363575060209250010190565b600181602092949394548385870101520191019190611352565b6003545f929161138c82610aec565b80825291600181169081156112c057506001146113a7575050565b60035f9081529293509091907fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b5b8383106113e6575060209250010190565b6001816020929493945483858701015201910191906113d5565b6114086108ba565b6020815191012060405160208101917f3aefc31558906b9b2c54de94f82a9b2455c24b4ba2b642ebb545ea2cc64a1e4b8352604082015260806060820152610ae68161145660a082016112fa565b306080830152037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282610183565b3d156114b2573d906114998261020c565b916114a76040519384610183565b82523d5f602084013e565b606090565b90816020910312610131575180151581036101315790565b5f91610a6661154f849360405192839160208301957fbbdd82070000000000000000000000000000000000000000000000000000000087526060602485015261151a6084850161137d565b9160448501527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc84830301606485015261072f565b519060ca5afa61155d611488565b90156115765780602080610297935183010191016114b7565b7ffd23ff64000000000000000000000000000000000000000000000000000000005f5260045ffd5b9190604083525f600354906115b282610aec565b918260408701526001811690815f1461162d57506001146115d7575b60209150930152565b5060035f9081527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b5b82821061161657506020915084016060016115ce565b805482870160600152602090910190600101611600565b60209390849350604092507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166060880152151560051b860101016115ce565b60048151106116c157602001517fffffffff00000000000000000000000000000000000000000000000000000000167f2cc2842d000000000000000000000000000000000000000000000000000000001490565b505f90565b60048151106116c157602001517fffffffff00000000000000000000000000000000000000000000000000000000167fcac656d6000000000000000000000000000000000000000000000000000000001490565b5f809173ffffffffffffffffffffffffffffffffffffffff8151166040602083015192015191602083519301915af1611751611488565b9091565b908160209103126101315751610297816101d6565b6040513d5f823e3d90fd5b80513073ffffffffffffffffffffffffffffffffffffffff9091160361187c576020015161187c57600460206117dc6117c360065473ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff1690565b604051928380927ff8ba7e030000000000000000000000000000000000000000000000000000000082525afa9081156118d3575f916118a4575b5073ffffffffffffffffffffffffffffffffffffffff81161561187c575f809160405160208101907ff6829c3200000000000000000000000000000000000000000000000000000000825260048152611870602482610183565b51915af4611751611488565b7fae962d4e000000000000000000000000000000000000000000000000000000005f5260045ffd5b6118c6915060203d6020116118cc575b6118be8183610183565b810190611755565b5f611816565b503d6118b4565b61176a565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b80518210156119195760209160051b010190565b6118d8565b604001519081517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc8101908111611a985761197161195b8261020c565b916119696040519384610183565b80835261020c565b917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06020830193013684375f5b82518110156119f857806119e56119bf6119b9600194611ac5565b88611ad8565b517fff000000000000000000000000000000000000000000000000000000000000001690565b5f1a6119f18286611ad8565b530161199e565b509250906020611a0c928051010190611ae9565b5f5b8151811015611a8b575f80611a41611a268486611905565b515173ffffffffffffffffffffffffffffffffffffffff1690565b6020611a4d8587611905565b510151906040611a5d8688611905565b51015191602083519301915af1611a72611488565b908015611a83575050600101611a0e565b939092509050565b50506001906102976108a6565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b9060048201809211611ad357565b611a98565b908151811015611919570160200190565b6020818303126101315780519067ffffffffffffffff821161013157019080601f830112156101315781519167ffffffffffffffff831161017e578260051b9160405193611b3a6020850186610183565b8452602080850193830101918183116101315760208101935b838510611b6257505050505090565b845167ffffffffffffffff811161013157820160607fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe082860301126101315760405190611bae82610162565b6020810151611bbc816101d6565b825260408101516020830152606081015167ffffffffffffffff81116101315760209101019184601f8401121561013157825191611bf98361020c565b611c066040519182610183565b838152866020858701011161013157611c2960209594869586808501910161070e565b6040820152815201940193611b5356fea26469706673582212201ccb86d8429e38b976f7d8a6f0b6fbb7c3cbc64fa3443fe78c9f0ff9b68a483464736f6c634300081a0033" - const UEA_PROXY_BYTECODE = "608060405260043610610028575f3560e01c806323efa7ec14610032578063aaf10f4214610051575b6100306100a8565b005b34801561003d575f80fd5b5061003061004c366004610368565b6100ba565b34801561005c575f80fd5b507f868a771a75a4aa6c2be13e9a9617cb8ea240ed84a3a90c8469537393ec3e115d5460405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b6100b86100b36102cc565b61034a565b565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000810460ff16159067ffffffffffffffff165f811580156101045750825b90505f8267ffffffffffffffff1660011480156101205750303b155b90508115801561012e575080155b15610165576040517ff92ee8a900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b84547fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000016600117855583156101c65784547fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff16680100000000000000001785555b5f6101ef7f868a771a75a4aa6c2be13e9a9617cb8ea240ed84a3a90c8469537393ec3e115d5490565b905073ffffffffffffffffffffffffffffffffffffffff81161561023f576040517fae962d4e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b867f868a771a75a4aa6c2be13e9a9617cb8ea240ed84a3a90c8469537393ec3e115d555083156102c45784547fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b505050505050565b5f806102f67f868a771a75a4aa6c2be13e9a9617cb8ea240ed84a3a90c8469537393ec3e115d5490565b905073ffffffffffffffffffffffffffffffffffffffff8116610345576040517fae962d4e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b919050565b365f80375f80365f845af43d5f803e808015610364573d5ff35b3d5ffd5b5f60208284031215610378575f80fd5b813573ffffffffffffffffffffffffffffffffffffffff8116811461039b575f80fd5b939250505056fea2646970667358221220c4b8f9457567bdcd08b95faef7df86de4e9daead65e2db22018126d9eb77d85864736f6c634300081a0033" const HANDLER_CONTRACT_BYTECODE = "608080604052600436101561001c575b50361561001a575f80fd5b005b5f905f3560e01c908162bc574b14614b895750806301ffc9a714614ab5578063022d63fb14614a98578063049bf04c146149955780630615f0a2146149355780630840ba721461445e57806308af7f86146143fe5780630aa6220b146142bc5780631a4e49d4146142935780631a873ce4146142605780631c90064a14614200578063248a9ca3146141ae57806325c36c751461416a5780632f2ff15d146140df57806336568abe14613ef65780633f4ba83a14613e1b5780634243fbaa14613dd1578063447146a214613d875780634b1d2eeb14613d475780634d20d0f814613d145780634d49fbf314613b105780634eb7d1a114613ad05780634f882f3314613a6b57806355b487f1146139f9578063580fc6a9146138cc5780635b549182146138995780635c975abb14613858578063634e93da146136c35780636435967b14612eec578063649a5ec714612bc257806364f10e5014612b7857806368c70c9e14612b2d5780636ca752e314612ae25780636f419e31146129fd5780637574d9a0146129e0578063780ad8271461235d57806378a881271461233a578063801bee15146120d157806381fbadad146120b35780638377e2301461207f5780638456cb5914611f0c5780638468c05b14611ea557806384ef8ffc14611d6e57806387525a3714611d735780638da5cb5b14611d6e5780638f247b8c14611cc85780638f40e8f514611cab57806391d1485414611c34578063a1eda53c14611bae578063a217fddf14611b92578063a5172ddb14611b47578063a861469f14611afd578063ad14d38514611ab3578063b5d8349f14611a52578063b6322a9f14611a07578063b6aa5ce3146119eb578063bb88f3d9146119a0578063bfc7a3e4146118d2578063c6b54b3c14611897578063c6f1b7e714611846578063cc8463c81461181b578063cd20c6e8146117d6578063cefc14291461161f578063cf6eefb714611593578063d29c5c1c14611558578063d547741f146114c1578063d602b9fd14611426578063dbc1b464146113c5578063dd19e755146111af578063e229cd7614611192578063e63ab1e914611157578063ec87621c1461111c578063eefbaa3514610ffd578063f5b541a614610fc2578063f6b9ec7c14610fa5578063f881446714610820578063f8c8765e146104b8578063fb46e99d1461049a578063fc6b5de81461043c5763fcb6c2300361000f5734610439576040600319360112610439576103ac614cf6565b6024359081151580920361043557602073ffffffffffffffffffffffffffffffffffffffff7f42cc79f957a9535dc49c660eec62747fb540bef0dd29cd7f6c0a810816af4cff92169283855260038252604085207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0081541660ff8316179055604051908152a280f35b8280fd5b80fd5b5034610439576020600319360112610439576004359067ffffffffffffffff8211610439576020610487816104743660048701614c7e565b8160405193828580945193849201614d5f565b8101601281520301902054604051908152f35b50346104395780600319360112610439576020600654604051908152f35b5034610439576080600319360112610439576104d2614cf6565b6104da614d19565b6104e2614d3c565b6064359173ffffffffffffffffffffffffffffffffffffffff831680930361081c577ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00549367ffffffffffffffff60ff8660401c1615951680159081610814575b600114908161080a575b159081610801575b506107d95773ffffffffffffffffffffffffffffffffffffffff92918380928760017fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000007ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005416177ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0055610763575b6105eb615874565b6105f3615874565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00556106203361505c565b50167fffffffffffffffffffffffff0000000000000000000000000000000000000000600a541617600a55167fffffffffffffffffffffffff00000000000000000000000000000000000000006007541617600755167fffffffffffffffffffffffff000000000000000000000000000000000000000060085416176008557fffffffffffffffffffffffff000000000000000000000000000000000000000060095416176009556106cf5780f35b7fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054167ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00557fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2602060405160018152a180f35b680100000000000000007fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005416177ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00556105e3565b6004867ff92ee8a9000000000000000000000000000000000000000000000000000000008152fd5b9050155f610555565b303b15915061054d565b869150610543565b8480fd5b5060a060031936011261043957610835614cf6565b9061083e614dc3565b9160443591606435936084359273ffffffffffffffffffffffffffffffffffffffff8416928385036104395773ffffffffffffffffffffffffffffffffffffffff601054163303610f7d576108916154d7565b61089961552a565b8691839773ffffffffffffffffffffffffffffffffffffffff8216948515610f55578615610f55573415610f2d578815610f2d5762ffffff1615610eeb575b15610ec3575b824211610e9b5761099960208973ffffffffffffffffffffffffffffffffffffffff6007541673ffffffffffffffffffffffffffffffffffffffff600a54169488861090815f14610e945786915b15610e8c57905b604051958694859384937f1698ee820000000000000000000000000000000000000000000000000000000085526004850191604091949373ffffffffffffffffffffffffffffffffffffffff62ffffff9281606087019816865216602085015216910152565b03915afa908115610d41579073ffffffffffffffffffffffffffffffffffffffff918491610e5d575b501615610e3557803b15610d3d5781600491604051928380927fd0e30db000000000000000000000000000000000000000000000000000000000825234905af18015610dd057908291610e20575b5050610a4f73ffffffffffffffffffffffffffffffffffffffff600a5416349073ffffffffffffffffffffffffffffffffffffffff6008541690615760565b62ffffff73ffffffffffffffffffffffffffffffffffffffff600a54169760405198610a7a8a614bb9565b89528460208a0152169182604089015230606089015260808801528560a08801523460c08801528060e08801526020610b6661010473ffffffffffffffffffffffffffffffffffffffff6008541699846040519b8c9485937fdb3e2198000000000000000000000000000000000000000000000000000000008552600485019073ffffffffffffffffffffffffffffffffffffffff60e0809282815116855282602082015116602086015262ffffff60408201511660408601528260608201511660608601526080810151608086015260a081015160a086015260c081015160c0860152015116910152565b5af1968715610e13578197610ddb575b50610bb273ffffffffffffffffffffffffffffffffffffffff600a541673ffffffffffffffffffffffffffffffffffffffff600854169061565a565b6040517f42966c6800000000000000000000000000000000000000000000000000000000815286600482015260208160248185885af1908115610dd0578291610da1575b5015610d795786340394348611610d4c57873403610c78575b505060606040967f01fd625a5ce1109c10761818e2ef64ea92cd4966d78086d37e5a4b50e322687892885191825287602083015288820152a360017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f005582519182526020820152f35b73ffffffffffffffffffffffffffffffffffffffff600a5416803b15610435578280916024604051809481937f2e1a7d4d0000000000000000000000000000000000000000000000000000000083528c60048401525af18015610d41579183918893610d23575b5081809381925af1610cef614e7b565b5015610cfb5780610c0f565b807f90b8ec180000000000000000000000000000000000000000000000000000000060049252fd5b610d309193508290614c03565b610d3d578186915f610cdf565b5080fd5b6040513d85823e3d90fd5b6024827f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b807f66b2a6fe0000000000000000000000000000000000000000000000000000000060049252fd5b610dc3915060203d602011610dc9575b610dbb8183614c03565b810190614ed9565b5f610bf6565b503d610db1565b6040513d84823e3d90fd5b9096506020813d602011610e0b575b81610df760209383614c03565b81010312610e075751955f610b76565b5f80fd5b3d9150610dea565b50604051903d90823e3d90fd5b81610e2a91614c03565b61043957805f610a10565b6004827f76ecffc0000000000000000000000000000000000000000000000000000000008152fd5b610e7f915060203d602011610e85575b610e778183614c03565b810190614fac565b5f6109c2565b503d610e6d565b508590610933565b809161092c565b6004827f1ab7da6b000000000000000000000000000000000000000000000000000000008152fd5b9150600654603c810290808204603c1490151715610d4c57610ee5904261504f565b916108de565b8483526004602052604083205462ffffff169850886108d8575b6004837f3733548a000000000000000000000000000000000000000000000000000000008152fd5b6004847f1f2a2005000000000000000000000000000000000000000000000000000000008152fd5b6004847fd92e233d000000000000000000000000000000000000000000000000000000008152fd5b807fbce361b00000000000000000000000000000000000000000000000000000000060049252fd5b503461043957806003193601126104395760206040516101f48152f35b503461043957806003193601126104395760206040517f97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b9298152f35b50346104395760606003193601126104395760043567ffffffffffffffff8111610d3d5761102f903690600401614c7e565b60243560443581156110f457916110dd917f5e41bf0052b493123a63e4e0d9095ed4324108e489d58c9a0948b2be366ac8c6936110b8604051838551916020818189019461107e818388614d5f565b8101600b8152030190205582604051602081885161109d818388614d5f565b81016011815203019020556040519182918651928391614d5f565b8101906012825260208142930301902055604051938493608085526080850190614d80565b91602084015260408301524260608301520390a180f35b6004847fe661aed0000000000000000000000000000000000000000000000000000000008152fd5b503461043957806003193601126104395760206040517f241ecf16d79d0f8dbfb92cbc07fe17840425976cf0667f022fe9877caa831b088152f35b503461043957806003193601126104395760206040517f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a8152f35b503461043957806003193601126104395760206040516113888152f35b5034610439576040600319360112610439576111c9614cf6565b602435604080516111da8282614c03565b600f815260208101927f6569703135353a3131313535313131000000000000000000000000000000000084528251865b600f81106113b257506014600f820152602f90205490811561138a578061134b5750915b73ffffffffffffffffffffffffffffffffffffffff8151602081855161125581838b614d5f565b8101600c815203019020541693841561132357602061127e918351809381928751928391614d5f565b8101600b815203019020549081156112fb57806112f1949596976112a185615421565b73ffffffffffffffffffffffffffffffffffffffff6112c08886614ec6565b991681526013602052205490805197889788526020880152860152606085015260c0608085015260c0840190614d80565b9060a08301520390f35b6004877fe661aed0000000000000000000000000000000000000000000000000000000008152fd5b6004877fd92e233d000000000000000000000000000000000000000000000000000000008152fd5b929080841061135a575061122e565b86604491857fff632bea000000000000000000000000000000000000000000000000000000008352600452602452fd5b6004877fba496b84000000000000000000000000000000000000000000000000000000008152fd5b806020809286010151818401520161120a565b5034610439576020600319360112610439576004359067ffffffffffffffff821161043957602073ffffffffffffffffffffffffffffffffffffffff611412826104743660048801614c7e565b8101600c8152030190205416604051908152f35b503461043957806003193601126104395761143f6152ab565b7feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d869840080547fffffffffffff0000000000000000000000000000000000000000000000000000811690915560a01c65ffffffffffff1661149a5780f35b7f8886ebfc4259abdbc16601dd8fb5678e54878f47b3c34836cfc51154a96051098180a180f35b5034610439576040600319360112610439576004356114de614d19565b90801561153057908161152761152261152c945f527f02dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b626800602052600160405f20015490565b61539b565b615aa7565b5080f35b6004837f3fc3c27a000000000000000000000000000000000000000000000000000000008152fd5b503461043957806003193601126104395760206040517fe53b6cbb4204145187ea4c9b95f311e9ee4f2690cdb9b3a219f863f3a71e06c98152f35b5034610439578060031936011261043957604065ffffffffffff6115f97feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d8698400549065ffffffffffff73ffffffffffffffffffffffffffffffffffffffff83169260a01c1690565b73ffffffffffffffffffffffffffffffffffffffff849392935193168352166020820152f35b50346104395780600319360112610439577feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d86984005473ffffffffffffffffffffffffffffffffffffffff1633036117aa577feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d86984005473ffffffffffffffffffffffffffffffffffffffff81169060a01c65ffffffffffff16801580156117a0575b611775575061170b9061170573ffffffffffffffffffffffffffffffffffffffff7feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d869840154166159dd565b5061505c565b507fffffffffffff00000000000000000000000000000000000000000000000000007feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d869840054167feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d86984005580f35b7f19ca5ebb000000000000000000000000000000000000000000000000000000008352600452602482fd5b50428110156116bc565b807fc22c8022000000000000000000000000000000000000000000000000000000006024925233600452fd5b503461043957602060031936011261043957604060209173ffffffffffffffffffffffffffffffffffffffff61180a614cf6565b168152601383522054604051908152f35b50346104395780600319360112610439576020611836614fd8565b65ffffffffffff60405191168152f35b5034610439578060031936011261043957602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b503461043957806003193601126104395760206040517f0f6ee822d2ee125e4ce6edbae6c10a76fa9fd4617e0399ab687226fa334421008152f35b50346104395760206003193601126104395773ffffffffffffffffffffffffffffffffffffffff611901614cf6565b611909615313565b1680156119785773ffffffffffffffffffffffffffffffffffffffff601054827fffffffffffffffffffffffff0000000000000000000000000000000000000000821617601055167fda4281cd6f2da5f8862b210df953c55b2e8c441b67821264ef4a35ca833fc2a78380a380f35b6004827fd92e233d000000000000000000000000000000000000000000000000000000008152fd5b5034610439576020600319360112610439576004359067ffffffffffffffff82116104395760206119d8816104743660048701614c7e565b8101601681520301902054604051908152f35b5034610439578060031936011261043957602060405160648152f35b5034610439576020600319360112610439576004359067ffffffffffffffff8211610439576020611a3f816104743660048701614c7e565b8101601581520301902054604051908152f35b5034610439576020600319360112610439576004359067ffffffffffffffff821161043957602073ffffffffffffffffffffffffffffffffffffffff611a9f826104743660048801614c7e565b8101600d8152030190205416604051908152f35b50346104395760206003193601126104395760ff604060209273ffffffffffffffffffffffffffffffffffffffff611ae9614cf6565b168152600384522054166040519015158152f35b50346104395760206003193601126104395762ffffff604060209273ffffffffffffffffffffffffffffffffffffffff611b35614cf6565b16815260048452205416604051908152f35b5034610439576020600319360112610439576004359067ffffffffffffffff8211610439576020611b7f816104743660048701614c7e565b8101600b81520301902054604051908152f35b5034610439578060031936011261043957602090604051908152f35b50346104395780600319360112610439577feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d8698401548060d01c9182151580611c2a575b15611c21575060a01c65ffffffffffff165b6040805165ffffffffffff928316815292909116602083015290f35b0390f35b91505080611c01565b5042831015611bef565b50346104395760406003193601126104395773ffffffffffffffffffffffffffffffffffffffff6040611c65614d19565b9260043581527f02dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b6268006020522091165f52602052602060ff60405f2054166040519015158152f35b50346104395780600319360112610439576020604051610bb88152f35b50346104395760206003193601126104395773ffffffffffffffffffffffffffffffffffffffff611cf7614cf6565b611cff615313565b1680156119785773ffffffffffffffffffffffffffffffffffffffff600a54827fffffffffffffffffffffffff0000000000000000000000000000000000000000821617600a55167f3aa820195fb2daaa2fb7669944e7c9eccf99303478e74f09887bd8fe649b9c588380a380f35b614e29565b503461043957604060031936011261043957611d8d614cf6565b73ffffffffffffffffffffffffffffffffffffffff611daa614d19565b91611db3615313565b169081158015611e87575b611e5f578173ffffffffffffffffffffffffffffffffffffffff6040927f37af3ddd829f67f886ff225a9b652d2104f68d4cba4cbc989264fa5ef7621ac1947fffffffffffffffffffffffff0000000000000000000000000000000000000000600754161760075516807fffffffffffffffffffffffff0000000000000000000000000000000000000000600854161760085582519182526020820152a180f35b6004837fd92e233d000000000000000000000000000000000000000000000000000000008152fd5b5073ffffffffffffffffffffffffffffffffffffffff811615611dbe565b5034610439577f8529702b0bf1b5d9d01b0c119b695d9365ef62dc9f5e6a87c24f77bb38fd6518611ed536614cc4565b90816040516020818451611eec8183858901614d5f565b8101601681520301902055611f0660405192839283614eaa565b0390a180f35b50346104395780600319360112610439577f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a81527f02dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b6268006020526040812073ffffffffffffffffffffffffffffffffffffffff33165f5260205260ff60405f2054161561202f57611f996154d7565b60017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff007fcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f033005416177fcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f03300557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586020604051338152a180f35b807fe2517d3f0000000000000000000000000000000000000000000000000000000060449252336004527f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a602452fd5b5034610439578060031936011261043957602073ffffffffffffffffffffffffffffffffffffffff60105416604051908152f35b50346104395780600319360112610439576020600e54604051908152f35b50346104395760606003193601126104395760043567ffffffffffffffff8111610d3d57612103903690600401614c7e565b61210b614d19565b60443562ffffff81169182820361081c5773ffffffffffffffffffffffffffffffffffffffff8116801561231257916020916121ee9373ffffffffffffffffffffffffffffffffffffffff600754169173ffffffffffffffffffffffffffffffffffffffff600a541691821091825f1461230b5780925b1561230357506040517f1698ee8200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff92831660048201529116602482015262ffffff90921660448301529092839190829081906064820190565b03915afa80156122f85773ffffffffffffffffffffffffffffffffffffffff9185916122d9575b50169081156122b157916122a0917f21e3c1439de176cb39006e603b26a8d890fe2267c804597e40d2954871141d7d93604051602081855161225a8183858a01614d5f565b8101600d815203019020827fffffffffffffffffffffffff0000000000000000000000000000000000000000825416179055604051938493606085526060850190614d80565b91602084015260408301520390a180f35b6004847f76ecffc0000000000000000000000000000000000000000000000000000000008152fd5b6122f2915060203d602011610e8557610e778183614c03565b5f612215565b6040513d86823e3d90fd5b905090610933565b8192612182565b6004867fd92e233d000000000000000000000000000000000000000000000000000000008152fd5b50346104395761235a61234c36614dd5565b916123556154d7565b614ef1565b80f35b50346104395760c060031936011261043957612377614cf6565b60243590612383614d3c565b906064359262ffffff8416908185036127c75760843560a435936123a56154d7565b6123ad61552a565b6123b88684836155a1565b8473ffffffffffffffffffffffffffffffffffffffff821697888a52600360205260ff60408b205416156129b8579415612977575b15612922575b8442116128fa576020846124b0928a73ffffffffffffffffffffffffffffffffffffffff600754169173ffffffffffffffffffffffffffffffffffffffff600a541690818d10805f146128f35781935b501561230357506040517f1698ee8200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff92831660048201529116602482015262ffffff90921660448301529092839190829081906064820190565b03915afa80156128795773ffffffffffffffffffffffffffffffffffffffff9189916128d4575b5016156128ac578015612884576040517f47e7ef24000000000000000000000000000000000000000000000000000000008152306004820152602481018390526020816044818b8b5af190811561287957889161285a575b5015612832576125588273ffffffffffffffffffffffffffffffffffffffff6008541688615760565b62ffffff73ffffffffffffffffffffffffffffffffffffffff600a5416936040519461258386614bb9565b8886526020808701918252929091166040808701828152306060890190815260808901998a5260a0890188815260c08a0188815260e08b018f815260085495517f414bf3890000000000000000000000000000000000000000000000000000000081529b5173ffffffffffffffffffffffffffffffffffffffff90811660048e01529751881660248d0152935162ffffff1660448c01529151861660648b0152995160848a0152985160a4890152975160c48801529651821660e48701529585916101049183918c91165af19283156128275787936127f3575b5082106127cb5761268673ffffffffffffffffffffffffffffffffffffffff600854168661565a565b8573ffffffffffffffffffffffffffffffffffffffff600a5416803b15610d3d578180916024604051809481937f2e1a7d4d0000000000000000000000000000000000000000000000000000000083528960048401525af18015610dd0576127b2575b5080808085885af16126f9614e7b565b501561278a57927ff5d6ca9b390b5271e0cbb3d43b4d708d5b17804cb81a4c65e027226d87ccf0e2949273ffffffffffffffffffffffffffffffffffffffff9260c09584600a54169060405196875260208701526040860152606085015260808401521660a0820152a160017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f005580f35b6004867f90b8ec18000000000000000000000000000000000000000000000000000000008152fd5b816127bc91614c03565b6127c757855f6126e9565b8580fd5b6004867f8199f5f3000000000000000000000000000000000000000000000000000000008152fd5b9092506020813d60201161281f575b8161280f60209383614c03565b81010312610e075751915f61265d565b3d9150612802565b6040513d89823e3d90fd5b6004877f66b2a6fe000000000000000000000000000000000000000000000000000000008152fd5b612873915060203d602011610dc957610dbb8183614c03565b5f61252f565b6040513d8a823e3d90fd5b6004877f1f2a2005000000000000000000000000000000000000000000000000000000008152fd5b6004877f76ecffc0000000000000000000000000000000000000000000000000000000008152fd5b6128ed915060203d602011610e8557610e778183614c03565b5f6124d7565b8293612443565b6004887f1ab7da6b000000000000000000000000000000000000000000000000000000008152fd5b9350600654603c810290808204603c149015171561294a57612944904261504f565b936123f3565b6024887f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b8789526004602052604089205462ffffff169450846123ed576004897f3733548a000000000000000000000000000000000000000000000000000000008152fd5b60048a7f4e38f95a000000000000000000000000000000000000000000000000000000008152fd5b503461043957806003193601126104395760206040516127108152f35b503461043957604060031936011261043957612a17614cf6565b73ffffffffffffffffffffffffffffffffffffffff612a34614dc3565b9116908115611e5f5762ffffff16606481141580612ad6575b80612aca575b80612abe575b610f055760207f5734dc08ec8c21bd34e0f102d90ea2d1a9dbdcf23e787dc8744d2d0dd227fc73918385526004825260408520817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000825416179055604051908152a280f35b50612710811415612a59565b50610bb8811415612a53565b506101f4811415612a4d565b5034610439576020600319360112610439576004359067ffffffffffffffff8211610439576020612b1a816104743660048701614c7e565b8101601481520301902054604051908152f35b5034610439576020600319360112610439576004359067ffffffffffffffff8211610439576020612b65816104743660048701614c7e565b8101601181520301902054604051908152f35b503461043957612b9b612b8a36614dd5565b91612b936154d7565b61235561552a565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f005580f35b50346104395760206003193601126104395760043565ffffffffffff811680820361043557612bef6152ab565b612bf842615b7a565b9065ffffffffffff612c08614fd8565b1680821115612e8457507ff1038c18cf84a56e432fdbfaf746924b7ea511dfe03a6506a0ceba4888788d9b929165ffffffffffff826206978080612c569510911802620697801816906154b9565b907feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d8698401548060d01c80612dc3575b5050612d13817fffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffff79ffffffffffff00000000000000000000000000000000000000007feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d8698401549260a01b169116177feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d869840155565b612da18279ffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffff00000000000000000000000000000000000000000000000000007feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d8698401549260d01b169116177feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d869840155565b6040805165ffffffffffff928316815292909116602083015281908101611f06565b421115612e5a5779ffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffff00000000000000000000000000000000000000000000000000007feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d8698400549260301b169116177feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d8698400555b5f80612c83565b507f2b1fa2edafe6f7b9e97c1a9e0c3660e645beb2dcaa2d45bdbf9beaf5472e1ec58480a1612e53565b0365ffffffffffff8111612ebf577ff1038c18cf84a56e432fdbfaf746924b7ea511dfe03a6506a0ceba4888788d9b9291612c5691906154b9565b6024847f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b5034610e075760c0600319360112610e0757612f06614cf6565b602435612f11614d3c565b6064358015908115809103610e075760843562ffffff811690818103610e075760a43573ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016330361369b57612f796154d7565b612f8161552a565b612f8c86888a6155a1565b5f94156130d85750506040517f47e7ef2400000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85166004820152602481018690529050602081806044810103818a73ffffffffffffffffffffffffffffffffffffffff8b165af19081156128275787916130b9575b50156130915773ffffffffffffffffffffffffffffffffffffffff7ffa6ff091ec99bdfd127d51e7786764f2ff7e39f866bbb2a2996e1597052641e49360609382935b6040519788526020880152604087015216941692a360017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f005580f35b6004867f66b2a6fe000000000000000000000000000000000000000000000000000000008152fd5b6130d2915060203d602011610dc957610dbb8183614c03565b5f613012565b809192939450156136735773ffffffffffffffffffffffffffffffffffffffff871691825f52600360205260ff60405f2054161561364b579215613609575b600654603c810290808204603c14901517156135dc57613137904261504f565b8042116135b4576131ef60208573ffffffffffffffffffffffffffffffffffffffff6007541673ffffffffffffffffffffffffffffffffffffffff600a541680881090815f146135ad578d915b156135a5576040517f1698ee8200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff92831660048201529116602482015262ffffff90921660448301529092839190829081906064820190565b03915afa80156134b05773ffffffffffffffffffffffffffffffffffffffff915f91613586575b50161561355e576040517f47e7ef24000000000000000000000000000000000000000000000000000000008152306004820152602481018890526020816044815f885af19081156134b0575f9161353f575b5015613517576132918773ffffffffffffffffffffffffffffffffffffffff6008541685615760565b62ffffff73ffffffffffffffffffffffffffffffffffffffff600a541694604051956132bc87614bb9565b858752602087015216604085015230606085015260808401528560a08401528060c08401525f60e084015260206133a661010473ffffffffffffffffffffffffffffffffffffffff60085416955f60405197889485937f414bf389000000000000000000000000000000000000000000000000000000008552600485019073ffffffffffffffffffffffffffffffffffffffff60e0809282815116855282602082015116602086015262ffffff60408201511660408601528260608201511660608601526080810151608086015260a081015160a086015260c081015160c0860152015116910152565b5af19283156134b0575f936134e3575b5082106134bb576133e09073ffffffffffffffffffffffffffffffffffffffff600854169061565a565b73ffffffffffffffffffffffffffffffffffffffff600a5416803b15610e07575f80916024604051809481937f2e1a7d4d0000000000000000000000000000000000000000000000000000000083528760048401525af180156134b05761349b575b508580808084875af1613453614e7b565b501561278a5773ffffffffffffffffffffffffffffffffffffffff7ffa6ff091ec99bdfd127d51e7786764f2ff7e39f866bbb2a2996e1597052641e493606093829390613055565b6134a89196505f90614c03565b5f945f613442565b6040513d5f823e3d90fd5b7f8199f5f3000000000000000000000000000000000000000000000000000000005f5260045ffd5b9092506020813d60201161350f575b816134ff60209383614c03565b81010312610e075751915f6133b6565b3d91506134f2565b7f66b2a6fe000000000000000000000000000000000000000000000000000000005f5260045ffd5b613558915060203d602011610dc957610dbb8183614c03565b5f613268565b7f76ecffc0000000000000000000000000000000000000000000000000000000005f5260045ffd5b61359f915060203d602011610e8557610e778183614c03565b5f613216565b508c90610933565b8091613184565b7f1ab7da6b000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b9150805f52600460205262ffffff60405f2054169182613117577f3733548a000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f4e38f95a000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f22c50cbf000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f53e51723000000000000000000000000000000000000000000000000000000005f5260045ffd5b34610e07576020600319360112610e07576136dc614cf6565b6136e46152ab565b7f3377dc44241e779dd06afab5b788a35ca5f3b778836e2990bdb26a2a4b2e5ed6602061372161371342615b7a565b61371b614fd8565b906154b9565b65ffffffffffff73ffffffffffffffffffffffffffffffffffffffff6137897feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d8698400549065ffffffffffff73ffffffffffffffffffffffffffffffffffffffff83169260a01c1690565b96905016947feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d869840054867fffffffffffff000000000000000000000000000000000000000000000000000079ffffffffffff00000000000000000000000000000000000000008660a01b16921617177feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d8698400551661382f575b65ffffffffffff60405191168152a2005b7f8886ebfc4259abdbc16601dd8fb5678e54878f47b3c34836cfc51154a96051095f80a161381e565b34610e07575f600319360112610e0757602060ff7fcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f0330054166040519015158152f35b34610e07575f600319360112610e0757602073ffffffffffffffffffffffffffffffffffffffff60075416604051908152f35b34610e07576040600319360112610e075760043567ffffffffffffffff8111610e07576138fd903690600401614c7e565b73ffffffffffffffffffffffffffffffffffffffff61391a614d19565b1680156139d1577f0c7d242571a289736ea536c54ebe236d31ba62abfd4f22b8d54d2988dc0dd949916139c6915f6139a76020604051855190828181890193613964818387614d5f565b8101600c815203019020857fffffffffffffffffffffffff0000000000000000000000000000000000000000825416179055604051809381928851928391614d5f565b8101600b81520301902055604051928392604084526040840190614d80565b9060208301520390a1005b7fd92e233d000000000000000000000000000000000000000000000000000000005f5260045ffd5b34610e07576040600319360112610e0757613a12614cf6565b73ffffffffffffffffffffffffffffffffffffffff1660243581156139d15760207f911a025fb070fa2a29c37a3bf4c00d16acf15583cd050f17bdbacbab7e72320391835f52601382528060405f2055604051908152a2005b34610e07577f57ad858a99d9aee6f1fd395e454bb1659eb8500ccb081c729a103dc2247ba3a4613a9a36614cc4565b90816040516020818451613ab18183858901614d5f565b8101601581520301902055613acb60405192839283614eaa565b0390a1005b34610e07576020600319360112610e07576004355f526002602052602073ffffffffffffffffffffffffffffffffffffffff60405f205416604051908152f35b34610e07576020600319360112610e075760045f73ffffffffffffffffffffffffffffffffffffffff613b41614cf6565b16604051928380927fa0c50b690000000000000000000000000000000000000000000000000000000082525afa9081156134b0575f91613c9b575b5060405181519060208181850193613b95818387614d5f565b8101601581520301902054918215613c735773ffffffffffffffffffffffffffffffffffffffff6040516020818451613bcf818389614d5f565b8101600c81520301902054169182156139d1576020613bf991604051809381928651928391614d5f565b8101600b81520301902054908115613c4b57611c1d91613c1882615421565b613c228582614ec6565b94604051958695865260208601526040850152606084015260a0608084015260a0830190614d80565b7fe661aed0000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f9502a873000000000000000000000000000000000000000000000000000000005f5260045ffd5b90503d805f833e613cac8183614c03565b810190602081830312610e075780519067ffffffffffffffff8211610e07570181601f82011215610e07578051613ce281614c44565b92613cf06040519485614c03565b81845260208284010111610e0757613d0e9160208085019101614d5f565b81613b7c565b34610e07575f600319360112610e0757602073ffffffffffffffffffffffffffffffffffffffff60095416604051908152f35b34610e07576020600319360112610e07576004355f526001602052602073ffffffffffffffffffffffffffffffffffffffff60405f205416604051908152f35b34610e07576020600319360112610e075760043567ffffffffffffffff8111610e0757613dbe602061047481933690600401614c7e565b8101601881520301902054604051908152f35b34610e07576020600319360112610e075760043567ffffffffffffffff8111610e0757613e08602061047481933690600401614c7e565b8101601781520301902054604051908152f35b34610e07575f600319360112610e0757613e33615313565b7fcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f033005460ff811615613ece577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00167fcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f03300557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa6020604051338152a1005b7f8dfc202b000000000000000000000000000000000000000000000000000000005f5260045ffd5b34610e07576040600319360112610e0757600435613f12614d19565b811580614089575b613f6d575b3373ffffffffffffffffffffffffffffffffffffffff821603613f455761001a91615aa7565b7f6697b232000000000000000000000000000000000000000000000000000000005f5260045ffd5b7feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d86984005465ffffffffffff60a082901c169073ffffffffffffffffffffffffffffffffffffffff1615801590614079575b8015614067575b61403357507fffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffff7feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d869840054167feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d869840055613f1f565b65ffffffffffff907f19ca5ebb000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b504265ffffffffffff82161015613fc3565b5065ffffffffffff811615613fbc565b5073ffffffffffffffffffffffffffffffffffffffff7feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d8698401541673ffffffffffffffffffffffffffffffffffffffff821614613f1a565b34610e07576040600319360112610e07576004356140fb614d19565b8115614142578161413d61152261001a945f527f02dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b626800602052600160405f20015490565b6151d0565b7f3fc3c27a000000000000000000000000000000000000000000000000000000005f5260045ffd5b34610e07576020600319360112610e07577f424b07caa75ce8e1c3985f334273f957db9ce138de114e48e50d8240d4d7300b602060043580600655604051908152a1005b34610e07576020600319360112610e075760206141f86004355f527f02dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b626800602052600160405f20015490565b604051908152f35b34610e07577f507273e640affcefbad497278a9b264a65c62c430dd92d24dd0d58595529539c61422f36614cc4565b908160405160208184516142468183858901614d5f565b8101601781520301902055613acb60405192839283614eaa565b34610e07575f600319360112610e0757602073ffffffffffffffffffffffffffffffffffffffff600a5416604051908152f35b34610e07576020600319360112610e07576004355f525f602052602060405f2054604051908152f35b34610e07575f600319360112610e07576142d46152ab565b7feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d8698401548060d01c8061433d575b7feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d8698401805473ffffffffffffffffffffffffffffffffffffffff169055005b4211156143d45779ffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffff00000000000000000000000000000000000000000000000000007feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d8698400549260301b169116177feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d8698400555b8080614300565b507f2b1fa2edafe6f7b9e97c1a9e0c3660e645beb2dcaa2d45bdbf9beaf5472e1ec55f80a16143cd565b34610e07577f882f47825d4043cd04a564cad4f524a7fe00a604ae024c23dbc8065b77668b4761442d36614cc4565b908160405160208184516144448183858901614d5f565b8101601481520301902055613acb60405192839283614eaa565b34610e07576040600319360112610e0757614477614cf6565b61447f614d19565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005460ff8160401c1690811561491f575b506148f7577ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0080547fffffffffffffffffffffffffffffffffffffffffffffff000000000000000000166801000000000000000217905573ffffffffffffffffffffffffffffffffffffffff8216158080156148d9575b6139d157614533615874565b61453b615874565b6148ad57614813614819927c015180000000000000000000000000000000000000000000000000000079ffffffffffffffffffffffffffffffffffffffffffffffffffff7feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d86984005416177feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d8698400556145ce8161505c565b507fe53b6cbb4204145187ea4c9b95f311e9ee4f2690cdb9b3a219f863f3a71e06c95f8181527f02dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b6268006020527fe3e0a451f4d1f165b1071ce13280fc8d1dfe94c2663176890cdb309635afb92180547f0f6ee822d2ee125e4ce6edbae6c10a76fa9fd4617e0399ab687226fa3344210091829055909290917fbd79b86ffe0ab8e8776151514217cd7cacd52c909f66475c3af44e129f0b00ff9080a47f97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b9295f8181527f02dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b6268006020527f448256db8f8fb95ee3eaaf89c1051414494e85cebb6057fcf996cc3d0ccfb45780547f0f6ee822d2ee125e4ce6edbae6c10a76fa9fd4617e0399ab687226fa3344210091829055909290917fbd79b86ffe0ab8e8776151514217cd7cacd52c909f66475c3af44e129f0b00ff9080a47f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a5f8181527f02dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b6268006020527f75442b0a96088b5456bc4ed01394c96a4feec0f883c9494257d76b96ab1c9b6c80547f0f6ee822d2ee125e4ce6edbae6c10a76fa9fd4617e0399ab687226fa3344210091829055909290917fbd79b86ffe0ab8e8776151514217cd7cacd52c909f66475c3af44e129f0b00ff9080a461480381615128565b5061480d81615152565b5061517c565b506151a6565b507fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054167ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00557fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2602060405160028152a1005b7fc22c8022000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b5073ffffffffffffffffffffffffffffffffffffffff821615614527565b7ff92ee8a9000000000000000000000000000000000000000000000000000000005f5260045ffd5b6002915067ffffffffffffffff161015836144b0565b34610e07577f2d57170c913282d2886a5ace7e18bed8b1c53a069f2698ae9e048bd501f3af3b61496436614cc4565b9081604051602081845161497b8183858901614d5f565b8101601881520301902055613acb60405192839283614eaa565b34610e07576040600319360112610e075760043573ffffffffffffffffffffffffffffffffffffffff8116809103610e075760243581156139d1578015614a7057478111614a48575f80808084865af16149ed614e7b565b5015614a205760207f8fcd857d6e51ec18860a6c21c1772d69a342074fbae5225c8f11f8cdf00a049691604051908152a2005b7f90b8ec18000000000000000000000000000000000000000000000000000000005f5260045ffd5b7ff4d678b8000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f1f2a2005000000000000000000000000000000000000000000000000000000005f5260045ffd5b34610e07575f600319360112610e07576020604051620697808152f35b34610e07576020600319360112610e07576004357fffffffff000000000000000000000000000000000000000000000000000000008116809103610e0757807f314987860000000000000000000000000000000000000000000000000000000060209214908115614b2c575b506040519015158152f35b7f7965db0b00000000000000000000000000000000000000000000000000000000811491508115614b5f575b5082614b21565b7f01ffc9a70000000000000000000000000000000000000000000000000000000091501482614b58565b34610e07575f600319360112610e075760209073ffffffffffffffffffffffffffffffffffffffff600854168152f35b610100810190811067ffffffffffffffff821117614bd657604052565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff821117614bd657604052565b67ffffffffffffffff8111614bd657601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b81601f82011215610e0757803590614c9582614c44565b92614ca36040519485614c03565b82845260208383010111610e0757815f926020809301838601378301015290565b6040600319820112610e07576004359067ffffffffffffffff8211610e0757614cef91600401614c7e565b9060243590565b6004359073ffffffffffffffffffffffffffffffffffffffff82168203610e0757565b6024359073ffffffffffffffffffffffffffffffffffffffff82168203610e0757565b6044359073ffffffffffffffffffffffffffffffffffffffff82168203610e0757565b5f5b838110614d705750505f910152565b8181015183820152602001614d61565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f602093614dbc81518092818752878088019101614d5f565b0116010190565b6024359062ffffff82168203610e0757565b6003196060910112610e075760043573ffffffffffffffffffffffffffffffffffffffff81168103610e0757906024359060443573ffffffffffffffffffffffffffffffffffffffff81168103610e075790565b34610e07575f600319360112610e0757602073ffffffffffffffffffffffffffffffffffffffff7feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d86984015416604051908152f35b3d15614ea5573d90614e8c82614c44565b91614e9a6040519384614c03565b82523d5f602084013e565b606090565b929190614ec1602091604086526040860190614d80565b930152565b818102929181159184041417156135dc57565b90816020910312610e0757518015158103610e075790565b90602091614f7293614f048184846155a1565b5f73ffffffffffffffffffffffffffffffffffffffff6040518097819682957f47e7ef24000000000000000000000000000000000000000000000000000000008452600484016020909392919373ffffffffffffffffffffffffffffffffffffffff60408201951681520152565b0393165af19081156134b0575f91614f8d575b501561351757565b614fa6915060203d602011610dc957610dbb8183614c03565b5f614f85565b90816020910312610e07575173ffffffffffffffffffffffffffffffffffffffff81168103610e075790565b7feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d8698401548060d01c8015159081615045575b501561501c5760a01c65ffffffffffff1690565b507feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d86984005460d01c90565b905042115f615008565b919082018092116135dc57565b73ffffffffffffffffffffffffffffffffffffffff7feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d86984015416614142578061511f6151259273ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffff00000000000000000000000000000000000000007feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d86984015416177feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d869840155565b5f6158cb565b90565b615125907f0f6ee822d2ee125e4ce6edbae6c10a76fa9fd4617e0399ab687226fa334421006158cb565b615125907fe53b6cbb4204145187ea4c9b95f311e9ee4f2690cdb9b3a219f863f3a71e06c96158cb565b615125907f97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b9296158cb565b615125907f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a6158cb565b9081156151e1575b615125916158cb565b73ffffffffffffffffffffffffffffffffffffffff7feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d8698401541661414257615125916152a48273ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffff00000000000000000000000000000000000000007feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d86984015416177feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d869840155565b91506151d8565b335f9081527fb7db2dd08fcb62d0c9e08c51941cae53c267786a0b75803fb7960902fc8ef97d602052604090205460ff16156152e357565b7fe2517d3f000000000000000000000000000000000000000000000000000000005f52336004525f60245260445ffd5b335f9081527f448256db8f8fb95ee3eaaf89c1051414494e85cebb6057fcf996cc3d0ccfb456602052604090205460ff161561534b57565b7fe2517d3f000000000000000000000000000000000000000000000000000000005f52336004527f97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b92960245260445ffd5b805f527f02dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b62680060205260405f2073ffffffffffffffffffffffffffffffffffffffff33165f5260205260ff60405f205416156153f25750565b7fe2517d3f000000000000000000000000000000000000000000000000000000005f523360045260245260445ffd5b60405181519060208181850193615439818387614d5f565b81016016815203019020549182156154b45761546391602091604051938492839251928391614d5f565b8101601281520301902054615478828261504f565b4211615482575050565b7f2056463c000000000000000000000000000000000000000000000000000000005f526004524260245260445260645ffd5b505050565b9065ffffffffffff8091169116019065ffffffffffff82116135dc57565b60ff7fcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f03300541661550257565b7fd93c0665000000000000000000000000000000000000000000000000000000005f5260045ffd5b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146155795760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b7f3ee5aeb5000000000000000000000000000000000000000000000000000000005f5260045ffd5b90919073ffffffffffffffffffffffffffffffffffffffff16156139d15773ffffffffffffffffffffffffffffffffffffffff1680156139d15773ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168114908115615650575b506156285715614a7057565b7f82d5d76a000000000000000000000000000000000000000000000000000000005f5260045ffd5b905030145f61561c565b6040519060205f73ffffffffffffffffffffffffffffffffffffffff828501957f095ea7b30000000000000000000000000000000000000000000000000000000087521694856024860152816044860152604485526156ba606486614c03565b84519082855af15f513d8261572e575b5050156156d657505050565b61572761572c93604051907f095ea7b300000000000000000000000000000000000000000000000000000000602083015260248201525f604482015260448152615721606482614c03565b82615bc2565b615bc2565b565b909150615758575073ffffffffffffffffffffffffffffffffffffffff81163b15155b5f806156ca565b600114615751565b6040517f095ea7b300000000000000000000000000000000000000000000000000000000602080830191825273ffffffffffffffffffffffffffffffffffffffff85166024840152604480840196909652948252929390925f906157c5606486614c03565b84519082855af15f513d82615842575b5050156157e157505050565b61572761572c9373ffffffffffffffffffffffffffffffffffffffff604051917f095ea7b30000000000000000000000000000000000000000000000000000000060208401521660248201525f604482015260448152615721606482614c03565b90915061586c575073ffffffffffffffffffffffffffffffffffffffff81163b15155b5f806157d5565b600114615865565b60ff7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005460401c16156158a357565b7fd7e6bcf8000000000000000000000000000000000000000000000000000000005f5260045ffd5b805f527f02dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b62680060205260405f2073ffffffffffffffffffffffffffffffffffffffff83165f5260205260ff60405f205416155f146159d757805f527f02dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b62680060205260405f2073ffffffffffffffffffffffffffffffffffffffff83165f5260205260405f2060017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082541617905573ffffffffffffffffffffffffffffffffffffffff339216907f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d5f80a4600190565b50505f90565b6151259073ffffffffffffffffffffffffffffffffffffffff7feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d8698401541673ffffffffffffffffffffffffffffffffffffffff821614615a3c575b5f615c49565b7fffffffffffffffffffffffff00000000000000000000000000000000000000007feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d869840154167feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d869840155615a36565b9061512591801580615b24575b15615c49577fffffffffffffffffffffffff00000000000000000000000000000000000000007feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d869840154167feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d869840155615c49565b5073ffffffffffffffffffffffffffffffffffffffff7feef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d8698401541673ffffffffffffffffffffffffffffffffffffffff831614615ab4565b65ffffffffffff8111615b925765ffffffffffff1690565b7f6dfcc650000000000000000000000000000000000000000000000000000000005f52603060045260245260445ffd5b905f602091828151910182855af1156134b0575f513d615c40575073ffffffffffffffffffffffffffffffffffffffff81163b155b615bfe5750565b73ffffffffffffffffffffffffffffffffffffffff907f5274afe7000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b60011415615bf7565b805f527f02dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b62680060205260405f2073ffffffffffffffffffffffffffffffffffffffff83165f5260205260ff60405f2054165f146159d757805f527f02dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b62680060205260405f2073ffffffffffffffffffffffffffffffffffffffff83165f5260205260405f207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00815416905573ffffffffffffffffffffffffffffffffffffffff339216907ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b5f80a460019056fea2646970667358221220448bc29af17350ab754979c778787c0fd9eafcddbd56cdda8cdfee5189d931b764736f6c634300081a0033" diff --git a/testnet/core/setup/setup_genesis_validator.sh b/testnet/core/setup/setup_genesis_validator.sh index 6d250e786..f800ed358 100755 --- a/testnet/core/setup/setup_genesis_validator.sh +++ b/testnet/core/setup/setup_genesis_validator.sh @@ -120,7 +120,7 @@ echo "🛠️ Updating genesis parameters..." # EVM update_test_genesis `printf '.app_state["evm"]["params"]["evm_denom"]="%s"' $DENOM` # This seems duplicated since chain config already has this - update_test_genesis '.app_state["evm"]["params"]["active_static_precompiles"]=["0x00000000000000000000000000000000000000CB","0x00000000000000000000000000000000000000ca","0x0000000000000000000000000000000000000100","0x0000000000000000000000000000000000000400","0x0000000000000000000000000000000000000800","0x0000000000000000000000000000000000000801","0x0000000000000000000000000000000000000802","0x0000000000000000000000000000000000000803","0x0000000000000000000000000000000000000804","0x0000000000000000000000000000000000000805"]' + update_test_genesis '.app_state["evm"]["params"]["active_static_precompiles"]=["0x0000000000000000000000000000000000000100","0x0000000000000000000000000000000000000400","0x0000000000000000000000000000000000000800","0x0000000000000000000000000000000000000801","0x0000000000000000000000000000000000000802","0x0000000000000000000000000000000000000803","0x0000000000000000000000000000000000000804","0x0000000000000000000000000000000000000805","0xEC00000000000000000000000000000000000001"]' update_test_genesis '.app_state["evm"]["params"]["chain_config"]["homestead_block"]="0"' update_test_genesis '.app_state["evm"]["params"]["chain_config"]["dao_fork_block"]="0"' update_test_genesis '.app_state["evm"]["params"]["chain_config"]["dao_fork_support"]=true' diff --git a/x/uexecutor/README.md b/x/uexecutor/README.md index c1d5a96a8..c94a2beba 100755 --- a/x/uexecutor/README.md +++ b/x/uexecutor/README.md @@ -222,7 +222,7 @@ Vote messages check `IsBondedUniversalValidator` and `IsTombstonedUniversalValid The cryptographic binding is enforced inside the UEA contract's `executeUniversalTx` (see [`UEA_EVM.sol`](https://github.com/pushchain/push-chain-core-contracts/blob/86e20e2d26819e7cc885549f08c66895221dfab0/src/uea/UEA_EVM.sol#L145) and [`UEA_SVM.sol`](https://github.com/pushchain/push-chain-core-contracts/blob/86e20e2d26819e7cc885549f08c66895221dfab0/src/uea/UEA_SVM.sol)): 1. The contract holds the owner's public key as **immutable bytes** set at UEA deployment via `initialize(_id, _factory)`. There is no code path that mutates this after init. -2. `executeUniversalTx(payload, signature)` verifies the `signature` (passed in as `MsgExecutePayload.VerificationData`) against this stored owner — ECDSA recovery for EVM-origin owners, the Ed25519 precompile (`0x00…00ca`) for SVM-origin owners. +2. `executeUniversalTx(payload, signature)` verifies the `signature` (passed in as `MsgExecutePayload.VerificationData`) against this stored owner — ECDSA recovery for EVM-origin owners, the Ed25519 precompile (`0xEC…01`) for SVM-origin owners. 3. The signed payload hash includes a contract-tracked `nonce` (monotonic per UEA) and optional `deadline`, providing replay and freshness protection. 4. If signature verification fails, the contract reverts. The revert propagates as `execErr` from `CallUEAExecutePayload`; the keeper returns the error from `ExecutePayload`; the entire Cosmos transaction (including any partial gas-fee deduction) rolls back atomically. **No state changes survive a failed signature check.** From 9b05ec76558c6b187697d13169407c3dc8007b6d Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Mon, 24 Aug 2026 08:52:04 +0530 Subject: [PATCH 18/60] fix: F-2026-18825 | [Dual Defense] One Invalid Gateway Sibling Erases Valid Outbounds After Committed UEA Burn (#323) * fix: attach payload outbounds inside ExecutePayloadV2's cache (F-2026-18825) The gateway burn was committed before the handlers attached the outbounds, so one invalid leg of a multicall erased the valid ones with their burns already final and the failure swallowed into RevertError. * test: cover UEA multicall outbound atomicity (F-2026-18825) --- ...bound_multicall_outbound_atomicity_test.go | 469 ++++++++++++++++++ x/uexecutor/keeper/create_outbound.go | 5 +- .../execute_inbound_funds_and_payload.go | 13 +- .../keeper/execute_inbound_gas_and_payload.go | 12 +- x/uexecutor/keeper/execute_payload.go | 28 +- 5 files changed, 500 insertions(+), 27 deletions(-) create mode 100644 test/integration/uexecutor/inbound_multicall_outbound_atomicity_test.go diff --git a/test/integration/uexecutor/inbound_multicall_outbound_atomicity_test.go b/test/integration/uexecutor/inbound_multicall_outbound_atomicity_test.go new file mode 100644 index 000000000..06c3ca9a5 --- /dev/null +++ b/test/integration/uexecutor/inbound_multicall_outbound_atomicity_test.go @@ -0,0 +1,469 @@ +package integrationtest + +import ( + "fmt" + "math/big" + "strings" + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + authz "github.com/cosmos/cosmos-sdk/x/authz" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/require" + + "github.com/pushchain/push-chain-node/app" + utils "github.com/pushchain/push-chain-node/test/utils" + uexecutorkeeper "github.com/pushchain/push-chain-node/x/uexecutor/keeper" + uexecutortypes "github.com/pushchain/push-chain-node/x/uexecutor/types" + uregistrytypes "github.com/pushchain/push-chain-node/x/uregistry/types" + uvalidatortypes "github.com/pushchain/push-chain-node/x/uvalidator/types" +) + +// F-2026-18825. A UEA payload that calls UniversalGatewayPC burns PRC20 and +// emits UniversalTxOutbound. ExecutePayloadV2 used to commit that burn via +// writeCache() and only then hand the receipt back, leaving the two inbound +// handlers to attach the outbounds afterwards, outside any cache. +// BuildOutboundsFromReceipt is all-or-nothing, so a multicall carrying one +// invalid leg (unregistered PRC20, disabled chain) discarded every valid +// outbound alongside it — while the burns for all of them stayed committed. +// The handlers then stashed the failure in UniversalTx.RevertError (9 writes / +// 0 reads chain-wide), marked the payload PcTx SUCCESS and returned nil, so +// nothing on chain recorded that anything had gone wrong. +// +// The attach now runs inside ExecutePayloadV2's existing CacheContext, before +// writeCache(): the burn and the OutboundTx / PendingOutbounds rows commit +// together or not at all, and the failure surfaces as a FAILED PcTx. +// +// The vote tx must still succeed either way — the handler runs inside +// MsgVoteInbound, and returning an error there would lose the validator's vote. +// That constraint is why the fix is atomicity rather than error propagation. + +// unregisteredPRC20 is a PRC20 address with no TokenConfig registered against +// it, which is what makes the sibling leg of the multicall invalid. +var unregisteredPRC20 = common.HexToAddress("0x0000000000000000000000000000000000000e0f") + +// gatewayNonceSlot is UniversalGatewayPC storage slot 2 (its outbound nonce). +// The mock gateway bumps it on every withdraw, so it doubles as a witness for +// whether the payload's EVM state was committed or rolled back. +var gatewayNonceSlot = common.BigToHash(big.NewInt(2)) + +// ueaMulticallSelector is bytes4(keccak256("UEA_MULTICALL")), the magic prefix +// UEA_EVM._isMulticall() looks for before decoding payload.data as Multicall[]. +func ueaMulticallSelector(t *testing.T) []byte { + t.Helper() + sel := crypto.Keccak256([]byte("UEA_MULTICALL"))[:4] + // Guards against the deployed UEA_EVM_BYTECODE drifting away from the + // selector this test builds payloads with. + require.Equal(t, "0x2cc2842d", hexutil.Encode(sel), "UEA multicall selector drifted") + return sel +} + +// multicallLeg mirrors the Solidity `Multicall { address to; uint256 value; +// bytes data; }` struct the UEA decodes out of a multicall payload. +type multicallLeg struct { + To common.Address + Value *big.Int + Data []byte +} + +// encodeUEAMulticall builds payload.data for a UEA multicall: the magic +// selector followed by an ABI-encoded Multicall[]. +func encodeUEAMulticall(t *testing.T, legs []multicallLeg) string { + t.Helper() + + tupleArray, err := abi.NewType("tuple[]", "", []abi.ArgumentMarshaling{ + {Name: "to", Type: "address"}, + {Name: "value", Type: "uint256"}, + {Name: "data", Type: "bytes"}, + }) + require.NoError(t, err) + + encoded, err := abi.Arguments{{Type: tupleArray}}.Pack(legs) + require.NoError(t, err) + + return hexutil.Encode(append(ueaMulticallSelector(t), encoded...)) +} + +// gatewayWithdrawCalldata is the UniversalGatewayPC withdraw call used across +// the outbound tests (see TestInboundInitiatedOutbound), with the burned PRC20 +// left as a parameter so a leg can be made invalid. Word 2 of the argument +// block is the token the gateway reports in its UniversalTxOutbound event; the +// happy-path assertions below pin that mapping down. +func gatewayWithdrawCalldataFor(t *testing.T, prc20 common.Address) []byte { + t.Helper() + + words := []string{ + "0000000000000000000000000000000000000000000000000000000000000020", + "00000000000000000000000000000000000000000000000000000000000000c0", + hexutil.Encode(common.LeftPadBytes(prc20.Bytes(), 32))[2:], // PRC20 to burn + "00000000000000000000000000000000000000000000000000000000000f4240", // amount: 1000000 + "000000000000000000000000000000000000000000000000000000000007a120", + "0000000000000000000000000000000000000000000000000000000000000100", + "0000000000000000000000001234567890abcdef1234567890abcdef12345678", + "0000000000000000000000000000000000000000000000000000000000000014", + "1234567890abcdef1234567890abcdef12345678000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + } + + data, err := hexutil.Decode("0xb3ca1fbc" + strings.Join(words, "")) + require.NoError(t, err) + return data +} + +// multicallToGateway builds a UEA multicall payload whose legs each burn one of +// the given PRC20s through UniversalGatewayPC. +func multicallToGateway(t *testing.T, prc20s ...common.Address) string { + t.Helper() + + gateway := utils.GetDefaultAddresses().UniversalGatewayPCAddr + legs := make([]multicallLeg, 0, len(prc20s)) + for _, prc20 := range prc20s { + legs = append(legs, multicallLeg{ + To: gateway, + Value: big.NewInt(0), + Data: gatewayWithdrawCalldataFor(t, prc20), + }) + } + + return encodeUEAMulticall(t, legs) +} + +// setupMulticallOutboundTest registers eip155:11155111 with outbound enabled, +// registers PRC20USDC against it, deploys the UEA for DefaultTestAddr and funds +// it with upc so gas-fee deduction never masks the behaviour under test. +func setupMulticallOutboundTest( + t *testing.T, + numVals int, +) (*app.ChainApp, sdk.Context, []string, []stakingtypes.Validator, common.Address) { + t.Helper() + + chainApp, ctx, _, validators := utils.SetAppWithMultipleValidators(t, numVals) + + testAddress := utils.GetDefaultAddresses().DefaultTestAddr + prc20Address := utils.GetDefaultAddresses().PRC20USDCAddr + usdcAddress := utils.GetDefaultAddresses().ExternalUSDCAddr + + chainApp.UregistryKeeper.AddChainConfig(ctx, &uregistrytypes.ChainConfig{ + Chain: "eip155:11155111", + VmType: uregistrytypes.VmType_EVM, + PublicRpcUrl: "https://sepolia.drpc.org", + GatewayAddress: "0x28E0F09bE2321c1420Dc60Ee146aACbD68B335Fe", + BlockConfirmation: &uregistrytypes.BlockConfirmation{ + FastInbound: 5, + StandardInbound: 12, + }, + GatewayMethods: []*uregistrytypes.GatewayMethods{{ + Name: "addFunds", + Identifier: "", + EventIdentifier: "0xb28f49668e7e76dc96d7aabe5b7f63fecfbd1c3574774c05e8204e749fd96fbd", + ConfirmationType: 5, + }}, + Enabled: &uregistrytypes.ChainEnabled{ + IsInboundEnabled: true, + IsOutboundEnabled: true, + }, + }) + + chainApp.UregistryKeeper.AddTokenConfig(ctx, &uregistrytypes.TokenConfig{ + Chain: "eip155:11155111", + Address: usdcAddress.String(), + Name: "USD Coin", + Symbol: "USDC", + Decimals: 6, + Enabled: true, + LiquidityCap: "1000000000000000000000000", + TokenType: 1, + NativeRepresentation: &uregistrytypes.NativeRepresentation{ + Denom: "", + ContractAddress: prc20Address.String(), + }, + }) + + universalVals := make([]string, len(validators)) + for i, val := range validators { + network := uvalidatortypes.NetworkInfo{PeerId: fmt.Sprintf("temp%d", i+1), MultiAddrs: []string{"temp"}} + require.NoError(t, chainApp.UvalidatorKeeper.AddUniversalValidator(ctx, val.OperatorAddress, network)) + universalVals[i] = sdk.AccAddress([]byte(fmt.Sprintf("universal-validator-%d", i))).String() + } + + for i, val := range validators { + accAddr, err := sdk.ValAddressFromBech32(val.OperatorAddress) + require.NoError(t, err) + + coreValAddr := sdk.AccAddress(accAddr) + uniValAddr := sdk.MustAccAddressFromBech32(universalVals[i]) + + auth := authz.NewGenericAuthorization(sdk.MsgTypeURL(&uexecutortypes.MsgVoteInbound{})) + exp := ctx.BlockTime().Add(time.Hour) + require.NoError(t, chainApp.AuthzKeeper.SaveGrant(ctx, uniValAddr, coreValAddr, auth, &exp)) + } + + ueModuleAccAddress, _ := chainApp.UexecutorKeeper.GetUeModuleAddress(ctx) + receipt, err := chainApp.UexecutorKeeper.DeployUEAV2(ctx, ueModuleAccAddress, &uexecutortypes.UniversalAccountId{ + ChainNamespace: "eip155", + ChainId: "11155111", + Owner: testAddress, + }) + require.NoError(t, err) + ueaAddr := common.BytesToAddress(receipt.Ret) + + fundCoins := sdk.NewCoins(sdk.NewInt64Coin("upc", 1_000_000_000)) + require.NoError(t, chainApp.BankKeeper.MintCoins(ctx, utils.MintModule, fundCoins)) + require.NoError(t, chainApp.BankKeeper.SendCoinsFromModuleToAccount( + ctx, utils.MintModule, sdk.AccAddress(ueaAddr.Bytes()), fundCoins)) + + return chainApp, ctx, universalVals, validators, ueaAddr +} + +// multicallInbound builds a non-CEA inbound whose payload is the given +// multicall. The UE module is the caller of executeUniversalTx, so UEA_EVM +// skips signature verification and VerificationData is irrelevant here. +func multicallInbound(txHash string, txType uexecutortypes.TxType, amount, payloadData string) *uexecutortypes.Inbound { + return &uexecutortypes.Inbound{ + SourceChain: "eip155:11155111", + TxHash: txHash, + Sender: utils.GetDefaultAddresses().DefaultTestAddr, + Recipient: "", + Amount: amount, + AssetAddr: utils.GetDefaultAddresses().ExternalUSDCAddr.String(), + LogIndex: "1", + TxType: txType, + UniversalPayload: &uexecutortypes.UniversalPayload{ + To: utils.GetDefaultAddresses().UniversalGatewayPCAddr.Hex(), + Value: "0", + Data: payloadData, + GasLimit: "21000000", + MaxFeePerGas: "1000000000", + MaxPriorityFeePerGas: "200000000", + Nonce: "0", + Deadline: "0", + VType: uexecutortypes.VerificationType(1), + }, + VerificationData: "", + } +} + +// payloadPcTx returns the payload PcTx, which is always the last one recorded. +func payloadPcTx(t *testing.T, utx uexecutortypes.UniversalTx) *uexecutortypes.PCTx { + t.Helper() + require.NotEmpty(t, utx.PcTx, "at least one PcTx must be recorded") + return utx.PcTx[len(utx.PcTx)-1] +} + +func requireNoPendingOutbounds(t *testing.T, ctx sdk.Context, chainApp *app.ChainApp, msg string) { + t.Helper() + querier := uexecutorkeeper.NewQuerier(chainApp.UexecutorKeeper) + resp, err := querier.AllPendingOutbounds(ctx, &uexecutortypes.QueryAllPendingOutboundsRequest{}) + require.NoError(t, err) + require.Empty(t, resp.Entries, msg) +} + +func TestInboundMulticallOutboundAtomicity(t *testing.T) { + prc20 := utils.GetDefaultAddresses().PRC20USDCAddr + gateway := utils.GetDefaultAddresses().UniversalGatewayPCAddr + + // --- the headline case ------------------------------------------------ + + t.Run("FUNDS_AND_PAYLOAD one invalid sibling rolls the whole payload back", func(t *testing.T) { + chainApp, ctx, vals, coreVals, ueaAddr := setupMulticallOutboundTest(t, 4) + + ueaAcc := sdk.AccAddress(ueaAddr.Bytes()) + upcBefore := chainApp.BankKeeper.GetBalance(ctx, ueaAcc, "upc") + + // One valid outbound and one unregistered-PRC20 sibling, in that order, + // so the valid one is already accumulated when the invalid one fails. + inbound := multicallInbound("0xmulticall-funds-01", uexecutortypes.TxType_FUNDS_AND_PAYLOAD, "1000000", + multicallToGateway(t, prc20, unregisteredPRC20)) + voteToQuorum(t, ctx, chainApp, vals, coreVals, inbound) + + utxKey := uexecutortypes.GetInboundUniversalTxKey(*inbound) + utx, found, err := chainApp.UexecutorKeeper.GetUniversalTx(ctx, utxKey) + require.NoError(t, err) + require.True(t, found) + + // Nothing the payload did survives — including the burn behind the + // valid leg, witnessed by the gateway's outbound nonce. + require.Equal(t, common.Hash{}, chainApp.EVMKeeper.GetState(ctx, gateway, gatewayNonceSlot), + "the gateway burn must roll back with the failed attach") + require.Empty(t, utx.OutboundTx, "a partially-valid multicall must not leave a partial OutboundTx") + requireNoPendingOutbounds(t, ctx, chainApp, "a partially-valid multicall must not leave a PendingOutbounds row") + require.Equal(t, upcBefore.Amount, chainApp.BankKeeper.GetBalance(ctx, ueaAcc, "upc").Amount, + "no gas fee may be collected for a payload that was discarded") + + // The failure is recorded, not swallowed. + pcTx := payloadPcTx(t, utx) + require.Equal(t, "FAILED", pcTx.Status, "the payload PcTx must not report SUCCESS") + require.Contains(t, pcTx.ErrorMsg, "outbound attach failed") + require.Contains(t, strings.ToLower(pcTx.ErrorMsg), strings.ToLower(unregisteredPRC20.Hex()), + "the PcTx must name the leg that could not be resolved") + require.Empty(t, utx.RevertError, "RevertError must no longer be used to swallow attach failures") + + // The deposit happens before the payload cache, so the bridged funds + // stay credited to the UEA and the user can simply retry. + require.Equal(t, "SUCCESS", utx.PcTx[0].Status, "the deposit stays committed") + require.Equal(t, "1000000", prc20BalanceOf(t, chainApp, ctx, ueaAddr).String(), + "the bridged principal must remain with the UEA") + }) + + t.Run("GAS_AND_PAYLOAD one invalid sibling rolls the whole payload back", func(t *testing.T) { + chainApp, ctx, vals, coreVals, ueaAddr := setupMulticallOutboundTest(t, 4) + + ueaAcc := sdk.AccAddress(ueaAddr.Bytes()) + upcBefore := chainApp.BankKeeper.GetBalance(ctx, ueaAcc, "upc") + + // Amount 0 skips gasAndPayloadDepositAutoSwap, which needs a live + // Uniswap quoter/router the integration harness does not deploy. The + // UEA payload branch under test is reached either way. + inbound := multicallInbound("0xmulticall-gas-01", uexecutortypes.TxType_GAS_AND_PAYLOAD, "0", + multicallToGateway(t, prc20, unregisteredPRC20)) + voteToQuorum(t, ctx, chainApp, vals, coreVals, inbound) + + utxKey := uexecutortypes.GetInboundUniversalTxKey(*inbound) + utx, found, err := chainApp.UexecutorKeeper.GetUniversalTx(ctx, utxKey) + require.NoError(t, err) + require.True(t, found) + + require.Equal(t, common.Hash{}, chainApp.EVMKeeper.GetState(ctx, gateway, gatewayNonceSlot), + "the gateway burn must roll back with the failed attach") + require.Empty(t, utx.OutboundTx, "a partially-valid multicall must not leave a partial OutboundTx") + requireNoPendingOutbounds(t, ctx, chainApp, "a partially-valid multicall must not leave a PendingOutbounds row") + require.Equal(t, upcBefore.Amount, chainApp.BankKeeper.GetBalance(ctx, ueaAcc, "upc").Amount, + "no gas fee may be collected for a payload that was discarded") + + pcTx := payloadPcTx(t, utx) + require.Equal(t, "FAILED", pcTx.Status, "the payload PcTx must not report SUCCESS") + require.Contains(t, pcTx.ErrorMsg, "outbound attach failed") + require.Contains(t, strings.ToLower(pcTx.ErrorMsg), strings.ToLower(unregisteredPRC20.Hex()), + "the PcTx must name the leg that could not be resolved") + require.Empty(t, utx.RevertError, "RevertError must no longer be used to swallow attach failures") + }) + + // --- happy path: every leg valid -------------------------------------- + + t.Run("FUNDS_AND_PAYLOAD all-valid multicall attaches every outbound", func(t *testing.T) { + chainApp, ctx, vals, coreVals, _ := setupMulticallOutboundTest(t, 4) + + inbound := multicallInbound("0xmulticall-funds-02", uexecutortypes.TxType_FUNDS_AND_PAYLOAD, "1000000", + multicallToGateway(t, prc20, prc20)) + voteToQuorum(t, ctx, chainApp, vals, coreVals, inbound) + + utxKey := uexecutortypes.GetInboundUniversalTxKey(*inbound) + utx, found, err := chainApp.UexecutorKeeper.GetUniversalTx(ctx, utxKey) + require.NoError(t, err) + require.True(t, found) + + pcTx := payloadPcTx(t, utx) + require.Equal(t, "SUCCESS", pcTx.Status, "payload should succeed: %s", pcTx.ErrorMsg) + + require.Equal(t, common.BigToHash(big.NewInt(2)), chainApp.EVMKeeper.GetState(ctx, gateway, gatewayNonceSlot), + "both gateway burns must be committed") + require.Len(t, utx.OutboundTx, 2, "each valid leg must produce an OutboundTx") + + seen := map[string]bool{} + for _, out := range utx.OutboundTx { + require.Equal(t, "eip155:11155111", out.DestinationChain) + require.Equal(t, common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"), common.HexToAddress(out.Recipient)) + require.Equal(t, "1000000", out.Amount) + require.Equal(t, prc20, common.HexToAddress(out.Prc20AssetAddr)) + require.Equal(t, utils.GetDefaultAddresses().ExternalUSDCAddr, common.HexToAddress(out.ExternalAssetAddr)) + require.Equal(t, uexecutortypes.Status_PENDING, out.OutboundStatus) + + require.False(t, seen[out.Id], "each leg must get its own outbound id") + seen[out.Id] = true + + entry, err := chainApp.UexecutorKeeper.PendingOutbounds.Get(ctx, out.Id) + require.NoError(t, err, "every outbound must be indexed in PendingOutbounds") + require.Equal(t, utxKey, entry.UniversalTxId) + } + require.Empty(t, utx.RevertError) + }) + + t.Run("GAS_AND_PAYLOAD all-valid multicall attaches every outbound", func(t *testing.T) { + chainApp, ctx, vals, coreVals, _ := setupMulticallOutboundTest(t, 4) + + inbound := multicallInbound("0xmulticall-gas-02", uexecutortypes.TxType_GAS_AND_PAYLOAD, "0", + multicallToGateway(t, prc20, prc20)) + voteToQuorum(t, ctx, chainApp, vals, coreVals, inbound) + + utxKey := uexecutortypes.GetInboundUniversalTxKey(*inbound) + utx, found, err := chainApp.UexecutorKeeper.GetUniversalTx(ctx, utxKey) + require.NoError(t, err) + require.True(t, found) + + pcTx := payloadPcTx(t, utx) + require.Equal(t, "SUCCESS", pcTx.Status, "payload should succeed: %s", pcTx.ErrorMsg) + + require.Equal(t, common.BigToHash(big.NewInt(2)), chainApp.EVMKeeper.GetState(ctx, gateway, gatewayNonceSlot), + "both gateway burns must be committed") + require.Len(t, utx.OutboundTx, 2, "each valid leg must produce an OutboundTx") + + for _, out := range utx.OutboundTx { + require.Equal(t, uexecutortypes.Status_PENDING, out.OutboundStatus) + entry, err := chainApp.UexecutorKeeper.PendingOutbounds.Get(ctx, out.Id) + require.NoError(t, err, "every outbound must be indexed in PendingOutbounds") + require.Equal(t, utxKey, entry.UniversalTxId) + } + require.Empty(t, utx.RevertError) + }) + + // --- regression: payloads that emit no gateway outbound ---------------- + + t.Run("payload without a gateway call still succeeds with no outbound rows", func(t *testing.T) { + chainApp, ctx, vals, coreVals, ueaAddr := setupMulticallOutboundTest(t, 4) + + // A plain PRC20 transfer from the UEA: real EVM work, zero gateway logs. + inbound := multicallInbound("0xmulticall-noop-01", uexecutortypes.TxType_FUNDS_AND_PAYLOAD, "1000000", + "0xa9059cbb000000000000000000000000527f3692f5c53cfa83f7689885995606f93b616400000000000000000000000000000000000000000000000000000000000f4240") + inbound.UniversalPayload.To = utils.GetDefaultAddresses().PRC20USDCAddr.Hex() + voteToQuorum(t, ctx, chainApp, vals, coreVals, inbound) + + utxKey := uexecutortypes.GetInboundUniversalTxKey(*inbound) + utx, found, err := chainApp.UexecutorKeeper.GetUniversalTx(ctx, utxKey) + require.NoError(t, err) + require.True(t, found) + + pcTx := payloadPcTx(t, utx) + require.Equal(t, "SUCCESS", pcTx.Status, "payload should succeed: %s", pcTx.ErrorMsg) + require.Empty(t, pcTx.ErrorMsg) + + require.Empty(t, utx.OutboundTx, "a payload that emits no gateway event must not gain an outbound") + requireNoPendingOutbounds(t, ctx, chainApp, "no spurious PendingOutbounds row") + require.Empty(t, utx.RevertError) + + // The transfer itself committed, so the cache was written. + require.Equal(t, "0", prc20BalanceOf(t, chainApp, ctx, ueaAddr).String(), + "the payload's PRC20 transfer must still be committed") + }) +} + +// prc20BalanceOf reads PRC20USDC.balanceOf(holder). +func prc20BalanceOf(t *testing.T, chainApp *app.ChainApp, ctx sdk.Context, holder common.Address) *big.Int { + t.Helper() + + prc20ABI, err := uexecutortypes.ParsePRC20ABI() + require.NoError(t, err) + + ueModuleAccAddress, _ := chainApp.UexecutorKeeper.GetUeModuleAddress(ctx) + res, err := chainApp.EVMKeeper.CallEVM( + ctx, + prc20ABI, + ueModuleAccAddress, + utils.GetDefaultAddresses().PRC20USDCAddr, + false, + nil, + "balanceOf", + holder, + ) + require.NoError(t, err) + + values, err := prc20ABI.Unpack("balanceOf", res.Ret) + require.NoError(t, err) + require.Len(t, values, 1) + + return values[0].(*big.Int) +} diff --git a/x/uexecutor/keeper/create_outbound.go b/x/uexecutor/keeper/create_outbound.go index 3391c7c29..43e996941 100644 --- a/x/uexecutor/keeper/create_outbound.go +++ b/x/uexecutor/keeper/create_outbound.go @@ -63,7 +63,10 @@ func (k Keeper) BuildOutboundsFromReceipt( event.Token, // PRC20 address ) if err != nil { - return nil, err + // Wrapped so the caller can surface an actionable reason: the bare + // collections.ErrNotFound ("not found") says nothing about which leg + // of a multicall failed. + return nil, fmt.Errorf("no token config for PRC20 %s on chain %s: %w", event.Token, event.ChainId, err) } outbound := &types.OutboundTx{ diff --git a/x/uexecutor/keeper/execute_inbound_funds_and_payload.go b/x/uexecutor/keeper/execute_inbound_funds_and_payload.go index 6ac8f24c7..09f06fce6 100644 --- a/x/uexecutor/keeper/execute_inbound_funds_and_payload.go +++ b/x/uexecutor/keeper/execute_inbound_funds_and_payload.go @@ -304,7 +304,7 @@ func (k Keeper) ExecuteInboundFundsAndPayload(ctx context.Context, utx types.Uni // --- Step 3: execute payload via UEA k.Logger().Debug("executing payload via UEA", "utx_key", universalTxKey, "uea", ueaAddr.Hex()) var payloadErr error - receipt, payloadErr = k.ExecutePayloadV2(ctx, ueModuleAddr, ueaAddr, utx.InboundTx.UniversalPayload, utx.InboundTx.VerificationData) + receipt, payloadErr = k.ExecutePayloadV2(ctx, ueModuleAddr, ueaAddr, utx.InboundTx.UniversalPayload, utx.InboundTx.VerificationData, utx) payloadPcTx := types.PCTx{ Sender: ueModuleAddressStr, @@ -330,16 +330,9 @@ func (k Keeper) ExecuteInboundFundsAndPayload(ctx context.Context, utx types.Uni "tx_hash", receipt.Hash, "gas_used", receipt.GasUsed, ) + // Outbounds are attached inside ExecutePayloadV2, atomically with the + // payload execution: reaching here means they are already committed. payloadPcTx.Status = "SUCCESS" - - if attachErr := k.AttachOutboundsToExistingUniversalTx(sdkCtx, receipt, utx); attachErr != nil { - if storeErr := k.UpdateUniversalTx(sdkCtx, universalTxKey, func(u *types.UniversalTx) error { - u.RevertError = attachErr.Error() - return nil - }); storeErr != nil { - return storeErr - } - } } updateErr2 := k.UpdateUniversalTx(ctx, universalTxKey, func(utx *types.UniversalTx) error { diff --git a/x/uexecutor/keeper/execute_inbound_gas_and_payload.go b/x/uexecutor/keeper/execute_inbound_gas_and_payload.go index db6a32f7e..34f244792 100644 --- a/x/uexecutor/keeper/execute_inbound_gas_and_payload.go +++ b/x/uexecutor/keeper/execute_inbound_gas_and_payload.go @@ -309,6 +309,7 @@ func (k Keeper) ExecuteInboundGasAndPayload(ctx context.Context, utx types.Unive ueaAddr, utx.InboundTx.UniversalPayload, utx.InboundTx.VerificationData, + utx, ) payloadPcTx := types.PCTx{ @@ -335,16 +336,9 @@ func (k Keeper) ExecuteInboundGasAndPayload(ctx context.Context, utx types.Unive "tx_hash", receipt.Hash, "gas_used", receipt.GasUsed, ) + // Outbounds are attached inside ExecutePayloadV2, atomically with the + // payload execution: reaching here means they are already committed. payloadPcTx.Status = "SUCCESS" - - if attachErr := k.AttachOutboundsToExistingUniversalTx(sdkCtx, receipt, utx); attachErr != nil { - if storeErr := k.UpdateUniversalTx(sdkCtx, universalTxKey, func(u *types.UniversalTx) error { - u.RevertError = attachErr.Error() - return nil - }); storeErr != nil { - return storeErr - } - } } updateErr := k.UpdateUniversalTx(ctx, universalTxKey, func(utx *types.UniversalTx) error { diff --git a/x/uexecutor/keeper/execute_payload.go b/x/uexecutor/keeper/execute_payload.go index 58e81adfa..c95c440fb 100644 --- a/x/uexecutor/keeper/execute_payload.go +++ b/x/uexecutor/keeper/execute_payload.go @@ -12,9 +12,10 @@ import ( "github.com/pushchain/push-chain-node/x/uexecutor/types" ) -// ExecutePayloadV2 executes a universal payload through a UEA. +// ExecutePayloadV2 executes a universal payload through a UEA and attaches the +// gateway outbounds it emitted to utx, atomically with the execution itself. // The caller is responsible for resolving and validating ueaAddr before calling this function. -func (k Keeper) ExecutePayloadV2(ctx context.Context, evmFrom common.Address, ueaAddr common.Address, universalPayload *types.UniversalPayload, verificationData string) (*vmtypes.MsgEthereumTxResponse, error) { +func (k Keeper) ExecutePayloadV2(ctx context.Context, evmFrom common.Address, ueaAddr common.Address, universalPayload *types.UniversalPayload, verificationData string, utx types.UniversalTx) (*vmtypes.MsgEthereumTxResponse, error) { sdkCtx := sdk.UnwrapSDKContext(ctx) k.Logger().Debug("execute payload v2", @@ -32,10 +33,10 @@ func (k Keeper) ExecutePayloadV2(ctx context.Context, evmFrom common.Address, ue return nil, errors.Wrapf(err, "invalid verificationData format") } - // Step 2: Wrap EVM execution + fee deduction in a CacheContext so they - // commit/revert together. If fee deduction fails, the EVM state changes - // from CallUEAExecutePayload are discarded — closes the free-execution - // gap when the UEA has no native UPC to cover gas. + // Step 2: Wrap EVM execution + fee deduction + outbound attach in a + // CacheContext so they commit/revert together. If fee deduction fails, the + // EVM state changes from CallUEAExecutePayload are discarded — closes the + // free-execution gap when the UEA has no native UPC to cover gas. cacheCtx, writeCache := sdkCtx.CacheContext() receipt, execErr := k.CallUEAExecutePayload(cacheCtx, evmFrom, ueaAddr, universalPayload, verificationDataVal) @@ -52,7 +53,20 @@ func (k Keeper) ExecutePayloadV2(ctx context.Context, evmFrom common.Address, ue return receipt, execErr } - // Both succeeded — commit EVM state and fee deduction together. + // Step 4: Attach the outbounds the payload emitted, still inside the cache. + // A payload that calls UniversalGatewayPC has already burned the PRC20 by + // the time we get here, and BuildOutboundsFromReceipt is all-or-nothing: + // one invalid leg of a multicall (unregistered PRC20, disabled chain) + // discards every valid outbound alongside it. Attaching here means that + // failure also discards the burn, instead of leaving burned supply with no + // OutboundTx and no PendingOutbounds row to deliver against. + if receipt != nil { + if attachErr := k.AttachOutboundsToExistingUniversalTx(cacheCtx, receipt, utx); attachErr != nil { + return receipt, fmt.Errorf("outbound attach failed: %w", attachErr) + } + } + + // All succeeded — commit EVM state, fee deduction and outbounds together. writeCache() k.Logger().Debug("payload executed via UEA", From 8ad67288a022ad26bf119b8c1a57cde612115002 Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Mon, 24 Aug 2026 09:10:22 +0530 Subject: [PATCH 19/60] fix: F-2026-18194 | [Dual Defense] Unbacked Gas PRC20 Mint on INBOUND_REVERT via applyGasRefund (#321) --- test/integration/uexecutor/gas_refund_test.go | 40 +++++++++++++++++++ x/uexecutor/keeper/outbound.go | 8 ++++ 2 files changed, 48 insertions(+) create mode 100644 test/integration/uexecutor/gas_refund_test.go diff --git a/test/integration/uexecutor/gas_refund_test.go b/test/integration/uexecutor/gas_refund_test.go new file mode 100644 index 000000000..a5d238f2b --- /dev/null +++ b/test/integration/uexecutor/gas_refund_test.go @@ -0,0 +1,40 @@ +package integrationtest + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + utils "github.com/pushchain/push-chain-node/test/utils" + uexecutortypes "github.com/pushchain/push-chain-node/x/uexecutor/types" +) + +// TestInboundRevertGasNotRefunded proves an INBOUND_REVERT never refunds gas on +// settlement. A revert is protocol-initiated — the user was never charged a gas fee +// for it — so even when a GasFee budget is present (PRC20 reverts set one) and the +// observed gasFeeUsed is well below it, no refund must be attempted. +func TestInboundRevertGasNotRefunded(t *testing.T) { + chainApp, ctx, vals, utxId, ob, coreVals := setupOutboundVotingTest(t, 4) + + // Make the seeded outbound an INBOUND_REVERT carrying a gas budget with headroom + // that would otherwise trigger a refund (GasFee 1000, gasFeeUsed 100 below). + ob.TxType = uexecutortypes.TxType_INBOUND_REVERT + ob.GasFee = "1000" + ob.GasToken = "0x000000000000000000000000000000000000C0dE" + require.NoError(t, chainApp.UexecutorKeeper.UpdateOutbound(ctx, utxId, *ob)) + + // Settle it successfully with gasFeeUsed << GasFee. + for i := 0; i < 3; i++ { + valAddr, err := sdk.ValAddressFromBech32(coreVals[i].OperatorAddress) + require.NoError(t, err) + require.NoError(t, utils.ExecVoteOutbound( + t, ctx, chainApp, vals[i], sdk.AccAddress(valAddr).String(), utxId, ob, true, "", "100")) + } + + utx, found, err := chainApp.UexecutorKeeper.GetUniversalTx(ctx, utxId) + require.NoError(t, err) + require.True(t, found) + require.Nil(t, utx.OutboundTx[0].PcRefundExecution, + "INBOUND_REVERT must not attempt a gas refund — the user was never charged for it") +} diff --git a/x/uexecutor/keeper/outbound.go b/x/uexecutor/keeper/outbound.go index 6f8546a5d..990997c2b 100644 --- a/x/uexecutor/keeper/outbound.go +++ b/x/uexecutor/keeper/outbound.go @@ -176,6 +176,14 @@ func (k Keeper) handleSuccessfulOutbound(ctx sdk.Context, utxId string, outbound // It is called for both successful and failed outbounds — gas is consumed on the // external chain regardless of execution outcome. func (k Keeper) applyGasRefund(ctx sdk.Context, outbound *types.OutboundTx, obs *types.OutboundObservation) { + // INBOUND_REVERT is protocol-initiated: the user was never charged a gas fee for + // the revert, so its GasFee (when present) is only a relayer gas hint, not a + // user-paid budget. Refunding "excess" would hand the user funds they never paid, + // so never refund for a revert — regardless of PC20/PRC20 or whether GasFee is set. + if outbound.TxType == types.TxType_INBOUND_REVERT { + return + } + if obs.GasFeeUsed == "" || outbound.GasFee == "" || outbound.GasToken == "" { return } From f119cd27fb45a808573a182f0567e1c288149c5e Mon Sep 17 00:00:00 2001 From: Aman Gupta Date: Mon, 24 Aug 2026 13:58:12 +0530 Subject: [PATCH 20/60] fix: F-2026-18799 | [Dual Defense] SIGN_FUND_MIGRATE Selects Current Sign Threshold, Not TssKeyHistory Shareholders (#326) * fix(tss): select fund migration signers from the old key's shareholders * fix(tss): validate fund migration participants against the old key too * revert unrelated gofmt changes * refactor(tss): take the threshold as a parameter instead of a second helper * docs(tss): note the departure budget on an old key --- .../tss/coordinator/coordinator.go | 124 +++++++- .../tss/coordinator/coordinator_test.go | 19 +- .../fund_migrate_participants_test.go | 276 ++++++++++++++++++ universalClient/tss/coordinator/utils.go | 21 +- .../sessionmanager/fund_migrate_e2e_test.go | 263 +++++++++++++++++ .../tss/sessionmanager/sessionmanager.go | 37 ++- .../tss/sessionmanager/sessionmanager_test.go | 52 +++- 7 files changed, 753 insertions(+), 39 deletions(-) create mode 100644 universalClient/tss/coordinator/fund_migrate_participants_test.go create mode 100644 universalClient/tss/sessionmanager/fund_migrate_e2e_test.go diff --git a/universalClient/tss/coordinator/coordinator.go b/universalClient/tss/coordinator/coordinator.go index 55627b11d..ed72c6e9e 100644 --- a/universalClient/tss/coordinator/coordinator.go +++ b/universalClient/tss/coordinator/coordinator.go @@ -33,6 +33,7 @@ import ( type PushCoreClient interface { GetLatestBlock(ctx context.Context) (uint64, error) GetCurrentKey(ctx context.Context) (*utsstypes.TssKey, error) + GetKeyByID(ctx context.Context, keyID string) (*utsstypes.TssKey, error) GetAllUniversalValidators(ctx context.Context) ([]*types.UniversalValidator, error) } @@ -472,11 +473,13 @@ func (c *Coordinator) processConfirmedEvents(ctx context.Context) error { // For SIGN/FUND_MIGRATE: pick a random threshold subset (>2/3 of eligible) rather than all eligible. // A threshold subset suffices for signing and is more resilient when some nodes are offline. // For all other protocols (keygen, keyrefresh, quorum_change), all eligible must participate. - var participants []*types.UniversalValidator - if event.Type == store.EventTypeSignOutbound || event.Type == store.EventTypeSignFundMigrate { - participants = getSignParticipants(allValidators) - } else { - participants = getEligibleForProtocol(event.Type, allValidators) + participants, err := c.SelectParticipants(ctx, event, allValidators) + if err != nil { + c.logger.Error().Err(err). + Str("event_id", event.EventID). + Str("type", event.Type). + Msg("cannot select participants for event") + continue } if participants == nil { c.logger.Debug().Str("event_id", event.EventID).Str("type", event.Type).Msg("unknown protocol type") @@ -976,7 +979,7 @@ func getSignParticipants(allValidators []*types.UniversalValidator) []*types.Uni eligible := getSignEligible(allValidators) // Use utils function to select random threshold subset - return selectRandomThreshold(eligible) + return selectRandomThreshold(eligible, CalculateThreshold(len(eligible))) } // getInFlightSignCountPerChain returns per-chain in-flight SIGN count. @@ -1156,3 +1159,112 @@ func (c *Coordinator) assignFundMigrateNonce(ctx context.Context, event store.Ev return builder.GetNextNonce(ctx, oldTSSAddr, true) } + +// SelectParticipants picks who takes part in an event. +// +// For SIGN a random threshold subset (>2/3 of eligible) suffices and is more +// resilient when some nodes are offline. For all other protocols (keygen, +// keyrefresh, quorum change) every eligible validator must participate. +func (c *Coordinator) SelectParticipants( + ctx context.Context, + event store.Event, + allValidators []*types.UniversalValidator, +) ([]*types.UniversalValidator, error) { + switch event.Type { + case store.EventTypeSignOutbound: + return getSignParticipants(allValidators), nil + case store.EventTypeSignFundMigrate: + // Signed with the old key's shares, so the signers must be drawn from + // the validators that hold them rather than from whoever is eligible + // now. A newcomer selected here has no such share and never ACKs, so + // the session stalls waiting for a party that cannot take part. + return c.fundMigrateParticipants(ctx, event, allValidators) + default: + return getEligibleForProtocol(event.Type, allValidators), nil + } +} + +// FundMigrateEligible returns the validators that may sign a fund migration, +// and how many of them are required. +// +// Used by the coordinator to select signers and by every participant to +// validate the selection it receives. Both derive the answer from the same +// chain state, so a set the coordinator can legitimately pick is a set the +// participants accept. +func (c *Coordinator) FundMigrateEligible( + ctx context.Context, + event store.Event, +) ([]*types.UniversalValidator, int, error) { + return c.fundMigrateEligible(ctx, event, c.validatorsSnapshot()) +} + +// fundMigrateParticipants selects signers for a fund migration from the +// validators that hold the old key's shares. +func (c *Coordinator) fundMigrateParticipants( + ctx context.Context, + event store.Event, + allValidators []*types.UniversalValidator, +) ([]*types.UniversalValidator, error) { + holders, required, err := c.fundMigrateEligible(ctx, event, allValidators) + if err != nil { + return nil, err + } + return selectRandomThreshold(holders, required), nil +} + +// fundMigrateEligible resolves the eligible signers and the required count for +// a fund migration. +// +// The signature is produced with the old keyshare, so eligibility is decided by +// the historical shareholder set recorded on chain, not by who is a validator +// today. The required count is the old key's threshold for the same reason: it +// is the quorum that key was created under. +// +// Fails rather than returning a set that is already too small. Too few +// surviving shareholders means no subset can sign, and proceeding anyway would +// stall the session on an ACK that is never coming instead of reporting why. +// +// Nothing here can rebuild a lost quorum: an old key of N tolerates only +// N-threshold(N) departures, so migration must follow keygen promptly. +func (c *Coordinator) fundMigrateEligible( + ctx context.Context, + event store.Event, + allValidators []*types.UniversalValidator, +) ([]*types.UniversalValidator, int, error) { + var migrationData utsstypes.FundMigrationInitiatedEventData + if err := json.Unmarshal(event.EventData, &migrationData); err != nil { + return nil, 0, fmt.Errorf("parse fund migration data: %w", err) + } + if migrationData.OldKeyID == "" { + return nil, 0, fmt.Errorf("fund migration event carries no old key id") + } + + oldKey, err := c.pushCore.GetKeyByID(ctx, migrationData.OldKeyID) + if err != nil { + return nil, 0, fmt.Errorf("fetch old key %s: %w", migrationData.OldKeyID, err) + } + if oldKey == nil || len(oldKey.Participants) == 0 { + return nil, 0, fmt.Errorf("old key %s records no participants", migrationData.OldKeyID) + } + + shareholders := make(map[string]bool, len(oldKey.Participants)) + for _, p := range oldKey.Participants { + shareholders[p] = true + } + + var holders []*types.UniversalValidator + for _, v := range getSignEligible(allValidators) { + if v.IdentifyInfo != nil && shareholders[v.IdentifyInfo.CoreValidatorAddress] { + holders = append(holders, v) + } + } + + required := CalculateThreshold(len(oldKey.Participants)) + if len(holders) < required { + return nil, 0, fmt.Errorf( + "key %s needs %d of its %d shareholders to sign, only %d are still eligible", + migrationData.OldKeyID, required, len(oldKey.Participants), len(holders)) + } + + return holders, required, nil +} diff --git a/universalClient/tss/coordinator/coordinator_test.go b/universalClient/tss/coordinator/coordinator_test.go index 72daca8ec..df8ce9aa7 100644 --- a/universalClient/tss/coordinator/coordinator_test.go +++ b/universalClient/tss/coordinator/coordinator_test.go @@ -379,21 +379,21 @@ func TestSelectRandomThreshold(t *testing.T) { t.Run("returns exactly threshold count", func(t *testing.T) { // threshold(5) = 4 - assert.Len(t, selectRandomThreshold(makeN(5)), 4) + assert.Len(t, selectRandomThreshold(makeN(5), CalculateThreshold(5)), 4) }) t.Run("returns all when count equals threshold", func(t *testing.T) { // threshold(2) = 2 → returns all 2 - assert.Len(t, selectRandomThreshold(makeN(2)), 2) + assert.Len(t, selectRandomThreshold(makeN(2), CalculateThreshold(2)), 2) }) t.Run("returns all when count is below threshold", func(t *testing.T) { // threshold(1) = 1 → returns all 1 - assert.Len(t, selectRandomThreshold(makeN(1)), 1) + assert.Len(t, selectRandomThreshold(makeN(1), CalculateThreshold(1)), 1) }) t.Run("returns nil for empty list", func(t *testing.T) { - assert.Nil(t, selectRandomThreshold(nil)) + assert.Nil(t, selectRandomThreshold(nil, 3)) }) } @@ -1123,6 +1123,10 @@ type stalenessMockPushCore struct { block uint64 validators []*types.UniversalValidator failGetAll bool + + // Old key history, consulted when selecting fund migration signers. + keysByID map[string]*utsstypes.TssKey + keyErr error } func (m *stalenessMockPushCore) GetLatestBlock(_ context.Context) (uint64, error) { @@ -1133,6 +1137,13 @@ func (m *stalenessMockPushCore) GetCurrentKey(_ context.Context) (*utsstypes.Tss return &utsstypes.TssKey{KeyId: "test-key"}, nil } +func (m *stalenessMockPushCore) GetKeyByID(_ context.Context, keyID string) (*utsstypes.TssKey, error) { + if m.keyErr != nil { + return nil, m.keyErr + } + return m.keysByID[keyID], nil +} + func (m *stalenessMockPushCore) GetAllUniversalValidators(_ context.Context) ([]*types.UniversalValidator, error) { if m.failGetAll { return nil, fmt.Errorf("simulated GetAllUniversalValidators RPC failure") diff --git a/universalClient/tss/coordinator/fund_migrate_participants_test.go b/universalClient/tss/coordinator/fund_migrate_participants_test.go new file mode 100644 index 000000000..8bd919616 --- /dev/null +++ b/universalClient/tss/coordinator/fund_migrate_participants_test.go @@ -0,0 +1,276 @@ +package coordinator + +import ( + "context" + "encoding/json" + "fmt" + "testing" + + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/pushchain/push-chain-node/universalClient/store" + utsstypes "github.com/pushchain/push-chain-node/x/utss/types" + "github.com/pushchain/push-chain-node/x/uvalidator/types" +) + +func activeValidator(addr string) *types.UniversalValidator { + return &types.UniversalValidator{ + IdentifyInfo: &types.IdentityInfo{CoreValidatorAddress: addr}, + LifecycleInfo: &types.LifecycleInfo{CurrentStatus: types.UVStatus_UV_STATUS_ACTIVE}, + } +} + +func validatorWithStatus(addr string, status types.UVStatus) *types.UniversalValidator { + return &types.UniversalValidator{ + IdentifyInfo: &types.IdentityInfo{CoreValidatorAddress: addr}, + LifecycleInfo: &types.LifecycleInfo{CurrentStatus: status}, + } +} + +func validatorSet(addrs ...string) []*types.UniversalValidator { + set := make([]*types.UniversalValidator, 0, len(addrs)) + for _, a := range addrs { + set = append(set, activeValidator(a)) + } + return set +} + +func addressesOf(vs []*types.UniversalValidator) []string { + addrs := make([]string, 0, len(vs)) + for _, v := range vs { + addrs = append(addrs, v.IdentifyInfo.CoreValidatorAddress) + } + return addrs +} + +func fundMigrateEvent(t *testing.T, oldKeyID string) store.Event { + t.Helper() + data, err := json.Marshal(utsstypes.FundMigrationInitiatedEventData{OldKeyID: oldKeyID}) + require.NoError(t, err) + return store.Event{ + EventID: "fm-1", + Type: store.EventTypeSignFundMigrate, + EventData: data, + } +} + +func coordinatorWithKeys(keys map[string]*utsstypes.TssKey) *Coordinator { + return &Coordinator{ + pushCore: &stalenessMockPushCore{keysByID: keys}, + logger: zerolog.Nop(), + } +} + +// The finding's scenario: the old key has three shareholders, the validator set +// has since grown to ten. Selecting from the current set draws newcomers who +// hold no share of that key. +func TestFundMigrateParticipants_DrawsOnlyFromOldKeyShareholders(t *testing.T) { + keys := map[string]*utsstypes.TssKey{ + "old-key": {KeyId: "old-key", Participants: []string{"v1", "v2", "v3"}}, + } + c := coordinatorWithKeys(keys) + + all := validatorSet("v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8", "v9", "v10") + + // Selection is randomised, so repeat to catch a newcomer slipping in. + for i := 0; i < 200; i++ { + got, err := c.fundMigrateParticipants(context.Background(), fundMigrateEvent(t, "old-key"), all) + require.NoError(t, err) + + // Old key threshold is 3 of 3, not 7 of 10. + require.Len(t, got, 3) + assert.ElementsMatch(t, []string{"v1", "v2", "v3"}, addressesOf(got)) + } +} + +// A subset of shareholders large enough to sign, alongside a much larger +// current set. Every signer must still be a shareholder. +func TestFundMigrateParticipants_UsesOldKeyThreshold(t *testing.T) { + keys := map[string]*utsstypes.TssKey{ + "old-key": {KeyId: "old-key", Participants: []string{"v1", "v2", "v3", "v4", "v5", "v6"}}, + } + c := coordinatorWithKeys(keys) + + all := validatorSet("v1", "v2", "v3", "v4", "v5", "v6", "n1", "n2", "n3", "n4", "n5") + + shareholders := map[string]bool{"v1": true, "v2": true, "v3": true, "v4": true, "v5": true, "v6": true} + for i := 0; i < 200; i++ { + got, err := c.fundMigrateParticipants(context.Background(), fundMigrateEvent(t, "old-key"), all) + require.NoError(t, err) + + // CalculateThreshold(6) is 5, and it is the old key's size that decides. + require.Len(t, got, CalculateThreshold(6)) + for _, addr := range addressesOf(got) { + assert.True(t, shareholders[addr], "selected %s which holds no share of the old key", addr) + } + } +} + +// Fail closed rather than hand back a set that cannot reach the old key's +// threshold. A short set would stall the session on an ACK that never arrives. +func TestFundMigrateParticipants_FailsWhenTooFewShareholdersRemain(t *testing.T) { + keys := map[string]*utsstypes.TssKey{ + "old-key": {KeyId: "old-key", Participants: []string{"v1", "v2", "v3", "v4", "v5", "v6"}}, + } + c := coordinatorWithKeys(keys) + + // Only 4 of the 6 shareholders remain, one short of the threshold of 5, + // while the current set is comfortably large. + all := validatorSet("v1", "v2", "v3", "v4", "n1", "n2", "n3", "n4", "n5", "n6") + + got, err := c.fundMigrateParticipants(context.Background(), fundMigrateEvent(t, "old-key"), all) + require.Error(t, err) + assert.Nil(t, got) + assert.Contains(t, err.Error(), "only 4 are still eligible") +} + +// Pending leave keeps signing; anything else is not a usable signer even when +// it holds a share. +func TestFundMigrateParticipants_ExcludesIneligibleShareholders(t *testing.T) { + keys := map[string]*utsstypes.TssKey{ + "old-key": {KeyId: "old-key", Participants: []string{"v1", "v2", "v3"}}, + } + c := coordinatorWithKeys(keys) + + all := []*types.UniversalValidator{ + validatorWithStatus("v1", types.UVStatus_UV_STATUS_ACTIVE), + validatorWithStatus("v2", types.UVStatus_UV_STATUS_PENDING_LEAVE), + validatorWithStatus("v3", types.UVStatus_UV_STATUS_ACTIVE), + } + + got, err := c.fundMigrateParticipants(context.Background(), fundMigrateEvent(t, "old-key"), all) + require.NoError(t, err) + assert.ElementsMatch(t, []string{"v1", "v2", "v3"}, addressesOf(got)) + + // The same set with one shareholder no longer signing is one short. + all[1] = validatorWithStatus("v2", types.UVStatus_UV_STATUS_INACTIVE) + got, err = c.fundMigrateParticipants(context.Background(), fundMigrateEvent(t, "old-key"), all) + require.Error(t, err) + assert.Nil(t, got) +} + +func TestFundMigrateParticipants_RejectsUnusableEventData(t *testing.T) { + c := coordinatorWithKeys(map[string]*utsstypes.TssKey{ + "old-key": {KeyId: "old-key", Participants: []string{"v1", "v2", "v3"}}, + }) + all := validatorSet("v1", "v2", "v3") + + t.Run("malformed event data", func(t *testing.T) { + event := store.Event{EventID: "fm-1", Type: store.EventTypeSignFundMigrate, EventData: []byte("not json")} + _, err := c.fundMigrateParticipants(context.Background(), event, all) + require.Error(t, err) + assert.Contains(t, err.Error(), "parse fund migration data") + }) + + t.Run("no old key id", func(t *testing.T) { + _, err := c.fundMigrateParticipants(context.Background(), fundMigrateEvent(t, ""), all) + require.Error(t, err) + assert.Contains(t, err.Error(), "no old key id") + }) + + t.Run("unknown old key", func(t *testing.T) { + _, err := c.fundMigrateParticipants(context.Background(), fundMigrateEvent(t, "missing-key"), all) + require.Error(t, err) + assert.Contains(t, err.Error(), "records no participants") + }) + + t.Run("key with empty participants", func(t *testing.T) { + c := coordinatorWithKeys(map[string]*utsstypes.TssKey{"old-key": {KeyId: "old-key"}}) + _, err := c.fundMigrateParticipants(context.Background(), fundMigrateEvent(t, "old-key"), all) + require.Error(t, err) + assert.Contains(t, err.Error(), "records no participants") + }) + + t.Run("lookup failure", func(t *testing.T) { + c := &Coordinator{ + pushCore: &stalenessMockPushCore{keyErr: fmt.Errorf("rpc down")}, + logger: zerolog.Nop(), + } + _, err := c.fundMigrateParticipants(context.Background(), fundMigrateEvent(t, "old-key"), all) + require.Error(t, err) + assert.Contains(t, err.Error(), "fetch old key") + }) +} + +// A shareholder that has since dropped its identity record must not be counted +// towards the threshold, since it cannot be addressed as a party. +func TestFundMigrateParticipants_SkipsValidatorWithoutIdentity(t *testing.T) { + c := coordinatorWithKeys(map[string]*utsstypes.TssKey{ + "old-key": {KeyId: "old-key", Participants: []string{"v1", "v2", "v3"}}, + }) + + all := []*types.UniversalValidator{ + activeValidator("v1"), + {LifecycleInfo: &types.LifecycleInfo{CurrentStatus: types.UVStatus_UV_STATUS_ACTIVE}}, + activeValidator("v3"), + } + + _, err := c.fundMigrateParticipants(context.Background(), fundMigrateEvent(t, "old-key"), all) + require.Error(t, err) + assert.Contains(t, err.Error(), "only 2 are still eligible") +} + +// The routing itself: a fund migration must not be selected the way an +// outbound is, which is the defect this change fixes. +func TestSelectParticipants_RoutesFundMigrateToShareholders(t *testing.T) { + c := coordinatorWithKeys(map[string]*utsstypes.TssKey{ + "old-key": {KeyId: "old-key", Participants: []string{"v1", "v2", "v3"}}, + }) + + all := validatorSet("v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8", "v9", "v10") + + t.Run("fund migrate is confined to the old key", func(t *testing.T) { + for i := 0; i < 100; i++ { + got, err := c.SelectParticipants(context.Background(), fundMigrateEvent(t, "old-key"), all) + require.NoError(t, err) + assert.ElementsMatch(t, []string{"v1", "v2", "v3"}, addressesOf(got)) + } + }) + + t.Run("outbound still uses the current set", func(t *testing.T) { + event := store.Event{EventID: "ob-1", Type: store.EventTypeSignOutbound} + got, err := c.SelectParticipants(context.Background(), event, all) + require.NoError(t, err) + assert.Len(t, got, CalculateThreshold(len(all))) + }) + + t.Run("fund migrate reports rather than returning a short set", func(t *testing.T) { + _, err := c.SelectParticipants(context.Background(), fundMigrateEvent(t, "gone"), all) + require.Error(t, err) + }) + + t.Run("other protocols take every eligible validator", func(t *testing.T) { + event := store.Event{EventID: "kg-1", Type: store.EventTypeKeygen} + got, err := c.SelectParticipants(context.Background(), event, all) + require.NoError(t, err) + assert.Len(t, got, len(all)) + }) +} + +// The caller-supplied threshold is what keeps the count tied to the old key +// rather than to the surviving holders. +func TestSelectRandomThreshold_ExplicitCount(t *testing.T) { + all := validatorSet("v1", "v2", "v3", "v4", "v5") + + assert.Nil(t, selectRandomThreshold(nil, 3)) + assert.Nil(t, selectRandomThreshold(all, 0)) + assert.Nil(t, selectRandomThreshold(all, -1)) + assert.Len(t, selectRandomThreshold(all, 5), 5) + assert.Len(t, selectRandomThreshold(all, 9), 5) + + // Picks vary across calls and never repeat a validator within one pick. + seen := map[string]bool{} + for i := 0; i < 200; i++ { + got := selectRandomThreshold(all, 3) + require.Len(t, got, 3) + unique := map[string]bool{} + for _, addr := range addressesOf(got) { + assert.False(t, unique[addr], "duplicate %s in one selection", addr) + unique[addr] = true + seen[addr] = true + } + } + assert.Len(t, seen, 5, "selection never reached some validators") +} diff --git a/universalClient/tss/coordinator/utils.go b/universalClient/tss/coordinator/utils.go index b64371ba8..562138afe 100644 --- a/universalClient/tss/coordinator/utils.go +++ b/universalClient/tss/coordinator/utils.go @@ -65,28 +65,25 @@ func deriveKeyIDBytes(keyID string) []byte { return sum[:] } -// selectRandomThreshold selects a random subset of at least threshold count from eligible validators. -// Returns a shuffled copy of at least threshold validators (or all if fewer than threshold). -func selectRandomThreshold(eligible []*types.UniversalValidator) []*types.UniversalValidator { - if len(eligible) == 0 { +// selectRandomThreshold selects a random threshold count of eligible validators. +// Returns a shuffled copy of threshold validators (or all if fewer than threshold). +// The caller supplies the threshold: for fund migration it belongs to the old key, +// not to the set of validators still holding its shares. +func selectRandomThreshold(eligible []*types.UniversalValidator, threshold int) []*types.UniversalValidator { + if len(eligible) == 0 || threshold <= 0 { return nil } - // Calculate minimum required: >2/3 (same as threshold calculation) - minRequired := CalculateThreshold(len(eligible)) - - // If we have fewer than minRequired, return all - if len(eligible) <= minRequired { + // If we have fewer than threshold, return all + if len(eligible) <= threshold { return eligible } - // Randomly select at least minRequired participants - // Shuffle and take first minRequired shuffled := make([]*types.UniversalValidator, len(eligible)) copy(shuffled, eligible) rand.Shuffle(len(shuffled), func(i, j int) { shuffled[i], shuffled[j] = shuffled[j], shuffled[i] }) - return shuffled[:minRequired] + return shuffled[:threshold] } diff --git a/universalClient/tss/sessionmanager/fund_migrate_e2e_test.go b/universalClient/tss/sessionmanager/fund_migrate_e2e_test.go new file mode 100644 index 000000000..685295d7c --- /dev/null +++ b/universalClient/tss/sessionmanager/fund_migrate_e2e_test.go @@ -0,0 +1,263 @@ +package sessionmanager + +import ( + "context" + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/pushchain/push-chain-node/universalClient/store" + "github.com/pushchain/push-chain-node/universalClient/tss/coordinator" + utsstypes "github.com/pushchain/push-chain-node/x/utss/types" + "github.com/pushchain/push-chain-node/x/uvalidator/types" +) + +func fundMigrateStoreEvent(t *testing.T, oldKeyID string) *store.Event { + t.Helper() + data, err := json.Marshal(utsstypes.FundMigrationInitiatedEventData{OldKeyID: oldKeyID}) + require.NoError(t, err) + return &store.Event{ + EventID: "fm-e2e", + Type: store.EventTypeSignFundMigrate, + EventData: data, + } +} + +func activeValidators(addrs ...string) []*types.UniversalValidator { + set := make([]*types.UniversalValidator, 0, len(addrs)) + for _, a := range addrs { + set = append(set, makeActiveValidator(a)) + } + return set +} + +func partyIDs(vs []*types.UniversalValidator) []string { + ids := make([]string, 0, len(vs)) + for _, v := range vs { + ids = append(ids, v.IdentifyInfo.CoreValidatorAddress) + } + return ids +} + +// End to end across both components: the coordinator selects the participants, +// then a participant validates the setup message it receives. +// +// The two sides derive the answer independently, so a change to one that the +// other does not mirror leaves a selection the coordinator can legitimately +// make and every participant rejects. Neither side's own tests catch that. +func TestFundMigrate_CoordinatorSelectionPassesParticipantValidation(t *testing.T) { + ctx := context.Background() + + // The finding's scenario: the old key has 3 shareholders and the validator + // set has since grown to 10. + oldKey := &utsstypes.TssKey{KeyId: "old-key", Participants: []string{"v1", "v2", "v3"}} + + sm, coord, _, _, _, _ := setupTestSessionManager(t) + setCoordinatorPushCore(coord, &mockPushCore{ + keysByID: map[string]*utsstypes.TssKey{"old-key": oldKey}, + }) + setCoordinatorValidators(coord, activeValidators( + "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8", "v9", "v10")) + + event := fundMigrateStoreEvent(t, "old-key") + + // Selection is randomised, so repeat rather than trusting one draw. + for i := 0; i < 100; i++ { + selected, err := coord.SelectParticipants(ctx, *event, coord.Validators()) + require.NoError(t, err, "coordinator could not select signers") + + ids := partyIDs(selected) + assert.ElementsMatch(t, []string{"v1", "v2", "v3"}, ids, + "coordinator selected a validator that holds no share of the old key") + + require.NoError(t, sm.validateParticipants(ctx, ids, event), + "participant rejected a selection the coordinator legitimately made") + } +} + +// The same round trip for an outbound, which must keep using the current +// validator set on both sides. +func TestSignOutbound_CoordinatorSelectionPassesParticipantValidation(t *testing.T) { + ctx := context.Background() + + sm, coord, _, _, _, _ := setupTestSessionManager(t) + all := activeValidators("v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8", "v9", "v10") + setCoordinatorValidators(coord, all) + + event := &store.Event{EventID: "ob-e2e", Type: store.EventTypeSignOutbound} + + for i := 0; i < 100; i++ { + selected, err := coord.SelectParticipants(ctx, *event, coord.Validators()) + require.NoError(t, err) + + ids := partyIDs(selected) + require.Len(t, ids, coordinator.CalculateThreshold(len(all))) + require.NoError(t, sm.validateParticipants(ctx, ids, event)) + } +} + +// Validation must be tied to the old key, not merely lenient. A set that meets +// the count but contains a validator holding no share is still rejected. +func TestFundMigrate_ValidationRejectsNonShareholders(t *testing.T) { + ctx := context.Background() + + sm, coord, _, _, _, _ := setupTestSessionManager(t) + setCoordinatorPushCore(coord, &mockPushCore{ + keysByID: map[string]*utsstypes.TssKey{ + "old-key": {KeyId: "old-key", Participants: []string{"v1", "v2", "v3"}}, + }, + }) + setCoordinatorValidators(coord, activeValidators( + "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8", "v9", "v10")) + + event := fundMigrateStoreEvent(t, "old-key") + + t.Run("newcomer in an otherwise valid set", func(t *testing.T) { + err := sm.validateParticipants(ctx, []string{"v1", "v2", "v10"}, event) + require.Error(t, err) + assert.Contains(t, err.Error(), "v10") + }) + + t.Run("all newcomers, count satisfied", func(t *testing.T) { + err := sm.validateParticipants(ctx, []string{"v8", "v9", "v10"}, event) + require.Error(t, err) + }) + + t.Run("below the old key threshold", func(t *testing.T) { + err := sm.validateParticipants(ctx, []string{"v1", "v2"}, event) + require.Error(t, err) + assert.Contains(t, err.Error(), "below required threshold 3") + }) + + t.Run("exactly the shareholders is accepted", func(t *testing.T) { + require.NoError(t, sm.validateParticipants(ctx, []string{"v1", "v2", "v3"}, event)) + }) +} + +// A larger old key, so the accepted count is a strict subset of shareholders +// rather than all of them, and the current set is not what sizes it. +func TestFundMigrate_ValidationUsesOldKeyThresholdNotCurrentSet(t *testing.T) { + ctx := context.Background() + + sm, coord, _, _, _, _ := setupTestSessionManager(t) + setCoordinatorPushCore(coord, &mockPushCore{ + keysByID: map[string]*utsstypes.TssKey{ + "old-key": {KeyId: "old-key", Participants: []string{"v1", "v2", "v3", "v4", "v5", "v6"}}, + }, + }) + // 6 shareholders among 12 validators. Old key threshold is 5, the current + // set's would be 9, which no set of shareholders could ever satisfy. + setCoordinatorValidators(coord, activeValidators( + "v1", "v2", "v3", "v4", "v5", "v6", "n1", "n2", "n3", "n4", "n5", "n6")) + + event := fundMigrateStoreEvent(t, "old-key") + + require.Equal(t, 5, coordinator.CalculateThreshold(6)) + require.Equal(t, 9, coordinator.CalculateThreshold(12)) + + t.Run("old key threshold is accepted", func(t *testing.T) { + require.NoError(t, sm.validateParticipants(ctx, []string{"v1", "v2", "v3", "v4", "v5"}, event)) + }) + + t.Run("all shareholders is accepted", func(t *testing.T) { + require.NoError(t, sm.validateParticipants(ctx, []string{"v1", "v2", "v3", "v4", "v5", "v6"}, event)) + }) + + t.Run("one below the old key threshold is rejected", func(t *testing.T) { + err := sm.validateParticipants(ctx, []string{"v1", "v2", "v3", "v4"}, event) + require.Error(t, err) + assert.Contains(t, err.Error(), "below required threshold 5") + }) +} + +// Some shareholders are gone but enough remain to sign. The threshold must +// still be the old key's, not one derived from the survivors: deriving it from +// the survivors lowers the bar every time a shareholder drops out, so a +// coordinator could open a session below the quorum the key was created under. +func TestFundMigrate_ValidationThresholdDoesNotShrinkWithSurvivors(t *testing.T) { + ctx := context.Background() + + sm, coord, _, _, _, _ := setupTestSessionManager(t) + setCoordinatorPushCore(coord, &mockPushCore{ + keysByID: map[string]*utsstypes.TssKey{ + "old-key": {KeyId: "old-key", Participants: []string{"v1", "v2", "v3", "v4", "v5", "v6"}}, + }, + }) + // v6 is gone, so 5 of the 6 shareholders survive. The old key still requires + // 5, while a threshold over the survivors would be only 4. + setCoordinatorValidators(coord, activeValidators("v1", "v2", "v3", "v4", "v5", "n1", "n2", "n3")) + + event := fundMigrateStoreEvent(t, "old-key") + + require.Equal(t, 5, coordinator.CalculateThreshold(6), "old key threshold") + require.Equal(t, 4, coordinator.CalculateThreshold(5), "threshold over survivors") + + t.Run("four survivors is below the old key threshold", func(t *testing.T) { + err := sm.validateParticipants(ctx, []string{"v1", "v2", "v3", "v4"}, event) + require.Error(t, err) + assert.Contains(t, err.Error(), "below required threshold 5") + }) + + t.Run("all five survivors is accepted", func(t *testing.T) { + require.NoError(t, sm.validateParticipants(ctx, []string{"v1", "v2", "v3", "v4", "v5"}, event)) + }) + + t.Run("the coordinator selects exactly those five", func(t *testing.T) { + selected, err := coord.SelectParticipants(ctx, *event, coord.Validators()) + require.NoError(t, err) + ids := partyIDs(selected) + assert.ElementsMatch(t, []string{"v1", "v2", "v3", "v4", "v5"}, ids) + require.NoError(t, sm.validateParticipants(ctx, ids, event)) + }) +} + +// Too few shareholders left to sign at all. Both sides must refuse, and the +// coordinator must not dispatch a set it knows cannot reach quorum. +func TestFundMigrate_BothSidesFailClosedWhenShareholdersGone(t *testing.T) { + ctx := context.Background() + + sm, coord, _, _, _, _ := setupTestSessionManager(t) + setCoordinatorPushCore(coord, &mockPushCore{ + keysByID: map[string]*utsstypes.TssKey{ + "old-key": {KeyId: "old-key", Participants: []string{"v1", "v2", "v3", "v4", "v5", "v6"}}, + }, + }) + // Only 4 of the 6 shareholders remain, one short of the threshold of 5. + setCoordinatorValidators(coord, activeValidators("v1", "v2", "v3", "v4", "n1", "n2", "n3", "n4")) + + event := fundMigrateStoreEvent(t, "old-key") + + _, err := coord.SelectParticipants(ctx, *event, coord.Validators()) + require.Error(t, err, "coordinator dispatched a set that cannot reach quorum") + + err = sm.validateParticipants(ctx, []string{"v1", "v2", "v3", "v4"}, event) + require.Error(t, err) +} + +// Validation must not fall open when the old key cannot be resolved. +func TestFundMigrate_ValidationFailsClosedOnUnresolvableKey(t *testing.T) { + ctx := context.Background() + + sm, coord, _, _, _, _ := setupTestSessionManager(t) + setCoordinatorValidators(coord, activeValidators("v1", "v2", "v3", "v4", "v5")) + + // The default mock returns a key with no participants for any id. + setCoordinatorPushCore(coord, &mockPushCore{}) + + err := sm.validateParticipants(ctx, []string{"v1", "v2", "v3", "v4"}, fundMigrateStoreEvent(t, "old-key")) + require.Error(t, err) + assert.Contains(t, err.Error(), "resolve fund migration signers") + + t.Run("malformed event data", func(t *testing.T) { + event := &store.Event{ + EventID: "fm-bad", + Type: store.EventTypeSignFundMigrate, + EventData: []byte("not json"), + } + err := sm.validateParticipants(ctx, []string{"v1", "v2", "v3", "v4"}, event) + require.Error(t, err) + assert.Contains(t, err.Error(), "resolve fund migration signers") + }) +} diff --git a/universalClient/tss/sessionmanager/sessionmanager.go b/universalClient/tss/sessionmanager/sessionmanager.go index 47db05ec9..b6bc53d60 100644 --- a/universalClient/tss/sessionmanager/sessionmanager.go +++ b/universalClient/tss/sessionmanager/sessionmanager.go @@ -25,6 +25,7 @@ import ( "github.com/pushchain/push-chain-node/universalClient/tss/keyshare" uexecutortypes "github.com/pushchain/push-chain-node/x/uexecutor/types" utsstypes "github.com/pushchain/push-chain-node/x/utss/types" + uvalidatortypes "github.com/pushchain/push-chain-node/x/uvalidator/types" ) // SendFunc is a function type for sending messages to participants. @@ -179,7 +180,7 @@ func (sm *SessionManager) handleSetupMessage(ctx context.Context, senderPeerID s } // 5. Validate participants list matches event protocol requirements - if err := sm.validateParticipants(msg.Participants, event); err != nil { + if err := sm.validateParticipants(ctx, msg.Participants, event); err != nil { return fmt.Errorf("participants validation failed: %w", err) } @@ -752,9 +753,24 @@ func (sm *SessionManager) createSession(ctx context.Context, event *store.Event, // validateParticipants validates that participants match protocol requirements. // For keygen/keyrefresh: participants must match exactly with eligible participants (same elements). // For sign: participants must be a valid >2/3 subset of eligible participants. -func (sm *SessionManager) validateParticipants(participants []string, event *store.Event) error { - // Get eligible validators for this protocol - eligible := sm.coordinator.GetEligibleUV(string(event.Type)) +func (sm *SessionManager) validateParticipants(ctx context.Context, participants []string, event *store.Event) error { + // Get eligible validators for this protocol. + // + // Fund migration is signed with the old key's shares, so both who may take + // part and how many are required come from that key rather than from the + // current validator set. Resolved through the coordinator so the check here + // mirrors the selection exactly. + var eligible []*uvalidatortypes.UniversalValidator + var fundMigrateRequired int + if event.Type == store.EventTypeSignFundMigrate { + var err error + eligible, fundMigrateRequired, err = sm.coordinator.FundMigrateEligible(ctx, *event) + if err != nil { + return fmt.Errorf("resolve fund migration signers: %w", err) + } + } else { + eligible = sm.coordinator.GetEligibleUV(string(event.Type)) + } if len(eligible) == 0 { return fmt.Errorf("no eligible validators for protocol") } @@ -793,8 +809,8 @@ func (sm *SessionManager) validateParticipants(participants []string, event *sto } } - case store.EventTypeSignOutbound, store.EventTypeSignFundMigrate: - // For SIGN and FUND_MIGRATE the coordinator picks a random threshold subset (>2/3 of eligible) + case store.EventTypeSignOutbound: + // For SIGN the coordinator picks a random threshold subset (>2/3 of eligible) // rather than all eligible validators. Accept any subset as long as it meets the threshold // minimum; all participants are already verified eligible by the eligibleSet check above. threshold := coordinator.CalculateThreshold(len(eligibleList)) @@ -803,6 +819,15 @@ func (sm *SessionManager) validateParticipants(participants []string, event *sto event.Type, len(participants), threshold, len(eligibleList)) } + case store.EventTypeSignFundMigrate: + // The old key's threshold, not the current set's. Sizing this from the + // live validator set would reject a legitimate selection whenever the + // set has grown since that key was created. + if len(participants) < fundMigrateRequired { + return fmt.Errorf("%s participants count %d is below required threshold %d (shareholders still eligible: %d)", + event.Type, len(participants), fundMigrateRequired, len(eligibleList)) + } + default: return fmt.Errorf("unknown protocol type: %s", event.Type) } diff --git a/universalClient/tss/sessionmanager/sessionmanager_test.go b/universalClient/tss/sessionmanager/sessionmanager_test.go index 73f0dd2a9..cd1ae782a 100644 --- a/universalClient/tss/sessionmanager/sessionmanager_test.go +++ b/universalClient/tss/sessionmanager/sessionmanager_test.go @@ -53,6 +53,9 @@ func containsAny(s string, substrings []string) bool { // block height (0 by default) so coordinator-at-block math is deterministic. type mockPushCore struct { block uint64 + + // Old key history, consulted when validating fund migration signers. + keysByID map[string]*utsstypes.TssKey } func (m *mockPushCore) GetLatestBlock(_ context.Context) (uint64, error) { @@ -63,6 +66,13 @@ func (m *mockPushCore) GetCurrentKey(_ context.Context) (*utsstypes.TssKey, erro return &utsstypes.TssKey{KeyId: "test-key"}, nil } +func (m *mockPushCore) GetKeyByID(_ context.Context, keyID string) (*utsstypes.TssKey, error) { + if key, ok := m.keysByID[keyID]; ok { + return key, nil + } + return &utsstypes.TssKey{KeyId: keyID}, nil +} + func (m *mockPushCore) GetAllUniversalValidators(_ context.Context) ([]*types.UniversalValidator, error) { return nil, nil } @@ -367,6 +377,11 @@ func setCoordinatorValidators(coord *coordinator.Coordinator, validators []*type if field.IsValid() { *(*[]*types.UniversalValidator)(unsafe.Pointer(field.UnsafeAddr())) = validators } + // Keep the cache fresh, otherwise a slow test trips the staleness halt and + // the snapshot comes back empty. + if refresh := coordValue.FieldByName("lastValidatorsRefreshAt"); refresh.IsValid() { + *(*time.Time)(unsafe.Pointer(refresh.UnsafeAddr())) = time.Now() + } } func makeActiveValidator(addr string) *types.UniversalValidator { @@ -398,54 +413,69 @@ func TestValidateParticipants(t *testing.T) { t.Run("SIGN: threshold subset is valid", func(t *testing.T) { // 3 of 4 eligible satisfies threshold(4)=3 - assert.NoError(t, sm.validateParticipants([]string{"v1", "v2", "v3"}, signEvent)) + assert.NoError(t, sm.validateParticipants(context.Background(), []string{"v1", "v2", "v3"}, signEvent)) }) t.Run("SIGN: all eligible is also valid (threshold is a minimum)", func(t *testing.T) { - assert.NoError(t, sm.validateParticipants([]string{"v1", "v2", "v3", "v4"}, signEvent)) + assert.NoError(t, sm.validateParticipants(context.Background(), []string{"v1", "v2", "v3", "v4"}, signEvent)) }) t.Run("SIGN: below threshold is rejected", func(t *testing.T) { // 2 < threshold(4)=3 - err := sm.validateParticipants([]string{"v1", "v2"}, signEvent) + err := sm.validateParticipants(context.Background(), []string{"v1", "v2"}, signEvent) require.Error(t, err) assert.Contains(t, err.Error(), "threshold") }) t.Run("SIGN: non-eligible participant is rejected", func(t *testing.T) { - err := sm.validateParticipants([]string{"v1", "v2", "unknown"}, signEvent) + err := sm.validateParticipants(context.Background(), []string{"v1", "v2", "unknown"}, signEvent) require.Error(t, err) assert.Contains(t, err.Error(), "not eligible") }) - // --- SIGN_FUND_MIGRATE: same threshold rules as SIGN_OUTBOUND --- + // --- SIGN_FUND_MIGRATE: rules come from the old key, not the current set --- - fmEvent := &store.Event{EventID: "fm-1", Type: store.EventTypeSignFundMigrate} + // The old key's shareholders are v1..v4, matching the current set here, so + // the threshold is the same 3 as for SIGN_OUTBOUND above. The two diverge + // once the sets differ, covered in fund_migrate_e2e_test.go. + setCoordinatorPushCore(coord, &mockPushCore{ + keysByID: map[string]*utsstypes.TssKey{ + "old-key": {KeyId: "old-key", Participants: []string{"v1", "v2", "v3", "v4"}}, + }, + }) + fmEvent := fundMigrateStoreEvent(t, "old-key") t.Run("SIGN_FUND_MIGRATE: threshold subset is valid", func(t *testing.T) { - assert.NoError(t, sm.validateParticipants([]string{"v1", "v2", "v3"}, fmEvent)) + assert.NoError(t, sm.validateParticipants(context.Background(), []string{"v1", "v2", "v3"}, fmEvent)) }) t.Run("SIGN_FUND_MIGRATE: below threshold is rejected", func(t *testing.T) { - err := sm.validateParticipants([]string{"v1", "v2"}, fmEvent) + err := sm.validateParticipants(context.Background(), []string{"v1", "v2"}, fmEvent) require.Error(t, err) assert.Contains(t, err.Error(), "threshold") }) + t.Run("SIGN_FUND_MIGRATE: event without an old key id is rejected", func(t *testing.T) { + bare := &store.Event{EventID: "fm-bare", Type: store.EventTypeSignFundMigrate} + err := sm.validateParticipants(context.Background(), []string{"v1", "v2", "v3"}, bare) + require.Error(t, err) + assert.Contains(t, err.Error(), "resolve fund migration signers") + }) + // --- KEYGEN: exact-match rules (all eligible must participate) --- t.Run("KEYGEN: all eligible is valid", func(t *testing.T) { - assert.NoError(t, sm.validateParticipants([]string{"v1", "v2", "v3", "v4"}, keygenEvent)) + assert.NoError(t, sm.validateParticipants(context.Background(), []string{"v1", "v2", "v3", "v4"}, keygenEvent)) }) t.Run("KEYGEN: missing participant is rejected", func(t *testing.T) { - err := sm.validateParticipants([]string{"v1", "v2", "v3"}, keygenEvent) // v4 missing + err := sm.validateParticipants(context.Background(), []string{"v1", "v2", "v3"}, keygenEvent) // v4 missing require.Error(t, err) assert.Contains(t, err.Error(), "does not match eligible count") }) t.Run("KEYGEN: non-eligible participant is rejected", func(t *testing.T) { - err := sm.validateParticipants([]string{"v1", "v2", "v3", "v4", "unknown"}, keygenEvent) + err := sm.validateParticipants(context.Background(), []string{"v1", "v2", "v3", "v4", "unknown"}, keygenEvent) require.Error(t, err) assert.Contains(t, err.Error(), "not eligible") }) From b5be6a79931fba77cbaa0987e8fef0c4cfdb5e7d Mon Sep 17 00:00:00 2001 From: Aman Gupta Date: Mon, 24 Aug 2026 16:41:37 +0530 Subject: [PATCH 21/60] =?UTF-8?q?fix:=20F-2026-18800=20|=20[Dual=20Defense?= =?UTF-8?q?]=20Short=20SVM=20send=5Ffunds=20Defaults=20TxType=3D0=20While?= =?UTF-8?q?=20Claiming=20Funds=20=E2=86=92=20Mapped=20to=20GAS=20(#327)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(chains): reject truncated UniversalTx events instead of defaulting tx_type to GAS * fix(common): guard event cleaner lifecycle with a mutex and wait for its goroutine * revert unrelated gofmt change in client_test * test: fold new tests into the existing per-source test files --- .../chains/common/event_cleaner.go | 50 ++-- .../chains/common/event_cleaner_test.go | 140 ++++++++++- .../chains/common/event_processor_test.go | 74 ++++++ universalClient/chains/evm/event_parser.go | 36 +-- .../chains/evm/event_parser_test.go | 31 ++- universalClient/chains/svm/event_parser.go | 79 +++--- .../chains/svm/event_parser_test.go | 229 +++++++++++++++--- 7 files changed, 525 insertions(+), 114 deletions(-) diff --git a/universalClient/chains/common/event_cleaner.go b/universalClient/chains/common/event_cleaner.go index b7843a42a..0d89d1083 100644 --- a/universalClient/chains/common/event_cleaner.go +++ b/universalClient/chains/common/event_cleaner.go @@ -3,6 +3,7 @@ package common import ( "context" "fmt" + "sync" "time" "github.com/pushchain/push-chain-node/universalClient/db" @@ -23,9 +24,14 @@ type EventCleaner struct { cleanupInterval time.Duration retentionPeriod time.Duration logger zerolog.Logger - ticker *time.Ticker - stopCh chan struct{} - running bool + + // mu guards running and stopCh, which Start and Stop both touch. The + // cleanup goroutine reads neither: it closes over its own copies, so the + // only cross-goroutine state is the channel it selects on. + mu sync.Mutex + running bool + stopCh chan struct{} + wg sync.WaitGroup } // NewEventCleaner creates a new event cleaner for a chain @@ -54,9 +60,16 @@ func NewEventCleaner( // Start begins the periodic cleanup process func (ec *EventCleaner) Start(ctx context.Context) error { + ec.mu.Lock() if ec.running { + ec.mu.Unlock() return fmt.Errorf("event cleaner is already running") } + stopCh := make(chan struct{}) + ec.running = true + ec.stopCh = stopCh + ec.wg.Add(1) + ec.mu.Unlock() ec.logger.Debug(). Str("cleanup_interval", ec.cleanupInterval.String()). @@ -69,21 +82,23 @@ func (ec *EventCleaner) Start(ctx context.Context) error { // Don't fail startup on cleanup error, just log it } - ec.running = true - ec.stopCh = make(chan struct{}) - ec.ticker = time.NewTicker(ec.cleanupInterval) + // The ticker and stop channel are the goroutine's own. Holding them on the + // struct let Stop write the fields while the goroutine was still reading + // them, which is the race this shape removes. + ticker := time.NewTicker(ec.cleanupInterval) go func() { - defer ec.ticker.Stop() + defer ec.wg.Done() + defer ticker.Stop() for { select { case <-ctx.Done(): ec.logger.Debug().Msg("context cancelled, stopping event cleaner") return - case <-ec.stopCh: + case <-stopCh: ec.logger.Debug().Msg("stop signal received, stopping event cleaner") return - case <-ec.ticker.C: + case <-ticker.C: if err := ec.performCleanup(); err != nil { ec.logger.Error().Err(err).Msg("failed to perform scheduled cleanup") } @@ -94,17 +109,24 @@ func (ec *EventCleaner) Start(ctx context.Context) error { return nil } -// Stop gracefully stops the event cleaner. No-op if not running. +// Stop gracefully stops the event cleaner and waits for the cleanup goroutine +// to exit. No-op if not running. +// +// Waiting matters on shutdown: the goroutine runs queries against the chain +// database, and returning before it finishes lets the caller close that +// database underneath an in-flight cleanup. func (ec *EventCleaner) Stop() { + ec.mu.Lock() if !ec.running { + ec.mu.Unlock() return } ec.logger.Debug().Msg("stopping event cleaner") - if ec.ticker != nil { - ec.ticker.Stop() - } - close(ec.stopCh) ec.running = false + close(ec.stopCh) + ec.mu.Unlock() + + ec.wg.Wait() } // performCleanup executes cleanup of terminal events (COMPLETED, REORGED, REVERTED) diff --git a/universalClient/chains/common/event_cleaner_test.go b/universalClient/chains/common/event_cleaner_test.go index bc28d2eae..15eee2be1 100644 --- a/universalClient/chains/common/event_cleaner_test.go +++ b/universalClient/chains/common/event_cleaner_test.go @@ -3,6 +3,7 @@ package common import ( "context" "fmt" + "sync" "testing" "time" @@ -75,8 +76,8 @@ func TestEventCleanerStruct(t *testing.T) { assert.Nil(t, ec.database) assert.Equal(t, time.Duration(0), ec.cleanupInterval) assert.Equal(t, time.Duration(0), ec.retentionPeriod) - assert.Nil(t, ec.ticker) assert.Nil(t, ec.stopCh) + assert.False(t, ec.running) }) } @@ -250,7 +251,7 @@ func TestEventCleanerStart(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) require.NoError(t, cleaner.Start(ctx)) - require.NotNil(t, cleaner.ticker) + require.NotNil(t, cleaner.stopCh) cancel() time.Sleep(100 * time.Millisecond) @@ -337,3 +338,138 @@ func TestEventCleanerStartStopLifecycle(t *testing.T) { time.Sleep(50 * time.Millisecond) }) } + +// Start and Stop race against the cleanup goroutine. Run under -race. +func TestEventCleaner_StartStopUnderRace(t *testing.T) { + for i := 0; i < 20; i++ { + database := newTestCleanerDB(t, nil) + cleaner := NewEventCleaner(database, intPtr(3600), intPtr(0), "test-chain", zerolog.Nop()) + // Fast enough that the goroutine is inside performCleanup while Stop runs. + cleaner.cleanupInterval = time.Millisecond + + require.NoError(t, cleaner.Start(context.Background())) + cleaner.Stop() + } +} + +// Concurrent Stop calls must not double close the channel or return before the +// goroutine has exited. +func TestEventCleaner_ConcurrentStop(t *testing.T) { + database := newTestCleanerDB(t, nil) + cleaner := NewEventCleaner(database, intPtr(3600), intPtr(0), "test-chain", zerolog.Nop()) + cleaner.cleanupInterval = time.Millisecond + + require.NoError(t, cleaner.Start(context.Background())) + + var wg sync.WaitGroup + for i := 0; i < 8; i++ { + wg.Add(1) + go func() { + defer wg.Done() + cleaner.Stop() + }() + } + wg.Wait() + + assert.False(t, cleaner.running) +} + +// Concurrent Start calls must leave exactly one goroutine running. +func TestEventCleaner_ConcurrentStart(t *testing.T) { + database := newTestCleanerDB(t, nil) + cleaner := NewEventCleaner(database, intPtr(3600), intPtr(0), "test-chain", zerolog.Nop()) + cleaner.cleanupInterval = time.Millisecond + + var mu sync.Mutex + started := 0 + + var wg sync.WaitGroup + for i := 0; i < 8; i++ { + wg.Add(1) + go func() { + defer wg.Done() + if err := cleaner.Start(context.Background()); err == nil { + mu.Lock() + started++ + mu.Unlock() + } + }() + } + wg.Wait() + + assert.Equal(t, 1, started, "more than one cleanup goroutine was started") + cleaner.Stop() +} + +// Stop must not return while a cleanup is still in flight, otherwise the caller +// can close the chain database underneath an in-flight query. +// +// Held open with a write transaction so the goroutine is genuinely blocked +// inside performCleanup while Stop is called. Without that, the goroutine exits +// so fast that a Stop which does not wait looks identical to one that does. +func TestEventCleaner_StopWaitsForInFlightCleanup(t *testing.T) { + database := newTestCleanerDB(t, nil) + cleaner := NewEventCleaner(database, intPtr(3600), intPtr(0), "test-chain", zerolog.Nop()) + cleaner.cleanupInterval = time.Millisecond + + // Start first: the initial cleanup is synchronous and would block on the lock. + require.NoError(t, cleaner.Start(context.Background())) + + // Take the write lock so the next ticked cleanup blocks on DELETE. + tx := database.Client().Begin() + require.NoError(t, tx.Error) + require.NoError(t, tx.Exec( + "CREATE TABLE IF NOT EXISTS lock_probe (id INTEGER PRIMARY KEY)").Error) + require.NoError(t, tx.Exec("INSERT INTO lock_probe (id) VALUES (1)").Error) + + time.Sleep(50 * time.Millisecond) // let a tick land and block + + stopped := make(chan struct{}) + go func() { + cleaner.Stop() + close(stopped) + }() + + select { + case <-stopped: + tx.Rollback() + t.Fatal("Stop returned while a cleanup was still in flight") + case <-time.After(200 * time.Millisecond): + } + + tx.Rollback() // release the lock; the cleanup can now finish + + select { + case <-stopped: + case <-time.After(5 * time.Second): + t.Fatal("Stop did not return after the cleanup finished") + } +} + +// Cancelling the context stops the goroutine, and a later Stop is still safe. +func TestEventCleaner_ContextCancelThenStop(t *testing.T) { + database := newTestCleanerDB(t, nil) + cleaner := NewEventCleaner(database, intPtr(3600), intPtr(0), "test-chain", zerolog.Nop()) + cleaner.cleanupInterval = time.Millisecond + + ctx, cancel := context.WithCancel(context.Background()) + require.NoError(t, cleaner.Start(ctx)) + cancel() + time.Sleep(20 * time.Millisecond) + + cleaner.Stop() // must not hang or panic + assert.False(t, cleaner.running) +} + +// Restart after Stop gets a fresh channel rather than reusing the closed one. +func TestEventCleaner_RestartAfterStop(t *testing.T) { + database := newTestCleanerDB(t, nil) + cleaner := NewEventCleaner(database, intPtr(3600), intPtr(0), "test-chain", zerolog.Nop()) + cleaner.cleanupInterval = time.Millisecond + + require.NoError(t, cleaner.Start(context.Background())) + cleaner.Stop() + + require.NoError(t, cleaner.Start(context.Background()), "restart was refused") + cleaner.Stop() +} diff --git a/universalClient/chains/common/event_processor_test.go b/universalClient/chains/common/event_processor_test.go index 4a08c3056..bd300c485 100644 --- a/universalClient/chains/common/event_processor_test.go +++ b/universalClient/chains/common/event_processor_test.go @@ -1054,3 +1054,77 @@ func TestProcessConfirmedEventsEnabledFlags(t *testing.T) { assert.Equal(t, store.StatusConfirmed, inboundEvt.Status) }) } + +// The wire values the gateways emit are 0-indexed (Gas, GasAndPayload, Funds, +// FundsAndPayload) while the chain enum reserves 0 for UNSPECIFIED, so the +// mapping is shifted by one. A decoder that leaves TxType unset therefore does +// not produce "unknown", it produces GAS. +func TestConstructInbound_TxTypeMapping(t *testing.T) { + processor := &EventProcessor{} + + for _, tc := range []struct { + wire uint + want uexecutortypes.TxType + }{ + {0, uexecutortypes.TxType_GAS}, + {1, uexecutortypes.TxType_GAS_AND_PAYLOAD}, + {2, uexecutortypes.TxType_FUNDS}, + {3, uexecutortypes.TxType_FUNDS_AND_PAYLOAD}, + {4, uexecutortypes.TxType_UNSPECIFIED_TX}, + {99, uexecutortypes.TxType_UNSPECIFIED_TX}, + } { + data, err := json.Marshal(UniversalTx{ + SourceChain: "solana:devnet", + Sender: "0xabc", + Recipient: "0xdef", + Amount: "5000000", + TxType: tc.wire, + }) + require.NoError(t, err) + + inbound, err := processor.constructInbound(&store.Event{ + EventID: "sig:0", + EventData: data, + }) + require.NoError(t, err) + assert.Equal(t, tc.want, inbound.TxType, "wire value %d", tc.wire) + } +} + +// A FUNDS transfer must never reach the keeper as GAS. The two dispatch to +// different handlers: GAS mints and autoswaps into the sender UEA, FUNDS +// deposits PRC20 to the recipient, so the same amount lands with a different +// party. This is the end to end assertion the finding asks for. +func TestConstructInbound_FundsNeverBecomesGas(t *testing.T) { + processor := &EventProcessor{} + + data, err := json.Marshal(UniversalTx{ + SourceChain: "solana:devnet", + Sender: "0xabc", + Recipient: "0xdef", + Amount: "5000000", + TxType: 2, // Funds, as the real devnet events carry + }) + require.NoError(t, err) + + inbound, err := processor.constructInbound(&store.Event{ + EventID: "sig:0", + EventData: data, + }) + require.NoError(t, err) + + assert.Equal(t, uexecutortypes.TxType_FUNDS, inbound.TxType) + assert.NotEqual(t, uexecutortypes.TxType_GAS, inbound.TxType, + "a FUNDS transfer routed to GAS credits the sender instead of the recipient") +} + +// An event whose data never made it past the decoder must be refused outright +// rather than defaulted. The parsers now discard such events, so this is the +// backstop if one ever reaches the store. +func TestConstructInbound_RejectsEventWithoutData(t *testing.T) { + processor := &EventProcessor{} + + _, err := processor.constructInbound(&store.Event{EventID: "sig:0"}) + require.Error(t, err) + assert.Contains(t, err.Error(), "event data is missing") +} diff --git a/universalClient/chains/evm/event_parser.go b/universalClient/chains/evm/event_parser.go index 2c3382327..adbedc77c 100644 --- a/universalClient/chains/evm/event_parser.go +++ b/universalClient/chains/evm/event_parser.go @@ -95,8 +95,15 @@ func parseSendFundsEvent(log *types.Log, chainID string, logger zerolog.Logger) ExpiryBlockHeight: 0, // 0 means no expiry } - // Parse universal tx event data - parseUniversalTxEvent(event, log, chainID, logger) + // Parse universal tx event data. A malformed event is dropped rather than + // stored half-decoded: the zero values it would carry are not neutral. + if err := parseUniversalTxEvent(event, log, chainID, logger); err != nil { + logger.Warn(). + Err(err). + Str("event_id", eventID). + Msg("discarding malformed UniversalTx event") + return nil + } return event } @@ -167,10 +174,13 @@ func parseOutboundObservationEvent(log *types.Log, chainID string, logger zerolo } // parseUniversalTxEvent parses a UniversalTx event from log data. -func parseUniversalTxEvent(event *store.Event, log *types.Log, chainID string, logger zerolog.Logger) { +// +// Static words through txType are required. A truncated log is rejected rather +// than returned half-filled, since the zero values are not neutral: txType 0 is +// GAS, which routes funds to a different account than FUNDS does. +func parseUniversalTxEvent(event *store.Event, log *types.Log, chainID string, logger zerolog.Logger) error { if len(log.Topics) < 3 { - logger.Warn().Msg("not enough indexed fields; nothing to do") - return + return fmt.Errorf("need 3 indexed fields, got %d", len(log.Topics)) } payload := common.UniversalTx{ @@ -181,9 +191,7 @@ func parseUniversalTxEvent(event *store.Event, log *types.Log, chainID string, l } if len(log.Data) < 32*5 { - b, _ := json.Marshal(payload) - event.EventData = b - return + return fmt.Errorf("log data has %d bytes, need at least %d for the static words", len(log.Data), 32*5) } // Parse common static fields: token (Word 0), amount (Word 1) @@ -191,7 +199,7 @@ func parseUniversalTxEvent(event *store.Event, log *types.Log, chainID string, l payload.Amount = new(big.Int).SetBytes(log.Data[1*32 : 2*32]).String() dataOffset := new(big.Int).SetBytes(log.Data[2*32 : 3*32]).Uint64() - parseUniversalTx(event, log, dataOffset, &payload, logger) + return parseUniversalTx(event, log, dataOffset, &payload, logger) } // readDynamicBytes decodes ABI-encoded dynamic bytes at the given absolute offset in data. @@ -278,7 +286,7 @@ UniversalTx Event (V2 - upgraded chains): - signatureData (bytes) — Word 5 (offset) - fromCEA (bool) — Word 6 */ -func parseUniversalTx(event *store.Event, log *types.Log, dataOffset uint64, payload *common.UniversalTx, logger zerolog.Logger) { +func parseUniversalTx(event *store.Event, log *types.Log, dataOffset uint64, payload *common.UniversalTx, logger zerolog.Logger) error { data := log.Data decodePayload(data, dataOffset, payload, logger) @@ -288,10 +296,9 @@ func parseUniversalTx(event *store.Event, log *types.Log, dataOffset uint64, pay payload.RevertFundRecipient = ethcommon.BytesToAddress(w[12:32]).Hex() } - // txType (Word 4) - if w := readWord(data, 4); w != nil { - payload.TxType = uint(new(big.Int).SetBytes(w).Uint64()) - } + // txType (Word 4). Always present: the caller rejects anything shorter than + // five words, which is what stops this being left at 0 and read as GAS. + payload.TxType = uint(new(big.Int).SetBytes(readWord(data, 4)).Uint64()) // signatureData (Word 5 offset) if w := readWord(data, 5); w != nil { @@ -304,4 +311,5 @@ func parseUniversalTx(event *store.Event, log *types.Log, dataOffset uint64, pay } finalizeEvent(event, payload, logger) + return nil } diff --git a/universalClient/chains/evm/event_parser_test.go b/universalClient/chains/evm/event_parser_test.go index d3a71e798..4fb4c5401 100644 --- a/universalClient/chains/evm/event_parser_test.go +++ b/universalClient/chains/evm/event_parser_test.go @@ -183,7 +183,10 @@ func TestParseEventData(t *testing.T) { assert.NotNil(t, event.EventData) }) - t.Run("handles missing data gracefully", func(t *testing.T) { + // A log too short to carry txType is discarded rather than stored with the + // field left at 0, which is GAS on the wire and routes to a different + // account than FUNDS does. + t.Run("discards a log with no data", func(t *testing.T) { log := &types.Log{ Topics: []ethcommon.Hash{ ethcommon.HexToHash("0x1234"), @@ -193,9 +196,29 @@ func TestParseEventData(t *testing.T) { Data: []byte{}, // Empty data } - event := ParseEvent(log, EventTypeSendFunds, config.Chain, logger) - // Should still create event but with minimal data - require.NotNil(t, event) + assert.Nil(t, ParseEvent(log, EventTypeSendFunds, config.Chain, logger)) + }) + + t.Run("discards a log one word short of txType", func(t *testing.T) { + log := &types.Log{ + Topics: []ethcommon.Hash{ + ethcommon.HexToHash("0x1234"), + ethcommon.HexToHash("0x000000000000000000000000742d35cc6634c0532925a3b844bc9e7595f0beb7"), + ethcommon.HexToHash("0x000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7"), + }, + Data: make([]byte, 32*4), // words 0..3 present, txType (word 4) missing + } + + assert.Nil(t, ParseEvent(log, EventTypeSendFunds, config.Chain, logger)) + }) + + t.Run("discards a log without the indexed fields", func(t *testing.T) { + log := &types.Log{ + Topics: []ethcommon.Hash{ethcommon.HexToHash("0x1234")}, + Data: make([]byte, 32*8), + } + + assert.Nil(t, ParseEvent(log, EventTypeSendFunds, config.Chain, logger)) }) } diff --git a/universalClient/chains/svm/event_parser.go b/universalClient/chains/svm/event_parser.go index 4c25625dc..9df446175 100644 --- a/universalClient/chains/svm/event_parser.go +++ b/universalClient/chains/svm/event_parser.go @@ -93,8 +93,15 @@ func parseSendFundsEvent(log string, signature string, slot uint64, logIndex uin ExpiryBlockHeight: 0, // Will be set based on confirmation type if needed } - // Parse event data from this log - parseUniversalTxEvent(event, decoded, logIndex, chainID, logger) + // Parse event data from this log. A malformed event is dropped rather than + // stored half-decoded: the zero values it would carry are not neutral. + if err := parseUniversalTxEvent(event, decoded, logIndex, chainID, logger); err != nil { + logger.Warn(). + Err(err). + Str("event_id", eventID). + Msg("discarding malformed UniversalTx event") + return nil + } return event } @@ -201,15 +208,11 @@ func parseOutboundObservationEvent(log string, signature string, slot uint64, lo // parseUniversalTxEvent extracts specific data from a single log event // For TxWithFunds events, it JSON-marshals the decoded fields into event.EventData. -func parseUniversalTxEvent(event *store.Event, decoded []byte, logIndex uint, chainID string, logger zerolog.Logger) { - // Parse the TxWithFunds event +func parseUniversalTxEvent(event *store.Event, decoded []byte, logIndex uint, chainID string, logger zerolog.Logger) error { + // Parse the UniversalTx event payload, err := decodeUniversalTxEvent(decoded, logger) if err != nil { - logger.Warn(). - Err(err). - Uint("log_index", logIndex). - Msg("failed to decode TxWithFunds event") - return + return fmt.Errorf("decode UniversalTx event: %w", err) } // Set source chain and log index @@ -217,13 +220,11 @@ func parseUniversalTxEvent(event *store.Event, decoded []byte, logIndex uint, ch payload.LogIndex = logIndex // Marshal and store into event.EventData - if b, err := json.Marshal(payload); err == nil { - event.EventData = b - } else { - logger.Warn(). - Err(err). - Msg("failed to marshal universal tx payload") + b, err := json.Marshal(payload) + if err != nil { + return fmt.Errorf("marshal universal tx payload: %w", err) } + event.EventData = b // if TxType is 0 or 1, use FAST else use STANDARD if payload.TxType == 0 || payload.TxType == 1 { @@ -231,16 +232,20 @@ func parseUniversalTxEvent(event *store.Event, decoded []byte, logIndex uint, ch } else { event.ConfirmationType = store.ConfirmationStandard } + + return nil } -// decodeUniversalTxEvent decodes a TxWithFunds event +// decodeUniversalTxEvent decodes the gateway's Borsh-encoded UniversalTx event: +// +// sender 32, recipient 20, token 32, amount u64, payload (u32 len + bytes), +// revert_recipient 32, tx_type 1, signature_data (u32 len + bytes), from_cea 1 +// +// Every field through signature_data is required. A truncated event is rejected +// rather than returned half-filled, since the zero values are not neutral: +// tx_type 0 is GAS, which routes funds to a different account than FUNDS does. +// Only from_cea is optional, defaulting to false as its absence cannot misroute. func decodeUniversalTxEvent(data []byte, logger zerolog.Logger) (*common.UniversalTx, error) { - if len(data) < 120 { - logger.Warn(). - Int("data_len", len(data)). - Msg("data might be too short for complete TxWithFunds event") - } - offset := 8 payload := &common.UniversalTx{} @@ -286,19 +291,14 @@ func decodeUniversalTxEvent(data []byte, logger zerolog.Logger) (*common.Univers // Parse data field length (4 bytes) if len(data) < offset+4 { - logger.Warn().Msg("not enough data for data field length") - return payload, nil + return nil, fmt.Errorf("not enough data for data field length") } dataLen := binary.LittleEndian.Uint32(data[offset : offset+4]) offset += 4 // Parse data field if len(data) < offset+int(dataLen) { - logger.Warn(). - Uint32("expected_len", dataLen). - Int("available", len(data)-offset). - Msg("not enough data for data field") - return payload, nil + return nil, fmt.Errorf("data field claims %d bytes, only %d available", dataLen, len(data)-offset) } if dataLen > 0 { dataField := data[offset : offset+int(dataLen)] @@ -308,18 +308,19 @@ func decodeUniversalTxEvent(data []byte, logger zerolog.Logger) (*common.Univers // Parse revert_recipient (Pubkey) if len(data) < offset+32 { - logger.Warn().Msg("not enough data for revert recipient") - return payload, nil + return nil, fmt.Errorf("not enough data for revert recipient") } revertRecipient := solana.PublicKey(data[offset : offset+32]) payload.RevertFundRecipient = revertRecipient.String() offset += 32 // Parse tx_type (TxType enum) + // + // No default. Wire 0 is GAS, which credits the sender UEA via swap rather + // than depositing to the recipient, and also selects fast confirmation. + // Guessing it on a truncated event silently changes where the money goes. if len(data) <= offset { - logger.Warn().Msg("not enough data for tx_type, defaulting to Funds") - payload.TxType = uint(0) - return payload, nil + return nil, fmt.Errorf("not enough data for tx_type") } txType := data[offset] payload.TxType = uint(txType) @@ -327,19 +328,14 @@ func decodeUniversalTxEvent(data []byte, logger zerolog.Logger) (*common.Univers // Parse signature data length (4 bytes) if len(data) < offset+4 { - logger.Warn().Msg("not enough data for signature length") - return payload, nil + return nil, fmt.Errorf("not enough data for signature length") } sigLen := binary.LittleEndian.Uint32(data[offset : offset+4]) offset += 4 remainingBytes := len(data) - offset if int(sigLen) > remainingBytes { - logger.Warn(). - Uint32("expected_len", sigLen). - Int("available", remainingBytes). - Msg("signature data length exceeds available data, skipping") - return payload, nil + return nil, fmt.Errorf("signature data claims %d bytes, only %d available", sigLen, remainingBytes) } if sigLen > 0 { @@ -369,4 +365,3 @@ func decodeUniversalTxEvent(data []byte, logger zerolog.Logger) (*common.Univers return payload, nil } - diff --git a/universalClient/chains/svm/event_parser_test.go b/universalClient/chains/svm/event_parser_test.go index da11ce0c8..e8630f401 100644 --- a/universalClient/chains/svm/event_parser_test.go +++ b/universalClient/chains/svm/event_parser_test.go @@ -23,18 +23,19 @@ func nopLogger() zerolog.Logger { // parseSendFundsEvent / decodeUniversalTxEvent call. // // Layout (Borsh): -// discriminator 8 bytes -// sender 32 bytes (Pubkey) -// recipient 20 bytes (byte20) -// bridge_token 32 bytes (Pubkey) -// bridge_amount 8 bytes (u64 LE) -// data_len 4 bytes (u32 LE) -// data variable -// revert_recip 32 bytes (Pubkey) -// tx_type 1 byte -// sig_len 4 bytes (u32 LE) -// sig_data variable -// fromCEA 1 byte +// +// discriminator 8 bytes +// sender 32 bytes (Pubkey) +// recipient 20 bytes (byte20) +// bridge_token 32 bytes (Pubkey) +// bridge_amount 8 bytes (u64 LE) +// data_len 4 bytes (u32 LE) +// data variable +// revert_recip 32 bytes (Pubkey) +// tx_type 1 byte +// sig_len 4 bytes (u32 LE) +// sig_data variable +// fromCEA 1 byte func buildSendFundsPayload( sender [32]byte, recipient [20]byte, @@ -116,12 +117,12 @@ func TestBase58ToHex(t *testing.T) { }, { name: "known base58 value", - input: "1", // base58 "1" decodes to a single 0x00 byte + input: "1", // base58 "1" decodes to a single 0x00 byte want: "0x00", }, { name: "known base58 multi-byte", - input: "2g", // base58 "2g" decodes to 0x61 + input: "2g", // base58 "2g" decodes to 0x61 want: "0x61", }, { @@ -345,22 +346,30 @@ func TestParseSendFundsEvent_TruncatedData(t *testing.T) { chainID := "solana:devnet" sig := "truncSig" - t.Run("data too short for sender returns event with nil EventData", func(t *testing.T) { + // A truncated event is discarded rather than stored. Every field it fails to + // reach would otherwise be left at its zero value, and tx_type 0 is GAS, + // which credits the sender UEA instead of depositing to the recipient. + t.Run("data too short for sender is discarded", func(t *testing.T) { // Only discriminator (8 bytes), no sender data := make([]byte, 8) event := ParseEvent(wrapAsLog(data), sig, 1, 0, EventTypeSendFunds, chainID, logger) - require.NotNil(t, event) - // Event is created but parseUniversalTxEvent will fail to decode, - // so EventData may be nil - assert.Equal(t, store.EventTypeInbound, event.Type) + assert.Nil(t, event) }) - t.Run("data truncated after sender still returns event", func(t *testing.T) { + t.Run("data truncated after sender is discarded", func(t *testing.T) { // 8 disc + 32 sender = 40 bytes, missing recipient data := make([]byte, 40) event := ParseEvent(wrapAsLog(data), sig, 1, 0, EventTypeSendFunds, chainID, logger) - require.NotNil(t, event) - assert.Equal(t, store.EventTypeInbound, event.Type) + assert.Nil(t, event) + }) + + t.Run("truncated one byte before tx_type is discarded", func(t *testing.T) { + // Everything through revert_recipient, then nothing. This is the exact + // shape that used to decode as TxType 0 and route to GAS. + data := make([]byte, 136) + binary.LittleEndian.PutUint32(data[100:104], 0) + event := ParseEvent(wrapAsLog(data), sig, 1, 0, EventTypeSendFunds, chainID, logger) + assert.Nil(t, event) }) } @@ -582,42 +591,186 @@ func TestDecodeUniversalTxEvent_PartialData(t *testing.T) { assert.Contains(t, err.Error(), "bridge_amount") }) - t.Run("returns partial result when no data field length", func(t *testing.T) { + // Everything through signature_data is required. Returning a partial result + // leaves tx_type at 0, which is GAS on the wire, so a truncated FUNDS + // transfer would be credited to the sender UEA instead of the recipient. + + t.Run("returns error when no data field length", func(t *testing.T) { // 8 + 32 + 20 + 32 + 8 = 100, no data_len data := make([]byte, 100) binary.LittleEndian.PutUint64(data[92:100], 777) - result, err := decodeUniversalTxEvent(data, logger) - require.NoError(t, err) - assert.Equal(t, "777", result.Amount) + _, err := decodeUniversalTxEvent(data, logger) + require.Error(t, err) + assert.Contains(t, err.Error(), "data field length") }) - t.Run("returns partial result when data field exceeds available bytes", func(t *testing.T) { + t.Run("returns error when data field exceeds available bytes", func(t *testing.T) { // 8 + 32 + 20 + 32 + 8 + 4 = 104 data := make([]byte, 104) binary.LittleEndian.PutUint64(data[92:100], 555) binary.LittleEndian.PutUint32(data[100:104], 999) // claims 999 bytes of payload - result, err := decodeUniversalTxEvent(data, logger) - require.NoError(t, err) - assert.Equal(t, "555", result.Amount) - assert.Empty(t, result.RawPayload) // not enough data, so payload is skipped + _, err := decodeUniversalTxEvent(data, logger) + require.Error(t, err) + assert.Contains(t, err.Error(), "claims 999 bytes") }) - t.Run("returns partial result when missing revert recipient", func(t *testing.T) { + t.Run("returns error when missing revert recipient", func(t *testing.T) { // 8 + 32 + 20 + 32 + 8 + 4(data_len=0) = 104 data := make([]byte, 104) binary.LittleEndian.PutUint32(data[100:104], 0) // 0 length payload - result, err := decodeUniversalTxEvent(data, logger) - require.NoError(t, err) - assert.Empty(t, result.RevertFundRecipient) + _, err := decodeUniversalTxEvent(data, logger) + require.Error(t, err) + assert.Contains(t, err.Error(), "revert recipient") }) - t.Run("returns partial result when missing tx_type", func(t *testing.T) { + t.Run("returns error when missing tx_type rather than defaulting to GAS", func(t *testing.T) { // 8 + 32 + 20 + 32 + 8 + 4(data_len=0) + 32(revert) = 136 data := make([]byte, 136) binary.LittleEndian.PutUint32(data[100:104], 0) + _, err := decodeUniversalTxEvent(data, logger) + require.Error(t, err) + assert.Contains(t, err.Error(), "tx_type") + }) + + t.Run("returns error when missing signature length", func(t *testing.T) { + // 136 + 1(tx_type) = 137, no signature length + data := make([]byte, 137) + binary.LittleEndian.PutUint32(data[100:104], 0) + data[136] = 2 // Funds + _, err := decodeUniversalTxEvent(data, logger) + require.Error(t, err) + assert.Contains(t, err.Error(), "signature length") + }) + + t.Run("returns error when signature data exceeds available bytes", func(t *testing.T) { + data := make([]byte, 141) + binary.LittleEndian.PutUint32(data[100:104], 0) + data[136] = 2 + binary.LittleEndian.PutUint32(data[137:141], 500) + _, err := decodeUniversalTxEvent(data, logger) + require.Error(t, err) + assert.Contains(t, err.Error(), "claims 500 bytes") + }) + + t.Run("from_cea stays optional and defaults to false", func(t *testing.T) { + // 141 bytes: complete through signature_data, no from_cea byte. + data := make([]byte, 141) + binary.LittleEndian.PutUint32(data[100:104], 0) + data[136] = 2 // Funds + binary.LittleEndian.PutUint32(data[137:141], 0) result, err := decodeUniversalTxEvent(data, logger) require.NoError(t, err) - // tx_type defaults to 0 when missing - assert.Equal(t, uint(0), result.TxType) + assert.Equal(t, uint(2), result.TxType) + assert.False(t, result.FromCEA) }) } + +// Real UniversalTx events captured from the deployed devnet gateway +// CFVSincHYbETh2k7w6u1ENEkjbSLtveRCEBupKidw2VS. They pin the decoder to what +// the chain actually emits rather than to a hand-built fixture. +// +// Layout, matching the gateway on pc20-3rd-iteration: +// +// disc 8, sender 32, recipient 20, token 32, amount u64, +// payload (u32 len + bytes), revert_recipient 32, tx_type 1, +// signature_data (u32 len + bytes), from_cea 1 = 142 bytes when both vecs are empty +var devnetUniversalTxEvents = []struct { + name string + hex string + wantAmount string + wantTxType uint + wantFromCEA bool + wantConfirmDep string +}{ + { + name: "3000000 lamports, Funds", + hex: "6c9ad829b5ea1d7c5824d1bda3f79e54416ae3d2ec8d8a7456ae9a3e8a85e2f43e3bd20f25a39e5107a26674effcfbef4ee6cc6e8a00dc54801d83d90000000000000000000000000000000000000000000000000000000000000000c0c62d0000000000000000005824d1bda3f79e54416ae3d2ec8d8a7456ae9a3e8a85e2f43e3bd20f25a39e51020000000001", + wantAmount: "3000000", + wantTxType: 2, + wantFromCEA: true, + wantConfirmDep: store.ConfirmationStandard, + }, + { + name: "8000 lamports, Funds", + hex: "6c9ad829b5ea1d7cdc84c8dd7c695f0ed78f3507fd867827812dcd9ccbba61ca9a16d899b6f5ac665c70c864cf1adfb04a0e107ffa248ba3600eab8dcbcae9e66452fe98abcf0fa51e557fc8d671c7fc6ce83c05f92992a3d2bf1932401f00000000000000000000dc84c8dd7c695f0ed78f3507fd867827812dcd9ccbba61ca9a16d899b6f5ac66020000000001", + wantAmount: "8000", + wantTxType: 2, + wantFromCEA: true, + wantConfirmDep: store.ConfirmationStandard, + }, + { + name: "5000000 lamports, Funds", + hex: "6c9ad829b5ea1d7cdc84c8dd7c695f0ed78f3507fd867827812dcd9ccbba61ca9a16d899b6f5ac665c70c864cf1adfb04a0e107ffa248ba3600eab8d0000000000000000000000000000000000000000000000000000000000000000404b4c000000000000000000dc84c8dd7c695f0ed78f3507fd867827812dcd9ccbba61ca9a16d899b6f5ac66020000000001", + wantAmount: "5000000", + wantTxType: 2, + wantFromCEA: true, + wantConfirmDep: store.ConfirmationStandard, + }, +} + +func TestDecodeUniversalTxEvent_RealDevnetEvents(t *testing.T) { + logger := nopLogger() + + for _, tc := range devnetUniversalTxEvents { + t.Run(tc.name, func(t *testing.T) { + data, err := hex.DecodeString(tc.hex) + require.NoError(t, err) + require.Len(t, data, 142, "captured event is not the deployed layout") + + got, err := decodeUniversalTxEvent(data, logger) + require.NoError(t, err) + + assert.Equal(t, tc.wantAmount, got.Amount) + assert.Equal(t, tc.wantTxType, got.TxType, "tx_type must survive decoding, not be defaulted") + assert.Equal(t, tc.wantFromCEA, got.FromCEA) + assert.NotEmpty(t, got.Sender) + assert.NotEmpty(t, got.Recipient) + assert.NotEmpty(t, got.RevertFundRecipient) + }) + } +} + +// FUNDS must take the slower confirmation path. The old default of 0 selected +// FAST as well as routing to GAS, so a high value transfer lost finality too. +func TestParseSendFundsEvent_RealDevnetEventConfirmation(t *testing.T) { + logger := nopLogger() + + for _, tc := range devnetUniversalTxEvents { + t.Run(tc.name, func(t *testing.T) { + data, err := hex.DecodeString(tc.hex) + require.NoError(t, err) + log := "Program data: " + base64.StdEncoding.EncodeToString(data) + + event := ParseEvent(log, "devnetSig", 1, 0, EventTypeSendFunds, "solana:devnet", logger) + require.NotNil(t, event) + require.NotNil(t, event.EventData) + + assert.Equal(t, store.EventTypeInbound, event.Type) + assert.Equal(t, tc.wantConfirmDep, event.ConfirmationType) + }) + } +} + +// Truncating a real event anywhere past bridge_amount must be rejected. Before +// the fix each of these decoded successfully with TxType left at 0. +func TestDecodeUniversalTxEvent_TruncatedRealEventIsRejected(t *testing.T) { + logger := nopLogger() + + full, err := hex.DecodeString(devnetUniversalTxEvents[0].hex) + require.NoError(t, err) + + // 100 is the end of bridge_amount; 142 is the whole event. from_cea is the + // only optional field, so 141 is the shortest valid length. + for n := 100; n < 141; n++ { + got, err := decodeUniversalTxEvent(full[:n], logger) + require.Error(t, err, "%d-byte truncation was accepted", n) + assert.Nil(t, got) + } + + // The two valid lengths still decode, and both carry the real tx_type. + for _, n := range []int{141, 142} { + got, err := decodeUniversalTxEvent(full[:n], logger) + require.NoError(t, err, "%d-byte event was rejected", n) + assert.Equal(t, uint(2), got.TxType) + } +} From bb73afb83fcd1cc8f96e252062292e797fafa7e3 Mon Sep 17 00:00:00 2001 From: Aman Gupta Date: Tue, 25 Aug 2026 13:44:53 +0530 Subject: [PATCH 22/60] fix: F-2026-18827 | [Dual Defense] Normal EVM Outbound Resolution Uses Current TSS Address After Key Rotation (#314) * fix: check outbound nonce against the key that signed, not the current TSS (F-2026-18827) * test: add the broadcaster rotation regression and restore a misplaced doc comment --- universalClient/tss/tss.go | 9 - .../tss/txbroadcaster/broadcaster.go | 3 - .../tss/txbroadcaster/broadcaster_test.go | 165 +++++++++++++----- universalClient/tss/txbroadcaster/evm.go | 16 +- universalClient/tss/txflow/parse.go | 36 +++- universalClient/tss/txflow/parse_test.go | 102 +++++++++++ universalClient/tss/txresolver/evm.go | 25 ++- universalClient/tss/txresolver/resolver.go | 3 - .../tss/txresolver/resolver_test.go | 116 +++++++----- 9 files changed, 346 insertions(+), 129 deletions(-) create mode 100644 universalClient/tss/txflow/parse_test.go diff --git a/universalClient/tss/tss.go b/universalClient/tss/tss.go index 215f5dfb3..525c2d8d4 100644 --- a/universalClient/tss/tss.go +++ b/universalClient/tss/tss.go @@ -239,20 +239,12 @@ func NewNode(ctx context.Context, cfg Config) (*Node, error) { registeredPeers: make(map[string]bool), } - getTSSAddress := func(ctx context.Context) (string, error) { - if node.coordinator == nil { - return "", fmt.Errorf("coordinator not initialized") - } - return node.coordinator.GetTSSAddress(ctx) - } - node.txResolver = txresolver.NewResolver(txresolver.Config{ EventStore: evtStore, Chains: cfg.Chains, PushSigner: cfg.PushSigner, CheckInterval: sessionExpiryCheckInterval, Logger: logger, - GetTSSAddress: getTSSAddress, }) node.txBroadcaster = txbroadcaster.NewBroadcaster(txbroadcaster.Config{ @@ -260,7 +252,6 @@ func NewNode(ctx context.Context, cfg Config) (*Node, error) { Chains: cfg.Chains, CheckInterval: sessionExpiryCheckInterval, Logger: logger, - GetTSSAddress: getTSSAddress, }) node.expirySweeper = expirysweeper.NewSweeper(expirysweeper.Config{ diff --git a/universalClient/tss/txbroadcaster/broadcaster.go b/universalClient/tss/txbroadcaster/broadcaster.go index b5b99c4c1..759758d98 100644 --- a/universalClient/tss/txbroadcaster/broadcaster.go +++ b/universalClient/tss/txbroadcaster/broadcaster.go @@ -18,7 +18,6 @@ type Config struct { Chains *chains.Chains CheckInterval time.Duration Logger zerolog.Logger - GetTSSAddress func(ctx context.Context) (string, error) } type Broadcaster struct { @@ -26,7 +25,6 @@ type Broadcaster struct { chains *chains.Chains checkInterval time.Duration logger zerolog.Logger - getTSSAddress func(ctx context.Context) (string, error) } func NewBroadcaster(cfg Config) *Broadcaster { @@ -39,7 +37,6 @@ func NewBroadcaster(cfg Config) *Broadcaster { chains: cfg.Chains, checkInterval: interval, logger: cfg.Logger.With().Str("component", "txbroadcaster").Logger(), - getTSSAddress: cfg.GetTSSAddress, } } diff --git a/universalClient/tss/txbroadcaster/broadcaster_test.go b/universalClient/tss/txbroadcaster/broadcaster_test.go index 8ce0abca6..2fb354772 100644 --- a/universalClient/tss/txbroadcaster/broadcaster_test.go +++ b/universalClient/tss/txbroadcaster/broadcaster_test.go @@ -11,6 +11,7 @@ import ( "time" "unsafe" + "github.com/ethereum/go-ethereum/crypto" "github.com/rs/zerolog" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -25,6 +26,7 @@ import ( "github.com/pushchain/push-chain-node/universalClient/chains/common" "github.com/pushchain/push-chain-node/universalClient/config" "github.com/pushchain/push-chain-node/universalClient/store" + "github.com/pushchain/push-chain-node/universalClient/tss/coordinator" "github.com/pushchain/push-chain-node/universalClient/tss/eventstore" "github.com/pushchain/push-chain-node/universalClient/tss/txflow" ) @@ -117,10 +119,27 @@ func newTestChains(t *testing.T, chainID string, vmType uregistrytypes.VmType, c return c } +// testBroadcastSigningKeyHex signs the outbound fixtures. The broadcaster derives +// the nonce domain from the signature, so it has to be a real one. +const testBroadcastSigningKeyHex = "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318" + +// testBroadcastSigner is the address recovered from those fixtures, i.e. the +// nonce domain the broadcaster must query. +var testBroadcastSigner = func() string { + key, _ := crypto.HexToECDSA(testBroadcastSigningKeyHex) + addr, _ := coordinator.DeriveEVMAddressFromPubkey(hex.EncodeToString(crypto.CompressPubkey(&key.PublicKey))) + return addr +}() + func makeSignedOutboundData(t *testing.T, destChain string, nonce uint64) []byte { t.Helper() - sig := hex.EncodeToString(make([]byte, 64)) - hash := hex.EncodeToString(make([]byte, 32)) + key, err := crypto.HexToECDSA(testBroadcastSigningKeyHex) + require.NoError(t, err) + hashBytes := crypto.Keccak256([]byte("test outbound signing hash")) + sigBytes, err := crypto.Sign(hashBytes, key) + require.NoError(t, err) + sig := hex.EncodeToString(sigBytes) + hash := hex.EncodeToString(hashBytes) data := txflow.SignedOutboundData{ OutboundCreatedEvent: uexecutortypes.OutboundCreatedEvent{ TxID: "tx-123", @@ -197,17 +216,53 @@ func getEvent(t *testing.T, db *gorm.DB, eventID string) store.Event { return ev } -func newBroadcaster(evtStore *eventstore.Store, ch *chains.Chains, tssAddr string) *Broadcaster { - getTSSAddr := func(ctx context.Context) (string, error) { return tssAddr, nil } +func newBroadcaster(evtStore *eventstore.Store, ch *chains.Chains) *Broadcaster { return NewBroadcaster(Config{ EventStore: evtStore, Chains: ch, CheckInterval: 0, // uses default, doesn't matter for direct calls Logger: zerolog.Nop(), - GetTSSAddress: getTSSAddr, }) } +// The rotation case, mirroring the resolver. The broadcaster must query the +// nonce of the key that signed, not whichever key is current: after a rotation +// they are separate EOAs, and reading the successor's sequence would report a +// still-free nonce as consumed. +func TestEVM_BroadcastError_AfterRotation_ChecksSigningKeyNonce(t *testing.T) { + evtStore, db := setupTestDB(t) + builder := &mockTxBuilder{} + client := &mockChainClient{builder: builder} + ch := newTestChains(t, "eip155:1", uregistrytypes.VmType_EVM, client) + + rotatedKey, err := crypto.HexToECDSA("8a1f9a8f9c8b7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3d2e") + require.NoError(t, err) + rotatedSigner, err := coordinator.DeriveEVMAddressFromPubkey( + hex.EncodeToString(crypto.CompressPubkey(&rotatedKey.PublicKey))) + require.NoError(t, err) + require.NotEqual(t, testBroadcastSigner, rotatedSigner) + + // Signed under the original key at nonce 5. + insertSignedEvent(t, db, "ev-rotated", "eip155:1", 5) + + builder.On("BroadcastOutboundSigningRequest", mock.Anything, mock.Anything, mock.Anything, mock.Anything). + Return("0xabc", fmt.Errorf("already known")) + builder.On("VerifyBroadcastedTx", mock.Anything, "0xabc"). + Return(false, uint64(0), uint64(0), uint8(0), nil) + // The signing key's nonce 5 is still free, so the tx can still mine. + builder.On("GetNextNonce", mock.Anything, testBroadcastSigner, true).Return(uint64(5), nil) + // The rotated key has moved past it. Reading this domain is the bug. + builder.On("GetNextNonce", mock.Anything, rotatedSigner, true).Return(uint64(42), nil) + + b := newBroadcaster(evtStore, ch) + b.processSigned(context.Background()) + + builder.AssertCalled(t, "GetNextNonce", mock.Anything, testBroadcastSigner, true) + builder.AssertNotCalled(t, "GetNextNonce", mock.Anything, rotatedSigner, true) + require.Equal(t, store.StatusSigned, getEvent(t, db, "ev-rotated").Status, + "signing key nonce still free means retry, not a consumed-nonce transition") +} + func TestEVM_BroadcastError_NonceConsumed_MarksBroadcasted(t *testing.T) { // Broadcast fails with txHash, finalized nonce shows consumed → BROADCASTED. evtStore, db := setupTestDB(t) @@ -222,9 +277,9 @@ func TestEVM_BroadcastError_NonceConsumed_MarksBroadcasted(t *testing.T) { // VerifyBroadcastedTx=not found → fall through to the nonce-consumed check. builder.On("VerifyBroadcastedTx", mock.Anything, "0xabc"). Return(false, uint64(0), uint64(0), uint8(0), nil) - builder.On("GetNextNonce", mock.Anything, "0xTSS", true).Return(uint64(10), nil) + builder.On("GetNextNonce", mock.Anything, testBroadcastSigner, true).Return(uint64(10), nil) - b := newBroadcaster(evtStore, ch, "0xTSS") + b := newBroadcaster(evtStore, ch) b.processSigned(context.Background()) ev := getEvent(t, db, "ev-1") @@ -248,7 +303,7 @@ func TestEVM_BroadcastError_TxOnChain_MarksBroadcasted(t *testing.T) { builder.On("VerifyBroadcastedTx", mock.Anything, "0xabc"). Return(true, uint64(100), uint64(3), uint8(1), nil) - b := newBroadcaster(evtStore, ch, "0xTSS") + b := newBroadcaster(evtStore, ch) b.processSigned(context.Background()) ev := getEvent(t, db, "ev-1") @@ -269,7 +324,7 @@ func TestEVM_BroadcastSuccess_MarksBroadcasted(t *testing.T) { builder.On("BroadcastOutboundSigningRequest", mock.Anything, mock.Anything, mock.Anything, mock.Anything). Return("0xabc123", nil) - b := newBroadcaster(evtStore, ch, "0xTSS") + b := newBroadcaster(evtStore, ch) b.processSigned(context.Background()) ev := getEvent(t, db, "ev-1") @@ -291,7 +346,7 @@ func TestEVM_BroadcastAssemblyFails_StaysSigned(t *testing.T) { builder.On("BroadcastOutboundSigningRequest", mock.Anything, mock.Anything, mock.Anything, mock.Anything). Return("", fmt.Errorf("connection refused")) - b := newBroadcaster(evtStore, ch, "0xTSS") + b := newBroadcaster(evtStore, ch) b.processSigned(context.Background()) ev := getEvent(t, db, "ev-1") @@ -312,44 +367,61 @@ func TestEVM_BroadcastFails_WithTxHash_NonceNotConsumed_StaysSigned(t *testing.T Return("0xabc", fmt.Errorf("gas too low")) builder.On("VerifyBroadcastedTx", mock.Anything, "0xabc"). Return(false, uint64(0), uint64(0), uint8(0), nil) - builder.On("GetNextNonce", mock.Anything, "0xTSS", true).Return(uint64(5), nil) + builder.On("GetNextNonce", mock.Anything, testBroadcastSigner, true).Return(uint64(5), nil) - b := newBroadcaster(evtStore, ch, "0xTSS") + b := newBroadcaster(evtStore, ch) b.processSigned(context.Background()) ev := getEvent(t, db, "ev-1") require.Equal(t, store.StatusSigned, ev.Status) // stays SIGNED } -func TestEVM_GetTSSAddressNil_UsesEmptyAddress(t *testing.T) { - // getTSSAddress is nil → empty string passed to GetNextNonce on broadcast error. +// An unrecoverable signer leaves no nonce domain to query. The broadcaster must +// defer rather than fall back to another address, which previously meant asking +// for the nonce of the empty string. +func TestEVM_SignerUnrecoverable_StaysSigned(t *testing.T) { evtStore, db := setupTestDB(t) builder := &mockTxBuilder{} client := &mockChainClient{builder: builder} ch := newTestChains(t, "eip155:1", uregistrytypes.VmType_EVM, client) - insertSignedEvent(t, db, "ev-1", "eip155:1", 5) + insertSignedEventUnsigned(t, db, "ev-1", "eip155:1", 5) builder.On("BroadcastOutboundSigningRequest", mock.Anything, mock.Anything, mock.Anything, mock.Anything). Return("0xabc", fmt.Errorf("already known")) builder.On("VerifyBroadcastedTx", mock.Anything, "0xabc"). Return(false, uint64(0), uint64(0), uint8(0), nil) - // Expect empty address since GetTSSAddress is nil. - builder.On("GetNextNonce", mock.Anything, "", true).Return(uint64(10), nil) - b := NewBroadcaster(Config{ - EventStore: evtStore, - Chains: ch, - Logger: zerolog.Nop(), - GetTSSAddress: nil, // explicitly nil - }) + b := newBroadcaster(evtStore, ch) b.processSigned(context.Background()) - ev := getEvent(t, db, "ev-1") - require.Equal(t, store.StatusBroadcasted, ev.Status) - builder.AssertCalled(t, "GetNextNonce", mock.Anything, "", true) + require.Equal(t, store.StatusSigned, getEvent(t, db, "ev-1").Status) + builder.AssertNotCalled(t, "GetNextNonce", mock.Anything, mock.Anything, mock.Anything) } +// insertSignedEventUnsigned inserts a SIGNED outbound whose signature cannot be +// recovered, standing in for a legacy or malformed payload. +func insertSignedEventUnsigned(t *testing.T, db *gorm.DB, eventID, destChain string, nonce uint64) { + t.Helper() + data := txflow.SignedOutboundData{ + OutboundCreatedEvent: uexecutortypes.OutboundCreatedEvent{ + TxID: "tx-123", UniversalTxId: "utx-456", DestinationChain: destChain, + Recipient: "0xRecipient", Amount: "1000000", + }, + SigningData: &txflow.SigningData{ + Signature: hex.EncodeToString(make([]byte, 64)), + SigningHash: hex.EncodeToString(make([]byte, 32)), + Nonce: nonce, + }, + } + b, err := json.Marshal(data) + require.NoError(t, err) + require.NoError(t, db.Create(&store.Event{ + EventID: eventID, BlockHeight: 100, ExpiryBlockHeight: 99999, + Type: "SIGN_OUTBOUND", ConfirmationType: "STANDARD", + Status: store.StatusSigned, EventData: b, + }).Error) +} func TestSVM_DeadlineZero_ClusterConfirmsExpiry_MarksBroadcasted(t *testing.T) { // Legacy event without a signing deadline. `now > 0` enters the deadline // branch and any fresh cluster time (>> 0) trips the expiry case → @@ -362,7 +434,7 @@ func TestSVM_DeadlineZero_ClusterConfirmsExpiry_MarksBroadcasted(t *testing.T) { insertSignedEvent(t, db, "ev-1", "solana:mainnet", 0) builder.On("IsAlreadyExecuted", mock.Anything, "tx-123").Return(false, time.Now().Unix(), nil) - b := newBroadcaster(evtStore, ch, "") + b := newBroadcaster(evtStore, ch) b.processSigned(context.Background()) ev := getEvent(t, db, "ev-1") @@ -385,7 +457,7 @@ func TestSVM_BroadcastSuccess_MarksBroadcasted(t *testing.T) { builder.On("BroadcastOutboundSigningRequest", mock.Anything, mock.Anything, mock.Anything, mock.Anything). Return("solTxSig123", nil) - b := newBroadcaster(evtStore, ch, "") + b := newBroadcaster(evtStore, ch) b.processSigned(context.Background()) ev := getEvent(t, db, "ev-1") @@ -407,7 +479,7 @@ func TestSVM_BroadcastFails_PDAExists_MarksBroadcasted(t *testing.T) { Return("", fmt.Errorf("tx simulation failed: account already exists")) builder.On("IsAlreadyExecuted", mock.Anything, "tx-123").Return(true, int64(0), nil) - b := newBroadcaster(evtStore, ch, "") + b := newBroadcaster(evtStore, ch) b.processSigned(context.Background()) ev := getEvent(t, db, "ev-1") @@ -429,7 +501,7 @@ func TestSVM_BroadcastFails_BeforeDeadline_StaysSigned(t *testing.T) { Return("", fmt.Errorf("simulation failed: invalid instruction")) builder.On("IsAlreadyExecuted", mock.Anything, "tx-123").Return(false, int64(0), nil) - b := newBroadcaster(evtStore, ch, "") + b := newBroadcaster(evtStore, ch) b.processSigned(context.Background()) ev := getEvent(t, db, "ev-1") @@ -448,7 +520,7 @@ func TestSVM_BroadcastFails_PastDeadline_MarksBroadcastedForRevert(t *testing.T) // PDA absent, cluster time = now (fresh) and well past deadline → cluster-confirmed expiry. builder.On("IsAlreadyExecuted", mock.Anything, "tx-123").Return(false, time.Now().Unix(), nil) - b := newBroadcaster(evtStore, ch, "") + b := newBroadcaster(evtStore, ch) b.processSigned(context.Background()) ev := getEvent(t, db, "ev-1") @@ -468,7 +540,7 @@ func TestSVM_PastLocalDeadline_ExecutedByPeer_MarksBroadcasted(t *testing.T) { insertSignedSVMEventWithDeadline(t, db, "ev-1", "solana:mainnet", 0, time.Now().Unix()-3600) builder.On("IsAlreadyExecuted", mock.Anything, "tx-123").Return(true, time.Now().Unix(), nil) - b := newBroadcaster(evtStore, ch, "") + b := newBroadcaster(evtStore, ch) b.processSigned(context.Background()) ev := getEvent(t, db, "ev-1") @@ -494,7 +566,7 @@ func TestSVM_PastLocalDeadline_ClusterSaysStillInWindow_FallsThroughToBroadcast( builder.On("BroadcastOutboundSigningRequest", mock.Anything, mock.Anything, mock.Anything, mock.Anything). Return("tx-hash-ok", nil) - b := newBroadcaster(evtStore, ch, "") + b := newBroadcaster(evtStore, ch) b.processSigned(context.Background()) ev := getEvent(t, db, "ev-1") @@ -513,7 +585,7 @@ func TestSVM_PastLocalDeadline_RPCError_StaysSigned(t *testing.T) { insertSignedSVMEventWithDeadline(t, db, "ev-1", "solana:mainnet", 0, time.Now().Unix()-3600) builder.On("IsAlreadyExecuted", mock.Anything, "tx-123").Return(false, int64(0), fmt.Errorf("RPC down")) - b := newBroadcaster(evtStore, ch, "") + b := newBroadcaster(evtStore, ch) b.processSigned(context.Background()) ev := getEvent(t, db, "ev-1") @@ -535,7 +607,7 @@ func TestSVM_BroadcastFails_PDACheckFails_StaysSigned(t *testing.T) { Return("", fmt.Errorf("RPC timeout")) builder.On("IsAlreadyExecuted", mock.Anything, "tx-123").Return(false, int64(0), fmt.Errorf("RPC down")) - b := newBroadcaster(evtStore, ch, "") + b := newBroadcaster(evtStore, ch) b.processSigned(context.Background()) ev := getEvent(t, db, "ev-1") @@ -548,7 +620,7 @@ func TestProcessSigned_NoEvents_DoesNothing(t *testing.T) { client := &mockChainClient{builder: builder} ch := newTestChains(t, "eip155:1", uregistrytypes.VmType_EVM, client) - b := newBroadcaster(evtStore, ch, "0xTSS") + b := newBroadcaster(evtStore, ch) b.processSigned(context.Background()) // no panic, no calls builder.AssertNotCalled(t, "GetNextNonce", mock.Anything, mock.Anything, mock.Anything) @@ -556,7 +628,7 @@ func TestProcessSigned_NoEvents_DoesNothing(t *testing.T) { func TestProcessSigned_NilChains_DoesNothing(t *testing.T) { evtStore, _ := setupTestDB(t) - b := newBroadcaster(evtStore, nil, "") + b := newBroadcaster(evtStore, nil) b.processSigned(context.Background()) // should not panic } @@ -573,7 +645,7 @@ func TestProcessSigned_MultipleEvents(t *testing.T) { builder.On("BroadcastOutboundSigningRequest", mock.Anything, mock.Anything, mock.Anything, mock.Anything). Return("0xabc", nil) - b := newBroadcaster(evtStore, ch, "0xTSS") + b := newBroadcaster(evtStore, ch) b.processSigned(context.Background()) ev1 := getEvent(t, db, "ev-1") @@ -586,7 +658,7 @@ func TestMarkBroadcasted_FormatsCAIPTxHash(t *testing.T) { evtStore, db := setupTestDB(t) insertSignedEvent(t, db, "ev-1", "eip155:1", 5) - b := newBroadcaster(evtStore, nil, "") + b := newBroadcaster(evtStore, nil) ev := getEvent(t, db, "ev-1") b.markBroadcasted(&ev, "eip155:1", "0xdeadbeef") @@ -599,7 +671,7 @@ func TestMarkBroadcasted_EmptyTxHash(t *testing.T) { evtStore, db := setupTestDB(t) insertSignedEvent(t, db, "ev-1", "solana:mainnet", 3) - b := newBroadcaster(evtStore, nil, "") + b := newBroadcaster(evtStore, nil) ev := getEvent(t, db, "ev-1") b.markBroadcasted(&ev, "solana:mainnet", "") @@ -681,7 +753,7 @@ func TestFundMigrationEVM_BroadcastSuccess(t *testing.T) { mock.Anything). Return("0xmigrate123", nil) - b := newBroadcaster(evtStore, ch, "") + b := newBroadcaster(evtStore, ch) b.processSigned(context.Background()) ev := getEvent(t, db, "fm-1") @@ -721,7 +793,7 @@ func TestFundMigrationEVM_TSSFundMigrationAmountThreaded(t *testing.T) { mock.Anything). Return("0xmigrate777", nil) - b := newBroadcaster(evtStore, ch, "") + b := newBroadcaster(evtStore, ch) b.processSigned(context.Background()) ev := getEvent(t, db, "fm-transfer") @@ -743,7 +815,7 @@ func TestFundMigrationEVM_BroadcastFails_NonceConsumed(t *testing.T) { Return(false, uint64(0), uint64(0), uint8(0), nil) builder.On("GetNextNonce", mock.Anything, mock.Anything, true).Return(uint64(10), nil) - b := newBroadcaster(evtStore, ch, "") + b := newBroadcaster(evtStore, ch) b.processSigned(context.Background()) ev := getEvent(t, db, "fm-1") @@ -752,7 +824,7 @@ func TestFundMigrationEVM_BroadcastFails_NonceConsumed(t *testing.T) { func TestMarkBroadcasted_NonExistentEvent(t *testing.T) { evtStore, _ := setupTestDB(t) - b := newBroadcaster(evtStore, nil, "") + b := newBroadcaster(evtStore, nil) ev := &store.Event{EventID: "does-not-exist"} b.markBroadcasted(ev, "eip155:1", "0xdeadbeef") @@ -763,7 +835,7 @@ func TestMarkBroadcasted_SetsAllFields(t *testing.T) { evtStore, db := setupTestDB(t) insertSignedEvent(t, db, "ev-fields", "eip155:1", 5) - b := newBroadcaster(evtStore, nil, "") + b := newBroadcaster(evtStore, nil) ev := getEvent(t, db, "ev-fields") b.markBroadcasted(&ev, "eip155:42", "0xcafe") @@ -816,10 +888,9 @@ func TestFundMigrationEVM_BroadcastFails_NonceNotConsumed_StaysSigned(t *testing Return(false, uint64(0), uint64(0), uint8(0), nil) builder.On("GetNextNonce", mock.Anything, mock.Anything, true).Return(uint64(3), nil) - b := newBroadcaster(evtStore, ch, "") + b := newBroadcaster(evtStore, ch) b.processSigned(context.Background()) ev := getEvent(t, db, "fm-1") require.Equal(t, store.StatusSigned, ev.Status) // stays SIGNED for retry } - diff --git a/universalClient/tss/txbroadcaster/evm.go b/universalClient/tss/txbroadcaster/evm.go index da0df3153..e0bb380f0 100644 --- a/universalClient/tss/txbroadcaster/evm.go +++ b/universalClient/tss/txbroadcaster/evm.go @@ -68,17 +68,15 @@ func (b *Broadcaster) broadcastOutboundEVM(ctx context.Context, event *store.Eve return } - tssAddress := "" - if b.getTSSAddress != nil { - var addrErr error - tssAddress, addrErr = b.getTSSAddress(ctx) - if addrErr != nil { - log.Warn().Err(addrErr).Msg("failed to get TSS address for nonce check, will retry next tick") - return - } + // Nonce check must use the key that signed this tx, not the live TSS: after a + // rotation they are different EOAs with unrelated nonce sequences. + signer, signedNonce, ok := txflow.RecoverOutboundSigner(event) + if !ok { + log.Warn().Msg("could not recover signing key for nonce check, will retry next tick") + return } - b.checkNonceAndMarkBroadcasted(ctx, event, builder, chainID, txHash, tssAddress, data.SigningData.Nonce, broadcastErr) + b.checkNonceAndMarkBroadcasted(ctx, event, builder, chainID, txHash, signer, signedNonce, broadcastErr) } // broadcastFundMigrationEVM broadcasts a signed EVM fund migration transaction. diff --git a/universalClient/tss/txflow/parse.go b/universalClient/tss/txflow/parse.go index 6bb71857e..63ec70972 100644 --- a/universalClient/tss/txflow/parse.go +++ b/universalClient/tss/txflow/parse.go @@ -5,6 +5,8 @@ import ( "encoding/json" "fmt" + "github.com/ethereum/go-ethereum/crypto" + "github.com/pushchain/push-chain-node/universalClient/chains/common" "github.com/pushchain/push-chain-node/universalClient/store" "github.com/pushchain/push-chain-node/universalClient/tss/coordinator" @@ -50,9 +52,41 @@ func ReadSigningDeadline(event *store.Event) int64 { return data.SigningDeadline } +// RecoverOutboundSigner returns the EVM address that actually signed a SIGNED or +// BROADCASTED outbound, recovered from the persisted signature and signing hash. +// +// Nonces are per-EOA, so a nonce check is only meaningful against the key that +// signed. Outbound SigningData carries no key id, and after a TSS rotation the +// current key is a different EOA with an unrelated nonce sequence — comparing a +// K1-signed nonce against K2's would report "consumed" while K1's nonce is still +// free. Recovering from the signature binds the check to the right key without +// persisting anything new, so events signed before this existed are covered too. +// +// Returns ok=false when the signer cannot be established, which callers must +// treat as "defer", never as evidence the transaction did not execute. +func RecoverOutboundSigner(event *store.Event) (signer string, nonce uint64, ok bool) { + var data SignedOutboundData + if err := json.Unmarshal(event.EventData, &data); err != nil || data.SigningData == nil { + return "", 0, false + } + req, signature, err := DecodeSigningData(data.SigningData) + if err != nil || len(signature) != 65 || len(req.SigningHash) != 32 { + return "", 0, false + } + pub, err := crypto.SigToPub(req.SigningHash, signature) + if err != nil || pub == nil { + return "", 0, false + } + addr, err := coordinator.DeriveEVMAddressFromPubkey(hex.EncodeToString(crypto.CompressPubkey(pub))) + if err != nil { + return "", 0, false + } + return addr, data.SigningData.Nonce, true +} + // ReadFundMigrationSigner derives the sender EVM address (old TSS) and reads // the signed nonce from a fund migration event payload. Returns ok=false on -// missing/invalid fields — caller defers in that case. +// missing/invalid fields, and the caller defers in that case. func ReadFundMigrationSigner(event *store.Event) (signer string, nonce uint64, ok bool) { var data SignedFundMigrationData if err := json.Unmarshal(event.EventData, &data); err != nil || data.SigningData == nil || data.OldTssPubkey == "" { diff --git a/universalClient/tss/txflow/parse_test.go b/universalClient/tss/txflow/parse_test.go new file mode 100644 index 000000000..1062b0a3c --- /dev/null +++ b/universalClient/tss/txflow/parse_test.go @@ -0,0 +1,102 @@ +package txflow + +import ( + "encoding/hex" + "encoding/json" + "testing" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/pushchain/push-chain-node/universalClient/store" + "github.com/pushchain/push-chain-node/universalClient/tss/coordinator" +) + +const ( + keyAHex = "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318" + keyBHex = "8a1f9a8f9c8b7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3d2e" +) + +func signerAddr(t *testing.T, keyHex string) string { + t.Helper() + key, err := crypto.HexToECDSA(keyHex) + require.NoError(t, err) + addr, err := coordinator.DeriveEVMAddressFromPubkey(hex.EncodeToString(crypto.CompressPubkey(&key.PublicKey))) + require.NoError(t, err) + return addr +} + +// outboundEvent builds a SIGNED outbound payload signed by keyHex, matching what +// sessionManager persists. +func outboundEvent(t *testing.T, keyHex string, nonce uint64) *store.Event { + t.Helper() + key, err := crypto.HexToECDSA(keyHex) + require.NoError(t, err) + hash := crypto.Keccak256([]byte("signing hash")) + sig, err := crypto.Sign(hash, key) + require.NoError(t, err) + + b, err := json.Marshal(map[string]any{ + "tx_id": "tx-1", "utx_id": "utx-1", "destination_chain": "eip155:1", + "signing_data": map[string]any{ + "nonce": nonce, + "signature": hex.EncodeToString(sig), + "signing_hash": hex.EncodeToString(hash), + }, + }) + require.NoError(t, err) + return &store.Event{EventData: b} +} + +func TestRecoverOutboundSigner(t *testing.T) { + t.Run("recovers the address that signed", func(t *testing.T) { + signer, nonce, ok := RecoverOutboundSigner(outboundEvent(t, keyAHex, 5)) + require.True(t, ok) + assert.Equal(t, signerAddr(t, keyAHex), signer) + assert.Equal(t, uint64(5), nonce) + }) + + // The point of the change: two keys are two EOAs with unrelated nonce + // sequences, so the recovered signer has to follow the key that signed rather + // than whichever key is current. + t.Run("different keys recover to different addresses", func(t *testing.T) { + a, _, okA := RecoverOutboundSigner(outboundEvent(t, keyAHex, 5)) + b, _, okB := RecoverOutboundSigner(outboundEvent(t, keyBHex, 5)) + require.True(t, okA) + require.True(t, okB) + assert.NotEqual(t, a, b) + assert.Equal(t, signerAddr(t, keyBHex), b) + }) + + // Every failure has to report ok=false. A wrong address would be worse than + // no address: it produces a confident answer about the wrong nonce domain. + t.Run("unusable payloads report failure", func(t *testing.T) { + cases := map[string]*store.Event{ + "not json": {EventData: []byte("{")}, + "no signing data": {EventData: []byte(`{"tx_id":"tx-1"}`)}, + "short signature": {EventData: []byte(`{"signing_data":{"nonce":5,"signature":"deadbeef","signing_hash":"` + + hex.EncodeToString(crypto.Keccak256([]byte("h"))) + `"}}`)}, + "bad hex": {EventData: []byte(`{"signing_data":{"nonce":5,"signature":"zz","signing_hash":"zz"}}`)}, + "short hash": {EventData: []byte(`{"signing_data":{"nonce":5,"signature":"` + + hex.EncodeToString(make([]byte, 65)) + `","signing_hash":"00"}}`)}, + "unrecoverable signature": {EventData: []byte(`{"signing_data":{"nonce":5,"signature":"` + + hex.EncodeToString(make([]byte, 65)) + `","signing_hash":"` + + hex.EncodeToString(crypto.Keccak256([]byte("h"))) + `"}}`)}, + } + for name, ev := range cases { + t.Run(name, func(t *testing.T) { + _, _, ok := RecoverOutboundSigner(ev) + assert.False(t, ok) + }) + } + }) + + // Same signature, different persisted nonce: the nonce is read from the + // payload, the domain from the signature. They are independent. + t.Run("nonce comes from the payload", func(t *testing.T) { + _, nonce, ok := RecoverOutboundSigner(outboundEvent(t, keyAHex, 99)) + require.True(t, ok) + assert.Equal(t, uint64(99), nonce) + }) +} diff --git a/universalClient/tss/txresolver/evm.go b/universalClient/tss/txresolver/evm.go index 9167a5162..83773b91c 100644 --- a/universalClient/tss/txresolver/evm.go +++ b/universalClient/tss/txresolver/evm.go @@ -21,9 +21,13 @@ import ( // - Tx not found, nonce check unavailable → stay BROADCASTED (retry) // // The nonce IS the give-up signal; there is no max-retry counter. The two -// flows differ only in (a) which vote function records success/failure and -// (b) where the signer address comes from — current TSS for outbound, OLD TSS -// (derived from the event's old pubkey) for fund migration. +// flows differ only in which vote function records success/failure. +// +// Both check the nonce against the key that actually signed, never the current +// TSS: nonces are per-EOA, so after a rotation the live key is a different EOA +// whose sequence says nothing about an outbound signed under the previous one. +// Outbound recovers that signer from the signature, fund migration derives it +// from the event's old pubkey. // // Shared types (SignedOutboundData / SigningData) and helpers (DecodeSigningData, // ReadSignedNonce, ReadFundMigrationSigner, CheckNonce, NonceVerdict) live in @@ -164,21 +168,12 @@ func (r *Resolver) resolveFundMigrationEVM(ctx context.Context, event *store.Eve func (r *Resolver) outboundSigner(ctx context.Context, event *store.Event) (string, uint64, bool) { log := r.logger.With().Str("event_id", event.EventID).Logger() - signedNonce, ok := txflow.ReadSignedNonce(event) + signer, signedNonce, ok := txflow.RecoverOutboundSigner(event) if !ok { - log.Warn().Msg("EVM tx not found and signed nonce unavailable, staying BROADCASTED") - return "", 0, false - } - if r.getTSSAddress == nil { - log.Warn().Msg("EVM tx not found and no TSS-address resolver configured, staying BROADCASTED") - return "", 0, false - } - addr, err := r.getTSSAddress(ctx) - if err != nil { - log.Debug().Err(err).Msg("could not fetch TSS address, will retry next tick") + log.Warn().Msg("EVM tx not found and signing key unrecoverable, staying BROADCASTED") return "", 0, false } - return addr, signedNonce, true + return signer, signedNonce, true } // rewindToSigned moves a BROADCASTED event back to SIGNED so the broadcaster diff --git a/universalClient/tss/txresolver/resolver.go b/universalClient/tss/txresolver/resolver.go index 03fe4dcd3..60284cb87 100644 --- a/universalClient/tss/txresolver/resolver.go +++ b/universalClient/tss/txresolver/resolver.go @@ -25,7 +25,6 @@ type Config struct { PushSigner *pushsigner.Signer CheckInterval time.Duration Logger zerolog.Logger - GetTSSAddress func(ctx context.Context) (string, error) } type Resolver struct { @@ -34,7 +33,6 @@ type Resolver struct { pushSigner *pushsigner.Signer checkInterval time.Duration logger zerolog.Logger - getTSSAddress func(ctx context.Context) (string, error) } func NewResolver(cfg Config) *Resolver { @@ -48,7 +46,6 @@ func NewResolver(cfg Config) *Resolver { pushSigner: cfg.PushSigner, checkInterval: interval, logger: cfg.Logger.With().Str("component", "txresolver").Logger(), - getTSSAddress: cfg.GetTSSAddress, } } diff --git a/universalClient/tss/txresolver/resolver_test.go b/universalClient/tss/txresolver/resolver_test.go index 752b03c2d..5937a4f4d 100644 --- a/universalClient/tss/txresolver/resolver_test.go +++ b/universalClient/tss/txresolver/resolver_test.go @@ -2,12 +2,14 @@ package txresolver import ( "context" + "encoding/hex" "encoding/json" "reflect" "testing" "time" "unsafe" + "github.com/ethereum/go-ethereum/crypto" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -23,6 +25,7 @@ import ( "github.com/pushchain/push-chain-node/universalClient/chains/common" "github.com/pushchain/push-chain-node/universalClient/config" "github.com/pushchain/push-chain-node/universalClient/store" + "github.com/pushchain/push-chain-node/universalClient/tss/coordinator" "github.com/pushchain/push-chain-node/universalClient/tss/eventstore" ) @@ -178,19 +181,6 @@ func newResolver(evtStore *eventstore.Store, ch *chains.Chains) *Resolver { }) } -// newResolverWithTSSAddress builds a Resolver that returns a fixed TSS address -// from GetTSSAddress — needed by tests that exercise the EVM nonce-based -// retry/revert path. -func newResolverWithTSSAddress(evtStore *eventstore.Store, ch *chains.Chains, addr string) *Resolver { - return NewResolver(Config{ - EventStore: evtStore, - Chains: ch, - CheckInterval: 0, - Logger: zerolog.Nop(), - GetTSSAddress: func(ctx context.Context) (string, error) { return addr, nil }, - }) -} - func TestParseCAIPTxHash(t *testing.T) { t.Run("valid CAIP tx hash", func(t *testing.T) { chainID, txHash, err := parseCAIPTxHash("eip155:1:0xabc123") @@ -930,14 +920,39 @@ func makeOutboundEventDataWithNonce(txID, utxID, destChain string, nonce uint64) "tx_id": txID, "utx_id": utxID, "destination_chain": destChain, - "signing_data": map[string]any{ - "nonce": nonce, - }, + "signing_data": testOutboundSigningData(testSigningKeyHex, nonce), }) return b } -const testEVMTSSAddr = "0x4D353565442Eb33b66ef88E14336F3F4Bf3a02FB" +// The resolver recovers the nonce domain from the signature, so payloads have to +// carry a real one. Two fixed keys stand in for a TSS key and its rotation +// successor; signing with one and checking the other's nonce is the bug. +const ( + testSigningKeyHex = "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318" + testRotatedSigningKeyHex = "8a1f9a8f9c8b7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3d2e" +) + +func testOutboundSigningData(keyHex string, nonce uint64) map[string]any { + key, _ := crypto.HexToECDSA(keyHex) + hash := crypto.Keccak256([]byte("test outbound signing hash")) + sig, _ := crypto.Sign(hash, key) + return map[string]any{ + "nonce": nonce, + "signature": hex.EncodeToString(sig), + "signing_hash": hex.EncodeToString(hash), + } +} + +func testSignerAddr(keyHex string) string { + key, _ := crypto.HexToECDSA(keyHex) + addr, _ := coordinator.DeriveEVMAddressFromPubkey(hex.EncodeToString(crypto.CompressPubkey(&key.PublicKey))) + return addr +} + +// The address that signed the payloads above, i.e. the nonce domain the resolver +// must query. Not a configured value any more — it comes from the signature. +var testEVMTSSAddr = testSignerAddr(testSigningKeyHex) func TestResolveOutboundEVM_NotFound_NonceConsumed_Reverts(t *testing.T) { // Tx not found AND signed nonce < finalized nonce → another tx consumed @@ -957,7 +972,7 @@ func TestResolveOutboundEVM_NotFound_NonceConsumed_Reverts(t *testing.T) { // Finalized nonce = 7 → our nonce 5 is past finalized → consumed. builder.On("GetNextNonce", mock.Anything, testEVMTSSAddr, true).Return(uint64(7), nil) - resolver := newResolverWithTSSAddress(evtStore, ch, testEVMTSSAddr) + resolver := newResolver(evtStore, ch) resolver.processBroadcasted(context.Background()) ev := getEvent(t, db, "ev-consumed-1") @@ -983,7 +998,7 @@ func TestResolveOutboundEVM_ReceiptError_NonceConsumed_DoesNotVoteFailure(t *tes // Finalized nonce 7 > signed nonce 5, so the nonce check would say "consumed". builder.On("GetNextNonce", mock.Anything, testEVMTSSAddr, true).Return(uint64(7), nil) - resolver := newResolverWithTSSAddress(evtStore, ch, testEVMTSSAddr) + resolver := newResolver(evtStore, ch) resolver.processBroadcasted(context.Background()) require.Equal(t, store.StatusBroadcasted, getEvent(t, db, "ev-18826").Status) @@ -1015,7 +1030,7 @@ func TestResolveOutboundEVM_NotFound_NonceUnconsumed_RewindsToSigned(t *testing. // Finalized nonce = 5 → our nonce 5 not yet finalized. builder.On("GetNextNonce", mock.Anything, testEVMTSSAddr, true).Return(uint64(5), nil) - resolver := newResolverWithTSSAddress(evtStore, ch, testEVMTSSAddr) + resolver := newResolver(evtStore, ch) resolver.processBroadcasted(context.Background()) ev := getEvent(t, db, "ev-unconsumed-1") @@ -1036,7 +1051,7 @@ func TestResolveOutboundEVM_NotFound_NonceRPCError_StaysBroadcasted(t *testing.T Return(false, uint64(0), uint64(0), uint8(0), nil) builder.On("GetNextNonce", mock.Anything, testEVMTSSAddr, true).Return(uint64(0), assert.AnError) - resolver := newResolverWithTSSAddress(evtStore, ch, testEVMTSSAddr) + resolver := newResolver(evtStore, ch) resolver.processBroadcasted(context.Background()) ev := getEvent(t, db, "ev-rpc-err-1") @@ -1058,7 +1073,7 @@ func TestResolveOutboundEVM_NotFound_SignedNonceMissing_StaysBroadcasted(t *test builder.On("VerifyBroadcastedTx", mock.Anything, "0xmissing"). Return(false, uint64(0), uint64(0), uint8(0), nil) - resolver := newResolverWithTSSAddress(evtStore, ch, testEVMTSSAddr) + resolver := newResolver(evtStore, ch) resolver.processBroadcasted(context.Background()) ev := getEvent(t, db, "ev-no-nonce") @@ -1066,52 +1081,69 @@ func TestResolveOutboundEVM_NotFound_SignedNonceMissing_StaysBroadcasted(t *test builder.AssertNotCalled(t, "GetNextNonce", mock.Anything, mock.Anything, mock.Anything) } -func TestResolveOutboundEVM_NotFound_TSSAddressFetchError_StaysBroadcasted(t *testing.T) { - // Tx not found and GetTSSAddress callback errors → defer (retry next tick). +// The nonce domain is derived from the signature, so if the signer cannot be +// recovered there is no domain to check. That must defer, never fall through to +// some other key's sequence. +func TestResolveOutboundEVM_NotFound_SignerUnrecoverable_StaysBroadcasted(t *testing.T) { evtStore, db := setupTestDB(t) builder := &mockTxBuilder{} client := &mockChainClient{builder: builder} ch := newTestChains(t, "eip155:1", uregistrytypes.VmType_EVM, client) - eventData := makeOutboundEventDataWithNonce("tx-100", "utx-200", "eip155:1", 5) - insertBroadcastedEvent(t, db, "ev-tss-err", "eip155:1", "eip155:1:0xmissing", eventData) + eventData, _ := json.Marshal(map[string]any{ + "tx_id": "tx-100", "utx_id": "utx-200", "destination_chain": "eip155:1", + "signing_data": map[string]any{ + "nonce": 5, + "signature": "deadbeef", // not 65 bytes + "signing_hash": hex.EncodeToString(crypto.Keccak256([]byte("h"))), + }, + }) + insertBroadcastedEvent(t, db, "ev-nosigner", "eip155:1", "eip155:1:0xmissing", eventData) builder.On("VerifyBroadcastedTx", mock.Anything, "0xmissing"). Return(false, uint64(0), uint64(0), uint8(0), nil) - resolver := NewResolver(Config{ - EventStore: evtStore, - Chains: ch, - CheckInterval: 0, - Logger: zerolog.Nop(), - GetTSSAddress: func(ctx context.Context) (string, error) { return "", assert.AnError }, - }) + resolver := newResolver(evtStore, ch) resolver.processBroadcasted(context.Background()) - ev := getEvent(t, db, "ev-tss-err") - require.Equal(t, store.StatusBroadcasted, ev.Status) + require.Equal(t, store.StatusBroadcasted, getEvent(t, db, "ev-nosigner").Status) builder.AssertNotCalled(t, "GetNextNonce", mock.Anything, mock.Anything, mock.Anything) } -func TestResolveOutboundEVM_NotFound_NoTSSAddressResolver_StaysBroadcasted(t *testing.T) { - // Tx not found and GetTSSAddress is nil → can't run nonce check → defer. +// F-2026-18827. An outbound signed under K1 is still BROADCASTED when the TSS +// rotates to K2. K1 and K2 are separate EOAs with unrelated nonce sequences, so +// checking K2's would report the nonce consumed and fail-vote a transaction K1 +// can still land, while the refund path remints. The check must follow the key +// that signed. +func TestResolveOutboundEVM_NotFound_AfterRotation_ChecksSigningKeyNonce(t *testing.T) { evtStore, db := setupTestDB(t) builder := &mockTxBuilder{} client := &mockChainClient{builder: builder} ch := newTestChains(t, "eip155:1", uregistrytypes.VmType_EVM, client) + k1 := testSignerAddr(testSigningKeyHex) + k2 := testSignerAddr(testRotatedSigningKeyHex) + require.NotEqual(t, k1, k2) + + // Signed under K1 at nonce 5, still unresolved. eventData := makeOutboundEventDataWithNonce("tx-100", "utx-200", "eip155:1", 5) - insertBroadcastedEvent(t, db, "ev-no-tss-1", "eip155:1", "eip155:1:0xmissing", eventData) + insertBroadcastedEvent(t, db, "ev-rotated", "eip155:1", "eip155:1:0xmissing", eventData) builder.On("VerifyBroadcastedTx", mock.Anything, "0xmissing"). Return(false, uint64(0), uint64(0), uint8(0), nil) + // K1 nonce 5 is still free, so this tx can still mine. + builder.On("GetNextNonce", mock.Anything, k1, true).Return(uint64(5), nil) + // K2 has moved well past 5. Reading this domain is the bug. + builder.On("GetNextNonce", mock.Anything, k2, true).Return(uint64(42), nil) - resolver := newResolver(evtStore, ch) // no GetTSSAddress configured + // The live TSS is K2, the rotation successor. + resolver := newResolver(evtStore, ch) resolver.processBroadcasted(context.Background()) - ev := getEvent(t, db, "ev-no-tss-1") - require.Equal(t, store.StatusBroadcasted, ev.Status) - builder.AssertNotCalled(t, "GetNextNonce", mock.Anything, mock.Anything, mock.Anything) + builder.AssertCalled(t, "GetNextNonce", mock.Anything, k1, true) + builder.AssertNotCalled(t, "GetNextNonce", mock.Anything, k2, true) + require.Equal(t, store.StatusSigned, getEvent(t, db, "ev-rotated").Status, + "K1 nonce still free means rebroadcast, not a failure vote") } func TestResolveOutboundEVM_VerifyError_StaysBroadcasted(t *testing.T) { From 49f8e7ccacf1ba98a5499f1be546dd2cd961b740 Mon Sep 17 00:00:00 2001 From: Aman Gupta Date: Tue, 25 Aug 2026 13:56:01 +0530 Subject: [PATCH 23/60] fix: F-2026-18196 | [Dual Defense] Native SOL Misclassified as SPL Due to EVM-Only isNative Check (#307) * fix: recognize Solana native marker in SVM builder isNative check (F-2026-18196) * fix: also treat hex-encoded zero pubkey as native SOL (F-2026-18196) * docs: record that core sends EVM zero hex for solana native, base58 on reverts (F-2026-18196) * test: assert both native marker forms build identical accounts (F-2026-18196) * docs: state why the hex length check is load bearing --- universalClient/chains/svm/tx_builder.go | 29 ++++++- universalClient/chains/svm/tx_builder_test.go | 82 ++++++++++++++++++- 2 files changed, 106 insertions(+), 5 deletions(-) diff --git a/universalClient/chains/svm/tx_builder.go b/universalClient/chains/svm/tx_builder.go index ee77d59ea..cd6698be1 100644 --- a/universalClient/chains/svm/tx_builder.go +++ b/universalClient/chains/svm/tx_builder.go @@ -215,9 +215,8 @@ func (tb *TxBuilder) GetOutboundSigningRequest( } // Determine if this is native SOL or an SPL token transfer. - // Empty or zero address = native SOL. Otherwise it's the SPL token mint address. assetAddr := data.AssetAddr - isNative := assetAddr == "" || assetAddr == "0x0" || assetAddr == "0x0000000000000000000000000000000000000000" + isNative := isNativeAsset(assetAddr) txType, err := parseTxType(data.TxType) if err != nil { @@ -713,7 +712,7 @@ func (tb *TxBuilder) BuildOutboundTransaction( } assetAddr := data.AssetAddr - isNative := assetAddr == "" || assetAddr == "0x0" || assetAddr == "0x0000000000000000000000000000000000000000" + isNative := isNativeAsset(assetAddr) txType, err := parseTxType(data.TxType) if err != nil { @@ -1058,7 +1057,7 @@ func (tb *TxBuilder) BuildRefRouteTransactions( } assetAddr := data.AssetAddr - isNative := assetAddr == "" || assetAddr == "0x0" || assetAddr == "0x0000000000000000000000000000000000000000" + isNative := isNativeAsset(assetAddr) var txID [32]byte txIDBytes, err := hex.DecodeString(removeHexPrefix(data.TxID)) @@ -1288,6 +1287,28 @@ func removeHexPrefix(s string) string { return s } +// isNativeAsset reports whether addr denotes native SOL rather than an SPL mint. +// Both encodings of the zero address reach us. Core sends the EVM zero hex on +// withdrawals (registry token address), while reverts carry the base58 zero +// pubkey, SystemProgram 11111111111111111111111111111111, copied from the +// inbound. SPL mints are always base58 and parse as an ordinary non-zero pubkey. +func isNativeAsset(addr string) bool { + switch addr { + case "", "0x0", "0x0000000000000000000000000000000000000000": + return true + } + if pubkey, err := solana.PublicKeyFromBase58(addr); err == nil { + return pubkey.IsZero() + } + // The builder also accepts hex mints, so cover a hex-encoded zero pubkey. + // The length check is load bearing: PublicKeyFromBytes panics on anything + // other than 32 bytes, and a short hex string such as 0x1234 reaches here. + if raw, err := hex.DecodeString(removeHexPrefix(addr)); err == nil && len(raw) == 32 { + return solana.PublicKeyFromBytes(raw).IsZero() + } + return false +} + // ============================================================================= // PDA Derivation & On-Chain Data // ============================================================================= diff --git a/universalClient/chains/svm/tx_builder_test.go b/universalClient/chains/svm/tx_builder_test.go index f94145e17..5f923d7f6 100644 --- a/universalClient/chains/svm/tx_builder_test.go +++ b/universalClient/chains/svm/tx_builder_test.go @@ -2394,7 +2394,7 @@ func buildAndSimulateRescue(t *testing.T, rpcClient *RPCClient, builder *TxBuild require.NoError(t, err) copy(sender[:], senderBytes) - isNative := assetAddr == "" + isNative := isNativeAsset(assetAddr) var token [32]byte var mintPubkey solana.PublicKey if !isNative { @@ -2722,6 +2722,86 @@ func TestSimulate_RefRoute_Execute(t *testing.T) { requireSimulationSuccess(t, storeSim) } +// Both encodings of native SOL reach the builder, verified against donut: +// withdrawals carry the EVM zero hex from the registry token address, reverts +// carry the base58 SystemProgram marker copied from the inbound. Missing either +// builds an SPL transfer whose ATA-create reverts, since neither is a mint. +func TestIsNativeAsset(t *testing.T) { + t.Run("core withdrawal form is native", func(t *testing.T) { + // create_outbound.go copies the registry token address verbatim. + assert.True(t, isNativeAsset("0x0000000000000000000000000000000000000000")) + }) + + t.Run("core revert form is native", func(t *testing.T) { + // build_revert_outbound.go copies inbound.AssetAddr, which the SVM parser + // sets from the pubkey, so native SOL arrives base58 encoded. + assert.True(t, isNativeAsset("11111111111111111111111111111111")) + assert.True(t, isNativeAsset(solana.SystemProgramID.String())) + assert.True(t, isNativeAsset(solana.PublicKey{}.String())) + }) + + t.Run("other zero spellings are native", func(t *testing.T) { + assert.True(t, isNativeAsset("")) + assert.True(t, isNativeAsset("0x0")) + assert.True(t, isNativeAsset("0x"+strings.Repeat("0", 64)), "hex-encoded zero pubkey") + }) + + // SPL mints are base58 in both directions, so they parse normally and must + // keep taking the token path. + t.Run("real SPL mints are not native", func(t *testing.T) { + assert.False(t, isNativeAsset("EiXDnrAg9ea2Q6vEPV7E5TpTU1vh41jcuZqKjU5Dc4ZF"), "USDT.sol") + assert.False(t, isNativeAsset("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"), "USDC.sol") + assert.False(t, isNativeAsset(solana.TokenProgramID.String())) + }) + + t.Run("malformed addresses are not native", func(t *testing.T) { + assert.False(t, isNativeAsset("not-base58-0OlI")) + assert.False(t, isNativeAsset("0x1234")) + }) +} + +// Core sent the base58 marker before switching to the EVM zero, so both forms +// are live: withdrawals carry the zero hex and reverts still carry base58. Both +// must build the identical native account layout, with no recipient ATA. +func TestNativeMarkerFormsBuildIdenticalAccounts(t *testing.T) { + builder := newTestBuilder(t) + + caller := solana.NewWallet().PublicKey() + config := solana.NewWallet().PublicKey() + vault := solana.NewWallet().PublicKey() + cea := solana.NewWallet().PublicKey() + tss := solana.NewWallet().PublicKey() + executed := solana.NewWallet().PublicKey() + recipient := solana.NewWallet().PublicKey() + + build := func(t *testing.T, assetAddr string) []*solana.AccountMeta { + t.Helper() + isNative := isNativeAsset(assetAddr) + require.True(t, isNative, "asset %q must classify as native", assetAddr) + return builder.buildWithdrawAndExecuteAccounts( + caller, config, vault, cea, tss, executed, + solana.SystemProgramID, + isNative, 1, + recipient, solana.PublicKey{}, + nil, + solana.PublicKey{}, solana.PublicKey{}, + ) + } + + withdrawForm := build(t, "0x0000000000000000000000000000000000000000") + revertForm := build(t, "11111111111111111111111111111111") + + assert.Equal(t, withdrawForm, revertForm, + "revert-form native SOL must build the same accounts as withdraw-form") + + // The old bug took the SPL path and derived an ATA for a non-mint. + ata, _, err := solana.FindAssociatedTokenAddress(recipient, solana.SystemProgramID) + require.NoError(t, err) + for _, acc := range revertForm { + assert.NotEqual(t, ata, acc.PublicKey, "native layout must not include a recipient ATA") + } +} + // --------------------------------------------------------------------------- // VerifyBroadcastedTx // --------------------------------------------------------------------------- From 115951eb062413bfa0d6cfb9e5672f51054b6b25 Mon Sep 17 00:00:00 2001 From: Aman Gupta Date: Tue, 25 Aug 2026 16:16:09 +0530 Subject: [PATCH 24/60] fix: F-2026-18139 | [Dual Defense] Cross-RPC Height Skew Can Underflow Confirmation Counts and Prematurely Finalize Inbounds (#302) * fix: guard confirmation depth against RPC height skew; gate zero-confirmation instant routes to testnet (F-2026-18139) * chore: rename Network to PushNetwork, trim comments (F-2026-18139) * refactor: extract default confirmation depths to common constants; EVM fast default 5 (F-2026-18139) * chore: log RPC height skew at debug not warn (F-2026-18139) * test: cover RPC height skew end to end; pin signature discovery to finalized * feat: allow zero confirmations per chain for instant-finality sources * revert per-chain instant finality opt-in; no such source chain today --- universalClient/chains/chains.go | 4 +- universalClient/chains/common/confirmation.go | 19 ++++ .../chains/common/confirmation_test.go | 42 +++++++++ universalClient/chains/evm/client.go | 38 +++++--- universalClient/chains/evm/client_test.go | 87 +++++++++++++++--- universalClient/chains/evm/event_confirmer.go | 31 +++---- .../chains/evm/event_confirmer_test.go | 85 ++++++++++++++++- universalClient/chains/svm/client.go | 44 ++++++--- universalClient/chains/svm/client_test.go | 77 ++++++++++++---- universalClient/chains/svm/event_confirmer.go | 30 +++--- .../chains/svm/event_confirmer_test.go | 92 +++++++++++++++++-- universalClient/chains/svm/rpc_client.go | 8 +- universalClient/config/config_test.go | 22 +++++ universalClient/config/default_config.json | 1 + universalClient/config/types.go | 32 ++++++- 15 files changed, 496 insertions(+), 116 deletions(-) create mode 100644 universalClient/chains/common/confirmation.go create mode 100644 universalClient/chains/common/confirmation_test.go diff --git a/universalClient/chains/chains.go b/universalClient/chains/chains.go index 7fdda147a..66b2dcdde 100644 --- a/universalClient/chains/chains.go +++ b/universalClient/chains/chains.go @@ -314,9 +314,9 @@ func (c *Chains) addChain(ctx context.Context, cfg *uregistrytypes.ChainConfig) var client common.ChainClient switch cfg.VmType { case uregistrytypes.VmType_EVM: - client, err = evm.NewClient(cfg, chainDB, chainConfig, c.pushSigner, c.logger) + client, err = evm.NewClient(cfg, chainDB, chainConfig, c.pushSigner, c.config.AllowsZeroConfirmations(), c.logger) case uregistrytypes.VmType_SVM: - client, err = svm.NewClient(cfg, chainDB, chainConfig, c.pushSigner, c.config.NodeHome, c.logger) + client, err = svm.NewClient(cfg, chainDB, chainConfig, c.pushSigner, c.config.NodeHome, c.config.AllowsZeroConfirmations(), c.logger) default: return fmt.Errorf("unsupported VM type: %v", cfg.VmType) } diff --git a/universalClient/chains/common/confirmation.go b/universalClient/chains/common/confirmation.go new file mode 100644 index 000000000..438159197 --- /dev/null +++ b/universalClient/chains/common/confirmation.go @@ -0,0 +1,19 @@ +package common + +// Safe fallback confirmation depths used when the registry configures 0 and +// instant routes are not enabled. +const ( + DefaultFastConfirmations uint64 = 5 + DefaultStandardConfirmations uint64 = 12 +) + +// ConfirmationDepth returns latestHeight - txHeight + 1, the confirmation count +// with the inclusion block counted as one. ok is false when latestHeight < +// txHeight (a cross-RPC height skew); callers must defer rather than trust the +// depth, since the unchecked subtraction would underflow. +func ConfirmationDepth(latestHeight, txHeight uint64) (depth uint64, ok bool) { + if latestHeight < txHeight { + return 0, false + } + return latestHeight - txHeight + 1, true +} diff --git a/universalClient/chains/common/confirmation_test.go b/universalClient/chains/common/confirmation_test.go new file mode 100644 index 000000000..b50afbabc --- /dev/null +++ b/universalClient/chains/common/confirmation_test.go @@ -0,0 +1,42 @@ +package common + +import ( + "math" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestConfirmationDepth(t *testing.T) { + tests := []struct { + name string + latest uint64 + tx uint64 + wantDepth uint64 + wantOK bool + }{ + {"latest greater than tx", 110, 100, 11, true}, + {"latest equals tx (inclusion block)", 100, 100, 1, true}, + {"latest one below tx (skew)", 99, 100, 0, false}, + {"latest far below tx (skew)", 1, math.MaxUint64, 0, false}, + {"no underflow to near-2^64", 0, 1, 0, false}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + depth, ok := ConfirmationDepth(tc.latest, tc.tx) + assert.Equal(t, tc.wantOK, ok) + assert.Equal(t, tc.wantDepth, depth) + }) + } +} + +// TestConfirmationDepth_SkewNeverSatisfiesThreshold guards the exact finding: +// a transaction one block ahead of the observed tip must not produce a depth +// that clears a realistic confirmation threshold. +func TestConfirmationDepth_SkewNeverSatisfiesThreshold(t *testing.T) { + const threshold = uint64(12) + depth, ok := ConfirmationDepth(500, 501) + assert.False(t, ok, "skewed read must be flagged not-ok") + assert.False(t, depth >= threshold, "skewed depth must not satisfy threshold") +} diff --git a/universalClient/chains/evm/client.go b/universalClient/chains/evm/client.go index 80c7f1500..1bce6ac66 100644 --- a/universalClient/chains/evm/client.go +++ b/universalClient/chains/evm/client.go @@ -20,10 +20,11 @@ import ( // Client implements the ChainClient interface for EVM chains type Client struct { // Core configuration - logger zerolog.Logger - chainIDStr string - registryConfig *uregistrytypes.ChainConfig - chainConfig *config.ChainSpecificConfig + logger zerolog.Logger + chainIDStr string + registryConfig *uregistrytypes.ChainConfig + chainConfig *config.ChainSpecificConfig + allowZeroConfirmations bool // Infrastructure rpcClient *RPCClient @@ -49,6 +50,7 @@ func NewClient( database *db.DB, chainConfig *config.ChainSpecificConfig, pushSigner *pushsigner.Signer, + allowZeroConfirmations bool, logger zerolog.Logger, ) (*Client, error) { if config == nil { @@ -68,12 +70,13 @@ func NewClient( } client := &Client{ - logger: log, - chainIDStr: chainIDStr, - registryConfig: config, - chainConfig: chainConfig, - database: database, - pushSigner: pushSigner, + logger: log, + chainIDStr: chainIDStr, + registryConfig: config, + chainConfig: chainConfig, + allowZeroConfirmations: allowZeroConfirmations, + database: database, + pushSigner: pushSigner, } client.eventCleaner = common.NewEventCleaner( @@ -356,8 +359,8 @@ func (c *Client) applyDefaults() componentConfig { config := componentConfig{ eventPollingInterval: 5, // default gasPriceInterval: 30, // default - fastConfirmations: 2, - standardConfirmations: 12, + fastConfirmations: common.DefaultFastConfirmations, + standardConfirmations: common.DefaultStandardConfirmations, } // Apply event polling interval @@ -381,6 +384,17 @@ func (c *Client) applyDefaults() componentConfig { config.standardConfirmations = uint64(c.registryConfig.BlockConfirmation.StandardInbound) } + // A registry-configured 0 disables the reorg-safety depth. Honor it only + // when instant routes are enabled; otherwise fall back to a safe default. + if !c.allowZeroConfirmations { + if config.fastConfirmations == 0 { + config.fastConfirmations = common.DefaultFastConfirmations + } + if config.standardConfirmations == 0 { + config.standardConfirmations = common.DefaultStandardConfirmations + } + } + return config } diff --git a/universalClient/chains/evm/client_test.go b/universalClient/chains/evm/client_test.go index 1ea67b10c..9f05086fb 100644 --- a/universalClient/chains/evm/client_test.go +++ b/universalClient/chains/evm/client_test.go @@ -36,7 +36,7 @@ func TestClientInitialization(t *testing.T) { } chainSpecificConfig := testChainConfig([]string{"https://eth-mainnet.example.com"}) - client, err := NewClient(chainConfig, nil, chainSpecificConfig, nil, logger) + client, err := NewClient(chainConfig, nil, chainSpecificConfig, nil, false, logger) require.NoError(t, err) assert.NotNil(t, client) assert.Equal(t, chainConfig, client.GetConfig()) @@ -44,7 +44,7 @@ func TestClientInitialization(t *testing.T) { }) t.Run("Nil config", func(t *testing.T) { - client, err := NewClient(nil, nil, nil, nil, logger) + client, err := NewClient(nil, nil, nil, nil, false, logger) assert.Error(t, err) assert.Nil(t, client) assert.Contains(t, err.Error(), "config is nil") @@ -57,7 +57,7 @@ func TestClientInitialization(t *testing.T) { } chainSpecificConfig := testChainConfig([]string{}) - client, err := NewClient(chainConfig, nil, chainSpecificConfig, nil, logger) + client, err := NewClient(chainConfig, nil, chainSpecificConfig, nil, false, logger) assert.Error(t, err) assert.Nil(t, client) assert.Contains(t, err.Error(), "no RPC URLs configured") @@ -69,7 +69,7 @@ func TestClientInitialization(t *testing.T) { VmType: uregistrytypes.VmType_SVM, // Wrong VM type } - client, err := NewClient(chainConfig, nil, nil, nil, logger) + client, err := NewClient(chainConfig, nil, nil, nil, false, logger) assert.Error(t, err) assert.Nil(t, client) assert.Contains(t, err.Error(), "invalid VM type for EVM client") @@ -177,7 +177,7 @@ func TestClientStartStop(t *testing.T) { } chainSpecificConfig := testChainConfig([]string{server.URL}) - client, err := NewClient(chainConfig, nil, chainSpecificConfig, nil, logger) + client, err := NewClient(chainConfig, nil, chainSpecificConfig, nil, false, logger) require.NoError(t, err) ctx := context.Background() @@ -199,7 +199,7 @@ func TestClientStartStop(t *testing.T) { chainSpecificConfig := testChainConfig([]string{"http://invalid.localhost:99999"}) - client, err := NewClient(chainConfig, nil, chainSpecificConfig, nil, logger) + client, err := NewClient(chainConfig, nil, chainSpecificConfig, nil, false, logger) require.NoError(t, err) // Use context with timeout to ensure fast failure @@ -238,7 +238,7 @@ func TestClientStartStop(t *testing.T) { // Use valid URL but cancel context immediately chainSpecificConfig := testChainConfig([]string{server.URL}) - client, err := NewClient(chainConfig, nil, chainSpecificConfig, nil, logger) + client, err := NewClient(chainConfig, nil, chainSpecificConfig, nil, false, logger) require.NoError(t, err) ctx, cancel := context.WithCancel(context.Background()) @@ -295,7 +295,7 @@ func TestClientIsHealthy(t *testing.T) { } chainSpecificConfig := testChainConfig([]string{server.URL}) - client, err := NewClient(chainConfig, nil, chainSpecificConfig, nil, logger) + client, err := NewClient(chainConfig, nil, chainSpecificConfig, nil, false, logger) require.NoError(t, err) // Start the client @@ -320,7 +320,7 @@ func TestClientIsHealthy(t *testing.T) { // Provide valid RPC URLs for NewClient to succeed // But don't start the client chainSpecificConfig := testChainConfig([]string{"https://eth-mainnet.example.com"}) - client, err := NewClient(chainConfig, nil, chainSpecificConfig, nil, logger) + client, err := NewClient(chainConfig, nil, chainSpecificConfig, nil, false, logger) require.NoError(t, err) healthy := client.IsHealthy() @@ -342,7 +342,7 @@ func TestApplyDefaults(t *testing.T) { assert.Equal(t, 5, cfg.eventPollingInterval) assert.Equal(t, 30, cfg.gasPriceInterval) assert.Equal(t, 0, cfg.gasPriceMarkupPercent) - assert.Equal(t, uint64(2), cfg.fastConfirmations) + assert.Equal(t, uint64(5), cfg.fastConfirmations) assert.Equal(t, uint64(12), cfg.standardConfirmations) }) @@ -412,7 +412,7 @@ func TestApplyDefaults(t *testing.T) { } cfg := client.applyDefaults() - assert.Equal(t, uint64(2), cfg.fastConfirmations) + assert.Equal(t, uint64(5), cfg.fastConfirmations) assert.Equal(t, uint64(12), cfg.standardConfirmations) }) @@ -426,11 +426,68 @@ func TestApplyDefaults(t *testing.T) { } cfg := client.applyDefaults() - assert.Equal(t, uint64(2), cfg.fastConfirmations) + assert.Equal(t, uint64(5), cfg.fastConfirmations) assert.Equal(t, uint64(12), cfg.standardConfirmations) }) } +// A registry-configured 0 falls back to a safe depth unless instant routes are +// enabled, in which case it is honored. +func TestApplyDefaults_ZeroConfirmations(t *testing.T) { + logger := zerolog.New(zerolog.NewTestWriter(t)) + + zeroRegistry := &uregistrytypes.ChainConfig{ + BlockConfirmation: &uregistrytypes.BlockConfirmation{ + FastInbound: 0, + StandardInbound: 0, + }, + } + + t.Run("mainnet falls back to safe depth", func(t *testing.T) { + client := &Client{ + logger: logger, + chainIDStr: "eip155:1", + registryConfig: zeroRegistry, + allowZeroConfirmations: false, + } + + cfg := client.applyDefaults() + assert.Equal(t, uint64(5), cfg.fastConfirmations, "zero fast must not disable depth on mainnet") + assert.Equal(t, uint64(12), cfg.standardConfirmations, "zero standard must not disable depth on mainnet") + }) + + t.Run("testnet honors zero as instant", func(t *testing.T) { + client := &Client{ + logger: logger, + chainIDStr: "eip155:1", + registryConfig: zeroRegistry, + allowZeroConfirmations: true, + } + + cfg := client.applyDefaults() + assert.Equal(t, uint64(0), cfg.fastConfirmations, "testnet instant route keeps zero") + assert.Equal(t, uint64(0), cfg.standardConfirmations, "testnet instant route keeps zero") + }) + + t.Run("nonzero registry values unaffected by flag", func(t *testing.T) { + client := &Client{ + logger: logger, + chainIDStr: "eip155:1", + registryConfig: &uregistrytypes.ChainConfig{ + BlockConfirmation: &uregistrytypes.BlockConfirmation{ + FastInbound: 3, + StandardInbound: 9, + }, + }, + allowZeroConfirmations: false, + } + + cfg := client.applyDefaults() + assert.Equal(t, uint64(3), cfg.fastConfirmations) + assert.Equal(t, uint64(9), cfg.standardConfirmations) + }) +} + // TestGetTxBuilderNil tests GetTxBuilder when txBuilder is not initialized func TestGetTxBuilderNil(t *testing.T) { logger := zerolog.New(zerolog.NewTestWriter(t)) @@ -441,7 +498,7 @@ func TestGetTxBuilderNil(t *testing.T) { } chainSpecificConfig := testChainConfig([]string{"https://eth-mainnet.example.com"}) - client, err := NewClient(chainConfig, nil, chainSpecificConfig, nil, logger) + client, err := NewClient(chainConfig, nil, chainSpecificConfig, nil, false, logger) require.NoError(t, err) // txBuilder is nil because gateway is not configured / Start not called @@ -464,7 +521,7 @@ func TestClientGetMethods(t *testing.T) { } chainSpecificConfig := testChainConfig([]string{"https://eth-sepolia.example.com"}) - client, err := NewClient(chainConfig, nil, chainSpecificConfig, nil, logger) + client, err := NewClient(chainConfig, nil, chainSpecificConfig, nil, false, logger) require.NoError(t, err) t.Run("ChainID", func(t *testing.T) { @@ -496,7 +553,7 @@ func TestClientConcurrency(t *testing.T) { } chainSpecificConfig := testChainConfig([]string{server.URL}) - client, err := NewClient(chainConfig, nil, chainSpecificConfig, nil, logger) + client, err := NewClient(chainConfig, nil, chainSpecificConfig, nil, false, logger) require.NoError(t, err) ctx := context.Background() diff --git a/universalClient/chains/evm/event_confirmer.go b/universalClient/chains/evm/event_confirmer.go index e43f15326..e12549961 100644 --- a/universalClient/chains/evm/event_confirmer.go +++ b/universalClient/chains/evm/event_confirmer.go @@ -159,7 +159,17 @@ func (ec *EventConfirmer) processPendingEvents(ctx context.Context) error { // Check if transaction is confirmed based on confirmation type requiredConfirmations := ec.getRequiredConfirmations(event.ConfirmationType) - confirmations := latestBlock - receipt.BlockNumber + 1 + txBlock := receipt.BlockNumber + confirmations, ok := chaincommon.ConfirmationDepth(latestBlock, txBlock) + if !ok { + // RPC height skew: latest block is behind the tx block. Defer. + ec.logger.Debug(). + Str("event_id", event.EventID). + Uint64("latest_block", latestBlock). + Uint64("tx_block", txBlock). + Msg("latest block behind tx block (RPC height skew); deferring confirmation") + continue + } if confirmations >= requiredConfirmations { var rowsAffected int64 @@ -242,24 +252,13 @@ func (ec *EventConfirmer) getTxHashFromEventID(eventID string) string { return parts[0] } -// getRequiredConfirmations returns the required number of confirmations based on confirmation type +// getRequiredConfirmations returns the depth for a confirmation type. Values are +// resolved by applyDefaults, so a 0 here is an intentional instant route. func (ec *EventConfirmer) getRequiredConfirmations(confirmationType string) uint64 { switch confirmationType { case store.ConfirmationFast: - if ec.fastConfirmations >= 0 { - return ec.fastConfirmations - } - return 5 - case store.ConfirmationStandard: - if ec.standardConfirmations >= 0 { - return ec.standardConfirmations - } - return 12 + return ec.fastConfirmations default: - // Default to standard if unknown - if ec.standardConfirmations >= 0 { - return ec.standardConfirmations - } - return 12 + return ec.standardConfirmations } } diff --git a/universalClient/chains/evm/event_confirmer_test.go b/universalClient/chains/evm/event_confirmer_test.go index 221729dd8..3ee506bb7 100644 --- a/universalClient/chains/evm/event_confirmer_test.go +++ b/universalClient/chains/evm/event_confirmer_test.go @@ -375,25 +375,27 @@ func TestEventConfirmer_PendingEventsWithBlockHeightZero(t *testing.T) { assert.Equal(t, uint64(0), pending[0].BlockHeight) } +// The confirmer honors whatever depth it is given; the fallback policy lives in +// applyDefaults, so a 0 here is an intentional instant route. func TestEventConfirmer_GetRequiredConfirmations_ZeroValues(t *testing.T) { logger := zerolog.Nop() - t.Run("zero fast confirmations returns 0", func(t *testing.T) { + t.Run("zero fast confirmations honored as instant", func(t *testing.T) { ec := NewEventConfirmer(nil, nil, "eip155:1", 5, 0, 12, logger) result := ec.getRequiredConfirmations(store.ConfirmationFast) assert.Equal(t, uint64(0), result) }) - t.Run("zero standard confirmations returns 0", func(t *testing.T) { + t.Run("zero standard confirmations honored as instant", func(t *testing.T) { ec := NewEventConfirmer(nil, nil, "eip155:1", 5, 5, 0, logger) result := ec.getRequiredConfirmations(store.ConfirmationStandard) assert.Equal(t, uint64(0), result) }) - t.Run("zero standard with unknown type returns 0", func(t *testing.T) { - ec := NewEventConfirmer(nil, nil, "eip155:1", 5, 5, 0, logger) + t.Run("unknown type uses standard depth", func(t *testing.T) { + ec := NewEventConfirmer(nil, nil, "eip155:1", 5, 5, 7, logger) result := ec.getRequiredConfirmations("INSTANT") - assert.Equal(t, uint64(0), result) + assert.Equal(t, uint64(7), result) }) } @@ -527,3 +529,76 @@ func TestProcessPendingEvents_FailedReceiptMarkedReverted(t *testing.T) { require.NoError(t, memDB.Client().Where("event_id = ?", pending.EventID).First(&got).Error) assert.Equal(t, store.StatusReverted, got.Status, "failed receipt must transition to REVERTED, not CONFIRMED") } + +// The skew this finding reports, end to end: the endpoint serving the receipt +// is ahead of the one serving the tip, so the tx block is above the latest +// block. Unchecked, latest-tx underflows to near 2^64 and clears any threshold. +// The event must stay PENDING and be retried, never confirmed. +func TestProcessPendingEvents_RPCHeightSkew_StaysPending(t *testing.T) { + txHash := "0x3333333333333333333333333333333333333333333333333333333333333333" + const ( + eventBlockHex = "0x96" // 150, the receipt endpoint is ahead + latestBlockHex = "0x64" // 100, the tip endpoint lags by 50 blocks + ) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + body := make([]byte, r.ContentLength) + r.Body.Read(body) + bodyStr := string(body) + + switch { + case strings.Contains(bodyStr, "eth_chainId"): + w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":"0x1"}`)) + case strings.Contains(bodyStr, "eth_blockNumber"): + w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":"` + latestBlockHex + `"}`)) + case strings.Contains(bodyStr, "eth_getTransactionReceipt"): + w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":{` + + `"transactionHash":"` + txHash + `",` + + `"blockNumber":"` + eventBlockHex + `",` + + `"blockHash":"0x4444444444444444444444444444444444444444444444444444444444444444",` + + `"transactionIndex":"0x0",` + + `"gasUsed":"0x5208",` + + `"cumulativeGasUsed":"0x5208",` + + `"logsBloom":"0x` + strings.Repeat("0", 512) + `",` + + `"logs":[],` + + `"status":"0x1",` + + `"type":"0x2"` + + `}}`)) + default: + w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":null}`)) + } + })) + defer server.Close() + + logger := zerolog.Nop() + rpcClient, err := NewRPCClient([]string{server.URL}, 1, logger) + require.NoError(t, err) + defer rpcClient.Close() + + memDB, err := db.OpenInMemoryDB(true) + require.NoError(t, err) + defer memDB.Close() + + ec := NewEventConfirmer(rpcClient, memDB, "eip155:1", 5, 5, 12, logger) + cs := common.NewChainStore(memDB) + + pending := &store.Event{ + EventID: txHash + ":0", + BlockHeight: 150, + Type: store.EventTypeInbound, + ConfirmationType: store.ConfirmationStandard, + Status: store.StatusPending, + EventData: []byte(`{}`), + } + inserted, err := cs.InsertEventIfNotExists(pending) + require.NoError(t, err) + require.True(t, inserted) + + require.NoError(t, ec.processPendingEvents(context.Background())) + + var got store.Event + require.NoError(t, memDB.Client().Where("event_id = ?", pending.EventID).First(&got).Error) + assert.Equal(t, store.StatusPending, got.Status, + "a tx block above the observed tip must defer, not confirm") +} diff --git a/universalClient/chains/svm/client.go b/universalClient/chains/svm/client.go index 9e96f95fa..e63af21df 100644 --- a/universalClient/chains/svm/client.go +++ b/universalClient/chains/svm/client.go @@ -18,11 +18,12 @@ import ( // Client implements the ChainClient interface for Solana chains type Client struct { // Core configuration - logger zerolog.Logger - chainIDStr string - genesisHash string - registryConfig *uregistrytypes.ChainConfig - chainConfig *config.ChainSpecificConfig + logger zerolog.Logger + chainIDStr string + genesisHash string + registryConfig *uregistrytypes.ChainConfig + chainConfig *config.ChainSpecificConfig + allowZeroConfirmations bool // Infrastructure rpcClient *RPCClient @@ -51,6 +52,7 @@ func NewClient( chainConfig *config.ChainSpecificConfig, pushSigner *pushsigner.Signer, nodeHome string, + allowZeroConfirmations bool, logger zerolog.Logger, ) (*Client, error) { if config == nil { @@ -76,14 +78,15 @@ func NewClient( } client := &Client{ - logger: log, - chainIDStr: chainIDStr, - genesisHash: genesisHash, - registryConfig: config, - chainConfig: chainConfig, - database: database, - pushSigner: pushSigner, - nodeHome: nodeHome, + logger: log, + chainIDStr: chainIDStr, + genesisHash: genesisHash, + registryConfig: config, + chainConfig: chainConfig, + allowZeroConfirmations: allowZeroConfirmations, + database: database, + pushSigner: pushSigner, + nodeHome: nodeHome, } client.eventCleaner = common.NewEventCleaner( @@ -365,8 +368,8 @@ func (c *Client) applyDefaults() componentConfig { config := componentConfig{ eventPollingInterval: 5, // default gasPriceInterval: 30, // default - fastConfirmations: 5, // Solana fast confirmations - standardConfirmations: 12, // Solana standard confirmations + fastConfirmations: common.DefaultFastConfirmations, + standardConfirmations: common.DefaultStandardConfirmations, rentReclaimSweepInterval: rentReclaimSweepInterval, rentReclaimMinPDAAge: rentReclaimMinPDAAge, } @@ -408,6 +411,17 @@ func (c *Client) applyDefaults() componentConfig { config.standardConfirmations = uint64(c.registryConfig.BlockConfirmation.StandardInbound) } + // A registry-configured 0 disables the reorg-safety depth. Honor it only + // when instant routes are enabled; otherwise fall back to a safe default. + if !c.allowZeroConfirmations { + if config.fastConfirmations == 0 { + config.fastConfirmations = common.DefaultFastConfirmations + } + if config.standardConfirmations == 0 { + config.standardConfirmations = common.DefaultStandardConfirmations + } + } + return config } diff --git a/universalClient/chains/svm/client_test.go b/universalClient/chains/svm/client_test.go index 50f1084f7..36a5a91bf 100644 --- a/universalClient/chains/svm/client_test.go +++ b/universalClient/chains/svm/client_test.go @@ -35,7 +35,7 @@ func validChainConfig() *uregistrytypes.ChainConfig { func TestNewClient_NilConfig(t *testing.T) { logger := zerolog.New(zerolog.NewTestWriter(t)) - client, err := NewClient(nil, nil, nil, nil, "", logger) + client, err := NewClient(nil, nil, nil, nil, "", false, logger) assert.Error(t, err) assert.Nil(t, client) assert.Contains(t, err.Error(), "config is nil") @@ -49,7 +49,7 @@ func TestNewClient_InvalidVMType(t *testing.T) { VmType: uregistrytypes.VmType_EVM, // wrong VM type } - client, err := NewClient(cfg, nil, nil, nil, "", logger) + client, err := NewClient(cfg, nil, nil, nil, "", false, logger) assert.Error(t, err) assert.Nil(t, client) assert.Contains(t, err.Error(), "invalid VM type for Solana client") @@ -63,7 +63,7 @@ func TestNewClient_InvalidChainID(t *testing.T) { VmType: uregistrytypes.VmType_SVM, } - client, err := NewClient(cfg, nil, testChainConfig([]string{"https://rpc.example.com"}), nil, "", logger) + client, err := NewClient(cfg, nil, testChainConfig([]string{"https://rpc.example.com"}), nil, "", false, logger) assert.Error(t, err) assert.Nil(t, client) assert.Contains(t, err.Error(), "failed to parse chain ID") @@ -74,7 +74,7 @@ func TestNewClient_NoRPCURLs_NilChainConfig(t *testing.T) { cfg := validChainConfig() - client, err := NewClient(cfg, nil, nil, nil, "", logger) + client, err := NewClient(cfg, nil, nil, nil, "", false, logger) assert.Error(t, err) assert.Nil(t, client) assert.Contains(t, err.Error(), "no RPC URLs configured") @@ -85,7 +85,7 @@ func TestNewClient_NoRPCURLs_EmptySlice(t *testing.T) { cfg := validChainConfig() - client, err := NewClient(cfg, nil, testChainConfig([]string{}), nil, "", logger) + client, err := NewClient(cfg, nil, testChainConfig([]string{}), nil, "", false, logger) assert.Error(t, err) assert.Nil(t, client) assert.Contains(t, err.Error(), "no RPC URLs configured") @@ -103,7 +103,7 @@ func TestNewClient_ValidCreation(t *testing.T) { chainSpecific := testChainConfig([]string{"https://api.mainnet-beta.solana.com"}) - client, err := NewClient(cfg, nil, chainSpecific, nil, "/tmp/node", logger) + client, err := NewClient(cfg, nil, chainSpecific, nil, "/tmp/node", false, logger) require.NoError(t, err) require.NotNil(t, client) @@ -122,7 +122,7 @@ func TestNewClient_WithDatabase(t *testing.T) { cfg := validChainConfig() chainSpecific := testChainConfig([]string{"https://api.mainnet-beta.solana.com"}) - client, err := NewClient(cfg, database, chainSpecific, nil, "", logger) + client, err := NewClient(cfg, database, chainSpecific, nil, "", false, logger) require.NoError(t, err) require.NotNil(t, client) assert.Equal(t, database, client.database) @@ -134,7 +134,7 @@ func TestChainID(t *testing.T) { cfg := validChainConfig() chainSpecific := testChainConfig([]string{"https://rpc.example.com"}) - client, err := NewClient(cfg, nil, chainSpecific, nil, "", logger) + client, err := NewClient(cfg, nil, chainSpecific, nil, "", false, logger) require.NoError(t, err) assert.Equal(t, validSVMChainID(), client.ChainID()) @@ -150,7 +150,7 @@ func TestGetConfig(t *testing.T) { } chainSpecific := testChainConfig([]string{"https://rpc.example.com"}) - client, err := NewClient(cfg, nil, chainSpecific, nil, "", logger) + client, err := NewClient(cfg, nil, chainSpecific, nil, "", false, logger) require.NoError(t, err) got := client.GetConfig() @@ -164,7 +164,7 @@ func TestGetTxBuilder_NilBeforeStart(t *testing.T) { cfg := validChainConfig() chainSpecific := testChainConfig([]string{"https://rpc.example.com"}) - client, err := NewClient(cfg, nil, chainSpecific, nil, "", logger) + client, err := NewClient(cfg, nil, chainSpecific, nil, "", false, logger) require.NoError(t, err) txb, err := client.GetTxBuilder() @@ -179,7 +179,7 @@ func TestIsHealthy_NotStarted(t *testing.T) { cfg := validChainConfig() chainSpecific := testChainConfig([]string{"https://rpc.example.com"}) - client, err := NewClient(cfg, nil, chainSpecific, nil, "", logger) + client, err := NewClient(cfg, nil, chainSpecific, nil, "", false, logger) require.NoError(t, err) // rpcClient is nil before Start @@ -192,7 +192,7 @@ func TestStop_BeforeStart(t *testing.T) { cfg := validChainConfig() chainSpecific := testChainConfig([]string{"https://rpc.example.com"}) - client, err := NewClient(cfg, nil, chainSpecific, nil, "", logger) + client, err := NewClient(cfg, nil, chainSpecific, nil, "", false, logger) require.NoError(t, err) // Calling Stop before Start should not panic @@ -206,7 +206,7 @@ func TestStop_CalledTwice(t *testing.T) { cfg := validChainConfig() chainSpecific := testChainConfig([]string{"https://rpc.example.com"}) - client, err := NewClient(cfg, nil, chainSpecific, nil, "", logger) + client, err := NewClient(cfg, nil, chainSpecific, nil, "", false, logger) require.NoError(t, err) // Double stop should be safe @@ -220,7 +220,7 @@ func TestApplyDefaults_AllDefaults(t *testing.T) { cfg := validChainConfig() chainSpecific := testChainConfig([]string{"https://rpc.example.com"}) - client, err := NewClient(cfg, nil, chainSpecific, nil, "", logger) + client, err := NewClient(cfg, nil, chainSpecific, nil, "", false, logger) require.NoError(t, err) defaults := client.applyDefaults() @@ -242,7 +242,7 @@ func TestApplyDefaults_EventPollingOverride(t *testing.T) { } cfg := validChainConfig() - client, err := NewClient(cfg, nil, chainSpecific, nil, "", logger) + client, err := NewClient(cfg, nil, chainSpecific, nil, "", false, logger) require.NoError(t, err) defaults := client.applyDefaults() @@ -261,7 +261,7 @@ func TestApplyDefaults_GasPriceOverride(t *testing.T) { } cfg := validChainConfig() - client, err := NewClient(cfg, nil, chainSpecific, nil, "", logger) + client, err := NewClient(cfg, nil, chainSpecific, nil, "", false, logger) require.NoError(t, err) defaults := client.applyDefaults() @@ -282,7 +282,7 @@ func TestApplyDefaults_BlockConfirmationOverride(t *testing.T) { } chainSpecific := testChainConfig([]string{"https://rpc.example.com"}) - client, err := NewClient(cfg, nil, chainSpecific, nil, "", logger) + client, err := NewClient(cfg, nil, chainSpecific, nil, "", false, logger) require.NoError(t, err) defaults := client.applyDefaults() @@ -305,7 +305,7 @@ func TestApplyDefaults_ZeroValueNotApplied(t *testing.T) { } cfg := validChainConfig() - client, err := NewClient(cfg, nil, chainSpecific, nil, "", logger) + client, err := NewClient(cfg, nil, chainSpecific, nil, "", false, logger) require.NoError(t, err) defaults := client.applyDefaults() @@ -315,6 +315,45 @@ func TestApplyDefaults_ZeroValueNotApplied(t *testing.T) { assert.Equal(t, 0, defaults.gasPriceMarkupPercent) // 0 is the default too } +// A registry-configured 0 falls back to a safe depth unless instant routes are +// enabled, in which case it is honored. +func TestApplyDefaults_ZeroConfirmations(t *testing.T) { + logger := zerolog.New(zerolog.NewTestWriter(t)) + + zeroRegistry := &uregistrytypes.ChainConfig{ + BlockConfirmation: &uregistrytypes.BlockConfirmation{ + FastInbound: 0, + StandardInbound: 0, + }, + } + + t.Run("mainnet falls back to safe depth", func(t *testing.T) { + client := &Client{ + logger: logger, + chainIDStr: "solana:mainnet", + registryConfig: zeroRegistry, + allowZeroConfirmations: false, + } + + defaults := client.applyDefaults() + assert.Equal(t, uint64(5), defaults.fastConfirmations, "zero fast must not disable depth on mainnet") + assert.Equal(t, uint64(12), defaults.standardConfirmations, "zero standard must not disable depth on mainnet") + }) + + t.Run("testnet honors zero as instant", func(t *testing.T) { + client := &Client{ + logger: logger, + chainIDStr: "solana:mainnet", + registryConfig: zeroRegistry, + allowZeroConfirmations: true, + } + + defaults := client.applyDefaults() + assert.Equal(t, uint64(0), defaults.fastConfirmations, "testnet instant route keeps zero") + assert.Equal(t, uint64(0), defaults.standardConfirmations, "testnet instant route keeps zero") + }) +} + func TestParseSolanaChainID(t *testing.T) { tests := []struct { name string @@ -404,7 +443,7 @@ func TestNewClient_FullConfigGetters(t *testing.T) { GasPriceMarkupPercent: &gasMarkup, } - client, err := NewClient(cfg, nil, chainSpecific, nil, "/tmp/home", logger) + client, err := NewClient(cfg, nil, chainSpecific, nil, "/tmp/home", false, logger) require.NoError(t, err) // Verify all getters diff --git a/universalClient/chains/svm/event_confirmer.go b/universalClient/chains/svm/event_confirmer.go index c9895ff8c..acb3f29bd 100644 --- a/universalClient/chains/svm/event_confirmer.go +++ b/universalClient/chains/svm/event_confirmer.go @@ -180,7 +180,16 @@ func (ec *EventConfirmer) processPendingEvents(ctx context.Context) error { // Check if transaction is confirmed based on confirmation type requiredConfirmations := ec.getRequiredConfirmations(event.ConfirmationType) - confirmations := latestSlot - txSlot + 1 + confirmations, ok := chaincommon.ConfirmationDepth(latestSlot, txSlot) + if !ok { + // RPC height skew: latest slot is behind the tx slot. Defer. + ec.logger.Debug(). + Str("event_id", event.EventID). + Uint64("latest_slot", latestSlot). + Uint64("tx_slot", txSlot). + Msg("latest slot behind tx slot (RPC height skew); deferring confirmation") + continue + } if confirmations >= requiredConfirmations { // GasFeeUsed for outbound events is already set by the event parser from the on-chain event data @@ -225,24 +234,13 @@ func (ec *EventConfirmer) getTxSignatureFromEventID(eventID string) string { return parts[0] } -// getRequiredConfirmations returns the required number of confirmations based on confirmation type +// getRequiredConfirmations returns the depth for a confirmation type. Values are +// resolved by applyDefaults, so a 0 here is an intentional instant route. func (ec *EventConfirmer) getRequiredConfirmations(confirmationType string) uint64 { switch confirmationType { case store.ConfirmationFast: - if ec.fastConfirmations > 0 { - return ec.fastConfirmations - } - return 5 - case store.ConfirmationStandard: - if ec.standardConfirmations > 0 { - return ec.standardConfirmations - } - return 12 + return ec.fastConfirmations default: - // Default to standard if unknown - if ec.standardConfirmations > 0 { - return ec.standardConfirmations - } - return 12 + return ec.standardConfirmations } } diff --git a/universalClient/chains/svm/event_confirmer_test.go b/universalClient/chains/svm/event_confirmer_test.go index 10f9f7979..7a812bfc3 100644 --- a/universalClient/chains/svm/event_confirmer_test.go +++ b/universalClient/chains/svm/event_confirmer_test.go @@ -129,10 +129,11 @@ func TestEventConfirmerGetRequiredConfirmations(t *testing.T) { assert.Equal(t, uint64(5), confirmations) }) - t.Run("FAST confirmation type with zero uses default", func(t *testing.T) { + t.Run("FAST confirmation type with zero honored as instant", func(t *testing.T) { + // Fallback policy lives in applyDefaults; the confirmer honors a resolved 0. confirmer := NewEventConfirmer(nil, nil, "solana:mainnet", 5, 0, 12, logger) confirmations := confirmer.getRequiredConfirmations(store.ConfirmationFast) - assert.Equal(t, uint64(5), confirmations) // Default is 5 + assert.Equal(t, uint64(0), confirmations) }) t.Run("STANDARD confirmation type with custom value", func(t *testing.T) { @@ -141,10 +142,10 @@ func TestEventConfirmerGetRequiredConfirmations(t *testing.T) { assert.Equal(t, uint64(20), confirmations) }) - t.Run("STANDARD confirmation type with zero uses default", func(t *testing.T) { + t.Run("STANDARD confirmation type with zero honored as instant", func(t *testing.T) { confirmer := NewEventConfirmer(nil, nil, "solana:mainnet", 5, 5, 0, logger) confirmations := confirmer.getRequiredConfirmations(store.ConfirmationStandard) - assert.Equal(t, uint64(12), confirmations) // Default is 12 + assert.Equal(t, uint64(0), confirmations) }) t.Run("unknown type defaults to standard configured", func(t *testing.T) { @@ -153,10 +154,10 @@ func TestEventConfirmerGetRequiredConfirmations(t *testing.T) { assert.Equal(t, uint64(25), confirmations) }) - t.Run("unknown type with zero falls back to default 12", func(t *testing.T) { + t.Run("unknown type with zero standard honored as instant", func(t *testing.T) { confirmer := NewEventConfirmer(nil, nil, "solana:mainnet", 5, 0, 0, logger) confirmations := confirmer.getRequiredConfirmations("UNKNOWN") - assert.Equal(t, uint64(12), confirmations) + assert.Equal(t, uint64(0), confirmations) }) t.Run("empty type defaults to standard", func(t *testing.T) { @@ -326,16 +327,16 @@ func TestEventConfirmerGetRequiredConfirmations_MoreEdgeCases(t *testing.T) { assert.Equal(t, uint64(10), unknown) }) - t.Run("zero fast falls back to default 5", func(t *testing.T) { + t.Run("zero fast honored as instant", func(t *testing.T) { ec := NewEventConfirmer(nil, nil, "solana:mainnet", 5, 0, 20, logger) result := ec.getRequiredConfirmations(store.ConfirmationFast) - assert.Equal(t, uint64(5), result) // default 5 + assert.Equal(t, uint64(0), result) }) - t.Run("zero standard falls back to default 12", func(t *testing.T) { + t.Run("zero standard honored as instant", func(t *testing.T) { ec := NewEventConfirmer(nil, nil, "solana:mainnet", 5, 10, 0, logger) result := ec.getRequiredConfirmations(store.ConfirmationStandard) - assert.Equal(t, uint64(12), result) // default 12 + assert.Equal(t, uint64(0), result) }) } @@ -441,3 +442,74 @@ func TestEventConfirmer_StartStop_ZeroPollInterval(t *testing.T) { t.Fatal("event confirmer did not stop after context cancellation with zero poll interval") } } + +// The skew this finding reports, end to end: the endpoint serving the +// transaction is ahead of the one serving the slot, so the tx slot is above the +// latest slot. Unchecked, latest-tx underflows to near 2^64 and clears any +// threshold. The event must stay PENDING and be retried, never confirmed. +func TestProcessPendingEvents_RPCHeightSkew_StaysPending(t *testing.T) { + sigStr := strings.Repeat("1", 64) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + body := make([]byte, r.ContentLength) + r.Body.Read(body) + bodyStr := string(body) + + switch { + case strings.Contains(bodyStr, `"getHealth"`): + w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":"ok"}`)) + case strings.Contains(bodyStr, `"getSlot"`): + // The tip endpoint lags well behind the tx endpoint. + w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":100}`)) + case strings.Contains(bodyStr, `"getTransaction"`): + // Successful tx, but at a slot the observed tip has not reached. + w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":{` + + `"slot":1000,` + + `"meta":{` + + `"err":null,` + + `"fee":5000,` + + `"preBalances":[],` + + `"postBalances":[],` + + `"logMessages":[],` + + `"status":{"Ok":null}` + + `},` + + `"transaction":["AQ==","base64"]` + + `}}`)) + default: + w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":null}`)) + } + })) + defer server.Close() + + logger := zerolog.Nop() + rpcClient, err := NewRPCClient([]string{server.URL}, "", logger) + require.NoError(t, err) + defer rpcClient.Close() + + memDB, err := db.OpenInMemoryDB(true) + require.NoError(t, err) + defer memDB.Close() + + ec := NewEventConfirmer(rpcClient, memDB, "solana:mainnet", 5, 5, 12, logger) + cs := common.NewChainStore(memDB) + + pending := &store.Event{ + EventID: sigStr + ":0", + BlockHeight: 1000, + Type: store.EventTypeInbound, + ConfirmationType: store.ConfirmationStandard, + Status: store.StatusPending, + EventData: []byte(`{}`), + } + inserted, err := cs.InsertEventIfNotExists(pending) + require.NoError(t, err) + require.True(t, inserted) + + require.NoError(t, ec.processPendingEvents(context.Background())) + + var got store.Event + require.NoError(t, memDB.Client().Where("event_id = ?", pending.EventID).First(&got).Error) + assert.Equal(t, store.StatusPending, got.Status, + "a tx slot above the observed tip must defer, not confirm") +} diff --git a/universalClient/chains/svm/rpc_client.go b/universalClient/chains/svm/rpc_client.go index fb788b7a8..3f8170351 100644 --- a/universalClient/chains/svm/rpc_client.go +++ b/universalClient/chains/svm/rpc_client.go @@ -297,9 +297,12 @@ func calculateMedian(fees []uint64) uint64 { // otherwise it returns signatures strictly older than `before`, enabling // backward pagination. func (rc *RPCClient) GetSignaturesForAddress(ctx context.Context, address solana.PublicKey, before solana.Signature) ([]*rpc.TransactionSignature, error) { - var opts *rpc.GetSignaturesForAddressOpts + // Commitment is set explicitly rather than left to the server default, so + // discovery and the slot the confirmation depth is measured against are on + // the same footing. + opts := &rpc.GetSignaturesForAddressOpts{Commitment: rpc.CommitmentFinalized} if !before.IsZero() { - opts = &rpc.GetSignaturesForAddressOpts{Before: before} + opts.Before = before } var signatures []*rpc.TransactionSignature err := rc.executeWithFailover(ctx, "get_signatures_for_address", func(client *rpc.Client) error { @@ -321,6 +324,7 @@ func (rc *RPCClient) GetTransaction(ctx context.Context, signature solana.Signat signature, &rpc.GetTransactionOpts{ Encoding: solana.EncodingBase64, + Commitment: rpc.CommitmentFinalized, MaxSupportedTransactionVersion: &maxVersion, }, ) diff --git a/universalClient/config/config_test.go b/universalClient/config/config_test.go index 1efc379da..b656c5190 100644 --- a/universalClient/config/config_test.go +++ b/universalClient/config/config_test.go @@ -321,3 +321,25 @@ func TestGetChainCleanupSettings(t *testing.T) { assert.Contains(t, err.Error(), "cleanup_interval_seconds") }) } + +func TestNetworkGating(t *testing.T) { + cases := []struct { + network string + wantTestnet bool + }{ + {"", false}, + {"mainnet", false}, + {"MAINNET", false}, + {"prod", false}, + {"testnet", true}, + {"TESTNET", true}, + {" testnet ", true}, + } + for _, tc := range cases { + t.Run("network="+tc.network, func(t *testing.T) { + c := &Config{PushNetwork: tc.network} + assert.Equal(t, tc.wantTestnet, c.IsTestnet()) + assert.Equal(t, tc.wantTestnet, c.AllowsZeroConfirmations()) + }) + } +} diff --git a/universalClient/config/default_config.json b/universalClient/config/default_config.json index 4355eace5..86867d20b 100644 --- a/universalClient/config/default_config.json +++ b/universalClient/config/default_config.json @@ -2,6 +2,7 @@ "log_level": 1, "log_format": "console", "log_sampler": false, + "push_network": "mainnet", "push_chain_id": "localchain_9000-1", "push_chain_grpc_urls": [ "localhost:9090" diff --git a/universalClient/config/types.go b/universalClient/config/types.go index 8a43a7091..7c1c39153 100644 --- a/universalClient/config/types.go +++ b/universalClient/config/types.go @@ -1,6 +1,9 @@ package config -import "fmt" +import ( + "fmt" + "strings" +) // KeyringBackend represents the type of keyring backend to use. type KeyringBackend string @@ -10,6 +13,24 @@ const ( KeyringBackendFile KeyringBackend = "file" ) +const NetworkTestnet = "testnet" + +// IsTestnet reports whether this node is on testnet. Any other value, including +// unset, is treated as mainnet. +func (c *Config) IsTestnet() bool { + return strings.EqualFold(strings.TrimSpace(c.PushNetwork), NetworkTestnet) +} + +// AllowsZeroConfirmations reports whether a registry confirmation depth of 0 is +// honored instead of falling back to a safe depth. +// +// A registry 0 is ambiguous: proto3 encodes a deliberate 0 and an unset field +// identically, so it cannot be read as "instant finality" on its own. Honoring +// it therefore needs an out-of-band signal, which today is a testnet deployment. +func (c *Config) AllowsZeroConfirmations() bool { + return c.IsTestnet() +} + // Config holds all configuration for the Universal Validator. type Config struct { // Logging @@ -27,6 +48,9 @@ type Config struct { ConfigRefreshIntervalSeconds int `json:"config_refresh_interval_seconds"` MaxRetries int `json:"max_retries"` + // PushNetwork is "mainnet" or "testnet"; unset/unknown is treated as mainnet. + PushNetwork string `json:"push_network"` + // Query Server QueryServerPort int `json:"query_server_port"` @@ -52,9 +76,9 @@ type ChainSpecificConfig struct { EventPollingIntervalSeconds *int `json:"event_polling_interval_seconds,omitempty"` EventStartFrom *int64 `json:"event_start_from,omitempty"` GasPriceIntervalSeconds *int `json:"gas_price_interval_seconds,omitempty"` - GasPriceMarkupPercent *int `json:"gas_price_markup_percent,omitempty"` // % markup on fetched gas price to handle spikes - ProtocolALT string `json:"protocol_alt,omitempty"` // Protocol ALT address (base58) for V0 transactions - TokenALTs map[string]string `json:"token_alts,omitempty"` // mint address → token ALT address (base58) + GasPriceMarkupPercent *int `json:"gas_price_markup_percent,omitempty"` // % markup on fetched gas price to handle spikes + ProtocolALT string `json:"protocol_alt,omitempty"` // Protocol ALT address (base58) for V0 transactions + TokenALTs map[string]string `json:"token_alts,omitempty"` // mint address → token ALT address (base58) // SVM rent reclaimer (orphaned StoredIxData PDA cleanup). Both default if unset. RentReclaimSweepIntervalSeconds *int `json:"rent_reclaim_sweep_interval_seconds,omitempty"` // how often to sweep From 83c145f0285a62277c35264ff6adfa518fcdfdfa Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Wed, 26 Aug 2026 07:32:04 +0530 Subject: [PATCH 25/60] fix: use a valid 20-byte signer in the gasless module-sender test (#331) The hardcoded literal decoded to 42 bytes, which F-2026-18200's signer-length guard rejects in GetAddressPair before ExecutePayload runs. Red on audit-fixes since #317. --- test/integration/uexecutor/gasless_module_sender_test.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/integration/uexecutor/gasless_module_sender_test.go b/test/integration/uexecutor/gasless_module_sender_test.go index 0cbe3078a..8ecbe7133 100644 --- a/test/integration/uexecutor/gasless_module_sender_test.go +++ b/test/integration/uexecutor/gasless_module_sender_test.go @@ -120,8 +120,13 @@ func TestGaslessExecutePayloadWithModuleSender(t *testing.T) { // The gasless message itself: signer is a relayer, the EVM caller is the // uexecutor module. This must still succeed after the x/vm change. + // testSigner (execute_payload_test.go) is a valid 20-byte account. The + // literal this test originally carried decoded to 42 bytes, which + // F-2026-18200's signer-length guard rejects in GetAddressPair before + // ExecutePayload does any work - so the test failed on an address that was + // never the thing under test. _, err = ms.ExecutePayload(ctx, &uexecutortypes.MsgExecutePayload{ - Signer: "cosmos1xpurwdecvsenyvpkxvmnge3cv93nyd34xuersef38pjnxen9xfsk2dnz8yek2drrv56qmn2ak9", + Signer: testSigner, UniversalAccountId: universalAccount, UniversalPayload: payload, VerificationData: "0x91987784d56359fa91c3e3e0332f4f0cffedf9c081eb12874a63b41d5b5e5c660dc827947c2ae26e658d0551ad4b2d2aa073d62691429a0ae239d2cc58055bf11c", From 258061f87d3bf8f440a76bd41130b3fd31337199 Mon Sep 17 00:00:00 2001 From: Aman Gupta Date: Wed, 26 Aug 2026 13:47:53 +0530 Subject: [PATCH 26/60] fix: F-2026-18142 | [Dual Defense] Fund Migration Signing Hash Uses Live Balance and Is Raceable by Dust Inflows (#303) * fix: pin fund-migration signing hash to coordinator amount, verify it against live balance (F-2026-18142) * feat(utss): pin fund-migration transfer amount on chain Admin supplies the observed balance; the keeper derives transfer_amount from it using the gas figures it fetches, stores it on the migration and emits it, so validators sign one pinned amount instead of re-deriving from a live balance. Rejects a balance that cannot cover its own fee. * test(utss): set bech32 prefixes before validating the signer Without them validSigner fails to parse and ValidateBasic returns on the signer check, so the balance assertions never ran. * fix(push): carry the pinned transfer amount into the fund migration event * fix(tss): sign the chain-pinned fund migration amount instead of a live balance * chore(tss): drop PinnedMigrationAmount, unused since the amount left the wire * test: end-to-end that the pinned migration amount reaches the wire --------- Co-authored-by: Nilesh Gupta --- api/utss/v1/tx.pulsar.go | 203 ++++++--- api/utss/v1/types.pulsar.go | 166 +++++-- proto/utss/v1/tx.proto | 6 + proto/utss/v1/types.proto | 1 + test/integration/utss/fund_migration_test.go | 89 +++- universalClient/chains/common/types.go | 42 +- universalClient/chains/evm/tx_builder.go | 63 +-- universalClient/chains/evm/tx_builder_test.go | 405 ++++++++++++------ universalClient/chains/push/event_parser.go | 1 + .../chains/push/event_parser_test.go | 40 +- .../tss/coordinator/coordinator.go | 34 +- .../tss/coordinator/msg_handler.go | 11 +- .../tss/coordinator/msg_handler_test.go | 4 +- universalClient/tss/coordinator/types.go | 8 +- universalClient/tss/eventstore/store.go | 5 - universalClient/tss/eventstore/store_test.go | 10 +- .../tss/sessionmanager/sessionmanager.go | 58 +-- .../tss/sessionmanager/sessionmanager_test.go | 73 +++- .../tss/txbroadcaster/broadcaster_test.go | 23 +- universalClient/tss/txbroadcaster/evm.go | 17 +- universalClient/tss/txflow/parse.go | 5 +- universalClient/tss/txflow/types.go | 9 +- x/utss/keeper/msg_initiate_fund_migration.go | 30 +- x/utss/keeper/msg_server.go | 2 +- x/utss/types/events.go | 5 + x/utss/types/msg_initiate_fund_migration.go | 69 +++ .../types/msg_initiate_fund_migration_test.go | 112 +++++ x/utss/types/tx.pb.go | 149 +++++-- x/utss/types/types.pb.go | 184 +++++--- 29 files changed, 1245 insertions(+), 579 deletions(-) create mode 100644 x/utss/types/msg_initiate_fund_migration.go create mode 100644 x/utss/types/msg_initiate_fund_migration_test.go diff --git a/api/utss/v1/tx.pulsar.go b/api/utss/v1/tx.pulsar.go index e063337e4..93adbc36f 100644 --- a/api/utss/v1/tx.pulsar.go +++ b/api/utss/v1/tx.pulsar.go @@ -2652,6 +2652,7 @@ var ( fd_MsgInitiateFundMigration_signer protoreflect.FieldDescriptor fd_MsgInitiateFundMigration_old_key_id protoreflect.FieldDescriptor fd_MsgInitiateFundMigration_chain protoreflect.FieldDescriptor + fd_MsgInitiateFundMigration_balance protoreflect.FieldDescriptor ) func init() { @@ -2660,6 +2661,7 @@ func init() { fd_MsgInitiateFundMigration_signer = md_MsgInitiateFundMigration.Fields().ByName("signer") fd_MsgInitiateFundMigration_old_key_id = md_MsgInitiateFundMigration.Fields().ByName("old_key_id") fd_MsgInitiateFundMigration_chain = md_MsgInitiateFundMigration.Fields().ByName("chain") + fd_MsgInitiateFundMigration_balance = md_MsgInitiateFundMigration.Fields().ByName("balance") } var _ protoreflect.Message = (*fastReflection_MsgInitiateFundMigration)(nil) @@ -2745,6 +2747,12 @@ func (x *fastReflection_MsgInitiateFundMigration) Range(f func(protoreflect.Fiel return } } + if x.Balance != "" { + value := protoreflect.ValueOfString(x.Balance) + if !f(fd_MsgInitiateFundMigration_balance, value) { + return + } + } } // Has reports whether a field is populated. @@ -2766,6 +2774,8 @@ func (x *fastReflection_MsgInitiateFundMigration) Has(fd protoreflect.FieldDescr return x.OldKeyId != "" case "utss.v1.MsgInitiateFundMigration.chain": return x.Chain != "" + case "utss.v1.MsgInitiateFundMigration.balance": + return x.Balance != "" default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: utss.v1.MsgInitiateFundMigration")) @@ -2788,6 +2798,8 @@ func (x *fastReflection_MsgInitiateFundMigration) Clear(fd protoreflect.FieldDes x.OldKeyId = "" case "utss.v1.MsgInitiateFundMigration.chain": x.Chain = "" + case "utss.v1.MsgInitiateFundMigration.balance": + x.Balance = "" default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: utss.v1.MsgInitiateFundMigration")) @@ -2813,6 +2825,9 @@ func (x *fastReflection_MsgInitiateFundMigration) Get(descriptor protoreflect.Fi case "utss.v1.MsgInitiateFundMigration.chain": value := x.Chain return protoreflect.ValueOfString(value) + case "utss.v1.MsgInitiateFundMigration.balance": + value := x.Balance + return protoreflect.ValueOfString(value) default: if descriptor.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: utss.v1.MsgInitiateFundMigration")) @@ -2839,6 +2854,8 @@ func (x *fastReflection_MsgInitiateFundMigration) Set(fd protoreflect.FieldDescr x.OldKeyId = value.Interface().(string) case "utss.v1.MsgInitiateFundMigration.chain": x.Chain = value.Interface().(string) + case "utss.v1.MsgInitiateFundMigration.balance": + x.Balance = value.Interface().(string) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: utss.v1.MsgInitiateFundMigration")) @@ -2865,6 +2882,8 @@ func (x *fastReflection_MsgInitiateFundMigration) Mutable(fd protoreflect.FieldD panic(fmt.Errorf("field old_key_id of message utss.v1.MsgInitiateFundMigration is not mutable")) case "utss.v1.MsgInitiateFundMigration.chain": panic(fmt.Errorf("field chain of message utss.v1.MsgInitiateFundMigration is not mutable")) + case "utss.v1.MsgInitiateFundMigration.balance": + panic(fmt.Errorf("field balance of message utss.v1.MsgInitiateFundMigration is not mutable")) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: utss.v1.MsgInitiateFundMigration")) @@ -2884,6 +2903,8 @@ func (x *fastReflection_MsgInitiateFundMigration) NewField(fd protoreflect.Field return protoreflect.ValueOfString("") case "utss.v1.MsgInitiateFundMigration.chain": return protoreflect.ValueOfString("") + case "utss.v1.MsgInitiateFundMigration.balance": + return protoreflect.ValueOfString("") default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: utss.v1.MsgInitiateFundMigration")) @@ -2965,6 +2986,10 @@ func (x *fastReflection_MsgInitiateFundMigration) ProtoMethods() *protoiface.Met if l > 0 { n += 1 + l + runtime.Sov(uint64(l)) } + l = len(x.Balance) + if l > 0 { + n += 1 + l + runtime.Sov(uint64(l)) + } if x.unknownFields != nil { n += len(x.unknownFields) } @@ -2994,6 +3019,13 @@ func (x *fastReflection_MsgInitiateFundMigration) ProtoMethods() *protoiface.Met i -= len(x.unknownFields) copy(dAtA[i:], x.unknownFields) } + if len(x.Balance) > 0 { + i -= len(x.Balance) + copy(dAtA[i:], x.Balance) + i = runtime.EncodeVarint(dAtA, i, uint64(len(x.Balance))) + i-- + dAtA[i] = 0x22 + } if len(x.Chain) > 0 { i -= len(x.Chain) copy(dAtA[i:], x.Chain) @@ -3160,6 +3192,38 @@ func (x *fastReflection_MsgInitiateFundMigration) ProtoMethods() *protoiface.Met } x.Chain = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 4: + if wireType != 2 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field Balance", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + if postIndex > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + x.Balance = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := runtime.Skip(dAtA[iNdEx:]) @@ -4800,6 +4864,12 @@ type MsgInitiateFundMigration struct { Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` OldKeyId string `protobuf:"bytes,2,opt,name=old_key_id,json=oldKeyId,proto3" json:"old_key_id,omitempty"` Chain string `protobuf:"bytes,3,opt,name=chain,proto3" json:"chain,omitempty"` // CAIP-2 chain identifier + // Native balance (wei, uint256 decimal) observed by the admin on the old TSS + // address. The chain derives transfer_amount = balance - gas - l1_gas_fee from + // it, using the same fee figures it pins into the migration record, so every + // universal validator signs one amount instead of re-deriving it from a live + // balance that a 1-wei inflow can shift (F-2026-18142). + Balance string `protobuf:"bytes,4,opt,name=balance,proto3" json:"balance,omitempty"` } func (x *MsgInitiateFundMigration) Reset() { @@ -4843,6 +4913,13 @@ func (x *MsgInitiateFundMigration) GetChain() string { return "" } +func (x *MsgInitiateFundMigration) GetBalance() string { + if x != nil { + return x.Balance + } + return "" +} + type MsgInitiateFundMigrationResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -5016,7 +5093,7 @@ var file_utss_v1_tx_proto_rawDesc = []byte{ 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x54, 0x73, 0x73, 0x4b, 0x65, 0x79, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x22, 0x1e, 0x0a, 0x1c, 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x54, 0x73, 0x73, 0x4b, 0x65, 0x79, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xaf, 0x01, 0x0a, 0x18, 0x4d, 0x73, 0x67, 0x49, 0x6e, 0x69, 0x74, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xc9, 0x01, 0x0a, 0x18, 0x4d, 0x73, 0x67, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x65, 0x46, 0x75, 0x6e, 0x64, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x30, 0x0a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64, @@ -5024,68 +5101,70 @@ var file_utss_v1_tx_proto_rawDesc = []byte{ 0x6e, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x0a, 0x6f, 0x6c, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x6c, 0x64, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x3a, 0x2d, 0x82, 0xe7, 0xb0, 0x2a, 0x06, 0x73, 0x69, - 0x67, 0x6e, 0x65, 0x72, 0x8a, 0xe7, 0xb0, 0x2a, 0x1d, 0x75, 0x74, 0x73, 0x73, 0x2f, 0x4d, 0x73, - 0x67, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x65, 0x46, 0x75, 0x6e, 0x64, 0x4d, 0x69, 0x67, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x45, 0x0a, 0x20, 0x4d, 0x73, 0x67, 0x49, 0x6e, 0x69, - 0x74, 0x69, 0x61, 0x74, 0x65, 0x46, 0x75, 0x6e, 0x64, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x69, - 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x0b, 0x6d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0xc9, 0x01, - 0x0a, 0x14, 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x46, 0x75, 0x6e, 0x64, 0x4d, 0x69, 0x67, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x30, 0x0a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, - 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, - 0x52, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x69, 0x67, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, - 0x6d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x74, - 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x78, - 0x48, 0x61, 0x73, 0x68, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x3a, 0x29, - 0x82, 0xe7, 0xb0, 0x2a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x8a, 0xe7, 0xb0, 0x2a, 0x19, - 0x75, 0x74, 0x73, 0x73, 0x2f, 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x46, 0x75, 0x6e, 0x64, - 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x1e, 0x0a, 0x1c, 0x4d, 0x73, 0x67, - 0x56, 0x6f, 0x74, 0x65, 0x46, 0x75, 0x6e, 0x64, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xdc, 0x03, 0x0a, 0x03, 0x4d, 0x73, - 0x67, 0x12, 0x4a, 0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, - 0x73, 0x12, 0x18, 0x2e, 0x75, 0x74, 0x73, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x1a, 0x20, 0x2e, 0x75, 0x74, + 0x52, 0x05, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, + 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, + 0x65, 0x3a, 0x2d, 0x82, 0xe7, 0xb0, 0x2a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x8a, 0xe7, + 0xb0, 0x2a, 0x1d, 0x75, 0x74, 0x73, 0x73, 0x2f, 0x4d, 0x73, 0x67, 0x49, 0x6e, 0x69, 0x74, 0x69, + 0x61, 0x74, 0x65, 0x46, 0x75, 0x6e, 0x64, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x22, 0x45, 0x0a, 0x20, 0x4d, 0x73, 0x67, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x65, 0x46, + 0x75, 0x6e, 0x64, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x69, 0x67, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0xc9, 0x01, 0x0a, 0x14, 0x4d, 0x73, 0x67, 0x56, + 0x6f, 0x74, 0x65, 0x46, 0x75, 0x6e, 0x64, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x30, 0x0a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x06, 0x73, 0x69, 0x67, 0x6e, + 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x69, 0x67, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x18, + 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x3a, 0x29, 0x82, 0xe7, 0xb0, 0x2a, 0x06, 0x73, + 0x69, 0x67, 0x6e, 0x65, 0x72, 0x8a, 0xe7, 0xb0, 0x2a, 0x19, 0x75, 0x74, 0x73, 0x73, 0x2f, 0x4d, + 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x46, 0x75, 0x6e, 0x64, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x22, 0x1e, 0x0a, 0x1c, 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x46, 0x75, + 0x6e, 0x64, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x32, 0xdc, 0x03, 0x0a, 0x03, 0x4d, 0x73, 0x67, 0x12, 0x4a, 0x0a, 0x0c, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x18, 0x2e, 0x75, 0x74, 0x73, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, - 0x61, 0x72, 0x61, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x65, 0x0a, - 0x15, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x65, 0x54, 0x73, 0x73, 0x4b, 0x65, 0x79, 0x50, - 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x12, 0x21, 0x2e, 0x75, 0x74, 0x73, 0x73, 0x2e, 0x76, 0x31, - 0x2e, 0x4d, 0x73, 0x67, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x65, 0x54, 0x73, 0x73, 0x4b, - 0x65, 0x79, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x1a, 0x29, 0x2e, 0x75, 0x74, 0x73, 0x73, - 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x65, 0x54, - 0x73, 0x73, 0x4b, 0x65, 0x79, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x59, 0x0a, 0x11, 0x56, 0x6f, 0x74, 0x65, 0x54, 0x73, 0x73, 0x4b, - 0x65, 0x79, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x12, 0x1d, 0x2e, 0x75, 0x74, 0x73, 0x73, - 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x54, 0x73, 0x73, 0x4b, 0x65, - 0x79, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x1a, 0x25, 0x2e, 0x75, 0x74, 0x73, 0x73, 0x2e, - 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x54, 0x73, 0x73, 0x4b, 0x65, 0x79, - 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x65, 0x0a, 0x15, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x65, 0x46, 0x75, 0x6e, 0x64, 0x4d, - 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x2e, 0x75, 0x74, 0x73, 0x73, 0x2e, - 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x65, 0x46, 0x75, - 0x6e, 0x64, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x29, 0x2e, 0x75, 0x74, - 0x73, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, - 0x65, 0x46, 0x75, 0x6e, 0x64, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x59, 0x0a, 0x11, 0x56, 0x6f, 0x74, 0x65, 0x46, 0x75, - 0x6e, 0x64, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x2e, 0x75, 0x74, - 0x73, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x46, 0x75, 0x6e, - 0x64, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x25, 0x2e, 0x75, 0x74, 0x73, - 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x46, 0x75, 0x6e, 0x64, - 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x1a, 0x05, 0x80, 0xe7, 0xb0, 0x2a, 0x01, 0x42, 0x8c, 0x01, 0x0a, 0x0b, 0x63, 0x6f, 0x6d, - 0x2e, 0x75, 0x74, 0x73, 0x73, 0x2e, 0x76, 0x31, 0x42, 0x07, 0x54, 0x78, 0x50, 0x72, 0x6f, 0x74, - 0x6f, 0x50, 0x01, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x70, 0x75, 0x73, 0x68, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2f, 0x70, 0x75, 0x73, 0x68, 0x2d, 0x63, - 0x68, 0x61, 0x69, 0x6e, 0x2d, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x75, 0x74, - 0x73, 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x75, 0x74, 0x73, 0x73, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x55, - 0x58, 0x58, 0xaa, 0x02, 0x07, 0x55, 0x74, 0x73, 0x73, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x07, 0x55, - 0x74, 0x73, 0x73, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x13, 0x55, 0x74, 0x73, 0x73, 0x5c, 0x56, 0x31, - 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x08, 0x55, - 0x74, 0x73, 0x73, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x61, 0x72, 0x61, 0x6d, 0x73, 0x1a, 0x20, 0x2e, 0x75, 0x74, 0x73, 0x73, 0x2e, 0x76, 0x31, 0x2e, + 0x4d, 0x73, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x65, 0x0a, 0x15, 0x49, 0x6e, 0x69, 0x74, 0x69, + 0x61, 0x74, 0x65, 0x54, 0x73, 0x73, 0x4b, 0x65, 0x79, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, + 0x12, 0x21, 0x2e, 0x75, 0x74, 0x73, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x49, 0x6e, + 0x69, 0x74, 0x69, 0x61, 0x74, 0x65, 0x54, 0x73, 0x73, 0x4b, 0x65, 0x79, 0x50, 0x72, 0x6f, 0x63, + 0x65, 0x73, 0x73, 0x1a, 0x29, 0x2e, 0x75, 0x74, 0x73, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, + 0x67, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x65, 0x54, 0x73, 0x73, 0x4b, 0x65, 0x79, 0x50, + 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x59, + 0x0a, 0x11, 0x56, 0x6f, 0x74, 0x65, 0x54, 0x73, 0x73, 0x4b, 0x65, 0x79, 0x50, 0x72, 0x6f, 0x63, + 0x65, 0x73, 0x73, 0x12, 0x1d, 0x2e, 0x75, 0x74, 0x73, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, + 0x67, 0x56, 0x6f, 0x74, 0x65, 0x54, 0x73, 0x73, 0x4b, 0x65, 0x79, 0x50, 0x72, 0x6f, 0x63, 0x65, + 0x73, 0x73, 0x1a, 0x25, 0x2e, 0x75, 0x74, 0x73, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, + 0x56, 0x6f, 0x74, 0x65, 0x54, 0x73, 0x73, 0x4b, 0x65, 0x79, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x65, 0x0a, 0x15, 0x49, 0x6e, 0x69, + 0x74, 0x69, 0x61, 0x74, 0x65, 0x46, 0x75, 0x6e, 0x64, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x21, 0x2e, 0x75, 0x74, 0x73, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, + 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x65, 0x46, 0x75, 0x6e, 0x64, 0x4d, 0x69, 0x67, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x29, 0x2e, 0x75, 0x74, 0x73, 0x73, 0x2e, 0x76, 0x31, 0x2e, + 0x4d, 0x73, 0x67, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x65, 0x46, 0x75, 0x6e, 0x64, 0x4d, + 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x59, 0x0a, 0x11, 0x56, 0x6f, 0x74, 0x65, 0x46, 0x75, 0x6e, 0x64, 0x4d, 0x69, 0x67, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x2e, 0x75, 0x74, 0x73, 0x73, 0x2e, 0x76, 0x31, 0x2e, + 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x46, 0x75, 0x6e, 0x64, 0x4d, 0x69, 0x67, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x25, 0x2e, 0x75, 0x74, 0x73, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4d, + 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x46, 0x75, 0x6e, 0x64, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x05, 0x80, 0xe7, 0xb0, + 0x2a, 0x01, 0x42, 0x8c, 0x01, 0x0a, 0x0b, 0x63, 0x6f, 0x6d, 0x2e, 0x75, 0x74, 0x73, 0x73, 0x2e, + 0x76, 0x31, 0x42, 0x07, 0x54, 0x78, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x37, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x75, 0x73, 0x68, 0x63, 0x68, + 0x61, 0x69, 0x6e, 0x2f, 0x70, 0x75, 0x73, 0x68, 0x2d, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2d, 0x6e, + 0x6f, 0x64, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x75, 0x74, 0x73, 0x73, 0x2f, 0x76, 0x31, 0x3b, + 0x75, 0x74, 0x73, 0x73, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x55, 0x58, 0x58, 0xaa, 0x02, 0x07, 0x55, + 0x74, 0x73, 0x73, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x07, 0x55, 0x74, 0x73, 0x73, 0x5c, 0x56, 0x31, + 0xe2, 0x02, 0x13, 0x55, 0x74, 0x73, 0x73, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x08, 0x55, 0x74, 0x73, 0x73, 0x3a, 0x3a, 0x56, + 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/api/utss/v1/types.pulsar.go b/api/utss/v1/types.pulsar.go index f9694e7c2..9d3acdaaa 100644 --- a/api/utss/v1/types.pulsar.go +++ b/api/utss/v1/types.pulsar.go @@ -2883,6 +2883,7 @@ var ( fd_FundMigration_gas_price protoreflect.FieldDescriptor fd_FundMigration_gas_limit protoreflect.FieldDescriptor fd_FundMigration_l1_gas_fee protoreflect.FieldDescriptor + fd_FundMigration_transfer_amount protoreflect.FieldDescriptor ) func init() { @@ -2901,6 +2902,7 @@ func init() { fd_FundMigration_gas_price = md_FundMigration.Fields().ByName("gas_price") fd_FundMigration_gas_limit = md_FundMigration.Fields().ByName("gas_limit") fd_FundMigration_l1_gas_fee = md_FundMigration.Fields().ByName("l1_gas_fee") + fd_FundMigration_transfer_amount = md_FundMigration.Fields().ByName("transfer_amount") } var _ protoreflect.Message = (*fastReflection_FundMigration)(nil) @@ -3046,6 +3048,12 @@ func (x *fastReflection_FundMigration) Range(f func(protoreflect.FieldDescriptor return } } + if x.TransferAmount != "" { + value := protoreflect.ValueOfString(x.TransferAmount) + if !f(fd_FundMigration_transfer_amount, value) { + return + } + } } // Has reports whether a field is populated. @@ -3087,6 +3095,8 @@ func (x *fastReflection_FundMigration) Has(fd protoreflect.FieldDescriptor) bool return x.GasLimit != uint64(0) case "utss.v1.FundMigration.l1_gas_fee": return x.L1GasFee != "" + case "utss.v1.FundMigration.transfer_amount": + return x.TransferAmount != "" default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: utss.v1.FundMigration")) @@ -3129,6 +3139,8 @@ func (x *fastReflection_FundMigration) Clear(fd protoreflect.FieldDescriptor) { x.GasLimit = uint64(0) case "utss.v1.FundMigration.l1_gas_fee": x.L1GasFee = "" + case "utss.v1.FundMigration.transfer_amount": + x.TransferAmount = "" default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: utss.v1.FundMigration")) @@ -3184,6 +3196,9 @@ func (x *fastReflection_FundMigration) Get(descriptor protoreflect.FieldDescript case "utss.v1.FundMigration.l1_gas_fee": value := x.L1GasFee return protoreflect.ValueOfString(value) + case "utss.v1.FundMigration.transfer_amount": + value := x.TransferAmount + return protoreflect.ValueOfString(value) default: if descriptor.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: utss.v1.FundMigration")) @@ -3230,6 +3245,8 @@ func (x *fastReflection_FundMigration) Set(fd protoreflect.FieldDescriptor, valu x.GasLimit = value.Uint() case "utss.v1.FundMigration.l1_gas_fee": x.L1GasFee = value.Interface().(string) + case "utss.v1.FundMigration.transfer_amount": + x.TransferAmount = value.Interface().(string) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: utss.v1.FundMigration")) @@ -3276,6 +3293,8 @@ func (x *fastReflection_FundMigration) Mutable(fd protoreflect.FieldDescriptor) panic(fmt.Errorf("field gas_limit of message utss.v1.FundMigration is not mutable")) case "utss.v1.FundMigration.l1_gas_fee": panic(fmt.Errorf("field l1_gas_fee of message utss.v1.FundMigration is not mutable")) + case "utss.v1.FundMigration.transfer_amount": + panic(fmt.Errorf("field transfer_amount of message utss.v1.FundMigration is not mutable")) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: utss.v1.FundMigration")) @@ -3315,6 +3334,8 @@ func (x *fastReflection_FundMigration) NewField(fd protoreflect.FieldDescriptor) return protoreflect.ValueOfUint64(uint64(0)) case "utss.v1.FundMigration.l1_gas_fee": return protoreflect.ValueOfString("") + case "utss.v1.FundMigration.transfer_amount": + return protoreflect.ValueOfString("") default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: utss.v1.FundMigration")) @@ -3431,6 +3452,10 @@ func (x *fastReflection_FundMigration) ProtoMethods() *protoiface.Methods { if l > 0 { n += 1 + l + runtime.Sov(uint64(l)) } + l = len(x.TransferAmount) + if l > 0 { + n += 1 + l + runtime.Sov(uint64(l)) + } if x.unknownFields != nil { n += len(x.unknownFields) } @@ -3460,6 +3485,13 @@ func (x *fastReflection_FundMigration) ProtoMethods() *protoiface.Methods { i -= len(x.unknownFields) copy(dAtA[i:], x.unknownFields) } + if len(x.TransferAmount) > 0 { + i -= len(x.TransferAmount) + copy(dAtA[i:], x.TransferAmount) + i = runtime.EncodeVarint(dAtA, i, uint64(len(x.TransferAmount))) + i-- + dAtA[i] = 0x72 + } if len(x.L1GasFee) > 0 { i -= len(x.L1GasFee) copy(dAtA[i:], x.L1GasFee) @@ -3941,6 +3973,38 @@ func (x *fastReflection_FundMigration) ProtoMethods() *protoiface.Methods { } x.L1GasFee = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 14: + if wireType != 2 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field TransferAmount", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + if postIndex > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + x.TransferAmount = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := runtime.Skip(dAtA[iNdEx:]) @@ -4545,9 +4609,10 @@ type FundMigration struct { InitiatedBlock int64 `protobuf:"varint,8,opt,name=initiated_block,json=initiatedBlock,proto3" json:"initiated_block,omitempty"` CompletedBlock int64 `protobuf:"varint,9,opt,name=completed_block,json=completedBlock,proto3" json:"completed_block,omitempty"` TxHash string `protobuf:"bytes,10,opt,name=tx_hash,json=txHash,proto3" json:"tx_hash,omitempty"` - GasPrice string `protobuf:"bytes,11,opt,name=gas_price,json=gasPrice,proto3" json:"gas_price,omitempty"` // gas price from oracle (wei) - GasLimit uint64 `protobuf:"varint,12,opt,name=gas_limit,json=gasLimit,proto3" json:"gas_limit,omitempty"` // gas limit sourced from UniversalCore per chain namespace - L1GasFee string `protobuf:"bytes,13,opt,name=l1_gas_fee,json=l1GasFee,proto3" json:"l1_gas_fee,omitempty"` // L1 data-availability fee (wei) from UniversalCore; 0 for non-L2 chains + GasPrice string `protobuf:"bytes,11,opt,name=gas_price,json=gasPrice,proto3" json:"gas_price,omitempty"` // gas price from oracle (wei) + GasLimit uint64 `protobuf:"varint,12,opt,name=gas_limit,json=gasLimit,proto3" json:"gas_limit,omitempty"` // gas limit sourced from UniversalCore per chain namespace + L1GasFee string `protobuf:"bytes,13,opt,name=l1_gas_fee,json=l1GasFee,proto3" json:"l1_gas_fee,omitempty"` // L1 data-availability fee (wei) from UniversalCore; 0 for non-L2 chains + TransferAmount string `protobuf:"bytes,14,opt,name=transfer_amount,json=transferAmount,proto3" json:"transfer_amount,omitempty"` // native amount (wei) to sweep, derived at initiate time as balance - (gas_price * gas_limit) - l1_gas_fee } func (x *FundMigration) Reset() { @@ -4661,6 +4726,13 @@ func (x *FundMigration) GetL1GasFee() string { return "" } +func (x *FundMigration) GetTransferAmount() string { + if x != nil { + return x.TransferAmount + } + return "" +} + var File_utss_v1_types_proto protoreflect.FileDescriptor var file_utss_v1_types_proto_rawDesc = []byte{ @@ -4732,7 +4804,7 @@ var file_utss_v1_types_proto_rawDesc = []byte{ 0x69, 0x67, 0x68, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6b, 0x65, 0x79, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x73, 0x73, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x74, 0x73, 0x73, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x22, 0xc6, 0x03, 0x0a, 0x0d, 0x46, + 0x09, 0x74, 0x73, 0x73, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x22, 0xef, 0x03, 0x0a, 0x0d, 0x46, 0x75, 0x6e, 0x64, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1c, 0x0a, 0x0a, 0x6f, 0x6c, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, @@ -4761,48 +4833,50 @@ var file_utss_v1_types_proto_rawDesc = []byte{ 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x67, 0x61, 0x73, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1c, 0x0a, 0x0a, 0x6c, 0x31, 0x5f, 0x67, 0x61, 0x73, 0x5f, 0x66, 0x65, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x31, 0x47, 0x61, 0x73, - 0x46, 0x65, 0x65, 0x2a, 0x6b, 0x0a, 0x13, 0x54, 0x73, 0x73, 0x4b, 0x65, 0x79, 0x50, 0x72, 0x6f, - 0x63, 0x65, 0x73, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x17, 0x54, 0x53, - 0x53, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x50, 0x52, 0x4f, 0x43, 0x45, 0x53, 0x53, 0x5f, 0x50, 0x45, - 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x54, 0x53, 0x53, 0x5f, 0x4b, - 0x45, 0x59, 0x5f, 0x50, 0x52, 0x4f, 0x43, 0x45, 0x53, 0x53, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, - 0x53, 0x53, 0x10, 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x54, 0x53, 0x53, 0x5f, 0x4b, 0x45, 0x59, 0x5f, - 0x50, 0x52, 0x4f, 0x43, 0x45, 0x53, 0x53, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x02, - 0x2a, 0x60, 0x0a, 0x0e, 0x54, 0x73, 0x73, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x16, 0x0a, 0x12, 0x54, 0x53, 0x53, 0x5f, 0x50, 0x52, 0x4f, 0x43, 0x45, 0x53, - 0x53, 0x5f, 0x4b, 0x45, 0x59, 0x47, 0x45, 0x4e, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x54, 0x53, - 0x53, 0x5f, 0x50, 0x52, 0x4f, 0x43, 0x45, 0x53, 0x53, 0x5f, 0x52, 0x45, 0x46, 0x52, 0x45, 0x53, - 0x48, 0x10, 0x01, 0x12, 0x1d, 0x0a, 0x19, 0x54, 0x53, 0x53, 0x5f, 0x50, 0x52, 0x4f, 0x43, 0x45, - 0x53, 0x53, 0x5f, 0x51, 0x55, 0x4f, 0x52, 0x55, 0x4d, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, - 0x10, 0x02, 0x2a, 0x4c, 0x0a, 0x0c, 0x54, 0x73, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x1b, 0x54, 0x53, 0x53, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, - 0x50, 0x52, 0x4f, 0x43, 0x45, 0x53, 0x53, 0x5f, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x45, - 0x44, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x54, 0x53, 0x53, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, - 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x46, 0x49, 0x4e, 0x41, 0x4c, 0x49, 0x5a, 0x45, 0x44, 0x10, 0x01, - 0x2a, 0x56, 0x0a, 0x0e, 0x54, 0x73, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x53, 0x53, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, - 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x54, 0x53, 0x53, 0x5f, - 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, - 0x01, 0x12, 0x15, 0x0a, 0x11, 0x54, 0x53, 0x53, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x45, - 0x58, 0x50, 0x49, 0x52, 0x45, 0x44, 0x10, 0x02, 0x2a, 0x7f, 0x0a, 0x13, 0x46, 0x75, 0x6e, 0x64, - 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, - 0x21, 0x0a, 0x1d, 0x46, 0x55, 0x4e, 0x44, 0x5f, 0x4d, 0x49, 0x47, 0x52, 0x41, 0x54, 0x49, 0x4f, - 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, - 0x10, 0x00, 0x12, 0x23, 0x0a, 0x1f, 0x46, 0x55, 0x4e, 0x44, 0x5f, 0x4d, 0x49, 0x47, 0x52, 0x41, - 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x43, 0x4f, 0x4d, 0x50, - 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x20, 0x0a, 0x1c, 0x46, 0x55, 0x4e, 0x44, 0x5f, - 0x4d, 0x49, 0x47, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, - 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x42, 0x8f, 0x01, 0x0a, 0x0b, 0x63, 0x6f, - 0x6d, 0x2e, 0x75, 0x74, 0x73, 0x73, 0x2e, 0x76, 0x31, 0x42, 0x0a, 0x54, 0x79, 0x70, 0x65, 0x73, - 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x75, 0x73, 0x68, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2f, 0x70, 0x75, - 0x73, 0x68, 0x2d, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2d, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x61, 0x70, - 0x69, 0x2f, 0x75, 0x74, 0x73, 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x75, 0x74, 0x73, 0x73, 0x76, 0x31, - 0xa2, 0x02, 0x03, 0x55, 0x58, 0x58, 0xaa, 0x02, 0x07, 0x55, 0x74, 0x73, 0x73, 0x2e, 0x56, 0x31, - 0xca, 0x02, 0x07, 0x55, 0x74, 0x73, 0x73, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x13, 0x55, 0x74, 0x73, - 0x73, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0xea, 0x02, 0x08, 0x55, 0x74, 0x73, 0x73, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x46, 0x65, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x5f, + 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x74, 0x72, + 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x2a, 0x6b, 0x0a, 0x13, + 0x54, 0x73, 0x73, 0x4b, 0x65, 0x79, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x17, 0x54, 0x53, 0x53, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x50, + 0x52, 0x4f, 0x43, 0x45, 0x53, 0x53, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x00, + 0x12, 0x1b, 0x0a, 0x17, 0x54, 0x53, 0x53, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x50, 0x52, 0x4f, 0x43, + 0x45, 0x53, 0x53, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x01, 0x12, 0x1a, 0x0a, + 0x16, 0x54, 0x53, 0x53, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x50, 0x52, 0x4f, 0x43, 0x45, 0x53, 0x53, + 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x2a, 0x60, 0x0a, 0x0e, 0x54, 0x73, 0x73, + 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x12, 0x54, + 0x53, 0x53, 0x5f, 0x50, 0x52, 0x4f, 0x43, 0x45, 0x53, 0x53, 0x5f, 0x4b, 0x45, 0x59, 0x47, 0x45, + 0x4e, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x54, 0x53, 0x53, 0x5f, 0x50, 0x52, 0x4f, 0x43, 0x45, + 0x53, 0x53, 0x5f, 0x52, 0x45, 0x46, 0x52, 0x45, 0x53, 0x48, 0x10, 0x01, 0x12, 0x1d, 0x0a, 0x19, + 0x54, 0x53, 0x53, 0x5f, 0x50, 0x52, 0x4f, 0x43, 0x45, 0x53, 0x53, 0x5f, 0x51, 0x55, 0x4f, 0x52, + 0x55, 0x4d, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x10, 0x02, 0x2a, 0x4c, 0x0a, 0x0c, 0x54, + 0x73, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x1b, 0x54, + 0x53, 0x53, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x50, 0x52, 0x4f, 0x43, 0x45, 0x53, 0x53, + 0x5f, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, + 0x54, 0x53, 0x53, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x46, 0x49, + 0x4e, 0x41, 0x4c, 0x49, 0x5a, 0x45, 0x44, 0x10, 0x01, 0x2a, 0x56, 0x0a, 0x0e, 0x54, 0x73, 0x73, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x10, 0x54, + 0x53, 0x53, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x10, + 0x00, 0x12, 0x17, 0x0a, 0x13, 0x54, 0x53, 0x53, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x43, + 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, 0x54, 0x53, + 0x53, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x45, 0x44, 0x10, + 0x02, 0x2a, 0x7f, 0x0a, 0x13, 0x46, 0x75, 0x6e, 0x64, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x21, 0x0a, 0x1d, 0x46, 0x55, 0x4e, 0x44, + 0x5f, 0x4d, 0x49, 0x47, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, + 0x53, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x00, 0x12, 0x23, 0x0a, 0x1f, 0x46, + 0x55, 0x4e, 0x44, 0x5f, 0x4d, 0x49, 0x47, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, + 0x41, 0x54, 0x55, 0x53, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x01, + 0x12, 0x20, 0x0a, 0x1c, 0x46, 0x55, 0x4e, 0x44, 0x5f, 0x4d, 0x49, 0x47, 0x52, 0x41, 0x54, 0x49, + 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, + 0x10, 0x02, 0x42, 0x8f, 0x01, 0x0a, 0x0b, 0x63, 0x6f, 0x6d, 0x2e, 0x75, 0x74, 0x73, 0x73, 0x2e, + 0x76, 0x31, 0x42, 0x0a, 0x54, 0x79, 0x70, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, + 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x75, 0x73, + 0x68, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2f, 0x70, 0x75, 0x73, 0x68, 0x2d, 0x63, 0x68, 0x61, 0x69, + 0x6e, 0x2d, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x75, 0x74, 0x73, 0x73, 0x2f, + 0x76, 0x31, 0x3b, 0x75, 0x74, 0x73, 0x73, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x55, 0x58, 0x58, 0xaa, + 0x02, 0x07, 0x55, 0x74, 0x73, 0x73, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x07, 0x55, 0x74, 0x73, 0x73, + 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x13, 0x55, 0x74, 0x73, 0x73, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, + 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x08, 0x55, 0x74, 0x73, 0x73, + 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/proto/utss/v1/tx.proto b/proto/utss/v1/tx.proto index 735c019da..e490097bc 100755 --- a/proto/utss/v1/tx.proto +++ b/proto/utss/v1/tx.proto @@ -87,6 +87,12 @@ message MsgInitiateFundMigration { string signer = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; string old_key_id = 2; string chain = 3; // CAIP-2 chain identifier + // Native balance (wei, uint256 decimal) observed by the admin on the old TSS + // address. The chain derives transfer_amount = balance - gas - l1_gas_fee from + // it, using the same fee figures it pins into the migration record, so every + // universal validator signs one amount instead of re-deriving it from a live + // balance that a 1-wei inflow can shift (F-2026-18142). + string balance = 4; } message MsgInitiateFundMigrationResponse { diff --git a/proto/utss/v1/types.proto b/proto/utss/v1/types.proto index 7084065da..9ca513147 100644 --- a/proto/utss/v1/types.proto +++ b/proto/utss/v1/types.proto @@ -105,4 +105,5 @@ message FundMigration { string gas_price = 11; // gas price from oracle (wei) uint64 gas_limit = 12; // gas limit sourced from UniversalCore per chain namespace string l1_gas_fee = 13; // L1 data-availability fee (wei) from UniversalCore; 0 for non-L2 chains + string transfer_amount = 14; // native amount (wei) to sweep, derived at initiate time as balance - (gas_price * gas_limit) - l1_gas_fee } diff --git a/test/integration/utss/fund_migration_test.go b/test/integration/utss/fund_migration_test.go index b6564ce9e..402fd4911 100644 --- a/test/integration/utss/fund_migration_test.go +++ b/test/integration/utss/fund_migration_test.go @@ -60,6 +60,11 @@ const universalCoreSetupABI = `[ } ]` +// testBalance is the native balance the admin reports observing on the old TSS +// address. Comfortably above gas_price*21000 + 150 so the derived +// transfer_amount is positive for any oracle gas price the harness produces. +const testBalance = "1000000000000000000" // 1e18 wei + // seedFundMigrationChainValues grants MANAGER_ROLE to the admin and seeds the // per-chain tss-fund-migration gas limit and L1 gas fee on UniversalCore. // InitiateFundMigration rejects a zero gas limit, so without this seeding the @@ -196,7 +201,7 @@ func TestInitiateFundMigration(t *testing.T) { t.Run("Successfully initiates fund migration", func(t *testing.T) { app, ctx, _, oldKeyId := setupFundMigrationTest(t, 3, false) - migrationId, err := app.UtssKeeper.InitiateFundMigration(ctx, oldKeyId, testChain) + migrationId, err := app.UtssKeeper.InitiateFundMigration(ctx, oldKeyId, testChain, testBalance) require.NoError(t, err) require.Equal(t, uint64(0), migrationId) @@ -228,10 +233,68 @@ func TestInitiateFundMigration(t *testing.T) { require.True(t, found, "FundMigrationInitiatedEvent should be emitted") }) + t.Run("Derives transfer_amount from the observed balance and pinned fees", func(t *testing.T) { + app, ctx, _, oldKeyId := setupFundMigrationTest(t, 3, false) + + migrationId, err := app.UtssKeeper.InitiateFundMigration(ctx, oldKeyId, testChain, testBalance) + require.NoError(t, err) + + migration, err := app.UtssKeeper.FundMigrations.Get(ctx, migrationId) + require.NoError(t, err) + + // transfer_amount must equal balance - (gas_price * gas_limit) - l1_gas_fee, + // computed from the very fields recorded alongside it. Deriving it here + // rather than accepting it from the admin is what makes the two consistent + // by construction — the admin cannot know these fees, they are read from + // UniversalCore inside the handler (F-2026-18142). + gasPrice, ok := new(big.Int).SetString(migration.GasPrice, 10) + require.True(t, ok) + l1GasFee, ok := new(big.Int).SetString(migration.L1GasFee, 10) + require.True(t, ok) + balance, ok := new(big.Int).SetString(testBalance, 10) + require.True(t, ok) + + want := new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(migration.GasLimit)) + want.Add(want, l1GasFee) + want.Sub(balance, want) + + require.Equal(t, want.String(), migration.TransferAmount) + require.Positive(t, want.Sign(), "the fixture balance must exceed the fees or this proves nothing") + + // The pinned amount must also reach the universal validators, which read + // it off the event rather than re-deriving it from a live balance. + var attr string + for _, ev := range ctx.EventManager().Events() { + if ev.Type != utsstypes.EventTypeFundMigrationInitiated { + continue + } + for _, a := range ev.Attributes { + if a.Key == "transfer_amount" { + attr = a.Value + } + } + } + require.Equal(t, migration.TransferAmount, attr, + "transfer_amount must be emitted on the event") + }) + + t.Run("Fails when the balance cannot cover the migration fee", func(t *testing.T) { + app, ctx, _, oldKeyId := setupFundMigrationTest(t, 3, false) + + // 1 wei cannot cover gas_price*21000 + 150. Rejecting at initiate time + // beats creating a PENDING migration that can never be signed. + _, err := app.UtssKeeper.InitiateFundMigration(ctx, oldKeyId, testChain, "1") + require.ErrorContains(t, err, "does not cover the migration fee") + + // Nothing may be left behind. + _, err = app.UtssKeeper.FundMigrations.Get(ctx, 0) + require.Error(t, err, "a rejected migration must not be stored") + }) + t.Run("Fails if old key not found", func(t *testing.T) { app, ctx, _, _ := setupFundMigrationTest(t, 3, false) - _, err := app.UtssKeeper.InitiateFundMigration(ctx, "nonexistent-key", testChain) + _, err := app.UtssKeeper.InitiateFundMigration(ctx, "nonexistent-key", testChain, testBalance) require.ErrorContains(t, err, "not found in TssKeyHistory") }) @@ -241,25 +304,25 @@ func TestInitiateFundMigration(t *testing.T) { currentKey, err := app.UtssKeeper.CurrentTssKey.Get(ctx) require.NoError(t, err) - _, err = app.UtssKeeper.InitiateFundMigration(ctx, currentKey.KeyId, testChain) + _, err = app.UtssKeeper.InitiateFundMigration(ctx, currentKey.KeyId, testChain, testBalance) require.ErrorContains(t, err, "current active key") }) t.Run("Fails if outbound is still enabled", func(t *testing.T) { app, ctx, _, oldKeyId := setupFundMigrationTest(t, 3, true) // outbound enabled - _, err := app.UtssKeeper.InitiateFundMigration(ctx, oldKeyId, testChain) + _, err := app.UtssKeeper.InitiateFundMigration(ctx, oldKeyId, testChain, testBalance) require.ErrorContains(t, err, "outbound is still enabled") }) t.Run("Fails if duplicate pending migration exists", func(t *testing.T) { app, ctx, _, oldKeyId := setupFundMigrationTest(t, 3, false) - _, err := app.UtssKeeper.InitiateFundMigration(ctx, oldKeyId, testChain) + _, err := app.UtssKeeper.InitiateFundMigration(ctx, oldKeyId, testChain, testBalance) require.NoError(t, err) // Try again — should fail (same chain already has pending migration) - _, err = app.UtssKeeper.InitiateFundMigration(ctx, oldKeyId, testChain) + _, err = app.UtssKeeper.InitiateFundMigration(ctx, oldKeyId, testChain, testBalance) require.ErrorContains(t, err, "pending migration already exists for chain") }) } @@ -269,7 +332,7 @@ func TestVoteFundMigration(t *testing.T) { app, ctx, universalVals, oldKeyId := setupFundMigrationTest(t, 3, false) // Initiate migration - migrationId, err := app.UtssKeeper.InitiateFundMigration(ctx, oldKeyId, testChain) + migrationId, err := app.UtssKeeper.InitiateFundMigration(ctx, oldKeyId, testChain, testBalance) require.NoError(t, err) txHash := "0xdeadbeef12345678deadbeef12345678deadbeef12345678deadbeef12345678" @@ -317,7 +380,7 @@ func TestVoteFundMigration(t *testing.T) { t.Run("Migration failure flow", func(t *testing.T) { app, ctx, universalVals, oldKeyId := setupFundMigrationTest(t, 3, false) - migrationId, err := app.UtssKeeper.InitiateFundMigration(ctx, oldKeyId, testChain) + migrationId, err := app.UtssKeeper.InitiateFundMigration(ctx, oldKeyId, testChain, testBalance) require.NoError(t, err) txHash := "" @@ -345,7 +408,7 @@ func TestVoteFundMigration(t *testing.T) { t.Run("Fails to vote on already finalized migration", func(t *testing.T) { app, ctx, universalVals, oldKeyId := setupFundMigrationTest(t, 3, false) - migrationId, err := app.UtssKeeper.InitiateFundMigration(ctx, oldKeyId, testChain) + migrationId, err := app.UtssKeeper.InitiateFundMigration(ctx, oldKeyId, testChain, testBalance) require.NoError(t, err) // Finalize it first @@ -365,7 +428,7 @@ func TestFundMigrationQueries(t *testing.T) { t.Run("GetFundMigration returns correct migration", func(t *testing.T) { app, ctx, _, oldKeyId := setupFundMigrationTest(t, 3, false) - migrationId, err := app.UtssKeeper.InitiateFundMigration(ctx, oldKeyId, testChain) + migrationId, err := app.UtssKeeper.InitiateFundMigration(ctx, oldKeyId, testChain, testBalance) require.NoError(t, err) migration, err := app.UtssKeeper.FundMigrations.Get(ctx, migrationId) @@ -377,7 +440,7 @@ func TestFundMigrationQueries(t *testing.T) { t.Run("PendingMigrations tracks correctly", func(t *testing.T) { app, ctx, universalVals, oldKeyId := setupFundMigrationTest(t, 3, false) - migrationId, err := app.UtssKeeper.InitiateFundMigration(ctx, oldKeyId, testChain) + migrationId, err := app.UtssKeeper.InitiateFundMigration(ctx, oldKeyId, testChain, testBalance) require.NoError(t, err) // Should be in pending @@ -413,7 +476,7 @@ func TestFundMigrationQueries(t *testing.T) { func TestVoteFundMigration_EquivalentHashEncodingsConverge(t *testing.T) { app, ctx, universalVals, oldKeyId := setupFundMigrationTest(t, 3, false) - migrationId, err := app.UtssKeeper.InitiateFundMigration(ctx, oldKeyId, testChain) + migrationId, err := app.UtssKeeper.InitiateFundMigration(ctx, oldKeyId, testChain, testBalance) require.NoError(t, err) canonical := "0xb28f49668e7e76dc96d7aabe5b7f63fecfbd1c3574774c05e8204e749fd96fbd" @@ -446,7 +509,7 @@ func TestVoteFundMigration_EquivalentHashEncodingsConverge(t *testing.T) { func TestVoteFundMigration_MalformedHashRejected(t *testing.T) { app, ctx, universalVals, oldKeyId := setupFundMigrationTest(t, 3, false) - migrationId, err := app.UtssKeeper.InitiateFundMigration(ctx, oldKeyId, testChain) + migrationId, err := app.UtssKeeper.InitiateFundMigration(ctx, oldKeyId, testChain, testBalance) require.NoError(t, err) valAddr, _ := sdk.ValAddressFromBech32(universalVals[0]) diff --git a/universalClient/chains/common/types.go b/universalClient/chains/common/types.go index c67343625..53a6fb615 100644 --- a/universalClient/chains/common/types.go +++ b/universalClient/chains/common/types.go @@ -26,13 +26,12 @@ type ChainClient interface { // FundMigrationData contains the data needed to build a fund migration transaction. // Populated by the coordinator from the migration event + derived addresses. type FundMigrationData struct { - From string // Old TSS address (derived from old pubkey) - To string // New TSS address (derived from current pubkey) - GasPrice *big.Int // Gas price from the migration event - GasLimit uint64 // Gas limit from the migration event - L1GasFee *big.Int // Extra L1 data-availability fee (wei); 0 for non-L2 chains - - Balance *big.Int // if nil, builder queries chain + From string // Old TSS address (derived from old pubkey) + To string // New TSS address (derived from current pubkey) + GasPrice *big.Int // Gas price from the migration event + GasLimit uint64 // Gas limit from the migration event + L1GasFee *big.Int // Extra L1 data-availability fee (wei); 0 for non-L2 chains + TransferAmount *big.Int // sweep amount pinned on chain; never derived from a live balance } // UnsignedSigningReq contains the request for signing an outbound or fund-migration transaction. @@ -40,10 +39,6 @@ type UnsignedSigningReq struct { SigningHash []byte // Hash to be signed by TSS Nonce uint64 // evm - TSS Address nonce | svm - PDA nonce - // TSSFundMigrationAmount is the native value swept for a fund-migration tx, fixed at - // signing time. Nil for outbound. Must be reused verbatim at broadcast — re-querying - // balance there races with a successful sweep from another validator. - TSSFundMigrationAmount *big.Int `json:"TSSFundMigrationAmount,omitempty"` } // TxBuilder builds and broadcasts transactions for outbound transfers @@ -96,17 +91,17 @@ type TxBuilder interface { // UniversalTx Payload type UniversalTx struct { - SourceChain string `json:"sourceChain"` - LogIndex uint `json:"logIndex"` - Sender string `json:"sender"` - Recipient string `json:"recipient"` - Token string `json:"bridgeToken"` - Amount string `json:"bridgeAmount"` // uint256 as decimal string + SourceChain string `json:"sourceChain"` + LogIndex uint `json:"logIndex"` + Sender string `json:"sender"` + Recipient string `json:"recipient"` + Token string `json:"bridgeToken"` + Amount string `json:"bridgeAmount"` // uint256 as decimal string RawPayload string `json:"rawPayload,omitempty"` // hex-encoded raw payload bytes from source chain VerificationData string `json:"verificationData"` - RevertFundRecipient string `json:"revertFundRecipient,omitempty"` - TxType uint `json:"txType"` // enum backing uint as decimal string - FromCEA bool `json:"fromCEA"` // true if inbound is initiated by a CEA + RevertFundRecipient string `json:"revertFundRecipient,omitempty"` + TxType uint `json:"txType"` // enum backing uint as decimal string + FromCEA bool `json:"fromCEA"` // true if inbound is initiated by a CEA } // OutboundEvent represents an outbound observation event from the gateway contract @@ -114,8 +109,7 @@ type UniversalTx struct { // - txID at 1st indexed position (bytes32) // - universalTxID at 2nd indexed position (bytes32) type OutboundEvent struct { - TxID string `json:"tx_id"` // bytes32 hex-encoded (0x...) - UniversalTxID string `json:"universal_tx_id"` // bytes32 hex-encoded (0x...) - GasFeeUsed string `json:"gas_fee_used,omitempty"` // gas fee used in wei (decimal string) + TxID string `json:"tx_id"` // bytes32 hex-encoded (0x...) + UniversalTxID string `json:"universal_tx_id"` // bytes32 hex-encoded (0x...) + GasFeeUsed string `json:"gas_fee_used,omitempty"` // gas fee used in wei (decimal string) } - diff --git a/universalClient/chains/evm/tx_builder.go b/universalClient/chains/evm/tx_builder.go index 7e74cd131..3bb4e4265 100644 --- a/universalClient/chains/evm/tx_builder.go +++ b/universalClient/chains/evm/tx_builder.go @@ -452,7 +452,6 @@ func (tb *TxBuilder) IsAlreadyExecuted(ctx context.Context, txID string) (bool, return false, 0, nil } - // GetGasFeeUsed returns the gas fee used by a transaction on the EVM chain: // L2 execution (gasUsed * effectiveGasPrice) plus the OP-Stack L1 data fee // (0 on non-OP chains). Errors when the fee cannot be determined so callers @@ -478,13 +477,10 @@ func gasFeeUsed(gasUsed uint64, gasPrice, l1Fee *big.Int) *big.Int { return fee.Add(fee, l1Fee) } -// GetFundMigrationSigningRequest builds a native token transfer for fund migration, -// transferring the maximum possible balance (balance minus gas cost minus L1 fee). -// Fund migration only triggers when outbound is disabled and no pending outbounds remain, -// so the balance at signing time will equal the balance at broadcast time. -// L1GasFee covers OP-stack sequencer data-availability charges; 0 for non-L2 chains. +// GetFundMigrationSigningRequest builds the native transfer sweeping the old TSS +// balance to the current one. The amount is pinned on chain, so this makes no RPC +// call and stays reproducible after the sweep has already landed. func (tb *TxBuilder) GetFundMigrationSigningRequest(ctx context.Context, data *common.FundMigrationData, nonce uint64) (*common.UnsignedSigningReq, error) { - fromAddr := ethcommon.HexToAddress(data.From) toAddr := ethcommon.HexToAddress(data.To) if data.GasPrice == nil || data.GasPrice.Sign() == 0 { @@ -494,26 +490,14 @@ func (tb *TxBuilder) GetFundMigrationSigningRequest(ctx context.Context, data *c return nil, fmt.Errorf("gas limit must be provided for fund migration") } - var balance *big.Int - if data.Balance != nil { - balance = new(big.Int).Set(data.Balance) - } else { - queried, err := tb.rpcClient.GetBalance(ctx, fromAddr) - if err != nil { - return nil, fmt.Errorf("failed to get balance of %s: %w", data.From, err) - } - balance = queried - } - - maxTransfer, err := computeFundMigrationTransfer(balance, data.GasPrice, data.GasLimit, data.L1GasFee) - if err != nil { - return nil, err + if data.TransferAmount == nil || data.TransferAmount.Sign() <= 0 { + return nil, fmt.Errorf("fund migration transfer amount is required") } + maxTransfer := new(big.Int).Set(data.TransferAmount) tb.logger.Debug(). Str("from", data.From). Str("to", data.To). - Str("balance", balance.String()). Str("gas_price", data.GasPrice.String()). Uint64("gas_limit", data.GasLimit). Str("l1_gas_fee", l1GasFeeString(data.L1GasFee)). @@ -532,17 +516,13 @@ func (tb *TxBuilder) GetFundMigrationSigningRequest(ctx context.Context, data *c signer := types.NewEIP155Signer(big.NewInt(tb.chainIDInt)) txHash := signer.Hash(tx).Bytes() - // TSSFundMigrationAmount rides alongside Nonce in the req — both are signing-time-decided - // values that must reach broadcast unchanged so the signed tx is reproduced exactly. return &common.UnsignedSigningReq{ - SigningHash: txHash, - Nonce: nonce, - TSSFundMigrationAmount: new(big.Int).Set(maxTransfer), + SigningHash: txHash, + Nonce: nonce, }, nil } // BroadcastFundMigrationTx assembles and broadcasts a signed fund migration transaction. -// Uses req.TSSFundMigrationAmount fixed at signing time — do not re-query balance. func (tb *TxBuilder) BroadcastFundMigrationTx(ctx context.Context, req *common.UnsignedSigningReq, data *common.FundMigrationData, signature []byte) (string, error) { if len(signature) != 65 { return "", fmt.Errorf("signature must be 65 bytes [r(32)|s(32)|v(1)], got %d", len(signature)) @@ -555,13 +535,11 @@ func (tb *TxBuilder) BroadcastFundMigrationTx(ctx context.Context, req *common.U return "", fmt.Errorf("gas limit must be provided for fund migration") } - // Use the exact amount fixed at signing time. Re-querying balance here would race - // with a successful broadcast from another validator (balance goes to 0 post-sweep). - if req.TSSFundMigrationAmount == nil || req.TSSFundMigrationAmount.Sign() <= 0 { - return "", fmt.Errorf("req.TSSFundMigrationAmount must be set for fund migration broadcast") + if data.TransferAmount == nil || data.TransferAmount.Sign() <= 0 { + return "", fmt.Errorf("fund migration transfer amount is required for broadcast") } toAddr := ethcommon.HexToAddress(data.To) - maxTransfer := new(big.Int).Set(req.TSSFundMigrationAmount) + maxTransfer := new(big.Int).Set(data.TransferAmount) tx := types.NewTransaction( req.Nonce, @@ -591,25 +569,6 @@ func (tb *TxBuilder) BroadcastFundMigrationTx(ctx context.Context, req *common.U return txHashStr, nil } -// computeFundMigrationTransfer returns the native amount to sweep from the old -// TSS address to the new one: balance - (gasPrice * gasLimit) - l1GasFee. -// The l1GasFee covers OP-stack sequencer data-availability charges (0 for -// non-L2 chains). All validators must compute the same value — any drift -// here breaks the TSS signing hash. -func computeFundMigrationTransfer(balance, gasPrice *big.Int, gasLimit uint64, l1GasFee *big.Int) (*big.Int, error) { - gasCost := new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(gasLimit)) - totalFee := new(big.Int).Set(gasCost) - if l1GasFee != nil && l1GasFee.Sign() > 0 { - totalFee.Add(totalFee, l1GasFee) - } - maxTransfer := new(big.Int).Sub(balance, totalFee) - if maxTransfer.Sign() <= 0 { - return nil, fmt.Errorf("insufficient balance for gas: balance=%s gasCost=%s l1GasFee=%s", - balance.String(), gasCost.String(), l1GasFeeString(l1GasFee)) - } - return maxTransfer, nil -} - // l1GasFeeString returns a stable decimal representation of the L1 gas fee // for logging / error messages, treating nil as "0". func l1GasFeeString(v *big.Int) string { diff --git a/universalClient/chains/evm/tx_builder_test.go b/universalClient/chains/evm/tx_builder_test.go index d5c2fa589..629f8ab34 100644 --- a/universalClient/chains/evm/tx_builder_test.go +++ b/universalClient/chains/evm/tx_builder_test.go @@ -3,6 +3,7 @@ package evm import ( "context" "encoding/hex" + "encoding/json" "io" "math/big" "net/http" @@ -13,6 +14,8 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" @@ -20,6 +23,7 @@ import ( "github.com/pushchain/push-chain-node/universalClient/chains/common" uetypes "github.com/pushchain/push-chain-node/x/uexecutor/types" + utsstypes "github.com/pushchain/push-chain-node/x/utss/types" ) // testVaultAddress is a non-zero address used as the vault in tests @@ -1185,74 +1189,6 @@ func TestNewTxBuilderZeroGatewayAddress(t *testing.T) { // Fund migration transfer math // --------------------------------------------------------------------------- -// TestComputeFundMigrationTransfer covers the sweep-amount formula -// balance - (gasPrice * gasLimit) - l1GasFee for both L1 and L2-style chains. -// All validators must compute the same value — any drift breaks the TSS hash. -func TestComputeFundMigrationTransfer(t *testing.T) { - t.Run("no L1 fee (mainnet-style) nil", func(t *testing.T) { - // balance 1 ETH, gasPrice 20 gwei, gasLimit 21000 → gasCost = 420000 gwei - balance := new(big.Int).SetUint64(1_000_000_000_000_000_000) - gasPrice := new(big.Int).SetUint64(20_000_000_000) - got, err := computeFundMigrationTransfer(balance, gasPrice, 21000, nil) - require.NoError(t, err) - want := new(big.Int).Sub(balance, new(big.Int).Mul(gasPrice, big.NewInt(21000))) - assert.Equal(t, want.String(), got.String()) - }) - - t.Run("zero L1 fee (mainnet-style) treated as zero", func(t *testing.T) { - balance := new(big.Int).SetUint64(1_000_000_000_000_000_000) - gasPrice := new(big.Int).SetUint64(20_000_000_000) - got, err := computeFundMigrationTransfer(balance, gasPrice, 21000, big.NewInt(0)) - require.NoError(t, err) - want := new(big.Int).Sub(balance, new(big.Int).Mul(gasPrice, big.NewInt(21000))) - assert.Equal(t, want.String(), got.String()) - }) - - t.Run("non-zero L1 fee (OP-stack) is subtracted on top of L2 gas cost", func(t *testing.T) { - // 1 ETH balance, L2 gasCost=420000 gwei, L1 data-availability fee=150 gwei - balance := new(big.Int).SetUint64(1_000_000_000_000_000_000) - gasPrice := new(big.Int).SetUint64(20_000_000_000) - l1Fee := new(big.Int).SetUint64(150_000_000_000) - got, err := computeFundMigrationTransfer(balance, gasPrice, 21000, l1Fee) - require.NoError(t, err) - gasCost := new(big.Int).Mul(gasPrice, big.NewInt(21000)) - want := new(big.Int).Sub(balance, new(big.Int).Add(gasCost, l1Fee)) - assert.Equal(t, want.String(), got.String()) - }) - - t.Run("balance exactly equals total fee → insufficient", func(t *testing.T) { - gasPrice := new(big.Int).SetUint64(20_000_000_000) - l1Fee := big.NewInt(100) - gasCost := new(big.Int).Mul(gasPrice, big.NewInt(21000)) - balance := new(big.Int).Add(gasCost, l1Fee) - _, err := computeFundMigrationTransfer(balance, gasPrice, 21000, l1Fee) - require.Error(t, err) - assert.Contains(t, err.Error(), "insufficient balance") - }) - - t.Run("L1 fee tips balance into insufficient", func(t *testing.T) { - // Without L1 fee, balance covers gas and leaves 100 wei. With L1 fee of 200, it's insufficient. - gasPrice := new(big.Int).SetUint64(20_000_000_000) - gasCost := new(big.Int).Mul(gasPrice, big.NewInt(21000)) - balance := new(big.Int).Add(gasCost, big.NewInt(100)) - _, err := computeFundMigrationTransfer(balance, gasPrice, 21000, big.NewInt(200)) - require.Error(t, err) - assert.Contains(t, err.Error(), "insufficient balance") - }) - - t.Run("deterministic across equivalent l1 fee representations", func(t *testing.T) { - // big.NewInt(0) and nil must produce identical results — the TSS signing - // hash depends on it. - balance := new(big.Int).SetUint64(500_000_000_000_000_000) - gasPrice := new(big.Int).SetUint64(15_000_000_000) - withNil, err := computeFundMigrationTransfer(balance, gasPrice, 21000, nil) - require.NoError(t, err) - withZero, err := computeFundMigrationTransfer(balance, gasPrice, 21000, big.NewInt(0)) - require.NoError(t, err) - assert.Equal(t, withNil.String(), withZero.String()) - }) -} - func TestL1GasFeeString(t *testing.T) { assert.Equal(t, "0", l1GasFeeString(nil)) assert.Equal(t, "0", l1GasFeeString(big.NewInt(0))) @@ -1276,96 +1212,90 @@ func TestGetFundMigrationSigningRequest_RejectsZeroGasLimit(t *testing.T) { } // TestBroadcastFundMigrationTx_RejectsMissingAmount verifies broadcast refuses -// to assemble a tx without the signing-time amount. +// Broadcast refuses without the chain-pinned amount rather than re-deriving it. func TestBroadcastFundMigrationTx_RejectsMissingAmount(t *testing.T) { tb := newTestTxBuilder(t) - data := &common.FundMigrationData{ - From: "0x1111111111111111111111111111111111111111", - To: "0x2222222222222222222222222222222222222222", - GasPrice: big.NewInt(20_000_000_000), - GasLimit: 21000, - } - sig := make([]byte, 65) // valid length; bytes don't have to be a real ECDSA sig - - t.Run("nil amount rejected", func(t *testing.T) { - req := &common.UnsignedSigningReq{ - SigningHash: []byte{0x01}, - Nonce: 0, - // TSSFundMigrationAmount intentionally nil - } - _, err := tb.BroadcastFundMigrationTx(context.Background(), req, data, sig) - require.Error(t, err) - assert.Contains(t, err.Error(), "TSSFundMigrationAmount must be set") - }) - - t.Run("zero amount rejected", func(t *testing.T) { - req := &common.UnsignedSigningReq{ - SigningHash: []byte{0x01}, - Nonce: 0, - TSSFundMigrationAmount: big.NewInt(0), - } - _, err := tb.BroadcastFundMigrationTx(context.Background(), req, data, sig) - require.Error(t, err) - assert.Contains(t, err.Error(), "TSSFundMigrationAmount must be set") - }) + sig := make([]byte, 65) + req := &common.UnsignedSigningReq{SigningHash: []byte{0x01}, Nonce: 0} - t.Run("negative amount rejected", func(t *testing.T) { - req := &common.UnsignedSigningReq{ - SigningHash: []byte{0x01}, - Nonce: 0, - TSSFundMigrationAmount: big.NewInt(-1), - } - _, err := tb.BroadcastFundMigrationTx(context.Background(), req, data, sig) - require.Error(t, err) - assert.Contains(t, err.Error(), "TSSFundMigrationAmount must be set") - }) + for _, tc := range []struct { + name string + amount *big.Int + }{ + {"nil amount rejected", nil}, + {"zero amount rejected", big.NewInt(0)}, + {"negative amount rejected", big.NewInt(-1)}, + } { + t.Run(tc.name, func(t *testing.T) { + data := &common.FundMigrationData{ + From: "0x1111111111111111111111111111111111111111", + To: "0x2222222222222222222222222222222222222222", + GasPrice: big.NewInt(20_000_000_000), + GasLimit: 21000, + L1GasFee: big.NewInt(0), + TransferAmount: tc.amount, + } + _, err := tb.BroadcastFundMigrationTx(context.Background(), req, data, sig) + require.Error(t, err) + assert.Contains(t, err.Error(), "transfer amount is required") + }) + } } -// TestGetFundMigrationSigningRequest_UsesProvidedBalance verifies that when -// data.Balance is non-nil the builder uses it verbatim and skips the RPC -// GetBalance call. This is the determinism guarantee the coordinator's -// verification path depends on. -func TestGetFundMigrationSigningRequest_UsesProvidedBalance(t *testing.T) { - tb := newTestTxBuilder(t) - +// The builder signs the chain-pinned amount verbatim. +func TestGetFundMigrationSigningRequest_UsesPinnedAmount(t *testing.T) { gasPrice := big.NewInt(20_000_000_000) gasLimit := uint64(21000) expectedAmount := big.NewInt(1_000_000_000_000_000) gasCost := new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(gasLimit)) balance := new(big.Int).Add(expectedAmount, gasCost) + // Live balance is sufficient (equal to the provided balance). + tb := txBuilderWithBalance(t, new(big.Int).Set(balance)) + data := &common.FundMigrationData{ - From: "0x1111111111111111111111111111111111111111", - To: "0x2222222222222222222222222222222222222222", - GasPrice: gasPrice, - GasLimit: gasLimit, - L1GasFee: big.NewInt(0), - Balance: balance, + From: "0x1111111111111111111111111111111111111111", + To: "0x2222222222222222222222222222222222222222", + GasPrice: gasPrice, + GasLimit: gasLimit, + L1GasFee: big.NewInt(0), + TransferAmount: expectedAmount, } req, err := tb.GetFundMigrationSigningRequest(context.Background(), data, 42) require.NoError(t, err) - assert.Equal(t, 0, expectedAmount.Cmp(req.TSSFundMigrationAmount)) assert.NotEmpty(t, req.SigningHash) assert.Equal(t, uint64(42), req.Nonce) } -// TestGetFundMigrationSigningRequest_ProvidedBalanceInsufficient verifies the -// insufficient-balance check fires on caller-provided Balance below gas cost. -func TestGetFundMigrationSigningRequest_ProvidedBalanceInsufficient(t *testing.T) { - tb := newTestTxBuilder(t) - - data := &common.FundMigrationData{ - From: "0x1111111111111111111111111111111111111111", - To: "0x2222222222222222222222222222222222222222", - GasPrice: big.NewInt(20_000_000_000), - GasLimit: 21000, - L1GasFee: big.NewInt(0), - Balance: big.NewInt(1), +// A missing pinned amount must be refused, not filled from a live balance. +func TestGetFundMigrationSigningRequest_RequiresPinnedAmount(t *testing.T) { + base := func() *common.FundMigrationData { + return &common.FundMigrationData{ + From: "0x1111111111111111111111111111111111111111", + To: "0x2222222222222222222222222222222222222222", + GasPrice: big.NewInt(20_000_000_000), + GasLimit: 21000, + L1GasFee: big.NewInt(0), + } + } + for _, tc := range []struct { + name string + amount *big.Int + }{ + {"nil", nil}, + {"zero", big.NewInt(0)}, + {"negative", big.NewInt(-1)}, + } { + t.Run(tc.name, func(t *testing.T) { + tb := txBuilderWithBalance(t, new(big.Int).SetUint64(1_000_000_000_000_000_000)) + data := base() + data.TransferAmount = tc.amount + _, err := tb.GetFundMigrationSigningRequest(context.Background(), data, 0) + require.Error(t, err) + assert.Contains(t, err.Error(), "transfer amount is required") + }) } - _, err := tb.GetFundMigrationSigningRequest(context.Background(), data, 0) - require.Error(t, err) - assert.Contains(t, err.Error(), "insufficient balance") } // TestBroadcastFundMigrationTx_DoesNotQueryBalance asserts broadcast never @@ -1380,9 +1310,8 @@ func TestBroadcastFundMigrationTx_DoesNotQueryBalance(t *testing.T) { L1GasFee: big.NewInt(0), } req := &common.UnsignedSigningReq{ - SigningHash: []byte{0x01}, - Nonce: 0, - TSSFundMigrationAmount: big.NewInt(1_000_000_000_000_000), // 0.001 ETH + SigningHash: []byte{0x01}, + Nonce: 0, // 0.001 ETH } sig := make([]byte, 65) @@ -1391,3 +1320,199 @@ func TestBroadcastFundMigrationTx_DoesNotQueryBalance(t *testing.T) { assert.NotContains(t, err.Error(), "get_balance", "broadcast must not call GetBalance") assert.NotContains(t, err.Error(), "failed to get balance", "broadcast must not call GetBalance") } + +// txBuilderWithBalance returns a TxBuilder whose RPC pool answers eth_getBalance +// with the given wei value (Sepolia chain id). +func txBuilderWithBalance(t *testing.T, balanceWei *big.Int) *TxBuilder { + t.Helper() + balHex := "0x" + balanceWei.Text(16) + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + body := make([]byte, r.ContentLength) + r.Body.Read(body) + switch { + case strings.Contains(string(body), "eth_chainId"): + w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":"0xaa36a7"}`)) // 11155111 + case strings.Contains(string(body), "eth_getBalance"): + w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":"` + balHex + `"}`)) + default: + w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":null}`)) + } + })) + t.Cleanup(server.Close) + + rc, err := NewRPCClient([]string{server.URL}, 11155111, zerolog.Nop()) + require.NoError(t, err) + t.Cleanup(func() { rc.Close() }) + + return &TxBuilder{ + rpcClient: rc, + chainID: "eip155:11155111", + chainIDInt: 11155111, + logger: zerolog.Nop(), + } +} + +// A dust transfer to the old TSS EOA between the coordinator's build and a +// follower's verify must not change the pinned-balance signing hash, and a +// pinned amount larger than the live balance must be rejected. +func TestGetFundMigrationSigningRequest_PinnedBalance(t *testing.T) { + from := "0x1111111111111111111111111111111111111111" + to := "0x2222222222222222222222222222222222222222" + gasPrice := big.NewInt(20_000_000_000) + gasLimit := uint64(21000) + amount := big.NewInt(1_000_000_000_000_000) // 0.001 ETH + fees := new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(gasLimit)) + pinned := new(big.Int).Add(amount, fees) // balance = amount + fees + + data := func() *common.FundMigrationData { + return &common.FundMigrationData{ + From: from, + To: to, + GasPrice: gasPrice, + GasLimit: gasLimit, + L1GasFee: big.NewInt(0), + TransferAmount: new(big.Int).Set(amount), + } + } + + t.Run("hash unchanged by +1 wei dust inflow", func(t *testing.T) { + tbExact := txBuilderWithBalance(t, new(big.Int).Set(pinned)) + reqExact, err := tbExact.GetFundMigrationSigningRequest(context.Background(), data(), 7) + require.NoError(t, err) + + tbDust := txBuilderWithBalance(t, new(big.Int).Add(pinned, big.NewInt(1))) + reqDust, err := tbDust.GetFundMigrationSigningRequest(context.Background(), data(), 7) + require.NoError(t, err) + + assert.Equal(t, reqExact.SigningHash, reqDust.SigningHash) + }) + + // Reproducible after the sweep landed and the balance is gone, which the ACK + // verify path depends on. + t.Run("hash unchanged once the balance is swept away", func(t *testing.T) { + tbFunded := txBuilderWithBalance(t, new(big.Int).Set(pinned)) + reqFunded, err := tbFunded.GetFundMigrationSigningRequest(context.Background(), data(), 7) + require.NoError(t, err) + + tbDrained := txBuilderWithBalance(t, big.NewInt(0)) + reqDrained, err := tbDrained.GetFundMigrationSigningRequest(context.Background(), data(), 7) + require.NoError(t, err) + + assert.Equal(t, reqFunded.SigningHash, reqDrained.SigningHash) + }) +} + +// End to end for the pinned sweep amount: the value the chain pinned is the +// value that gets signed, and the value that reaches the wire. It no longer +// travels with the signature, so nothing but the event determines it. +func TestFundMigration_PinnedAmountReachesTheWire(t *testing.T) { + const ( + fromAddr = "0x1111111111111111111111111111111111111111" + toAddr = "0x2222222222222222222222222222222222222222" + nonce = uint64(7) + ) + pinned := big.NewInt(1_234_567_890_000_000) + gasPrice := big.NewInt(20_000_000_000) + gasLimit := uint64(21000) + + // The event as the chain pins it at initiate time. + migration := utsstypes.FundMigrationInitiatedEventData{ + Chain: "eip155:11155111", + GasPrice: gasPrice.String(), + GasLimit: gasLimit, + L1GasFee: "0", + TransferAmount: pinned.String(), + } + + dataFromEvent := func() *common.FundMigrationData { + amount, ok := new(big.Int).SetString(migration.TransferAmount, 10) + require.True(t, ok) + gp, ok := new(big.Int).SetString(migration.GasPrice, 10) + require.True(t, ok) + l1, ok := new(big.Int).SetString(migration.L1GasFee, 10) + require.True(t, ok) + return &common.FundMigrationData{ + From: fromAddr, + To: toAddr, + GasPrice: gp, + GasLimit: migration.GasLimit, + L1GasFee: l1, + TransferAmount: amount, + } + } + + // Coordinator and a follower on a chain whose balance has since moved. + coordinator := txBuilderWithBalance(t, new(big.Int).Add(pinned, big.NewInt(1_000_000_000_000_000))) + follower := txBuilderWithBalance(t, big.NewInt(0)) + + coordReq, err := coordinator.GetFundMigrationSigningRequest(context.Background(), dataFromEvent(), nonce) + require.NoError(t, err) + followerReq, err := follower.GetFundMigrationSigningRequest(context.Background(), dataFromEvent(), nonce) + require.NoError(t, err) + require.Equal(t, coordReq.SigningHash, followerReq.SigningHash, + "validators must agree on the hash regardless of what the balance is doing") + + key, err := crypto.HexToECDSA("4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318") + require.NoError(t, err) + signature, err := crypto.Sign(coordReq.SigningHash, key) + require.NoError(t, err) + + sent, sender := txBuilderCapturingSend(t) + txHash, err := sender.BroadcastFundMigrationTx(context.Background(), coordReq, dataFromEvent(), signature) + require.NoError(t, err) + require.NotEmpty(t, txHash) + + raw := <-sent + var broadcast types.Transaction + require.NoError(t, broadcast.UnmarshalBinary(raw)) + + assert.Equal(t, pinned.String(), broadcast.Value().String(), "the wire value must be the pinned amount") + assert.Equal(t, toAddr, strings.ToLower(broadcast.To().Hex())) + assert.Equal(t, nonce, broadcast.Nonce()) + assert.Equal(t, gasLimit, broadcast.Gas()) + + // The signature covers exactly this transaction. + recovered, err := types.Sender(types.NewEIP155Signer(big.NewInt(11155111)), &broadcast) + require.NoError(t, err) + assert.Equal(t, crypto.PubkeyToAddress(key.PublicKey), recovered) +} + +// txBuilderCapturingSend returns a builder whose RPC accepts eth_sendRawTransaction +// and hands the raw bytes back on the channel. +func txBuilderCapturingSend(t *testing.T) (chan []byte, *TxBuilder) { + t.Helper() + sent := make(chan []byte, 1) + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + body, _ := io.ReadAll(r.Body) + switch { + case strings.Contains(string(body), "eth_chainId"): + w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":"0xaa36a7"}`)) + case strings.Contains(string(body), "eth_sendRawTransaction"): + var req struct { + Params []string `json:"params"` + } + require.NoError(t, json.Unmarshal(body, &req)) + require.Len(t, req.Params, 1) + decoded, err := hexutil.Decode(req.Params[0]) + require.NoError(t, err) + sent <- decoded + w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":"0x` + strings.Repeat("ab", 32) + `"}`)) + default: + w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":null}`)) + } + })) + t.Cleanup(server.Close) + + rc, err := NewRPCClient([]string{server.URL}, 11155111, zerolog.Nop()) + require.NoError(t, err) + t.Cleanup(func() { rc.Close() }) + + return sent, &TxBuilder{ + rpcClient: rc, + chainID: "eip155:11155111", + chainIDInt: 11155111, + logger: zerolog.Nop(), + } +} diff --git a/universalClient/chains/push/event_parser.go b/universalClient/chains/push/event_parser.go index 2f25a99fb..ab7f81085 100644 --- a/universalClient/chains/push/event_parser.go +++ b/universalClient/chains/push/event_parser.go @@ -76,6 +76,7 @@ func convertFundMigrationEvent(migration *utsstypes.FundMigration) (*store.Event GasPrice: migration.GasPrice, GasLimit: migration.GasLimit, L1GasFee: migration.L1GasFee, + TransferAmount: migration.TransferAmount, }) if err != nil { return nil, fmt.Errorf("failed to marshal fund migration event data: %w", err) diff --git a/universalClient/chains/push/event_parser_test.go b/universalClient/chains/push/event_parser_test.go index 8ea0d47a3..518cfdea1 100644 --- a/universalClient/chains/push/event_parser_test.go +++ b/universalClient/chains/push/event_parser_test.go @@ -315,6 +315,7 @@ func TestConvertFundMigrationEvent(t *testing.T) { GasPrice: "1000000000", GasLimit: 21100, L1GasFee: "42", + TransferAmount: "999999999999999999", } result, err := convertFundMigrationEvent(migration) @@ -340,6 +341,44 @@ func TestConvertFundMigrationEvent(t *testing.T) { assert.Equal(t, "1000000000", data.GasPrice) assert.Equal(t, uint64(21100), data.GasLimit) assert.Equal(t, "42", data.L1GasFee, "L1 gas fee must be forwarded to downstream consumers") + assert.Equal(t, "999999999999999999", data.TransferAmount, + "the chain-pinned sweep amount is what every validator signs; dropping it here leaves nothing deterministic to sign") + }) + + // A field on the record but not copied here reaches signers empty. + t.Run("fields the signing path depends on are all carried", func(t *testing.T) { + migration := &utsstypes.FundMigration{ + Id: 7, + OldKeyId: "old", + OldTssPubkey: "0x02aa", + CurrentKeyId: "new", + CurrentTssPubkey: "0x03bb", + Chain: "eip155:84532", + InitiatedBlock: 9, + GasPrice: "1", + GasLimit: 2, + L1GasFee: "3", + TransferAmount: "4", + } + + result, err := convertFundMigrationEvent(migration) + require.NoError(t, err) + + var data utsstypes.FundMigrationInitiatedEventData + require.NoError(t, json.Unmarshal(result.EventData, &data)) + + for name, got := range map[string]string{ + "old_tss_pubkey": data.OldTssPubkey, + "current_tss_pubkey": data.CurrentTssPubkey, + "chain": data.Chain, + "gas_price": data.GasPrice, + "l1_gas_fee": data.L1GasFee, + "transfer_amount": data.TransferAmount, + "old_key_id": data.OldKeyID, + } { + assert.NotEmpty(t, got, "%s was dropped in conversion", name) + } + assert.NotZero(t, data.GasLimit, "gas_limit was dropped in conversion") }) t.Run("event ID is hash of type and migration ID", func(t *testing.T) { @@ -378,4 +417,3 @@ func TestHashEventID(t *testing.T) { assert.Len(t, id, 64) // sha256 = 32 bytes = 64 hex chars }) } - diff --git a/universalClient/tss/coordinator/coordinator.go b/universalClient/tss/coordinator/coordinator.go index ed72c6e9e..5a3be325e 100644 --- a/universalClient/tss/coordinator/coordinator.go +++ b/universalClient/tss/coordinator/coordinator.go @@ -616,7 +616,7 @@ func (c *Coordinator) createFundMigrationSignSetup(ctx context.Context, eventDat } keyIDBytes := deriveKeyIDBytes(migrationData.OldKeyID) - signingReq, err := c.buildFundMigrationTransaction(ctx, eventData, assignedNonce, nil /* query chain for balance */) + signingReq, err := c.buildFundMigrationTransaction(ctx, eventData, assignedNonce) if err != nil { return nil, nil, fmt.Errorf("failed to build fund migration transaction: %w", err) } @@ -637,12 +637,9 @@ func (c *Coordinator) createFundMigrationSignSetup(ctx context.Context, eventDat return setupData, signingReq, nil } -// buildFundMigrationTransaction parses event data and returns the signing -// request for sweeping old-TSS funds to the current TSS. If claimedAmount is -// non-nil, the balance is reconstructed as amount + gas + L1 instead of -// queried from chain — used by the ACK verify path to rebuild the hash -// deterministically without racing a successful sweep. -func (c *Coordinator) buildFundMigrationTransaction(ctx context.Context, eventData []byte, assignedNonce *uint64, claimedAmount *big.Int) (*common.UnsignedSigningReq, error) { +// buildFundMigrationTransaction returns the signing request for sweeping +// old-TSS funds to the current TSS, using the amount pinned on the event. +func (c *Coordinator) buildFundMigrationTransaction(ctx context.Context, eventData []byte, assignedNonce *uint64) (*common.UnsignedSigningReq, error) { if assignedNonce == nil { return nil, fmt.Errorf("assigned nonce is required for fund migration transaction") } @@ -675,23 +672,18 @@ func (c *Coordinator) buildFundMigrationTransaction(ctx context.Context, eventDa l1GasFee := new(big.Int) l1GasFee.SetString(migrationData.L1GasFee, 10) - var balance *big.Int - if claimedAmount != nil { - // balance = amount + gas + L1; inverse of computeFundMigrationTransfer - balance = new(big.Int).Set(claimedAmount) - balance.Add(balance, new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(migrationData.GasLimit))) - if l1GasFee.Sign() > 0 { - balance.Add(balance, l1GasFee) - } + transferAmount, ok := new(big.Int).SetString(migrationData.TransferAmount, 10) + if !ok || transferAmount.Sign() <= 0 { + return nil, fmt.Errorf("migration event carries no usable transfer amount: %q", migrationData.TransferAmount) } return builder.GetFundMigrationSigningRequest(ctx, &common.FundMigrationData{ - From: oldTSSAddr, - To: currentTSSAddr, - GasPrice: gasPrice, - GasLimit: migrationData.GasLimit, - L1GasFee: l1GasFee, - Balance: balance, + From: oldTSSAddr, + To: currentTSSAddr, + GasPrice: gasPrice, + GasLimit: migrationData.GasLimit, + L1GasFee: l1GasFee, + TransferAmount: transferAmount, }, *assignedNonce) } diff --git a/universalClient/tss/coordinator/msg_handler.go b/universalClient/tss/coordinator/msg_handler.go index 736a0e485..d2f3816d1 100644 --- a/universalClient/tss/coordinator/msg_handler.go +++ b/universalClient/tss/coordinator/msg_handler.go @@ -6,7 +6,6 @@ import ( "encoding/json" "errors" "fmt" - "math/big" "github.com/pushchain/push-chain-node/universalClient/store" utsstypes "github.com/pushchain/push-chain-node/x/utss/types" @@ -199,7 +198,6 @@ func (c *Coordinator) handleSignedAck(ctx context.Context, senderPeerID, eventID signedData.Signature, signedData.SigningHash, signedData.Nonce, - signedData.TSSFundMigrationAmount, ) if err != nil { return fmt.Errorf("event %s: persist verified signature: %w", eventID, err) @@ -234,7 +232,7 @@ func (c *Coordinator) VerifySignedData(ctx context.Context, event *store.Event, if len(signedData.Signature) != 64 && len(signedData.Signature) != 65 { return fmt.Errorf("signature must be 64 or 65 bytes, got %d", len(signedData.Signature)) } - expectedHash, err := c.rebuildSigningHash(ctx, event, signedData.Nonce, signedData.TSSFundMigrationAmount) + expectedHash, err := c.rebuildSigningHash(ctx, event, signedData.Nonce) if err != nil { return fmt.Errorf("rebuild signing hash: %w", err) } @@ -279,7 +277,7 @@ func (c *Coordinator) verifyingPubkey(ctx context.Context, event *store.Event) ( } } -func (c *Coordinator) rebuildSigningHash(ctx context.Context, event *store.Event, nonce uint64, claimedAmount *big.Int) ([]byte, error) { +func (c *Coordinator) rebuildSigningHash(ctx context.Context, event *store.Event, nonce uint64) ([]byte, error) { switch event.Type { case store.EventTypeSignOutbound: req, err := c.buildSignTransaction(ctx, event.EventData, &nonce) @@ -288,10 +286,7 @@ func (c *Coordinator) rebuildSigningHash(ctx context.Context, event *store.Event } return req.SigningHash, nil case store.EventTypeSignFundMigrate: - if claimedAmount == nil || claimedAmount.Sign() <= 0 { - return nil, fmt.Errorf("fund migration verification requires positive claimed amount") - } - req, err := c.buildFundMigrationTransaction(ctx, event.EventData, &nonce, claimedAmount) + req, err := c.buildFundMigrationTransaction(ctx, event.EventData, &nonce) if err != nil { return nil, err } diff --git a/universalClient/tss/coordinator/msg_handler_test.go b/universalClient/tss/coordinator/msg_handler_test.go index f271ce34e..f61a2f4a5 100644 --- a/universalClient/tss/coordinator/msg_handler_test.go +++ b/universalClient/tss/coordinator/msg_handler_test.go @@ -202,7 +202,7 @@ func TestHandleSignedAck_FailurePaths(t *testing.T) { assert.Contains(t, err.Error(), "has no signature to verify") }) - t.Run("fund migration without claimed amount rejected", func(t *testing.T) { + t.Run("fund migration with unusable event data rejected", func(t *testing.T) { require.NoError(t, db.Create(&store.Event{ EventID: "fm-evt", BlockHeight: 1, @@ -216,7 +216,7 @@ func TestHandleSignedAck_FailurePaths(t *testing.T) { SigningHash: make([]byte, 32), }) require.Error(t, err) - assert.Contains(t, err.Error(), "requires positive claimed amount") + assert.Contains(t, err.Error(), "rebuild signing hash") }) t.Run("verification failure does not touch ackTracking", func(t *testing.T) { diff --git a/universalClient/tss/coordinator/types.go b/universalClient/tss/coordinator/types.go index 22e7b1e81..7f5c1affe 100644 --- a/universalClient/tss/coordinator/types.go +++ b/universalClient/tss/coordinator/types.go @@ -2,7 +2,6 @@ package coordinator import ( "context" - "math/big" "github.com/pushchain/push-chain-node/universalClient/chains/common" ) @@ -25,10 +24,9 @@ const ( // when the participant already holds a valid signature for this event, // letting the coordinator skip a fresh DKLS run. type SignedDataPayload struct { - Signature []byte `json:"signature"` // ECDSA (r || s [|| v]) - SigningHash []byte `json:"signing_hash"` // 32-byte message hash - Nonce uint64 `json:"nonce"` // EVM nonce; ignored by SVM - TSSFundMigrationAmount *big.Int `json:"tss_fund_migration_amount,omitempty"` + Signature []byte `json:"signature"` // ECDSA (r || s [|| v]) + SigningHash []byte `json:"signing_hash"` // 32-byte message hash + Nonce uint64 `json:"nonce"` // EVM nonce; ignored by SVM } // Message is the wire format for all TSS coordination messages. diff --git a/universalClient/tss/eventstore/store.go b/universalClient/tss/eventstore/store.go index d7bb7ede9..f8cb4b50a 100644 --- a/universalClient/tss/eventstore/store.go +++ b/universalClient/tss/eventstore/store.go @@ -4,7 +4,6 @@ import ( "encoding/hex" "encoding/json" "fmt" - "math/big" "time" "github.com/rs/zerolog" @@ -74,16 +73,12 @@ func (s *Store) PersistSignature( signature []byte, signingHash []byte, nonce uint64, - fundMigrationAmount *big.Int, ) (bool, error) { signingData := map[string]any{ "signature": hex.EncodeToString(signature), "signing_hash": hex.EncodeToString(signingHash), "nonce": nonce, } - if fundMigrationAmount != nil && fundMigrationAmount.Sign() > 0 { - signingData["tss_fund_migration_amount"] = fundMigrationAmount - } var raw map[string]any if err := json.Unmarshal(eventData, &raw); err != nil { diff --git a/universalClient/tss/eventstore/store_test.go b/universalClient/tss/eventstore/store_test.go index 0ee1f8566..8d62f3c22 100644 --- a/universalClient/tss/eventstore/store_test.go +++ b/universalClient/tss/eventstore/store_test.go @@ -715,7 +715,7 @@ func TestPersistSignature(t *testing.T) { t.Fatalf("seed event: %v", err) } - persisted, err := s.PersistSignature("ev-1", baseEventData, sig, hash, 42, nil) + persisted, err := s.PersistSignature("ev-1", baseEventData, sig, hash, 42) if err != nil { t.Fatalf("PersistSignature: %v", err) } @@ -758,7 +758,7 @@ func TestPersistSignature(t *testing.T) { t.Fatalf("seed event: %v", err) } - persisted, err := s.PersistSignature("ev-2", baseEventData, sig, hash, 7, nil) + persisted, err := s.PersistSignature("ev-2", baseEventData, sig, hash, 7) if err != nil { t.Fatalf("PersistSignature: %v", err) } @@ -783,7 +783,7 @@ func TestPersistSignature(t *testing.T) { t.Fatalf("seed event: %v", err) } - persisted, err := s.PersistSignature("ev-3", baseEventData, sig, hash, 1, nil) + persisted, err := s.PersistSignature("ev-3", baseEventData, sig, hash, 1) if err != nil { t.Fatalf("PersistSignature: %v", err) } @@ -811,7 +811,7 @@ func TestPersistSignature(t *testing.T) { t.Fatalf("seed event: %v", err) } - persisted, err := s.PersistSignature("ev-4", baseEventData, sig, hash, 1, nil) + persisted, err := s.PersistSignature("ev-4", baseEventData, sig, hash, 1) if err != nil { t.Fatalf("PersistSignature: %v", err) } @@ -829,7 +829,7 @@ func TestPersistSignature(t *testing.T) { t.Run("invalid event data JSON returns error", func(t *testing.T) { s := setupTestStore(t) - _, err := s.PersistSignature("ev-5", []byte("not json"), sig, hash, 1, nil) + _, err := s.PersistSignature("ev-5", []byte("not json"), sig, hash, 1) if err == nil { t.Fatal("expected error on invalid JSON") } diff --git a/universalClient/tss/sessionmanager/sessionmanager.go b/universalClient/tss/sessionmanager/sessionmanager.go index b6bc53d60..bbd79b1b2 100644 --- a/universalClient/tss/sessionmanager/sessionmanager.go +++ b/universalClient/tss/sessionmanager/sessionmanager.go @@ -428,9 +428,8 @@ func (sm *SessionManager) handleSignatureBroadcast(ctx context.Context, senderPe // Persist as SIGNED via the same path a local sign-completion uses, so // signing_data lands on event_data in the format txbroadcaster expects. rebuiltReq := &common.UnsignedSigningReq{ - SigningHash: msg.SignedData.SigningHash, - Nonce: msg.SignedData.Nonce, - TSSFundMigrationAmount: msg.SignedData.TSSFundMigrationAmount, + SigningHash: msg.SignedData.SigningHash, + Nonce: msg.SignedData.Nonce, } if err := sm.handleSigningComplete(ctx, msg.EventID, event.EventData, msg.SignedData.Signature, rebuiltReq); err != nil { return fmt.Errorf("persist signature from broadcast: %w", err) @@ -516,10 +515,9 @@ func (sm *SessionManager) handleSignFinished(ctx context.Context, eventID string // SIGNED and can vote on failure. Best-effort: failed sends are logged but // do not abort. Recovery via sweeper retry covers any peers we miss. sm.broadcastSignature(ctx, eventID, &coordinator.SignedDataPayload{ - Signature: result.Signature, - SigningHash: signingReq.SigningHash, - Nonce: signingReq.Nonce, - TSSFundMigrationAmount: signingReq.TSSFundMigrationAmount, + Signature: result.Signature, + SigningHash: signingReq.SigningHash, + Nonce: signingReq.Nonce, }) sm.logger.Info().Str("event_id", eventID).Msg("sign session finished successfully") @@ -1158,6 +1156,10 @@ func (sm *SessionManager) verifyFundMigrationSigningRequest(ctx context.Context, if err != nil { return fmt.Errorf("failed to derive current TSS address: %w", err) } + transferAmount, ok := new(big.Int).SetString(migrationData.TransferAmount, 10) + if !ok || transferAmount.Sign() <= 0 { + return fmt.Errorf("migration event carries no usable transfer amount: %q", migrationData.TransferAmount) + } // Get chain client and tx builder if sm.chains == nil { @@ -1181,7 +1183,6 @@ func (sm *SessionManager) verifyFundMigrationSigningRequest(ctx context.Context, } else if err := checkNonceInRange(req.Nonce, finalizedNonce, ceilingBase, oldTSSAddr); err != nil { return err } - // Rebuild fund migration signing request with coordinator's nonce. // Parsing must match what the coordinator did; otherwise the reconstructed // hash on OP-stack chains diverges and the verification below rejects it. @@ -1192,11 +1193,12 @@ func (sm *SessionManager) verifyFundMigrationSigningRequest(ctx context.Context, l1GasFee.SetString(migrationData.L1GasFee, 10) migrationFundData := &common.FundMigrationData{ - From: oldTSSAddr, - To: currentTSSAddr, - GasPrice: gasPrice, - GasLimit: migrationData.GasLimit, - L1GasFee: l1GasFee, + From: oldTSSAddr, + To: currentTSSAddr, + GasPrice: gasPrice, + GasLimit: migrationData.GasLimit, + L1GasFee: l1GasFee, + TransferAmount: transferAmount, } signingReq, err := builder.GetFundMigrationSigningRequest(ctx, migrationFundData, req.Nonce) if err != nil { @@ -1213,23 +1215,12 @@ func (sm *SessionManager) verifyFundMigrationSigningRequest(ctx context.Context, return fmt.Errorf("fund migration signing hash mismatch: our computed hash does not match coordinator's hash") } - // Defense-in-depth: hash match implies amount match, but cross-check explicitly so - // a wire-format bug, coordinator bug, or missing amount surfaces here rather than - // as a nil-deref / insufficient-balance error later in broadcast. - if req.TSSFundMigrationAmount == nil { - return fmt.Errorf("coordinator's signing request is missing TSSFundMigrationAmount") - } - if req.TSSFundMigrationAmount.Cmp(signingReq.TSSFundMigrationAmount) != 0 { - return fmt.Errorf("TSSFundMigrationAmount mismatch: coordinator=%s ours=%s", - req.TSSFundMigrationAmount.String(), signingReq.TSSFundMigrationAmount.String()) - } - sm.logger.Debug(). Str("event_id", event.EventID). Str("signing_hash", hex.EncodeToString(req.SigningHash)). Str("old_tss_addr", oldTSSAddr). Str("current_tss_addr", currentTSSAddr). - Msg("fund migration sign metadata verified - hash and amount match") + Msg("fund migration sign metadata verified") return nil } @@ -1244,8 +1235,6 @@ func (sm *SessionManager) getTSSAddress(ctx context.Context) (string, error) { } // handleSigningComplete handles post-sign steps. EVM: set status SIGNED and store payload (txlifecycle/signed runs BroadcastOutboundSigningRequest). Solana: enqueue for sequential per-chain broadcast (PDA nonce order). -// signingReq is the cached signing request from the coordinator setup message; for FUND_MIGRATE -// its TSSFundMigrationAmount is populated by verifyFundMigrationSigningRequest and persisted here. func (sm *SessionManager) handleSigningComplete(_ context.Context, eventID string, eventData []byte, signature []byte, signingReq *common.UnsignedSigningReq) error { if signingReq == nil { return fmt.Errorf("signing request is nil - cannot persist signing data") @@ -1257,7 +1246,6 @@ func (sm *SessionManager) handleSigningComplete(_ context.Context, eventID strin signature, signingReq.SigningHash, signingReq.Nonce, - signingReq.TSSFundMigrationAmount, ) if err != nil { return fmt.Errorf("failed to persist signing data: %w", err) @@ -1284,10 +1272,9 @@ func extractSignedDataFromEvent(event *store.Event) (*coordinator.SignedDataPayl } var raw struct { SigningData *struct { - Signature string `json:"signature"` - SigningHash string `json:"signing_hash"` - Nonce uint64 `json:"nonce"` - TSSFundMigrationAmount *big.Int `json:"tss_fund_migration_amount,omitempty"` + Signature string `json:"signature"` + SigningHash string `json:"signing_hash"` + Nonce uint64 `json:"nonce"` } `json:"signing_data,omitempty"` } if err := json.Unmarshal(event.EventData, &raw); err != nil { @@ -1305,9 +1292,8 @@ func extractSignedDataFromEvent(event *store.Event) (*coordinator.SignedDataPayl return nil, fmt.Errorf("decode signing_data.signing_hash hex: %w", err) } return &coordinator.SignedDataPayload{ - Signature: sigBytes, - SigningHash: hashBytes, - Nonce: raw.SigningData.Nonce, - TSSFundMigrationAmount: raw.SigningData.TSSFundMigrationAmount, + Signature: sigBytes, + SigningHash: hashBytes, + Nonce: raw.SigningData.Nonce, }, nil } diff --git a/universalClient/tss/sessionmanager/sessionmanager_test.go b/universalClient/tss/sessionmanager/sessionmanager_test.go index cd1ae782a..11b82af20 100644 --- a/universalClient/tss/sessionmanager/sessionmanager_test.go +++ b/universalClient/tss/sessionmanager/sessionmanager_test.go @@ -7,7 +7,6 @@ import ( "encoding/json" "errors" "fmt" - "math/big" "reflect" "strings" "testing" @@ -655,6 +654,40 @@ func TestVerifyFundMigrationSigningRequest_Validation(t *testing.T) { assert.Contains(t, err.Error(), "failed to parse fund migration event data") }) + // Rejected before any chain call: nothing deterministic to sign. + t.Run("event without a pinned transfer amount is rejected", func(t *testing.T) { + const validPubkey = "024e3b81af9c2234cad09d679ce6035ed1392347ce64ce405f5dcd36228a25de6e" + for _, tc := range []struct{ name, amount string }{ + {"missing", ""}, + {"zero", "0"}, + {"negative", "-1"}, + {"not a number", "abc"}, + } { + t.Run(tc.name, func(t *testing.T) { + eventDataBytes, err := json.Marshal(utsstypes.FundMigrationInitiatedEventData{ + OldTssPubkey: validPubkey, + CurrentTssPubkey: validPubkey, + Chain: "eip155:1", + GasPrice: "20000000000", + GasLimit: 21000, + L1GasFee: "0", + TransferAmount: tc.amount, + }) + require.NoError(t, err) + event := &store.Event{ + EventID: "fm-no-amount-" + tc.name, + Type: store.EventTypeSignFundMigrate, + EventData: eventDataBytes, + } + err = sm.verifyFundMigrationSigningRequest(ctx, event, &common.UnsignedSigningReq{ + SigningHash: []byte{0x01}, + }) + require.Error(t, err) + assert.Contains(t, err.Error(), "no usable transfer amount") + }) + } + }) + t.Run("invalid old TSS pubkey is rejected", func(t *testing.T) { migrationData := utsstypes.FundMigrationInitiatedEventData{ OldTssPubkey: "not-a-valid-pubkey", @@ -708,6 +741,7 @@ func TestVerifyFundMigrationSigningRequest_Validation(t *testing.T) { GasPrice: "1000000000", GasLimit: 21100, L1GasFee: "150", + TransferAmount: "500000000000000000", } eventDataBytes, _ := json.Marshal(migrationData) event := &store.Event{ @@ -719,7 +753,7 @@ func TestVerifyFundMigrationSigningRequest_Validation(t *testing.T) { req := &common.UnsignedSigningReq{SigningHash: []byte{0x01, 0x02}} err := sm.verifyFundMigrationSigningRequest(ctx, event, req) assert.NoError(t, err) - assert.Nil(t, req.TSSFundMigrationAmount, "amount stays nil when chain/builder is skipped") + }) } @@ -925,10 +959,9 @@ func TestSendACK(t *testing.T) { ) signed := &coordinator.SignedDataPayload{ - Signature: bytes.Repeat([]byte{0xaa}, 64), - SigningHash: bytes.Repeat([]byte{0xbb}, 32), - Nonce: 42, - TSSFundMigrationAmount: big.NewInt(123_456), + Signature: bytes.Repeat([]byte{0xaa}, 64), + SigningHash: bytes.Repeat([]byte{0xbb}, 32), + Nonce: 42, } require.NoError(t, sm.sendACK(context.Background(), "coord-peer", "evt-signed", signed)) @@ -940,8 +973,6 @@ func TestSendACK(t *testing.T) { assert.Equal(t, signed.Signature, msg.SignedData.Signature) assert.Equal(t, signed.SigningHash, msg.SignedData.SigningHash) assert.Equal(t, signed.Nonce, msg.SignedData.Nonce) - require.NotNil(t, msg.SignedData.TSSFundMigrationAmount) - assert.Equal(t, 0, signed.TSSFundMigrationAmount.Cmp(msg.SignedData.TSSFundMigrationAmount)) }) } @@ -1158,7 +1189,9 @@ func TestHandleSigningComplete(t *testing.T) { assert.False(t, hasAmount, "tss_fund_migration_amount is omitted for outbound events") }) - t.Run("fund migration signing complete persists tss_fund_migration_amount", func(t *testing.T) { + // The amount is not carried through signing data any more: broadcast reads it + // from the migration event, so nothing raceable rides with the signature. + t.Run("fund migration signing complete does not carry the amount", func(t *testing.T) { event := store.Event{ EventID: "fm-complete-1", BlockHeight: 250, @@ -1169,9 +1202,8 @@ func TestHandleSigningComplete(t *testing.T) { require.NoError(t, testDB.Create(&event).Error) req := &common.UnsignedSigningReq{ - SigningHash: []byte{0xca, 0xfe}, - Nonce: 3, - TSSFundMigrationAmount: new(big.Int).SetUint64(123456789), + SigningHash: []byte{0xca, 0xfe}, + Nonce: 3, } err := sm.handleSigningComplete(context.Background(), "fm-complete-1", event.EventData, []byte{0xbe, 0xef}, req) require.NoError(t, err) @@ -1180,17 +1212,12 @@ func TestHandleSigningComplete(t *testing.T) { require.NoError(t, testDB.Where("event_id = ?", "fm-complete-1").First(&updated).Error) assert.Equal(t, store.StatusSigned, updated.Status) - // Decode the field into *big.Int directly — unmarshalling into map[string]any - // would coerce the JSON number into float64 and lose precision for wei values. - var decoded struct { - SigningData struct { - TSSFundMigrationAmount *big.Int `json:"tss_fund_migration_amount"` - } `json:"signing_data"` - } - require.NoError(t, json.Unmarshal(updated.EventData, &decoded)) - require.NotNil(t, decoded.SigningData.TSSFundMigrationAmount, - "tss_fund_migration_amount must survive the sign→broadcast handoff so broadcast reproduces the signed tx") - assert.Equal(t, "123456789", decoded.SigningData.TSSFundMigrationAmount.String()) + var rawData map[string]any + require.NoError(t, json.Unmarshal(updated.EventData, &rawData)) + signingData, ok := rawData["signing_data"].(map[string]any) + require.True(t, ok) + _, hasAmount := signingData["tss_fund_migration_amount"] + assert.False(t, hasAmount, "the amount is read from the event at broadcast, not carried here") }) } diff --git a/universalClient/tss/txbroadcaster/broadcaster_test.go b/universalClient/tss/txbroadcaster/broadcaster_test.go index 2fb354772..80ff6792e 100644 --- a/universalClient/tss/txbroadcaster/broadcaster_test.go +++ b/universalClient/tss/txbroadcaster/broadcaster_test.go @@ -686,7 +686,7 @@ const testNewTSSPubkey = "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac func makeSignedFundMigrationData(t *testing.T, chainID string, nonce uint64) []byte { t.Helper() - return makeSignedFundMigrationDataWithTransfer(t, chainID, nonce, nil) + return makeSignedFundMigrationDataWithTransfer(t, chainID, nonce, big.NewInt(500_000_000_000_000_000)) } func makeSignedFundMigrationDataWithTransfer(t *testing.T, chainID string, nonce uint64, transferAmount *big.Int) []byte { @@ -704,12 +704,12 @@ func makeSignedFundMigrationDataWithTransfer(t *testing.T, chainID string, nonce GasPrice: "1000000000", GasLimit: 21100, L1GasFee: "150", + TransferAmount: transferAmountString(transferAmount), }, SigningData: &txflow.SigningData{ - Signature: sig, - SigningHash: hash, - Nonce: nonce, - TSSFundMigrationAmount: transferAmount, + Signature: sig, + SigningHash: hash, + Nonce: nonce, }, } b, err := json.Marshal(data) @@ -717,6 +717,13 @@ func makeSignedFundMigrationDataWithTransfer(t *testing.T, chainID string, nonce return b } +func transferAmountString(v *big.Int) string { + if v == nil { + return "" + } + return v.String() +} + func insertSignedFundMigrationEvent(t *testing.T, db *gorm.DB, eventID, chainID string, nonce uint64) { t.Helper() event := store.Event{ @@ -786,10 +793,10 @@ func TestFundMigrationEVM_TSSFundMigrationAmountThreaded(t *testing.T) { builder.On("BroadcastFundMigrationTx", mock.Anything, - mock.MatchedBy(func(req *common.UnsignedSigningReq) bool { - return req.TSSFundMigrationAmount != nil && req.TSSFundMigrationAmount.String() == "777000000000000000" - }), mock.Anything, + mock.MatchedBy(func(data *common.FundMigrationData) bool { + return data.TransferAmount != nil && data.TransferAmount.String() == "777000000000000000" + }), mock.Anything). Return("0xmigrate777", nil) diff --git a/universalClient/tss/txbroadcaster/evm.go b/universalClient/tss/txbroadcaster/evm.go index e0bb380f0..8c72cf676 100644 --- a/universalClient/tss/txbroadcaster/evm.go +++ b/universalClient/tss/txbroadcaster/evm.go @@ -118,12 +118,19 @@ func (b *Broadcaster) broadcastFundMigrationEVM(ctx context.Context, event *stor l1GasFee := new(big.Int) l1GasFee.SetString(data.L1GasFee, 10) + transferAmount, ok := new(big.Int).SetString(data.TransferAmount, 10) + if !ok || transferAmount.Sign() <= 0 { + log.Warn().Str("transfer_amount", data.TransferAmount).Msg("event carries no usable transfer amount") + return + } + migrationData := &common.FundMigrationData{ - From: oldTSSAddr, - To: currentTSSAddr, - GasPrice: gasPrice, - GasLimit: data.GasLimit, - L1GasFee: l1GasFee, + From: oldTSSAddr, + To: currentTSSAddr, + GasPrice: gasPrice, + GasLimit: data.GasLimit, + L1GasFee: l1GasFee, + TransferAmount: transferAmount, } txHash, broadcastErr := builder.BroadcastFundMigrationTx(ctx, signingReq, migrationData, signature) diff --git a/universalClient/tss/txflow/parse.go b/universalClient/tss/txflow/parse.go index 63ec70972..bd69ac57e 100644 --- a/universalClient/tss/txflow/parse.go +++ b/universalClient/tss/txflow/parse.go @@ -24,9 +24,8 @@ func DecodeSigningData(sd *SigningData) (*common.UnsignedSigningReq, []byte, err return nil, nil, fmt.Errorf("failed to decode signature: %w", err) } return &common.UnsignedSigningReq{ - SigningHash: signingHash, - Nonce: sd.Nonce, - TSSFundMigrationAmount: sd.TSSFundMigrationAmount, + SigningHash: signingHash, + Nonce: sd.Nonce, }, signature, nil } diff --git a/universalClient/tss/txflow/types.go b/universalClient/tss/txflow/types.go index c51a31f81..81ab272bc 100644 --- a/universalClient/tss/txflow/types.go +++ b/universalClient/tss/txflow/types.go @@ -8,8 +8,6 @@ package txflow import ( - "math/big" - uexecutortypes "github.com/pushchain/push-chain-node/x/uexecutor/types" utsstypes "github.com/pushchain/push-chain-node/x/utss/types" ) @@ -19,10 +17,9 @@ import ( // — broadcaster to assemble + send the tx, resolver to compare the signed // nonce against the chain's finalized nonce. type SigningData struct { - Signature string `json:"signature"` // hex-encoded 64/65 byte signature - SigningHash string `json:"signing_hash"` // hex-encoded signing hash - Nonce uint64 `json:"nonce"` - TSSFundMigrationAmount *big.Int `json:"tss_fund_migration_amount,omitempty"` + Signature string `json:"signature"` // hex-encoded 64/65 byte signature + SigningHash string `json:"signing_hash"` // hex-encoded signing hash + Nonce uint64 `json:"nonce"` } // SignedOutboundData wraps OutboundCreatedEvent with the signing data the diff --git a/x/utss/keeper/msg_initiate_fund_migration.go b/x/utss/keeper/msg_initiate_fund_migration.go index c24fbf36a..a076312cc 100644 --- a/x/utss/keeper/msg_initiate_fund_migration.go +++ b/x/utss/keeper/msg_initiate_fund_migration.go @@ -3,6 +3,7 @@ package keeper import ( "context" "fmt" + "math/big" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/pushchain/push-chain-node/x/utss/types" @@ -10,7 +11,15 @@ import ( // InitiateFundMigration validates and creates a fund migration from an old TSS key vault // to the current TSS key vault for a specific chain. -func (k Keeper) InitiateFundMigration(ctx context.Context, oldKeyId, chain string) (uint64, error) { +// +// balance is the native balance the admin observed on the old TSS address. The +// amount to sweep is derived here rather than supplied, because the gas figures +// it depends on are read from UniversalCore below, after the admin signed the +// message. Deriving it here means transfer_amount and the gas fields recorded on +// the migration are consistent by construction, and every universal validator +// signs that one pinned amount instead of re-deriving it from a live balance +// that any 1-wei inflow can shift (F-2026-18142). +func (k Keeper) InitiateFundMigration(ctx context.Context, oldKeyId, chain, balance string) (uint64, error) { sdkCtx := sdk.UnwrapSDKContext(ctx) // 1. Validate old key exists in history @@ -88,6 +97,23 @@ func (k Keeper) InitiateFundMigration(ctx context.Context, oldKeyId, chain strin return 0, fmt.Errorf("failed to get next migration id: %w", err) } + // Derive the sweep amount from the observed balance and the fees just + // fetched. Rejecting here turns a balance that cannot cover its own transfer + // into a clean error at initiate time, rather than a migration that is + // created PENDING and then fails to sign. + observedBalance, err := types.ParseBalance(balance) + if err != nil { + return 0, err + } + totalFee := new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(gasLimit)) + totalFee.Add(totalFee, l1GasFee) + transferAmount := new(big.Int).Sub(observedBalance, totalFee) + if transferAmount.Sign() <= 0 { + return 0, fmt.Errorf( + "balance %s on the old TSS address does not cover the migration fee %s (gas_price %s * gas_limit %d + l1_gas_fee %s) for chain %s", + observedBalance, totalFee, gasPrice, gasLimit, l1GasFee, chain) + } + migration := types.FundMigration{ Id: migrationId, OldKeyId: oldKeyId, @@ -100,6 +126,7 @@ func (k Keeper) InitiateFundMigration(ctx context.Context, oldKeyId, chain strin GasPrice: gasPrice.String(), GasLimit: gasLimit, L1GasFee: l1GasFee.String(), + TransferAmount: transferAmount.String(), } if err := k.FundMigrations.Set(ctx, migrationId, migration); err != nil { @@ -121,6 +148,7 @@ func (k Keeper) InitiateFundMigration(ctx context.Context, oldKeyId, chain strin GasPrice: gasPrice.String(), GasLimit: gasLimit, L1GasFee: l1GasFee.String(), + TransferAmount: transferAmount.String(), }) if err != nil { return 0, fmt.Errorf("failed to create migration event: %w", err) diff --git a/x/utss/keeper/msg_server.go b/x/utss/keeper/msg_server.go index ff5ff890a..f9ee8056e 100755 --- a/x/utss/keeper/msg_server.go +++ b/x/utss/keeper/msg_server.go @@ -103,7 +103,7 @@ func (ms msgServer) InitiateFundMigration(ctx context.Context, msg *types.MsgIni return nil, errors.Wrapf(sdkErrors.ErrUnauthorized, "invalid authority; expected %s, got %s", params.Admin, msg.Signer) } - migrationId, err := ms.k.InitiateFundMigration(ctx, msg.OldKeyId, msg.Chain) + migrationId, err := ms.k.InitiateFundMigration(ctx, msg.OldKeyId, msg.Chain, msg.Balance) if err != nil { return nil, err } diff --git a/x/utss/types/events.go b/x/utss/types/events.go index 5ae720c6b..53e09c3ec 100644 --- a/x/utss/types/events.go +++ b/x/utss/types/events.go @@ -110,6 +110,10 @@ type FundMigrationInitiatedEventData struct { GasPrice string `json:"gas_price"` GasLimit uint64 `json:"gas_limit"` L1GasFee string `json:"l1_gas_fee"` + // TransferAmount is the native amount (wei) to sweep, pinned by the chain as + // balance - (gas_price * gas_limit) - l1_gas_fee. Universal validators sign + // this value instead of re-deriving it from a live balance (F-2026-18142). + TransferAmount string `json:"transfer_amount"` } // NewFundMigrationInitiatedEvent creates and returns a Cosmos SDK event. @@ -130,6 +134,7 @@ func NewFundMigrationInitiatedEvent(e FundMigrationInitiatedEventData) (sdk.Even sdk.NewAttribute("gas_price", e.GasPrice), sdk.NewAttribute("gas_limit", fmt.Sprintf("%d", e.GasLimit)), sdk.NewAttribute("l1_gas_fee", e.L1GasFee), + sdk.NewAttribute("transfer_amount", e.TransferAmount), sdk.NewAttribute("data", string(bz)), ) diff --git a/x/utss/types/msg_initiate_fund_migration.go b/x/utss/types/msg_initiate_fund_migration.go new file mode 100644 index 000000000..228050bb9 --- /dev/null +++ b/x/utss/types/msg_initiate_fund_migration.go @@ -0,0 +1,69 @@ +package types + +import ( + "math/big" + "strings" + + "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +var _ sdk.Msg = &MsgInitiateFundMigration{} + +const ( + // maxUint256Bits is the width of the native balances this message deals in. + maxUint256Bits = 256 + // maxUint256DecimalLen bounds the decimal string before it is parsed. Max + // uint256 is exactly 78 digits; the slack absorbs a zero-padded client value. + // Checking length first matters: big.Int decimal parsing is superlinear, so a + // caller-supplied million-digit string is rejected in O(1) instead of being + // parsed and then discarded. + maxUint256DecimalLen = 80 +) + +// ValidateBasic does a sanity check on the provided data. +func (msg *MsgInitiateFundMigration) ValidateBasic() error { + if _, err := sdk.AccAddressFromBech32(msg.Signer); err != nil { + return errors.Wrap(err, "invalid signer address") + } + if strings.TrimSpace(msg.OldKeyId) == "" { + return errors.Wrap(sdkerrors.ErrInvalidRequest, "old_key_id is required") + } + if strings.TrimSpace(msg.Chain) == "" { + return errors.Wrap(sdkerrors.ErrInvalidRequest, "chain is required") + } + if _, err := ParseBalance(msg.Balance); err != nil { + return err + } + return nil +} + +// ParseBalance validates the admin-supplied native balance and returns it. +// +// The value is the balance the admin observed on the old TSS address, not the +// amount to sweep: the keeper derives that as balance - gas - l1_gas_fee using +// the fee figures it fetches itself, so the emitted transfer_amount is +// consistent with the emitted fees by construction. The admin cannot compute it +// because those fees are read from UniversalCore inside the handler, after the +// message is signed. +func ParseBalance(balance string) (*big.Int, error) { + balance = strings.TrimSpace(balance) + if balance == "" { + return nil, errors.Wrap(sdkerrors.ErrInvalidRequest, "balance is required") + } + if len(balance) > maxUint256DecimalLen { + return nil, errors.Wrapf(sdkerrors.ErrInvalidRequest, + "balance is too long: %d characters (max %d)", len(balance), maxUint256DecimalLen) + } + bi, ok := new(big.Int).SetString(balance, 10) + if !ok || bi.Sign() < 0 { + return nil, errors.Wrap(sdkerrors.ErrInvalidRequest, "balance must be a valid non-negative integer") + } + // A length cap alone is not enough: 78 nines fits in 78 characters but is + // wider than uint256, and the EVM ABI encoder truncates such values silently. + if bi.BitLen() > maxUint256Bits { + return nil, errors.Wrap(sdkerrors.ErrInvalidRequest, "balance exceeds uint256") + } + return bi, nil +} diff --git a/x/utss/types/msg_initiate_fund_migration_test.go b/x/utss/types/msg_initiate_fund_migration_test.go new file mode 100644 index 000000000..6f6429a8c --- /dev/null +++ b/x/utss/types/msg_initiate_fund_migration_test.go @@ -0,0 +1,112 @@ +package types_test + +import ( + "math/big" + "strings" + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + "github.com/pushchain/push-chain-node/app" + "github.com/pushchain/push-chain-node/x/utss/types" +) + +const validSigner = "push1fgaewhyd9fkwtqaj9c233letwcuey6dgly9gv9" + +// setBech32Prefixes installs the push prefixes on the global SDK config. Without +// it validSigner fails to parse, ValidateBasic returns on the signer check +// before reaching anything else, and every assertion below becomes vacuous. +// Tests in a package share this global, so each entry point sets it rather than +// relying on another test file having run first. +func setBech32Prefixes() { + cfg := sdk.GetConfig() + cfg.SetBech32PrefixForAccount(app.Bech32PrefixAccAddr, app.Bech32PrefixAccPub) + cfg.SetBech32PrefixForValidator(app.Bech32PrefixValAddr, app.Bech32PrefixValPub) +} + +func baseInitiateMsg() *types.MsgInitiateFundMigration { + return &types.MsgInitiateFundMigration{ + Signer: validSigner, + OldKeyId: "old-key-1", + Chain: "eip155:11155111", + Balance: "1000000000000000000", + } +} + +func TestMsgInitiateFundMigration_ValidateBasic(t *testing.T) { + setBech32Prefixes() + + maxUint256 := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(1)) + + tests := []struct { + name string + mutate func(*types.MsgInitiateFundMigration) + wantErr string + }{ + {"valid", func(m *types.MsgInitiateFundMigration) {}, ""}, + {"max uint256 balance accepted", func(m *types.MsgInitiateFundMigration) { + m.Balance = maxUint256.String() + }, ""}, + {"zero balance accepted here (the keeper rejects it once fees are known)", func(m *types.MsgInitiateFundMigration) { + m.Balance = "0" + }, ""}, + {"bad signer", func(m *types.MsgInitiateFundMigration) { m.Signer = "not-bech32" }, "invalid signer"}, + {"missing old_key_id", func(m *types.MsgInitiateFundMigration) { m.OldKeyId = " " }, "old_key_id is required"}, + {"missing chain", func(m *types.MsgInitiateFundMigration) { m.Chain = "" }, "chain is required"}, + {"missing balance", func(m *types.MsgInitiateFundMigration) { m.Balance = "" }, "balance is required"}, + {"negative balance", func(m *types.MsgInitiateFundMigration) { m.Balance = "-1" }, "non-negative"}, + {"non-numeric balance", func(m *types.MsgInitiateFundMigration) { m.Balance = "1e18" }, "non-negative"}, + {"balance over uint256", func(m *types.MsgInitiateFundMigration) { + m.Balance = new(big.Int).Add(maxUint256, big.NewInt(1)).String() + }, "exceeds uint256"}, + { + // 78 nines fits the 80-character cap but is wider than uint256, so a + // length check alone would let it through. The EVM ABI encoder + // truncates such values silently, so BitLen is the load-bearing check. + "78 nines rejected despite fitting the length cap", + func(m *types.MsgInitiateFundMigration) { m.Balance = strings.Repeat("9", 78) }, + "exceeds uint256", + }, + {"absurdly long balance", func(m *types.MsgInitiateFundMigration) { + m.Balance = strings.Repeat("9", 1000) + }, "too long"}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + msg := baseInitiateMsg() + tc.mutate(msg) + err := msg.ValidateBasic() + if tc.wantErr == "" { + require.NoError(t, err) + return + } + require.Error(t, err) + require.Contains(t, err.Error(), tc.wantErr) + }) + } +} + +// TestMsgInitiateFundMigration_RejectsHugeBalanceFast pins the ordering inside +// ParseBalance: the length cap has to run before big.Int parses the string. +// Decimal parsing is superlinear, so without the cap a caller-supplied +// multi-million-digit balance is fully parsed and then thrown away. +func TestMsgInitiateFundMigration_RejectsHugeBalanceFast(t *testing.T) { + setBech32Prefixes() + + msg := baseInitiateMsg() + msg.Balance = strings.Repeat("9", 3_000_000) + + start := time.Now() + err := msg.ValidateBasic() + elapsed := time.Since(start) + + // Timing first: require.Contains aborts the test, so asserting the message + // before the duration would mean the duration is never checked. + require.Less(t, elapsed, time.Second, + "rejecting a 3000000-digit balance took %s — the length cap must reject before big.Int parses", elapsed) + require.Error(t, err) + require.Contains(t, err.Error(), "too long") +} diff --git a/x/utss/types/tx.pb.go b/x/utss/types/tx.pb.go index b93c81a3c..8c67a0f76 100644 --- a/x/utss/types/tx.pb.go +++ b/x/utss/types/tx.pb.go @@ -330,6 +330,12 @@ type MsgInitiateFundMigration struct { Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` OldKeyId string `protobuf:"bytes,2,opt,name=old_key_id,json=oldKeyId,proto3" json:"old_key_id,omitempty"` Chain string `protobuf:"bytes,3,opt,name=chain,proto3" json:"chain,omitempty"` + // Native balance (wei, uint256 decimal) observed by the admin on the old TSS + // address. The chain derives transfer_amount = balance - gas - l1_gas_fee from + // it, using the same fee figures it pins into the migration record, so every + // universal validator signs one amount instead of re-deriving it from a live + // balance that a 1-wei inflow can shift (F-2026-18142). + Balance string `protobuf:"bytes,4,opt,name=balance,proto3" json:"balance,omitempty"` } func (m *MsgInitiateFundMigration) Reset() { *m = MsgInitiateFundMigration{} } @@ -386,6 +392,13 @@ func (m *MsgInitiateFundMigration) GetChain() string { return "" } +func (m *MsgInitiateFundMigration) GetBalance() string { + if m != nil { + return m.Balance + } + return "" +} + type MsgInitiateFundMigrationResponse struct { MigrationId uint64 `protobuf:"varint,1,opt,name=migration_id,json=migrationId,proto3" json:"migration_id,omitempty"` } @@ -551,52 +564,53 @@ func init() { func init() { proto.RegisterFile("utss/v1/tx.proto", fileDescriptor_4dcb8cba4d8073e4) } var fileDescriptor_4dcb8cba4d8073e4 = []byte{ - // 711 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x95, 0xcd, 0x4e, 0xdb, 0x4a, - 0x14, 0xc7, 0x63, 0x3e, 0x02, 0x19, 0x10, 0x5c, 0x7c, 0x83, 0x62, 0x22, 0xf0, 0x0d, 0x96, 0x90, - 0x00, 0x29, 0xf1, 0x85, 0x4a, 0x5d, 0x64, 0x57, 0xa4, 0x56, 0x4d, 0x51, 0x24, 0xe4, 0xd2, 0x4a, - 0xed, 0x26, 0x32, 0xf1, 0xc8, 0x1e, 0x81, 0x3d, 0x96, 0xcf, 0x18, 0xc5, 0xbb, 0xaa, 0xab, 0xaa, - 0xab, 0xbe, 0x49, 0x59, 0x74, 0xd1, 0x47, 0xa0, 0x3b, 0xd4, 0x55, 0x17, 0x55, 0x55, 0xc1, 0x82, - 0xd7, 0xa8, 0x3c, 0x1e, 0x3b, 0x38, 0x31, 0x20, 0x95, 0x4d, 0x34, 0x73, 0xbe, 0xe6, 0xff, 0x3b, - 0x67, 0x26, 0x46, 0xff, 0x84, 0x0c, 0x40, 0x3f, 0xdd, 0xd1, 0xd9, 0xa0, 0xe5, 0x07, 0x94, 0x51, - 0x79, 0x26, 0xb6, 0xb4, 0x4e, 0x77, 0xea, 0xb5, 0x3e, 0x05, 0x97, 0x82, 0xee, 0x82, 0x1d, 0x07, - 0xb8, 0x60, 0x27, 0x11, 0xf5, 0xe5, 0x34, 0xc7, 0xc6, 0x1e, 0x06, 0x02, 0xc2, 0xfc, 0x6f, 0x56, - 0x2a, 0xf2, 0x71, 0x6a, 0xac, 0xda, 0xd4, 0xa6, 0x7c, 0xa9, 0xc7, 0x2b, 0x61, 0x5d, 0x49, 0x4a, - 0xf7, 0x12, 0x47, 0xb2, 0x11, 0xae, 0x25, 0xd3, 0x25, 0x1e, 0xd5, 0xf9, 0x6f, 0x62, 0xd2, 0x3e, - 0x48, 0x68, 0xb1, 0x0b, 0xf6, 0x2b, 0xdf, 0x32, 0x19, 0x3e, 0x30, 0x03, 0xd3, 0x05, 0xf9, 0x31, - 0xaa, 0x98, 0x21, 0x73, 0x68, 0x40, 0x58, 0xa4, 0x48, 0x0d, 0x69, 0xb3, 0xb2, 0xa7, 0x7c, 0xff, - 0xd2, 0xac, 0x8a, 0x5a, 0x4f, 0x2c, 0x2b, 0xc0, 0x00, 0x2f, 0x59, 0x40, 0x3c, 0xdb, 0x18, 0x86, - 0xca, 0x4d, 0x54, 0xf6, 0x79, 0x05, 0x65, 0xa2, 0x21, 0x6d, 0xce, 0xed, 0x2e, 0xb6, 0x04, 0x6e, - 0x2b, 0x29, 0xbc, 0x37, 0x75, 0xfe, 0xeb, 0xbf, 0x92, 0x21, 0x82, 0xda, 0x0b, 0xef, 0xaf, 0xcf, - 0xb6, 0x87, 0xe9, 0xda, 0x0a, 0xaa, 0x8d, 0x28, 0x31, 0x30, 0xf8, 0xd4, 0x03, 0xac, 0x7d, 0x95, - 0x90, 0xd2, 0x05, 0xbb, 0xe3, 0x11, 0x46, 0x4c, 0x86, 0x0f, 0x01, 0xf6, 0x71, 0x74, 0x10, 0xd0, - 0x3e, 0x06, 0x90, 0xff, 0x47, 0x65, 0x20, 0xb6, 0x87, 0x83, 0x7b, 0xb5, 0x8a, 0x38, 0xb9, 0x8d, - 0xe6, 0xfd, 0x24, 0xb9, 0x17, 0xf7, 0x93, 0xcb, 0x5d, 0xd8, 0xad, 0x65, 0x72, 0x0f, 0x01, 0x44, - 0xf1, 0xc3, 0xc8, 0xc7, 0xc6, 0x9c, 0x3f, 0xdc, 0xb4, 0x5b, 0xb1, 0x6a, 0x51, 0xe8, 0xe3, 0xf5, - 0xd9, 0xb6, 0xca, 0x27, 0xd3, 0x05, 0xfb, 0x35, 0x65, 0x38, 0x15, 0x38, 0x54, 0xa7, 0x69, 0xa8, - 0x71, 0x9b, 0xf2, 0x0c, 0xef, 0x5c, 0x42, 0x55, 0x51, 0xe1, 0xa1, 0x68, 0x6b, 0x08, 0x31, 0x80, - 0x9e, 0x1f, 0x1e, 0x1d, 0xe3, 0x88, 0x83, 0x55, 0x8c, 0x0a, 0x03, 0x38, 0xe0, 0x06, 0x79, 0x19, - 0x95, 0x8f, 0x71, 0xd4, 0x23, 0x96, 0x32, 0xc9, 0x5d, 0xd3, 0xc7, 0x38, 0xea, 0x58, 0x71, 0x56, - 0xda, 0x10, 0x62, 0x29, 0x53, 0x0d, 0x69, 0x73, 0xca, 0xa8, 0x08, 0x4b, 0xc7, 0x6a, 0x6f, 0x8d, - 0x30, 0xaf, 0xdc, 0x64, 0xce, 0x29, 0xd6, 0x54, 0xb4, 0x5a, 0x64, 0xcf, 0x50, 0x3f, 0xe7, 0x27, - 0xf9, 0x2c, 0xf4, 0xac, 0x2e, 0xb1, 0x03, 0x93, 0x11, 0xea, 0xfd, 0x05, 0xee, 0x2a, 0x42, 0xf4, - 0xc4, 0xea, 0x09, 0xa6, 0x04, 0x77, 0x96, 0x9e, 0x58, 0xfb, 0x1c, 0xab, 0x8a, 0xa6, 0xfb, 0x8e, - 0x49, 0xbc, 0x14, 0x96, 0x6f, 0xda, 0xcd, 0x11, 0x9a, 0xb5, 0x94, 0xa6, 0x50, 0x94, 0xf6, 0x34, - 0x37, 0xc0, 0x9c, 0x2f, 0xa5, 0x92, 0xd7, 0xd1, 0xbc, 0x9b, 0x1a, 0x63, 0x21, 0x12, 0xef, 0xe0, - 0x5c, 0x66, 0xeb, 0x58, 0xda, 0xb7, 0xe1, 0x8c, 0x1f, 0x0a, 0x3d, 0x7a, 0xda, 0xc4, 0xd8, 0x69, - 0x72, 0x0d, 0xcd, 0xb0, 0x41, 0xcf, 0x31, 0xc1, 0x11, 0xec, 0x65, 0x36, 0x78, 0x6e, 0x82, 0x23, - 0x2b, 0x68, 0x06, 0xc2, 0x7e, 0x3c, 0x12, 0x3e, 0xe6, 0x59, 0x23, 0xdd, 0xde, 0x3d, 0xe4, 0x7c, - 0x4b, 0x86, 0x43, 0x2e, 0x6c, 0xc7, 0xee, 0xcf, 0x49, 0x34, 0xd9, 0x05, 0x5b, 0x7e, 0x81, 0xe6, - 0x73, 0x7f, 0x2c, 0x4a, 0xf6, 0xc2, 0x46, 0x1e, 0x7a, 0xbd, 0x71, 0x9b, 0x27, 0x6b, 0x31, 0x46, - 0xcb, 0xc5, 0xcf, 0x7f, 0xfd, 0x66, 0x6a, 0x61, 0x48, 0x7d, 0xeb, 0xde, 0x90, 0xec, 0x98, 0x37, - 0x68, 0x69, 0xfc, 0x19, 0xae, 0xdd, 0xcc, 0x1f, 0x73, 0xd7, 0x37, 0xee, 0x74, 0x17, 0x11, 0xe4, - 0x6f, 0x40, 0x21, 0x41, 0x2e, 0xa4, 0x98, 0xa0, 0xf8, 0x2e, 0x0a, 0x82, 0xfc, 0x11, 0x63, 0x04, - 0xf9, 0xf2, 0x1b, 0x77, 0xba, 0xd3, 0xd2, 0xf5, 0xe9, 0x77, 0xd7, 0x67, 0xdb, 0xd2, 0xde, 0xfe, - 0xf9, 0xa5, 0x2a, 0x5d, 0x5c, 0xaa, 0xd2, 0xef, 0x4b, 0x55, 0xfa, 0x74, 0xa5, 0x96, 0x2e, 0xae, - 0xd4, 0xd2, 0x8f, 0x2b, 0xb5, 0xf4, 0x76, 0xc7, 0x26, 0xcc, 0x09, 0x8f, 0x5a, 0x7d, 0xea, 0xea, - 0x7e, 0x08, 0x0e, 0x7f, 0x70, 0x7c, 0xd5, 0xe4, 0xcb, 0xa6, 0x47, 0x2d, 0xac, 0x0f, 0x74, 0x7e, - 0xb5, 0xf8, 0xa7, 0xec, 0xa8, 0xcc, 0xbf, 0x43, 0x8f, 0xfe, 0x04, 0x00, 0x00, 0xff, 0xff, 0x7f, - 0xd9, 0x50, 0x46, 0x2d, 0x07, 0x00, 0x00, + // 724 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x55, 0x4d, 0x4f, 0xdb, 0x48, + 0x18, 0x8e, 0xf9, 0x08, 0x64, 0x40, 0xb0, 0x78, 0x83, 0x62, 0x22, 0xf0, 0x06, 0x4b, 0x48, 0x80, + 0x94, 0x78, 0x61, 0xa5, 0x3d, 0xe4, 0x56, 0xa4, 0x56, 0x4d, 0x51, 0x24, 0xe4, 0xd2, 0x4a, 0xed, + 0x25, 0x72, 0xe2, 0x91, 0x3d, 0x02, 0x7b, 0x2c, 0xbf, 0x63, 0x14, 0xdf, 0xaa, 0x9e, 0xaa, 0x9e, + 0xfa, 0x53, 0x38, 0xf4, 0xd0, 0x9f, 0x40, 0x6f, 0xa8, 0xa7, 0x1e, 0xaa, 0xaa, 0x82, 0x03, 0x7f, + 0xa3, 0xf2, 0x78, 0xec, 0xe0, 0xc4, 0x80, 0x54, 0x2e, 0xd6, 0xbc, 0x9f, 0xf3, 0x3c, 0xcf, 0x3b, + 0x33, 0x46, 0x7f, 0x85, 0x0c, 0x40, 0x3f, 0xdb, 0xd3, 0xd9, 0xb0, 0xe5, 0x07, 0x94, 0x51, 0x79, + 0x2e, 0xf6, 0xb4, 0xce, 0xf6, 0xea, 0xb5, 0x01, 0x05, 0x97, 0x82, 0xee, 0x82, 0x1d, 0x27, 0xb8, + 0x60, 0x27, 0x19, 0xf5, 0xd5, 0xb4, 0xc6, 0xc6, 0x1e, 0x06, 0x02, 0xc2, 0xfd, 0x77, 0xd6, 0x2a, + 0xf2, 0x71, 0xea, 0xac, 0xda, 0xd4, 0xa6, 0x7c, 0xa9, 0xc7, 0x2b, 0xe1, 0x5d, 0x4b, 0x5a, 0xf7, + 0x92, 0x40, 0x62, 0x88, 0xd0, 0x8a, 0xe9, 0x12, 0x8f, 0xea, 0xfc, 0x9b, 0xb8, 0xb4, 0x0f, 0x12, + 0x5a, 0xee, 0x82, 0xfd, 0xca, 0xb7, 0x4c, 0x86, 0x8f, 0xcc, 0xc0, 0x74, 0x41, 0xfe, 0x1f, 0x55, + 0xcc, 0x90, 0x39, 0x34, 0x20, 0x2c, 0x52, 0xa4, 0x86, 0xb4, 0x5d, 0x39, 0x50, 0xbe, 0x7d, 0x6e, + 0x56, 0x45, 0xaf, 0x27, 0x96, 0x15, 0x60, 0x80, 0x97, 0x2c, 0x20, 0x9e, 0x6d, 0x8c, 0x52, 0xe5, + 0x26, 0x2a, 0xfb, 0xbc, 0x83, 0x32, 0xd5, 0x90, 0xb6, 0x17, 0xf6, 0x97, 0x5b, 0x82, 0x6e, 0x2b, + 0x69, 0x7c, 0x30, 0x73, 0xf1, 0xf3, 0x9f, 0x92, 0x21, 0x92, 0xda, 0x4b, 0xef, 0x6f, 0xce, 0x77, + 0x47, 0xe5, 0xda, 0x1a, 0xaa, 0x8d, 0x21, 0x31, 0x30, 0xf8, 0xd4, 0x03, 0xac, 0x7d, 0x91, 0x90, + 0xd2, 0x05, 0xbb, 0xe3, 0x11, 0x46, 0x4c, 0x86, 0x8f, 0x01, 0x0e, 0x71, 0x74, 0x14, 0xd0, 0x01, + 0x06, 0x90, 0xff, 0x45, 0x65, 0x20, 0xb6, 0x87, 0x83, 0x07, 0xb1, 0x8a, 0x3c, 0xb9, 0x8d, 0x16, + 0xfd, 0xa4, 0xb8, 0x17, 0xeb, 0xc9, 0xe1, 0x2e, 0xed, 0xd7, 0x32, 0xb8, 0xc7, 0x00, 0xa2, 0xf9, + 0x71, 0xe4, 0x63, 0x63, 0xc1, 0x1f, 0x19, 0xed, 0x56, 0x8c, 0x5a, 0x34, 0xfa, 0x78, 0x73, 0xbe, + 0xab, 0xf2, 0xc9, 0x74, 0xc1, 0x7e, 0x4d, 0x19, 0x4e, 0x01, 0x8e, 0xd0, 0x69, 0x1a, 0x6a, 0xdc, + 0x85, 0x3c, 0xa3, 0x77, 0x21, 0xa1, 0xaa, 0xe8, 0xf0, 0x58, 0x6a, 0x1b, 0x08, 0x31, 0x80, 0x9e, + 0x1f, 0xf6, 0x4f, 0x70, 0xc4, 0x89, 0x55, 0x8c, 0x0a, 0x03, 0x38, 0xe2, 0x0e, 0x79, 0x15, 0x95, + 0x4f, 0x70, 0xd4, 0x23, 0x96, 0x32, 0xcd, 0x43, 0xb3, 0x27, 0x38, 0xea, 0x58, 0x71, 0x55, 0x2a, + 0x08, 0xb1, 0x94, 0x99, 0x86, 0xb4, 0x3d, 0x63, 0x54, 0x84, 0xa7, 0x63, 0xb5, 0x77, 0xc6, 0x38, + 0xaf, 0xdd, 0xe6, 0x9c, 0x43, 0xac, 0xa9, 0x68, 0xbd, 0xc8, 0x9f, 0x51, 0xfd, 0x9a, 0x9f, 0xe4, + 0xb3, 0xd0, 0xb3, 0xba, 0xc4, 0x0e, 0x4c, 0x46, 0xa8, 0xf7, 0x07, 0x74, 0xd7, 0x11, 0xa2, 0xa7, + 0x56, 0x4f, 0x70, 0x4a, 0xe8, 0xce, 0xd3, 0x53, 0xeb, 0x90, 0xd3, 0xaa, 0xa2, 0xd9, 0x81, 0x63, + 0x12, 0x2f, 0x25, 0xcb, 0x0d, 0x59, 0x41, 0x73, 0x7d, 0xf3, 0xd4, 0xf4, 0x06, 0x98, 0x33, 0xad, + 0x18, 0xa9, 0xd9, 0x6e, 0x8e, 0xf1, 0xdc, 0x48, 0x79, 0x16, 0xc2, 0xd5, 0x9e, 0xe6, 0x46, 0x9b, + 0x8b, 0xa5, 0x7c, 0xe5, 0x4d, 0xb4, 0xe8, 0xa6, 0xce, 0x18, 0xa2, 0xc4, 0xb5, 0x5d, 0xc8, 0x7c, + 0x1d, 0x2b, 0x96, 0x24, 0x9d, 0xfe, 0x63, 0xe5, 0x18, 0xdf, 0x6d, 0x6a, 0x62, 0x37, 0xb9, 0x86, + 0xe6, 0xd8, 0xb0, 0xe7, 0x98, 0xe0, 0x08, 0x55, 0xca, 0x6c, 0xf8, 0xdc, 0x04, 0x27, 0x96, 0x05, + 0xc2, 0x41, 0x3c, 0x2c, 0x2e, 0xcb, 0xbc, 0x91, 0x9a, 0xf7, 0x8f, 0x3f, 0x2f, 0xc9, 0x68, 0xfc, + 0x85, 0x72, 0xec, 0xff, 0x98, 0x46, 0xd3, 0x5d, 0xb0, 0xe5, 0x17, 0x68, 0x31, 0xf7, 0xe4, 0x28, + 0xd9, 0xdd, 0x1b, 0x7b, 0x02, 0xea, 0x8d, 0xbb, 0x22, 0x99, 0xc4, 0x18, 0xad, 0x16, 0x3f, 0x0c, + 0x9b, 0xb7, 0x4b, 0x0b, 0x53, 0xea, 0x3b, 0x0f, 0xa6, 0x64, 0xdb, 0xbc, 0x41, 0x2b, 0x93, 0x17, + 0x74, 0xe3, 0x76, 0xfd, 0x44, 0xb8, 0xbe, 0x75, 0x6f, 0xb8, 0x88, 0x41, 0xfe, 0x04, 0x14, 0x32, + 0xc8, 0xa5, 0x14, 0x33, 0x28, 0x3e, 0x8b, 0x82, 0x41, 0x7e, 0x8b, 0x09, 0x06, 0xf9, 0xf6, 0x5b, + 0xf7, 0x86, 0xd3, 0xd6, 0xf5, 0xd9, 0x77, 0x37, 0xe7, 0xbb, 0xd2, 0xc1, 0xe1, 0xc5, 0x95, 0x2a, + 0x5d, 0x5e, 0xa9, 0xd2, 0xaf, 0x2b, 0x55, 0xfa, 0x74, 0xad, 0x96, 0x2e, 0xaf, 0xd5, 0xd2, 0xf7, + 0x6b, 0xb5, 0xf4, 0x76, 0xcf, 0x26, 0xcc, 0x09, 0xfb, 0xad, 0x01, 0x75, 0x75, 0x3f, 0x04, 0x87, + 0x5f, 0x45, 0xbe, 0x6a, 0xf2, 0x65, 0xd3, 0xa3, 0x16, 0xd6, 0x87, 0x3a, 0x3f, 0x5a, 0xfc, 0x27, + 0xd7, 0x2f, 0xf3, 0x3f, 0xd4, 0x7f, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x58, 0x7a, 0x6f, 0x8f, + 0x47, 0x07, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1050,6 +1064,13 @@ func (m *MsgInitiateFundMigration) MarshalToSizedBuffer(dAtA []byte) (int, error _ = i var l int _ = l + if len(m.Balance) > 0 { + i -= len(m.Balance) + copy(dAtA[i:], m.Balance) + i = encodeVarintTx(dAtA, i, uint64(len(m.Balance))) + i-- + dAtA[i] = 0x22 + } if len(m.Chain) > 0 { i -= len(m.Chain) copy(dAtA[i:], m.Chain) @@ -1288,6 +1309,10 @@ func (m *MsgInitiateFundMigration) Size() (n int) { if l > 0 { n += 1 + l + sovTx(uint64(l)) } + l = len(m.Balance) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } return n } @@ -1997,6 +2022,38 @@ func (m *MsgInitiateFundMigration) Unmarshal(dAtA []byte) error { } m.Chain = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Balance", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Balance = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipTx(dAtA[iNdEx:]) diff --git a/x/utss/types/types.pb.go b/x/utss/types/types.pb.go index 2bdd43794..b1020b7e6 100644 --- a/x/utss/types/types.pb.go +++ b/x/utss/types/types.pb.go @@ -509,6 +509,7 @@ type FundMigration struct { GasPrice string `protobuf:"bytes,11,opt,name=gas_price,json=gasPrice,proto3" json:"gas_price,omitempty"` GasLimit uint64 `protobuf:"varint,12,opt,name=gas_limit,json=gasLimit,proto3" json:"gas_limit,omitempty"` L1GasFee string `protobuf:"bytes,13,opt,name=l1_gas_fee,json=l1GasFee,proto3" json:"l1_gas_fee,omitempty"` + TransferAmount string `protobuf:"bytes,14,opt,name=transfer_amount,json=transferAmount,proto3" json:"transfer_amount,omitempty"` } func (m *FundMigration) Reset() { *m = FundMigration{} } @@ -635,6 +636,13 @@ func (m *FundMigration) GetL1GasFee() string { return "" } +func (m *FundMigration) GetTransferAmount() string { + if m != nil { + return m.TransferAmount + } + return "" +} + func init() { proto.RegisterEnum("utss.v1.TssKeyProcessStatus", TssKeyProcessStatus_name, TssKeyProcessStatus_value) proto.RegisterEnum("utss.v1.TssProcessType", TssProcessType_name, TssProcessType_value) @@ -651,72 +659,73 @@ func init() { func init() { proto.RegisterFile("utss/v1/types.proto", fileDescriptor_6ecfa9650339f6c3) } var fileDescriptor_6ecfa9650339f6c3 = []byte{ - // 1030 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x56, 0xcf, 0x4f, 0xe3, 0x46, - 0x14, 0x8e, 0x13, 0x08, 0x9b, 0x47, 0x48, 0xc3, 0x10, 0x20, 0xcb, 0x8f, 0x00, 0xd9, 0x95, 0x8a, - 0x50, 0x37, 0x56, 0x5a, 0x0e, 0x15, 0xb7, 0x2c, 0x38, 0x60, 0x01, 0x21, 0x75, 0x1c, 0xd4, 0xdd, - 0x8b, 0xeb, 0xc4, 0xb3, 0xce, 0x28, 0x89, 0x6d, 0x65, 0x1c, 0x44, 0x7a, 0xa9, 0xd4, 0x63, 0x4f, - 0x3d, 0xf6, 0xc8, 0x9f, 0xd0, 0xbf, 0xa2, 0xea, 0x71, 0x8f, 0x3d, 0x56, 0xa0, 0xaa, 0xfd, 0x33, - 0xaa, 0x99, 0x71, 0x12, 0x3b, 0xc9, 0x05, 0x66, 0xde, 0xf7, 0xbd, 0xe7, 0x37, 0xdf, 0xfb, 0x66, - 0x00, 0x36, 0x86, 0x3e, 0xa5, 0xf2, 0x43, 0x59, 0xf6, 0x47, 0x1e, 0xa6, 0x25, 0x6f, 0xe0, 0xfa, - 0x2e, 0x5a, 0x61, 0xc1, 0xd2, 0x43, 0x79, 0xa7, 0xd0, 0x76, 0x69, 0xdf, 0xa5, 0x72, 0xcb, 0xa4, - 0x58, 0x7e, 0x28, 0xb7, 0xb0, 0x6f, 0x96, 0xe5, 0xb6, 0x4b, 0x1c, 0x41, 0xdc, 0xd9, 0x0e, 0xf0, - 0x3e, 0xb5, 0x59, 0x8d, 0x3e, 0xb5, 0x03, 0x20, 0x67, 0xbb, 0xb6, 0xcb, 0x97, 0x32, 0x5b, 0x05, - 0xd1, 0x75, 0xb3, 0x4f, 0x1c, 0x57, 0xe6, 0x3f, 0x45, 0xa8, 0xf8, 0x2d, 0x24, 0xeb, 0xe6, 0xc0, - 0xec, 0x53, 0x94, 0x83, 0x65, 0xd3, 0xea, 0x13, 0x27, 0x2f, 0x1d, 0x4a, 0xc7, 0x29, 0x4d, 0x6c, - 0xce, 0xf2, 0xbf, 0x3d, 0x1d, 0xc4, 0xfe, 0x7b, 0x3a, 0x90, 0x7e, 0xf9, 0xf7, 0xf7, 0x93, 0x55, - 0xde, 0xac, 0xc7, 0xf9, 0xc5, 0xa7, 0x38, 0xac, 0xe9, 0x94, 0x5e, 0xe3, 0x51, 0x7d, 0xe0, 0xb6, - 0x31, 0xa5, 0xe8, 0x14, 0x92, 0xd4, 0x37, 0xfd, 0x21, 0xe5, 0x25, 0x32, 0x5f, 0xef, 0x95, 0x82, - 0x73, 0x94, 0x22, 0xbc, 0x06, 0xe7, 0x68, 0x01, 0x17, 0x15, 0x21, 0xed, 0x99, 0x03, 0x9f, 0xb4, - 0x89, 0x67, 0x3a, 0x3e, 0xcd, 0xc7, 0x0f, 0x13, 0xc7, 0x29, 0x2d, 0x12, 0x43, 0x47, 0x90, 0x6e, - 0xf5, 0xdc, 0x76, 0xd7, 0xe8, 0x60, 0x62, 0x77, 0xfc, 0x7c, 0xe2, 0x50, 0x3a, 0x4e, 0x68, 0xab, - 0x3c, 0x76, 0xc5, 0x43, 0xe8, 0x0d, 0xac, 0xe1, 0x47, 0x8f, 0x0c, 0x46, 0x63, 0xce, 0x12, 0xe7, - 0xa4, 0x45, 0x30, 0x20, 0x9d, 0x41, 0xda, 0x13, 0x4d, 0x18, 0x4c, 0xef, 0xfc, 0x32, 0xef, 0x73, - 0x3b, 0xdc, 0x67, 0xd0, 0xa4, 0x3e, 0xf2, 0xb0, 0xb6, 0xea, 0x4d, 0x37, 0x28, 0x03, 0x71, 0x62, - 0xe5, 0x93, 0x87, 0xd2, 0xf1, 0x92, 0x16, 0x27, 0xd6, 0xd9, 0x51, 0x58, 0x99, 0x1c, 0x57, 0xc6, - 0xa7, 0xd4, 0xe8, 0xe2, 0x91, 0x11, 0xa4, 0x15, 0x7f, 0x8e, 0x43, 0x52, 0x1c, 0x1d, 0xed, 0x03, - 0x30, 0xd4, 0x1b, 0xb6, 0xba, 0x78, 0x14, 0x48, 0x9c, 0xf2, 0x29, 0xad, 0xf3, 0x00, 0xda, 0x84, - 0x24, 0x4b, 0x24, 0x56, 0x3e, 0x2e, 0xd4, 0xef, 0xe2, 0x91, 0x6a, 0xcd, 0x69, 0x93, 0x58, 0xa0, - 0xcd, 0x29, 0x6c, 0x7d, 0x22, 0x8e, 0xd9, 0x23, 0x3f, 0x62, 0xcb, 0x88, 0xa8, 0x24, 0x14, 0xc8, - 0x4d, 0xd0, 0xf7, 0x21, 0xb9, 0x4a, 0xb0, 0xd1, 0xc5, 0x23, 0x1b, 0x3b, 0xd1, 0x94, 0x65, 0x9e, - 0xb2, 0x2e, 0xa0, 0x30, 0x7f, 0x1f, 0x60, 0xac, 0xdc, 0x44, 0x85, 0x54, 0x10, 0x51, 0xad, 0xb3, - 0xd7, 0x61, 0x31, 0xd2, 0x61, 0x31, 0x8a, 0xff, 0xc4, 0xe1, 0x95, 0x4e, 0xa9, 0xf2, 0x80, 0x1d, - 0x3f, 0x10, 0x51, 0x1a, 0x8b, 0x88, 0x4e, 0x01, 0x30, 0x03, 0xc4, 0x38, 0xe2, 0x7c, 0x1c, 0x9b, - 0xe1, 0x71, 0xf0, 0x34, 0x3e, 0x8c, 0x14, 0x1e, 0x2f, 0x91, 0x3c, 0x31, 0x5a, 0x62, 0x7e, 0x80, - 0x3c, 0x63, 0xc6, 0x63, 0xd1, 0xee, 0x97, 0x66, 0xba, 0x67, 0xf6, 0x9a, 0xb3, 0x45, 0x2a, 0x3a, - 0xfd, 0xd9, 0x49, 0x24, 0x17, 0x4c, 0x62, 0xce, 0x82, 0x2b, 0x0b, 0x2c, 0x38, 0x6b, 0xe5, 0x57, - 0xf3, 0x56, 0x9e, 0x9a, 0x21, 0x15, 0x36, 0x43, 0xd4, 0x42, 0x30, 0x63, 0xa1, 0xe2, 0x1f, 0x09, - 0x58, 0xab, 0x0e, 0x1d, 0xeb, 0x96, 0xd8, 0x03, 0xd3, 0x27, 0xae, 0x33, 0x27, 0xf6, 0x1e, 0x80, - 0xdb, 0xb3, 0x8c, 0x88, 0xd1, 0x5e, 0xb9, 0x3d, 0xeb, 0x9a, 0x97, 0x7f, 0x0b, 0x19, 0x86, 0x86, - 0x3e, 0x91, 0xe0, 0x8c, 0xb4, 0xdb, 0xb3, 0xf4, 0x89, 0x51, 0xdf, 0x42, 0xa6, 0x3d, 0x1c, 0x0c, - 0xd8, 0xc8, 0x82, 0x3a, 0x4b, 0x82, 0x15, 0x44, 0x45, 0xad, 0xaf, 0x00, 0x8d, 0x59, 0xa1, 0x7a, - 0x42, 0xd6, 0x6c, 0x80, 0x4c, 0x6b, 0xe6, 0x60, 0xb9, 0xdd, 0x31, 0x89, 0xc3, 0x6d, 0x95, 0xd2, - 0xc4, 0x26, 0xf4, 0x9a, 0xac, 0xcc, 0xbc, 0x26, 0x91, 0x53, 0xce, 0x4c, 0xfa, 0x4b, 0xf8, 0x82, - 0x38, 0xc4, 0x27, 0xa6, 0x3f, 0xbe, 0x0d, 0x81, 0xc2, 0x99, 0x49, 0x98, 0xdb, 0x9a, 0x11, 0xdb, - 0x6e, 0xdf, 0xeb, 0xe1, 0x29, 0x31, 0x25, 0x88, 0x93, 0xb0, 0x20, 0x6e, 0xc3, 0x8a, 0xff, 0x68, - 0x74, 0x4c, 0xda, 0x09, 0x34, 0x4f, 0xfa, 0x8f, 0x57, 0x26, 0xed, 0xa0, 0x5d, 0x48, 0xd9, 0x26, - 0x35, 0xbc, 0x01, 0x69, 0xe3, 0xfc, 0xaa, 0x50, 0xd3, 0x36, 0x69, 0x9d, 0xed, 0xc7, 0x60, 0x8f, - 0xf4, 0x89, 0x9f, 0x4f, 0xf3, 0x11, 0x30, 0xf0, 0x86, 0xed, 0xd9, 0x20, 0x7a, 0x65, 0x83, 0xe1, - 0x9f, 0x30, 0xce, 0xaf, 0x89, 0xd4, 0x5e, 0xf9, 0xd2, 0xa4, 0x55, 0x8c, 0x4f, 0xba, 0xb0, 0xb1, - 0xe0, 0xbd, 0x44, 0xbb, 0xb0, 0xad, 0x37, 0x1a, 0xc6, 0xb5, 0xf2, 0xc1, 0xa8, 0x6b, 0x77, 0xe7, - 0x4a, 0xa3, 0x61, 0xd4, 0x95, 0xda, 0x85, 0x5a, 0xbb, 0xcc, 0xc6, 0x16, 0x81, 0x8d, 0xe6, 0x39, - 0xfb, 0x9d, 0x95, 0xd0, 0x0e, 0x6c, 0xcd, 0x82, 0xd5, 0x8a, 0x7a, 0xa3, 0x5c, 0x64, 0xe3, 0x27, - 0x3f, 0x40, 0x26, 0xfa, 0xe8, 0xa1, 0x2d, 0x40, 0x8c, 0x3d, 0x66, 0x5e, 0x2b, 0x1f, 0x2e, 0x95, - 0x5a, 0x36, 0x86, 0xb6, 0x61, 0x23, 0x1c, 0xd7, 0x94, 0xaa, 0xa6, 0x34, 0xae, 0xb2, 0x12, 0xda, - 0x87, 0xd7, 0x61, 0xe0, 0xbb, 0xe6, 0x9d, 0xd6, 0xbc, 0x35, 0xce, 0xaf, 0x2a, 0xb5, 0x4b, 0x25, - 0x1b, 0x3f, 0xb9, 0x81, 0x74, 0xf8, 0x1e, 0xa3, 0x03, 0xd8, 0x65, 0x74, 0xe5, 0x5e, 0xa9, 0xe9, - 0x93, 0x24, 0xb5, 0xa6, 0xea, 0x6a, 0x45, 0x57, 0x2e, 0xa6, 0x67, 0x11, 0x04, 0xd6, 0x74, 0x55, - 0xad, 0x55, 0x6e, 0xd4, 0x8f, 0xca, 0x45, 0x56, 0x3a, 0xb9, 0xe7, 0xfd, 0x86, 0xee, 0x38, 0xca, - 0x41, 0x76, 0x4a, 0xaf, 0x9c, 0xeb, 0xea, 0xbd, 0x32, 0xed, 0x56, 0x44, 0xcf, 0xef, 0x6e, 0xeb, - 0x37, 0x0a, 0xab, 0x2e, 0xa1, 0x4d, 0x58, 0x9f, 0x02, 0xca, 0xf7, 0x75, 0x55, 0xe3, 0x3a, 0xfc, - 0x04, 0x1b, 0x0b, 0x6c, 0x85, 0x8e, 0x60, 0xbf, 0xda, 0xac, 0x5d, 0x18, 0xb7, 0xea, 0xa5, 0x56, - 0xd1, 0xd5, 0xbb, 0x9a, 0xd1, 0xd0, 0x2b, 0x7a, 0x33, 0x2c, 0xfd, 0x1b, 0x38, 0x58, 0x4c, 0x09, - 0x7f, 0xf5, 0x10, 0xf6, 0x16, 0x93, 0xc6, 0x83, 0x78, 0x7f, 0xfd, 0xe7, 0x73, 0x41, 0xfa, 0xfc, - 0x5c, 0x90, 0xfe, 0x7e, 0x2e, 0x48, 0xbf, 0xbe, 0x14, 0x62, 0x9f, 0x5f, 0x0a, 0xb1, 0xbf, 0x5e, - 0x0a, 0xb1, 0x8f, 0x65, 0x9b, 0xf8, 0x9d, 0x61, 0xab, 0xd4, 0x76, 0xfb, 0xb2, 0x37, 0xa4, 0x1d, - 0x7e, 0x3d, 0xf8, 0xea, 0x1d, 0x5f, 0xbe, 0x73, 0x5c, 0x0b, 0xcb, 0x8f, 0xb2, 0x78, 0x75, 0xd9, - 0xbf, 0x11, 0xad, 0x24, 0xff, 0xe3, 0xfe, 0xcd, 0xff, 0x01, 0x00, 0x00, 0xff, 0xff, 0x6c, 0xb8, - 0x11, 0x04, 0x5e, 0x08, 0x00, 0x00, + // 1051 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x56, 0x4d, 0x4f, 0xeb, 0x46, + 0x14, 0x8d, 0x13, 0x08, 0xe4, 0x12, 0xd2, 0x30, 0x04, 0xf0, 0xe3, 0x23, 0x40, 0xde, 0x93, 0x8a, + 0x50, 0x5f, 0xac, 0xb4, 0x2c, 0x2a, 0x76, 0x79, 0xe0, 0x80, 0x05, 0x84, 0xd4, 0x31, 0xa8, 0xef, + 0x6d, 0x5c, 0x27, 0x1e, 0x92, 0x51, 0x12, 0xdb, 0xf2, 0x38, 0x88, 0x74, 0x53, 0xa9, 0xcb, 0xae, + 0xba, 0xec, 0x92, 0x9f, 0xd0, 0x9f, 0xd1, 0xe5, 0x5b, 0x76, 0x59, 0x81, 0xaa, 0x76, 0xd7, 0xbf, + 0x50, 0xcd, 0x8c, 0x93, 0x38, 0x1f, 0x9b, 0x64, 0xe6, 0xdc, 0x73, 0xaf, 0xef, 0x9c, 0x7b, 0x3c, + 0x32, 0xac, 0xf7, 0x03, 0x4a, 0x95, 0xc7, 0x92, 0x12, 0x0c, 0x3c, 0x4c, 0x8b, 0x9e, 0xef, 0x06, + 0x2e, 0x5a, 0x62, 0x60, 0xf1, 0xb1, 0xb4, 0x9d, 0x6f, 0xba, 0xb4, 0xe7, 0x52, 0xa5, 0x61, 0x51, + 0xac, 0x3c, 0x96, 0x1a, 0x38, 0xb0, 0x4a, 0x4a, 0xd3, 0x25, 0x8e, 0x20, 0x6e, 0x6f, 0x85, 0xf1, + 0x1e, 0x6d, 0xb1, 0x1a, 0x3d, 0xda, 0x0a, 0x03, 0xb9, 0x96, 0xdb, 0x72, 0xf9, 0x52, 0x61, 0xab, + 0x10, 0x5d, 0xb3, 0x7a, 0xc4, 0x71, 0x15, 0xfe, 0x2b, 0xa0, 0xc2, 0xb7, 0x90, 0xac, 0x59, 0xbe, + 0xd5, 0xa3, 0x28, 0x07, 0x8b, 0x96, 0xdd, 0x23, 0x8e, 0x2c, 0x1d, 0x48, 0x47, 0x29, 0x5d, 0x6c, + 0x4e, 0xe5, 0xdf, 0x9e, 0xf7, 0x63, 0xff, 0x3e, 0xef, 0x4b, 0xbf, 0xfc, 0xf3, 0xfb, 0xf1, 0x0a, + 0x6f, 0xd6, 0xe3, 0xfc, 0xc2, 0x73, 0x1c, 0x56, 0x0d, 0x4a, 0xaf, 0xf0, 0xa0, 0xe6, 0xbb, 0x4d, + 0x4c, 0x29, 0x3a, 0x81, 0x24, 0x0d, 0xac, 0xa0, 0x4f, 0x79, 0x89, 0xcc, 0xd7, 0xbb, 0xc5, 0xf0, + 0x1c, 0xc5, 0x09, 0x5e, 0x9d, 0x73, 0xf4, 0x90, 0x8b, 0x0a, 0x90, 0xf6, 0x2c, 0x3f, 0x20, 0x4d, + 0xe2, 0x59, 0x4e, 0x40, 0xe5, 0xf8, 0x41, 0xe2, 0x28, 0xa5, 0x4f, 0x60, 0xe8, 0x10, 0xd2, 0x8d, + 0xae, 0xdb, 0xec, 0x98, 0x6d, 0x4c, 0x5a, 0xed, 0x40, 0x4e, 0x1c, 0x48, 0x47, 0x09, 0x7d, 0x85, + 0x63, 0x97, 0x1c, 0x42, 0x6f, 0x61, 0x15, 0x3f, 0x79, 0xc4, 0x1f, 0x0c, 0x39, 0x0b, 0x9c, 0x93, + 0x16, 0x60, 0x48, 0x3a, 0x85, 0xb4, 0x27, 0x9a, 0x30, 0x99, 0xde, 0xf2, 0x22, 0xef, 0x73, 0x2b, + 0xda, 0x67, 0xd8, 0xa4, 0x31, 0xf0, 0xb0, 0xbe, 0xe2, 0x8d, 0x37, 0x28, 0x03, 0x71, 0x62, 0xcb, + 0xc9, 0x03, 0xe9, 0x68, 0x41, 0x8f, 0x13, 0xfb, 0xf4, 0x30, 0xaa, 0x4c, 0x8e, 0x2b, 0x13, 0x50, + 0x6a, 0x76, 0xf0, 0xc0, 0x0c, 0xd3, 0x0a, 0x3f, 0xc7, 0x21, 0x29, 0x8e, 0x8e, 0xf6, 0x00, 0x58, + 0xd4, 0xeb, 0x37, 0x3a, 0x78, 0x10, 0x4a, 0x9c, 0x0a, 0x28, 0xad, 0x71, 0x00, 0x6d, 0x40, 0x92, + 0x25, 0x12, 0x5b, 0x8e, 0x0b, 0xf5, 0x3b, 0x78, 0xa0, 0xd9, 0x33, 0xda, 0x24, 0xe6, 0x68, 0x73, + 0x02, 0x9b, 0x0f, 0xc4, 0xb1, 0xba, 0xe4, 0x47, 0x6c, 0x9b, 0x13, 0x2a, 0x09, 0x05, 0x72, 0xa3, + 0xe8, 0x87, 0x88, 0x5c, 0x45, 0x58, 0xef, 0xe0, 0x41, 0x0b, 0x3b, 0x93, 0x29, 0x8b, 0x3c, 0x65, + 0x4d, 0x84, 0xa2, 0xfc, 0x3d, 0x80, 0xa1, 0x72, 0x23, 0x15, 0x52, 0x21, 0xa2, 0xd9, 0xa7, 0x6f, + 0xa2, 0x62, 0xa4, 0xa3, 0x62, 0x14, 0xfe, 0x8e, 0xc3, 0xb2, 0x41, 0xa9, 0xfa, 0x88, 0x9d, 0x20, + 0x14, 0x51, 0x1a, 0x8a, 0x88, 0x4e, 0x00, 0x30, 0x0b, 0x88, 0x71, 0xc4, 0xf9, 0x38, 0x36, 0xa2, + 0xe3, 0xe0, 0x69, 0x7c, 0x18, 0x29, 0x3c, 0x5c, 0x22, 0x65, 0x64, 0xb4, 0xc4, 0xec, 0x00, 0x79, + 0xc6, 0x94, 0xc7, 0x26, 0xbb, 0x5f, 0x98, 0xea, 0x9e, 0xd9, 0x6b, 0xc6, 0x16, 0xa9, 0xc9, 0xe9, + 0x4f, 0x4f, 0x22, 0x39, 0x67, 0x12, 0x33, 0x16, 0x5c, 0x9a, 0x63, 0xc1, 0x69, 0x2b, 0x2f, 0xcf, + 0x5a, 0x79, 0x6c, 0x86, 0x54, 0xd4, 0x0c, 0x93, 0x16, 0x82, 0x29, 0x0b, 0x15, 0xfe, 0x4b, 0xc0, + 0x6a, 0xa5, 0xef, 0xd8, 0x37, 0xa4, 0xe5, 0x5b, 0x01, 0x71, 0x9d, 0x19, 0xb1, 0x77, 0x01, 0xdc, + 0xae, 0x6d, 0x4e, 0x18, 0x6d, 0xd9, 0xed, 0xda, 0x57, 0xbc, 0xfc, 0x3b, 0xc8, 0xb0, 0x68, 0xe4, + 0x11, 0x09, 0xce, 0x48, 0xbb, 0x5d, 0xdb, 0x18, 0x19, 0xf5, 0x1d, 0x64, 0x9a, 0x7d, 0xdf, 0x67, + 0x23, 0x0b, 0xeb, 0x2c, 0x08, 0x56, 0x88, 0x8a, 0x5a, 0x5f, 0x01, 0x1a, 0xb2, 0x22, 0xf5, 0x84, + 0xac, 0xd9, 0x30, 0x32, 0xae, 0x99, 0x83, 0xc5, 0x66, 0xdb, 0x22, 0x0e, 0xb7, 0x55, 0x4a, 0x17, + 0x9b, 0xc8, 0x6d, 0xb2, 0x34, 0x75, 0x9b, 0x4c, 0x9c, 0x72, 0x6a, 0xd2, 0x5f, 0xc2, 0x17, 0xc4, + 0x21, 0x01, 0xb1, 0x82, 0xe1, 0xdb, 0x10, 0x2a, 0x9c, 0x19, 0xc1, 0xdc, 0xd6, 0x8c, 0xd8, 0x74, + 0x7b, 0x5e, 0x17, 0x8f, 0x89, 0x29, 0x41, 0x1c, 0xc1, 0x82, 0xb8, 0x05, 0x4b, 0xc1, 0x93, 0xd9, + 0xb6, 0x68, 0x3b, 0xd4, 0x3c, 0x19, 0x3c, 0x5d, 0x5a, 0xb4, 0x8d, 0x76, 0x20, 0xd5, 0xb2, 0xa8, + 0xe9, 0xf9, 0xa4, 0x89, 0xe5, 0x15, 0xa1, 0x66, 0xcb, 0xa2, 0x35, 0xb6, 0x1f, 0x06, 0xbb, 0xa4, + 0x47, 0x02, 0x39, 0xcd, 0x47, 0xc0, 0x82, 0xd7, 0x6c, 0xcf, 0x06, 0xd1, 0x2d, 0x99, 0x2c, 0xfe, + 0x80, 0xb1, 0xbc, 0x2a, 0x52, 0xbb, 0xa5, 0x0b, 0x8b, 0x56, 0x30, 0x66, 0x9d, 0x05, 0xbe, 0xe5, + 0xd0, 0x07, 0xec, 0x9b, 0x56, 0xcf, 0xed, 0x3b, 0x81, 0x9c, 0xe1, 0x94, 0xcc, 0x10, 0x2e, 0x73, + 0xf4, 0xb8, 0x03, 0xeb, 0x73, 0x2e, 0x56, 0xb4, 0x03, 0x5b, 0x46, 0xbd, 0x6e, 0x5e, 0xa9, 0x1f, + 0xcd, 0x9a, 0x7e, 0x7b, 0xa6, 0xd6, 0xeb, 0x66, 0x4d, 0xad, 0x9e, 0x6b, 0xd5, 0x8b, 0x6c, 0x6c, + 0x5e, 0xb0, 0x7e, 0x77, 0xc6, 0xfe, 0xb3, 0x12, 0xda, 0x86, 0xcd, 0xe9, 0x60, 0xa5, 0xac, 0x5d, + 0xab, 0xe7, 0xd9, 0xf8, 0xf1, 0x0f, 0x90, 0x99, 0xbc, 0x1d, 0xd1, 0x26, 0x20, 0xc6, 0x1e, 0x32, + 0xaf, 0xd4, 0x8f, 0x17, 0x6a, 0x35, 0x1b, 0x43, 0x5b, 0xb0, 0x1e, 0xc5, 0x75, 0xb5, 0xa2, 0xab, + 0xf5, 0xcb, 0xac, 0x84, 0xf6, 0xe0, 0x4d, 0x34, 0xf0, 0xdd, 0xdd, 0xad, 0x7e, 0x77, 0x63, 0x9e, + 0x5d, 0x96, 0xab, 0x17, 0x6a, 0x36, 0x7e, 0x7c, 0x0d, 0xe9, 0xe8, 0x0b, 0x8f, 0xf6, 0x61, 0x87, + 0xd1, 0xd5, 0x7b, 0xb5, 0x6a, 0x8c, 0x92, 0xb4, 0xaa, 0x66, 0x68, 0x65, 0x43, 0x3d, 0x1f, 0x9f, + 0x45, 0x10, 0x58, 0xd3, 0x15, 0xad, 0x5a, 0xbe, 0xd6, 0x3e, 0xa9, 0xe7, 0x59, 0xe9, 0xf8, 0x9e, + 0xf7, 0x1b, 0xb9, 0x0c, 0x50, 0x0e, 0xb2, 0x63, 0x7a, 0xf9, 0xcc, 0xd0, 0xee, 0xd5, 0x71, 0xb7, + 0x02, 0x3d, 0xbb, 0xbd, 0xa9, 0x5d, 0xab, 0xac, 0xba, 0x84, 0x36, 0x60, 0x6d, 0x1c, 0x50, 0xbf, + 0xaf, 0x69, 0x3a, 0xd7, 0xe1, 0x27, 0x58, 0x9f, 0xe3, 0x3f, 0x74, 0x08, 0x7b, 0x95, 0xbb, 0xea, + 0xb9, 0x79, 0xa3, 0x5d, 0xe8, 0x65, 0x43, 0xbb, 0xad, 0x9a, 0x75, 0xa3, 0x6c, 0xdc, 0x45, 0xa5, + 0x7f, 0x0b, 0xfb, 0xf3, 0x29, 0xd1, 0xa7, 0x1e, 0xc0, 0xee, 0x7c, 0xd2, 0x70, 0x10, 0x1f, 0xae, + 0xfe, 0x78, 0xc9, 0x4b, 0x9f, 0x5f, 0xf2, 0xd2, 0x5f, 0x2f, 0x79, 0xe9, 0xd7, 0xd7, 0x7c, 0xec, + 0xf3, 0x6b, 0x3e, 0xf6, 0xe7, 0x6b, 0x3e, 0xf6, 0xa9, 0xd4, 0x22, 0x41, 0xbb, 0xdf, 0x28, 0x36, + 0xdd, 0x9e, 0xe2, 0xf5, 0x69, 0x9b, 0xbf, 0x47, 0x7c, 0xf5, 0x9e, 0x2f, 0xdf, 0x3b, 0xae, 0x8d, + 0x95, 0x27, 0x45, 0x5c, 0xcf, 0xec, 0x7b, 0xa3, 0x91, 0xe4, 0x5f, 0x01, 0xdf, 0xfc, 0x1f, 0x00, + 0x00, 0xff, 0xff, 0x6d, 0xb7, 0x9c, 0x2f, 0x87, 0x08, 0x00, 0x00, } func (this *Params) Equal(that interface{}) bool { @@ -1082,6 +1091,13 @@ func (m *FundMigration) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.TransferAmount) > 0 { + i -= len(m.TransferAmount) + copy(dAtA[i:], m.TransferAmount) + i = encodeVarintTypes(dAtA, i, uint64(len(m.TransferAmount))) + i-- + dAtA[i] = 0x72 + } if len(m.L1GasFee) > 0 { i -= len(m.L1GasFee) copy(dAtA[i:], m.L1GasFee) @@ -1350,6 +1366,10 @@ func (m *FundMigration) Size() (n int) { if l > 0 { n += 1 + l + sovTypes(uint64(l)) } + l = len(m.TransferAmount) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } return n } @@ -2493,6 +2513,38 @@ func (m *FundMigration) Unmarshal(dAtA []byte) error { } m.L1GasFee = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TransferAmount", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.TransferAmount = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipTypes(dAtA[iNdEx:]) From 51c2dd7cf366d38ac1b9f3133c346b4249d8f965 Mon Sep 17 00:00:00 2001 From: Aman Gupta Date: Wed, 26 Aug 2026 15:56:29 +0530 Subject: [PATCH 27/60] fix: F-2026-18817 | [Dual Defense] SVM Log Truncation + Resolver No Success-Vote Starves Oldest-1000 Pending Queue (#318) * fix: paginate pending outbound polling so a stuck prefix cannot hide newer rows (F-2026-18817) * fix: paginate chain config query so it cannot silently cap at the SDK default (F-2026-18817) * fix: carry pending outbound cursor between polls so the page budget costs latency not coverage (F-2026-18817) * fix: request the full pending outbound set; the server pages by offset and returns no next key (F-2026-18817) * fix: raise grpc receive limit so a large pending set does not fail the whole poll (F-2026-18817) * fix: alternate pending outbound sweep direction instead of raising transport limits (F-2026-18817) * fix: read newest-first every poll, sweep the backlog only when it exceeds one request (F-2026-18817) * simplify: read pending outbounds newest-first, drop the backlog sweep machinery (F-2026-18817) * fix: walk pending outbounds oldest-first instead of reading one page --- universalClient/pushcore/pushCore.go | 123 +++++++++++++++++----- universalClient/pushcore/pushCore_test.go | 116 ++++++++++++++++++++ 2 files changed, 212 insertions(+), 27 deletions(-) diff --git a/universalClient/pushcore/pushCore.go b/universalClient/pushcore/pushCore.go index ccf5aca04..a6247078f 100644 --- a/universalClient/pushcore/pushCore.go +++ b/universalClient/pushcore/pushCore.go @@ -139,19 +139,45 @@ func retryWithRoundRobin[T any]( // GetAllChainConfigs retrieves all chain configurations from Push Chain. func (c *Client) GetAllChainConfigs(ctx context.Context) ([]*uregistrytypes.ChainConfig, error) { - return retryWithRoundRobin( - len(c.eps), - &c.rr, - func(idx int) ([]*uregistrytypes.ChainConfig, error) { - resp, err := c.eps[idx].AllChainConfigs(ctx, &uregistrytypes.QueryAllChainConfigsRequest{}) - if err != nil { - return nil, err - } - return resp.Configs, nil - }, - "GetAllChainConfigs", - c.logger, + // Paged rather than a single request: the server paginates this collection, + // and an omitted PageRequest silently caps the response at the SDK default of + // 100. A chain missing from this list is simply never watched, so truncation + // must not be possible. + var ( + configs []*uregistrytypes.ChainConfig + nextKey []byte ) + for page := 0; page < chainConfigMaxPages; page++ { + key := nextKey + resp, err := retryWithRoundRobin( + len(c.eps), + &c.rr, + func(idx int) (*uregistrytypes.QueryAllChainConfigsResponse, error) { + return c.eps[idx].AllChainConfigs(ctx, &uregistrytypes.QueryAllChainConfigsRequest{ + Pagination: &query.PageRequest{Key: key, Limit: chainConfigPageSize}, + }) + }, + "GetAllChainConfigs", + c.logger, + ) + if err != nil { + return nil, err + } + + configs = append(configs, resp.Configs...) + + if resp.Pagination == nil || len(resp.Pagination.NextKey) == 0 { + return configs, nil + } + nextKey = resp.Pagination.NextKey + } + + // Unreachable with any plausible number of chains; loud rather than silent. + c.logger.Error(). + Int("max_pages", chainConfigMaxPages). + Int("fetched", len(configs)). + Msg("chain config page cap reached; some chains will not be watched") + return configs, nil } // GetLatestBlock retrieves the latest block from Push Chain. @@ -368,24 +394,67 @@ func (c *Client) GetPendingFundMigrations(ctx context.Context) ([]*utsstypes.Fun ) } -// GetAllPendingOutbounds retrieves up to the first 1000 pending outbound transactions from Push Chain. -// Sorted by created_at (block height) ascending — oldest first. +// Page size and page cap for the pending-outbound walk. The cap bounds a single +// poll; anything beyond it is picked up on the next tick. +const ( + // A row costs roughly a kilobyte on the wire, so a page stays well inside + // gRPC's 4 MiB default. Asking for the whole set in one request would fail + // the call outright once the set grew, taking the poll down entirely. + pendingOutboundPageSize = 1000 + pendingOutboundMaxPages = 5 + + chainConfigPageSize = 200 + chainConfigMaxPages = 20 +) + +// GetAllPendingOutbounds retrieves pending outbound transactions from Push Chain, +// oldest first, so older work is signed before newer. +// +// Walked by offset rather than read as a single page. An outbound leaves the +// pending set only when a quorum vote terminalizes it, so rows that cannot reach +// one accumulate at the head of the list; without the walk they would hide every +// newer outbound behind them, on every chain, since this query is not chain +// scoped. +// +// The walk stops at the first short page, so the ordinary case where the whole +// set fits in one page costs exactly one request. func (c *Client) GetAllPendingOutbounds(ctx context.Context) ([]*uexecutortypes.PendingOutboundEntry, []*uexecutortypes.OutboundTx, error) { - resp, err := retryWithRoundRobin( - len(c.uexecutorClients), - &c.rr, - func(idx int) (*uexecutortypes.QueryAllPendingOutboundsResponse, error) { - return c.uexecutorClients[idx].AllPendingOutbounds(ctx, &uexecutortypes.QueryAllPendingOutboundsRequest{ - Pagination: &query.PageRequest{Limit: 1000}, - }) - }, - "GetAllPendingOutbounds", - c.logger, + var ( + entries []*uexecutortypes.PendingOutboundEntry + outbounds []*uexecutortypes.OutboundTx ) - if err != nil { - return nil, nil, err + + for page := 0; page < pendingOutboundMaxPages; page++ { + offset := uint64(page) * pendingOutboundPageSize + resp, err := retryWithRoundRobin( + len(c.uexecutorClients), + &c.rr, + func(idx int) (*uexecutortypes.QueryAllPendingOutboundsResponse, error) { + return c.uexecutorClients[idx].AllPendingOutbounds(ctx, &uexecutortypes.QueryAllPendingOutboundsRequest{ + Pagination: &query.PageRequest{Offset: offset, Limit: pendingOutboundPageSize}, + }) + }, + "GetAllPendingOutbounds", + c.logger, + ) + if err != nil { + return nil, nil, err + } + + entries = append(entries, resp.Entries...) + outbounds = append(outbounds, resp.Outbounds...) + + if len(resp.Entries) < pendingOutboundPageSize { + return entries, outbounds, nil + } } - return resp.Entries, resp.Outbounds, nil + + c.logger.Warn(). + Int("max_pages", pendingOutboundMaxPages). + Int("fetched", len(entries)). + Msg("pending outbound page cap reached; the remainder is read on the next poll") + + return entries, outbounds, nil } // createGRPCConnection creates a gRPC connection with appropriate transport security. diff --git a/universalClient/pushcore/pushCore_test.go b/universalClient/pushcore/pushCore_test.go index e7b88e045..e5930d08c 100644 --- a/universalClient/pushcore/pushCore_test.go +++ b/universalClient/pushcore/pushCore_test.go @@ -1,13 +1,16 @@ package pushcore import ( + "bytes" "context" "errors" + "fmt" "math/big" "testing" cmtservice "github.com/cosmos/cosmos-sdk/client/grpc/cmtservice" sdktypes "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/query" "github.com/cosmos/cosmos-sdk/types/tx" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/cosmos/cosmos-sdk/x/authz" @@ -917,9 +920,24 @@ type mockRegistryQueryClient struct { uregistrytypes.QueryClient allChainConfigsResp *uregistrytypes.QueryAllChainConfigsResponse err error + + chainConfigPages []*uregistrytypes.QueryAllChainConfigsResponse + chainConfigKeys [][]byte } func (m *mockRegistryQueryClient) AllChainConfigs(ctx context.Context, req *uregistrytypes.QueryAllChainConfigsRequest, opts ...grpc.CallOption) (*uregistrytypes.QueryAllChainConfigsResponse, error) { + if m.chainConfigPages != nil { + var key []byte + if req.Pagination != nil { + key = req.Pagination.Key + } + m.chainConfigKeys = append(m.chainConfigKeys, key) + idx := len(m.chainConfigKeys) - 1 + if idx >= len(m.chainConfigPages) { + return nil, assert.AnError + } + return m.chainConfigPages[idx], nil + } if m.err != nil { return nil, m.err } @@ -1028,6 +1046,14 @@ type mockUExecutorQueryClient struct { gasPriceResp *uexecutortypes.QueryGasPriceResponse allPendingOutboundsResp *uexecutortypes.QueryAllPendingOutboundsResponse err error + + // lastPendingReq records the request so tests can assert the limit sent. + lastPendingReq *uexecutortypes.QueryAllPendingOutboundsRequest + + // pendingReqs records every page request of a walk. + pendingReqs []*uexecutortypes.QueryAllPendingOutboundsRequest + // pendingTotal, when set, makes the mock serve that many rows by offset. + pendingTotal int } func (m *mockUExecutorQueryClient) GasPrice(ctx context.Context, req *uexecutortypes.QueryGasPriceRequest, opts ...grpc.CallOption) (*uexecutortypes.QueryGasPriceResponse, error) { @@ -1054,9 +1080,27 @@ func (m *mockUExecutorQueryClient) AllUniversalTx(ctx context.Context, req *uexe } func (m *mockUExecutorQueryClient) AllPendingOutbounds(ctx context.Context, req *uexecutortypes.QueryAllPendingOutboundsRequest, opts ...grpc.CallOption) (*uexecutortypes.QueryAllPendingOutboundsResponse, error) { + m.lastPendingReq = req + m.pendingReqs = append(m.pendingReqs, req) if m.err != nil { return nil, m.err } + if m.pendingTotal > 0 { + offset := int(req.Pagination.GetOffset()) + end := offset + int(req.Pagination.GetLimit()) + if end > m.pendingTotal { + end = m.pendingTotal + } + resp := &uexecutortypes.QueryAllPendingOutboundsResponse{ + Pagination: &query.PageResponse{Total: uint64(m.pendingTotal)}, + } + for i := offset; i < end; i++ { + id := fmt.Sprintf("ob-%d", i) + resp.Entries = append(resp.Entries, &uexecutortypes.PendingOutboundEntry{OutboundId: id}) + resp.Outbounds = append(resp.Outbounds, &uexecutortypes.OutboundTx{Id: id}) + } + return resp, nil + } return m.allPendingOutboundsResp, nil } @@ -1149,3 +1193,75 @@ func TestClient_GetKeyByID(t *testing.T) { assert.Nil(t, key) }) } + +// An outbound leaves the pending set only on a quorum vote, so rows that cannot +// reach one accumulate at the head of an oldest-first list. The walk is what +// stops them hiding everything newer. +func TestClient_GetAllPendingOutbounds_WalksOldestFirst(t *testing.T) { + ctx := context.Background() + + newClient := func(total int) (*Client, *mockUExecutorQueryClient) { + m := &mockUExecutorQueryClient{pendingTotal: total} + return &Client{logger: zerolog.Nop(), uexecutorClients: []uexecutortypes.QueryClient{m}}, m + } + + t.Run("oldest first, never reversed", func(t *testing.T) { + client, m := newClient(9) + _, _, err := client.GetAllPendingOutbounds(ctx) + require.NoError(t, err) + + p := m.pendingReqs[0].Pagination + require.NotNil(t, p) + assert.False(t, p.Reverse, "older outbounds must be read first") + assert.Zero(t, p.Offset) + assert.Equal(t, uint64(pendingOutboundPageSize), p.Limit) + }) + + // The ordinary case is a set that fits, and it must not cost extra requests. + t.Run("a set that fits costs one request", func(t *testing.T) { + client, m := newClient(9) + entries, _, err := client.GetAllPendingOutbounds(ctx) + require.NoError(t, err) + assert.Len(t, m.pendingReqs, 1) + assert.Len(t, entries, 9) + }) + + // A stuck prefix must not hide what is behind it. + t.Run("walks past a full first page", func(t *testing.T) { + client, m := newClient(pendingOutboundPageSize + 250) + entries, outbounds, err := client.GetAllPendingOutbounds(ctx) + require.NoError(t, err) + + require.Len(t, m.pendingReqs, 2) + assert.Equal(t, uint64(0), m.pendingReqs[0].Pagination.GetOffset()) + assert.Equal(t, uint64(pendingOutboundPageSize), m.pendingReqs[1].Pagination.GetOffset()) + + require.Len(t, entries, pendingOutboundPageSize+250) + require.Len(t, outbounds, pendingOutboundPageSize+250) + assert.Equal(t, "ob-0", entries[0].OutboundId, "oldest first") + assert.Equal(t, fmt.Sprintf("ob-%d", pendingOutboundPageSize+249), entries[len(entries)-1].OutboundId) + }) + + // The cap bounds one poll; the rest is read on the next tick. + t.Run("stops at the page cap and says so", func(t *testing.T) { + var logBuf bytes.Buffer + m := &mockUExecutorQueryClient{pendingTotal: pendingOutboundPageSize * (pendingOutboundMaxPages + 2)} + client := &Client{logger: zerolog.New(&logBuf), uexecutorClients: []uexecutortypes.QueryClient{m}} + + entries, _, err := client.GetAllPendingOutbounds(ctx) + require.NoError(t, err) + assert.Len(t, m.pendingReqs, pendingOutboundMaxPages) + assert.Len(t, entries, pendingOutboundPageSize*pendingOutboundMaxPages) + assert.Contains(t, logBuf.String(), "page cap reached") + }) + + t.Run("quiet when the set fits", func(t *testing.T) { + var logBuf bytes.Buffer + m := &mockUExecutorQueryClient{pendingTotal: 9} + client := &Client{logger: zerolog.New(&logBuf), uexecutorClients: []uexecutortypes.QueryClient{m}} + + _, _, err := client.GetAllPendingOutbounds(ctx) + require.NoError(t, err) + assert.NotContains(t, logBuf.String(), "page cap reached") + }) +} From b3a205891ca27005e59b7776fa7fbc278344d378 Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Wed, 26 Aug 2026 19:41:18 +0530 Subject: [PATCH 28/60] fix: skip quadratic base58 decode outside the 64-byte signature length band (#329) Only 64..88 base58 chars can decode to 64 bytes, so gating the decode on that band is output-equivalent. Also cap tx_hash on the unauthenticated InboundKeys query. --- utils/canonical.go | 21 +++++- utils/canonical_test.go | 92 ++++++++++++++++++++++++--- x/uexecutor/keeper/query_keys.go | 16 +++++ x/uexecutor/keeper/query_keys_test.go | 87 +++++++++++++++++++++++++ 4 files changed, 205 insertions(+), 11 deletions(-) create mode 100644 x/uexecutor/keeper/query_keys_test.go diff --git a/utils/canonical.go b/utils/canonical.go index 64c5bc62f..876a3c277 100644 --- a/utils/canonical.go +++ b/utils/canonical.go @@ -20,6 +20,18 @@ const ( const base58Alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" +// A base58-encoded 64-byte Solana signature is always 64..88 characters: 88 is +// ceil(512 / log2(58)) for a full-range value, and 64 is the all-zero case +// (each leading zero byte encodes as one '1'). Outside that band the decode can +// never produce 64 bytes, so its result would be discarded — see +// canonicalizeSolanaTxHash. mr-tron/base58's decoder is quadratic (for each of +// n characters it walks ceil(n/4) limbs), so decoding attacker-supplied strings +// only to throw the result away is an unmetered CPU sink on public query paths. +const ( + solanaSigBase58MinLen = 64 + solanaSigBase58MaxLen = 88 +) + // CAIP2Namespace returns the namespace component of a CAIP-2 chain id // ("eip155:1" → "eip155"). Returns "" when the id has no namespace. func CAIP2Namespace(chain string) string { @@ -119,8 +131,13 @@ func canonicalizeSolanaTxHash(s string) (string, error) { if strings.HasPrefix(canon, "0x") { return canon, nil } - if raw, decErr := base58.Decode(canon); decErr == nil && len(raw) == 64 { - return "0x" + hex.EncodeToString(raw), nil + // Only attempt the decode for lengths that can actually yield 64 bytes. + // This is output-equivalent for every possible input: a string outside the + // band already falls through to `return canon` below, decode or not. + if n := len(canon); n >= solanaSigBase58MinLen && n <= solanaSigBase58MaxLen { + if raw, decErr := base58.Decode(canon); decErr == nil && len(raw) == 64 { + return "0x" + hex.EncodeToString(raw), nil + } } return canon, nil } diff --git a/utils/canonical_test.go b/utils/canonical_test.go index 15ba309eb..c7c1af0de 100644 --- a/utils/canonical_test.go +++ b/utils/canonical_test.go @@ -1,23 +1,28 @@ package utils_test import ( + "encoding/hex" + "math/rand" + "strings" "testing" + "time" + "github.com/mr-tron/base58" "github.com/stretchr/testify/require" "github.com/pushchain/push-chain-node/utils" ) const ( - eip55Addr = "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed" - lowerAddr = "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed" - upperAddr = "0X5AAEB6053F3E94C9B9A09F33669435E7EF1BEAED" - noPfxAddr = "5aaeb6053f3e94c9b9a09f33669435e7ef1beaed" - mixedHash = "0xB28F49668e7e76dc96D7aaBE5b7f63FEcfbd1c3574774c05e8204e749fd96fbd" - lowerHash = "0xb28f49668e7e76dc96d7aabe5b7f63fecfbd1c3574774c05e8204e749fd96fbd" - noPfxHash = "b28f49668e7e76dc96d7aabe5b7f63fecfbd1c3574774c05e8204e749fd96fbd" - solPubkey = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" - solSig = "5j7s6NiJS3JAkvgkoc18WVAsiSaci2pxB2A6ueCJP4tprA2TFg9wSyTLeYouxPBJEMzJinENTkpA52YStRW5Dia7" + eip55Addr = "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed" + lowerAddr = "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed" + upperAddr = "0X5AAEB6053F3E94C9B9A09F33669435E7EF1BEAED" + noPfxAddr = "5aaeb6053f3e94c9b9a09f33669435e7ef1beaed" + mixedHash = "0xB28F49668e7e76dc96D7aaBE5b7f63FEcfbd1c3574774c05e8204e749fd96fbd" + lowerHash = "0xb28f49668e7e76dc96d7aabe5b7f63fecfbd1c3574774c05e8204e749fd96fbd" + noPfxHash = "b28f49668e7e76dc96d7aabe5b7f63fecfbd1c3574774c05e8204e749fd96fbd" + solPubkey = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + solSig = "5j7s6NiJS3JAkvgkoc18WVAsiSaci2pxB2A6ueCJP4tprA2TFg9wSyTLeYouxPBJEMzJinENTkpA52YStRW5Dia7" ) func TestCanonicalizeEVMAddress_EquivalentEncodingsConverge(t *testing.T) { @@ -134,3 +139,72 @@ func TestCAIP2Namespace(t *testing.T) { require.Equal(t, "solana", utils.CAIP2Namespace("solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1")) require.Equal(t, "", utils.CAIP2Namespace("no-colon")) } + +// referenceSolanaTxHash reproduces the pre-fix behaviour for pure-base58 input: +// decode unconditionally, convert only on an exact 64-byte result, otherwise +// return the input untouched. The length band added in canonicalizeSolanaTxHash +// must not change the result for any input. +func referenceSolanaTxHash(s string) string { + if raw, err := base58.Decode(s); err == nil && len(raw) == 64 { + return "0x" + hex.EncodeToString(raw) + } + return s +} + +func TestCanonicalizeTxHashByNamespace_Solana_LengthBandIsOutputEquivalent(t *testing.T) { + // Only 64..88 base58 chars can decode to exactly 64 bytes, so the band gate + // is a pure performance change. Sweep across it — 63/64/88/89 are the edges. + rng := rand.New(rand.NewSource(1)) + alphabet := []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") + + lengths := []int{1, 2, 31, 32, 43, 44, 63, 64, 65, 87, 88, 89, 90, 128, 200, 300} + for n := 3; n < 63; n += 7 { + lengths = append(lengths, n) + } + + for _, n := range lengths { + for variant := 0; variant < 4; variant++ { + b := make([]byte, n) + for i := range b { + switch variant { + case 0: + b[i] = '1' // all-zero decode: the short edge of the band + case 1: + b[i] = 'z' // largest digit: the long edge + default: + b[i] = alphabet[rng.Intn(len(alphabet))] + } + } + in := string(b) + require.Equal(t, referenceSolanaTxHash(in), + utils.LenientCanonicalizeTxHash("solana:devnet", in), + "length band changed the result for a %d-char input %q", n, in) + } + } +} + +func TestCanonicalizeTxHashByNamespace_Solana_RealSignatureStillConverges(t *testing.T) { + // The band must not break the case it exists to serve: an 88-char base58 + // signature still folds to 0x-hex. + got, err := utils.CanonicalizeTxHashByNamespace("solana:devnet", solSig) + require.NoError(t, err) + require.Equal(t, "0x", got[:2]) + require.Len(t, got, 2+128) +} + +func TestCanonicalizeTxHashByNamespace_Solana_OversizedInputDoesNotDecode(t *testing.T) { + // F-2026-18821: mr-tron/base58 decoding is quadratic, and the result for an + // out-of-band length is discarded. Before the fix a single 1e5-char decode + // measured 4.5-29s (and InboundKeys does three of them); after, no decode + // runs at all. The bound is loose enough not to flake on a busy CI box while + // still failing hard on any return to O(n^2). + huge := strings.Repeat("z", 100_000) + + start := time.Now() + got := utils.LenientCanonicalizeTxHash("solana:devnet", huge) + elapsed := time.Since(start) + + require.Equal(t, huge, got, "out-of-band input must pass through unchanged") + require.Less(t, elapsed, time.Second, + "oversized base58 tx_hash must not be decoded (took %s)", elapsed) +} diff --git a/x/uexecutor/keeper/query_keys.go b/x/uexecutor/keeper/query_keys.go index 503c22678..0708baadd 100644 --- a/x/uexecutor/keeper/query_keys.go +++ b/x/uexecutor/keeper/query_keys.go @@ -12,6 +12,10 @@ import ( "github.com/pushchain/push-chain-node/x/uexecutor/types" ) +// maxQueryTxHashLen bounds the tx_hash accepted by the unauthenticated key +// derivation queries. Longest real value is an 88-char base58 Solana signature. +const maxQueryTxHashLen = 128 + // InboundKeys derives the canonical UTX id and inbound ballot id for the given // inbound, applying the same canonicalization the vote path uses. Lets off-chain // validators read the keys from the chain instead of re-implementing the rules. @@ -19,6 +23,18 @@ func (k Querier) InboundKeys(goCtx context.Context, req *types.QueryInboundKeysR if req == nil || req.Inbound == nil { return nil, status.Error(codes.InvalidArgument, "inbound is required") } + // This endpoint is unauthenticated, reads no state and so consumes no gas. + // Bound the one field that drives a decode (tx_hash) rather than trusting + // the caller. The limit is far above any real hash — 88 chars for a base58 + // Solana signature, 66 for 0x-prefixed EVM — so it rejects only garbage. + // Deliberately not applied to raw_payload / verification_data, which are + // legitimately long, nor pushed down into utils.Canonicalize*: the vote + // path must stay lenient (a malformed inbound still has to produce a UTX), + // and changing shared canonicalization would alter ballot keys. + if n := len(req.Inbound.TxHash); n > maxQueryTxHashLen { + return nil, status.Errorf(codes.InvalidArgument, + "tx_hash too long: %d chars (max %d)", n, maxQueryTxHashLen) + } inbound := *req.Inbound inbound.Canonicalize() diff --git a/x/uexecutor/keeper/query_keys_test.go b/x/uexecutor/keeper/query_keys_test.go new file mode 100644 index 000000000..51bbf64ce --- /dev/null +++ b/x/uexecutor/keeper/query_keys_test.go @@ -0,0 +1,87 @@ +package keeper_test + +import ( + "strings" + "testing" + "time" + + "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/pushchain/push-chain-node/x/uexecutor/types" +) + +// solanaSig is a real 88-char base58 Solana signature (64 bytes). +const solanaSig = "5j7s6NiJS3JAkvgkoc18WVAsiSaci2pxB2A6ueCJP4tprA2TFg9wSyTLeYouxPBJEMzJinENTkpA52YStRW5Dia7" + +func TestInboundKeys_RejectsOversizedTxHash(t *testing.T) { + // F-2026-18821: InboundKeys is unauthenticated, reads no state and so burns + // no gas. It canonicalizes tx_hash three times (Canonicalize, then the UTX + // and ballot key helpers), and base58 decoding is quadratic — a 1e5-char + // hash cost tens of seconds of CPU per request before the fix. + f := SetupTest(t) + + huge := strings.Repeat("z", 100_000) + + start := time.Now() + _, err := f.queryServer.InboundKeys(f.ctx, &types.QueryInboundKeysRequest{ + Inbound: &types.Inbound{ + SourceChain: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", + TxHash: huge, + LogIndex: "0", + TxType: types.TxType_FUNDS, + }, + }) + elapsed := time.Since(start) + + require.Error(t, err) + require.Equal(t, codes.InvalidArgument, status.Code(err)) + require.Contains(t, err.Error(), "tx_hash too long") + require.Less(t, elapsed, time.Second, "oversized tx_hash must fail fast (took %s)", elapsed) +} + +func TestInboundKeys_AcceptsRealSolanaSignature(t *testing.T) { + // The cap must not reject anything real: 88 chars is the longest a base58 + // 64-byte signature can be. + f := SetupTest(t) + + resp, err := f.queryServer.InboundKeys(f.ctx, &types.QueryInboundKeysRequest{ + Inbound: &types.Inbound{ + SourceChain: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", + TxHash: solanaSig, + LogIndex: "0", + TxType: types.TxType_FUNDS, + }, + }) + + require.NoError(t, err) + require.NotEmpty(t, resp.UtxId) + require.NotEmpty(t, resp.BallotId) + // Canonicalization folds the base58 signature into 0x-hex. + require.Equal(t, "0x", resp.CanonicalInbound.TxHash[:2]) + require.Len(t, resp.CanonicalInbound.TxHash, 2+128) +} + +func TestInboundKeys_TxHashAtCapIsAccepted(t *testing.T) { + // Boundary: exactly maxQueryTxHashLen (128) is allowed, 129 is not. + f := SetupTest(t) + + newReq := func(n int) *types.QueryInboundKeysRequest { + return &types.QueryInboundKeysRequest{ + Inbound: &types.Inbound{ + SourceChain: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", + TxHash: strings.Repeat("z", n), + LogIndex: "0", + TxType: types.TxType_FUNDS, + }, + } + } + + _, err := f.queryServer.InboundKeys(f.ctx, newReq(128)) + require.NoError(t, err, "128-char tx_hash is at the cap and must be accepted") + + _, err = f.queryServer.InboundKeys(f.ctx, newReq(129)) + require.Error(t, err) + require.Equal(t, codes.InvalidArgument, status.Code(err)) +} From 4f6a1253f55e44946a3a68580663daf3d627e31a Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Wed, 26 Aug 2026 19:41:23 +0530 Subject: [PATCH 29/60] fix: F-2026-18823 | [Dual Defense] buildRevertOutbound Fail-Open Leaves Unsignable INBOUND_REVERT and Blocks Rescue (#330) * fix(uexecutor): abort unsignable inbound reverts instead of queueing them buildRevertOutbound failed open: when the gas metadata lookup failed it returned a PENDING outbound with empty gas fields, which attachOutboundsToUtx indexed into PendingOutbounds unconditionally. UVs refuse to sign it, so the row sat there forever, and non-CEA rescue was gated on a REVERTED inbound-revert so the user had no way out either. - buildRevertOutbound returns (outbound, error) - on gas-metadata failure the revert is marked ABORTED with an AbortReason - attachOutboundsToUtx indexes only PENDING outbounds, and emits outbound_aborted for the rest - the non-CEA rescue gate accepts REVERTED or ABORTED * test(uexecutor): cover aborted inbound-revert and rescue recovery - keeper unit tests drive buildRevertOutbound with the gas lookup mocked both ways: resolvable stays PENDING with exact gas fields and is indexed, unresolvable aborts with a reason and is not - integration tests assert the revert is ABORTED, absent from PendingOutbounds, and that a non-CEA RESCUE_FUNDS is then accepted - fix the unit fixture's auth store key (authtypes.StoreKey != ModuleName) and wire the real account keeper so UniversalCore calls work --- .../uexecutor/execute_inbound_gas_test.go | 21 +- .../uexecutor/inbound_revert_abort_test.go | 158 ++++++++++++ .../uexecutor/rescue_funds_test.go | 4 +- .../uexecutor/revert_stuck_inbound_test.go | 20 +- .../uexecutor/vote_inbound_validation_test.go | 13 +- x/uexecutor/keeper/admin_revert.go | 16 +- x/uexecutor/keeper/build_revert_outbound.go | 73 +++++- .../keeper/build_revert_outbound_test.go | 241 ++++++++++++++++++ x/uexecutor/keeper/create_outbound.go | 61 +++-- x/uexecutor/keeper/execute_inbound_funds.go | 13 +- .../execute_inbound_funds_and_payload.go | 13 +- x/uexecutor/keeper/execute_inbound_gas.go | 13 +- .../keeper/execute_inbound_gas_and_payload.go | 13 +- x/uexecutor/keeper/export_test.go | 4 + .../handle_failed_inbound_validation.go | 13 +- x/uexecutor/keeper/keeper_test.go | 12 +- 16 files changed, 637 insertions(+), 51 deletions(-) create mode 100644 test/integration/uexecutor/inbound_revert_abort_test.go create mode 100644 x/uexecutor/keeper/build_revert_outbound_test.go diff --git a/test/integration/uexecutor/execute_inbound_gas_test.go b/test/integration/uexecutor/execute_inbound_gas_test.go index b01de0073..f946f1cd0 100644 --- a/test/integration/uexecutor/execute_inbound_gas_test.go +++ b/test/integration/uexecutor/execute_inbound_gas_test.go @@ -292,12 +292,16 @@ func TestInboundGas(t *testing.T) { "revert outbound amount must match inbound amount") require.Equal(t, inbound.AssetAddr, ob.ExternalAssetAddr, "revert outbound asset must match inbound asset") - require.Equal(t, uexecutortypes.Status_PENDING, ob.OutboundStatus, - "revert outbound should start in PENDING status") - - // Gas fields are populated from UniversalCore if chain meta is set. - // In test env without VoteChainMeta, they may be zero/empty — that's OK, - // the outbound is still created (graceful degradation). + // The UniversalCore stub deployed by the integration harness cannot + // serve getOutboundTxGasAndFees, so the revert's gas metadata is + // unresolvable here and the outbound is recorded ABORTED rather than + // queued for a signature it could never receive. The resolvable + // (PENDING) path is covered by + // x/uexecutor/keeper/build_revert_outbound_test.go. + require.Equal(t, uexecutortypes.Status_ABORTED, ob.OutboundStatus, + "a revert with unresolvable gas metadata must be ABORTED, not PENDING") + require.NotEmpty(t, ob.AbortReason, "ABORTED revert must carry a reason") + requireNotQueuedForSigning(t, chainApp, ctx, ob.Id) // When chain meta IS set, these will be populated. break } @@ -464,7 +468,10 @@ func TestInboundGas(t *testing.T) { if ob.TxType == uexecutortypes.TxType_INBOUND_REVERT { foundRevert = true require.Equal(t, inbound.SourceChain, ob.DestinationChain) - require.Equal(t, uexecutortypes.Status_PENDING, ob.OutboundStatus) + // Gas metadata is unresolvable against the harness's UniversalCore stub, + // so the revert is recorded ABORTED instead of entering the signing queue. + require.Equal(t, uexecutortypes.Status_ABORTED, ob.OutboundStatus) + requireNotQueuedForSigning(t, chainApp, ctx, ob.Id) break } } diff --git a/test/integration/uexecutor/inbound_revert_abort_test.go b/test/integration/uexecutor/inbound_revert_abort_test.go new file mode 100644 index 000000000..90a10e463 --- /dev/null +++ b/test/integration/uexecutor/inbound_revert_abort_test.go @@ -0,0 +1,158 @@ +package integrationtest + +import ( + "math/big" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + + "github.com/pushchain/push-chain-node/app" + utils "github.com/pushchain/push-chain-node/test/utils" + uexecutortypes "github.com/pushchain/push-chain-node/x/uexecutor/types" +) + +// Regression coverage for F-2026-18823. +// +// buildRevertOutbound used to fail open: when it could not resolve the revert's +// gas metadata it logged "proceeding without gas fields" and returned the +// outbound anyway, still marked PENDING. attachOutboundsToUtx then indexed it +// into PendingOutbounds unconditionally, where the universal validators refused +// to sign it ("gas price is zero or missing"). The row could never leave the +// queue: no ballot forms for an unsignable outbound and there is no admin abort +// for outbounds. Worse, non-CEA rescue was gated on an INBOUND_REVERT having +// reached REVERTED, so the user had no recovery route either. +// +// The revert is now recorded ABORTED with a reason, kept off the signing queue, +// and accepted by the rescue gate. +// +// NOTE ON THIS ENVIRONMENT: the UniversalCore contract deployed by the test +// harness cannot serve getOutboundTxGasAndFees (its PRC20 stub has no +// SOURCE_CHAIN_NAMESPACE), so every INBOUND_REVERT built here takes the abort +// path. That makes the failure realistic end-to-end but means the resolvable +// path cannot be exercised at this level; it is covered by +// x/uexecutor/keeper/build_revert_outbound_test.go, which drives the same +// function with the gas lookup mocked both ways. + +// requireNotQueuedForSigning asserts that an outbound was never indexed into +// PendingOutbounds, i.e. it will not be picked up for TSS signing. +func requireNotQueuedForSigning(t *testing.T, chainApp *app.ChainApp, ctx sdk.Context, outboundId string) { + t.Helper() + has, err := chainApp.UexecutorKeeper.PendingOutbounds.Has(ctx, outboundId) + require.NoError(t, err) + require.False(t, has, + "outbound %s must not be indexed in PendingOutbounds: it can never be signed and nothing would ever remove it", outboundId) +} + +// findInboundRevert returns the INBOUND_REVERT outbound on a UTX, if any. +func findInboundRevert(utx uexecutortypes.UniversalTx) *uexecutortypes.OutboundTx { + for _, ob := range utx.OutboundTx { + if ob != nil && ob.TxType == uexecutortypes.TxType_INBOUND_REVERT { + return ob + } + } + return nil +} + +// driveNonCEAInboundToAbortedRevert votes a non-CEA FUNDS inbound with an empty +// recipient to quorum. Execution validation rejects it, so an INBOUND_REVERT is +// built — and since the harness cannot serve gas metadata, that revert aborts. +// +// The token/chain config is deliberately left registered so the failure is the +// gas lookup alone; the PRC20-not-found variant is covered in +// vote_inbound_validation_test.go. +func driveNonCEAInboundToAbortedRevert(t *testing.T, txHash string) (*app.ChainApp, sdk.Context, string) { + t.Helper() + + chainApp, ctx, vals, inbound, coreVals := setupInboundBridgeTest(t, 4) + inbound.TxHash = txHash + inbound.IsCEA = false + inbound.Recipient = "" // FUNDS requires a recipient — fails ValidateForExecution post-quorum + + for i := 0; i < 3; i++ { + valAddr, err := sdk.ValAddressFromBech32(coreVals[i].OperatorAddress) + require.NoError(t, err) + require.NoError(t, utils.ExecVoteInbound(t, ctx, chainApp, vals[i], sdk.AccAddress(valAddr).String(), inbound)) + } + + return chainApp, ctx, uexecutortypes.GetInboundUniversalTxKey(*inbound) +} + +// TestInboundRevert_UnresolvableGasMetadata_AbortsInsteadOfQueueing is the +// headline regression test: the revert must be recorded ABORTED with a reason +// and must never reach PendingOutbounds. +func TestInboundRevert_UnresolvableGasMetadata_AbortsInsteadOfQueueing(t *testing.T) { + chainApp, ctx, utxId := driveNonCEAInboundToAbortedRevert(t, "0xabortrevert01") + + utx, found, err := chainApp.UexecutorKeeper.GetUniversalTx(ctx, utxId) + require.NoError(t, err) + require.True(t, found, "UTX must exist after quorum") + + revert := findInboundRevert(utx) + require.NotNil(t, revert, "a failed non-CEA inbound must still record an INBOUND_REVERT attempt") + + require.Equal(t, uexecutortypes.Status_ABORTED, revert.OutboundStatus, + "a revert whose gas metadata could not be resolved must be ABORTED, never PENDING") + require.NotEmpty(t, revert.AbortReason, "the abort reason must say why the revert could not be built") + require.Contains(t, revert.AbortReason, "gas fee info", + "the reason must name the lookup that failed") + + // Fail-closed: the gas fields stay empty rather than being half-written. + require.Empty(t, revert.GasToken) + require.Empty(t, revert.GasFee) + require.Empty(t, revert.GasPrice) + require.Empty(t, revert.GasLimit) + + requireNotQueuedForSigning(t, chainApp, ctx, revert.Id) + + // The whole queue stays clean, not just this id. + err = chainApp.UexecutorKeeper.PendingOutbounds.Walk(ctx, nil, func(id string, _ uexecutortypes.PendingOutboundEntry) (bool, error) { + t.Fatalf("PendingOutbounds must be empty, found %s", id) + return true, nil + }) + require.NoError(t, err) +} + +// TestInboundRevert_AbortedRevert_UnlocksRescue proves the other half of the +// fix: skipping the queue is not enough on its own, because non-CEA rescue used +// to require a REVERTED inbound-revert. An ABORTED one must now be accepted, or +// the user is left with a clean queue and no way out. +func TestInboundRevert_AbortedRevert_UnlocksRescue(t *testing.T) { + chainApp, ctx, utxId := driveNonCEAInboundToAbortedRevert(t, "0xabortrevert02") + + utx, found, err := chainApp.UexecutorKeeper.GetUniversalTx(ctx, utxId) + require.NoError(t, err) + require.True(t, found) + revert := findInboundRevert(utx) + require.NotNil(t, revert) + require.Equal(t, uexecutortypes.Status_ABORTED, revert.OutboundStatus, + "precondition: the revert must have aborted for this test to mean anything") + + prc20Addr := utils.GetDefaultAddresses().PRC20USDCAddr + senderAddr := common.HexToAddress(utils.GetDefaultAddresses().DefaultTestAddr) + log := buildRescueFundsLog(t, utxId, prc20Addr, senderAddr, + "eip155", big.NewInt(333), big.NewInt(1_000_000_000), big.NewInt(200_000)) + + err = chainApp.UexecutorKeeper.AttachRescueOutboundFromReceipt( + ctx, + makeRescueReceipt(t, "0xrescueafterabort", log), + uexecutortypes.PCTx{TxHash: "0xrescueafterabort", Status: "SUCCESS"}, + ) + require.NoError(t, err, "rescue must be accepted when the auto-revert aborted; the funds never came back") + + utx, _, err = chainApp.UexecutorKeeper.GetUniversalTx(ctx, utxId) + require.NoError(t, err) + + rescue := findRescueOutbound(utx) + require.NotNil(t, rescue, "a RESCUE_FUNDS outbound must be attached") + require.Equal(t, uexecutortypes.Status_PENDING, rescue.OutboundStatus, + "the rescue itself is signable and must be queued") + require.Equal(t, "333", rescue.GasFee) + + // The rescue is queued; the aborted revert still is not. + has, err := chainApp.UexecutorKeeper.PendingOutbounds.Has(ctx, rescue.Id) + require.NoError(t, err) + require.True(t, has, "the rescue outbound must be indexed for UV pickup") + requireNotQueuedForSigning(t, chainApp, ctx, revert.Id) +} diff --git a/test/integration/uexecutor/rescue_funds_test.go b/test/integration/uexecutor/rescue_funds_test.go index 0fa054361..ba25b367e 100644 --- a/test/integration/uexecutor/rescue_funds_test.go +++ b/test/integration/uexecutor/rescue_funds_test.go @@ -226,7 +226,7 @@ func TestRescueFunds(t *testing.T) { "eip155", big.NewInt(111), big.NewInt(1_000_000_000), big.NewInt(200_000)) err := chainApp.UexecutorKeeper.AttachRescueOutboundFromReceipt(ctx, makeRescueReceipt(t, "0xrescuetx03", log), uexecutortypes.PCTx{TxHash: "0xrescuetx03", Status: "SUCCESS"}) require.Error(t, err) - require.Contains(t, err.Error(), "no reverted inbound-revert outbound") + require.Contains(t, err.Error(), "no reverted or aborted inbound-revert outbound") }) t.Run("rescue is rejected for non-CEA inbound when auto-revert is PENDING", func(t *testing.T) { @@ -255,7 +255,7 @@ func TestRescueFunds(t *testing.T) { "eip155", big.NewInt(111), big.NewInt(1_000_000_000), big.NewInt(200_000)) err = chainApp.UexecutorKeeper.AttachRescueOutboundFromReceipt(ctx, makeRescueReceipt(t, "0xrescuetx03b", log), uexecutortypes.PCTx{TxHash: "0xrescuetx03b", Status: "SUCCESS"}) require.Error(t, err) - require.Contains(t, err.Error(), "no reverted inbound-revert outbound") + require.Contains(t, err.Error(), "no reverted or aborted inbound-revert outbound") }) t.Run("rescue succeeds for non-CEA inbound with reverted auto-revert", func(t *testing.T) { diff --git a/test/integration/uexecutor/revert_stuck_inbound_test.go b/test/integration/uexecutor/revert_stuck_inbound_test.go index d5dc8e8cc..826e67f9c 100644 --- a/test/integration/uexecutor/revert_stuck_inbound_test.go +++ b/test/integration/uexecutor/revert_stuck_inbound_test.go @@ -125,7 +125,15 @@ func TestRevertStuckInbound_HappyPath_ExpiredBallot_CreatesRevertOutbound(t *tes require.Equal(t, uexecutortypes.GetOutboundRevertId(inbound.SourceChain, inbound.TxHash, inbound.LogIndex), ob.Id, "outbound id must follow the canonical revert-id format") require.Equal(t, uexecutortypes.TxType_INBOUND_REVERT, ob.TxType, "outbound type must be INBOUND_REVERT") - require.Equal(t, uexecutortypes.Status_PENDING, ob.OutboundStatus, "outbound must start PENDING so UVs sign it") + // The harness's UniversalCore stub cannot serve getOutboundTxGasAndFees, so the + // revert's gas metadata is unresolvable and it is recorded ABORTED rather than + // queued for a signature it could never receive. The admin message still reports + // the outbound it created, and the UTX becomes eligible for RESCUE_FUNDS. The + // resolvable (PENDING) path is covered by + // x/uexecutor/keeper/build_revert_outbound_test.go. + require.Equal(t, uexecutortypes.Status_ABORTED, ob.OutboundStatus, + "a revert with unresolvable gas metadata must be ABORTED, not PENDING") + require.NotEmpty(t, ob.AbortReason, "ABORTED revert must record why it could not be built") require.Equal(t, inbound.SourceChain, ob.DestinationChain, "revert goes back to the source chain") require.Equal(t, inbound.RevertInstructions.FundRecipient, ob.Recipient, "recipient must use RevertInstructions.FundRecipient when set") @@ -134,10 +142,12 @@ func TestRevertStuckInbound_HappyPath_ExpiredBallot_CreatesRevertOutbound(t *tes require.Equal(t, chainutils.LenientCanonicalizeEVMAddress(inbound.Sender), ob.Sender, "sender field carries original depositor") // --- PendingOutbounds index assertions --- - pending, err := chainApp.UexecutorKeeper.PendingOutbounds.Get(ctx, ob.Id) - require.NoError(t, err, "revert outbound must be indexed in PendingOutbounds for UV pickup") - require.Equal(t, ob.Id, pending.OutboundId) - require.Equal(t, utx.Id, pending.UniversalTxId) + // An ABORTED revert must stay out of the signing queue: no ballot can ever form + // for it and there is no admin abort for outbounds, so an indexed row would be + // permanently stuck. + has, err := chainApp.UexecutorKeeper.PendingOutbounds.Has(ctx, ob.Id) + require.NoError(t, err) + require.False(t, has, "an ABORTED revert must not be indexed in PendingOutbounds") } // TestRevertStuckInbound_RecipientFallback_UsesSender covers the case where diff --git a/test/integration/uexecutor/vote_inbound_validation_test.go b/test/integration/uexecutor/vote_inbound_validation_test.go index 4e833dec6..ac1978fd1 100644 --- a/test/integration/uexecutor/vote_inbound_validation_test.go +++ b/test/integration/uexecutor/vote_inbound_validation_test.go @@ -233,7 +233,11 @@ func TestVoteInboundValidation(t *testing.T) { foundRevert = true require.Equal(t, inbound.SourceChain, ob.DestinationChain) require.Equal(t, inbound.Amount, ob.Amount) - require.Equal(t, uexecutortypes.Status_PENDING, ob.OutboundStatus) + // The harness's UniversalCore stub cannot serve gas metadata, so the + // revert is unsignable and is recorded ABORTED instead of queued. + require.Equal(t, uexecutortypes.Status_ABORTED, ob.OutboundStatus) + require.NotEmpty(t, ob.AbortReason) + requireNotQueuedForSigning(t, chainApp, ctx, ob.Id) break } } @@ -356,7 +360,12 @@ func TestVoteInboundValidation(t *testing.T) { require.Equal(t, inbound.SourceChain, ob.DestinationChain) require.Equal(t, inbound.Amount, ob.Amount) require.Equal(t, inbound.AssetAddr, ob.ExternalAssetAddr) - require.Equal(t, uexecutortypes.Status_PENDING, ob.OutboundStatus) + // The token config was removed above, so the revert cannot resolve the + // PRC20 it needs for gas metadata. It is recorded ABORTED with the + // reason instead of being queued as an unsignable PENDING row. + require.Equal(t, uexecutortypes.Status_ABORTED, ob.OutboundStatus) + require.Contains(t, ob.AbortReason, "failed to resolve PRC20") + requireNotQueuedForSigning(t, chainApp, ctx, ob.Id) break } } diff --git a/x/uexecutor/keeper/admin_revert.go b/x/uexecutor/keeper/admin_revert.go index d7a606941..d986cb52a 100644 --- a/x/uexecutor/keeper/admin_revert.go +++ b/x/uexecutor/keeper/admin_revert.go @@ -70,9 +70,20 @@ func (k Keeper) RevertStuckInbound(ctx context.Context, inbound types.Inbound) ( return "", "", fmt.Errorf("failed to create utx for revert: %w", cErr) } - revertOutbound := k.buildRevertOutbound(sdkCtx, &inbound) + revertOutbound, buildErr := k.buildRevertOutbound(sdkCtx, &inbound) if revertOutbound == nil { - return "", "", fmt.Errorf("failed to build revert outbound for inbound %s", universalTxKey) + return "", "", fmt.Errorf("failed to build revert outbound for inbound %s: %w", universalTxKey, buildErr) + } + if buildErr != nil { + // Gas metadata was unresolvable, so the revert is recorded ABORTED instead of + // entering the signing queue. It is still attached: the attempt stays auditable + // and it makes the UTX eligible for RESCUE_FUNDS, which is the remaining route + // back to the user. + k.Logger().Error("admin revert: revert outbound recorded without gas metadata", + "utx_id", universalTxKey, + "outbound_id", revertOutbound.Id, + "error", buildErr.Error(), + ) } if attachErr := k.attachOutboundsToUtx(sdkCtx, universalTxKey, []*types.OutboundTx{revertOutbound}, "admin revert: stuck ballot expired"); attachErr != nil { @@ -82,6 +93,7 @@ func (k Keeper) RevertStuckInbound(ctx context.Context, inbound types.Inbound) ( k.Logger().Info("admin revert: inbound revert outbound created", "utx_id", universalTxKey, "outbound_id", revertOutbound.Id, + "status", revertOutbound.OutboundStatus.String(), "source_chain", inbound.SourceChain, "recipient", revertOutbound.Recipient, "amount", revertOutbound.Amount, diff --git a/x/uexecutor/keeper/build_revert_outbound.go b/x/uexecutor/keeper/build_revert_outbound.go index 967adeb3d..b9dc44de1 100644 --- a/x/uexecutor/keeper/build_revert_outbound.go +++ b/x/uexecutor/keeper/build_revert_outbound.go @@ -1,13 +1,37 @@ package keeper import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/pushchain/push-chain-node/x/uexecutor/types" ) -// buildRevertOutbound creates an INBOUND_REVERT outbound with gas fields populated -// from the UniversalCore contract via getOutboundTxGasAndFees. -func (k Keeper) buildRevertOutbound(sdkCtx sdk.Context, inbound *types.Inbound) *types.OutboundTx { +// buildRevertOutbound creates an INBOUND_REVERT outbound that returns a failed +// inbound's funds on the source chain. +// +// The gas fields (gas token / fee / price / limit) are resolved from the +// UniversalCore contract and are mandatory: the universal validators refuse to +// sign an outbound whose gas price is zero or missing, so a revert built without +// them can never be broadcast, and re-resolving the metadata later does not +// rewrite the fields already stored on the outbound. +// +// Failure to resolve them is therefore never silent. The outbound is returned +// marked Status_ABORTED with an AbortReason instead of Status_PENDING, together +// with a non-nil error describing what failed: +// +// - it is still worth recording. The attempt stays in the audit trail and it +// makes the universal tx eligible for RESCUE_FUNDS, which is the recovery +// route for funds that never made it back to the user. +// - it must never be queued for signing. attachOutboundsToUtx enforces that by +// indexing only PENDING outbounds into PendingOutbounds. +// +// A nil outbound together with a non-nil error means nothing could be built at all. +func (k Keeper) buildRevertOutbound(sdkCtx sdk.Context, inbound *types.Inbound) (*types.OutboundTx, error) { + if inbound == nil { + return nil, fmt.Errorf("cannot build revert outbound: inbound is nil") + } + recipient := inbound.Sender if inbound.RevertInstructions != nil && inbound.RevertInstructions.FundRecipient != "" { recipient = inbound.RevertInstructions.FundRecipient @@ -27,24 +51,40 @@ func (k Keeper) buildRevertOutbound(sdkCtx sdk.Context, inbound *types.Inbound) // Look up the PRC20 address for this external token tokenCfg, err := k.uregistryKeeper.GetTokenConfig(sdkCtx, inbound.SourceChain, inbound.AssetAddr) if err != nil || tokenCfg.NativeRepresentation == nil || tokenCfg.NativeRepresentation.ContractAddress == "" { - k.Logger().Warn("failed to get PRC20 for revert outbound gas lookup, proceeding without gas fields", + lookupErr := err + if lookupErr == nil { + lookupErr = fmt.Errorf("token config has no native representation") + } + abortErr := fmt.Errorf("failed to resolve PRC20 for revert outbound of %s on %s: %w", + inbound.AssetAddr, inbound.SourceChain, lookupErr) + + k.Logger().Error("revert outbound aborted: PRC20 lookup failed", "chain", inbound.SourceChain, "asset", inbound.AssetAddr, - "error", err, + "outbound_id", outbound.Id, + "error", abortErr.Error(), ) - return outbound + + abortRevertOutbound(outbound, abortErr) + return outbound, abortErr } // Fetch gas fields from UniversalCore.getOutboundTxGasAndFees(prc20, 0) // 0 means use the contract's baseLimit for this chain gasToken, gasFee, gasPrice, gasLimit, err := k.GetGasFeeInfoForRevertOutbound(sdkCtx, tokenCfg.NativeRepresentation.ContractAddress) if err != nil { - k.Logger().Warn("failed to fetch gas fee info for revert outbound, proceeding without gas fields", + abortErr := fmt.Errorf("failed to fetch gas fee info for revert outbound of PRC20 %s on %s: %w", + tokenCfg.NativeRepresentation.ContractAddress, inbound.SourceChain, err) + + k.Logger().Error("revert outbound aborted: gas fee lookup failed", "chain", inbound.SourceChain, "prc20", tokenCfg.NativeRepresentation.ContractAddress, - "error", err, + "outbound_id", outbound.Id, + "error", abortErr.Error(), ) - return outbound + + abortRevertOutbound(outbound, abortErr) + return outbound, abortErr } outbound.GasToken = gasToken @@ -52,5 +92,18 @@ func (k Keeper) buildRevertOutbound(sdkCtx sdk.Context, inbound *types.Inbound) outbound.GasPrice = gasPrice outbound.GasLimit = gasLimit - return outbound + return outbound, nil +} + +// abortRevertOutbound marks a half-built revert outbound as ABORTED with a reason. +// It mirrors the shape AbortOutbound writes for outbounds that are already attached +// to a universal tx; the matching outbound_aborted event is emitted by +// attachOutboundsToUtx, which is where the universal tx id is known. +func abortRevertOutbound(outbound *types.OutboundTx, reason error) { + outbound.OutboundStatus = types.Status_ABORTED + outbound.AbortReason = reason.Error() + outbound.GasToken = "" + outbound.GasFee = "" + outbound.GasPrice = "" + outbound.GasLimit = "" } diff --git a/x/uexecutor/keeper/build_revert_outbound_test.go b/x/uexecutor/keeper/build_revert_outbound_test.go new file mode 100644 index 000000000..890262c42 --- /dev/null +++ b/x/uexecutor/keeper/build_revert_outbound_test.go @@ -0,0 +1,241 @@ +package keeper_test + +import ( + "errors" + "math/big" + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + evmtypes "github.com/cosmos/evm/x/vm/types" + "github.com/ethereum/go-ethereum/common" + + "github.com/pushchain/push-chain-node/x/uexecutor/types" + uregistrytypes "github.com/pushchain/push-chain-node/x/uregistry/types" +) + +const ( + revertSourceChain = "eip155:11155111" + revertAssetAddr = "0x0000000000000000000000000000000000000e07" + revertPRC20Addr = "0x0000000000000000000000000000000000000e06" + revertGasTokenHex = "0x0000000000000000000000000000000000001111" +) + +// revertTestInbound is a non-CEA FUNDS inbound whose execution failed, i.e. the +// input to buildRevertOutbound. +func revertTestInbound() *types.Inbound { + return &types.Inbound{ + SourceChain: revertSourceChain, + TxHash: "0xdeadbeef", + LogIndex: "1", + Sender: "0x778d3206374F8ac265728e18E3fE2Ae6b93E4ce4", + Recipient: "0x778d3206374F8ac265728e18E3fE2Ae6b93E4ce4", + Amount: "1000000", + AssetAddr: revertAssetAddr, + TxType: types.TxType_FUNDS, + RevertInstructions: &types.RevertInstructions{ + FundRecipient: "0x527F3692F5C53CfA83F7689885995606F93b6164", + }, + } +} + +func revertTestTokenConfig() uregistrytypes.TokenConfig { + return uregistrytypes.TokenConfig{ + Chain: revertSourceChain, + Address: revertAssetAddr, + Enabled: true, + NativeRepresentation: &uregistrytypes.NativeRepresentation{ + ContractAddress: revertPRC20Addr, + }, + } +} + +// expectGasFeeCall stubs UniversalCore.getOutboundTxGasAndFees to return a +// well-formed 6-output response, i.e. the healthy path. +func expectGasFeeCall(t *testing.T, f *testFixture, gasFee, gasPrice, gasLimit *big.Int) { + t.Helper() + + ucABI, err := types.ParseUniversalCoreABI() + require.NoError(t, err) + + packed, err := ucABI.Methods["getOutboundTxGasAndFees"].Outputs.Pack( + common.HexToAddress(revertGasTokenHex), // gasToken + gasFee, // gasFee + big.NewInt(0), // protocolFee + gasPrice, // gasPrice + "eip155", // chainNamespace + gasLimit, // gasLimitUsed + ) + require.NoError(t, err) + + f.mockEVMKeeper.EXPECT(). + CallEVM(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), + gomock.Eq("getOutboundTxGasAndFees"), gomock.Any(), gomock.Any()). + Return(&evmtypes.MsgEthereumTxResponse{Ret: packed}, nil). + AnyTimes() +} + +// attachRevert stores a UTX and runs the revert outbound through the same attach +// path the production callers use, so PendingOutbounds indexing is exercised. +func attachRevert(t *testing.T, f *testFixture, utxId string, ob *types.OutboundTx) { + t.Helper() + + require.NoError(t, f.k.UniversalTx.Set(f.ctx, utxId, types.UniversalTx{ + Id: utxId, + InboundTx: revertTestInbound(), + })) + f.mockUregistryKeeper.EXPECT(). + GetChainConfig(gomock.Any(), revertSourceChain). + Return(uregistrytypes.ChainConfig{Chain: revertSourceChain}, nil). + AnyTimes() + + require.NoError(t, f.k.TestAttachOutboundsToUtx(f.ctx, utxId, []*types.OutboundTx{ob}, "execution failed")) +} + +func hasEvent(events sdk.Events, evtType string) bool { + for _, e := range events { + if e.Type == evtType { + return true + } + } + return false +} + +// TestBuildRevertOutbound_HealthyPath is the regression guard for the untouched +// path: when the gas metadata resolves, the revert is PENDING, carries the exact +// values UniversalCore returned, and is indexed for universal-validator pickup. +func TestBuildRevertOutbound_HealthyPath(t *testing.T) { + f := setupPendingOutboundFixture(t) + + f.mockUregistryKeeper.EXPECT(). + GetTokenConfig(gomock.Any(), revertSourceChain, revertAssetAddr). + Return(revertTestTokenConfig(), nil). + AnyTimes() + expectGasFeeCall(t, f, big.NewInt(123_456), big.NewInt(1_000_000_000), big.NewInt(200_000)) + + inbound := revertTestInbound() + ob, err := f.k.TestBuildRevertOutbound(f.ctx, inbound) + require.NoError(t, err, "healthy gas metadata must not produce an error") + require.NotNil(t, ob) + + require.Equal(t, types.Status_PENDING, ob.OutboundStatus, "healthy revert must stay PENDING so UVs sign it") + require.Empty(t, ob.AbortReason, "healthy revert must carry no abort reason") + require.Equal(t, types.TxType_INBOUND_REVERT, ob.TxType) + require.Equal(t, revertSourceChain, ob.DestinationChain) + require.Equal(t, inbound.Amount, ob.Amount) + require.Equal(t, inbound.AssetAddr, ob.ExternalAssetAddr) + require.Equal(t, inbound.RevertInstructions.FundRecipient, ob.Recipient) + + // Gas fields exactly as UniversalCore returned them. + require.Equal(t, common.HexToAddress(revertGasTokenHex).Hex(), ob.GasToken) + require.Equal(t, "123456", ob.GasFee) + require.Equal(t, "1000000000", ob.GasPrice) + require.Equal(t, "200000", ob.GasLimit) + + // ...and it still enters the signing queue. + attachRevert(t, f, "utx-healthy", ob) + entry, err := f.k.PendingOutbounds.Get(f.ctx, ob.Id) + require.NoError(t, err, "a PENDING revert must be indexed in PendingOutbounds") + require.Equal(t, "utx-healthy", entry.UniversalTxId) +} + +// TestBuildRevertOutbound_GasFeeLookupFails is the headline case: the +// UniversalCore call reverts, so the outbound must be recorded ABORTED and must +// never reach the signing queue. +func TestBuildRevertOutbound_GasFeeLookupFails(t *testing.T) { + f := setupPendingOutboundFixture(t) + + f.mockUregistryKeeper.EXPECT(). + GetTokenConfig(gomock.Any(), revertSourceChain, revertAssetAddr). + Return(revertTestTokenConfig(), nil). + AnyTimes() + f.mockEVMKeeper.EXPECT(). + CallEVM(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), + gomock.Eq("getOutboundTxGasAndFees"), gomock.Any(), gomock.Any()). + Return(nil, errors.New("execution reverted: ZeroGasPrice")). + AnyTimes() + + ob, err := f.k.TestBuildRevertOutbound(f.ctx, revertTestInbound()) + require.Error(t, err, "a gas-metadata failure must be reported, not swallowed") + require.Contains(t, err.Error(), "gas fee info") + require.NotNil(t, ob, "the aborted attempt is still returned so it can be recorded") + + require.Equal(t, types.Status_ABORTED, ob.OutboundStatus, + "an unsignable revert must be ABORTED, never PENDING") + require.NotEmpty(t, ob.AbortReason, "abort reason must explain why the revert could not be built") + require.Contains(t, ob.AbortReason, "ZeroGasPrice") + require.Empty(t, ob.GasFee) + require.Empty(t, ob.GasPrice) + require.Empty(t, ob.GasLimit) + require.Empty(t, ob.GasToken) + + // Recorded on the UTX for the audit trail... + attachRevert(t, f, "utx-aborted", ob) + utx, found, err := f.k.GetUniversalTx(f.ctx, "utx-aborted") + require.NoError(t, err) + require.True(t, found) + require.Len(t, utx.OutboundTx, 1) + require.Equal(t, types.Status_ABORTED, utx.OutboundTx[0].OutboundStatus) + + // ...but NOT queued for signing: an unsignable row here would sit forever. + has, err := f.k.PendingOutbounds.Has(f.ctx, ob.Id) + require.NoError(t, err) + require.False(t, has, "an ABORTED revert must never be indexed in PendingOutbounds") + + require.True(t, hasEvent(f.ctx.EventManager().Events(), "outbound_aborted"), + "an outbound_aborted event must be emitted so monitoring sees the failure") +} + +// TestBuildRevertOutbound_TokenConfigMissing covers the other fail-open branch: +// the PRC20 for the inbound asset cannot be resolved at all. +func TestBuildRevertOutbound_TokenConfigMissing(t *testing.T) { + f := setupPendingOutboundFixture(t) + + f.mockUregistryKeeper.EXPECT(). + GetTokenConfig(gomock.Any(), revertSourceChain, revertAssetAddr). + Return(uregistrytypes.TokenConfig{}, errors.New("token config not found")). + AnyTimes() + + ob, err := f.k.TestBuildRevertOutbound(f.ctx, revertTestInbound()) + require.Error(t, err) + require.Contains(t, err.Error(), "PRC20") + require.NotNil(t, ob) + require.Equal(t, types.Status_ABORTED, ob.OutboundStatus) + require.Contains(t, ob.AbortReason, "token config not found") + + attachRevert(t, f, "utx-no-token-config", ob) + has, err := f.k.PendingOutbounds.Has(f.ctx, ob.Id) + require.NoError(t, err) + require.False(t, has, "an ABORTED revert must never be indexed in PendingOutbounds") +} + +// TestBuildRevertOutbound_TokenConfigWithoutNativeRepresentation covers a token +// config that resolves but carries no PRC20 — the lookup returns no error, so the +// abort reason has to be synthesised. +func TestBuildRevertOutbound_TokenConfigWithoutNativeRepresentation(t *testing.T) { + f := setupPendingOutboundFixture(t) + + f.mockUregistryKeeper.EXPECT(). + GetTokenConfig(gomock.Any(), revertSourceChain, revertAssetAddr). + Return(uregistrytypes.TokenConfig{Chain: revertSourceChain, Address: revertAssetAddr}, nil). + AnyTimes() + + ob, err := f.k.TestBuildRevertOutbound(f.ctx, revertTestInbound()) + require.Error(t, err) + require.NotNil(t, ob) + require.Equal(t, types.Status_ABORTED, ob.OutboundStatus) + require.Contains(t, ob.AbortReason, "no native representation") +} + +// TestBuildRevertOutbound_NilInbound proves the (outbound, error) contract: a nil +// outbound only ever comes back with a non-nil error, which is what the admin +// revert path checks before it claims a revert was created. +func TestBuildRevertOutbound_NilInbound(t *testing.T) { + f := setupPendingOutboundFixture(t) + + ob, err := f.k.TestBuildRevertOutbound(f.ctx, nil) + require.Error(t, err) + require.Nil(t, ob) +} diff --git a/x/uexecutor/keeper/create_outbound.go b/x/uexecutor/keeper/create_outbound.go index 43e996941..f8995ae23 100644 --- a/x/uexecutor/keeper/create_outbound.go +++ b/x/uexecutor/keeper/create_outbound.go @@ -245,22 +245,28 @@ func (k Keeper) AttachRescueOutboundFromReceipt( // never arrived on Push Chain and are still locked on the source chain. // // Non-CEA inbounds: the auto-generated INBOUND_REVERT outbound must exist and - // have reached REVERTED status, meaning TSS could not return the funds to the - // source chain and they are stuck (held by the gateway contract or in escrow). + // have reached REVERTED or ABORTED status. REVERTED means TSS tried and could + // not return the funds to the source chain; ABORTED means the revert could not + // even be built (its gas metadata was unresolvable) so it was never queued for + // signing. Either way the funds never came back and are stuck (held by the + // gateway contract or in escrow), which is exactly what rescue exists for. if originalUtx.InboundTx.IsCEA { if len(originalUtx.PcTx) == 0 || originalUtx.PcTx[0] == nil || originalUtx.PcTx[0].Status != "FAILED" { return fmt.Errorf("rescue: UTX %s CEA deposit did not fail", originalUtxId) } } else { - hasRevertedAutoRevert := false + hasUnrecoveredAutoRevert := false for _, ob := range originalUtx.OutboundTx { - if ob != nil && ob.TxType == types.TxType_INBOUND_REVERT && ob.OutboundStatus == types.Status_REVERTED { - hasRevertedAutoRevert = true + if ob == nil || ob.TxType != types.TxType_INBOUND_REVERT { + continue + } + if ob.OutboundStatus == types.Status_REVERTED || ob.OutboundStatus == types.Status_ABORTED { + hasUnrecoveredAutoRevert = true break } } - if !hasRevertedAutoRevert { - return fmt.Errorf("rescue: UTX %s has no reverted inbound-revert outbound", originalUtxId) + if !hasUnrecoveredAutoRevert { + return fmt.Errorf("rescue: UTX %s has no reverted or aborted inbound-revert outbound", originalUtxId) } } @@ -363,14 +369,28 @@ func (k Keeper) attachOutboundsToUtx( } } - // Write to pending outbounds index (inside UpdateUniversalTx closure for atomicity) - if err := k.PendingOutbounds.Set(ctx, outbound.Id, types.PendingOutboundEntry{ - OutboundId: outbound.Id, - UniversalTxId: utxId, - CreatedAt: ctx.BlockHeight(), - SigningDeadline: signingDeadline, - }); err != nil { - return fmt.Errorf("failed to set pending outbound index for %s: %w", outbound.Id, err) + // Only PENDING outbounds belong in the signing queue. Anything already + // ABORTED (e.g. a revert whose gas metadata could not be resolved) is + // recorded on the universal tx for the audit trail, but indexing it would + // park a row that can never be signed: no ballot forms for it, nothing + // removes it, and there is no admin abort for outbounds. + if outbound.OutboundStatus == types.Status_PENDING { + // Write to pending outbounds index (inside UpdateUniversalTx closure for atomicity) + if err := k.PendingOutbounds.Set(ctx, outbound.Id, types.PendingOutboundEntry{ + OutboundId: outbound.Id, + UniversalTxId: utxId, + CreatedAt: ctx.BlockHeight(), + SigningDeadline: signingDeadline, + }); err != nil { + return fmt.Errorf("failed to set pending outbound index for %s: %w", outbound.Id, err) + } + } else { + k.Logger().Warn("outbound attached without entering the signing queue", + "utx_id", utxId, + "outbound_id", outbound.Id, + "status", outbound.OutboundStatus.String(), + "abort_reason", outbound.AbortReason, + ) } var pcTxHash string @@ -403,6 +423,17 @@ func (k Keeper) attachOutboundsToUtx( if err == nil { ctx.EventManager().EmitEvent(evt) } + + // Mirror AbortOutbound's monitoring signal for outbounds that arrive + // already aborted, so alerting sees them the same way. + if outbound.OutboundStatus == types.Status_ABORTED { + ctx.EventManager().EmitEvent(sdk.NewEvent( + "outbound_aborted", + sdk.NewAttribute("utx_id", utxId), + sdk.NewAttribute("outbound_id", outbound.Id), + sdk.NewAttribute("abort_reason", outbound.AbortReason), + )) + } } return nil diff --git a/x/uexecutor/keeper/execute_inbound_funds.go b/x/uexecutor/keeper/execute_inbound_funds.go index fa3901ef9..5241f6aa8 100644 --- a/x/uexecutor/keeper/execute_inbound_funds.go +++ b/x/uexecutor/keeper/execute_inbound_funds.go @@ -74,7 +74,18 @@ func (k Keeper) ExecuteInboundFunds(ctx context.Context, utx types.UniversalTx) // isCEA failures never create an INBOUND_REVERT outbound // (consistent with execute_inbound_funds_and_payload.go and execute_inbound_gas_and_payload.go) if err != nil && !inbound.IsCEA { - revertOutbound := k.buildRevertOutbound(sdkCtx, inbound) + revertOutbound, buildErr := k.buildRevertOutbound(sdkCtx, inbound) + if buildErr != nil { + // The revert is still attached (recorded ABORTED) so the attempt stays + // auditable and the UTX becomes eligible for rescue. + k.Logger().Error("revert outbound could not be fully built", + "utx_id", utx.Id, + "error", buildErr.Error(), + ) + } + if revertOutbound == nil { + return nil + } if attachErr := k.attachOutboundsToUtx(sdkCtx, utx.Id, []*types.OutboundTx{revertOutbound}, err.Error()); attachErr != nil { if storeErr := k.UpdateUniversalTx(sdkCtx, utx.Id, func(u *types.UniversalTx) error { u.RevertError = attachErr.Error() diff --git a/x/uexecutor/keeper/execute_inbound_funds_and_payload.go b/x/uexecutor/keeper/execute_inbound_funds_and_payload.go index 09f06fce6..fadd38373 100644 --- a/x/uexecutor/keeper/execute_inbound_funds_and_payload.go +++ b/x/uexecutor/keeper/execute_inbound_funds_and_payload.go @@ -187,7 +187,18 @@ func (k Keeper) ExecuteInboundFundsAndPayload(ctx context.Context, utx types.Uni // If deposit failed, stop here. if execErr != nil { if shouldRevert { - revertOutbound := k.buildRevertOutbound(sdkCtx, utx.InboundTx) + revertOutbound, buildErr := k.buildRevertOutbound(sdkCtx, utx.InboundTx) + if buildErr != nil { + // The revert is still attached (recorded ABORTED) so the attempt stays + // auditable and the UTX becomes eligible for rescue. + k.Logger().Error("revert outbound could not be fully built", + "utx_id", universalTxKey, + "error", buildErr.Error(), + ) + } + if revertOutbound == nil { + return nil + } if attachErr := k.attachOutboundsToUtx( sdkCtx, universalTxKey, diff --git a/x/uexecutor/keeper/execute_inbound_gas.go b/x/uexecutor/keeper/execute_inbound_gas.go index 9a9d194db..132ec126c 100644 --- a/x/uexecutor/keeper/execute_inbound_gas.go +++ b/x/uexecutor/keeper/execute_inbound_gas.go @@ -190,7 +190,18 @@ func (k Keeper) ExecuteInboundGas(ctx context.Context, inbound types.Inbound) er } if execErr != nil && shouldRevert { - revertOutbound := k.buildRevertOutbound(sdkCtx, &inbound) + revertOutbound, buildErr := k.buildRevertOutbound(sdkCtx, &inbound) + if buildErr != nil { + // The revert is still attached (recorded ABORTED) so the attempt stays + // auditable and the UTX becomes eligible for rescue. + k.Logger().Error("revert outbound could not be fully built", + "utx_id", universalTxKey, + "error", buildErr.Error(), + ) + } + if revertOutbound == nil { + return nil + } if attachErr := k.attachOutboundsToUtx( sdkCtx, diff --git a/x/uexecutor/keeper/execute_inbound_gas_and_payload.go b/x/uexecutor/keeper/execute_inbound_gas_and_payload.go index 34f244792..158c9f4ec 100644 --- a/x/uexecutor/keeper/execute_inbound_gas_and_payload.go +++ b/x/uexecutor/keeper/execute_inbound_gas_and_payload.go @@ -189,7 +189,18 @@ func (k Keeper) ExecuteInboundGasAndPayload(ctx context.Context, utx types.Unive // --- create revert ONLY for pre-deposit / deposit failures (non-isCEA path) if execErr != nil && shouldRevert { - revertOutbound := k.buildRevertOutbound(sdkCtx, utx.InboundTx) + revertOutbound, buildErr := k.buildRevertOutbound(sdkCtx, utx.InboundTx) + if buildErr != nil { + // The revert is still attached (recorded ABORTED) so the attempt stays + // auditable and the UTX becomes eligible for rescue. + k.Logger().Error("revert outbound could not be fully built", + "utx_id", universalTxKey, + "error", buildErr.Error(), + ) + } + if revertOutbound == nil { + return nil + } if attachErr := k.attachOutboundsToUtx( sdkCtx, diff --git a/x/uexecutor/keeper/export_test.go b/x/uexecutor/keeper/export_test.go index 2a4060035..641345fa4 100644 --- a/x/uexecutor/keeper/export_test.go +++ b/x/uexecutor/keeper/export_test.go @@ -8,3 +8,7 @@ import ( func (k Keeper) TestAttachOutboundsToUtx(ctx sdk.Context, utxId string, outbounds []*types.OutboundTx, revertMsg string) error { return k.attachOutboundsToUtx(ctx, utxId, outbounds, revertMsg) } + +func (k Keeper) TestBuildRevertOutbound(ctx sdk.Context, inbound *types.Inbound) (*types.OutboundTx, error) { + return k.buildRevertOutbound(ctx, inbound) +} diff --git a/x/uexecutor/keeper/handle_failed_inbound_validation.go b/x/uexecutor/keeper/handle_failed_inbound_validation.go index 0713aaa32..1c00f4702 100644 --- a/x/uexecutor/keeper/handle_failed_inbound_validation.go +++ b/x/uexecutor/keeper/handle_failed_inbound_validation.go @@ -44,7 +44,18 @@ func (k Keeper) handleFailedInboundValidation(sdkCtx sdk.Context, utx types.Univ "source_chain", inbound.SourceChain, "amount", inbound.Amount, ) - revertOutbound := k.buildRevertOutbound(sdkCtx, inbound) + revertOutbound, buildErr := k.buildRevertOutbound(sdkCtx, inbound) + if buildErr != nil { + // The revert is still attached (recorded ABORTED) so the attempt stays + // auditable and the UTX becomes eligible for rescue. + k.Logger().Error("revert outbound could not be fully built", + "utx_key", universalTxKey, + "error", buildErr.Error(), + ) + } + if revertOutbound == nil { + return nil + } if attachErr := k.attachOutboundsToUtx( sdkCtx, diff --git a/x/uexecutor/keeper/keeper_test.go b/x/uexecutor/keeper/keeper_test.go index d99108628..793162f3a 100755 --- a/x/uexecutor/keeper/keeper_test.go +++ b/x/uexecutor/keeper/keeper_test.go @@ -50,6 +50,9 @@ var maccPerms = map[string][]string{ stakingtypes.NotBondedPoolName: {authtypes.Burner, authtypes.Staking}, minttypes.ModuleName: {authtypes.Minter}, govtypes.ModuleName: {authtypes.Burner}, + // The uexecutor module account is resolved by Keeper.GetUeModuleAddress, which + // every UniversalCore call goes through. + types.ModuleName: nil, } type testFixture struct { @@ -117,10 +120,10 @@ func SetupTest(t *testing.T) *testFixture { registerBaseSDKModules(logger, f, encCfg, keys, accountAddressCodec, validatorAddressCodec, consensusAddressCodec) // Setup Keeper. - f.k = keeper.NewKeeper(encCfg.Codec, runtime.NewKVStoreService(keys[types.ModuleName]), logger, f.govModAddr, f.mockEVMKeeper, &feemarketkeeper.Keeper{}, f.mockBankKeeper, authkeeper.AccountKeeper{}, f.mockUregistryKeeper, &uvalidatorKeeper.Keeper{}) + f.k = keeper.NewKeeper(encCfg.Codec, runtime.NewKVStoreService(keys[types.ModuleName]), logger, f.govModAddr, f.mockEVMKeeper, &feemarketkeeper.Keeper{}, f.mockBankKeeper, f.accountkeeper, f.mockUregistryKeeper, &uvalidatorKeeper.Keeper{}) f.msgServer = keeper.NewMsgServerImpl(f.k) f.queryServer = keeper.NewQuerier(f.k) - f.appModule = module.NewAppModule(encCfg.Codec, f.k, f.mockEVMKeeper, &feemarketkeeper.Keeper{}, f.mockBankKeeper, authkeeper.AccountKeeper{}, f.mockUregistryKeeper, &uvalidatorKeeper.Keeper{}) + f.appModule = module.NewAppModule(encCfg.Codec, f.k, f.mockEVMKeeper, &feemarketkeeper.Keeper{}, f.mockBankKeeper, f.accountkeeper, f.mockUregistryKeeper, &uvalidatorKeeper.Keeper{}) return f } @@ -146,8 +149,11 @@ func registerBaseSDKModules( registerModuleInterfaces(encCfg) // Auth Keeper. + // NOTE: keys is built from module names, and authtypes.StoreKey ("acc") is not + // authtypes.ModuleName ("auth") — looking up the wrong one yields a nil store key + // and panics the first time the account keeper is actually touched. f.accountkeeper = authkeeper.NewAccountKeeper( - encCfg.Codec, runtime.NewKVStoreService(keys[authtypes.StoreKey]), + encCfg.Codec, runtime.NewKVStoreService(keys[authtypes.ModuleName]), authtypes.ProtoBaseAccount, maccPerms, ac, app.Bech32PrefixAccAddr, From b851fde9b0274bace2629c2af0d480853857d486 Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Wed, 26 Aug 2026 19:41:28 +0530 Subject: [PATCH 30/60] fix: reject empty authz.MsgExec in IsGaslessTx (F-2026-18816) (#332) An empty inner message list passed the allowlist loop vacuously, making the tx gasless and skipping the fee and min-gas-price decorators. --- app/txpolicy/gasless.go | 5 +++ app/txpolicy/gasless_test.go | 84 ++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/app/txpolicy/gasless.go b/app/txpolicy/gasless.go index 40acd8f1b..b77fe52e6 100644 --- a/app/txpolicy/gasless.go +++ b/app/txpolicy/gasless.go @@ -33,6 +33,11 @@ func IsGaslessTx(tx sdk.Tx) bool { for _, msg := range msgs { switch m := msg.(type) { case *authz.MsgExec: + // An empty nest would pass the loop below vacuously and make the whole + // tx gasless, bypassing the fee and min-gas-price decorators (F-2026-18816). + if len(m.Msgs) == 0 { + return false + } // Only gasless if ALL inner messages are allowed for _, innerMsg := range m.Msgs { if !slices.Contains(GaslessMsgTypes, innerMsg.TypeUrl) { diff --git a/app/txpolicy/gasless_test.go b/app/txpolicy/gasless_test.go index 78f2c57e0..9531ce0a5 100644 --- a/app/txpolicy/gasless_test.go +++ b/app/txpolicy/gasless_test.go @@ -52,3 +52,87 @@ func TestGaslessMsgTypesExcludeEthereumTx(t *testing.T) { require.True(t, txpolicy.IsGaslessTx(tx)) }) } + +// TestIsGaslessTxAuthzExecNesting guards the authz.MsgExec branch of IsGaslessTx. +// +// The inner-message loop is an "all must be allowlisted" check, so an empty nest +// satisfies it vacuously and would make the whole tx gasless - skipping +// DeductFeeDecorator and MinGasPriceDecorator for a zero-fee tx, and handing the +// signer a free on-chain account via AccountInitDecorator, which gates on this +// same predicate. Nothing upstream catches it: authz.MsgExec has no ValidateBasic +// in SDK v0.53.7, and the empty check lives only in the msg server, which runs +// after the fee decorators (F-2026-18816). +func TestIsGaslessTxAuthzExecNesting(t *testing.T) { + anyOf := func(t *testing.T, msg sdk.Msg) *codectypes.Any { + t.Helper() + a, err := codectypes.NewAnyWithValue(msg) + require.NoError(t, err) + return a + } + + tests := []struct { + name string + inner []sdk.Msg + gasless bool + reason string + }{ + { + name: "empty nest is not gasless", + inner: nil, + gasless: false, + reason: "an empty authz.MsgExec must not pass the inner allowlist loop vacuously", + }, + { + name: "empty non-nil nest is not gasless", + inner: []sdk.Msg{}, + gasless: false, + reason: "a zero-length (but non-nil) inner message list must be rejected too", + }, + { + name: "all-allowlisted nest stays gasless", + inner: []sdk.Msg{&uexecutortypes.MsgVoteInbound{}, &uexecutortypes.MsgVoteOutbound{}}, + gasless: true, + reason: "a nest of only allowlisted messages must remain gasless", + }, + { + name: "mixed nest is not gasless", + inner: []sdk.Msg{&uexecutortypes.MsgVoteInbound{}, &evmtypes.MsgEthereumTx{}}, + gasless: false, + reason: "one non-allowlisted inner message must disqualify the whole tx", + }, + { + name: "nested MsgExec is not gasless", + inner: []sdk.Msg{&authz.MsgExec{Msgs: []*codectypes.Any{}}}, + gasless: false, + reason: "authz.MsgExec is not itself an allowlisted type, so nesting one must not recurse into a vacuous pass", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Preserve the nil vs. zero-length distinction: len() treats them the + // same, but constructing both proves the guard does not depend on it. + var inner []*codectypes.Any + if tc.inner != nil { + inner = make([]*codectypes.Any, 0, len(tc.inner)) + for _, m := range tc.inner { + inner = append(inner, anyOf(t, m)) + } + } + + tx := msgsOnlyTx{msgs: []sdk.Msg{&authz.MsgExec{Msgs: inner}}} + require.Equal(t, tc.gasless, txpolicy.IsGaslessTx(tx), tc.reason) + }) + } +} + +// TestIsGaslessTxEmptyExecAlongsideAllowedMsg pins the multi-message case: the +// outer loop must not let an allowlisted sibling carry an empty nest through. +func TestIsGaslessTxEmptyExecAlongsideAllowedMsg(t *testing.T) { + tx := msgsOnlyTx{msgs: []sdk.Msg{ + &uexecutortypes.MsgVoteInbound{}, + &authz.MsgExec{}, + }} + require.False(t, txpolicy.IsGaslessTx(tx), + "an empty authz.MsgExec must disqualify the tx even next to an allowlisted message") +} From 5b4b744b44efe2d4dee59313199e6a23c7000538 Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Wed, 26 Aug 2026 19:41:33 +0530 Subject: [PATCH 31/60] fix: F-2026-18803 | [Dual Defense] Gasless MsgVoteChainMeta Can Inflate Unregistered ChainMetas Keys (#333) * fix: reject MsgVoteChainMeta for unregistered chains (F-2026-18803) Gate Keeper.VoteChainMeta on uregistry before any state read/write, and cap observed_chain_id length + CAIP-2 shape in ValidateBasic. * test: cover the unregistered-chain chain-meta gate (F-2026-18803) Assert no ChainMetas row is written, at keeper and integration level. --- .../uexecutor/vote_chain_meta_test.go | 43 +++++ x/uexecutor/keeper/chain_meta.go | 17 ++ x/uexecutor/keeper/chain_meta_test.go | 176 ++++++++++++++++++ x/uexecutor/types/msg_vote_chain_meta.go | 23 +++ x/uexecutor/types/msg_vote_chain_meta_test.go | 101 ++++++++++ 5 files changed, 360 insertions(+) create mode 100644 x/uexecutor/keeper/chain_meta_test.go create mode 100644 x/uexecutor/types/msg_vote_chain_meta_test.go diff --git a/test/integration/uexecutor/vote_chain_meta_test.go b/test/integration/uexecutor/vote_chain_meta_test.go index 9bc81a9b7..5c72966bc 100644 --- a/test/integration/uexecutor/vote_chain_meta_test.go +++ b/test/integration/uexecutor/vote_chain_meta_test.go @@ -53,6 +53,18 @@ func setupVoteChainMetaTest(t *testing.T, numVals int) (*app.ChainApp, sdk.Conte return testApp, ctx, universalVals, validators } +// chainMetaKeys returns every key currently present in the ChainMetas map. +func chainMetaKeys(t *testing.T, ctx sdk.Context, testApp *app.ChainApp) []string { + t.Helper() + var keys []string + require.NoError(t, testApp.UexecutorKeeper.ChainMetas.Walk(ctx, nil, + func(chainID string, _ uexecutortypes.ChainMeta) (bool, error) { + keys = append(keys, chainID) + return false, nil + })) + return keys +} + func TestVoteChainMetaIntegration(t *testing.T) { t.Parallel() chainId := "eip155:11155111" @@ -84,6 +96,37 @@ func TestVoteChainMetaIntegration(t *testing.T) { require.Equal(t, uint64(0), stored.LastAppliedChainHeight, "two votes should still not bootstrap the oracle") }) + t.Run("vote for an unregistered chain is rejected and writes no ChainMetas row", func(t *testing.T) { + // F-2026-18803: only eip155:11155111 is registered by the fixture. A + // bonded universal validator voting on any other chain id used to mint a + // ChainMetas row keyed by that raw id (collections.StringKey). + const unregistered = "eip155:999999999" + + testApp, ctx, uvals, vals := setupVoteChainMetaTest(t, 1) + + coreVal, err := sdk.ValAddressFromBech32(vals[0].OperatorAddress) + require.NoError(t, err) + coreAcc := sdk.AccAddress(coreVal).String() + + before := chainMetaKeys(t, ctx, testApp) + require.Empty(t, before) + + voteErr := utils.ExecVoteChainMeta(t, ctx, testApp, uvals[0], coreAcc, unregistered, 100_000_000_000, 12345) + + // Store first, deliberately: the finding is the row being written. + _, found, err := testApp.UexecutorKeeper.GetChainMeta(ctx, unregistered) + require.NoError(t, err) + require.False(t, found, "unregistered chain must not create a ChainMetas row") + require.Equal(t, before, chainMetaKeys(t, ctx, testApp), "ChainMetas must be unchanged") + + require.Error(t, voteErr) + require.Contains(t, voteErr.Error(), "is not registered") + + // The registered chain still works from the same validator. + require.NoError(t, utils.ExecVoteChainMeta(t, ctx, testApp, uvals[0], coreAcc, chainId, 100_000_000_000, 12345)) + require.Equal(t, []string{chainId}, chainMetaKeys(t, ctx, testApp)) + }) + t.Run("third fresh vote bootstraps the oracle and sets LastAppliedChainHeight to median", func(t *testing.T) { testApp, ctx, uvals, vals := setupVoteChainMetaTest(t, 3) diff --git a/x/uexecutor/keeper/chain_meta.go b/x/uexecutor/keeper/chain_meta.go index 179644685..c5777cf71 100644 --- a/x/uexecutor/keeper/chain_meta.go +++ b/x/uexecutor/keeper/chain_meta.go @@ -46,6 +46,8 @@ func (k Keeper) SetChainMeta(ctx context.Context, chainID string, chainMeta type // VoteChainMeta processes a universal validator's vote on chain metadata (gas price + chain height). // // Rules: +// 0. The observed chain must be registered in x/uregistry. Unregistered chains are +// rejected before any state is touched (F-2026-18803). // 1. Each vote is stamped with the current block time (storedAt) when it is recorded // and either inserted (new validator) or updated in place (existing validator). // 2. The oracle is bootstrapped on the first EVM write only after at least @@ -60,6 +62,21 @@ func (k Keeper) SetChainMeta(ctx context.Context, chainID string, chainMeta type // 5. Price median and chain-height median are computed independently (upper median = len/2). // 6. After a successful EVM call, LastAppliedChainHeight is updated. func (k Keeper) VoteChainMeta(ctx context.Context, universalValidator sdk.ValAddress, observedChainId string, price, blockNumber uint64) error { + // F-2026-18803: check the chain is registered before any state read/write. + // A GetChainMeta miss below *creates* the row on the cold-start path, so a + // vote for an arbitrary chain id would otherwise mint an unbounded number of + // ChainMetas keys (the raw id is the IAVL key) that every node then walks in + // AfterValidatorRemoved. Gate on *registered*, not IsChainInboundEnabled: + // chain meta also feeds gas-price quoting for outbounds, so an inbound-only + // check would starve outbound-enabled chains. + if _, err := k.uregistryKeeper.GetChainConfig(ctx, observedChainId); err != nil { + k.Logger().Warn("chain meta vote rejected: chain not registered", + "chain_id", observedChainId, + "validator", universalValidator.String(), + ) + return sdkerrors.Wrapf(err, "chain %s is not registered", observedChainId) + } + sdkCtx := sdk.UnwrapSDKContext(ctx) now := uint64(sdkCtx.BlockTime().Unix()) diff --git a/x/uexecutor/keeper/chain_meta_test.go b/x/uexecutor/keeper/chain_meta_test.go new file mode 100644 index 000000000..a51ac98a5 --- /dev/null +++ b/x/uexecutor/keeper/chain_meta_test.go @@ -0,0 +1,176 @@ +package keeper_test + +import ( + "testing" + "time" + + "cosmossdk.io/collections" + "github.com/golang/mock/gomock" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + "github.com/pushchain/push-chain-node/x/uexecutor/types" + uregistrytypes "github.com/pushchain/push-chain-node/x/uregistry/types" +) + +const ( + registeredChainID = "eip155:11155111" + unregisteredChainID = "eip155:999999999" +) + +// setupChainMetaFixture builds the keeper fixture with a deterministic block +// time so storedAt/staleness arithmetic is stable. +func setupChainMetaFixture(t *testing.T) *testFixture { + t.Helper() + f := SetupTest(t) + f.ctx = f.ctx.WithBlockTime(time.Unix(1_700_000_000, 0)) + return f +} + +// registerChain makes the uregistry mock answer GetChainConfig for chain. +func registerChain(f *testFixture, chain string) { + f.mockUregistryKeeper.EXPECT(). + GetChainConfig(gomock.Any(), chain). + Return(uregistrytypes.ChainConfig{ + Chain: chain, + VmType: uregistrytypes.VmType_EVM, + Enabled: &uregistrytypes.ChainEnabled{ + IsInboundEnabled: true, + IsOutboundEnabled: true, + }, + }, nil). + AnyTimes() +} + +// unregisterChain makes the uregistry mock report chain as absent, exactly as +// the real keeper does (collections.ErrNotFound out of ChainConfigs.Get). +func unregisterChain(f *testFixture, chain string) { + f.mockUregistryKeeper.EXPECT(). + GetChainConfig(gomock.Any(), chain). + Return(uregistrytypes.ChainConfig{}, collections.ErrNotFound). + AnyTimes() +} + +// countChainMetas returns every key currently present in the ChainMetas map. +func countChainMetas(t *testing.T, f *testFixture) []string { + t.Helper() + var keys []string + require.NoError(t, f.k.ChainMetas.Walk(f.ctx, nil, func(chainID string, _ types.ChainMeta) (bool, error) { + keys = append(keys, chainID) + return false, nil + })) + return keys +} + +// F-2026-18803: a vote for a chain that is not in x/uregistry must be rejected +// *before* the keeper writes anything. The finding is not "an error is missing" +// — it is that the GetChainMeta miss creates the row on the cold-start path, so +// the store assertion is the one that matters. +func TestVoteChainMeta_UnregisteredChain_RejectedAndStoreUnchanged(t *testing.T) { + f := setupChainMetaFixture(t) + require := require.New(t) + + unregisterChain(f, unregisteredChainID) + + before := countChainMetas(t, f) + require.Empty(before) + + err := f.k.VoteChainMeta(f.ctx, sdk.ValAddress(f.addrs[0]), unregisteredChainID, 100_000_000_000, 12345) + + // Store first, deliberately: the finding is the *row being written*, not a + // missing error. Removing the registry gate must break this assertion. + has, hasErr := f.k.ChainMetas.Has(f.ctx, unregisteredChainID) + require.NoError(hasErr) + require.False(has, "unregistered chain must not create a ChainMetas row") + require.Equal(before, countChainMetas(t, f), "ChainMetas must be unchanged") + + require.Error(err) + require.Contains(err.Error(), "is not registered") +} + +// An attacker-shaped id (long, arbitrary) must not become an IAVL key either. +func TestVoteChainMeta_UnregisteredLongChainId_WritesNoKey(t *testing.T) { + f := setupChainMetaFixture(t) + require := require.New(t) + + longID := "eip155:" + for i := 0; i < 200; i++ { + longID += "9" + } + unregisterChain(f, longID) + + err := f.k.VoteChainMeta(f.ctx, sdk.ValAddress(f.addrs[0]), longID, 1, 1) + + require.Empty(countChainMetas(t, f), "no ChainMetas key may be minted for an unregistered id") + require.Error(err) +} + +// A registered chain keeps working: the first vote is recorded and creates the +// row (this is required — bootstrap quorum can never be reached otherwise). +func TestVoteChainMeta_RegisteredChain_CreatesRow(t *testing.T) { + f := setupChainMetaFixture(t) + require := require.New(t) + + registerChain(f, registeredChainID) + + valAddr := sdk.ValAddress(f.addrs[0]) + require.NoError(f.k.VoteChainMeta(f.ctx, valAddr, registeredChainID, 100_000_000_000, 12345)) + + stored, found, err := f.k.GetChainMeta(f.ctx, registeredChainID) + require.NoError(err) + require.True(found) + require.Equal(registeredChainID, stored.ObservedChainId) + require.Equal([]string{valAddr.String()}, stored.Signers) + require.Equal([]uint64{100_000_000_000}, stored.Prices) + require.Equal([]uint64{12345}, stored.ChainHeights) + require.Equal([]uint64{uint64(f.ctx.BlockTime().Unix())}, stored.StoredAts) + // Below the bootstrap quorum the oracle is not written. + require.Equal(uint64(0), stored.LastAppliedChainHeight) + + require.Equal([]string{registeredChainID}, countChainMetas(t, f)) +} + +// Existing pre-bootstrap accumulation behaviour is unchanged for a registered +// chain: votes below chainMetaMinVotesForFirstWrite are stored, not applied. +func TestVoteChainMeta_RegisteredChain_BootstrapAccumulationUnchanged(t *testing.T) { + f := setupChainMetaFixture(t) + require := require.New(t) + + registerChain(f, registeredChainID) + + val0 := sdk.ValAddress(f.addrs[0]) + val1 := sdk.ValAddress(f.addrs[1]) + + require.NoError(f.k.VoteChainMeta(f.ctx, val0, registeredChainID, 100_000_000_000, 12345)) + require.NoError(f.k.VoteChainMeta(f.ctx, val1, registeredChainID, 200_000_000_000, 12346)) + + stored, found, err := f.k.GetChainMeta(f.ctx, registeredChainID) + require.NoError(err) + require.True(found) + require.Len(stored.Signers, 2) + require.Equal([]uint64{100_000_000_000, 200_000_000_000}, stored.Prices) + require.Equal(uint64(0), stored.LastAppliedChainHeight, "two votes must not bootstrap the oracle") + + // A re-vote from the same validator still updates in place, not appends. + require.NoError(f.k.VoteChainMeta(f.ctx, val0, registeredChainID, 400_000_000_000, 12350)) + stored, _, err = f.k.GetChainMeta(f.ctx, registeredChainID) + require.NoError(err) + require.Len(stored.Signers, 2) + require.Equal(uint64(400_000_000_000), stored.Prices[0]) + require.Equal(uint64(12350), stored.ChainHeights[0]) +} + +// Registering one chain must not implicitly admit its neighbours. +func TestVoteChainMeta_OnlyRegisteredChainAdmitted(t *testing.T) { + f := setupChainMetaFixture(t) + require := require.New(t) + + registerChain(f, registeredChainID) + unregisterChain(f, unregisteredChainID) + + valAddr := sdk.ValAddress(f.addrs[0]) + require.NoError(f.k.VoteChainMeta(f.ctx, valAddr, registeredChainID, 1, 1)) + require.Error(f.k.VoteChainMeta(f.ctx, valAddr, unregisteredChainID, 1, 1)) + + require.Equal([]string{registeredChainID}, countChainMetas(t, f)) +} diff --git a/x/uexecutor/types/msg_vote_chain_meta.go b/x/uexecutor/types/msg_vote_chain_meta.go index f4052aa6a..fc3dec952 100644 --- a/x/uexecutor/types/msg_vote_chain_meta.go +++ b/x/uexecutor/types/msg_vote_chain_meta.go @@ -10,6 +10,16 @@ var ( _ sdk.Msg = &MsgVoteChainMeta{} ) +// MaxObservedChainIdLen caps the CAIP-2 chain id carried by a chain-meta vote. +// +// F-2026-18803: the id is used verbatim as the ChainMetas map key +// (collections.StringKey), so an uncapped id is an attacker-controlled IAVL key +// of arbitrary size. CAIP-2 itself allows at most 8 (namespace) + 1 + 32 +// (reference) = 41 characters, and the longest id we actually register is +// "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1" (41). 128 leaves generous headroom +// for future namespaces while keeping the key bounded. +const MaxObservedChainIdLen = 128 + // NewMsgVoteChainMeta creates new instance of MsgVoteChainMeta func NewMsgVoteChainMeta( sender sdk.Address, @@ -49,6 +59,19 @@ func (msg *MsgVoteChainMeta) ValidateBasic() error { if msg.ObservedChainId == "" { return errors.Wrap(sdkerrors.ErrInvalidRequest, "observed_chain_id cannot be empty") } + // F-2026-18803 (stateless half): ValidateBasic has no keeper, so it cannot + // ask whether the chain is registered — Keeper.VoteChainMeta does that. What + // it can do for free at CheckTx time is bound the id's size and shape, so an + // absurd id is dropped at mempool admission rather than after a block + // commits it as a ChainMetas key. + if len(msg.ObservedChainId) > MaxObservedChainIdLen { + return errors.Wrapf(sdkerrors.ErrInvalidRequest, + "observed_chain_id exceeds %d characters (got %d)", MaxObservedChainIdLen, len(msg.ObservedChainId)) + } + if _, _, err := ParseCAIP2(msg.ObservedChainId); err != nil { + return errors.Wrap(sdkerrors.ErrInvalidRequest, + "observed_chain_id must be in CAIP-2 format :") + } if msg.Price == 0 { return errors.Wrap(sdkerrors.ErrInvalidRequest, "price must be greater than 0") } diff --git a/x/uexecutor/types/msg_vote_chain_meta_test.go b/x/uexecutor/types/msg_vote_chain_meta_test.go new file mode 100644 index 000000000..8c234c346 --- /dev/null +++ b/x/uexecutor/types/msg_vote_chain_meta_test.go @@ -0,0 +1,101 @@ +package types_test + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/pushchain/push-chain-node/x/uexecutor/types" +) + +// F-2026-18803 (stateless half): observed_chain_id becomes the ChainMetas map +// key verbatim, so ValidateBasic bounds its size and shape at CheckTx time. +func TestMsgVoteChainMeta_ValidateBasic(t *testing.T) { + const validSigner = "push1fgaewhyd9fkwtqaj9c233letwcuey6dgly9gv9" + + newMsg := func(chainID string) *types.MsgVoteChainMeta { + return &types.MsgVoteChainMeta{ + Signer: validSigner, + ObservedChainId: chainID, + Price: 100_000_000_000, + ChainHeight: 12345, + } + } + + tests := []struct { + name string + msg *types.MsgVoteChainMeta + expectErr string + }{ + { + name: "valid evm chain id", + msg: newMsg("eip155:11155111"), + }, + { + name: "valid solana chain id", + msg: newMsg("solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1"), + }, + { + name: "chain id exactly at the cap is accepted", + msg: newMsg("eip155:" + strings.Repeat("9", types.MaxObservedChainIdLen-len("eip155:"))), + }, + { + name: "chain id one byte over the cap is rejected", + msg: newMsg("eip155:" + strings.Repeat("9", types.MaxObservedChainIdLen-len("eip155:")+1)), + expectErr: "exceeds 128 characters", + }, + { + name: "oversized chain id is rejected", + msg: newMsg("eip155:" + strings.Repeat("9", 100_000)), + expectErr: "exceeds 128 characters", + }, + { + name: "non-CAIP-2 chain id is rejected", + msg: newMsg("ethereum"), + expectErr: "CAIP-2 format", + }, + { + name: "empty namespace is rejected", + msg: newMsg(":11155111"), + expectErr: "CAIP-2 format", + }, + { + name: "empty reference is rejected", + msg: newMsg("eip155:"), + expectErr: "CAIP-2 format", + }, + { + name: "empty chain id is rejected", + msg: newMsg(""), + expectErr: "observed_chain_id cannot be empty", + }, + { + name: "invalid signer is rejected", + msg: &types.MsgVoteChainMeta{Signer: "not-bech32", ObservedChainId: "eip155:1", Price: 1, ChainHeight: 1}, + expectErr: "invalid signer address", + }, + { + name: "zero price is rejected", + msg: &types.MsgVoteChainMeta{Signer: validSigner, ObservedChainId: "eip155:1", Price: 0, ChainHeight: 1}, + expectErr: "price must be greater than 0", + }, + { + name: "zero chain height is rejected", + msg: &types.MsgVoteChainMeta{Signer: validSigner, ObservedChainId: "eip155:1", Price: 1, ChainHeight: 0}, + expectErr: "chain_height must be greater than 0", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := tc.msg.ValidateBasic() + if tc.expectErr == "" { + require.NoError(t, err) + return + } + require.Error(t, err) + require.Contains(t, err.Error(), tc.expectErr) + }) + } +} From 3eed921570c4d74291065d94cd21df1d38087a4d Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Wed, 26 Aug 2026 19:42:25 +0530 Subject: [PATCH 32/60] docs: record why admin revert refuses REJECTED inbound ballots (#334) REJECTED is a quorum asserting the observation is invalid, not a stuck deposit, so refunding it would pay out against a deposit the validator set said never happened. Adds an alarm if the unreachable state ever occurs. --- .../uexecutor/revert_stuck_inbound_test.go | 39 +++++++++++++++++++ x/uexecutor/keeper/admin_revert.go | 21 ++++++++++ x/uexecutor/keeper/ballot_hooks.go | 24 ++++++++++++ 3 files changed, 84 insertions(+) diff --git a/test/integration/uexecutor/revert_stuck_inbound_test.go b/test/integration/uexecutor/revert_stuck_inbound_test.go index 826e67f9c..76f6f1530 100644 --- a/test/integration/uexecutor/revert_stuck_inbound_test.go +++ b/test/integration/uexecutor/revert_stuck_inbound_test.go @@ -307,3 +307,42 @@ func TestRevertStuckInbound_RecomputeThenRevert_E2E(t *testing.T) { require.Len(t, utx.OutboundTx, 1) require.Equal(t, uexecutortypes.TxType_INBOUND_REVERT, utx.OutboundTx[0].TxType) } + +// TestRevertStuckInbound_RejectedBallot_RefusedDeliberately pins the refusal +// documented for F-2026-18801. +// +// The terminal-routing hook files BOTH terminal-failure statuses into +// ExpiredInbounds, but the admin hatch accepts only EXPIRED. That asymmetry is +// intentional, and this test exists so a future change cannot quietly relax it: +// +// - EXPIRED is uncertainty. Quorum never formed, the deposit may be real, the +// funds may be stuck in the source gateway. Refunding is correct. +// - REJECTED is a supermajority asserting the observation is invalid. A revert +// outbound there would pay out of the TSS vault against a deposit the +// validator set concluded never happened. +// +// Note this state is unreachable for inbounds today (VoteOnInboundBallot +// hardcodes VOTE_RESULT_SUCCESS, so threshold-FAILURE never fires); the ballot is +// seeded directly here precisely because no vote path can produce it. If inbound +// negative voting is ever added, this test is the place the design decision has +// to be re-made rather than inherited. +func TestRevertStuckInbound_RejectedBallot_RefusedDeliberately(t *testing.T) { + chainApp, ctx, inbound, admin := setupRevertStuckInbound(t) + seedBallot(t, chainApp, ctx, inbound, uvalidatortypes.BallotStatus_BALLOT_STATUS_REJECTED) + + ms := uexecutorkeeper.NewMsgServerImpl(chainApp.UexecutorKeeper) + _, err := ms.RevertStuckInbound(sdk.WrapSDKContext(ctx), &uexecutortypes.MsgRevertStuckInbound{ + Signer: admin, + Inbound: inbound, + }) + require.Error(t, err, "admin revert must refuse a REJECTED ballot") + require.Contains(t, err.Error(), "admin revert requires EXPIRED", + "the refusal must name the required status so an operator knows why") + + // The refusal must be total: no UTX, and therefore no revert outbound that + // could later be signed and broadcast. + utxKey := uexecutortypes.GetInboundUniversalTxKey(*inbound) + has, hErr := chainApp.UexecutorKeeper.HasUniversalTx(ctx, utxKey) + require.NoError(t, hErr) + require.False(t, has, "a refused revert must not leave a UniversalTx behind") +} diff --git a/x/uexecutor/keeper/admin_revert.go b/x/uexecutor/keeper/admin_revert.go index d986cb52a..a1918762a 100644 --- a/x/uexecutor/keeper/admin_revert.go +++ b/x/uexecutor/keeper/admin_revert.go @@ -22,6 +22,27 @@ import ( // to EXPIRED if it isn't already (recompute auto-expires when no eligible // voters remain). // +// REJECTED is refused deliberately, not by omission (F-2026-18801). The two +// terminal-failure statuses mean opposite things: +// +// - EXPIRED is uncertainty. Quorum never formed, so we do not know whether the +// deposit happened; the funds may genuinely be sitting in the source-chain +// gateway. Refunding is the right instinct. +// - REJECTED is a supermajority of universal validators affirmatively voting +// that the observation is invalid. Building a revert outbound for that would +// pay real funds out of the TSS-controlled vault against a deposit the +// validator set concluded never occurred. +// +// It is also unreachable for inbounds today: REJECTED is only produced by +// Ballot.IsFinalizingVote's threshold-FAILURE branch, and VoteOnInboundBallot +// hardcodes VOTE_RESULT_SUCCESS - an inbound observer either votes for what it +// saw or stays silent, there is no "I assert this did not happen" vote. So an +// inbound ballot terminates PASSED or EXPIRED, never REJECTED. +// +// If a negative-vote path for inbounds is ever added, this refusal must be +// revisited as a design decision rather than silently inherited; see the +// unreachable-status warning in BallotHooks.afterInboundBallotTerminal. +// // Returns the new UTX ID and revert outbound ID for telemetry. func (k Keeper) RevertStuckInbound(ctx context.Context, inbound types.Inbound) (utxId, outboundId string, err error) { sdkCtx := sdk.UnwrapSDKContext(ctx) diff --git a/x/uexecutor/keeper/ballot_hooks.go b/x/uexecutor/keeper/ballot_hooks.go index 85e53c5e4..541e39d1b 100644 --- a/x/uexecutor/keeper/ballot_hooks.go +++ b/x/uexecutor/keeper/ballot_hooks.go @@ -134,6 +134,30 @@ func (h BallotHooks) afterInboundBallotTerminal( // All variants are terminal-failure (EXPIRED or REJECTED). Preserve // the full audit trail in ExpiredInbounds for the future escape-hatch // refund flow. + // + // Only EXPIRED is reachable here for inbounds: REJECTED comes solely from + // Ballot.IsFinalizingVote's threshold-FAILURE branch, and VoteOnInboundBallot + // hardcodes VOTE_RESULT_SUCCESS. An inbound observer votes for what it saw or + // stays silent; disagreement forks the ballot key into a separate variant + // rather than voting against one. The admin hatch (RevertStuckInbound) + // therefore accepts EXPIRED only, and refuses REJECTED deliberately - see the + // reasoning there. + // + // Shout if that ever stops being true. A REJECTED inbound reaching this point + // means someone added a negative-vote path and silently reopened a terminal + // state with no refund route (F-2026-18801). + for _, v := range entry.Variants { + if v.TerminalStatus == uvalidatortypes.BallotStatus_BALLOT_STATUS_REJECTED { + h.k.Logger().Error( + "REJECTED inbound ballot variant reached terminal routing - this should be unreachable; "+ + "inbound votes are SUCCESS-only. RevertStuckInbound will refuse this entry, leaving it "+ + "with no shipped refund path. Revisit F-2026-18801 before shipping inbound negative voting.", + "utx_key", utxKey, + "ballot_id", v.BallotId, + ) + } + } + sdkCtx := sdk.UnwrapSDKContext(ctx) return h.k.ExpiredInbounds.Set(ctx, utxKey, types.ExpiredInboundEntry{ UtxKey: utxKey, From ac3ee3a86b93fa5bb0a9aa9ca96272f049ec8016 Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Wed, 26 Aug 2026 19:42:30 +0530 Subject: [PATCH 33/60] fix: bound uexecutor uint256 string fields (F-2026-18798) (#335) Length-cap before parse, then BitLen<=256, via a shared helper applied to the six UniversalPayload numeric fields, Inbound.Amount and OutboundTx.Amount. --- x/uexecutor/types/inbound.go | 8 +- x/uexecutor/types/outbound_tx.go | 7 +- x/uexecutor/types/uint256.go | 65 +++++ x/uexecutor/types/uint256_test.go | 356 +++++++++++++++++++++++++ x/uexecutor/types/universal_payload.go | 7 +- 5 files changed, 434 insertions(+), 9 deletions(-) create mode 100644 x/uexecutor/types/uint256.go create mode 100644 x/uexecutor/types/uint256_test.go diff --git a/x/uexecutor/types/inbound.go b/x/uexecutor/types/inbound.go index c857d48ae..c9959ff13 100644 --- a/x/uexecutor/types/inbound.go +++ b/x/uexecutor/types/inbound.go @@ -3,7 +3,6 @@ package types import ( "encoding/json" "fmt" - "math/big" "strings" "cosmossdk.io/errors" @@ -128,9 +127,10 @@ func (p Inbound) ValidateForExecution() error { if strings.TrimSpace(p.Amount) == "" { return errors.Wrap(sdkerrors.ErrInvalidRequest, "amount cannot be empty") } - bi, ok := new(big.Int).SetString(p.Amount, 10) - if !ok || bi.Sign() < 0 { - return errors.Wrap(sdkerrors.ErrInvalidRequest, "amount must be a valid non-negative uint256") + // Length-capped, range-checked uint256 parse — see F-2026-18798. + bi, err := ValidateUint256String(p.Amount, "amount must be a valid non-negative uint256") + if err != nil { + return err } // Only GAS_AND_PAYLOAD and FUNDS_AND_PAYLOAD allow zero amount (skip deposit, still execute payload) if bi.Sign() == 0 && p.TxType != TxType_GAS_AND_PAYLOAD && p.TxType != TxType_FUNDS_AND_PAYLOAD { diff --git a/x/uexecutor/types/outbound_tx.go b/x/uexecutor/types/outbound_tx.go index 61ef36366..c1b8586c5 100644 --- a/x/uexecutor/types/outbound_tx.go +++ b/x/uexecutor/types/outbound_tx.go @@ -57,7 +57,12 @@ func (p OutboundTx) ValidateBasic() error { if strings.TrimSpace(p.Amount) == "" { return errors.Wrap(sdkerrors.ErrInvalidRequest, "amount cannot be empty for funds tx") } - if bi, ok := new(big.Int).SetString(p.Amount, 10); !ok || bi.Sign() <= 0 { + // Length-capped, range-checked uint256 parse — see F-2026-18798. + bi, err := ValidateUint256String(p.Amount, "amount must be a valid positive uint256") + if err != nil { + return err + } + if bi.Sign() <= 0 { return errors.Wrap(sdkerrors.ErrInvalidRequest, "amount must be a valid positive uint256") } } diff --git a/x/uexecutor/types/uint256.go b/x/uexecutor/types/uint256.go new file mode 100644 index 000000000..6efe7d229 --- /dev/null +++ b/x/uexecutor/types/uint256.go @@ -0,0 +1,65 @@ +package types + +import ( + "math/big" + + "cosmossdk.io/errors" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +const ( + // MaxUint256Bits is the width of a Solidity uint256. Anything wider cannot be + // ABI-encoded faithfully: go-ethereum's encoder truncates mod 2^256 *silently*, + // so an over-range field would make the UEA execute a value different from the + // one the user signed over. + MaxUint256Bits = 256 + + // MaxUint256DecimalLen caps the decimal string length accepted for a uint256 + // field. 2^256-1 is exactly 78 digits; 80 leaves slack for clients that + // zero-pad. It is a cheap pre-filter, not the range check — see + // ValidateUint256String. + MaxUint256DecimalLen = 80 +) + +// ValidateUint256String parses value as a base-10 uint256 and returns it. +// +// The order of the three checks is load-bearing (audit finding F-2026-18798): +// +// 1. Length cap FIRST, before big.Int.SetString. big.Int decimal parsing is +// superlinear in the digit count — as reported in the finding: 78 digits +// 18µs · 100k 24.2ms · 400k 486.6ms · 900k 3.353s. This runs in +// ValidateBasic, which BaseApp executes via validateBasicTxMsgs *before* the +// ante handler, on messages that are gasless — so the work is free and +// unmetered to the attacker, and it is paid per field. Rejecting on len() +// makes that O(1) instead of O(n²). +// +// 2. Parse, rejecting non-numeric and negative input (pre-existing behaviour). +// +// 3. BitLen() <= 256. This is the authoritative range check and the one that +// closes the silent-truncation gap. The length cap alone is NOT sufficient: +// 78 nines is only 78 characters but has BitLen 260, i.e. it fits the cap +// and still overflows uint256. +// +// errMsg is the caller's message for a malformed or negative value, so each call +// site keeps its own wording; the two range failures append a specific reason. +func ValidateUint256String(value string, errMsg string) (*big.Int, error) { + // 1. Cheap reject before the expensive parse. + if len(value) > MaxUint256DecimalLen { + return nil, errors.Wrapf(sdkerrors.ErrInvalidRequest, + "%s: length %d exceeds the maximum of %d characters", errMsg, len(value), MaxUint256DecimalLen) + } + + // 2. Parse. + bi, ok := new(big.Int).SetString(value, 10) + if !ok || bi.Sign() < 0 { + return nil, errors.Wrap(sdkerrors.ErrInvalidRequest, errMsg) + } + + // 3. Authoritative uint256 range check. + if bi.BitLen() > MaxUint256Bits { + return nil, errors.Wrapf(sdkerrors.ErrInvalidRequest, + "%s: value exceeds the uint256 range", errMsg) + } + + return bi, nil +} diff --git a/x/uexecutor/types/uint256_test.go b/x/uexecutor/types/uint256_test.go new file mode 100644 index 000000000..7aade8e4f --- /dev/null +++ b/x/uexecutor/types/uint256_test.go @@ -0,0 +1,356 @@ +package types_test + +import ( + "math/big" + "strings" + "testing" + "time" + + "github.com/pushchain/push-chain-node/x/uexecutor/types" + "github.com/stretchr/testify/require" +) + +// Regression coverage for F-2026-18798 — UExecutor ValidateBasic parsed unbounded +// decimal strings before ante, and never bounded them to uint256. +// +// Two independent defects, and therefore two independent kinds of test here: +// +// - DoS: big.Int decimal parsing is superlinear, ValidateBasic runs before the +// ante handler on a gasless message, and the cost is paid per field. The +// length cap is what makes the reject O(1) — only the *timing* assertions +// below catch its removal, because BitLen still rejects the value. +// - Silent truncation: go-ethereum's ABI encoder truncates mod 2^256 without +// erroring, so an over-range value would execute an amount different from the +// one signed. Only BitLen catches that — 78 nines fits inside the 80-char cap +// but has BitLen 260. + +const ( + // 2^256-1 — the largest legal uint256, exactly 78 digits, BitLen 256. + maxUint256Dec = "115792089237316195423570985008687907853269984665640564039457584007913129639935" + // 2^256 — one past the top, BitLen 257. + overMaxUint256Dec = "115792089237316195423570985008687907853269984665640564039457584007913129639936" + // dosDigits sizes the DoS input. big.Int decimal parsing is superlinear — + // measured on the dev machine: 78 digits 24µs · 100k 9.2ms · 400k 107ms · + // 900k 532ms · 2M 2.6s · 3M 5.7s (the finding reports 3.353s at 900k on + // slower hardware). 3M is chosen so that the two margins are both wide: the + // length cap rejects it in O(1) — nanoseconds — while a parse of it overruns + // dosBudget several times over, so removing the cap fails this test loudly. + dosDigits = 3_000_000 + // dosBudget is deliberately generous relative to the ~nanoseconds an O(1) + // length reject costs, so the assertion cannot flake on a loaded CI runner, + // while still failing hard if the length cap is removed and the superlinear + // parse comes back. + dosBudget = time.Second +) + +// nines78 is 78 characters — inside the 80-char cap — but BitLen 260. This is the +// case that proves a length cap alone is not sufficient. +func nines78() string { return strings.Repeat("9", 78) } + +func hugeDecimal() string { return strings.Repeat("9", dosDigits) } + +// baseValidInbound mirrors the valid FUNDS fixture used in inbound_test.go. +func baseValidInbound() types.Inbound { + return types.Inbound{ + SourceChain: "eip155:11155111", + TxHash: "0x123abc", + Sender: "0x000000000000000000000000000000000000dead", + Recipient: "0x000000000000000000000000000000000000beef", + Amount: "1000", + AssetAddr: "0x000000000000000000000000000000000000cafe", + LogIndex: "1", + TxType: types.TxType_FUNDS, + } +} + +func TestValidateUint256String_Bounds(t *testing.T) { + tests := []struct { + name string + value string + expectError bool + errContains string + }{ + {name: "zero", value: "0"}, + {name: "small", value: "21000"}, + {name: "one wei", value: "1"}, + {name: "typical 1e18", value: "1000000000000000000"}, + {name: "zero padded within cap", value: strings.Repeat("0", 60) + "12345"}, + { + name: "max uint256 accepted", + value: maxUint256Dec, + }, + { + name: "2^256 rejected", + value: overMaxUint256Dec, + expectError: true, + errContains: "exceeds the uint256 range", + }, + { + name: "78 nines rejected despite fitting the length cap", + value: nines78(), + expectError: true, + errContains: "exceeds the uint256 range", + }, + { + name: "negative rejected", + value: "-1", + expectError: true, + errContains: "test field must be valid", + }, + { + name: "non-numeric rejected", + value: "not-a-number", + expectError: true, + errContains: "test field must be valid", + }, + { + name: "decimal point rejected", + value: "12.34", + expectError: true, + errContains: "test field must be valid", + }, + { + name: "over length cap rejected", + value: strings.Repeat("1", types.MaxUint256DecimalLen+1), + expectError: true, + errContains: "exceeds the maximum of 80 characters", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + bi, err := types.ValidateUint256String(tc.value, "test field must be valid") + + if tc.expectError { + require.Error(t, err) + require.Contains(t, err.Error(), tc.errContains) + require.Nil(t, bi) + return + } + + require.NoError(t, err) + require.NotNil(t, bi) + expected, ok := new(big.Int).SetString(tc.value, 10) + require.True(t, ok) + require.Zero(t, bi.Cmp(expected)) + }) + } +} + +// The boundary pair, stated explicitly: max uint256 in, one past it out. +func TestValidateUint256String_BitLenBoundary(t *testing.T) { + max, ok := new(big.Int).SetString(maxUint256Dec, 10) + require.True(t, ok) + require.Equal(t, 256, max.BitLen(), "sanity: max uint256 is 256 bits") + + over, ok := new(big.Int).SetString(overMaxUint256Dec, 10) + require.True(t, ok) + require.Equal(t, 257, over.BitLen(), "sanity: 2^256 is 257 bits") + + nines, ok := new(big.Int).SetString(nines78(), 10) + require.True(t, ok) + require.Equal(t, 260, nines.BitLen(), "sanity: 78 nines is 260 bits") + require.LessOrEqual(t, len(nines78()), types.MaxUint256DecimalLen, + "sanity: 78 nines fits the length cap, so only BitLen can reject it") + + _, err := types.ValidateUint256String(maxUint256Dec, "amount must be valid") + require.NoError(t, err, "2^256-1 must be accepted") + + _, err = types.ValidateUint256String(overMaxUint256Dec, "amount must be valid") + require.Error(t, err, "2^256 must be rejected") + + _, err = types.ValidateUint256String(nines78(), "amount must be valid") + require.Error(t, err, "78 nines must be rejected") +} + +// F-2026-18798, DoS half. A multi-million-digit field must be rejected, and +// rejected fast. The timing bound is asserted first and on purpose: it is the only +// assertion that fails if the length cap is dropped, because BitLen still rejects +// the value — just after paying for the parse. +func TestUniversalPayload_ValidateBasic_RejectsHugeDecimalFast(t *testing.T) { + huge := hugeDecimal() + + // Every numeric field is reachable, and in the real message the attacker pays + // for none of them — ValidateBasic runs before ante on a gasless msg. + fields := []struct { + name string + payload types.UniversalPayload + }{ + {"value", types.UniversalPayload{To: mockHexAddress(), Value: huge}}, + {"gas_limit", types.UniversalPayload{To: mockHexAddress(), GasLimit: huge}}, + {"max_fee_per_gas", types.UniversalPayload{To: mockHexAddress(), MaxFeePerGas: huge}}, + {"max_priority_fee_per_gas", types.UniversalPayload{To: mockHexAddress(), MaxPriorityFeePerGas: huge}}, + {"nonce", types.UniversalPayload{To: mockHexAddress(), Nonce: huge}}, + {"deadline", types.UniversalPayload{To: mockHexAddress(), Deadline: huge}}, + } + + for _, f := range fields { + t.Run(f.name, func(t *testing.T) { + start := time.Now() + err := f.payload.ValidateBasic() + elapsed := time.Since(start) + + require.Error(t, err, "%s: a %d-digit value must be rejected", f.name, dosDigits) + require.Less(t, elapsed, dosBudget, + "%s: rejecting a %d-digit value took %s — the length cap must reject before big.Int parses", + f.name, dosDigits, elapsed) + require.Contains(t, err.Error(), "exceeds the maximum of 80 characters", + "%s: must be rejected on length, before the parse", f.name) + }) + } +} + +// Same DoS shape at the other two call sites. +func TestInboundAndOutbound_RejectHugeDecimalFast(t *testing.T) { + huge := hugeDecimal() + + t.Run("inbound amount", func(t *testing.T) { + ib := baseValidInbound() + ib.Amount = huge + + start := time.Now() + err := ib.ValidateForExecution() + elapsed := time.Since(start) + + require.Error(t, err) + require.Less(t, elapsed, dosBudget, "rejecting a %d-digit amount took %s", dosDigits, elapsed) + require.Contains(t, err.Error(), "exceeds the maximum of 80 characters") + }) + + t.Run("outbound amount", func(t *testing.T) { + ob := baseValidOutbound() + ob.Amount = huge + + start := time.Now() + err := ob.ValidateBasic() + elapsed := time.Since(start) + + require.Error(t, err) + require.Less(t, elapsed, dosBudget, "rejecting a %d-digit amount took %s", dosDigits, elapsed) + require.Contains(t, err.Error(), "exceeds the maximum of 80 characters") + }) +} + +// F-2026-18798, truncation half, per call site. go-ethereum packs an over-range +// value mod 2^256 without erroring, so these must never reach the encoder. +func TestUniversalPayload_ValidateBasic_Uint256Range(t *testing.T) { + tests := []struct { + name string + payload types.UniversalPayload + expectError bool + }{ + { + name: "max uint256 value accepted", + payload: types.UniversalPayload{To: mockHexAddress(), Value: maxUint256Dec}, + }, + { + name: "max uint256 on every field accepted", + payload: types.UniversalPayload{ + To: mockHexAddress(), + Value: maxUint256Dec, + GasLimit: maxUint256Dec, + MaxFeePerGas: maxUint256Dec, + MaxPriorityFeePerGas: maxUint256Dec, + Nonce: maxUint256Dec, + Deadline: maxUint256Dec, + }, + }, + { + name: "empty numeric fields still skipped", + payload: types.UniversalPayload{To: mockHexAddress()}, + }, + { + name: "2^256 value rejected", + payload: types.UniversalPayload{To: mockHexAddress(), Value: overMaxUint256Dec}, + expectError: true, + }, + { + name: "78 nines value rejected", + payload: types.UniversalPayload{To: mockHexAddress(), Value: nines78()}, + expectError: true, + }, + { + name: "78 nines gas_limit rejected", + payload: types.UniversalPayload{To: mockHexAddress(), GasLimit: nines78()}, + expectError: true, + }, + { + name: "78 nines nonce rejected", + payload: types.UniversalPayload{To: mockHexAddress(), Nonce: nines78()}, + expectError: true, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := tc.payload.ValidateBasic() + if tc.expectError { + require.Error(t, err) + require.Contains(t, err.Error(), "exceeds the uint256 range") + } else { + require.NoError(t, err) + } + }) + } +} + +func TestInbound_ValidateForExecution_Uint256Range(t *testing.T) { + tests := []struct { + name string + amount string + expectError bool + }{ + {name: "normal amount accepted", amount: "1000"}, + {name: "max uint256 accepted", amount: maxUint256Dec}, + {name: "2^256 rejected", amount: overMaxUint256Dec, expectError: true}, + {name: "78 nines rejected", amount: nines78(), expectError: true}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ib := baseValidInbound() + ib.Amount = tc.amount + + err := ib.ValidateForExecution() + if tc.expectError { + require.Error(t, err) + require.Contains(t, err.Error(), "exceeds the uint256 range") + } else { + require.NoError(t, err) + } + }) + } +} + +func TestOutboundTx_ValidateBasic_Uint256Range(t *testing.T) { + tests := []struct { + name string + amount string + expectError bool + errContains string + }{ + {name: "normal amount accepted", amount: "1000"}, + {name: "max uint256 accepted", amount: maxUint256Dec}, + {name: "2^256 rejected", amount: overMaxUint256Dec, expectError: true, errContains: "exceeds the uint256 range"}, + {name: "78 nines rejected", amount: nines78(), expectError: true, errContains: "exceeds the uint256 range"}, + // Pre-existing semantics preserved: this site requires strictly positive. + {name: "zero still rejected", amount: "0", expectError: true, errContains: "amount must be a valid positive uint256"}, + {name: "negative still rejected", amount: "-1", expectError: true, errContains: "amount must be a valid positive uint256"}, + {name: "non-numeric still rejected", amount: "abc", expectError: true, errContains: "amount must be a valid positive uint256"}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ob := baseValidOutbound() + ob.Amount = tc.amount + + err := ob.ValidateBasic() + if tc.expectError { + require.Error(t, err) + require.Contains(t, err.Error(), tc.errContains) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/x/uexecutor/types/universal_payload.go b/x/uexecutor/types/universal_payload.go index 500f8acb8..cbab3c888 100644 --- a/x/uexecutor/types/universal_payload.go +++ b/x/uexecutor/types/universal_payload.go @@ -3,7 +3,6 @@ package types import ( "encoding/hex" "encoding/json" - "math/big" "strings" "cosmossdk.io/errors" @@ -50,9 +49,9 @@ func (p UniversalPayload) ValidateBasic() error { for fieldName, value := range uintFields { if value != "" { - bi, ok := new(big.Int).SetString(value, 10) - if !ok || bi.Sign() < 0 { - return errors.Wrapf(sdkerrors.ErrInvalidRequest, "%s must be a valid unsigned integer", fieldName) + // Length-capped, range-checked uint256 parse — see F-2026-18798. + if _, err := ValidateUint256String(value, fieldName+" must be a valid unsigned integer"); err != nil { + return err } } } From a8a997ec514e65124d13f0862ded658fd968566e Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Wed, 26 Aug 2026 19:42:35 +0530 Subject: [PATCH 34/60] fix: refuse RecomputeBallotQuorum on non-2/3 ballot types (#336) TSS_KEY ballots use 100% of the DKLS participant set, not 2/3+1; recompute would rewrite both the threshold and the eligible voters. Gate on an explicit default-deny allow-list (INBOUND_TX, OUTBOUND_TX, FUND_MIGRATION). --- .../recompute_ballot_quorum_test.go | 254 ++++++++++++++++++ x/uvalidator/keeper/ballot.go | 45 ++++ 2 files changed, 299 insertions(+) diff --git a/test/integration/uvalidator/recompute_ballot_quorum_test.go b/test/integration/uvalidator/recompute_ballot_quorum_test.go index 7966d9d2e..df66e477d 100644 --- a/test/integration/uvalidator/recompute_ballot_quorum_test.go +++ b/test/integration/uvalidator/recompute_ballot_quorum_test.go @@ -290,3 +290,257 @@ func TestRecomputeBallotQuorum_AdminAuth_AcceptsAdmin(t *testing.T) { require.NotNil(t, resp) require.Equal(t, int64(3), resp.NewEligibleCount) } + +// --------------------------------------------------------------------------- +// F-2026-18793 — RecomputeBallotQuorum is type-aware (default-deny allow-list) +// --------------------------------------------------------------------------- + +// makeTypedBallot builds a PENDING ballot of an arbitrary observation type with +// an explicit threshold, so TSS-style ballots (100% of the DKLS participant set, +// not 2/3+1) can be constructed exactly as x/utss creates them. +func makeTypedBallot( + t *testing.T, + ballotID string, + ballotType uvalidatortypes.BallotObservationType, + eligibleVoters []string, + votes []uvalidatortypes.VoteResult, + threshold int64, +) uvalidatortypes.Ballot { + t.Helper() + if len(votes) == 0 { + votes = make([]uvalidatortypes.VoteResult, len(eligibleVoters)) + } + return uvalidatortypes.Ballot{ + Id: ballotID, + BallotType: ballotType, + EligibleVoters: eligibleVoters, + Votes: votes, + VotingThreshold: threshold, + Status: uvalidatortypes.BallotStatus_BALLOT_STATUS_PENDING, + BlockHeightCreated: 1, + BlockHeightExpiry: 100_000_000, + } +} + +// Hacken rec 3 — the headline case. A TSS key ballot is created with a 100% +// quorum over the DKLS participants (votesNeeded = len(Participants)). An admin +// recompute must be refused outright: it would drop the threshold from 5 to +// (2*3)/3+1 = 3 AND swap the participant list for the live UV set, manufacturing +// an attestation the DKLS run never produced. +// +// The state assertions run BEFORE the error assertion on purpose: require.Error +// aborts the test on failure, so an error-first ordering would never reach the +// checks that catch a refusal which had already mutated state. +func TestRecomputeBallotQuorum_TSSKeyBallot_Refused_StateUnchanged(t *testing.T) { + chainApp, ctx, validators := setupQueryTest(t, 5) + for _, v := range validators { + setUVStatus(t, chainApp, ctx, v, uvalidatortypes.UVStatus_UV_STATUS_ACTIVE) + } + + // 5 DKLS participants, 100% quorum (threshold 5), 4 of 5 votes cast. + participants := make([]string, len(validators)) + for i, v := range validators { + participants[i] = v.OperatorAddress + } + votes := []uvalidatortypes.VoteResult{ + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_NOT_YET_VOTED, + } + ballot := makeTypedBallot(t, "tss-key-ballot", uvalidatortypes.BallotObservationType_BALLOT_OBSERVATION_TYPE_TSS_KEY, participants, votes, 5) + require.NoError(t, chainApp.UvalidatorKeeper.Ballots.Set(ctx, ballot.Id, ballot)) + require.NoError(t, chainApp.UvalidatorKeeper.ActiveBallotIDs.Set(ctx, ballot.Id)) + + // Make the live UV set genuinely differ from the participant set, so an + // unguarded recompute would visibly rewrite both threshold and eligibles. + for i := 0; i < 2; i++ { + v := validators[i] + v.Status = stakingtypes.Unbonded + require.NoError(t, chainApp.StakingKeeper.SetValidator(ctx, v)) + } + eligibleNow, err := chainApp.UvalidatorKeeper.GetEligibleVoters(ctx) + require.NoError(t, err) + require.Len(t, eligibleNow, 3, "live UV set must differ from the DKLS participant set for this test to bite") + + _, _, _, _, _, recomputeErr := chainApp.UvalidatorKeeper.RecomputeBallotQuorum(ctx, ballot.Id) + + // --- state first --- + after, getErr := chainApp.UvalidatorKeeper.Ballots.Get(ctx, ballot.Id) + require.NoError(t, getErr) + require.Equal(t, int64(5), after.VotingThreshold, + "TSS threshold must stay at 100% of participants; a recompute would have set it to 3") + require.Equal(t, participants, after.EligibleVoters, + "the DKLS participant set must be byte-for-byte unchanged; a recompute would have swapped in the live UV set") + require.Equal(t, votes, after.Votes, "votes must be untouched") + require.Equal(t, uvalidatortypes.BallotStatus_BALLOT_STATUS_PENDING, after.Status, + "refused recompute must not finalize the ballot") + + // --- then the refusal itself --- + require.Error(t, recomputeErr, "recompute on a TSS_KEY ballot must be refused") + require.Contains(t, recomputeErr.Error(), "cannot be recomputed") + require.Contains(t, recomputeErr.Error(), "TSS_KEY") +} + +// The three allow-listed types are created from GetEligibleVoters() with a +// (2*N)/3+1 threshold, so recompute reproduces creation exactly for them and +// must keep working unchanged. +func TestRecomputeBallotQuorum_AllowedTypes_StillRecompute(t *testing.T) { + allowed := []uvalidatortypes.BallotObservationType{ + uvalidatortypes.BallotObservationType_BALLOT_OBSERVATION_TYPE_INBOUND_TX, + uvalidatortypes.BallotObservationType_BALLOT_OBSERVATION_TYPE_OUTBOUND_TX, + uvalidatortypes.BallotObservationType_BALLOT_OBSERVATION_TYPE_FUND_MIGRATION, + } + + for _, bt := range allowed { + t.Run(bt.String(), func(t *testing.T) { + chainApp, ctx, validators := setupQueryTest(t, 5) + for _, v := range validators { + setUVStatus(t, chainApp, ctx, v, uvalidatortypes.UVStatus_UV_STATUS_ACTIVE) + } + + voterStrs := make([]string, len(validators)) + for i, v := range validators { + voterStrs[i] = v.OperatorAddress + } + ballot := makeTypedBallot(t, "allowed-"+bt.String(), bt, voterStrs, nil, 4) + require.NoError(t, chainApp.UvalidatorKeeper.Ballots.Set(ctx, ballot.Id, ballot)) + require.NoError(t, chainApp.UvalidatorKeeper.ActiveBallotIDs.Set(ctx, ballot.Id)) + + // Strand 3 → 2 eligible → threshold (2*2)/3+1 = 2. + for i := 0; i < 3; i++ { + v := validators[i] + v.Status = stakingtypes.Unbonded + require.NoError(t, chainApp.StakingKeeper.SetValidator(ctx, v)) + } + + oldEligible, newEligible, oldThreshold, newThreshold, newStatus, err := + chainApp.UvalidatorKeeper.RecomputeBallotQuorum(ctx, ballot.Id) + require.NoError(t, err, "%s must remain recomputable", bt.String()) + require.Equal(t, int64(5), oldEligible) + require.Equal(t, int64(2), newEligible) + require.Equal(t, int64(4), oldThreshold) + require.Equal(t, int64(2), newThreshold) + require.Equal(t, uvalidatortypes.BallotStatus_BALLOT_STATUS_PENDING, newStatus) + + updated, err := chainApp.UvalidatorKeeper.Ballots.Get(ctx, ballot.Id) + require.NoError(t, err) + require.Equal(t, int64(2), updated.VotingThreshold) + require.Len(t, updated.EligibleVoters, 2) + require.Equal(t, []string{validators[3].OperatorAddress, validators[4].OperatorAddress}, updated.EligibleVoters) + }) + } +} + +// Pins the default-deny: UNSPECIFIED and a type value the switch has never seen +// are both refused. A future ballot type (e.g. READ_RESULT, which lands with the +// read-state branch) therefore inherits a refusal instead of silently inheriting +// the 2/3+1 formula. +func TestRecomputeBallotQuorum_UnrecognisedType_Refused(t *testing.T) { + cases := []struct { + name string + ballotType uvalidatortypes.BallotObservationType + }{ + {"unspecified", uvalidatortypes.BallotObservationType_BALLOT_OBSERVATION_TYPE_UNSPECIFIED}, + {"future type not on the allow-list", uvalidatortypes.BallotObservationType(99)}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + chainApp, ctx, validators := setupQueryTest(t, 3) + for _, v := range validators { + setUVStatus(t, chainApp, ctx, v, uvalidatortypes.UVStatus_UV_STATUS_ACTIVE) + } + + voterStrs := []string{validators[0].OperatorAddress, validators[1].OperatorAddress, validators[2].OperatorAddress} + ballot := makeTypedBallot(t, "unknown-type-"+tc.name, tc.ballotType, voterStrs, nil, 7) + require.NoError(t, chainApp.UvalidatorKeeper.Ballots.Set(ctx, ballot.Id, ballot)) + require.NoError(t, chainApp.UvalidatorKeeper.ActiveBallotIDs.Set(ctx, ballot.Id)) + + _, _, _, _, _, recomputeErr := chainApp.UvalidatorKeeper.RecomputeBallotQuorum(ctx, ballot.Id) + + // State first — see the TSS test for why the ordering matters. + after, getErr := chainApp.UvalidatorKeeper.Ballots.Get(ctx, ballot.Id) + require.NoError(t, getErr) + require.Equal(t, int64(7), after.VotingThreshold, "threshold must be untouched by a refused recompute") + require.Equal(t, voterStrs, after.EligibleVoters, "eligible voters must be untouched by a refused recompute") + require.Equal(t, uvalidatortypes.BallotStatus_BALLOT_STATUS_PENDING, after.Status) + + require.Error(t, recomputeErr) + require.Contains(t, recomputeErr.Error(), "cannot be recomputed") + }) + } +} + +// The PENDING-only guard still runs before the type check, so a non-pending +// ballot reports the status problem rather than the type problem. +func TestRecomputeBallotQuorum_StatusGuardRunsBeforeTypeGuard(t *testing.T) { + chainApp, ctx, validators := setupQueryTest(t, 3) + + voterStrs := []string{validators[0].OperatorAddress, validators[1].OperatorAddress, validators[2].OperatorAddress} + ballot := makeTypedBallot(t, "finalized-tss-ballot", uvalidatortypes.BallotObservationType_BALLOT_OBSERVATION_TYPE_TSS_KEY, voterStrs, nil, 3) + ballot.Status = uvalidatortypes.BallotStatus_BALLOT_STATUS_PASSED + require.NoError(t, chainApp.UvalidatorKeeper.Ballots.Set(ctx, ballot.Id, ballot)) + + _, _, _, _, _, err := chainApp.UvalidatorKeeper.RecomputeBallotQuorum(ctx, ballot.Id) + require.Error(t, err) + require.Contains(t, err.Error(), "not pending") +} + +// The zero-eligible → EXPIRED path is reached only by allow-listed types; a +// refused type is refused outright and is NOT auto-expired as a side effect. +func TestRecomputeBallotQuorum_ZeroEligible_AllowedExpires_RefusedDoesNot(t *testing.T) { + t.Run("allowed type still auto-expires", func(t *testing.T) { + chainApp, ctx, validators := setupQueryTest(t, 3) + for _, v := range validators { + setUVStatus(t, chainApp, ctx, v, uvalidatortypes.UVStatus_UV_STATUS_ACTIVE) + } + + voterStrs := []string{validators[0].OperatorAddress, validators[1].OperatorAddress, validators[2].OperatorAddress} + ballot := makeTypedBallot(t, "zero-eligible-fund-migration", uvalidatortypes.BallotObservationType_BALLOT_OBSERVATION_TYPE_FUND_MIGRATION, voterStrs, nil, 3) + require.NoError(t, chainApp.UvalidatorKeeper.Ballots.Set(ctx, ballot.Id, ballot)) + require.NoError(t, chainApp.UvalidatorKeeper.ActiveBallotIDs.Set(ctx, ballot.Id)) + + for _, v := range validators { + v.Status = stakingtypes.Unbonded + require.NoError(t, chainApp.StakingKeeper.SetValidator(ctx, v)) + } + + _, newEligible, _, _, newStatus, err := chainApp.UvalidatorKeeper.RecomputeBallotQuorum(ctx, ballot.Id) + require.NoError(t, err) + require.Equal(t, int64(0), newEligible) + require.Equal(t, uvalidatortypes.BallotStatus_BALLOT_STATUS_EXPIRED, newStatus) + + updated, _ := chainApp.UvalidatorKeeper.Ballots.Get(ctx, ballot.Id) + require.Equal(t, uvalidatortypes.BallotStatus_BALLOT_STATUS_EXPIRED, updated.Status) + }) + + t.Run("refused type stays pending", func(t *testing.T) { + chainApp, ctx, validators := setupQueryTest(t, 3) + for _, v := range validators { + setUVStatus(t, chainApp, ctx, v, uvalidatortypes.UVStatus_UV_STATUS_ACTIVE) + } + + voterStrs := []string{validators[0].OperatorAddress, validators[1].OperatorAddress, validators[2].OperatorAddress} + ballot := makeTypedBallot(t, "zero-eligible-tss", uvalidatortypes.BallotObservationType_BALLOT_OBSERVATION_TYPE_TSS_KEY, voterStrs, nil, 3) + require.NoError(t, chainApp.UvalidatorKeeper.Ballots.Set(ctx, ballot.Id, ballot)) + require.NoError(t, chainApp.UvalidatorKeeper.ActiveBallotIDs.Set(ctx, ballot.Id)) + + for _, v := range validators { + v.Status = stakingtypes.Unbonded + require.NoError(t, chainApp.StakingKeeper.SetValidator(ctx, v)) + } + + _, _, _, _, _, recomputeErr := chainApp.UvalidatorKeeper.RecomputeBallotQuorum(ctx, ballot.Id) + + after, getErr := chainApp.UvalidatorKeeper.Ballots.Get(ctx, ballot.Id) + require.NoError(t, getErr) + require.Equal(t, uvalidatortypes.BallotStatus_BALLOT_STATUS_PENDING, after.Status, + "a refused recompute must not expire the ballot as a side effect") + require.Equal(t, voterStrs, after.EligibleVoters) + require.Equal(t, int64(3), after.VotingThreshold) + + require.Error(t, recomputeErr) + }) +} diff --git a/x/uvalidator/keeper/ballot.go b/x/uvalidator/keeper/ballot.go index bafeb3115..2966987bc 100644 --- a/x/uvalidator/keeper/ballot.go +++ b/x/uvalidator/keeper/ballot.go @@ -229,6 +229,29 @@ func (k Keeper) GetAdmin(ctx context.Context) (string, error) { // downstream UVs must re-vote on the same ballot to trigger finalize+execute // via the normal flow. // +// Only the ballot types on the allow-list below may be recomputed. Recompute +// rebuilds the eligible set from the live UV set and applies the 2/3+1 +// threshold, so it is only correct for ballots that were created that way — +// INBOUND_TX, OUTBOUND_TX and FUND_MIGRATION all call GetEligibleVoters() with +// a (2*N)/3+1 threshold at creation, so recompute reproduces creation exactly +// for them. +// +// TSS_KEY ballots are not such ballots and are refused. They are created with a +// 100% quorum over the DKLS participant set (votesNeeded = len(Participants), +// EligibleVoters = Participants; see x/utss/keeper/voting.go), so a recompute +// would rewrite *both* halves: dropping the threshold from N to (2*N)/3+1 and +// replacing the participants with whoever is a live UV now. The eligible-set +// rewrite is the worse half — it can make validators who never took part in +// that DKLS run eligible to attest its key. A TSS ballot whose participants +// changed is not a quorum problem: the DKLS run itself is invalid, and a +// recomputed threshold would manufacture an attestation nobody made. The fix is +// a fresh keygen round, not a lower bar. +// +// The list is default-deny on purpose. A new ballot type inherits a refusal +// rather than silently inheriting a formula that may not apply to it — which is +// exactly how the TSS case went unnoticed. Adding a type here must be a +// deliberate act, after checking how that type is created. +// // Returns the old/new counts and threshold for the response. func (k Keeper) RecomputeBallotQuorum(ctx context.Context, ballotID string) ( oldEligibleCount, newEligibleCount, oldThreshold, newThreshold int64, @@ -244,6 +267,28 @@ func (k Keeper) RecomputeBallotQuorum(ctx context.Context, ballotID string) ( return 0, 0, 0, 0, 0, fmt.Errorf("ballot %s is not pending (status=%s); only pending ballots can be recomputed", ballotID, ballot.Status.String()) } + // Default-deny allow-list of recomputable ballot types. See the doc comment: + // everything not listed here — TSS_KEY, UNSPECIFIED, and any type added + // later — is refused rather than silently recomputed with a formula that may + // not describe how it was created. + switch ballot.BallotType { + case types.BallotObservationType_BALLOT_OBSERVATION_TYPE_INBOUND_TX, + types.BallotObservationType_BALLOT_OBSERVATION_TYPE_OUTBOUND_TX, + types.BallotObservationType_BALLOT_OBSERVATION_TYPE_FUND_MIGRATION: + // Created from GetEligibleVoters() with a (2*N)/3+1 threshold — recompute + // reproduces creation exactly. + default: + return 0, 0, 0, 0, 0, fmt.Errorf( + "ballot %s has type %s, which cannot be recomputed: recompute rebuilds the eligible-voter "+ + "set from the live universal-validator set and applies the 2/3+1 threshold, which only "+ + "reproduces how INBOUND_TX, OUTBOUND_TX and FUND_MIGRATION ballots are created; a %s "+ + "ballot is created differently, so recomputing it would change what the ballot attests. "+ + "Resolve it through its owning module instead (a TSS_KEY ballot whose participants "+ + "changed needs a fresh keygen round, not a lower threshold)", + ballotID, ballot.BallotType.String(), ballot.BallotType.String(), + ) + } + oldEligibleCount = int64(len(ballot.EligibleVoters)) oldThreshold = ballot.VotingThreshold From 64b8aff3778f4cc9a65cdc22fb4472b1eefa3b98 Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Wed, 26 Aug 2026 19:42:40 +0530 Subject: [PATCH 35/60] fix(uvalidator): index pending ballots by expiry height and sweep them in EndBlock (#337) Removes the O(active-set) IAVL scan CreateBallot ran on every ballot creation. --- .../uvalidator/ballot_voting_test.go | 119 +++++++- x/uvalidator/abci.go | 22 ++ x/uvalidator/keeper/ballot.go | 110 ++++++-- x/uvalidator/keeper/ballot_test.go | 264 +++++++++++++++++- x/uvalidator/keeper/genesis_test.go | 63 +++++ x/uvalidator/keeper/keeper.go | 24 +- x/uvalidator/keeper/voting.go | 6 + x/uvalidator/module.go | 14 + x/uvalidator/types/keys.go | 7 + 9 files changed, 590 insertions(+), 39 deletions(-) diff --git a/test/integration/uvalidator/ballot_voting_test.go b/test/integration/uvalidator/ballot_voting_test.go index 64b0be3a0..ff1b55b2e 100644 --- a/test/integration/uvalidator/ballot_voting_test.go +++ b/test/integration/uvalidator/ballot_voting_test.go @@ -4,12 +4,15 @@ import ( "fmt" "testing" + "cosmossdk.io/core/appmodule" + storetypes "cosmossdk.io/store/types" sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/stretchr/testify/require" "github.com/pushchain/push-chain-node/app" utils "github.com/pushchain/push-chain-node/test/utils" + uvalidatorkeeper "github.com/pushchain/push-chain-node/x/uvalidator/keeper" uvalidatortypes "github.com/pushchain/push-chain-node/x/uvalidator/types" ) @@ -102,7 +105,7 @@ func TestIntegration_CreateBallot(t *testing.T) { require.Equal(t, int64(60), ballot.BlockHeightExpiry) }) - t.Run("creating a new ballot expires stale active ballots", func(t *testing.T) { + t.Run("creating a new ballot does NOT expire stale active ballots", func(t *testing.T) { chainApp, ctx, validators := setupBallotTest(t, 2) k := chainApp.UvalidatorKeeper voters := voterAddrs(t, validators) @@ -114,16 +117,24 @@ func TestIntegration_CreateBallot(t *testing.T) { voters, 1, 1) require.NoError(t, err) - // Advance height past the expiry so the next CreateBallot triggers cleanup + // Advance past the old ballot's expiry and create another one. Creation + // no longer scans the active set — that walked every active ballot and + // paid an IAVL read each time. Expiry is the EndBlocker's job now. ctx = ctx.WithBlockHeight(10) _, err = k.CreateBallot(ctx, "new-ballot", uvalidatortypes.BallotObservationType_BALLOT_OBSERVATION_TYPE_INBOUND_TX, voters, 1, 100) require.NoError(t, err) - // Old ballot should now be expired old, err := k.GetBallot(ctx, "old-ballot") require.NoError(t, err) + require.Equal(t, uvalidatortypes.BallotStatus_BALLOT_STATUS_PENDING, old.Status, + "CreateBallot must not sweep; the EndBlocker owns expiry") + + // The sweep is what expires it. + require.NoError(t, k.ExpireBallotsBeforeHeight(ctx, ctx.BlockHeight())) + old, err = k.GetBallot(ctx, "old-ballot") + require.NoError(t, err) require.Equal(t, uvalidatortypes.BallotStatus_BALLOT_STATUS_EXPIRED, old.Status) }) } @@ -963,3 +974,105 @@ func TestIntegration_IsTombstonedUniversalValidator(t *testing.T) { require.Contains(t, err.Error(), "invalid signer address") }) } + +// ─── EndBlocker expiry sweep ───────────────────────────────────────────────── + +// endBlockCtx moves ctx to the given height and attaches a block gas meter. +// The test fixture's context has none, and x/feemarket's EndBlocker — which the +// module manager runs before x/uvalidator's — errors out without one. +func endBlockCtx(ctx sdk.Context, height int64) sdk.Context { + return ctx.WithBlockHeight(height).WithBlockGasMeter(storetypes.NewInfiniteGasMeter()) +} + +// TestIntegration_UvalidatorEndBlockerRuns is the guard against a +// silently-never-called EndBlock. The sweep only fires if x/uvalidator is BOTH +// listed in SetOrderEndBlockers AND satisfies appmodule.HasEndBlocker — listing +// alone is not enough, the module manager skips modules that do not implement +// the interface. This drives the app's real EndBlocker and observes the effect. +func TestIntegration_UvalidatorEndBlockerRuns(t *testing.T) { + t.Run("expires due ballots and leaves the rest alone", func(t *testing.T) { + chainApp, ctx, validators := setupBallotTest(t, 3) + k := chainApp.UvalidatorKeeper + voters := voterAddrs(t, validators) + + // ctx starts at height 1 → expiry heights 2 and 1001. + _, err := k.CreateBallot(ctx, "eb-due", + uvalidatortypes.BallotObservationType_BALLOT_OBSERVATION_TYPE_INBOUND_TX, + voters, 2, 1) + require.NoError(t, err) + + _, err = k.CreateBallot(ctx, "eb-future", + uvalidatortypes.BallotObservationType_BALLOT_OBSERVATION_TYPE_INBOUND_TX, + voters, 2, 1000) + require.NoError(t, err) + + // Creation must not have swept anything. + due, err := k.GetBallot(ctx, "eb-due") + require.NoError(t, err) + require.Equal(t, uvalidatortypes.BallotStatus_BALLOT_STATUS_PENDING, due.Status) + + // Advance the block and run the app's real EndBlocker chain. + ctx = endBlockCtx(ctx, 5) + _, err = chainApp.EndBlocker(ctx) + require.NoError(t, err) + + // THE assertion: if x/uvalidator's EndBlock never fires, this fails. + due, err = k.GetBallot(ctx, "eb-due") + require.NoError(t, err) + require.Equal(t, uvalidatortypes.BallotStatus_BALLOT_STATUS_EXPIRED, due.Status, + "the x/uvalidator EndBlocker did not run the ballot expiry sweep") + + future, err := k.GetBallot(ctx, "eb-future") + require.NoError(t, err) + require.Equal(t, uvalidatortypes.BallotStatus_BALLOT_STATUS_PENDING, future.Status, + "a not-yet-due ballot must survive the sweep") + }) + + t.Run("caps a large backlog at MaxExpiriesPerBlock per block", func(t *testing.T) { + chainApp, ctx, validators := setupBallotTest(t, 3) + k := chainApp.UvalidatorKeeper + voters := voterAddrs(t, validators) + + const extra = 3 + total := uvalidatorkeeper.MaxExpiriesPerBlock + extra + for i := 0; i < total; i++ { + _, err := k.CreateBallot(ctx, fmt.Sprintf("eb-cap-%03d", i), + uvalidatortypes.BallotObservationType_BALLOT_OBSERVATION_TYPE_INBOUND_TX, + voters, 2, 1) + require.NoError(t, err) + } + + countExpired := func(ctx sdk.Context) int { + n := 0 + require.NoError(t, k.ExpiredBallotIDs.Walk(ctx, nil, func(string) (bool, error) { + n++ + return false, nil + })) + return n + } + + ctx = endBlockCtx(ctx, 5) + _, err := chainApp.EndBlocker(ctx) + require.NoError(t, err) + require.Equal(t, uvalidatorkeeper.MaxExpiriesPerBlock, countExpired(ctx), + "one block must expire at most MaxExpiriesPerBlock ballots") + + // The leftovers are carried to the next block, not lost. + ctx = endBlockCtx(ctx, 6) + _, err = chainApp.EndBlocker(ctx) + require.NoError(t, err) + require.Equal(t, total, countExpired(ctx), + "the backlog remainder must be swept by the following block") + }) + + t.Run("module is wired into the EndBlocker ordering", func(t *testing.T) { + chainApp, _, _ := setupBallotTest(t, 1) + + require.Contains(t, chainApp.ModuleManager.OrderEndBlockers, uvalidatortypes.ModuleName, + "x/uvalidator must be listed in SetOrderEndBlockers or its EndBlock never runs") + + _, ok := chainApp.ModuleManager.Modules[uvalidatortypes.ModuleName].(appmodule.HasEndBlocker) + require.True(t, ok, + "x/uvalidator must implement appmodule.HasEndBlocker; the module manager skips modules that do not") + }) +} diff --git a/x/uvalidator/abci.go b/x/uvalidator/abci.go index 4a0117db1..5034d5513 100644 --- a/x/uvalidator/abci.go +++ b/x/uvalidator/abci.go @@ -65,6 +65,28 @@ func BeginBlocker(ctx sdk.Context, uvalidatorKeeper keeper.Keeper) error { return nil } +// EndBlocker runs the ballot expiry sweep once per block. +// +// Expiry used to be driven off ballot *creation*, which meant it both scanned +// the whole active set on a consensus hot path and only ran when inbound +// traffic happened to arrive. Running it here decouples expiry from traffic. +// +// A failed sweep is logged and swallowed rather than returned: expiry must +// never halt the chain, and the work is idempotent — anything not expired this +// block is still in PendingByExpiry for the next one. +func EndBlocker(ctx sdk.Context, uvalidatorKeeper keeper.Keeper) error { + defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker) + + if err := uvalidatorKeeper.ExpireBallotsBeforeHeight(ctx, ctx.BlockHeight()); err != nil { + ctx.Logger().Error("uvalidator: ballot expiry sweep failed", + "height", ctx.BlockHeight(), + "err", err.Error(), + ) + } + + return nil +} + // AllocateTokens performs reward and fee distribution to all validators based // on the F1 fee distribution specification. func AllocateTokens(ctx context.Context, totalPreviousPower int64, bondedVotes []abci.VoteInfo, k keeper.Keeper) error { diff --git a/x/uvalidator/keeper/ballot.go b/x/uvalidator/keeper/ballot.go index 2966987bc..da901e361 100644 --- a/x/uvalidator/keeper/ballot.go +++ b/x/uvalidator/keeper/ballot.go @@ -2,13 +2,21 @@ package keeper import ( "context" + "errors" "fmt" + "cosmossdk.io/collections" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/pushchain/push-chain-node/x/uvalidator/types" ) +// MaxExpiriesPerBlock bounds how many ballots a single expiry sweep may +// transition. It keeps the per-block cost of the sweep constant even if a +// large backlog of ballots comes due at once; anything left over stays in +// PendingByExpiry and is picked up by the next block's sweep. +const MaxExpiriesPerBlock = 50 + // CreateBallot creates a new ballot with the given parameters, stores it, and marks it as active. func (k Keeper) CreateBallot( ctx context.Context, @@ -33,10 +41,10 @@ func (k Keeper) CreateBallot( "block_height", blockHeight, ) - // First, expire any old ballots before this height - if err := k.ExpireBallotsBeforeHeight(ctx, blockHeight); err != nil { - return types.Ballot{}, err - } + // NOTE: creation deliberately does NOT sweep expired ballots. That scan used + // to run here on every create and walked the whole active set, paying an + // IAVL read + unmarshal per entry. Expiry now runs once per block in the + // module EndBlocker off the PendingByExpiry index instead. // Create ballot ballot := types.NewBallot( @@ -57,6 +65,9 @@ func (k Keeper) CreateBallot( if err := k.ActiveBallotIDs.Set(ctx, ballot.Id); err != nil { return types.Ballot{}, err } + if err := k.indexPending(ctx, ballot.Id, ballot.BlockHeightExpiry); err != nil { + return types.Ballot{}, err + } k.Logger().Debug("ballot created and marked active", "ballot_id", ballot.Id, @@ -100,9 +111,16 @@ func (k Keeper) SetBallot(ctx context.Context, ballot types.Ballot) error { return k.Ballots.Set(ctx, ballot.Id, ballot) } -// DeleteBallot removes a ballot and its ID from all collections +// DeleteBallot removes a ballot and its ID from all collections. +// +// The PendingByExpiry row is keyed by the ballot's expiry height, so the record +// must be read before it is removed. A missing record means there is nothing to +// unindex (DeleteBallot stays idempotent on absent IDs). func (k Keeper) DeleteBallot(ctx context.Context, id string) error { k.Logger().Debug("deleting ballot", "ballot_id", id) + if ballot, err := k.Ballots.Get(ctx, id); err == nil { + _ = k.unindexPending(ctx, id, ballot.BlockHeightExpiry) + } if err := k.Ballots.Remove(ctx, id); err != nil { return err } @@ -137,6 +155,9 @@ func (k Keeper) MarkBallotExpired(ctx context.Context, id string) error { if err := k.ActiveBallotIDs.Remove(ctx, id); err != nil { return err } + if err := k.unindexPending(ctx, id, ballot.BlockHeightExpiry); err != nil { + return err + } if err := k.ExpiredBallotIDs.Set(ctx, id); err != nil { return err } @@ -175,6 +196,9 @@ func (k Keeper) MarkBallotFinalized(ctx context.Context, id string, status types if err := k.ActiveBallotIDs.Remove(ctx, id); err != nil { return err } + if err := k.unindexPending(ctx, id, ballot.BlockHeightExpiry); err != nil { + return err + } if err := k.FinalizedBallotIDs.Set(ctx, id); err != nil { return err } @@ -362,45 +386,83 @@ func (k Keeper) RecomputeBallotQuorum(ctx context.Context, ballotID string) ( return oldEligibleCount, newEligibleCount, oldThreshold, newThreshold, types.BallotStatus_BALLOT_STATUS_PENDING, nil } -// ExpireBallotsBeforeHeight checks active ballots and marks expired ones. -// It uses a two-phase approach: first collect IDs to expire, then mutate, -// to avoid modifying the ActiveBallotIDs collection during iteration. +// indexPending adds the (expiryHeight, ballotID) row that mirrors an entry in +// ActiveBallotIDs. Every ActiveBallotIDs.Set must be paired with this call, or +// the ballot becomes invisible to the expiry sweep. +func (k Keeper) indexPending(ctx context.Context, id string, expiryHeight int64) error { + return k.PendingByExpiry.Set(ctx, collections.Join(expiryHeight, id)) +} + +// unindexPending drops the (expiryHeight, ballotID) row. Every +// ActiveBallotIDs.Remove must be paired with this call, or the sweep keeps +// re-visiting a ballot that is no longer active. +func (k Keeper) unindexPending(ctx context.Context, id string, expiryHeight int64) error { + return k.PendingByExpiry.Remove(ctx, collections.Join(expiryHeight, id)) +} + +// ExpireBallotsBeforeHeight marks every active ballot whose expiry height is at +// or below currentHeight as expired, up to MaxExpiriesPerBlock per call. +// +// It ranges over the PendingByExpiry index rather than ActiveBallotIDs. Because +// collections.Pair orders by its first component, iteration ends at the first +// row past currentHeight: ballots that are not yet due are never visited, and +// no Ballots.Get is needed to decide whether a ballot is due — the expiry +// height IS the key. Only ballots actually being expired are ever loaded. +// +// It keeps the original two-phase shape: IDs are collected while the iterator +// is open and mutated only after it is closed. Mutating a collection mid- +// iteration skips entries at best and panics at worst. func (k Keeper) ExpireBallotsBeforeHeight(ctx context.Context, currentHeight int64) error { - iter, err := k.ActiveBallotIDs.Iterate(ctx, nil) + // NewPrefixUntilPairRange ends at the prefix-end of currentHeight, so the + // range is inclusive of currentHeight — matching the `<=` expiry semantics. + rng := collections.NewPrefixUntilPairRange[int64, string](currentHeight) + + iter, err := k.PendingByExpiry.Iterate(ctx, rng) if err != nil { return err } - // Phase 1: collect IDs to expire - var toExpire []string + // Phase 1: collect the due index keys, bounded by MaxExpiriesPerBlock. + var due []collections.Pair[int64, string] for ; iter.Valid(); iter.Next() { - id, err := iter.Key() + key, err := iter.Key() if err != nil { iter.Close() return err } - ballot, err := k.Ballots.Get(ctx, id) - if err != nil { - iter.Close() - return err - } - - if ballot.BlockHeightExpiry <= currentHeight { - toExpire = append(toExpire, id) + due = append(due, key) + if len(due) >= MaxExpiriesPerBlock { + break } } // Close iterator explicitly before mutation phase to release the IAVL snapshot iter.Close() - if len(toExpire) > 0 { - k.Logger().Debug("expiring stale ballots", "count", len(toExpire), "current_height", currentHeight) + if len(due) == 0 { + return nil } + k.Logger().Debug("expiring stale ballots", "count", len(due), "current_height", currentHeight) + // Phase 2: expire collected ballots (safe — iterator is closed) - for _, id := range toExpire { - if err := k.MarkBallotExpired(ctx, id); err != nil { + for _, key := range due { + id := key.K2() + err := k.MarkBallotExpired(ctx, id) + switch { + case err == nil: + case errors.Is(err, collections.ErrNotFound): + // Defensive: an index row whose ballot record no longer exists must + // not wedge the sweep every block. Drop the orphan row and continue. + k.Logger().Warn("dropping orphaned ballot expiry index entry", + "ballot_id", id, + "expiry_height", key.K1(), + ) + if rErr := k.PendingByExpiry.Remove(ctx, key); rErr != nil { + return rErr + } + default: return err } } diff --git a/x/uvalidator/keeper/ballot_test.go b/x/uvalidator/keeper/ballot_test.go index f6432003d..0c525bcc9 100644 --- a/x/uvalidator/keeper/ballot_test.go +++ b/x/uvalidator/keeper/ballot_test.go @@ -4,10 +4,46 @@ import ( "fmt" "testing" + "cosmossdk.io/collections" + "github.com/pushchain/push-chain-node/x/uvalidator/keeper" "github.com/pushchain/push-chain-node/x/uvalidator/types" "github.com/stretchr/testify/require" ) +const inboundBallot = types.BallotObservationType_BALLOT_OBSERVATION_TYPE_INBOUND_TX + +// pendingIndexed reports whether the (expiryHeight, ballotID) row exists in the +// PendingByExpiry index. +func pendingIndexed(t *testing.T, f *testFixture, id string, expiryHeight int64) bool { + t.Helper() + has, err := f.k.PendingByExpiry.Has(f.ctx, collections.Join(expiryHeight, id)) + require.NoError(t, err) + return has +} + +// pendingIndexIDs returns every ballot ID currently carried by the expiry index. +func pendingIndexIDs(t *testing.T, f *testFixture) []string { + t.Helper() + ids := []string{} + require.NoError(t, f.k.PendingByExpiry.Walk(f.ctx, nil, + func(key collections.Pair[int64, string]) (bool, error) { + ids = append(ids, key.K2()) + return false, nil + })) + return ids +} + +// expiredCount returns how many ballots sit in ExpiredBallotIDs. +func expiredCount(t *testing.T, f *testFixture) int { + t.Helper() + n := 0 + require.NoError(t, f.k.ExpiredBallotIDs.Walk(f.ctx, nil, func(string) (bool, error) { + n++ + return false, nil + })) + return n +} + func TestCreateAndGetBallot(t *testing.T) { f := SetupTest(t) require := require.New(t) @@ -150,32 +186,48 @@ func TestExpireBallotsBeforeHeight(t *testing.T) { require.Equal(types.BallotStatus_BALLOT_STATUS_PENDING, got2.Status) } -func TestCreateBallot_ExpiresOldOnCreate(t *testing.T) { +// Creation must no longer sweep expired ballots: that scan walked the whole +// active set on a consensus hot path. Expiry moved to the module EndBlocker. +func TestCreateBallot_DoesNotExpireOnCreate(t *testing.T) { f := SetupTest(t) require := require.New(t) - // Create a ballot that expires quickly (expiry = 1 block) + // Create a ballot that comes due quickly (expiry = 1 block) oldBallot, err := f.k.CreateBallot(f.ctx, "old", types.BallotObservationType_BALLOT_OBSERVATION_TYPE_INBOUND_TX, []string{"v1"}, 1, 1) require.NoError(err) - // Manually simulate advancing block height + // Advance well past the old ballot's expiry height f.ctx = f.ctx.WithBlockHeight(oldBallot.BlockHeightCreated + 5) - // Now create a NEW ballot → should trigger expiry cleanup of the old one + // Creating a NEW ballot must NOT expire the due one — no scan on create. newBallot, err := f.k.CreateBallot(f.ctx, "new", types.BallotObservationType_BALLOT_OBSERVATION_TYPE_INBOUND_TX, []string{"v2"}, 1, 10) require.NoError(err) - - // New ballot must be created fine require.Equal("new", newBallot.Id) - // Old ballot should now be expired got, err := f.k.GetBallot(f.ctx, "old") require.NoError(err) + require.Equal(types.BallotStatus_BALLOT_STATUS_PENDING, got.Status, + "CreateBallot must not expire ballots; expiry belongs to the EndBlocker sweep") + + has, err := f.k.ActiveBallotIDs.Has(f.ctx, "old") + require.NoError(err) + require.True(has, "due ballot must stay active until the sweep runs") + + // The sweep — not creation — is what expires it. + require.NoError(f.k.ExpireBallotsBeforeHeight(f.ctx, f.ctx.BlockHeight())) + + got, err = f.k.GetBallot(f.ctx, "old") + require.NoError(err) require.Equal(types.BallotStatus_BALLOT_STATUS_EXPIRED, got.Status) + + // The not-yet-due ballot survives the sweep. + gotNew, err := f.k.GetBallot(f.ctx, "new") + require.NoError(err) + require.Equal(types.BallotStatus_BALLOT_STATUS_PENDING, gotNew.Status) } func TestCreateBallot_NoExpiryTriggered(t *testing.T) { @@ -207,7 +259,7 @@ func TestCreateBallot_NoExpiryTriggered(t *testing.T) { require.Equal("newer", got2.Id) } -func TestCreateBallot_ExpiresMultipleOld(t *testing.T) { +func TestCreateBallot_DoesNotExpireMultipleOldOnCreate(t *testing.T) { f := SetupTest(t) require := require.New(t) @@ -225,19 +277,30 @@ func TestCreateBallot_ExpiresMultipleOld(t *testing.T) { // Advance height beyond both expiries f.ctx = f.ctx.WithBlockHeight(b2.BlockHeightCreated + 5) - // Create fresh ballot (triggers cleanup) + // Create fresh ballot — must NOT trigger any cleanup _, err = f.k.CreateBallot(f.ctx, "fresh", types.BallotObservationType_BALLOT_OBSERVATION_TYPE_INBOUND_TX, []string{"v3"}, 1, 5) require.NoError(err) - // Both old ballots should now be expired + // Both due ballots must still be pending — creation does not sweep got1, err := f.k.GetBallot(f.ctx, b1.Id) require.NoError(err) - require.Equal(types.BallotStatus_BALLOT_STATUS_EXPIRED, got1.Status) + require.Equal(types.BallotStatus_BALLOT_STATUS_PENDING, got1.Status) got2, err := f.k.GetBallot(f.ctx, b2.Id) require.NoError(err) + require.Equal(types.BallotStatus_BALLOT_STATUS_PENDING, got2.Status) + + // The sweep expires them. + require.NoError(f.k.ExpireBallotsBeforeHeight(f.ctx, f.ctx.BlockHeight())) + + got1, err = f.k.GetBallot(f.ctx, b1.Id) + require.NoError(err) + require.Equal(types.BallotStatus_BALLOT_STATUS_EXPIRED, got1.Status) + + got2, err = f.k.GetBallot(f.ctx, b2.Id) + require.NoError(err) require.Equal(types.BallotStatus_BALLOT_STATUS_EXPIRED, got2.Status) } @@ -401,3 +464,182 @@ func TestExpireBallotsBeforeHeight_EmptySet(t *testing.T) { err := f.k.ExpireBallotsBeforeHeight(f.ctx, 100) require.NoError(err) } + +// ─── PendingByExpiry index ─────────────────────────────────────────────────── + +// TestPendingByExpiryIndex_MirrorsEveryActiveSetWriter is the guard against a +// missed mirror site. Every writer of ActiveBallotIDs must write PendingByExpiry +// too; a miss makes the index drift from reality, which either hides a ballot +// from the sweep forever or lets the sweep act on a ballot that is no longer +// active. Each subtest asserts the index state FIRST, so the assertion that +// catches the missing mirror runs before anything else can abort the subtest. +func TestPendingByExpiryIndex_MirrorsEveryActiveSetWriter(t *testing.T) { + t.Run("CreateBallot indexes under the expiry height", func(t *testing.T) { + f := SetupTest(t) + + b, err := f.k.CreateBallot(f.ctx, "idx-create", inboundBallot, []string{"v1"}, 1, 7) + require.NoError(t, err) + + require.True(t, pendingIndexed(t, f, "idx-create", b.BlockHeightExpiry), + "CreateBallot must mirror ActiveBallotIDs into PendingByExpiry") + require.Equal(t, []string{"idx-create"}, pendingIndexIDs(t, f)) + + // The row must be keyed by the expiry height, not the creation height — + // that is the whole point of the index. + require.NotEqual(t, b.BlockHeightCreated, b.BlockHeightExpiry) + require.False(t, pendingIndexed(t, f, "idx-create", b.BlockHeightCreated), + "index must be keyed by expiry height, not creation height") + }) + + t.Run("MarkBallotExpired unindexes", func(t *testing.T) { + f := SetupTest(t) + + b, err := f.k.CreateBallot(f.ctx, "idx-expire", inboundBallot, []string{"v1"}, 1, 3) + require.NoError(t, err) + require.NoError(t, f.k.MarkBallotExpired(f.ctx, b.Id)) + + require.False(t, pendingIndexed(t, f, b.Id, b.BlockHeightExpiry), + "MarkBallotExpired must remove the PendingByExpiry row") + require.Empty(t, pendingIndexIDs(t, f)) + + // A leaked row would be re-processed by the sweep every single block, + // permanently consuming part of the per-block budget. + require.NoError(t, f.k.ExpireBallotsBeforeHeight(f.ctx, b.BlockHeightExpiry+1)) + require.Empty(t, pendingIndexIDs(t, f)) + }) + + t.Run("MarkBallotFinalized unindexes", func(t *testing.T) { + f := SetupTest(t) + + b, err := f.k.CreateBallot(f.ctx, "idx-final", inboundBallot, []string{"v1"}, 1, 3) + require.NoError(t, err) + require.NoError(t, f.k.MarkBallotFinalized(f.ctx, b.Id, types.BallotStatus_BALLOT_STATUS_PASSED)) + + require.False(t, pendingIndexed(t, f, b.Id, b.BlockHeightExpiry), + "MarkBallotFinalized must remove the PendingByExpiry row") + + // Behavioural consequence of a leaked row: the sweep would reach a + // finalized ballot and overwrite PASSED with EXPIRED. + require.NoError(t, f.k.ExpireBallotsBeforeHeight(f.ctx, b.BlockHeightExpiry+1)) + got, err := f.k.GetBallot(f.ctx, b.Id) + require.NoError(t, err) + require.Equal(t, types.BallotStatus_BALLOT_STATUS_PASSED, got.Status, + "a stale index row let the sweep overwrite a finalized ballot") + }) + + t.Run("DeleteBallot unindexes", func(t *testing.T) { + f := SetupTest(t) + + b, err := f.k.CreateBallot(f.ctx, "idx-delete", inboundBallot, []string{"v1"}, 1, 3) + require.NoError(t, err) + require.NoError(t, f.k.DeleteBallot(f.ctx, b.Id)) + + require.False(t, pendingIndexed(t, f, b.Id, b.BlockHeightExpiry), + "DeleteBallot must remove the PendingByExpiry row") + require.Empty(t, pendingIndexIDs(t, f)) + + // Deleting an absent ballot stays a no-op. + require.NoError(t, f.k.DeleteBallot(f.ctx, b.Id)) + require.Empty(t, pendingIndexIDs(t, f)) + }) + + t.Run("VoteOnBallot create path indexes the new ballot", func(t *testing.T) { + f := SetupTest(t) + + b, _, isNew, err := f.k.VoteOnBallot(f.ctx, "idx-vote", inboundBallot, + "v1", types.VoteResult_VOTE_RESULT_SUCCESS, + []string{"v1", "v2"}, 2, 9) + require.NoError(t, err) + require.True(t, isNew) + + require.True(t, pendingIndexed(t, f, "idx-vote", b.BlockHeightExpiry), + "the vote-driven create path must leave the expiry index populated") + }) +} + +// TestExpireBallotsBeforeHeight_OnlyDueRowsAreTouched pins the range semantics: +// rows past currentHeight are neither expired nor dropped from the index. +func TestExpireBallotsBeforeHeight_OnlyDueRowsAreTouched(t *testing.T) { + f := SetupTest(t) + require := require.New(t) + + // Created at height 0 → expiry heights 3, 10 and exactly 5. + due, err := f.k.CreateBallot(f.ctx, "due", inboundBallot, []string{"v1"}, 1, 3) + require.NoError(err) + atBoundary, err := f.k.CreateBallot(f.ctx, "at-boundary", inboundBallot, []string{"v1"}, 1, 5) + require.NoError(err) + future, err := f.k.CreateBallot(f.ctx, "future", inboundBallot, []string{"v1"}, 1, 10) + require.NoError(err) + + require.NoError(f.k.ExpireBallotsBeforeHeight(f.ctx, 5)) + + // The not-yet-due row must survive in the index for a later block. + require.Equal([]string{"future"}, pendingIndexIDs(t, f)) + require.True(pendingIndexed(t, f, future.Id, future.BlockHeightExpiry)) + + gotFuture, err := f.k.GetBallot(f.ctx, future.Id) + require.NoError(err) + require.Equal(types.BallotStatus_BALLOT_STATUS_PENDING, gotFuture.Status) + + // Expiry is inclusive of currentHeight (`<=` semantics). + for _, id := range []string{due.Id, atBoundary.Id} { + got, err := f.k.GetBallot(f.ctx, id) + require.NoError(err) + require.Equal(types.BallotStatus_BALLOT_STATUS_EXPIRED, got.Status, "ballot %s should be expired", id) + } +} + +// TestExpireBallotsBeforeHeight_CapsAtMaxExpiriesPerBlock proves the per-block +// bound holds and that the leftovers are carried in the index to the next block. +func TestExpireBallotsBeforeHeight_CapsAtMaxExpiriesPerBlock(t *testing.T) { + f := SetupTest(t) + require := require.New(t) + + const extra = 7 + total := keeper.MaxExpiriesPerBlock + extra + + // All due at height 1, i.e. the whole backlog comes due at once. + for i := 0; i < total; i++ { + _, err := f.k.CreateBallot(f.ctx, fmt.Sprintf("cap-%03d", i), inboundBallot, []string{"v1"}, 1, 1) + require.NoError(err) + } + require.Len(pendingIndexIDs(t, f), total) + + // First sweep: exactly MaxExpiriesPerBlock, never the whole backlog. + require.NoError(f.k.ExpireBallotsBeforeHeight(f.ctx, 100)) + require.Equal(keeper.MaxExpiriesPerBlock, expiredCount(t, f), + "a single sweep must expire at most MaxExpiriesPerBlock ballots") + require.Len(pendingIndexIDs(t, f), extra, + "the remainder must stay in the index for the next block") + + // Second sweep drains the rest. + require.NoError(f.k.ExpireBallotsBeforeHeight(f.ctx, 100)) + require.Equal(total, expiredCount(t, f)) + require.Empty(pendingIndexIDs(t, f)) +} + +// TestExpireBallotsBeforeHeight_OrphanedIndexRow covers the defensive path: an +// index row whose ballot record is gone must be dropped instead of wedging the +// sweep on every subsequent block. +func TestExpireBallotsBeforeHeight_OrphanedIndexRow(t *testing.T) { + f := SetupTest(t) + require := require.New(t) + + b, err := f.k.CreateBallot(f.ctx, "orphan", inboundBallot, []string{"v1"}, 1, 1) + require.NoError(err) + + // Drop the record behind the index row's back. + require.NoError(f.k.Ballots.Remove(f.ctx, b.Id)) + require.True(pendingIndexed(t, f, b.Id, b.BlockHeightExpiry)) + + // A healthy ballot queued behind the orphan must still get expired. + good, err := f.k.CreateBallot(f.ctx, "good", inboundBallot, []string{"v1"}, 1, 2) + require.NoError(err) + + require.NoError(f.k.ExpireBallotsBeforeHeight(f.ctx, 100)) + + require.Empty(pendingIndexIDs(t, f), "the orphaned row must be dropped, not retried forever") + gotGood, err := f.k.GetBallot(f.ctx, good.Id) + require.NoError(err) + require.Equal(types.BallotStatus_BALLOT_STATUS_EXPIRED, gotGood.Status) +} diff --git a/x/uvalidator/keeper/genesis_test.go b/x/uvalidator/keeper/genesis_test.go index b052f32ba..0f025f0c4 100755 --- a/x/uvalidator/keeper/genesis_test.go +++ b/x/uvalidator/keeper/genesis_test.go @@ -3,6 +3,7 @@ package keeper_test import ( "testing" + "cosmossdk.io/collections" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/pushchain/push-chain-node/x/uvalidator/types" "github.com/stretchr/testify/require" @@ -64,3 +65,65 @@ func TestGenesisExportImportRoundTrip(t *testing.T) { require.Equal(t, len(exported.ActiveBallotIds), len(reExported.ActiveBallotIds)) require.Equal(t, exported.UniversalValidators[0].Key, reExported.UniversalValidators[0].Key) } + +// TestInitGenesisRebuildsPendingByExpiry pins the reason this change needs no +// state migration: the expiry index is derived state that InitGenesis rebuilds +// from the ballots it just restored. +func TestInitGenesisRebuildsPendingByExpiry(t *testing.T) { + f := SetupTest(t) + f.k.InitGenesis(f.ctx, &types.GenesisState{Params: types.Params{Admin: f.addrs[0].String()}}) + + genesis := &types.GenesisState{ + Params: types.Params{Admin: f.addrs[0].String()}, + Ballots: []types.Ballot{ + {Id: "g-1", Status: types.BallotStatus_BALLOT_STATUS_PENDING, BlockHeightExpiry: 42}, + {Id: "g-2", Status: types.BallotStatus_BALLOT_STATUS_PENDING, BlockHeightExpiry: 7}, + {Id: "g-3", Status: types.BallotStatus_BALLOT_STATUS_PASSED, BlockHeightExpiry: 9}, + }, + ActiveBallotIds: []string{"g-1", "g-2"}, + FinalizedBallotIds: []string{"g-3"}, + } + + f2 := SetupTest(t) + require.NoError(t, f2.k.InitGenesis(f2.ctx, genesis)) + + // Both active ballots are indexed under their own expiry heights. + for _, tc := range []struct { + id string + expiry int64 + }{{"g-1", 42}, {"g-2", 7}} { + has, err := f2.k.PendingByExpiry.Has(f2.ctx, collections.Join(tc.expiry, tc.id)) + require.NoError(t, err) + require.True(t, has, "InitGenesis must rebuild the expiry index for %s", tc.id) + } + + // The finalized ballot is not active, so it must not be indexed. + has, err := f2.k.PendingByExpiry.Has(f2.ctx, collections.Join(int64(9), "g-3")) + require.NoError(t, err) + require.False(t, has, "only active ballots belong in the expiry index") + + // The rebuilt index drives the sweep: g-2 (expiry 7) is due at height 10, + // g-1 (expiry 42) is not. + require.NoError(t, f2.k.ExpireBallotsBeforeHeight(f2.ctx, 10)) + + got, err := f2.k.GetBallot(f2.ctx, "g-2") + require.NoError(t, err) + require.Equal(t, types.BallotStatus_BALLOT_STATUS_EXPIRED, got.Status) + + got, err = f2.k.GetBallot(f2.ctx, "g-1") + require.NoError(t, err) + require.Equal(t, types.BallotStatus_BALLOT_STATUS_PENDING, got.Status) +} + +// TestInitGenesisRejectsDanglingActiveBallotID: an active ballot id with no +// ballot record cannot be indexed, so it is surfaced rather than silently +// dropped into an index that no longer matches the active set. +func TestInitGenesisRejectsDanglingActiveBallotID(t *testing.T) { + f := SetupTest(t) + err := f.k.InitGenesis(f.ctx, &types.GenesisState{ + Params: types.Params{Admin: f.addrs[0].String()}, + ActiveBallotIds: []string{"ghost"}, + }) + require.Error(t, err) + require.Contains(t, err.Error(), "ghost") +} diff --git a/x/uvalidator/keeper/keeper.go b/x/uvalidator/keeper/keeper.go index 2345d4382..c2603181c 100755 --- a/x/uvalidator/keeper/keeper.go +++ b/x/uvalidator/keeper/keeper.go @@ -2,6 +2,7 @@ package keeper import ( "context" + "fmt" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" @@ -33,6 +34,14 @@ type Keeper struct { ExpiredBallotIDs collections.KeySet[string] // set of ballot IDs that have expired (not yet pruned) FinalizedBallotIDs collections.KeySet[string] // set of ballot IDs that are PASSED or REJECTED + // PendingByExpiry is a secondary index over ActiveBallotIDs keyed by + // (expiryHeight, ballotID). collections.Pair orders by the first component, + // so the expiry sweep can range over [0, currentHeight] and stop at the + // first entry beyond it — ballots that are not due are never visited, and + // the height being part of the key means no Ballots.Get is needed to decide + // whether a ballot is due. Every ActiveBallotIDs writer must mirror here. + PendingByExpiry collections.KeySet[collections.Pair[int64, string]] + StakingKeeper types.StakingKeeper SlashingKeeper types.SlashingKeeper UtssKeeper types.UtssKeeper @@ -98,6 +107,10 @@ func NewKeeper( sb, types.FinalizedBallotIDsKey, types.FinalizedBallotIDsName, collections.StringKey, ), + PendingByExpiry: collections.NewKeySet( + sb, types.PendingByExpiryKey, types.PendingByExpiryName, + collections.PairKeyCodec(collections.Int64Key, collections.StringKey), + ), authority: authority, StakingKeeper: stakingKeeper, @@ -144,11 +157,20 @@ func (k *Keeper) InitGenesis(ctx context.Context, data *types.GenesisState) erro } } - // Restore ActiveBallotIDs + // Restore ActiveBallotIDs, rebuilding the PendingByExpiry index from the + // ballots restored just above. This is why no state migration is needed for + // a chain that starts from (or is re-imported through) genesis. for _, id := range data.ActiveBallotIds { + ballot, err := k.Ballots.Get(ctx, id) + if err != nil { + return fmt.Errorf("active ballot id %q has no matching ballot record in genesis: %w", id, err) + } if err := k.ActiveBallotIDs.Set(ctx, id); err != nil { return err } + if err := k.PendingByExpiry.Set(ctx, collections.Join(ballot.BlockHeightExpiry, id)); err != nil { + return err + } } // Restore ExpiredBallotIDs diff --git a/x/uvalidator/keeper/voting.go b/x/uvalidator/keeper/voting.go index c0adb980b..adecb7cbc 100644 --- a/x/uvalidator/keeper/voting.go +++ b/x/uvalidator/keeper/voting.go @@ -181,6 +181,12 @@ func (k Keeper) VoteOnBallot( if err != nil { return ballot, false, false, errors.Wrap(err, "Error while voting on the ballot") } + // Mirror the active-set write into the expiry index. CreateBallot has + // already written both; both writes are idempotent, and pairing them + // here keeps the invariant local to every ActiveBallotIDs.Set site. + if err := k.indexPending(ctx, id, ballot.BlockHeightExpiry); err != nil { + return ballot, false, false, errors.Wrap(err, "Error while voting on the ballot") + } } ballot, err = k.AddVoteToBallot(ctx, ballot, voter, voteResult) diff --git a/x/uvalidator/module.go b/x/uvalidator/module.go index 408cfbd6f..155e2b18d 100755 --- a/x/uvalidator/module.go +++ b/x/uvalidator/module.go @@ -10,6 +10,7 @@ import ( abci "github.com/cometbft/cometbft/abci/types" "cosmossdk.io/client/v2/autocli" + "cosmossdk.io/core/appmodule" errorsmod "cosmossdk.io/errors" "github.com/cosmos/cosmos-sdk/client" @@ -32,6 +33,13 @@ var ( _ module.AppModuleGenesis = AppModule{} _ module.AppModule = AppModule{} + // The module manager only calls BeginBlock/EndBlock on modules that satisfy + // these interfaces — being listed in SetOrderBeginBlockers/EndBlockers is + // necessary but not sufficient. These assertions fail the build if a + // signature drifts and the hook silently stops firing. + _ appmodule.HasBeginBlocker = AppModule{} + _ appmodule.HasEndBlocker = AppModule{} + _ autocli.HasAutoCLIConfig = AppModule{} ) @@ -172,3 +180,9 @@ func (a AppModule) BeginBlock(ctx context.Context) error { return BeginBlocker(sdkCtx, a.keeper) } + +func (a AppModule) EndBlock(ctx context.Context) error { + sdkCtx := sdk.UnwrapSDKContext(ctx) + + return EndBlocker(sdkCtx, a.keeper) +} diff --git a/x/uvalidator/types/keys.go b/x/uvalidator/types/keys.go index 9f0fb92da..1b9ef7d30 100755 --- a/x/uvalidator/types/keys.go +++ b/x/uvalidator/types/keys.go @@ -49,6 +49,13 @@ var ( // FinalizedBallotIDsName is the name of the finalized ballot IDs set. FinalizedBallotIDsName = "finalized_ballot_ids" + + // PendingByExpiryKey is the key for the (expiryHeight, ballotID) index over + // the active ballot set. Next free prefix after FinalizedBallotIDsKey(6). + PendingByExpiryKey = collections.NewPrefix(7) + + // PendingByExpiryName is the name of the pending-by-expiry index. + PendingByExpiryName = "pending_ballots_by_expiry" ) const ( From 5b50e762f5340aa1bf988d59934694a65e5e0f19 Mon Sep 17 00:00:00 2001 From: Aman Gupta Date: Wed, 26 Aug 2026 19:43:57 +0530 Subject: [PATCH 36/60] fix(svm): stop creating the recipient ATA; the gateway meters that rent (#338) --- universalClient/chains/svm/tx_builder.go | 55 +------ universalClient/chains/svm/tx_builder_test.go | 146 ++++++++++-------- 2 files changed, 85 insertions(+), 116 deletions(-) diff --git a/universalClient/chains/svm/tx_builder.go b/universalClient/chains/svm/tx_builder.go index cd6698be1..9df910ecd 100644 --- a/universalClient/chains/svm/tx_builder.go +++ b/universalClient/chains/svm/tx_builder.go @@ -922,8 +922,7 @@ func (tb *TxBuilder) BuildOutboundTransaction( // --- Assemble the Solana transaction --- // Instructions in order: // 1. SetComputeUnitLimit — tells the runtime how many compute units to allocate - // 2. (SPL only) CreateAssociatedTokenAccount — creates recipient ATA if it doesn't exist - // 3. The actual gateway instruction (withdraw/execute/revert) + // 2. The actual gateway instruction (withdraw/execute/revert) gatewayInstruction := solana.NewInstruction( tb.gatewayAddress, @@ -936,19 +935,9 @@ func (tb *TxBuilder) BuildOutboundTransaction( computeLimitIx := tb.buildSetComputeUnitLimitInstruction(defaultComputeUnitLimit) // Build the instruction list. - instructions := []solana.Instruction{computeLimitIx} - - needsRecipientATA := (instructionID == 1 && !isNative) || ((instructionID == 3 || instructionID == 4) && !isNative) - if needsRecipientATA { - createATAInstruction := tb.buildCreateATAIdempotentInstruction( - relayerKeypair.PublicKey(), - recipientPubkey, - mintPubkey, - ) - instructions = append(instructions, createATAInstruction) - } - - instructions = append(instructions, gatewayInstruction) + // The recipient ATA is created by the gateway, which meters the rent into + // gas_used. Creating it here left that cost outside the metered path. + instructions := []solana.Instruction{computeLimitIx, gatewayInstruction} // Get a recent blockhash — Solana uses this instead of nonces for transaction expiry. // Transactions expire after ~60-90 seconds if not confirmed. @@ -1242,14 +1231,7 @@ func (tb *TxBuilder) BuildRefRouteTransactions( refInstruction := solana.NewInstruction(tb.gatewayAddress, refAccounts, refInstructionData) computeLimitIx := tb.buildSetComputeUnitLimitInstruction(defaultComputeUnitLimit) - instructions := []solana.Instruction{computeLimitIx} - needsRecipientATA := !isNative && false // execute mode (id=2) doesn't create recipient ATA; gateway handles cea_ata internally - if needsRecipientATA { - instructions = append(instructions, tb.buildCreateATAIdempotentInstruction( - relayerKeypair.PublicKey(), recipientPubkey, mintPubkey, - )) - } - instructions = append(instructions, refInstruction) + instructions := []solana.Instruction{computeLimitIx, refInstruction} refOpts := []solana.TransactionOption{solana.TransactionPayer(relayerKeypair.PublicKey())} addressTables, altErr := tb.fetchAddressTables(ctx, mintPubkey, isNative) @@ -2335,33 +2317,6 @@ func (tb *TxBuilder) buildCloseStoredIxDataAccounts(caller, storedIxDataPDA, exe } } -// buildCreateATAIdempotentInstruction creates the recipient's ATA if absent -// (no-op if present). Required for SPL withdraw/revert flows because the -// gateway validates the recipient ATA exists but does NOT create it. Relayer -// pays the ~0.002 SOL rent, reimbursed via gas_fee. -func (tb *TxBuilder) buildCreateATAIdempotentInstruction( - payer solana.PublicKey, - owner solana.PublicKey, - mint solana.PublicKey, -) solana.Instruction { - ata, _, _ := solana.FindProgramAddress( - [][]byte{owner.Bytes(), solana.TokenProgramID.Bytes(), mint.Bytes()}, - solana.SPLAssociatedTokenAccountProgramID, - ) - - accounts := []*solana.AccountMeta{ - {PublicKey: payer, IsWritable: true, IsSigner: true}, - {PublicKey: ata, IsWritable: true, IsSigner: false}, - {PublicKey: owner, IsWritable: false, IsSigner: false}, - {PublicKey: mint, IsWritable: false, IsSigner: false}, - {PublicKey: solana.SystemProgramID, IsWritable: false, IsSigner: false}, - {PublicKey: solana.TokenProgramID, IsWritable: false, IsSigner: false}, - } - - // ATA program instruction discriminator: 0 = Create (fails if exists), 1 = CreateIdempotent. - return solana.NewInstruction(solana.SPLAssociatedTokenAccountProgramID, accounts, []byte{1}) -} - // ============================================================================= // Fund Migration (Unsupported on SVM) // SVM funds are held by the gateway program in PDA-controlled vaults, not by TSS diff --git a/universalClient/chains/svm/tx_builder_test.go b/universalClient/chains/svm/tx_builder_test.go index 5f923d7f6..c4696d916 100644 --- a/universalClient/chains/svm/tx_builder_test.go +++ b/universalClient/chains/svm/tx_builder_test.go @@ -1692,64 +1692,6 @@ func TestNewTxBuilder_ChainConfig(t *testing.T) { }) } -func TestBuildCreateATAIdempotentInstruction(t *testing.T) { - builder := newTestBuilder(t) - payer := solana.NewWallet().PublicKey() - owner := solana.NewWallet().PublicKey() - mint := solana.NewWallet().PublicKey() - - ix := builder.buildCreateATAIdempotentInstruction(payer, owner, mint) - - t.Run("program ID is ATA program", func(t *testing.T) { - expected := solana.MustPublicKeyFromBase58("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL") - assert.Equal(t, expected, ix.ProgramID()) - }) - - t.Run("has 6 accounts in correct order", func(t *testing.T) { - accounts := ix.Accounts() - require.Len(t, accounts, 6) - - // payer (signer, writable) - assert.Equal(t, payer, accounts[0].PublicKey) - assert.True(t, accounts[0].IsSigner) - assert.True(t, accounts[0].IsWritable) - - // ATA (writable, derived deterministically) - ataProgramID := solana.MustPublicKeyFromBase58("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL") - expectedATA, _, _ := solana.FindProgramAddress( - [][]byte{owner.Bytes(), solana.TokenProgramID.Bytes(), mint.Bytes()}, - ataProgramID, - ) - assert.Equal(t, expectedATA, accounts[1].PublicKey) - assert.True(t, accounts[1].IsWritable) - assert.False(t, accounts[1].IsSigner) - - // owner - assert.Equal(t, owner, accounts[2].PublicKey) - assert.False(t, accounts[2].IsWritable) - - // mint - assert.Equal(t, mint, accounts[3].PublicKey) - assert.False(t, accounts[3].IsWritable) - - // system program - assert.Equal(t, solana.SystemProgramID, accounts[4].PublicKey) - - // token program - assert.Equal(t, solana.TokenProgramID, accounts[5].PublicKey) - }) - - t.Run("instruction data is [1] for CreateIdempotent", func(t *testing.T) { - data, err := ix.Data() - require.NoError(t, err) - assert.Equal(t, []byte{1}, data) - }) -} - -// ============================================================================= -// Ref-Finalize Route Tests -// ============================================================================= - func TestDeriveStoredIxDataPDA(t *testing.T) { builder := newTestBuilder(t) subTxID := makeTxID(0xAB) @@ -2456,14 +2398,7 @@ func buildAndSimulateRescue(t *testing.T, rpcClient *RPCClient, builder *TxBuild gatewayIx := solana.NewInstruction(builder.gatewayAddress, accounts, instructionData) computeLimitIx := builder.buildSetComputeUnitLimitInstruction(400000) - instructions := []solana.Instruction{computeLimitIx} - if !isNative { - createATAIx := builder.buildCreateATAIdempotentInstruction( - relayerKeypair.PublicKey(), recipientPubkey, mintPubkey, - ) - instructions = append(instructions, createATAIx) - } - instructions = append(instructions, gatewayIx) + instructions := []solana.Instruction{computeLimitIx, gatewayIx} recentBlockhash, err := rpcClient.GetRecentBlockhash(ctx) require.NoError(t, err) @@ -2871,3 +2806,82 @@ func TestVerifyBroadcastedTx_NotFoundVersusRPCFailure(t *testing.T) { assert.False(t, found) }) } + +// The gateway creates the recipient ATA and meters the rent into gas_used. +// A create prepended here would put that cost outside the metered path, so the +// built transaction must carry only the compute limit and the gateway call. +func TestBuildOutboundTransaction_NoRecipientATACreate(t *testing.T) { + ataProgram := solana.MustPublicKeyFromBase58("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL") + + for _, tc := range []struct { + name string + txType string + assetAddr string + }{ + {"SPL withdraw", "FUNDS", solana.NewWallet().PublicKey().String()}, + {"SPL revert", "INBOUND_REVERT", solana.NewWallet().PublicKey().String()}, + {"native withdraw", "FUNDS", ""}, + } { + t.Run(tc.name, func(t *testing.T) { + builder := newBlockhashOnlyBuilder(t) + data := &uetypes.OutboundCreatedEvent{ + TxID: "0x" + strings.Repeat("11", 32), + UniversalTxId: "0x" + strings.Repeat("22", 32), + DestinationChain: "solana:devnet", + Sender: "0x" + strings.Repeat("33", 20), + Recipient: solana.NewWallet().PublicKey().String(), + Amount: "1000", + AssetAddr: tc.assetAddr, + GasLimit: "400000", + GasFee: "3000000", + TxType: tc.txType, + SigningDeadline: time.Now().Unix() + 600, + } + req := &common.UnsignedSigningReq{SigningHash: make([]byte, 32), Nonce: 0} + + tx, _, err := builder.BuildOutboundTransaction(context.Background(), req, data, make([]byte, 65)) + require.NoError(t, err) + require.NotNil(t, tx) + + require.Len(t, tx.Message.Instructions, 2, "expected only compute limit and the gateway call") + for i, ix := range tx.Message.Instructions { + program, err := tx.Message.Program(ix.ProgramIDIndex) + require.NoError(t, err) + assert.NotEqual(t, ataProgram, program, "instruction %d creates an ATA", i) + } + }) + } +} + +// newBlockhashOnlyBuilder answers the single RPC BuildOutboundTransaction makes. +// No ALTs are configured, so address-table lookup short-circuits offline. +func newBlockhashOnlyBuilder(t *testing.T) *TxBuilder { + t.Helper() + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + body, _ := io.ReadAll(r.Body) + w.Header().Set("Content-Type", "application/json") + switch { + case strings.Contains(string(body), `"getHealth"`): + _, _ = w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":"ok"}`)) + case strings.Contains(string(body), `"getLatestBlockhash"`): + _, _ = w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":{"context":{"slot":1},` + + `"value":{"blockhash":"9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM","lastValidBlockHeight":100}}}`)) + default: + _, _ = w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":null}`)) + } + })) + t.Cleanup(server.Close) + + rpcClient, err := NewRPCClient([]string{server.URL}, "", zerolog.Nop()) + require.NoError(t, err) + t.Cleanup(func() { rpcClient.Close() }) + + tmpDir := t.TempDir() + relayerDir := filepath.Join(tmpDir, "relayer") + require.NoError(t, os.MkdirAll(relayerDir, 0o755)) + require.NoError(t, os.WriteFile(filepath.Join(relayerDir, "solana.json"), []byte(testSolanaKeypairJSON), 0o600)) + + builder, err := NewTxBuilder(rpcClient, "solana:devnet", testGatewayAddress, tmpDir, zerolog.Nop(), nil) + require.NoError(t, err) + return builder +} From 2f161b07d36199ab3a89fcb2c9f316aeee5577f7 Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Wed, 26 Aug 2026 19:44:03 +0530 Subject: [PATCH 37/60] fix: drop SIGN_MODE_DIRECT_AUX from the enabled sign modes (#340) x/tx compares fee payer to signer with a raw string compare, so an uppercase bech32 alias of the victim slips past it. Nothing on Push signs with AUX. --- app/app.go | 27 +++++++++++++-- app/sign_modes_test.go | 75 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 app/sign_modes_test.go diff --git a/app/app.go b/app/app.go index 881a79cfd..298da10a9 100644 --- a/app/app.go +++ b/app/app.go @@ -504,8 +504,31 @@ func NewChainApp( logger, ) - // enable sign mode textual by overwriting the default tx config (after setting the bank keeper) - enabledSignModes := append(tx.DefaultSignModes, signingtype.SignMode_SIGN_MODE_TEXTUAL) + // Enabled sign modes, listed explicitly rather than appending to + // tx.DefaultSignModes so that what the chain accepts is stated here rather + // than inherited. + // + // SIGN_MODE_DIRECT_AUX is deliberately excluded (F-2026-18784). The handler + // in cosmossdk.io/x/tx rejects a fee payer who also signs with DIRECT_AUX + // using a raw string compare: + // + // if feePayer == signerData.Address { ... unauthorized ... } + // + // BIP-173 permits an all-uppercase bech32 encoding of the same account, so + // an uppercase Fee.Payer aliasing the victim's lowercase signer address + // fails that check open, while everything downstream decodes both to the + // same AccAddress and deduplicates signers. A sponsor holding a victim's + // DIRECT_AUX signature over a fixed TxBody could then rewrite AuthInfo to + // charge the victim. Still present in our pinned x/tx v0.14.0. + // + // Nothing on Push signs with DIRECT_AUX — the universal client pins + // SIGN_MODE_DIRECT — so enabling it only exposes surface. Restore it once + // x/tx compares decoded bytes (or folds case), not before. + enabledSignModes := []signingtype.SignMode{ + signingtype.SignMode_SIGN_MODE_DIRECT, + signingtype.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, + signingtype.SignMode_SIGN_MODE_TEXTUAL, + } txConfigOpts := tx.ConfigOptions{ EnabledSignModes: enabledSignModes, TextualCoinMetadataQueryFn: txmodule.NewBankKeeperCoinMetadataQueryFn(app.BankKeeper), diff --git a/app/sign_modes_test.go b/app/sign_modes_test.go new file mode 100644 index 000000000..573dca469 --- /dev/null +++ b/app/sign_modes_test.go @@ -0,0 +1,75 @@ +package app + +import ( + "testing" + + "github.com/stretchr/testify/require" + + signingtype "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + "github.com/cosmos/cosmos-sdk/x/auth/tx" +) + +// Regression test for F-2026-18784 (uppercase bech32 fee payer bypasses the +// DIRECT_AUX fee-payer guard). +// +// The handler in cosmossdk.io/x/tx compares the fee payer against the signer +// with a raw string compare: +// +// if feePayer == signerData.Address { ... unauthorized ... } +// +// BIP-173 allows an all-uppercase bech32 encoding of the same account, so an +// uppercase Fee.Payer aliasing the victim's lowercase signer address slips past +// that check while everything downstream decodes both to the same AccAddress. +// Our pinned x/tx v0.14.0 still has the raw compare, so the mode stays off. +// +// This test exists so that a future refactor cannot quietly re-enable DIRECT_AUX +// by going back to appending to tx.DefaultSignModes, which contains it. +func TestEnabledSignModes_ExcludesDirectAux(t *testing.T) { + // setup() constructs the app without InitChain, which is all this needs. + // Setup() is avoided on purpose: it passes the "testing" chain ID and panics + // in the EVM configurator unless another test has already initialised it. + gapp, _ := setup(t, ChainID, false, 0) + modes := gapp.TxConfig().SignModeHandler().SupportedModes() + + for _, m := range modes { + require.NotEqual(t, signingtype.SignMode_SIGN_MODE_DIRECT_AUX, m, + "SIGN_MODE_DIRECT_AUX must stay disabled until x/tx compares decoded "+ + "bytes rather than raw strings (F-2026-18784)") + } +} + +// TestEnabledSignModes_KeepsTheModesWeActuallyUse guards the other direction: +// dropping DIRECT_AUX must not take anything else with it. The universal client +// signs with SIGN_MODE_DIRECT, and TEXTUAL is enabled deliberately (it is not in +// tx.DefaultSignModes and needs the bank keeper). +func TestEnabledSignModes_KeepsTheModesWeActuallyUse(t *testing.T) { + gapp, _ := setup(t, ChainID, false, 0) + modes := gapp.TxConfig().SignModeHandler().SupportedModes() + + has := func(want signingtype.SignMode) bool { + for _, m := range modes { + if m == want { + return true + } + } + return false + } + + require.True(t, has(signingtype.SignMode_SIGN_MODE_DIRECT), "DIRECT is what the universal client signs with") + require.True(t, has(signingtype.SignMode_SIGN_MODE_LEGACY_AMINO_JSON), "AMINO_JSON is needed for ledger/legacy clients") + require.True(t, has(signingtype.SignMode_SIGN_MODE_TEXTUAL), "TEXTUAL is enabled deliberately") +} + +// TestDefaultSignModesStillContainsDirectAux documents why the explicit list +// exists. If upstream ever drops DIRECT_AUX from DefaultSignModes this test +// fails, and the explicit enumeration can be reconsidered. +func TestDefaultSignModesStillContainsDirectAux(t *testing.T) { + found := false + for _, m := range tx.DefaultSignModes { + if m.String() == "SIGN_MODE_DIRECT_AUX" { + found = true + } + } + require.True(t, found, + "tx.DefaultSignModes no longer contains DIRECT_AUX; the explicit list in app.go may no longer be needed") +} From a42effa3769426cd05f5f2ba2c880770be290134 Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Wed, 26 Aug 2026 19:44:09 +0530 Subject: [PATCH 38/60] fix(uexecutor): keep the module EVM nonce and the manual nonce counter in step (#342) Route every module-sender DerivedEVMCall through one helper that reads the module account's EVM nonce, burns one nonce per attempt, and writes both back. --- .../uexecutor/module_nonce_test.go | 206 ++++++++++++++++++ x/uexecutor/keeper/evm.go | 132 +++++------ x/uexecutor/keeper/keeper.go | 62 +++++- x/uexecutor/mocks/mock_evmkeeper.go | 14 ++ x/uexecutor/types/expected_keepers.go | 5 + 5 files changed, 333 insertions(+), 86 deletions(-) create mode 100644 test/integration/uexecutor/module_nonce_test.go diff --git a/test/integration/uexecutor/module_nonce_test.go b/test/integration/uexecutor/module_nonce_test.go new file mode 100644 index 000000000..74bc9cade --- /dev/null +++ b/test/integration/uexecutor/module_nonce_test.go @@ -0,0 +1,206 @@ +package integrationtest + +import ( + "math/big" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + evmtypes "github.com/cosmos/evm/x/vm/types" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + + "github.com/pushchain/push-chain-node/app" + utils "github.com/pushchain/push-chain-node/test/utils" + uexecutortypes "github.com/pushchain/push-chain-node/x/uexecutor/types" +) + +// F-2026-18189 — Manual Module EVM Nonce Desync on Reverted Inbound Execution. +// +// Every module-sender DerivedEVMCall in x/uexecutor supplies a *manual* nonce. +// x/vm turns that nonce into the derived transaction's identity: +// +// ethtypes.NewTx(&DynamicFeeTx{Nonce, GasFeeCap, GasTipCap, Gas, To, Value, Data}) +// -> tx.Hash() -> txConfig.TxHash -> res.Hash / the ethereum_tx event attribute +// +// so the nonce is the only field that distinguishes two otherwise byte-identical +// module calls. Two properties therefore have to hold at once, and these two tests +// pin one each: +// +// 1. the nonce the module hands to x/vm must not drift away from the module +// account's own EVM nonce when an attempt fails (Hacken's reported defect), and +// 2. a nonce must be burned by every *attempt*, not just by every committed +// success — otherwise a retry after a failed attempt reproduces a derived tx +// hash that has already been emitted in this block. +// +// (2) is why the naive "read evm.GetNonce(module) immediately before each call and +// drop the counter" fix cannot be shipped: x/vm only advances a sender's nonce for +// a CREATE (state_transition.go bumps it in the contractCreation branch only), so +// for the plain CALLs the module makes, evm.GetNonce(module) is a constant and +// every byte-identical call would collide. The module has to advance that nonce +// itself, unconditionally. + +// moduleDerivedTxHashes returns the ethereum_tx hashes emitted on ctx, in order. +// A derived tx that dies before execution (see the gas-estimation note in +// TestModuleSenderNonceDistinctHashesAcrossFailedAttempt) emits nothing, so this +// is also how the tests tell "attempted and emitted" from "attempted and dropped". +func moduleDerivedTxHashes(ctx sdk.Context) []string { + var out []string + for _, ev := range ctx.EventManager().Events() { + if ev.Type != evmtypes.EventTypeEthereumTx { + continue + } + for _, attr := range ev.Attributes { + if attr.Key == evmtypes.AttributeKeyEthereumTxHash { + out = append(out, attr.Value) + } + } + } + return out +} + +// moduleNonceState reports the two values that must never diverge: the persisted +// uexecutor counter that feeds the manual nonce, and the module account's own EVM +// nonce that x/vm and eth_getTransactionCount read. +func moduleNonceState(t *testing.T, chainApp *app.ChainApp, ctx sdk.Context) (counter, evmNonce uint64) { + t.Helper() + + counter, err := chainApp.UexecutorKeeper.GetModuleAccountNonce(ctx) + require.NoError(t, err) + + moduleAddr, _ := chainApp.UexecutorKeeper.GetUeModuleAddress(ctx) + return counter, chainApp.EVMKeeper.GetNonce(ctx, moduleAddr) +} + +// callDepositPRC20 issues one module-sender depositPRC20Token through the real +// keeper entry point, on an isolated event manager so the caller sees only the +// ethereum_tx events this one call produced. +func callDepositPRC20( + t *testing.T, + chainApp *app.ChainApp, + ctx sdk.Context, + prc20, to common.Address, + amount *big.Int, +) (hashes []string, err error) { + t.Helper() + + callCtx := ctx.WithEventManager(sdk.NewEventManager()) + _, err = chainApp.UexecutorKeeper.CallPRC20Deposit(callCtx, prc20, to, amount) + return moduleDerivedTxHashes(callCtx), err +} + +// TestModuleSenderNonceSurvivesRevertedDeposit is Hacken's stated case: a +// depositPRC20Token that fails must not leave the module's nonce bookkeeping in a +// state that breaks the *next* module-sender call. +// +// The forced failure is a deposit of a PRC20 address that has no code. The +// UniversalCore handler makes a high-level call into it, which reverts. +func TestModuleSenderNonceSurvivesRevertedDeposit(t *testing.T) { + chainApp, ctx, _ := utils.SetAppWithValidators(t) + + prc20 := utils.GetDefaultAddresses().PRC20USDCAddr + // No contract is ever deployed here, so depositPRC20Token reverts on it. + codelessPRC20 := common.HexToAddress("0x000000000000000000000000000000000000dEaD") + recipient := common.HexToAddress("0x0000000000000000000000000000000000001234") + amount := big.NewInt(1_000_000) + + // Sanity: the module account is the EVM sender for all of these calls. + moduleAddr, _ := chainApp.UexecutorKeeper.GetUeModuleAddress(ctx) + require.Equal(t, + sdk.AccAddress(moduleAddr.Bytes()), + chainApp.AccountKeeper.GetModuleAccount(ctx, uexecutortypes.ModuleName).GetAddress(), + ) + + counter, evmNonce := moduleNonceState(t, chainApp, ctx) + require.Equal(t, counter, evmNonce, "module nonce must start in sync") + + // A committed success first, so the reverted attempt below is not the very + // first thing the module ever does. + _, err := callDepositPRC20(t, chainApp, ctx, prc20, recipient, amount) + require.NoError(t, err, "baseline deposit must succeed") + + counter, evmNonce = moduleNonceState(t, chainApp, ctx) + require.Equal(t, counter, evmNonce, "module nonce must stay in sync after a committed deposit") + + beforeRevertCounter, _ := moduleNonceState(t, chainApp, ctx) + + // The reverted deposit. The inbound executors swallow this error and return + // nil, so on-chain nothing else reacts to it — whatever it leaves behind in + // the nonce bookkeeping is what the next call has to live with. + _, err = callDepositPRC20(t, chainApp, ctx, codelessPRC20, recipient, amount) + require.Error(t, err, "depositing a codeless PRC20 must fail") + + afterRevertCounter, afterRevertEvmNonce := moduleNonceState(t, chainApp, ctx) + + // The next module-sender call — Hacken's reported impact is that this one is + // blocked by the nonce the failed attempt left behind. + _, err = callDepositPRC20(t, chainApp, ctx, prc20, recipient, amount) + require.NoError(t, err, "a module-sender call after a reverted one must still succeed") + + // The failed attempt must still have consumed its nonce. A nonce handed back + // on failure is a nonce a byte-identical retry can re-use, and the derived tx + // hash is a pure function of the nonce and the calldata — see + // TestModuleSenderNonceDistinctHashesAcrossFailedAttempt. + require.Equal(t, beforeRevertCounter+1, afterRevertCounter, + "a failed module-sender attempt must still burn its nonce") + + // ...and this is the drift itself: the counter that feeds the manual nonce + // and the module account's own EVM nonce must still agree. + require.Equal(t, afterRevertCounter, afterRevertEvmNonce, + "reverted module call left the manual nonce counter drifted from the module account's EVM nonce") + + counter, evmNonce = moduleNonceState(t, chainApp, ctx) + require.Equal(t, counter, evmNonce, + "module nonce must be back in sync after the follow-up deposit") +} + +// TestModuleSenderNonceDistinctHashesAcrossFailedAttempt is the residual that +// decides the design (recommendation 4 in the write-up). +// +// Making the module account's EVM nonce the source of truth *without* advancing +// it removes the drift, but re-introduces the bug the counter was added for: the +// derived tx hash is a pure function of {Nonce, GasFeeCap, GasTipCap, Gas, To, +// Value, Data}, so byte-identical module calls collide. A failed attempt is the +// sharpest case — it commits nothing at all — but on this EVM fork plain +// successes collide too, because x/vm never advances a CALL sender's nonce. +// +// Note on the failed attempt: a module-sender call passes gasLimit == nil, so +// DerivedEVMCallWithData runs EstimateGasInternal first. For an always-reverting +// call that returns EstimateGasResponse{Gas: 0, VmError: "execution reverted"}, +// and the call then dies in ApplyMessageWithConfig with "intrinsic gas too low" +// before any ethereum_tx event is emitted. So the failed attempt has no hash of +// its own to compare — what it must still do is consume a nonce, so that the +// byte-identical call after it cannot reproduce the hash of the byte-identical +// call before it. +func TestModuleSenderNonceDistinctHashesAcrossFailedAttempt(t *testing.T) { + chainApp, ctx, _ := utils.SetAppWithValidators(t) + + prc20 := utils.GetDefaultAddresses().PRC20USDCAddr + codelessPRC20 := common.HexToAddress("0x000000000000000000000000000000000000dEaD") + recipient := common.HexToAddress("0x0000000000000000000000000000000000001234") + amount := big.NewInt(1_000_000) + + // Three byte-identical calls — same contract, same value, same calldata, same + // gas limit — with a failed attempt wedged between the first and the second. + first, err := callDepositPRC20(t, chainApp, ctx, prc20, recipient, amount) + require.NoError(t, err) + require.Len(t, first, 1, "a committed module deposit must emit exactly one ethereum_tx") + + failed, err := callDepositPRC20(t, chainApp, ctx, codelessPRC20, recipient, amount) + require.Error(t, err, "depositing a codeless PRC20 must fail") + require.Empty(t, failed, "a module call that dies in gas estimation emits no ethereum_tx") + + second, err := callDepositPRC20(t, chainApp, ctx, prc20, recipient, amount) + require.NoError(t, err) + require.Len(t, second, 1) + + third, err := callDepositPRC20(t, chainApp, ctx, prc20, recipient, amount) + require.NoError(t, err) + require.Len(t, third, 1) + + require.NotEqual(t, first[0], second[0], + "byte-identical module calls separated by a failed attempt produced the same derived tx hash") + require.NotEqual(t, second[0], third[0], + "consecutive byte-identical module calls produced the same derived tx hash") + require.NotEqual(t, first[0], third[0], + "byte-identical module calls produced the same derived tx hash") +} diff --git a/x/uexecutor/keeper/evm.go b/x/uexecutor/keeper/evm.go index 98cce158d..4ec8d5174 100644 --- a/x/uexecutor/keeper/evm.go +++ b/x/uexecutor/keeper/evm.go @@ -7,11 +7,57 @@ import ( "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" evmtypes "github.com/cosmos/evm/x/vm/types" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/pushchain/push-chain-node/x/uexecutor/types" uregistrytypes "github.com/pushchain/push-chain-node/x/uregistry/types" ) +// derivedModuleCall issues one committing EVM call whose sender is the uexecutor +// module account. +// +// It is the only place a module-sender DerivedEVMCall may be made from, because +// it is the only place that maintains the module's nonce (F-2026-18189). The +// nonce is taken from nextModuleSenderNonce and consumed by burnModuleSenderNonce +// whether or not the call succeeded — a reverted attempt commits nothing, so +// giving its nonce back would let a byte-identical retry reproduce a derived tx +// hash that was already emitted. Callers get the EVM error unchanged; the nonce +// bookkeeping is not part of it. +func (k Keeper) derivedModuleCall( + ctx sdk.Context, + contractABI abi.ABI, + moduleAddr, contract common.Address, + value, gasLimit *big.Int, + method string, + args ...interface{}, +) (*evmtypes.MsgEthereumTxResponse, error) { + nonce, err := k.nextModuleSenderNonce(ctx, moduleAddr) + if err != nil { + return nil, err + } + + res, callErr := k.evmKeeper.DerivedEVMCall( + ctx, + contractABI, + moduleAddr, // sender: module account + contract, // destination + value, + gasLimit, + true, // commit = true (real tx, not simulation) + false, // gasless = false (@dev: we need gas to be emitted in the tx receipt) + true, // module sender = true + &nonce, // manual nonce of module + method, + args..., + ) + + if err := k.burnModuleSenderNonce(ctx, moduleAddr, nonce); err != nil { + return nil, err + } + + return res, callErr +} + // CallFactoryToGetUEAAddressForOrigin calls FactoryV1.getUEAForOrigin(...) func (k Keeper) CallFactoryToGetUEAAddressForOrigin( ctx sdk.Context, @@ -273,28 +319,13 @@ func (k Keeper) CallPRC20Deposit( ueModuleAccAddress, _ := k.GetUeModuleAddress(ctx) - // Before sending an EVM tx from module - nonce, err := k.GetModuleAccountNonce(ctx) - if err != nil { - return nil, err - } - - // increment first (safe for internal modules) - if _, err := k.IncrementModuleAccountNonce(ctx); err != nil { - return nil, err - } - - return k.evmKeeper.DerivedEVMCall( + return k.derivedModuleCall( ctx, abi, - ueModuleAccAddress, // sender: module account - handlerAddr, // destination + ueModuleAccAddress, + handlerAddr, big.NewInt(0), nil, - true, // commit = true (real tx, not simulation) - false, // gasless = false (@dev: we need gas to be emitted in the tx receipt) - true, // module sender = true - &nonce, // manual nonce of module "depositPRC20Token", prc20Address, amount, @@ -319,26 +350,13 @@ func (k Keeper) CallUniversalCoreSetChainMeta( ueModuleAccAddress, _ := k.GetUeModuleAddress(ctx) - nonce, err := k.GetModuleAccountNonce(ctx) - if err != nil { - return nil, err - } - - if _, err := k.IncrementModuleAccountNonce(ctx); err != nil { - return nil, err - } - - return k.evmKeeper.DerivedEVMCall( + return k.derivedModuleCall( ctx, abi, ueModuleAccAddress, handlerAddr, big.NewInt(0), nil, - true, - false, - true, - &nonce, "setChainMeta", chainNamespace, price, @@ -560,28 +578,13 @@ func (k Keeper) CallPRC20DepositAutoSwap( ueModuleAccAddress, _ := k.GetUeModuleAddress(ctx) - // Before sending an EVM tx from module - nonce, err := k.GetModuleAccountNonce(ctx) - if err != nil { - return nil, err - } - - // increment first (safe for internal modules) - if _, err := k.IncrementModuleAccountNonce(ctx); err != nil { - return nil, err - } - - return k.evmKeeper.DerivedEVMCall( + return k.derivedModuleCall( ctx, abi, - ueModuleAccAddress, // who is sending the transaction - handlerAddr, // destination: Handler contract + ueModuleAccAddress, + handlerAddr, big.NewInt(0), nil, - true, // commit = true (real tx, not simulation) - false, // gasless = false (@dev: we need gas to be emitted in the tx receipt) - true, // module sender = true - &nonce, // manual nonce of module "depositPRC20WithAutoSwap", prc20Address, amount, @@ -612,27 +615,14 @@ func (k Keeper) CallUniversalCoreRefundUnusedGas( ueModuleAccAddress, _ := k.GetUeModuleAddress(ctx) - nonce, err := k.GetModuleAccountNonce(ctx) - if err != nil { - return nil, err - } - - if _, err := k.IncrementModuleAccountNonce(ctx); err != nil { - return nil, err - } - // fee is uint24 in Solidity — pass as *big.Int (go-ethereum ABI packs non-standard widths as *big.Int) - return k.evmKeeper.DerivedEVMCall( + return k.derivedModuleCall( ctx, abi, ueModuleAccAddress, handlerAddr, big.NewInt(0), nil, - true, - false, - true, - &nonce, "refundUnusedGas", gasToken, amount, @@ -662,25 +652,13 @@ func (k Keeper) CallExecuteUniversalTx( ueModuleAccAddress, _ := k.GetUeModuleAddress(ctx) - nonce, err := k.GetModuleAccountNonce(ctx) - if err != nil { - return nil, err - } - if _, err := k.IncrementModuleAccountNonce(ctx); err != nil { - return nil, err - } - - return k.evmKeeper.DerivedEVMCall( + return k.derivedModuleCall( ctx, recipientABI, ueModuleAccAddress, recipientAddr, big.NewInt(0), nil, - true, - false, - true, - &nonce, "executeUniversalTx", sourceChain, ceaAddress, diff --git a/x/uexecutor/keeper/keeper.go b/x/uexecutor/keeper/keeper.go index 923de5dcc..ba240750b 100755 --- a/x/uexecutor/keeper/keeper.go +++ b/x/uexecutor/keeper/keeper.go @@ -3,6 +3,7 @@ package keeper import ( "context" "errors" + "fmt" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" @@ -348,20 +349,63 @@ func (k Keeper) GetModuleAccountNonce(ctx sdk.Context) (uint64, error) { return nonce, nil } -// IncrementModuleAccountNonce increases the nonce by 1 and stores it back. -func (k Keeper) IncrementModuleAccountNonce(ctx sdk.Context) (uint64, error) { +// SetModuleAccountNonce allows explicitly setting the nonce (optional, for migration or testing). +// It keeps the module account's EVM nonce in step, so the two can never diverge — +// see nextModuleSenderNonce for why that matters. +func (k Keeper) SetModuleAccountNonce(ctx sdk.Context, nonce uint64) error { + if err := k.ModuleAccountNonce.Set(ctx, nonce); err != nil { + return err + } + + acc := k.accountKeeper.GetModuleAccount(ctx, types.ModuleName) + if acc == nil { + return fmt.Errorf("module account %s not found", types.ModuleName) + } + if acc.GetSequence() == nonce { + return nil + } + if err := acc.SetSequence(nonce); err != nil { + return err + } + k.accountKeeper.SetAccount(ctx, acc) + + return nil +} + +// nextModuleSenderNonce picks the nonce for the module's next DerivedEVMCall. +// +// F-2026-18189. The module account's EVM nonce is the source of truth, but x/vm +// will not maintain it: ApplyMessageWithConfig advances a sender's nonce only in +// its contractCreation branch, and every call the module makes is a plain CALL. +// So the module maintains it itself (see burnModuleSenderNonce), and reads it +// back here so that a nonce the EVM *did* advance — a CREATE from the module, or +// a chain upgraded from a build that left the account nonce behind — is picked up +// instead of being re-issued. +func (k Keeper) nextModuleSenderNonce(ctx sdk.Context, moduleAddr common.Address) (uint64, error) { nonce, err := k.GetModuleAccountNonce(ctx) if err != nil { return 0, err } - newNonce := nonce + 1 - if err := k.ModuleAccountNonce.Set(ctx, newNonce); err != nil { - return 0, err + if evmNonce := k.evmKeeper.GetNonce(ctx, moduleAddr); evmNonce > nonce { + nonce = evmNonce } - return newNonce, nil + return nonce, nil } -// SetModuleAccountNonce allows explicitly setting the nonce (optional, for migration or testing). -func (k Keeper) SetModuleAccountNonce(ctx sdk.Context, nonce uint64) error { - return k.ModuleAccountNonce.Set(ctx, nonce) +// burnModuleSenderNonce consumes the nonce handed out by nextModuleSenderNonce. +// +// The advance is unconditional: it happens whether the call committed, reverted, +// or never reached the EVM at all. That is deliberate. The derived tx hash is +// ethtypes.NewTx(&DynamicFeeTx{Nonce, GasFeeCap, GasTipCap, Gas, To, Value, +// Data}).Hash(), so the nonce is the only thing separating two byte-identical +// module calls; a failed attempt that gave its nonce back would let the retry +// reproduce a hash already emitted in this block. Because the counter and the +// account nonce move together, advancing on failure can no longer desync them — +// which is what F-2026-18189 reported. +func (k Keeper) burnModuleSenderNonce(ctx sdk.Context, moduleAddr common.Address, nonce uint64) error { + next := nonce + 1 + if evmNonce := k.evmKeeper.GetNonce(ctx, moduleAddr); evmNonce > next { + next = evmNonce + } + return k.SetModuleAccountNonce(ctx, next) } diff --git a/x/uexecutor/mocks/mock_evmkeeper.go b/x/uexecutor/mocks/mock_evmkeeper.go index 0c1f0487c..f33ed6019 100644 --- a/x/uexecutor/mocks/mock_evmkeeper.go +++ b/x/uexecutor/mocks/mock_evmkeeper.go @@ -73,6 +73,20 @@ func (mr *MockEVMKeeperMockRecorder) GetCodeHash(ctx, addr interface{}) *gomock. return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCodeHash", reflect.TypeOf((*MockEVMKeeper)(nil).GetCodeHash), ctx, addr) } +// GetNonce mocks base method. +func (m *MockEVMKeeper) GetNonce(ctx types.Context, addr common.Address) uint64 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetNonce", ctx, addr) + ret0, _ := ret[0].(uint64) + return ret0 +} + +// GetNonce indicates an expected call of GetNonce. +func (mr *MockEVMKeeperMockRecorder) GetNonce(ctx, addr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNonce", reflect.TypeOf((*MockEVMKeeper)(nil).GetNonce), ctx, addr) +} + // DerivedEVMCall mocks base method. func (m *MockEVMKeeper) DerivedEVMCall(ctx types.Context, abi abi.ABI, from, contract common.Address, value, gasLimit *big.Int, commit, gasless, isModuleSender bool, manualNonce *uint64, method string, args ...interface{}) (*types0.MsgEthereumTxResponse, error) { m.ctrl.T.Helper() diff --git a/x/uexecutor/types/expected_keepers.go b/x/uexecutor/types/expected_keepers.go index 788d3e5f8..7577b4d3a 100644 --- a/x/uexecutor/types/expected_keepers.go +++ b/x/uexecutor/types/expected_keepers.go @@ -53,6 +53,8 @@ type EVMKeeper interface { args ...interface{}, ) (*types.MsgEthereumTxResponse, error) GetCodeHash(ctx sdk.Context, addr common.Address) common.Hash + // GetNonce returns the account nonce (auth sequence) the EVM sees for addr. + GetNonce(ctx sdk.Context, addr common.Address) uint64 } // FeeMarketKeeper defines the expected interface for the fee market module. @@ -94,6 +96,9 @@ type BankKeeper interface { // AccountKeeper defines the expected interface for the auth module type AccountKeeper interface { GetModuleAccount(ctx context.Context, moduleName string) sdk.ModuleAccountI + // SetAccount persists an account. Used to keep the uexecutor module + // account's EVM nonce in step with the nonce handed to DerivedEVMCall. + SetAccount(ctx context.Context, acc sdk.AccountI) } type UValidatorKeeper interface { From aa5ad0e0606cb5efd75fa0d4dac9d0dea1dbb75c Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Wed, 26 Aug 2026 19:45:15 +0530 Subject: [PATCH 39/60] fix(uexecutor): derive rescue asset from the original inbound, not the event (#343) Reject a rescue whose event PRC20 disagrees with the original asset's registered PRC20. --- .../uexecutor/rescue_funds_test.go | 189 ++++++++++++++++-- x/uexecutor/keeper/create_outbound.go | 34 +++- 2 files changed, 203 insertions(+), 20 deletions(-) diff --git a/test/integration/uexecutor/rescue_funds_test.go b/test/integration/uexecutor/rescue_funds_test.go index ba25b367e..0f7f97bcb 100644 --- a/test/integration/uexecutor/rescue_funds_test.go +++ b/test/integration/uexecutor/rescue_funds_test.go @@ -4,6 +4,8 @@ import ( "encoding/hex" "fmt" "math/big" + "sort" + "strings" "testing" "time" @@ -18,7 +20,9 @@ import ( "github.com/pushchain/push-chain-node/app" utils "github.com/pushchain/push-chain-node/test/utils" chainutils "github.com/pushchain/push-chain-node/utils" + uexecutorkeeper "github.com/pushchain/push-chain-node/x/uexecutor/keeper" uexecutortypes "github.com/pushchain/push-chain-node/x/uexecutor/types" + uregistrytypes "github.com/pushchain/push-chain-node/x/uregistry/types" ) // buildRescueFundsLog constructs a synthetic evmtypes.Log that looks exactly like a @@ -71,8 +75,22 @@ func buildRescueFundsLog( } } -// setupRescueFundsTest creates a CEA inbound whose deposit will fail (asset address has -// no registered token config), drives it to quorum, and returns the UTX key of the failed UTX. +// Asset of the stuck deposit built by setupRescueFundsTest: a pETH-style 18-decimal +// token, registered in uregistry but whose PRC20 has no deployed contract — so the +// deposit fails while the asset still resolves through the registry. +// +// The 18-decimal choice is deliberate: the default token registered by the CEA setup is +// 6-decimal USDC, so the two form the 18 → 6 pair where a cross-asset rescue amplifies. +// 1e18 raw of an 18-decimal token is one whole token; reinterpreted as 6-decimal USDC the +// same raw integer is a claim on 10^12 whole USDC. +var ( + rescueOriginalAsset = common.HexToAddress("0x000000000000000000000000000000000000DEAD") + rescueOriginalPRC20 = common.HexToAddress("0x0000000000000000000000000000000000000eE1") + rescueOriginalAmount = "1000000000000000000" // 1e18 raw = 1 whole 18-decimal token +) + +// setupRescueFundsTest creates a CEA inbound whose deposit will fail (its PRC20 has no +// deployed contract), drives it to quorum, and returns the UTX key of the failed UTX. // The returned UTX has at least one FAILED PCTx and is ready for a rescue outbound. func setupRescueFundsTest( t *testing.T, @@ -91,21 +109,37 @@ func setupRescueFundsTest( testAddress := utils.GetDefaultAddresses().DefaultTestAddr recipient := utils.GetDefaultAddresses().TargetAddr2 - // Use an asset address that has no registered token config — depositPRC20 will fail. - unregisteredAsset := common.HexToAddress("0x000000000000000000000000000000000000DEAD") + + // Register the stuck asset. A rescue derives its asset from the original inbound, so + // the original asset must resolve; the deposit still fails because rescueOriginalPRC20 + // has no contract deployed at it, leaving the funds stuck on the source chain — which + // is exactly the situation rescue exists for. + require.NoError(t, chainApp.UregistryKeeper.AddTokenConfig(ctx, &uregistrytypes.TokenConfig{ + Chain: "eip155:11155111", + Address: rescueOriginalAsset.String(), + Name: "Push Ether", + Symbol: "pETH", + Decimals: 18, + Enabled: true, + LiquidityCap: "1000000000000000000000000", + TokenType: 1, + NativeRepresentation: &uregistrytypes.NativeRepresentation{ + ContractAddress: rescueOriginalPRC20.String(), + }, + })) inbound := &uexecutortypes.Inbound{ SourceChain: "eip155:11155111", TxHash: "0xrescue01", Sender: testAddress, Recipient: recipient, - Amount: "1000000", - AssetAddr: unregisteredAsset.String(), + Amount: rescueOriginalAmount, + AssetAddr: rescueOriginalAsset.String(), LogIndex: "1", TxType: uexecutortypes.TxType_FUNDS_AND_PAYLOAD, UniversalPayload: &uexecutortypes.UniversalPayload{ To: recipient, - Value: "1000000", + Value: rescueOriginalAmount, Data: "0x", GasLimit: "21000000", MaxFeePerGas: "1000000000", @@ -134,7 +168,7 @@ func setupRescueFundsTest( require.True(t, found, "UTX must exist after quorum") require.NotEmpty(t, utx.PcTx, "setup: at least one PCTx must exist") - require.Equal(t, "FAILED", utx.PcTx[0].Status, "setup: deposit must fail for unregistered asset") + require.Equal(t, "FAILED", utx.PcTx[0].Status, "setup: deposit must fail so the funds stay stuck") return chainApp, ctx, vals, utxId, coreVals } @@ -149,7 +183,12 @@ func makeRescueReceipt(t *testing.T, txHash string, log *evmtypes.Log) *evmtypes } func TestRescueFunds(t *testing.T) { - prc20Addr := utils.GetDefaultAddresses().PRC20USDCAddr + // A rescue event must name the PRC20 registered for the stuck inbound's OWN asset, so + // each subtest uses the PRC20 belonging to whichever setup it built its inbound from: + // setupRescueFundsTest stakes the 18-decimal pETH, the bridge/CEA-payload setups the + // 6-decimal USDC. + prc20Addr := rescueOriginalPRC20 + usdcPRC20Addr := utils.GetDefaultAddresses().PRC20USDCAddr senderAddr := common.HexToAddress(utils.GetDefaultAddresses().DefaultTestAddr) t.Run("rescue outbound is attached to original UTX on valid CEA inbound with failed deposit", func(t *testing.T) { @@ -172,9 +211,16 @@ func TestRescueFunds(t *testing.T) { require.Equal(t, uexecutortypes.Status_PENDING, rescueObs.OutboundStatus) require.Equal(t, uexecutortypes.TxType_RESCUE_FUNDS, rescueObs.TxType) require.Equal(t, "eip155:11155111", rescueObs.DestinationChain) - require.Equal(t, "1000000", rescueObs.Amount) + require.Equal(t, rescueOriginalAmount, rescueObs.Amount) require.Equal(t, "111", rescueObs.GasFee) + // The asset is the original inbound's, derived from the registry — never the + // caller's. Amount and asset must describe the same deposit. + require.Equal(t, rescueOriginalAsset.String(), rescueObs.ExternalAssetAddr, + "rescue must carry the original inbound's external asset") + require.Equal(t, rescueOriginalPRC20.String(), rescueObs.Prc20AssetAddr, + "rescue must carry the PRC20 registered for the original asset") + // The rescue call must be recorded as a PCTx in the UTX history. // UTX already had the failed deposit PCTx; the rescue pcTx is appended after it. require.Greater(t, len(utx.PcTx), 1, "rescue PCTx must be appended to UTX history") @@ -183,6 +229,106 @@ func TestRescueFunds(t *testing.T) { require.Equal(t, "SUCCESS", lastPcTx.Status) }) + // --- F-2026-18177: the rescue asset is derived, never supplied ---------------- + + t.Run("rescue naming a different registered PRC20 than the original asset is rejected", func(t *testing.T) { + // The headline case. The stuck deposit is 1e18 raw of an 18-decimal token; the + // rescue event names 6-decimal USDC, which is registered on the same chain and so + // passes every "is this a real PRC20" check. The amount always comes from the + // original inbound, so honouring the caller's PRC20 would emit an outbound paying + // 1e18 base units of USDC — 10^12 whole USDC — for a stuck deposit of one pETH. + chainApp, ctx, _, utxId, _ := setupRescueFundsTest(t, 4) + + pendingBefore := pendingOutboundIds(t, ctx, chainApp) + + log := buildRescueFundsLog(t, utxId, usdcPRC20Addr, senderAddr, + "eip155", big.NewInt(111), big.NewInt(1_000_000_000), big.NewInt(200_000)) + err := chainApp.UexecutorKeeper.AttachRescueOutboundFromReceipt(ctx, + makeRescueReceipt(t, "0xrescuetx13", log), + uexecutortypes.PCTx{TxHash: "0xrescuetx13", Status: "SUCCESS"}) + + // State is asserted before the error: if the derivation regresses, the call + // succeeds and the substituted outbound shows up here, rather than the subtest + // aborting on the require.Error below and never reaching these checks. + utx, found, getErr := chainApp.UexecutorKeeper.GetUniversalTx(ctx, utxId) + require.NoError(t, getErr) + require.True(t, found) + require.Nil(t, findRescueOutbound(utx), + "no rescue outbound may be created when the event names another asset") + require.Equal(t, pendingBefore, pendingOutboundIds(t, ctx, chainApp), + "a rejected cross-asset rescue must not add a PendingOutbounds row") + + require.Error(t, err) + require.Contains(t, err.Error(), "does not match") + }) + + t.Run("rescue for an original asset with no registered token config is rejected", func(t *testing.T) { + chainApp, ctx, _, utxId, _ := setupRescueFundsTest(t, 4) + + // Point the stored inbound at an asset uregistry knows nothing about. An + // unregistered original asset must be an error, not an opening to substitute + // whichever asset the caller happens to name. + unregistered := common.HexToAddress("0x000000000000000000000000000000000000BEEF") + require.NoError(t, chainApp.UexecutorKeeper.UpdateUniversalTx(ctx, utxId, + func(utx *uexecutortypes.UniversalTx) error { + utx.InboundTx.AssetAddr = unregistered.String() + return nil + })) + + pendingBefore := pendingOutboundIds(t, ctx, chainApp) + + log := buildRescueFundsLog(t, utxId, prc20Addr, senderAddr, + "eip155", big.NewInt(111), big.NewInt(1_000_000_000), big.NewInt(200_000)) + err := chainApp.UexecutorKeeper.AttachRescueOutboundFromReceipt(ctx, + makeRescueReceipt(t, "0xrescuetx14", log), + uexecutortypes.PCTx{TxHash: "0xrescuetx14", Status: "SUCCESS"}) + + utx, _, getErr := chainApp.UexecutorKeeper.GetUniversalTx(ctx, utxId) + require.NoError(t, getErr) + require.Nil(t, findRescueOutbound(utx), + "no rescue outbound may be created for an unregistered original asset") + require.Equal(t, pendingBefore, pendingOutboundIds(t, ctx, chainApp), + "a rejected rescue must not add a PendingOutbounds row") + + require.Error(t, err) + require.Contains(t, err.Error(), "no token config registered for original asset") + }) + + t.Run("rescue PRC20 comparison ignores address casing", func(t *testing.T) { + // The event's PRC20 is always EIP-55 checksummed by the log decoder, while the + // registry stores whatever an admin registered. Comparing canonically means a + // lowercase registry entry is still the same PRC20. + chainApp, ctx, _, utxId, _ := setupRescueFundsTest(t, 4) + + require.NoError(t, chainApp.UregistryKeeper.UpdateTokenConfig(ctx, &uregistrytypes.TokenConfig{ + Chain: "eip155:11155111", + Address: rescueOriginalAsset.String(), + Name: "Push Ether", + Symbol: "pETH", + Decimals: 18, + Enabled: true, + LiquidityCap: "1000000000000000000000000", + TokenType: 1, + NativeRepresentation: &uregistrytypes.NativeRepresentation{ + ContractAddress: strings.ToLower(rescueOriginalPRC20.String()), + }, + })) + + log := buildRescueFundsLog(t, utxId, rescueOriginalPRC20, senderAddr, + "eip155", big.NewInt(111), big.NewInt(1_000_000_000), big.NewInt(200_000)) + err := chainApp.UexecutorKeeper.AttachRescueOutboundFromReceipt(ctx, + makeRescueReceipt(t, "0xrescuetx15", log), + uexecutortypes.PCTx{TxHash: "0xrescuetx15", Status: "SUCCESS"}) + require.NoError(t, err) + + utx, _, err := chainApp.UexecutorKeeper.GetUniversalTx(ctx, utxId) + require.NoError(t, err) + rescueOb := findRescueOutbound(utx) + require.NotNil(t, rescueOb) + require.Equal(t, strings.ToLower(rescueOriginalPRC20.String()), rescueOb.Prc20AssetAddr, + "the registry's spelling of the PRC20 is what lands on the outbound") + }) + t.Run("rescue outbound recipient defaults to inbound sender when no revert instructions", func(t *testing.T) { chainApp, ctx, _, utxId, _ := setupRescueFundsTest(t, 4) @@ -222,7 +368,7 @@ func TestRescueFunds(t *testing.T) { } utxId := uexecutortypes.GetInboundUniversalTxKey(*inbound) - log := buildRescueFundsLog(t, utxId, prc20Addr, senderAddr, + log := buildRescueFundsLog(t, utxId, usdcPRC20Addr, senderAddr, "eip155", big.NewInt(111), big.NewInt(1_000_000_000), big.NewInt(200_000)) err := chainApp.UexecutorKeeper.AttachRescueOutboundFromReceipt(ctx, makeRescueReceipt(t, "0xrescuetx03", log), uexecutortypes.PCTx{TxHash: "0xrescuetx03", Status: "SUCCESS"}) require.Error(t, err) @@ -251,7 +397,7 @@ func TestRescueFunds(t *testing.T) { }) require.NoError(t, err) - log := buildRescueFundsLog(t, utxId, prc20Addr, senderAddr, + log := buildRescueFundsLog(t, utxId, usdcPRC20Addr, senderAddr, "eip155", big.NewInt(111), big.NewInt(1_000_000_000), big.NewInt(200_000)) err = chainApp.UexecutorKeeper.AttachRescueOutboundFromReceipt(ctx, makeRescueReceipt(t, "0xrescuetx03b", log), uexecutortypes.PCTx{TxHash: "0xrescuetx03b", Status: "SUCCESS"}) require.Error(t, err) @@ -280,7 +426,7 @@ func TestRescueFunds(t *testing.T) { }) require.NoError(t, err) - log := buildRescueFundsLog(t, utxId, prc20Addr, senderAddr, + log := buildRescueFundsLog(t, utxId, usdcPRC20Addr, senderAddr, "eip155", big.NewInt(222), big.NewInt(1_000_000_000), big.NewInt(200_000)) err = chainApp.UexecutorKeeper.AttachRescueOutboundFromReceipt(ctx, makeRescueReceipt(t, "0xrescuetx03c", log), uexecutortypes.PCTx{TxHash: "0xrescuetx03c", Status: "SUCCESS"}) require.NoError(t, err) @@ -317,7 +463,7 @@ func TestRescueFunds(t *testing.T) { // Confirm first PCTx (deposit) succeeded — that's the invariant we rely on. require.Equal(t, "SUCCESS", utx.PcTx[0].Status, "deposit must have succeeded for this test to be meaningful") - log := buildRescueFundsLog(t, utxId, prc20Addr, senderAddr, + log := buildRescueFundsLog(t, utxId, usdcPRC20Addr, senderAddr, "eip155", big.NewInt(111), big.NewInt(1_000_000_000), big.NewInt(200_000)) err = chainApp.UexecutorKeeper.AttachRescueOutboundFromReceipt(ctx, makeRescueReceipt(t, "0xrescuetx04", log), uexecutortypes.PCTx{TxHash: "0xrescuetx04", Status: "SUCCESS"}) require.Error(t, err) @@ -587,6 +733,21 @@ func TestRescueFunds(t *testing.T) { }) } +// pendingOutboundIds returns the sorted outbound IDs currently in the PendingOutbounds +// index, so a test can assert that a rejected rescue left the index untouched. +func pendingOutboundIds(t *testing.T, ctx sdk.Context, chainApp *app.ChainApp) []string { + t.Helper() + querier := uexecutorkeeper.NewQuerier(chainApp.UexecutorKeeper) + resp, err := querier.AllPendingOutbounds(ctx, &uexecutortypes.QueryAllPendingOutboundsRequest{}) + require.NoError(t, err) + ids := make([]string, 0, len(resp.Entries)) + for _, e := range resp.Entries { + ids = append(ids, e.OutboundId) + } + sort.Strings(ids) + return ids +} + // findRescueOutbound returns the first RESCUE_FUNDS outbound from a UTX, or nil. func findRescueOutbound(utx uexecutortypes.UniversalTx) *uexecutortypes.OutboundTx { for _, ob := range utx.OutboundTx { diff --git a/x/uexecutor/keeper/create_outbound.go b/x/uexecutor/keeper/create_outbound.go index f8995ae23..3ff5f1db3 100644 --- a/x/uexecutor/keeper/create_outbound.go +++ b/x/uexecutor/keeper/create_outbound.go @@ -290,15 +290,37 @@ func (k Keeper) AttachRescueOutboundFromReceipt( } } - // Resolve external asset address from PRC20 → token config for the source chain. - tokenCfg, err := k.uregistryKeeper.GetTokenConfigByPRC20( + // The asset is DERIVED from the original stuck inbound, never taken from the + // rescue event. The rescued Amount below is always originalUtx.InboundTx.Amount, + // so accepting the caller's event.PRC20 as the asset identity would pair one + // asset's raw amount with another asset's identity. Amounts are raw base units, + // so differing decimals amplify that: a stuck 1e18 of an 18-decimal token pointed + // at a 6-decimal token becomes a claim on 10^12 whole tokens. + tokenCfg, err := k.uregistryKeeper.GetTokenConfig( ctx, originalUtx.InboundTx.SourceChain, - event.PRC20, + originalUtx.InboundTx.AssetAddr, ) if err != nil { - return fmt.Errorf("rescue: token config not found for PRC20 %s on %s: %w", - event.PRC20, originalUtx.InboundTx.SourceChain, err) + return fmt.Errorf("rescue: no token config registered for original asset %s on %s: %w", + originalUtx.InboundTx.AssetAddr, originalUtx.InboundTx.SourceChain, err) + } + if tokenCfg.NativeRepresentation == nil || tokenCfg.NativeRepresentation.ContractAddress == "" { + return fmt.Errorf("rescue: token config for original asset %s on %s has no PRC20 representation", + originalUtx.InboundTx.AssetAddr, originalUtx.InboundTx.SourceChain) + } + derivedPRC20 := tokenCfg.NativeRepresentation.ContractAddress + + // Defence in depth: the event still names a PRC20, and it must agree with the one + // derived above. Reject on disagreement instead of silently overriding, so a + // mismatched caller surfaces as an error rather than a wrong-asset outbound. + // Lenient canonicalization mirrors uregistry's own PRC20 identity function + // (canonicalPRC20), so exactly the pairs the registry considers equal are accepted; + // a missing representation is already rejected above, so it can never read as a match. + if utils.LenientCanonicalizeEVMAddress(event.PRC20) != utils.LenientCanonicalizeEVMAddress(derivedPRC20) { + return fmt.Errorf( + "rescue: event PRC20 %s does not match PRC20 %s registered for original asset %s on %s", + event.PRC20, derivedPRC20, originalUtx.InboundTx.AssetAddr, originalUtx.InboundTx.SourceChain) } // Rescued funds go to the original revert recipient (or the sender as fallback). @@ -315,7 +337,7 @@ func (k Keeper) AttachRescueOutboundFromReceipt( Recipient: recipient, Amount: originalUtx.InboundTx.Amount, ExternalAssetAddr: tokenCfg.Address, - Prc20AssetAddr: event.PRC20, + Prc20AssetAddr: derivedPRC20, Sender: event.Sender, GasFee: event.GasFee.String(), GasPrice: event.GasPrice.String(), From ecccfb83f85601d95c44b4525fece947de10ca88 Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Wed, 26 Aug 2026 19:45:21 +0530 Subject: [PATCH 40/60] fix: accept provably unreachable PENDING ballots in RevertStuckInbound (#344) A PENDING ballot whose every eligible voter has already voted can never receive another vote, so it is terminal in fact; the admin hatch now opens for it. REJECTED stays refused (F-2026-18801). --- .../uexecutor/revert_stuck_inbound_test.go | 246 ++++++++++++++++++ x/uexecutor/keeper/admin_revert.go | 73 +++++- x/uvalidator/types/ballot.go | 38 +++ x/uvalidator/types/ballot_test.go | 61 +++++ 4 files changed, 409 insertions(+), 9 deletions(-) diff --git a/test/integration/uexecutor/revert_stuck_inbound_test.go b/test/integration/uexecutor/revert_stuck_inbound_test.go index 76f6f1530..e64faa990 100644 --- a/test/integration/uexecutor/revert_stuck_inbound_test.go +++ b/test/integration/uexecutor/revert_stuck_inbound_test.go @@ -89,6 +89,252 @@ func seedBallot(t *testing.T, chainApp *app.ChainApp, ctx sdk.Context, inbound * })) } +// seedPendingBallotWithVotes stores a PENDING ballot carrying a real +// eligible-voter list and per-voter vote slots, which seedBallot deliberately +// leaves empty. The F-2026-18147 scenarios all turn on whether any eligible +// voter still holds a NOT_YET_VOTED slot, so they need the populated shape. +// +// The voter strings are never resolved against the staking set on this path — +// RevertStuckInbound only reads Status/EligibleVoters/Votes off the ballot. +func seedPendingBallotWithVotes( + t *testing.T, + chainApp *app.ChainApp, + ctx sdk.Context, + inbound *uexecutortypes.Inbound, + status uvalidatortypes.BallotStatus, + voters []string, + votes []uvalidatortypes.VoteResult, + threshold int64, +) { + t.Helper() + require.Len(t, votes, len(voters), "each eligible voter needs exactly one vote slot") + ballotKey, err := uexecutortypes.GetInboundBallotKey(*inbound) + require.NoError(t, err) + require.NoError(t, chainApp.UvalidatorKeeper.Ballots.Set(ctx, ballotKey, uvalidatortypes.Ballot{ + Id: ballotKey, + BallotType: uvalidatortypes.BallotObservationType_BALLOT_OBSERVATION_TYPE_INBOUND_TX, + EligibleVoters: voters, + Votes: votes, + VotingThreshold: threshold, + Status: status, + BlockHeightCreated: 1, + BlockHeightExpiry: 100_000_000, + })) + require.NoError(t, chainApp.UvalidatorKeeper.ActiveBallotIDs.Set(ctx, ballotKey)) +} + +// threeVoters is the eligible-voter list shared by the F-2026-18147 scenarios. +func threeVoters() []string { + return []string{"cosmosvaloper1aaa", "cosmosvaloper1bbb", "cosmosvaloper1ccc"} +} + +// TestRevertStuckInbound_PendingUnreachable_ThresholdMet_CreatesRevertOutbound +// is the headline F-2026-18147 case. +// +// RecomputeBallotQuorum preserves the votes of still-eligible voters, lowers the +// threshold, and returns PENDING without ever calling CheckIfFinalizingVote. The +// shape reproduced here is what that leaves behind in the worst case: every +// eligible voter has voted YES and the preserved YES count already clears the +// recomputed threshold, so the ballot *should* have passed — but Ballot.AddVote +// rejects repeat votes, so no further vote can ever be cast and nothing will +// move it off PENDING. Natural expiry is 100M blocks away. +// +// Before this fix the admin hatch required EXPIRED, and recompute only expires a +// ballot at zero eligible voters, so the deposit was stranded permanently. +func TestRevertStuckInbound_PendingUnreachable_ThresholdMet_CreatesRevertOutbound(t *testing.T) { + chainApp, ctx, inbound, admin := setupRevertStuckInbound(t) + seedPendingBallotWithVotes(t, chainApp, ctx, inbound, + uvalidatortypes.BallotStatus_BALLOT_STATUS_PENDING, + threeVoters(), + []uvalidatortypes.VoteResult{ + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + }, + 2, // YES (3) already clears the recomputed threshold + ) + + ms := uexecutorkeeper.NewMsgServerImpl(chainApp.UexecutorKeeper) + resp, err := ms.RevertStuckInbound(sdk.WrapSDKContext(ctx), &uexecutortypes.MsgRevertStuckInbound{ + Signer: admin, + Inbound: inbound, + }) + require.NoError(t, err, "an unreachable PENDING ballot must be revertible") + require.NotEmpty(t, resp.UtxId) + require.NotEmpty(t, resp.OutboundId) + + // --- UTX assertions --- + utx, _, err := chainApp.UexecutorKeeper.GetUniversalTx(ctx, resp.UtxId) + require.NoError(t, err) + require.Equal(t, uexecutortypes.GetInboundUniversalTxKey(*inbound), utx.Id) + require.NotNil(t, utx.InboundTx) + require.Equal(t, inbound.TxHash, utx.InboundTx.TxHash) + + require.Len(t, utx.PcTx, 1) + require.Equal(t, "FAILED", utx.PcTx[0].Status) + require.Contains(t, utx.PcTx[0].ErrorMsg, "unreachable", + "the audit trail must record WHY the hatch opened, not the expired wording") + + // --- Revert outbound assertions --- + require.Len(t, utx.OutboundTx, 1) + ob := utx.OutboundTx[0] + require.Equal(t, resp.OutboundId, ob.Id) + require.Equal(t, uexecutortypes.TxType_INBOUND_REVERT, ob.TxType) + require.Equal(t, uexecutortypes.Status_PENDING, ob.OutboundStatus) + require.Equal(t, inbound.SourceChain, ob.DestinationChain) + require.Equal(t, inbound.RevertInstructions.FundRecipient, ob.Recipient) + require.Equal(t, inbound.Amount, ob.Amount) + require.Equal(t, inbound.AssetAddr, ob.ExternalAssetAddr) + + // --- PendingOutbounds index: the refund is actually queued for TSS signing --- + pending, err := chainApp.UexecutorKeeper.PendingOutbounds.Get(ctx, ob.Id) + require.NoError(t, err, "revert outbound must be indexed in PendingOutbounds for UV pickup") + require.Equal(t, ob.Id, pending.OutboundId) + require.Equal(t, utx.Id, pending.UniversalTxId) +} + +// TestRevertStuckInbound_PendingUnreachable_BelowThreshold_Accepted covers the +// second stuck shape: every eligible voter has voted, but the YES count never +// reached the threshold and the NO count never reached it either, so +// IsFinalizingVote fires for neither branch. Reachable without any recompute at +// all — 3 voters, threshold 3, one dissenting FAILURE vote. +// +// Unreachability, not vote arithmetic, is the predicate; both shapes qualify. +func TestRevertStuckInbound_PendingUnreachable_BelowThreshold_Accepted(t *testing.T) { + chainApp, ctx, inbound, admin := setupRevertStuckInbound(t) + seedPendingBallotWithVotes(t, chainApp, ctx, inbound, + uvalidatortypes.BallotStatus_BALLOT_STATUS_PENDING, + threeVoters(), + []uvalidatortypes.VoteResult{ + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_FAILURE, + }, + 3, // YES (2) < 3, NO (1) < 3 → neither branch of IsFinalizingVote fires + ) + + ms := uexecutorkeeper.NewMsgServerImpl(chainApp.UexecutorKeeper) + resp, err := ms.RevertStuckInbound(sdk.WrapSDKContext(ctx), &uexecutortypes.MsgRevertStuckInbound{ + Signer: admin, + Inbound: inbound, + }) + require.NoError(t, err, "a fully-voted PENDING ballot below threshold is equally unreachable") + + utx, _, err := chainApp.UexecutorKeeper.GetUniversalTx(ctx, resp.UtxId) + require.NoError(t, err) + require.Len(t, utx.OutboundTx, 1) + require.Equal(t, uexecutortypes.TxType_INBOUND_REVERT, utx.OutboundTx[0].TxType) + + _, err = chainApp.UexecutorKeeper.PendingOutbounds.Get(ctx, utx.OutboundTx[0].Id) + require.NoError(t, err, "revert outbound must be queued for UV pickup") +} + +// TestRevertStuckInbound_PendingWithUnvotedVoter_Refused is the guard against +// widening the hatch too far. +// +// This ballot is deliberately the most tempting possible refusal: the YES votes +// already clear the threshold, so it *looks* exactly like the headline case. It +// is not — one eligible voter still holds a NOT_YET_VOTED slot, so a single +// normal VoteOnBallot finalizes it through the proper VoteInbound pipeline, +// which mints and executes rather than refunding. Admin revert must not race +// that. This is also the shape Hacken's no-code workaround produces: add an +// eligible UV, recompute, and the new voter arrives NOT_YET_VOTED. +func TestRevertStuckInbound_PendingWithUnvotedVoter_Refused(t *testing.T) { + chainApp, ctx, inbound, admin := setupRevertStuckInbound(t) + seedPendingBallotWithVotes(t, chainApp, ctx, inbound, + uvalidatortypes.BallotStatus_BALLOT_STATUS_PENDING, + threeVoters(), + []uvalidatortypes.VoteResult{ + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_NOT_YET_VOTED, + }, + 2, // YES (2) already meets threshold — still refused, it can finalize normally + ) + + ms := uexecutorkeeper.NewMsgServerImpl(chainApp.UexecutorKeeper) + _, err := ms.RevertStuckInbound(sdk.WrapSDKContext(ctx), &uexecutortypes.MsgRevertStuckInbound{ + Signer: admin, + Inbound: inbound, + }) + require.Error(t, err, "a PENDING ballot with an unvoted eligible voter can still finalize; admin revert must refuse it") + require.Contains(t, err.Error(), "admin revert requires EXPIRED") + + // The refusal must be total: no UTX, so no revert outbound can be signed. + utxKey := uexecutortypes.GetInboundUniversalTxKey(*inbound) + has, hErr := chainApp.UexecutorKeeper.HasUniversalTx(ctx, utxKey) + require.NoError(t, hErr) + require.False(t, has, "a refused revert must not leave a UniversalTx behind") +} + +// TestRevertStuckInbound_RejectedBallot_FullyVoted_StillRefused re-pins the +// F-2026-18801 refusal against the new predicate. +// +// A REJECTED ballot is fully voted by construction, so the "every eligible voter +// has voted" test on its own would let it through. It must not: REJECTED means a +// supermajority affirmatively voted the observation invalid, and refunding would +// pay out of the TSS vault against a deposit the validator set concluded never +// happened. PENDING-unreachable is the opposite case — nobody can act at all. +// The status guard in IsUnreachablePending is what keeps them apart. +func TestRevertStuckInbound_RejectedBallot_FullyVoted_StillRefused(t *testing.T) { + chainApp, ctx, inbound, admin := setupRevertStuckInbound(t) + seedPendingBallotWithVotes(t, chainApp, ctx, inbound, + uvalidatortypes.BallotStatus_BALLOT_STATUS_REJECTED, + threeVoters(), + []uvalidatortypes.VoteResult{ + uvalidatortypes.VoteResult_VOTE_RESULT_FAILURE, + uvalidatortypes.VoteResult_VOTE_RESULT_FAILURE, + uvalidatortypes.VoteResult_VOTE_RESULT_FAILURE, + }, + 2, + ) + + ms := uexecutorkeeper.NewMsgServerImpl(chainApp.UexecutorKeeper) + _, err := ms.RevertStuckInbound(sdk.WrapSDKContext(ctx), &uexecutortypes.MsgRevertStuckInbound{ + Signer: admin, + Inbound: inbound, + }) + require.Error(t, err, "REJECTED stays refused however its vote slots are filled (F-2026-18801)") + require.Contains(t, err.Error(), "admin revert requires EXPIRED") + + utxKey := uexecutortypes.GetInboundUniversalTxKey(*inbound) + has, hErr := chainApp.UexecutorKeeper.HasUniversalTx(ctx, utxKey) + require.NoError(t, hErr) + require.False(t, has, "a refused revert must not leave a UniversalTx behind") +} + +// TestRevertStuckInbound_ExpiredBallot_FullyVoted_StillAccepted keeps the +// original precondition intact under the new switch: EXPIRED is accepted on its +// status alone, and still records the expired wording rather than the +// unreachable-pending wording. +func TestRevertStuckInbound_ExpiredBallot_FullyVoted_StillAccepted(t *testing.T) { + chainApp, ctx, inbound, admin := setupRevertStuckInbound(t) + seedPendingBallotWithVotes(t, chainApp, ctx, inbound, + uvalidatortypes.BallotStatus_BALLOT_STATUS_EXPIRED, + threeVoters(), + []uvalidatortypes.VoteResult{ + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_NOT_YET_VOTED, + uvalidatortypes.VoteResult_VOTE_RESULT_NOT_YET_VOTED, + }, + 3, + ) + + ms := uexecutorkeeper.NewMsgServerImpl(chainApp.UexecutorKeeper) + resp, err := ms.RevertStuckInbound(sdk.WrapSDKContext(ctx), &uexecutortypes.MsgRevertStuckInbound{ + Signer: admin, + Inbound: inbound, + }) + require.NoError(t, err) + + utx, _, err := chainApp.UexecutorKeeper.GetUniversalTx(ctx, resp.UtxId) + require.NoError(t, err) + require.Len(t, utx.PcTx, 1) + require.Contains(t, utx.PcTx[0].ErrorMsg, "expired") + require.Len(t, utx.OutboundTx, 1) + require.Equal(t, uexecutortypes.TxType_INBOUND_REVERT, utx.OutboundTx[0].TxType) +} + func TestRevertStuckInbound_HappyPath_ExpiredBallot_CreatesRevertOutbound(t *testing.T) { chainApp, ctx, inbound, admin := setupRevertStuckInbound(t) seedBallot(t, chainApp, ctx, inbound, uvalidatortypes.BallotStatus_BALLOT_STATUS_EXPIRED) diff --git a/x/uexecutor/keeper/admin_revert.go b/x/uexecutor/keeper/admin_revert.go index a1918762a..1a141954d 100644 --- a/x/uexecutor/keeper/admin_revert.go +++ b/x/uexecutor/keeper/admin_revert.go @@ -13,14 +13,59 @@ import ( ) // RevertStuckInbound creates an INBOUND_REVERT outbound for an inbound whose -// ballot has expired without finalizing. The revert outbound enters the normal +// ballot can no longer finalize. The revert outbound enters the normal // PendingOutbounds flow; UVs sign it via TSS and broadcast it to the source // chain, refunding the user. // -// Strict precondition: the ballot for the supplied inbound must be in EXPIRED -// state. Admin must run MsgRecomputeBallotQuorum first to drive a stuck ballot -// to EXPIRED if it isn't already (recompute auto-expires when no eligible -// voters remain). +// Precondition: the ballot for the supplied inbound must be either +// +// - EXPIRED, or +// - PENDING but provably unreachable - every eligible voter has already voted +// (Ballot.IsUnreachablePending). +// +// The second case exists because RecomputeBallotQuorum can leave a ballot +// permanently stuck (F-2026-18147). It preserves the votes of still-eligible +// voters, lowers the threshold, and returns PENDING without ever calling +// CheckIfFinalizingVote. If the preserved votes already fill every slot there is +// no vote left to cast - Ballot.AddVote rejects repeat votes - so nothing can +// move the ballot off PENDING and the deposit sits in the source gateway +// forever. Such a ballot is terminal in fact whatever its stored status says, so +// the hatch treats it as terminal too. This holds for both stuck shapes: YES +// already at or above the recomputed threshold (should have passed, never will) +// and YES below it (can never reach it). +// +// The deliberate limits of that widening: +// +// - A PENDING ballot with an unvoted eligible voter is still refused. It can +// finalize normally, and reverting would race a legitimate vote. +// - A PENDING ballot with no eligible voters at all is refused too. Recompute +// rebuilds the voter list from the live UV set, so it either gains real +// voters or auto-expires; a shipped path already resolves it. +// - Fixing this inside RecomputeBallotQuorum by calling CheckIfFinalizingVote +// was rejected. That marks the ballot PASSED without running VoteInbound's +// post-finalization pipeline, so no UniversalTx is ever built +// (msg_vote_inbound.go builds one only when that specific vote finalizes) +// and BallotHooks returns early on PASSED without minting or executing. The +// funds would stay stuck AND the ballot would no longer be PENDING, so +// recompute could not be retried - strictly worse than leaving it alone. +// +// This route reverts rather than executes: the user is refunded on the source +// chain instead of receiving bridged funds on Push. For a ballot whose YES votes +// met the threshold that is the less generous of the two resolutions, and it is +// the deliberate trade for a change that stays inside the module that owns +// inbound execution. +// +// The ballot record itself is left untouched. The HasUniversalTx guard below is +// the idempotency barrier, and mutating ballot status from x/uexecutor would +// fire the uvalidator terminal hook and re-enter inbound routing for an inbound +// this call is already resolving. +// +// REJECTED stays refused, deliberately and not by omission (F-2026-18801): a +// supermajority affirmatively voted that the observation is invalid, so a revert +// outbound would pay real funds out of the TSS-controlled vault against a +// deposit the validator set concluded never happened. PENDING-unreachable is the +// opposite situation - nobody can act at all - which is why it is accepted while +// REJECTED is not. // // REJECTED is refused deliberately, not by omission (F-2026-18801). The two // terminal-failure statuses mean opposite things: @@ -65,9 +110,17 @@ func (k Keeper) RevertStuckInbound(ctx context.Context, inbound types.Inbound) ( return "", "", errors.Wrap(sdkErrors.ErrNotFound, fmt.Sprintf("ballot for inbound not found (key=%s): %s", ballotKey, err)) } - if ballot.Status != uvalidatortypes.BallotStatus_BALLOT_STATUS_EXPIRED { + var revertReason string + switch { + case ballot.Status == uvalidatortypes.BallotStatus_BALLOT_STATUS_EXPIRED: + revertReason = "admin revert: stuck ballot expired" + case ballot.IsUnreachablePending(): + revertReason = "admin revert: pending ballot unreachable, every eligible voter has already voted" + default: return "", "", errors.Wrap(sdkErrors.ErrInvalidRequest, - fmt.Sprintf("ballot %s status is %s; admin revert requires EXPIRED (use MsgRecomputeBallotQuorum to drive a stuck pending ballot to EXPIRED)", + fmt.Sprintf("ballot %s status is %s; admin revert requires EXPIRED, or PENDING with every eligible voter already voted (no further vote can be cast). "+ + "MsgRecomputeBallotQuorum rebuilds the eligible-voter set from the live UV set and marks the ballot EXPIRED only when zero eligible voters remain, "+ + "so a pending ballot that still has an unvoted eligible voter has to be finalized by that voter through the normal vote flow", ballotKey, ballot.Status.String())) } @@ -84,7 +137,7 @@ func (k Keeper) RevertStuckInbound(ctx context.Context, inbound types.Inbound) ( InboundTx: &inbound, PcTx: []*types.PCTx{{ Status: "FAILED", - ErrorMsg: "admin revert: stuck ballot expired", + ErrorMsg: revertReason, }}, } if cErr := k.CreateUniversalTx(ctx, universalTxKey, utx); cErr != nil { @@ -107,7 +160,7 @@ func (k Keeper) RevertStuckInbound(ctx context.Context, inbound types.Inbound) ( ) } - if attachErr := k.attachOutboundsToUtx(sdkCtx, universalTxKey, []*types.OutboundTx{revertOutbound}, "admin revert: stuck ballot expired"); attachErr != nil { + if attachErr := k.attachOutboundsToUtx(sdkCtx, universalTxKey, []*types.OutboundTx{revertOutbound}, revertReason); attachErr != nil { return "", "", fmt.Errorf("failed to attach revert outbound: %w", attachErr) } @@ -118,6 +171,8 @@ func (k Keeper) RevertStuckInbound(ctx context.Context, inbound types.Inbound) ( "source_chain", inbound.SourceChain, "recipient", revertOutbound.Recipient, "amount", revertOutbound.Amount, + "ballot_status", ballot.Status.String(), + "reason", revertReason, ) return universalTxKey, revertOutbound.Id, nil diff --git a/x/uvalidator/types/ballot.go b/x/uvalidator/types/ballot.go index 03f37d71f..2b11e1531 100644 --- a/x/uvalidator/types/ballot.go +++ b/x/uvalidator/types/ballot.go @@ -45,6 +45,44 @@ func (b Ballot) AddVote(address string, vote VoteResult) (Ballot, error) { return b, nil } +// HasUnvotedEligibleVoter reports whether at least one eligible voter still +// holds a NOT_YET_VOTED slot, i.e. whether AddVote can still succeed for +// somebody. +// +// A ballot whose Votes slice is shorter than EligibleVoters is malformed; the +// missing slots are counted as unvoted. That is the conservative answer for +// every caller here, and it matches HasVoted, which would panic indexing them. +func (b Ballot) HasUnvotedEligibleVoter() bool { + for i := range b.EligibleVoters { + if i >= len(b.Votes) || b.Votes[i] == VoteResult_VOTE_RESULT_NOT_YET_VOTED { + return true + } + } + return false +} + +// IsUnreachablePending reports whether the ballot is stored PENDING yet is +// terminal in fact: every eligible voter has already voted, so AddVote can +// never fire again (it rejects repeat votes) and no further vote event can move +// the ballot to PASSED or REJECTED. Such a ballot stays PENDING forever unless +// its eligible-voter set is rebuilt. See F-2026-18147. +// +// A ballot with no eligible voters at all is deliberately NOT reported as +// unreachable. RecomputeBallotQuorum rebuilds the voter list from the live +// universal-validator set, so an empty ballot either gains real voters and +// becomes votable or is auto-expired by that same call; a shipped path already +// resolves it. An empty voter list is also evidence of a malformed ballot +// rather than of a completed vote. +func (b Ballot) IsUnreachablePending() bool { + if b.Status != BallotStatus_BALLOT_STATUS_PENDING { + return false + } + if len(b.EligibleVoters) == 0 { + return false + } + return !b.HasUnvotedEligibleVoter() +} + // CountVotes counts the YES and NO votes in the ballot. func (b Ballot) CountVotes() (yes, no int) { for _, v := range b.Votes { diff --git a/x/uvalidator/types/ballot_test.go b/x/uvalidator/types/ballot_test.go index 258a14381..201e24983 100644 --- a/x/uvalidator/types/ballot_test.go +++ b/x/uvalidator/types/ballot_test.go @@ -188,3 +188,64 @@ func TestIsFinalizingVote(t *testing.T) { _, done = b.IsFinalizingVote() require.False(t, done) } + +// TestIsUnreachablePending pins the F-2026-18147 predicate: a PENDING ballot +// whose every eligible voter has already voted can never receive another vote +// (AddVote rejects repeats), so it is terminal in fact. +func TestIsUnreachablePending(t *testing.T) { + voted := func(v ...VoteResult) Ballot { + return Ballot{ + Id: "b", + Status: BallotStatus_BALLOT_STATUS_PENDING, + EligibleVoters: []string{"addr1", "addr2", "addr3"}[:len(v)], + Votes: v, + VotingThreshold: 2, + } + } + + // Every slot filled → unreachable, regardless of the vote arithmetic. + require.True(t, voted( + VoteResult_VOTE_RESULT_SUCCESS, + VoteResult_VOTE_RESULT_SUCCESS, + VoteResult_VOTE_RESULT_SUCCESS, + ).IsUnreachablePending(), "YES above threshold but nobody left to vote") + require.True(t, voted( + VoteResult_VOTE_RESULT_SUCCESS, + VoteResult_VOTE_RESULT_FAILURE, + VoteResult_VOTE_RESULT_FAILURE, + ).IsUnreachablePending(), "YES below threshold and nobody left to vote") + + // One slot still open → the ballot can still finalize normally. + require.False(t, voted( + VoteResult_VOTE_RESULT_SUCCESS, + VoteResult_VOTE_RESULT_SUCCESS, + VoteResult_VOTE_RESULT_NOT_YET_VOTED, + ).IsUnreachablePending()) + + // Only PENDING ballots qualify; terminal ones are fully voted by + // construction and must not be swept in. + for _, st := range []BallotStatus{ + BallotStatus_BALLOT_STATUS_PASSED, + BallotStatus_BALLOT_STATUS_REJECTED, + BallotStatus_BALLOT_STATUS_EXPIRED, + } { + b := voted(VoteResult_VOTE_RESULT_SUCCESS, VoteResult_VOTE_RESULT_SUCCESS) + b.Status = st + require.False(t, b.IsUnreachablePending(), "status %s must not be reported as unreachable-pending", st) + } + + // Degenerate ballots are refused rather than swept in: an empty voter list + // is a malformed ballot, and RecomputeBallotQuorum resolves it by rebuilding + // the list from the live UV set (or auto-expiring at zero). + require.False(t, Ballot{Status: BallotStatus_BALLOT_STATUS_PENDING}.IsUnreachablePending()) + + // A Votes slice shorter than EligibleVoters is malformed too; the missing + // slots count as unvoted, which is the conservative answer. + short := Ballot{ + Status: BallotStatus_BALLOT_STATUS_PENDING, + EligibleVoters: []string{"addr1", "addr2"}, + Votes: []VoteResult{VoteResult_VOTE_RESULT_SUCCESS}, + } + require.True(t, short.HasUnvotedEligibleVoter()) + require.False(t, short.IsUnreachablePending()) +} From d5d8ee8eb4d87660fab24eac38c9859f2e33fc74 Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Wed, 26 Aug 2026 19:45:26 +0530 Subject: [PATCH 41/60] fix: allocate fund migration ids from 1 so the first one is votable (#345) Ids came straight off collections.Sequence, whose first value is 0, while MsgVoteFundMigration.ValidateBasic rejects migration_id == 0 as "unset". The first migration on a fresh chain was therefore unvotable, never left PendingMigrations, and blocked every later migration for that chain. Allocate as sequence + 1: stored ids start at 1 and 0 stays reserved for "unset", so the ValidateBasic guard remains a real check. F-2026-18789 --- test/integration/utss/fund_migration_test.go | 155 ++++++++++++++++++- x/utss/keeper/msg_initiate_fund_migration.go | 12 +- 2 files changed, 161 insertions(+), 6 deletions(-) diff --git a/test/integration/utss/fund_migration_test.go b/test/integration/utss/fund_migration_test.go index 402fd4911..ce0b30e1f 100644 --- a/test/integration/utss/fund_migration_test.go +++ b/test/integration/utss/fund_migration_test.go @@ -1,12 +1,14 @@ package integrationtest import ( + "bytes" "fmt" "math/big" "strconv" "strings" "testing" + "cosmossdk.io/collections" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -17,6 +19,7 @@ import ( utils "github.com/pushchain/push-chain-node/test/utils" uregistrytypes "github.com/pushchain/push-chain-node/x/uregistry/types" + utsskeeper "github.com/pushchain/push-chain-node/x/utss/keeper" utsstypes "github.com/pushchain/push-chain-node/x/utss/types" uvalidatortypes "github.com/pushchain/push-chain-node/x/uvalidator/types" ) @@ -203,7 +206,9 @@ func TestInitiateFundMigration(t *testing.T) { migrationId, err := app.UtssKeeper.InitiateFundMigration(ctx, oldKeyId, testChain, testBalance) require.NoError(t, err) - require.Equal(t, uint64(0), migrationId) + // Ids start at 1, not 0 — 0 is reserved for "unset" and is rejected by + // MsgVoteFundMigration.ValidateBasic (F-2026-18789). + require.Equal(t, uint64(1), migrationId) // Verify migration is stored migration, err := app.UtssKeeper.FundMigrations.Get(ctx, migrationId) @@ -286,9 +291,13 @@ func TestInitiateFundMigration(t *testing.T) { _, err := app.UtssKeeper.InitiateFundMigration(ctx, oldKeyId, testChain, "1") require.ErrorContains(t, err, "does not cover the migration fee") - // Nothing may be left behind. - _, err = app.UtssKeeper.FundMigrations.Get(ctx, 0) - require.Error(t, err, "a rejected migration must not be stored") + // Nothing may be left behind under any id. + var stored int + require.NoError(t, app.UtssKeeper.FundMigrations.Walk(ctx, nil, func(uint64, utsstypes.FundMigration) (bool, error) { + stored++ + return false, nil + })) + require.Zero(t, stored, "a rejected migration must not be stored") }) t.Run("Fails if old key not found", func(t *testing.T) { @@ -516,3 +525,141 @@ func TestVoteFundMigration_MalformedHashRejected(t *testing.T) { err = app.UtssKeeper.VoteFundMigration(ctx, valAddr, migrationId, "0xnot-a-real-hash", true) require.ErrorContains(t, err, "invalid tx hash") } + +// TestInitiateFundMigration_FirstMigrationIsVotable is the F-2026-18789 +// regression. +// +// Migration ids used to come straight off collections.Sequence, whose first +// value is 0, while MsgVoteFundMigration.ValidateBasic rejects +// migration_id == 0 as "unset". The very first migration on a fresh chain was +// therefore unvotable: every vote died in ValidateBasic before it ever reached +// the keeper, the migration never left PendingMigrations, and +// InitiateFundMigration then refused every later migration for that chain — +// the lane was bricked with no way out short of an upgrade. audit-fixes is a +// fresh-genesis branch, so this fires on first use. +// +// Ids are now allocated as sequence + 1: the first id is 1 and 0 stays +// reserved for "unset". The three cases are separate subtests on purpose, so +// that each one reports independently instead of the first failure masking +// the rest. +func TestInitiateFundMigration_FirstMigrationIsVotable(t *testing.T) { + // firstMigration runs the very first migration a fresh chain ever + // allocates — the case that used to be unreachable — and returns its id. + firstMigration := func(t *testing.T) (*app.ChainApp, sdk.Context, []string, string, uint64) { + t.Helper() + chainApp, ctx, universalVals, oldKeyId := setupFundMigrationTest(t, 3, false) + + seq, err := chainApp.UtssKeeper.NextMigrationId.Peek(ctx) + require.NoError(t, err) + require.Zero(t, seq, "fixture must start from a virgin sequence or this proves nothing") + + migrationId, err := chainApp.UtssKeeper.InitiateFundMigration(ctx, oldKeyId, testChain, testBalance) + require.NoError(t, err) + return chainApp, ctx, universalVals, oldKeyId, migrationId + } + + const txHash = "0xdeadbeef12345678deadbeef12345678deadbeef12345678deadbeef12345678" + + voteMsg := func(t *testing.T, val string, migrationId uint64) *utsstypes.MsgVoteFundMigration { + t.Helper() + valAddr, err := sdk.ValAddressFromBech32(val) + require.NoError(t, err) + return &utsstypes.MsgVoteFundMigration{ + Signer: sdk.AccAddress(valAddr).String(), + MigrationId: migrationId, + TxHash: txHash, + Success: true, + } + } + + t.Run("the first id is never the reserved 0", func(t *testing.T) { + chainApp, ctx, _, _, migrationId := firstMigration(t) + + require.NotZero(t, migrationId, + "the first migration id must never be 0: MsgVoteFundMigration.ValidateBasic rejects 0 as unset") + require.Equal(t, uint64(1), migrationId) + + // The record really is stored under that votable id. + migration, err := chainApp.UtssKeeper.FundMigrations.Get(ctx, migrationId) + require.NoError(t, err) + require.Equal(t, utsstypes.FundMigrationStatus_FUND_MIGRATION_STATUS_PENDING, migration.Status) + require.Equal(t, testChain, migration.Chain) + }) + + t.Run("a vote on the first migration passes ValidateBasic", func(t *testing.T) { + _, _, universalVals, _, migrationId := firstMigration(t) + + // This is the exact gate that bricked the lane: the message a universal + // validator broadcasts is rejected here, before the keeper is reached. + msg := voteMsg(t, universalVals[0], migrationId) + require.NoError(t, msg.ValidateBasic(), + "a vote on the first migration must survive ValidateBasic") + }) + + t.Run("the first migration finalizes and unblocks the chain", func(t *testing.T) { + chainApp, ctx, universalVals, oldKeyId, migrationId := firstMigration(t) + msgServer := utsskeeper.NewMsgServerImpl(chainApp.UtssKeeper) + + for _, val := range universalVals { + msg := voteMsg(t, val, migrationId) + require.NoError(t, msg.ValidateBasic()) + _, err := msgServer.VoteFundMigration(ctx, msg) + require.NoError(t, err, "a vote on the first migration must reach the ballot") + } + + migration, err := chainApp.UtssKeeper.FundMigrations.Get(ctx, migrationId) + require.NoError(t, err) + require.Equal(t, utsstypes.FundMigrationStatus_FUND_MIGRATION_STATUS_COMPLETED, migration.Status, + "votes on the first migration must be able to finalize it") + require.Equal(t, txHash, migration.TxHash) + + _, err = chainApp.UtssKeeper.PendingMigrations.Get(ctx, migrationId) + require.ErrorIs(t, err, collections.ErrNotFound, + "a finalized migration must leave PendingMigrations, or the chain stays blocked") + + // The outcome the bug denied: the chain is migratable again, under the + // next votable id. + secondId, err := chainApp.UtssKeeper.InitiateFundMigration(ctx, oldKeyId, testChain, testBalance) + require.NoError(t, err, "a second migration must be possible once the first finalized") + require.Equal(t, uint64(2), secondId) + }) +} + +// TestVoteFundMigration_ZeroMigrationIdStaysRejected pins the other half of +// the F-2026-18789 contract: 0 keeps meaning "unset". The fix moves ids off 0 +// rather than allowing 0, so this guard must stay in place. +func TestVoteFundMigration_ZeroMigrationIdStaysRejected(t *testing.T) { + msg := &utsstypes.MsgVoteFundMigration{ + Signer: sdk.AccAddress(bytes.Repeat([]byte{1}, 20)).String(), + MigrationId: 0, + TxHash: "0xdeadbeef12345678deadbeef12345678deadbeef12345678deadbeef12345678", + Success: true, + } + require.ErrorContains(t, msg.ValidateBasic(), "migration_id is required") +} + +// TestFundMigrationIdsSurviveGenesisRoundTrip guards the interaction between +// the sequence + 1 allocation and genesis: the exported counter must not hand +// an already-used id back after an export/import cycle. +func TestFundMigrationIdsSurviveGenesisRoundTrip(t *testing.T) { + app, ctx, _, oldKeyId := setupFundMigrationTest(t, 3, false) + + firstId, err := app.UtssKeeper.InitiateFundMigration(ctx, oldKeyId, testChain, testBalance) + require.NoError(t, err) + require.Equal(t, uint64(1), firstId) + + // ExportGenesis reads Params, which this fixture never seeds. + require.NoError(t, app.UtssKeeper.Params.Set(ctx, utsstypes.Params{ + Admin: "push1negskcfqu09j5zvpk7nhvacnwyy2mafffy7r6a", + })) + + exported := app.UtssKeeper.ExportGenesis(ctx) + require.Equal(t, uint64(1), exported.NextMigrationId) + require.NoError(t, app.UtssKeeper.InitGenesis(ctx, exported)) + + // The next allocation must not collide with the id already in state. + seq, err := app.UtssKeeper.NextMigrationId.Next(ctx) + require.NoError(t, err) + require.Greater(t, seq+1, firstId, + "an export/import cycle must not re-issue an id that is already taken") +} diff --git a/x/utss/keeper/msg_initiate_fund_migration.go b/x/utss/keeper/msg_initiate_fund_migration.go index a076312cc..0ddd973e7 100644 --- a/x/utss/keeper/msg_initiate_fund_migration.go +++ b/x/utss/keeper/msg_initiate_fund_migration.go @@ -91,11 +91,19 @@ func (k Keeper) InitiateFundMigration(ctx context.Context, oldKeyId, chain, bala return 0, fmt.Errorf("failed to get l1 gas fee for chain %s: %w", chain, err) } - // 8. Create migration record - migrationId, err := k.NextMigrationId.Next(ctx) + // 8. Create migration record. + // + // Ids are allocated as sequence + 1, so the first migration on a chain is 1 + // and never 0. MsgVoteFundMigration.ValidateBasic treats migration_id == 0 + // as "unset" and rejects the message, so a migration stored under id 0 + // could never be voted on: it would stay in PendingMigrations forever and + // block every later migration for that chain (F-2026-18789). Keeping 0 + // reserved for "unset" also keeps that ValidateBasic guard meaningful. + seq, err := k.NextMigrationId.Next(ctx) if err != nil { return 0, fmt.Errorf("failed to get next migration id: %w", err) } + migrationId := seq + 1 // Derive the sweep amount from the observed balance and the fees just // fetched. Rejecting here turns a balance that cannot cover its own transfer From 7473e484ba2b2310952d3ded86a1868657906736 Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Wed, 26 Aug 2026 19:45:30 +0530 Subject: [PATCH 42/60] fix: F-2026-18140 price ed25519 raw-message verification by length (#346) verifyEd25519RawMessage charged a flat 4000 gas no matter how long the message was, while ed25519.Verify hashes the whole slice (~58us at 32B, ~922us at 1MB). Charge 4000 + 12 per 32-byte word instead, matching the SHA-256 precompile's per-word rate, and hard-cap the message at 128 KiB since a view method can be looped from memory without re-paying calldata. verifyEd25519 stays flat: it always verifies the 66-byte hex form of a bytes32 digest, so its cost cannot vary with the calldata. --- app/README.md | 4 +- precompiles/usigverifier/README.md | 39 ++- precompiles/usigverifier/gas_test.go | 370 +++++++++++++++++++++++ precompiles/usigverifier/query.go | 10 +- precompiles/usigverifier/usigverifier.go | 93 +++++- 5 files changed, 503 insertions(+), 13 deletions(-) create mode 100644 precompiles/usigverifier/gas_test.go diff --git a/app/README.md b/app/README.md index 1fe798b25..293e9eb07 100644 --- a/app/README.md +++ b/app/README.md @@ -150,7 +150,9 @@ Push Chain ships exactly one custom precompile: |---|---|---| | `0xEC00000000000000000000000000000000000001` | `usigverifier` | Ed25519 signature verification (Solana signatures over `bytes32` digests), registered at the reserved Push range | -Gas cost: `4000` per `verifyEd25519` call. See [`precompiles/usigverifier/README.md`](../precompiles/usigverifier/README.md). +Gas cost: `4000` per `verifyEd25519` call (fixed 32-byte digest), and `4000` plus `12` per 32-byte +word of `message` for `verifyEd25519RawMessage`, whose message is hard-capped at 128 KiB. See +[`precompiles/usigverifier/README.md`](../precompiles/usigverifier/README.md). The baseline EVM precompiles (`bech32`, `p256`, `staking`, `distribution`, `ics20`, `bank`, `gov`, `slashing`, `evidence`) are wired in via `app/precompiles.go:NewAvailableStaticPrecompiles`. diff --git a/precompiles/usigverifier/README.md b/precompiles/usigverifier/README.md index 9c7e37c33..efef3620b 100644 --- a/precompiles/usigverifier/README.md +++ b/precompiles/usigverifier/README.md @@ -40,11 +40,36 @@ interface IUSigVerifier { | Method | Signed bytes | Gas | Use when | |---|---|---|---| -| `verifyEd25519(bytes,bytes32,bytes)` | `"0x" + hex(msgDigest)` (66 ASCII bytes) | 4000 | UEA_SVM / Solana-wallet flows where the user signs a hex string in Phantom/Solflare | -| `verifyEd25519RawMessage(bytes,bytes,bytes)` | Raw `message` bytes | 4000 | New integrations / relayers using standard `ed25519.Sign(privKey, rawBytes)` | +| `verifyEd25519(bytes,bytes32,bytes)` | `"0x" + hex(msgDigest)` (66 ASCII bytes) | 4000 (flat) | UEA_SVM / Solana-wallet flows where the user signs a hex string in Phantom/Solflare | +| `verifyEd25519RawMessage(bytes,bytes,bytes)` | Raw `message` bytes | `4000 + 12` per 32-byte word of `message` | New integrations / relayers using standard `ed25519.Sign(privKey, rawBytes)` | Both methods are `view` and touch no chain state. +### Why only the raw method scales with size + +`ed25519.Verify` hashes the whole message, so its CPU cost grows with the message +(~58 µs at 32 B, ~146 µs at 128 KiB, ~922 µs at 1 MB). `verifyEd25519` always verifies +the same 66-byte ASCII string no matter what the caller sends, so its cost is constant +and its price stays flat. `verifyEd25519RawMessage` verifies caller-supplied bytes, so +it is priced per 32-byte word — the same per-word rate the EVM `SHA-256` precompile +charges for comparable hashing work. + +Because both methods are `view`, a contract can park one large message in memory and +loop `STATICCALL`s over it, paying the calldata only once. Pricing alone is therefore +not the whole defence: `message` is also **hard-capped at 128 KiB** +(`MaxEd25519MessageBytes`), and anything larger reverts with `message too large` +instead of being verified. + +| `len(message)` | Gas | +|---|---| +| 0 | 4,000 | +| 32 B | 4,012 | +| 1 KiB | 4,384 | +| 8 KiB | 7,072 | +| 64 KiB | 28,576 | +| 128 KiB (cap) | 53,152 | +| > 128 KiB | reverts | + ## Verification Semantics Two methods, two distinct signing conventions. **A signature produced for one method will not verify under the other** — the test vectors in `query_test.go` lock this in. @@ -69,13 +94,14 @@ Standard Ed25519 verification — signature is checked against the raw `message` ok = ed25519.Verify(pubKeyBytes, message, signature) ``` -Use this when your signer uses `ed25519.Sign(privKey, rawBytes)` (default in every Solana SDK / nacl library). `message` may be any length, not just 32 bytes. +Use this when your signer uses `ed25519.Sign(privKey, rawBytes)` (default in every Solana SDK / nacl library). `message` may be any length up to `MaxEd25519MessageBytes` (128 KiB), not just 32 bytes. ### Common rules - `pubKey` must be exactly 32 bytes; `signature` must be exactly 64 bytes — otherwise the precompile reverts with `invalid params`. +- `verifyEd25519RawMessage` reverts with `message too large` past `MaxEd25519MessageBytes` (128 KiB). - Unknown method IDs revert with the standard `unknown method` error. -- Both methods cost `4000` gas. +- `verifyEd25519` costs a flat `4000` gas; `verifyEd25519RawMessage` costs `4000` plus `12` per 32-byte word of `message`. ## Generating the ABI @@ -120,7 +146,8 @@ If the call returns `0x` (empty), the precompile is not in `active_static_precom precompiles/usigverifier/ |-- USigVerifier.sol Solidity interface (the source of truth for the ABI) |-- abi.json Embedded into the binary via go:embed -|-- usigverifier.go Precompile struct, NewPrecompile / NewPrecompileV2, RequiredGas, Run -|-- query.go VerifyEd25519 method handler +|-- usigverifier.go Precompile struct, NewPrecompile / NewPrecompileV2, RequiredGas (gas schedule), Run +|-- query.go VerifyEd25519 / VerifyEd25519RawMessage method handlers +|-- gas_test.go Gas-schedule + size-cap regression tests and benchmarks +-- README.md (this file) ``` diff --git a/precompiles/usigverifier/gas_test.go b/precompiles/usigverifier/gas_test.go new file mode 100644 index 000000000..2b188144c --- /dev/null +++ b/precompiles/usigverifier/gas_test.go @@ -0,0 +1,370 @@ +package usigverifier + +import ( + "crypto/ed25519" + "fmt" + "testing" + + "github.com/ethereum/go-ethereum/core/vm" + "github.com/stretchr/testify/require" +) + +// Gas pricing for verifyEd25519RawMessage (F-2026-18140 remediation). +// +// ed25519.Verify runs a SHA-512 pass over the whole message, so its CPU cost +// grows with the message while the old price was a flat 4000 regardless of +// length (~58 µs at 32 B vs ~922 µs at 1 MB on a live node — 16x the work for +// the same fee). The remediation is twofold: price the message per 32-byte word, +// and refuse messages past MaxEd25519MessageBytes outright, because this is a +// view method a contract can loop from memory without re-paying the calldata. + +// capGas is what a message of exactly MaxEd25519MessageBytes costs, and the most +// any verifyEd25519RawMessage call can ever be charged. +const capGas = VerifyEd25519RawMessageBaseGas + + (MaxEd25519MessageBytes/32)*VerifyEd25519RawMessagePerWordGas + +// rawMessageCalldata ABI-encodes a verifyEd25519RawMessage call with a message +// of msgLen bytes, exactly as the EVM would hand it to the precompile. +func rawMessageCalldata(tb testing.TB, msgLen int) []byte { + tb.Helper() + + cd, err := ABI.Pack( + VerifyEd25519RawMessageMethod, + make([]byte, ed25519.PublicKeySize), + make([]byte, msgLen), + make([]byte, ed25519.SignatureSize), + ) + require.NoError(tb, err) + + return cd +} + +// TestRequiredGas_RawMessageScalesWithMessageLength locks in the price curve: +// a flat base plus VerifyEd25519RawMessagePerWordGas for every 32-byte word of +// the message. A flat price fails every row past the first. +func TestRequiredGas_RawMessageScalesWithMessageLength(t *testing.T) { + p, err := NewPrecompile() + require.NoError(t, err) + + for _, tc := range []struct { + name string + msgLen int + want uint64 + }{ + {"empty", 0, 4000}, + {"1 byte rounds up to a word", 1, 4012}, + {"32 bytes / 1 word", 32, 4012}, + {"33 bytes / 2 words", 33, 4024}, + {"1 KiB", 1024, 4000 + 32*12}, + {"8 KiB", 8 * 1024, 4000 + 256*12}, + {"64 KiB", 64 * 1024, 4000 + 2048*12}, + {"128 KiB (cap)", MaxEd25519MessageBytes, 4000 + 4096*12}, + } { + t.Run(tc.name, func(t *testing.T) { + require.Equal(t, tc.want, p.RequiredGas(rawMessageCalldata(t, tc.msgLen)), + "gas must be %d base + %d per 32-byte word", + VerifyEd25519RawMessageBaseGas, VerifyEd25519RawMessagePerWordGas) + }) + } +} + +// TestRequiredGas_RawMessageIsStrictlyIncreasing is the property behind the +// table: every extra word of message must cost more than the word before it. +func TestRequiredGas_RawMessageIsStrictlyIncreasing(t *testing.T) { + p, err := NewPrecompile() + require.NoError(t, err) + + prev := uint64(0) + for _, msgLen := range []int{0, 32, 64, 1024, 8 * 1024, 64 * 1024, MaxEd25519MessageBytes} { + gas := p.RequiredGas(rawMessageCalldata(t, msgLen)) + require.Greater(t, gas, prev, + "a %d-byte message must cost more than the smaller one before it", msgLen) + prev = gas + } + + // And the growth has to be material, not a rounding error: the largest + // accepted message costs an order of magnitude more than the base. + require.Greater(t, prev, 10*VerifyEd25519RawMessageBaseGas, + "a cap-sized message must cost far more than the flat base price") +} + +// TestRequiredGas_SmallMessagesKeepASaneCost guards the other direction: the +// per-word term must not make ordinary calls (a digest, a short payload) +// noticeably more expensive than they used to be. +func TestRequiredGas_SmallMessagesKeepASaneCost(t *testing.T) { + p, err := NewPrecompile() + require.NoError(t, err) + + require.Equal(t, VerifyEd25519RawMessageBaseGas, p.RequiredGas(rawMessageCalldata(t, 0)), + "an empty message costs exactly the base") + + for _, msgLen := range []int{1, 32, 64, 128} { + gas := p.RequiredGas(rawMessageCalldata(t, msgLen)) + require.Greater(t, gas, VerifyEd25519RawMessageBaseGas, + "a %d-byte message must cost more than an empty one", msgLen) + require.LessOrEqual(t, gas, VerifyEd25519RawMessageBaseGas+100, + "a %d-byte message must stay within a rounding error of the old flat price", msgLen) + } +} + +// TestRequiredGas_AboveCapIsClampedNotUnbounded: a message past the cap is +// rejected by Run, but it must still be priced — at the cap's price, never more, +// so the charge cannot be inflated (or overflowed) by a declared length. +func TestRequiredGas_AboveCapIsClampedNotUnbounded(t *testing.T) { + p, err := NewPrecompile() + require.NoError(t, err) + + for _, tc := range []struct { + name string + msgLen int + }{ + {"one byte over the cap", MaxEd25519MessageBytes + 1}, + {"twice the cap", 2 * MaxEd25519MessageBytes}, + {"1 MiB", 1024 * 1024}, + } { + t.Run(tc.name, func(t *testing.T) { + require.Equal(t, capGas, p.RequiredGas(rawMessageCalldata(t, tc.msgLen)), + "oversized messages must be priced at the cap, not above it") + }) + } +} + +// TestRequiredGas_LegacyMethodStaysFlat documents the deliberate divergence +// between the two constants. verifyEd25519 takes a bytes32 digest and always +// verifies the same 66-byte ASCII hex string, so its cost does not depend on the +// calldata — even an oversized pubKey argument cannot change the work done. +func TestRequiredGas_LegacyMethodStaysFlat(t *testing.T) { + p, err := NewPrecompile() + require.NoError(t, err) + + var digest [32]byte + + for _, pubKeyLen := range []int{32, 1024, 64 * 1024} { + cd, err := ABI.Pack( + VerifyEd25519Method, + make([]byte, pubKeyLen), + digest, + make([]byte, ed25519.SignatureSize), + ) + require.NoError(t, err) + + require.Equal(t, VerifyEd25519Gas, p.RequiredGas(cd), + "verifyEd25519 verifies a fixed 66-byte message, so it stays flat (pubKey len=%d)", pubKeyLen) + } +} + +// TestRequiredGas_MalformedCalldataIsPanicFreeAndBounded: RequiredGas runs +// before any validation, on whatever bytes the caller supplied, so it must never +// panic and must never charge more than a cap-sized message. +func TestRequiredGas_MalformedCalldataIsPanicFreeAndBounded(t *testing.T) { + p, err := NewPrecompile() + require.NoError(t, err) + + valid := rawMessageCalldata(t, 64) + + // mutate overwrites the 32-byte word at args[wordIdx] of a valid calldata. + mutate := func(wordIdx int, word []byte) []byte { + out := make([]byte, len(valid)) + copy(out, valid) + copy(out[4+wordIdx*32:4+(wordIdx+1)*32], word) + return out + } + + allOnes := make([]byte, 32) + for i := range allOnes { + allOnes[i] = 0xff + } + + // The `message` tail sits at args[160:] for a 32-byte pubKey: 3 head words + // plus the pubKey tail (length word + one data word). + const messageLenWordIdx = 5 + + for _, tc := range []struct { + name string + input []byte + }{ + {"nil", nil}, + {"one byte", []byte{0x01}}, + {"selector only", valid[:4]}, + {"selector plus half a head word", valid[:4+16]}, + {"head truncated before the message offset", valid[:4+32]}, + {"tail truncated at the message length word", valid[:4+160]}, + {"message offset larger than a uint64", mutate(1, allOnes)}, + {"message offset points past the calldata", mutate(1, append(make([]byte, 31), 0xff))}, + {"message length larger than a uint64", mutate(messageLenWordIdx, allOnes)}, + } { + t.Run(tc.name, func(t *testing.T) { + var gas uint64 + require.NotPanics(t, func() { gas = p.RequiredGas(tc.input) }, + "RequiredGas must never panic on malformed calldata") + require.LessOrEqual(t, gas, capGas, + "malformed calldata must never be charged more than a cap-sized message") + }) + } + + // An absurd declared length is priced at the cap rather than at the base, so + // lying about the size is not the cheap path. + require.Equal(t, capGas, p.RequiredGas(mutate(messageLenWordIdx, allOnes))) +} + +// TestVerifyEd25519RawMessage_RejectsOversizedMessage is the hard cap: past +// MaxEd25519MessageBytes the call reverts instead of verifying. +func TestVerifyEd25519RawMessage_RejectsOversizedMessage(t *testing.T) { + p, err := NewPrecompile() + require.NoError(t, err) + + priv := ed25519.NewKeyFromSeed(testSeed) + pub := priv.Public().(ed25519.PublicKey) + + method := ABI.Methods[VerifyEd25519RawMessageMethod] + msg := make([]byte, MaxEd25519MessageBytes+1) + sig := ed25519.Sign(priv, msg) + + bz, err := p.VerifyEd25519RawMessage(&method, []interface{}{[]byte(pub), msg, sig}) + + // Value assertion first: an error assertion aborts the test, and a nil + // result is the thing that proves no verification happened. + require.Nil(t, bz, "an oversized message must not produce a verification result") + require.Error(t, err, "a message past the cap must be rejected, not verified") + require.Contains(t, err.Error(), "message too large") +} + +// TestVerifyEd25519RawMessage_AcceptsMessageAtCap: the cap is inclusive — a +// message of exactly MaxEd25519MessageBytes still verifies. +func TestVerifyEd25519RawMessage_AcceptsMessageAtCap(t *testing.T) { + p, err := NewPrecompile() + require.NoError(t, err) + + priv := ed25519.NewKeyFromSeed(testSeed) + pub := priv.Public().(ed25519.PublicKey) + + method := ABI.Methods[VerifyEd25519RawMessageMethod] + msg := make([]byte, MaxEd25519MessageBytes) + sig := ed25519.Sign(priv, msg) + + bz, err := p.VerifyEd25519RawMessage(&method, []interface{}{[]byte(pub), msg, sig}) + require.NoError(t, err) + + out, err := method.Outputs.Unpack(bz) + require.NoError(t, err) + require.Equal(t, []interface{}{true}, out, "a message of exactly the cap must still verify") +} + +// TestRun_OversizedMessageReverts drives the same cap through the entry point +// the EVM actually calls, on real ABI-encoded calldata. +func TestRun_OversizedMessageReverts(t *testing.T) { + p, err := NewPrecompile() + require.NoError(t, err) + + priv := ed25519.NewKeyFromSeed(testSeed) + pub := priv.Public().(ed25519.PublicKey) + + t.Run("at the cap it verifies", func(t *testing.T) { + msg := make([]byte, MaxEd25519MessageBytes) + cd, err := ABI.Pack(VerifyEd25519RawMessageMethod, []byte(pub), msg, ed25519.Sign(priv, msg)) + require.NoError(t, err) + + bz, err := p.Run(nil, &vm.Contract{Input: cd}, true) + require.NoError(t, err) + + method := ABI.Methods[VerifyEd25519RawMessageMethod] + out, err := method.Outputs.Unpack(bz) + require.NoError(t, err) + require.Equal(t, []interface{}{true}, out) + }) + + t.Run("past the cap it reverts", func(t *testing.T) { + msg := make([]byte, MaxEd25519MessageBytes+1) + cd, err := ABI.Pack(VerifyEd25519RawMessageMethod, []byte(pub), msg, ed25519.Sign(priv, msg)) + require.NoError(t, err) + + bz, err := p.Run(nil, &vm.Contract{Input: cd}, true) + + require.Nil(t, bz, "an oversized message must not produce a verification result") + require.Error(t, err) + require.Contains(t, err.Error(), "message too large") + }) +} + +// TestLargeMessageLoopIsGasProhibitive is the abuse shape the finding describes: +// a contract parks one large message in memory (paying its calldata once) and +// loops STATICCALLs over it. What bounds that loop is the per-call gas, so the +// number of verifications a single block can be made to run must drop sharply +// against the old flat price. +func TestLargeMessageLoopIsGasProhibitive(t *testing.T) { + p, err := NewPrecompile() + require.NoError(t, err) + + const ( + blockGasLimit = uint64(100_000_000) + oldFlatGas = uint64(4000) // the pre-fix price, at any message length + ) + + gas := p.RequiredGas(rawMessageCalldata(t, MaxEd25519MessageBytes)) + + iterationsNow := blockGasLimit / gas + iterationsBefore := blockGasLimit / oldFlatGas + + require.Less(t, iterationsNow, iterationsBefore/10, + "a cap-sized message must buy at least 10x fewer verifications per block "+ + "than the old flat price did (now %d, before %d)", iterationsNow, iterationsBefore) +} + +// BenchmarkVerifyEd25519RawMessage shows the cost curve the pricing has to +// track. The gas/us column is the one to read: under the old flat price it fell +// away as the message grew (the same 4000 gas bought steadily more CPU), which +// is the finding. With the per-word term it holds up instead. +// +// go test ./precompiles/usigverifier/ -run '^$' -bench VerifyEd25519RawMessage -benchmem +func BenchmarkVerifyEd25519RawMessage(b *testing.B) { + p, err := NewPrecompile() + require.NoError(b, err) + + priv := ed25519.NewKeyFromSeed(testSeed) + pub := priv.Public().(ed25519.PublicKey) + method := ABI.Methods[VerifyEd25519RawMessageMethod] + + for _, msgLen := range []int{32, 1024, 8 * 1024, 64 * 1024, MaxEd25519MessageBytes} { + msg := make([]byte, msgLen) + sig := ed25519.Sign(priv, msg) + args := []interface{}{[]byte(pub), msg, sig} + gas := p.RequiredGas(rawMessageCalldata(b, msgLen)) + + b.Run(fmt.Sprintf("msg=%dB", msgLen), func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + if _, err := p.VerifyEd25519RawMessage(&method, args); err != nil { + b.Fatal(err) + } + } + usPerOp := float64(b.Elapsed().Nanoseconds()) / float64(b.N) / 1000 + b.ReportMetric(float64(gas), "gas/op") + b.ReportMetric(float64(gas)/usPerOp, "gas/us") + }) + } +} + +// BenchmarkRequiredGas checks the pricing itself stays cheap — it runs on every +// call, before any validation, so it must not become the expensive part. +// +// go test ./precompiles/usigverifier/ -run '^$' -bench RequiredGas -benchmem +func BenchmarkRequiredGas(b *testing.B) { + p, err := NewPrecompile() + require.NoError(b, err) + + cd, err := ABI.Pack( + VerifyEd25519RawMessageMethod, + make([]byte, ed25519.PublicKeySize), + make([]byte, MaxEd25519MessageBytes), + make([]byte, ed25519.SignatureSize), + ) + require.NoError(b, err) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + if p.RequiredGas(cd) == 0 { + b.Fatal("unexpected zero gas") + } + } +} diff --git a/precompiles/usigverifier/query.go b/precompiles/usigverifier/query.go index ce94da087..a01c9f278 100644 --- a/precompiles/usigverifier/query.go +++ b/precompiles/usigverifier/query.go @@ -57,7 +57,8 @@ func (p Precompile) VerifyEd25519( // VerifyEd25519RawMessage verifies a signature over raw message bytes — // standard Ed25519 semantics. Use this when the signer used the conventional -// ed25519.Sign(privKey, rawBytes) API. +// ed25519.Sign(privKey, rawBytes) API. Messages larger than +// MaxEd25519MessageBytes are rejected. func (p Precompile) VerifyEd25519RawMessage( method *abi.Method, args []interface{}, @@ -73,6 +74,13 @@ func (p Precompile) VerifyEd25519RawMessage( return nil, fmt.Errorf("invalid message type") } + // Hard size limit. RequiredGas already prices the message per 32-byte word, + // but this is a view method a contract can loop cheaply from memory, so the + // length is bounded outright rather than only priced. + if len(message) > MaxEd25519MessageBytes { + return nil, fmt.Errorf("message too large: %d bytes, max %d", len(message), MaxEd25519MessageBytes) + } + signature, ok := args[2].([]byte) if !ok { return nil, fmt.Errorf("invalid signature type") diff --git a/precompiles/usigverifier/usigverifier.go b/precompiles/usigverifier/usigverifier.go index 33b87d42b..2a1795aed 100644 --- a/precompiles/usigverifier/usigverifier.go +++ b/precompiles/usigverifier/usigverifier.go @@ -2,7 +2,9 @@ package usigverifier import ( "embed" + "encoding/binary" "fmt" + "math" storetypes "cosmossdk.io/store/types" "github.com/ethereum/go-ethereum/accounts/abi" @@ -14,11 +16,25 @@ import ( const ( USigVerifierPrecompileAddress = "0xEC00000000000000000000000000000000000001" - // VerifyEd25519Gas is the gas cost for verifying an Ed25519 signature. + // VerifyEd25519Gas is the gas cost for verifying an Ed25519 signature over a + // bytes32 digest. The verified message is always the 66-byte ASCII hex form + // of that digest, so the work does not vary with the calldata — flat is the + // honest price here. VerifyEd25519Gas uint64 = 4000 - // VerifyEd25519RawMessageGas matches VerifyEd25519Gas — same Ed25519 - // verification cost, only the message-prep step differs (no hex encoding). - VerifyEd25519RawMessageGas uint64 = 4000 + // VerifyEd25519RawMessageBaseGas is the fixed part of a raw-message + // verification: the Ed25519 curve arithmetic, which dominates below ~8 KiB. + VerifyEd25519RawMessageBaseGas uint64 = 4000 + // VerifyEd25519RawMessagePerWordGas is charged for every 32-byte word of the + // message on top of the base, so that the SHA-512 pass Ed25519 makes over the + // whole message is paid for. Priced off the EVM SHA-256 precompile, which + // charges 12 gas per 32-byte word for comparable hashing work. + VerifyEd25519RawMessagePerWordGas uint64 = 12 + // MaxEd25519MessageBytes hard-caps the message a raw-message verification + // accepts (128 KiB, the same limit used for gateway payloads). This is a view + // method, so a contract can hold one large message in memory and loop + // STATICCALLs over it, paying the calldata only once; on that path a price + // curve alone is not a defence, the size has to be bounded outright. + MaxEd25519MessageBytes = 128 * 1024 ) var _ vm.PrecompiledContract = &Precompile{} @@ -63,6 +79,8 @@ func NewPrecompile() (*Precompile, error) { return p, nil } +// RequiredGas is charged before Run executes, so it runs on unvalidated, +// attacker-controlled calldata and must never panic. func (p Precompile) RequiredGas(input []byte) uint64 { // NOTE: This check avoid panicking when trying to decode the method ID if len(input) < 4 { @@ -79,12 +97,77 @@ func (p Precompile) RequiredGas(input []byte) uint64 { case VerifyEd25519Method: return VerifyEd25519Gas case VerifyEd25519RawMessageMethod: - return VerifyEd25519RawMessageGas + return verifyEd25519RawMessageGas(rawMessageLen(input)) default: return p.Precompile.RequiredGas(input, p.IsTransaction(method)) } } +// verifyEd25519RawMessageGas prices a raw-message verification: a flat base for +// the curve arithmetic plus a per-word term for the hash pass over the message. +// A msgLen past MaxEd25519MessageBytes is clamped — Run rejects those calls, and +// clamping keeps the charge bounded (and overflow-free) for a calldata that +// declares an absurd length. +func verifyEd25519RawMessageGas(msgLen uint64) uint64 { + if msgLen > MaxEd25519MessageBytes { + msgLen = MaxEd25519MessageBytes + } + + words := (msgLen + 31) / 32 + + return VerifyEd25519RawMessageBaseGas + words*VerifyEd25519RawMessagePerWordGas +} + +// rawMessageLen recovers the declared length of the `message` argument of +// verifyEd25519RawMessage(bytes,bytes,bytes) straight out of the ABI-encoded +// calldata, without decoding the payload. `input` includes the 4-byte method ID. +// +// Layout: one 32-byte head slot per argument holding the offset of its tail, +// then each dynamic tail starting with a 32-byte length. `message` is argument +// index 1. Calldata that does not parse is priced at 0 extra — Run reverts on it +// before any verification happens — while a length too large for a uint64 is +// reported as the maximum so it prices at the cap instead of the base. +func rawMessageLen(input []byte) uint64 { + const ( + wordSize = 32 + messageArgIdx = 1 + ) + + if len(input) < 4 { + return 0 + } + args := input[4:] + + head := messageArgIdx * wordSize + if len(args) < head+wordSize { + return 0 + } + + offset, ok := abiWordToUint64(args[head : head+wordSize]) + if !ok || offset > uint64(len(args)) || uint64(len(args))-offset < wordSize { + return 0 + } + + length, ok := abiWordToUint64(args[offset : offset+wordSize]) + if !ok { + return math.MaxUint64 + } + + return length +} + +// abiWordToUint64 reads a big-endian 32-byte ABI word as a uint64. ok is false +// when the word does not fit one, i.e. its top 24 bytes are not all zero. +func abiWordToUint64(word []byte) (value uint64, ok bool) { + for _, b := range word[:len(word)-8] { + if b != 0 { + return 0, false + } + } + + return binary.BigEndian.Uint64(word[len(word)-8:]), true +} + func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz []byte, err error) { if len(contract.Input) < 4 { return nil, vm.ErrExecutionReverted From 2b07bccd05a293b2ba333698ab5d4f56ea1eb265 Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Wed, 26 Aug 2026 19:50:32 +0530 Subject: [PATCH 43/60] fix: F-2026-18184 | [Dual Defense] Empty SVM CEA Recipient Encodes to Invalid Address and Strands Outbound (#341) * fix(svm): resolve empty outbound recipient to the sender's CEA Honours the gateway's bytes("") parking convention on withdraw instead of rejecting "0x" pre-sign, which stranded the outbound PENDING with the PRC20 already burned. F-2026-18184. * docs(svm): trim the parking sentinel comments and drop an incorrect claim * test(svm): pin pre-sentinel recipient behaviour and run the parked path on devnet --------- Co-authored-by: aman035 --- universalClient/chains/svm/tx_builder.go | 95 +++- universalClient/chains/svm/tx_builder_test.go | 453 +++++++++++++++++- 2 files changed, 528 insertions(+), 20 deletions(-) diff --git a/universalClient/chains/svm/tx_builder.go b/universalClient/chains/svm/tx_builder.go index 9df910ecd..cb472e06d 100644 --- a/universalClient/chains/svm/tx_builder.go +++ b/universalClient/chains/svm/tx_builder.go @@ -299,14 +299,11 @@ func (tb *TxBuilder) GetOutboundSigningRequest( // - Withdraw (id=1): the wallet that receives the funds (target = recipient) // - Execute (id=2): the target program to CPI into (target = destination_program) // - Revert (id=3,4): the wallet that gets the refund - var recipientPubkey solana.PublicKey - recipientPubkey, err = solana.PublicKeyFromBase58(data.Recipient) + // + // An empty recipient is the parking sentinel; see resolveRecipient. + recipientPubkey, recipientParked, err := tb.resolveRecipient(data.Recipient, sender) if err != nil { - hexBytes, hexErr := hex.DecodeString(removeHexPrefix(data.Recipient)) - if hexErr != nil || len(hexBytes) != 32 { - return nil, fmt.Errorf("invalid recipient address format (expected Solana Pubkey): %s", data.Recipient) - } - recipientPubkey = solana.PublicKeyFromBytes(hexBytes) + return nil, err } // --- Determine instruction ID and decode payload --- @@ -392,6 +389,10 @@ func (tb *TxBuilder) GetOutboundSigningRequest( } } + if err := checkParkedRecipientScope(recipientParked, instructionID); err != nil { + return nil, err + } + // --- Construct the TSS message and hash it --- // This message is what TSS validators sign. The gateway contract reconstructs // the same message on-chain and verifies the signature matches. @@ -772,13 +773,11 @@ func (tb *TxBuilder) BuildOutboundTransaction( gasFee, _ = strconv.ParseUint(data.GasFee, 10, 64) } - recipientPubkey, err := solana.PublicKeyFromBase58(data.Recipient) + // Must resolve identically to GetOutboundSigningRequest, or the accounts list + // would not match the pubkey already bound into the TSS message. + recipientPubkey, recipientParked, err := tb.resolveRecipient(data.Recipient, sender) if err != nil { - hexBytes, hexErr := hex.DecodeString(removeHexPrefix(data.Recipient)) - if hexErr != nil || len(hexBytes) != 32 { - return nil, 0, fmt.Errorf("invalid recipient address format: %s", data.Recipient) - } - recipientPubkey = solana.PublicKeyFromBytes(hexBytes) + return nil, 0, err } revertMsgBytes, err := hex.DecodeString(removeHexPrefix(data.RevertMsg)) @@ -824,6 +823,10 @@ func (tb *TxBuilder) BuildOutboundTransaction( } } + if err := checkParkedRecipientScope(recipientParked, instructionID); err != nil { + return nil, 0, err + } + // --- Derive PDAs --- configPDA, _, err := solana.FindProgramAddress([][]byte{configSeed}, tb.gatewayAddress) if err != nil { @@ -1098,13 +1101,9 @@ func (tb *TxBuilder) BuildRefRouteTransactions( gasFee, _ = strconv.ParseUint(data.GasFee, 10, 64) } - recipientPubkey, err := solana.PublicKeyFromBase58(data.Recipient) + recipientPubkey, recipientParked, err := tb.resolveRecipient(data.Recipient, sender) if err != nil { - hexBytes, hexErr := hex.DecodeString(removeHexPrefix(data.Recipient)) - if hexErr != nil || len(hexBytes) != 32 { - return nil, nil, solana.PublicKey{}, fmt.Errorf("invalid recipient address format: %s", data.Recipient) - } - recipientPubkey = solana.PublicKeyFromBytes(hexBytes) + return nil, nil, solana.PublicKey{}, err } // Decode payload — ref route is execute-only, so we require an instruction_id of 2. @@ -1127,6 +1126,9 @@ func (tb *TxBuilder) BuildRefRouteTransactions( if instructionID != 2 { return nil, nil, solana.PublicKey{}, fmt.Errorf("ref route only valid for execute mode (instruction_id=2), got %d", instructionID) } + if err := checkParkedRecipientScope(recipientParked, instructionID); err != nil { + return nil, nil, solana.PublicKey{}, err + } if len(ixData) == 0 { return nil, nil, solana.PublicKey{}, fmt.Errorf("ref route requires non-empty ix_data") } @@ -1291,6 +1293,61 @@ func isNativeAsset(addr string) bool { return false } +// An empty recipient is the gateway's sentinel for parking funds in the caller's +// CEA rather than forwarding them to a wallet. Core hex-encodes the raw event +// bytes, so it arrives as "0x". The builder used to reject that pre-sign, which +// stranded the outbound PENDING with the PRC20 already burned. + +// isParkedRecipient reports whether recipient decodes to zero bytes. Tested that +// way rather than against the literal "0x" so "" and "0X" are covered too; +// anything decoding to a byte or more is a real address. +func isParkedRecipient(recipient string) bool { + s := strings.TrimSpace(recipient) + if len(s) >= 2 && (s[:2] == "0x" || s[:2] == "0X") { + s = s[2:] + } + decoded, err := hex.DecodeString(s) + return err == nil && len(decoded) == 0 +} + +// resolveRecipient converts the outbound recipient into a Solana pubkey, +// resolving the sentinel to the sender's CEA PDA. Callers must then run +// checkParkedRecipientScope once the instruction id is known. Shared by the +// signing and build paths so both derive the same pubkey. +func (tb *TxBuilder) resolveRecipient(recipient string, sender [20]byte) (solana.PublicKey, bool, error) { + if isParkedRecipient(recipient) { + ceaAuthorityPDA, _, err := solana.FindProgramAddress([][]byte{ceaAuthoritySeed, sender[:]}, tb.gatewayAddress) + if err != nil { + return solana.PublicKey{}, true, fmt.Errorf("failed to derive cea_authority PDA for parked recipient: %w", err) + } + return ceaAuthorityPDA, true, nil + } + + recipientPubkey, err := solana.PublicKeyFromBase58(recipient) + if err != nil { + hexBytes, hexErr := hex.DecodeString(removeHexPrefix(recipient)) + if hexErr != nil || len(hexBytes) != 32 { + return solana.PublicKey{}, false, fmt.Errorf("invalid recipient address format (expected Solana Pubkey): %s", recipient) + } + recipientPubkey = solana.PublicKeyFromBytes(hexBytes) + } + return recipientPubkey, false, nil +} + +// checkParkedRecipientScope confines the sentinel to withdraw (id=1), the only +// path where the recipient is a fund destination. On execute it is the CPI +// target, and on revert/rescue it is an observed source-chain address, so an +// empty value there means the outbound is malformed. +func checkParkedRecipientScope(parked bool, instructionID uint8) error { + if !parked || instructionID == 1 { + return nil + } + return fmt.Errorf( + "empty recipient parks funds in the sender CEA and is only valid for withdraw (instruction_id=1), got instruction_id=%d", + instructionID, + ) +} + // ============================================================================= // PDA Derivation & On-Chain Data // ============================================================================= diff --git a/universalClient/chains/svm/tx_builder_test.go b/universalClient/chains/svm/tx_builder_test.go index c4696d916..c1f5cb3e6 100644 --- a/universalClient/chains/svm/tx_builder_test.go +++ b/universalClient/chains/svm/tx_builder_test.go @@ -1,6 +1,7 @@ package svm import ( + "encoding/base64" "context" "crypto/ecdsa" crand "crypto/rand" @@ -2100,7 +2101,9 @@ const ( // Uses the hardcoded Solana relayer keypair (AdWDRaQfvWJqW4TaxTrXP5WogCWJMJBrtBfGjjHUDADM). func setupDevnetSimulation(t *testing.T) (*RPCClient, *TxBuilder) { - t.Skip("skipping simulation tests") // DELIBERATELY SKIPPING SIMULATION TESTS + if os.Getenv("RUN_SVM_SIM") != "1" { + t.Skip("skipping simulation tests; set RUN_SVM_SIM=1 to run against devnet") + } t.Helper() if testing.Short() { t.Skip("skipping simulation test in short mode") @@ -2885,3 +2888,451 @@ func newBlockhashOnlyBuilder(t *testing.T) *TxBuilder { require.NoError(t, err) return builder } + + + +// --------------------------------------------------------------------------- +// Empty-recipient parking sentinel (F-2026-18184) +// +// The PC gateway documents bytes("") as "park funds in the caller's CEA". +// Core hex-encodes the raw event bytes unconditionally, so that reaches the +// builder as the string "0x". The builder used to reject it pre-sign, which +// stranded the outbound PENDING forever with the PRC20 already burned. +// --------------------------------------------------------------------------- + +// parkedCEA returns the CEA PDA the gateway derives for sender — the address a +// parked recipient must resolve to. Derived independently of resolveRecipient +// so the test pins the seed rather than the implementation. +func parkedCEA(t *testing.T, gateway solana.PublicKey, sender [20]byte) solana.PublicKey { + t.Helper() + pda, _, err := solana.FindProgramAddress([][]byte{[]byte("push_identity"), sender[:]}, gateway) + require.NoError(t, err) + return pda +} + +func TestIsParkedRecipient(t *testing.T) { + tests := []struct { + name string + recipient string + want bool + }{ + {"core's encoding of bytes(\"\")", "0x", true}, + {"bare empty string", "", true}, + {"uppercase prefix", "0X", true}, + {"surrounding whitespace", " 0x ", true}, + {"one zero byte is a real value, not the sentinel", "0x00", false}, + {"32 zero bytes is a real pubkey, not the sentinel", "0x" + strings.Repeat("00", 32), false}, + {"base58 pubkey", testGatewayAddress, false}, + {"32-byte hex pubkey", "0x" + strings.Repeat("ab", 32), false}, + {"garbage", "not-an-address", false}, + {"lone 0", "0", false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, isParkedRecipient(tt.recipient)) + }) + } +} + +func TestResolveRecipient(t *testing.T) { + builder := newTestBuilder(t) + sender := makeSender(0xCC) + expectedCEA := parkedCEA(t, builder.gatewayAddress, sender) + + t.Run("sentinel resolves to the sender's CEA PDA", func(t *testing.T) { + pubkey, parked, err := builder.resolveRecipient("0x", sender) + require.NoError(t, err) + assert.True(t, parked) + assert.Equal(t, expectedCEA, pubkey) + }) + + t.Run("CEA is per-sender", func(t *testing.T) { + other, _, err := builder.resolveRecipient("0x", makeSender(0xDD)) + require.NoError(t, err) + assert.NotEqual(t, expectedCEA, other) + }) + + t.Run("base58 recipient parses unchanged", func(t *testing.T) { + wallet := solana.NewWallet().PublicKey() + pubkey, parked, err := builder.resolveRecipient(wallet.String(), sender) + require.NoError(t, err) + assert.False(t, parked) + assert.Equal(t, wallet, pubkey) + }) + + t.Run("32-byte hex recipient parses unchanged", func(t *testing.T) { + wallet := solana.NewWallet().PublicKey() + pubkey, parked, err := builder.resolveRecipient("0x"+hex.EncodeToString(wallet.Bytes()), sender) + require.NoError(t, err) + assert.False(t, parked) + assert.Equal(t, wallet, pubkey) + }) + + t.Run("malformed recipient still errors", func(t *testing.T) { + _, parked, err := builder.resolveRecipient("not-an-address", sender) + assert.False(t, parked, "a real parse failure must not be mistaken for parking") + require.Error(t, err) + assert.Contains(t, err.Error(), "invalid recipient address format") + }) + + t.Run("short hex is not 32 bytes and still errors", func(t *testing.T) { + _, _, err := builder.resolveRecipient("0xdeadbeef", sender) + require.Error(t, err) + assert.Contains(t, err.Error(), "invalid recipient address format") + }) +} + +func TestCheckParkedRecipientScope(t *testing.T) { + t.Run("parking is accepted on withdraw only", func(t *testing.T) { + require.NoError(t, checkParkedRecipientScope(true, 1)) + for _, id := range []uint8{0, 2, 3, 4} { + err := checkParkedRecipientScope(true, id) + require.Error(t, err, "instruction_id=%d", id) + assert.Contains(t, err.Error(), "only valid for withdraw") + } + }) + + t.Run("a real recipient is never scoped", func(t *testing.T) { + for _, id := range []uint8{0, 1, 2, 3, 4} { + require.NoError(t, checkParkedRecipientScope(false, id), "instruction_id=%d", id) + } + }) +} + +// newParkingRPCBuilder drives the real RPCClient against a local JSON-RPC +// server answering the two calls the outbound path makes: getAccountInfo (the +// TSS PDA, for the chain id bound into the signed message) and +// getLatestBlockhash (transaction assembly). The relayer keypair is written to +// disk so BuildOutboundTransaction can sign. +func newParkingRPCBuilder(t *testing.T, tssChainID string) *TxBuilder { + t.Helper() + + tssData := buildMockTSSPDAData([20]byte{}, tssChainID, 255) + accountInfoResp := fmt.Sprintf( + `{"jsonrpc":"2.0","id":1,"result":{"context":{"slot":1},"value":{"data":["%s","base64"],"executable":false,"lamports":1,"owner":"11111111111111111111111111111111","rentEpoch":0,"space":%d}}}`, + base64.StdEncoding.EncodeToString(tssData), len(tssData), + ) + blockhash := makeTxID(0x42) + blockhashResp := fmt.Sprintf( + `{"jsonrpc":"2.0","id":1,"result":{"context":{"slot":1},"value":{"blockhash":"%s","lastValidBlockHeight":100}}}`, + solana.Hash(blockhash).String(), + ) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + body, _ := io.ReadAll(r.Body) + w.Header().Set("Content-Type", "application/json") + switch { + case strings.Contains(string(body), `"getHealth"`): + _, _ = w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":"ok"}`)) + case strings.Contains(string(body), `"getAccountInfo"`): + _, _ = w.Write([]byte(accountInfoResp)) + case strings.Contains(string(body), `"getLatestBlockhash"`): + _, _ = w.Write([]byte(blockhashResp)) + default: + _, _ = w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":null}`)) + } + })) + t.Cleanup(server.Close) + + rpcClient, err := NewRPCClient([]string{server.URL}, "", zerolog.Nop()) + require.NoError(t, err) + + tmpDir := t.TempDir() + relayerDir := filepath.Join(tmpDir, "relayer") + require.NoError(t, os.MkdirAll(relayerDir, 0o755)) + require.NoError(t, os.WriteFile( + filepath.Join(relayerDir, "solana.json"), + []byte(testSolanaKeypairJSON), + 0o600, + )) + + builder, err := NewTxBuilder(rpcClient, "solana:devnet", testGatewayAddress, tmpDir, zerolog.Nop(), nil) + require.NoError(t, err) + return builder +} + +// parkingTestSender is the 20-byte EVM sender used by the outbound fixtures. +var parkingTestSender = makeSender(0x7E) + +// newWithdrawEvent is the happy withdraw template: native SOL, positive amount, +// empty payload — the exact shape a PRC20 withdraw to Solana produces +// (_fetchTxType: no payload + funds => TX_TYPE.FUNDS => instruction_id 1). +func newWithdrawEvent(recipient string) *uetypes.OutboundCreatedEvent { + txID := makeTxID(0xA1) + utxID := makeTxID(0xB2) + return &uetypes.OutboundCreatedEvent{ + TxID: "0x" + hex.EncodeToString(txID[:]), + UniversalTxId: "0x" + hex.EncodeToString(utxID[:]), + DestinationChain: "solana:devnet", + Sender: "0x" + hex.EncodeToString(parkingTestSender[:]), + Recipient: recipient, + Amount: "1000000", + AssetAddr: "0x0000000000000000000000000000000000000000", + GasFee: "5000", + SigningDeadline: 1735689600, + TxType: "FUNDS", + } +} + +// expectedWithdrawHash is the TSS message hash for the withdraw template with +// target as the bound target_program. For withdraw with an empty payload the +// builder copies the recipient into target_program, so this pins which pubkey +// the signature commits to. +func expectedWithdrawHash(t *testing.T, tb *TxBuilder, tssChainID string, target solana.PublicKey) []byte { + t.Helper() + txID := makeTxID(0xA1) + utxID := makeTxID(0xB2) + var targetProgram [32]byte + copy(targetProgram[:], target.Bytes()) + + hash, err := tb.constructTSSMessage( + 1, tssChainID, 1735689600, 1000000, + txID, utxID, parkingTestSender, [32]byte{}, 5000, + targetProgram, nil, nil, + [32]byte{}, [32]byte{}, nil, + ) + require.NoError(t, err) + return hash +} + +func TestGetOutboundSigningRequest_ParkedRecipient(t *testing.T) { + const tssChainID = "devnet" + builder := newParkingRPCBuilder(t, tssChainID) + ctx := context.Background() + cea := parkedCEA(t, builder.gatewayAddress, parkingTestSender) + + t.Run("sentinel signs against the sender's CEA", func(t *testing.T) { + req, err := builder.GetOutboundSigningRequest(ctx, newWithdrawEvent("0x"), 0) + require.NoError(t, err, "a parked recipient must produce a signing request, not strand the outbound") + require.NotNil(t, req) + assert.Equal(t, + expectedWithdrawHash(t, builder, tssChainID, cea), + req.SigningHash, + "signed target_program must be the CEA PDA for [\"push_identity\", sender]", + ) + }) + + t.Run("base58 recipient is unaffected", func(t *testing.T) { + wallet := solana.NewWallet().PublicKey() + req, err := builder.GetOutboundSigningRequest(ctx, newWithdrawEvent(wallet.String()), 0) + require.NoError(t, err) + assert.Equal(t, expectedWithdrawHash(t, builder, tssChainID, wallet), req.SigningHash) + assert.NotEqual(t, expectedWithdrawHash(t, builder, tssChainID, cea), req.SigningHash) + }) + + t.Run("32-byte hex recipient is unaffected", func(t *testing.T) { + wallet := solana.NewWallet().PublicKey() + ev := newWithdrawEvent("0x" + hex.EncodeToString(wallet.Bytes())) + req, err := builder.GetOutboundSigningRequest(ctx, ev, 0) + require.NoError(t, err) + assert.Equal(t, expectedWithdrawHash(t, builder, tssChainID, wallet), req.SigningHash) + }) + + t.Run("malformed recipient still errors", func(t *testing.T) { + _, err := builder.GetOutboundSigningRequest(ctx, newWithdrawEvent("not-an-address"), 0) + require.Error(t, err, "the sentinel must not become a catch-all that swallows real parse failures") + assert.Contains(t, err.Error(), "invalid recipient address format") + }) +} + +// Parking is a withdraw-only convention. On execute the recipient is the +// program to CPI into, and on revert/rescue it is an observed source-chain +// address that can never be the sentinel — so an empty recipient there is a +// malformed outbound and must stay an error. +func TestGetOutboundSigningRequest_ParkedRecipientScopedToWithdraw(t *testing.T) { + const tssChainID = "devnet" + builder := newParkingRPCBuilder(t, tssChainID) + ctx := context.Background() + + t.Run("execute (id=2) rejects the sentinel", func(t *testing.T) { + ev := newWithdrawEvent("0x") + ev.TxType = "FUNDS_AND_PAYLOAD" + ev.Payload = buildExecutePayloadForTest(t, []GatewayAccountMeta{}, []byte{0xDE, 0xAD}, 2, makeTxID(0x99)) + _, err := builder.GetOutboundSigningRequest(ctx, ev, 0) + require.Error(t, err) + assert.Contains(t, err.Error(), "only valid for withdraw") + }) + + t.Run("revert (id=3) rejects the sentinel", func(t *testing.T) { + ev := newWithdrawEvent("0x") + ev.TxType = "INBOUND_REVERT" + _, err := builder.GetOutboundSigningRequest(ctx, ev, 0) + require.Error(t, err) + assert.Contains(t, err.Error(), "only valid for withdraw") + }) + + t.Run("rescue (id=4) rejects the sentinel", func(t *testing.T) { + ev := newWithdrawEvent("0x") + ev.TxType = "RESCUE_FUNDS" + _, err := builder.GetOutboundSigningRequest(ctx, ev, 0) + require.Error(t, err) + assert.Contains(t, err.Error(), "only valid for withdraw") + }) + + t.Run("withdraw payload (id=1) accepts the sentinel", func(t *testing.T) { + ev := newWithdrawEvent("0x") + ev.Payload = buildExecutePayloadForTest(t, []GatewayAccountMeta{}, nil, 1, [32]byte{}) + _, err := builder.GetOutboundSigningRequest(ctx, ev, 0) + require.NoError(t, err) + }) + + t.Run("ref route rejects the sentinel — execute-only", func(t *testing.T) { + ev := newWithdrawEvent("0x") + ev.TxType = "FUNDS_AND_PAYLOAD" + ev.Payload = buildExecutePayloadForTest(t, []GatewayAccountMeta{}, []byte{0xDE, 0xAD}, 2, makeTxID(0x99)) + _, _, _, err := builder.BuildRefRouteTransactions( + ctx, + &common.UnsignedSigningReq{SigningHash: make([]byte, 32)}, + ev, + make([]byte, 65), + ) + require.Error(t, err) + assert.Contains(t, err.Error(), "only valid for withdraw") + }) +} + +// BuildOutboundTransaction re-parses the event independently of +// GetOutboundSigningRequest. If the two disagreed the accounts list would not +// match the pubkey already bound into the TSS signature, so the built tx must +// carry the same CEA. +func TestBuildOutboundTransaction_ParkedRecipient(t *testing.T) { + const tssChainID = "devnet" + builder := newParkingRPCBuilder(t, tssChainID) + ctx := context.Background() + cea := parkedCEA(t, builder.gatewayAddress, parkingTestSender) + + req, err := builder.GetOutboundSigningRequest(ctx, newWithdrawEvent("0x"), 0) + require.NoError(t, err) + + tx, instructionID, err := builder.BuildOutboundTransaction(ctx, req, newWithdrawEvent("0x"), make([]byte, 65)) + require.NoError(t, err, "a parked recipient must build a broadcastable tx") + require.NotNil(t, tx) + assert.Equal(t, uint8(1), instructionID) + + // Account #4 is cea_authority and #9 is the withdraw recipient (native SOL + // layout). Parking makes them the same PDA — that is what tells the gateway + // to leave the funds where finalize already staged them. + gatewayIx := tx.Message.Instructions[len(tx.Message.Instructions)-1] + metas, err := gatewayIx.ResolveInstructionAccounts(&tx.Message) + require.NoError(t, err) + require.GreaterOrEqual(t, len(metas), 9) + assert.Equal(t, cea, metas[3].PublicKey, "cea_authority slot") + assert.Equal(t, cea, metas[8].PublicKey, "recipient slot must be the CEA when parked") +} + +// The parked path must be structurally valid against the deployed gateway, not +// just against our own mocks. The devnet TSS PDA holds a real signer, so a test +// signature can never pass tss.rs. That still pins what we need: Anchor +// validates every account constraint before the handler runs, so reaching +// TssAuthFailed proves the program accepted our cea_authority derivation +// against its own `seeds = [CEA_SEED, push_account]`. A wrong PDA would fail +// earlier, with ConstraintSeeds, and never reach the signature check. +func TestSimulate_Withdraw_ParkedRecipient(t *testing.T) { + rpcClient, builder := setupDevnetSimulation(t) + defer rpcClient.Close() + + withdrawPayload := "0x" + hex.EncodeToString(buildMockWithdrawPayload()) + + stageOf := func(recipient string) string { + data, evmKey := newDevnetOutbound(t, "1000000", "", withdrawPayload, "", "FUNDS") + if recipient != "" { + data.Recipient = recipient + } + result, err := buildAndSimulate(t, rpcClient, builder, data, evmKey) + require.NoError(t, err, "the parked recipient must build and reach the cluster") + require.NotNil(t, result) + return fmt.Sprintf("%v", result.Err) + } + + parked := stageOf("0x") + normal := stageOf("") + t.Logf("parked on-chain result: %s", parked) + t.Logf("normal on-chain result: %s", normal) + + assert.Equal(t, normal, parked, + "a parked withdraw must reach the same on-chain stage as an ordinary one") + assert.NotContains(t, parked, "2006", "ConstraintSeeds means the CEA derivation disagrees with the gateway") +} + +// legacyResolveRecipient is the parsing that ran at both call sites before the +// sentinel was introduced, kept verbatim as an oracle. Every recipient that +// works today must resolve through the new path to the same pubkey. +func legacyResolveRecipient(recipient string) (solana.PublicKey, error) { + pk, err := solana.PublicKeyFromBase58(recipient) + if err != nil { + hexBytes, hexErr := hex.DecodeString(removeHexPrefix(recipient)) + if hexErr != nil || len(hexBytes) != 32 { + return solana.PublicKey{}, fmt.Errorf("invalid recipient address format (expected Solana Pubkey): %s", recipient) + } + pk = solana.PublicKeyFromBytes(hexBytes) + } + return pk, nil +} + +func TestResolveRecipient_MatchesPreSentinelBehaviour(t *testing.T) { + builder := newParkingRPCBuilder(t, "devnet") + wallet := solana.NewWallet().PublicKey() + + cases := []string{ + wallet.String(), + "AdWDRaQfvWJqW4TaxTrXP5WogCWJMJBrtBfGjjHUDADM", + "11111111111111111111111111111111", + solana.SystemProgramID.String(), + "0x" + hex.EncodeToString(wallet.Bytes()), + hex.EncodeToString(wallet.Bytes()), + "0x" + hex.EncodeToString(make([]byte, 32)), + "not-an-address", + "0xdeadbeef", + "0x" + hex.EncodeToString(make([]byte, 20)), + } + + for _, recipient := range cases { + t.Run(recipient, func(t *testing.T) { + want, wantErr := legacyResolveRecipient(recipient) + got, parked, gotErr := builder.resolveRecipient(recipient, parkingTestSender) + + assert.False(t, parked, "a non-empty recipient must never be treated as the sentinel") + if wantErr != nil { + require.Error(t, gotErr, "an input rejected before must still be rejected") + assert.Equal(t, wantErr.Error(), gotErr.Error()) + return + } + require.NoError(t, gotErr, "an input accepted before must still be accepted") + assert.Equal(t, want, got, "resolved pubkey drifted from pre-sentinel behaviour") + }) + } +} + +// Frozen hashes. The signing hash is what the TSS signs and what the gateway +// re-derives, so a change here is a consensus break, not a test update. +func TestGetOutboundSigningRequest_GoldenHashes(t *testing.T) { + builder := newParkingRPCBuilder(t, "devnet") + ctx := context.Background() + + t.Run("ordinary base58 recipient", func(t *testing.T) { + req, err := builder.GetOutboundSigningRequest(ctx, newWithdrawEvent("AdWDRaQfvWJqW4TaxTrXP5WogCWJMJBrtBfGjjHUDADM"), 0) + require.NoError(t, err) + assert.Equal(t, + "ba5378d8db7d82a64e3b5770845cc06356974cc020618ae2ab65cd2d027b5f5c", + hex.EncodeToString(req.SigningHash), + ) + }) + + t.Run("parked recipient", func(t *testing.T) { + req, err := builder.GetOutboundSigningRequest(ctx, newWithdrawEvent("0x"), 0) + require.NoError(t, err) + assert.Equal(t, + "c2ef605c5e3ce32a8c6b7f3db4898741c0fbeef464660970a2487191f55e4403", + hex.EncodeToString(req.SigningHash), + ) + }) +} + +// The scope check runs on every instruction id, so it has to be invisible to +// outbounds carrying a real recipient. +func TestCheckParkedRecipientScope_AllowsEveryInstructionWhenNotParked(t *testing.T) { + for id := uint8(0); id <= 5; id++ { + require.NoError(t, checkParkedRecipientScope(false, id), "instruction_id=%d", id) + } +} \ No newline at end of file From c4d2b02d7f74bd49d36c3cde357255a3ec2ff700 Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Wed, 26 Aug 2026 20:16:18 +0530 Subject: [PATCH 44/60] test(uexecutor): align unreachable-pending revert assertions with F-2026-18823 The two F-2026-18147 tests were written before #330 landed and asserted the revert outbound is PENDING and queued. #330 aborts a revert whose gas metadata is unresolvable, which is what the harness's UniversalCore stub produces, so they now assert ABORTED and not-queued, matching the sibling happy-path test. --- .../uexecutor/revert_stuck_inbound_test.go | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/test/integration/uexecutor/revert_stuck_inbound_test.go b/test/integration/uexecutor/revert_stuck_inbound_test.go index e64faa990..c574fcf1a 100644 --- a/test/integration/uexecutor/revert_stuck_inbound_test.go +++ b/test/integration/uexecutor/revert_stuck_inbound_test.go @@ -180,17 +180,27 @@ func TestRevertStuckInbound_PendingUnreachable_ThresholdMet_CreatesRevertOutboun ob := utx.OutboundTx[0] require.Equal(t, resp.OutboundId, ob.Id) require.Equal(t, uexecutortypes.TxType_INBOUND_REVERT, ob.TxType) - require.Equal(t, uexecutortypes.Status_PENDING, ob.OutboundStatus) + // The harness's UniversalCore stub cannot serve getOutboundTxGasAndFees, so the + // revert's gas metadata is unresolvable and F-2026-18823 records it ABORTED + // rather than queueing it for a signature it could never receive. What this test + // pins is that the unreachable-PENDING hatch BUILDS the revert at all; the + // resolvable (PENDING) path is covered by + // x/uexecutor/keeper/build_revert_outbound_test.go. + require.Equal(t, uexecutortypes.Status_ABORTED, ob.OutboundStatus, + "a revert with unresolvable gas metadata must be ABORTED, not PENDING") + require.NotEmpty(t, ob.AbortReason, "ABORTED revert must record why it could not be built") require.Equal(t, inbound.SourceChain, ob.DestinationChain) require.Equal(t, inbound.RevertInstructions.FundRecipient, ob.Recipient) require.Equal(t, inbound.Amount, ob.Amount) require.Equal(t, inbound.AssetAddr, ob.ExternalAssetAddr) - // --- PendingOutbounds index: the refund is actually queued for TSS signing --- - pending, err := chainApp.UexecutorKeeper.PendingOutbounds.Get(ctx, ob.Id) - require.NoError(t, err, "revert outbound must be indexed in PendingOutbounds for UV pickup") - require.Equal(t, ob.Id, pending.OutboundId) - require.Equal(t, utx.Id, pending.UniversalTxId) + // --- PendingOutbounds index --- + // An ABORTED revert must stay out of the signing queue: no ballot can form for + // it and there is no admin abort for outbounds, so an indexed row would be + // permanently stuck. + has, err := chainApp.UexecutorKeeper.PendingOutbounds.Has(ctx, ob.Id) + require.NoError(t, err) + require.False(t, has, "an ABORTED revert must not be indexed in PendingOutbounds") } // TestRevertStuckInbound_PendingUnreachable_BelowThreshold_Accepted covers the @@ -225,8 +235,12 @@ func TestRevertStuckInbound_PendingUnreachable_BelowThreshold_Accepted(t *testin require.Len(t, utx.OutboundTx, 1) require.Equal(t, uexecutortypes.TxType_INBOUND_REVERT, utx.OutboundTx[0].TxType) - _, err = chainApp.UexecutorKeeper.PendingOutbounds.Get(ctx, utx.OutboundTx[0].Id) - require.NoError(t, err, "revert outbound must be queued for UV pickup") + // Unresolvable gas metadata in this harness means the revert is ABORTED and so + // deliberately not queued (F-2026-18823); the hatch opening is what matters here. + require.Equal(t, uexecutortypes.Status_ABORTED, utx.OutboundTx[0].OutboundStatus) + has, err := chainApp.UexecutorKeeper.PendingOutbounds.Has(ctx, utx.OutboundTx[0].Id) + require.NoError(t, err) + require.False(t, has, "an ABORTED revert must not be indexed in PendingOutbounds") } // TestRevertStuckInbound_PendingWithUnvotedVoter_Refused is the guard against From 861fca0ca6295a3897f385a8fe6998732b66e89a Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Wed, 26 Aug 2026 20:26:12 +0530 Subject: [PATCH 45/60] fix: F-2026-18133 | [Dual Defense] Same-Block Validator Jailing Can Snapshot an Oversized Ballot Quorum (#347) Exclude jailed validators from GetEligibleVoters so a validator jailed in BeginBlock is not snapshotted into ballots created later in the same block. --- .../uvalidator/jailed_voter_quorum_test.go | 202 ++++++++++++++++++ x/uvalidator/keeper/validator.go | 14 +- 2 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 test/integration/uvalidator/jailed_voter_quorum_test.go diff --git a/test/integration/uvalidator/jailed_voter_quorum_test.go b/test/integration/uvalidator/jailed_voter_quorum_test.go new file mode 100644 index 000000000..dcf9beb1a --- /dev/null +++ b/test/integration/uvalidator/jailed_voter_quorum_test.go @@ -0,0 +1,202 @@ +package integrationtest + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/stretchr/testify/require" + + "github.com/pushchain/push-chain-node/app" + uexecutortypes "github.com/pushchain/push-chain-node/x/uexecutor/types" + uvalidatortypes "github.com/pushchain/push-chain-node/x/uvalidator/types" +) + +// jailLikeSlashingBeginBlock reproduces exactly what x/slashing does to a +// validator during BeginBlock: it calls staking's Keeper.Jail, which runs +// jailValidator -> sets Validator.Jailed and deletes the power index, and +// never touches Validator.Status. +// +// Crucially it does NOT run staking's EndBlocker, so the bonded -> unbonding +// transition has not happened yet. That is the exact window every transaction +// in the block is processed in. +func jailLikeSlashingBeginBlock(t *testing.T, chainApp *app.ChainApp, ctx sdk.Context, val stakingtypes.Validator) stakingtypes.Validator { + t.Helper() + + consAddr, err := val.GetConsAddr() + require.NoError(t, err) + require.NoError(t, chainApp.StakingKeeper.Jail(ctx, consAddr)) + + valAddr, err := sdk.ValAddressFromBech32(val.OperatorAddress) + require.NoError(t, err) + jailed, err := chainApp.StakingKeeper.GetValidator(ctx, valAddr) + require.NoError(t, err) + return jailed +} + +// TestGetEligibleVoters_ExcludesSameBlockJailedValidator is the F-2026-18133 +// regression suite. +// +// Slashing jails in BeginBlock; staking moves the validator bonded -> +// unbonding only in EndBlocker. For the entire tx-processing phase in between, +// a jailed validator is both Jailed and IsBonded(). Before the fix that +// validator was snapshotted into a new ballot's EligibleVoters, so the frozen +// VotingThreshold ((2*N)/3 + 1) was computed on an inflated N while only N-1 +// signers could actually vote -- stranding the ballot at N <= 3. +func TestGetEligibleVoters_ExcludesSameBlockJailedValidator(t *testing.T) { + t.Run("precondition: a same-block jailed validator still reports IsBonded", func(t *testing.T) { + // This subtest asserts the SDK behaviour the finding depends on. If it + // ever stops holding, the fix below is redundant and this will say so. + chainApp, ctx, validators := setupQueryTest(t, 3) + for _, v := range validators { + setUVStatus(t, chainApp, ctx, v, uvalidatortypes.UVStatus_UV_STATUS_ACTIVE) + } + + jailed := jailLikeSlashingBeginBlock(t, chainApp, ctx, validators[0]) + + require.True(t, jailed.IsJailed(), "staking.Jail must set Validator.Jailed") + require.Equal(t, stakingtypes.Bonded, jailed.Status, + "staking.Jail must NOT touch Validator.Status before EndBlocker") + require.True(t, jailed.IsBonded(), + "IsBonded() is GetStatus()==Bonded, so a jailed validator still passes it -- "+ + "this is precisely why an explicit Jailed gate is required") + }) + + t.Run("jailed validator is excluded from the eligible-voter set", func(t *testing.T) { + chainApp, ctx, validators := setupQueryTest(t, 3) + for _, v := range validators { + setUVStatus(t, chainApp, ctx, v, uvalidatortypes.UVStatus_UV_STATUS_ACTIVE) + } + + before, err := chainApp.UvalidatorKeeper.GetEligibleVoters(ctx) + require.NoError(t, err) + require.Len(t, before, 3, "all three are eligible before the jail") + + jailLikeSlashingBeginBlock(t, chainApp, ctx, validators[0]) + + after, err := chainApp.UvalidatorKeeper.GetEligibleVoters(ctx) + require.NoError(t, err) + require.Len(t, after, 2, "the jailed validator must drop out of the eligible set") + for _, v := range after { + require.NotEqual(t, validators[0].OperatorAddress, v.IdentifyInfo.CoreValidatorAddress, + "jailed validator must not appear among eligible voters") + } + }) + + t.Run("PENDING_JOIN validator jailed in the same block is also excluded", func(t *testing.T) { + // setupQueryTest leaves every UV in PENDING_JOIN, which is an eligible + // lifecycle state. The Jailed gate must apply there too. + chainApp, ctx, validators := setupQueryTest(t, 3) + + jailLikeSlashingBeginBlock(t, chainApp, ctx, validators[2]) + + voters, err := chainApp.UvalidatorKeeper.GetEligibleVoters(ctx) + require.NoError(t, err) + require.Len(t, voters, 2) + for _, v := range voters { + require.NotEqual(t, validators[2].OperatorAddress, v.IdentifyInfo.CoreValidatorAddress) + } + }) + + t.Run("ballot created in the same block freezes a threshold computed on N-1", func(t *testing.T) { + // N = 3 is the worst reachable row from the finding: with the jailed + // validator counted the threshold is (2*3)/3+1 = 3 against only 2 + // possible signers -> permanently stranded. With it excluded the + // threshold is (2*2)/3+1 = 2 -> reachable. + chainApp, ctx, validators := setupQueryTest(t, 3) + for _, v := range validators { + setUVStatus(t, chainApp, ctx, v, uvalidatortypes.UVStatus_UV_STATUS_ACTIVE) + } + + // BeginBlock: slashing jails validators[0]. + jailLikeSlashingBeginBlock(t, chainApp, ctx, validators[0]) + + // Same block, tx-processing phase: a surviving UV observes an inbound, + // which creates the ballot and freezes EligibleVoters + VotingThreshold. + ballot := voteInboundAndLoadBallot(t, chainApp, ctx, validators[1], sameBlockJailInbound) + + // Headline assertion first: the frozen threshold must be computed on + // N-1. Everything else in this subtest is corroboration. + require.Equal(t, int64(2), ballot.VotingThreshold, + "threshold must be (2*2)/3+1 = 2 on the N-1 survivors, not (2*3)/3+1 = 3 on the inflated N") + + require.Len(t, ballot.EligibleVoters, 2, + "the jailed validator must not be snapshotted into the ballot") + require.NotContains(t, ballot.EligibleVoters, validators[0].OperatorAddress, + "jailed validator address must be absent from the frozen voter snapshot") + require.Contains(t, ballot.EligibleVoters, validators[1].OperatorAddress) + require.Contains(t, ballot.EligibleVoters, validators[2].OperatorAddress) + }) + + t.Run("the surviving validators can still finalize that ballot", func(t *testing.T) { + // The liveness half of the finding: with the jailed validator counted, + // the frozen threshold of 3 is unreachable by the 2 survivors and the + // ballot is stranded (only an admin MsgRecomputeBallotQuorum recovers + // it, and DefaultExpiryAfterBlocks = 100_000_000 means it never ages + // out on its own). + chainApp, ctx, validators := setupQueryTest(t, 3) + for _, v := range validators { + setUVStatus(t, chainApp, ctx, v, uvalidatortypes.UVStatus_UV_STATUS_ACTIVE) + } + + jailLikeSlashingBeginBlock(t, chainApp, ctx, validators[0]) + + // First survivor votes: creates the ballot, does not finalize it. + firstVoter, err := sdk.ValAddressFromBech32(validators[1].OperatorAddress) + require.NoError(t, err) + isFinalized, isNew, err := chainApp.UexecutorKeeper.VoteOnInboundBallot(ctx, firstVoter, sameBlockJailInbound) + require.NoError(t, err) + require.True(t, isNew, "the first vote must have created the ballot") + require.False(t, isFinalized, "one vote out of a threshold of two must not finalize") + + // Second (and last) survivor votes: this must be the finalizing vote. + secondVoter, err := sdk.ValAddressFromBech32(validators[2].OperatorAddress) + require.NoError(t, err) + isFinalized, isNew, err = chainApp.UexecutorKeeper.VoteOnInboundBallot(ctx, secondVoter, sameBlockJailInbound) + require.NoError(t, err) + require.False(t, isNew, "second vote must land on the existing ballot") + require.True(t, isFinalized, + "every non-jailed validator has now voted; if this is false the ballot is stranded "+ + "behind a threshold no reachable signer set can meet") + + ballotKey, err := uexecutortypes.GetInboundBallotKey(sameBlockJailInbound) + require.NoError(t, err) + ballot, err := chainApp.UvalidatorKeeper.Ballots.Get(ctx, ballotKey) + require.NoError(t, err) + require.Equal(t, uvalidatortypes.BallotStatus_BALLOT_STATUS_PASSED, ballot.Status, + "the ballot must have reached a terminal PASSED status") + }) +} + +// sameBlockJailInbound is the observation used by the ballot subtests above. +var sameBlockJailInbound = uexecutortypes.Inbound{ + SourceChain: "eip155:11155111", + TxHash: "0xf18133jailedquorum", + LogIndex: "0", +} + +// voteInboundAndLoadBallot casts voter's inbound vote through the real +// uexecutor path (x/uexecutor/keeper/voting.go, the first of the seven +// GetEligibleVoters call sites) and returns the ballot it created. +func voteInboundAndLoadBallot( + t *testing.T, + chainApp *app.ChainApp, + ctx sdk.Context, + voter stakingtypes.Validator, + inbound uexecutortypes.Inbound, +) uvalidatortypes.Ballot { + t.Helper() + + voterAddr, err := sdk.ValAddressFromBech32(voter.OperatorAddress) + require.NoError(t, err) + + _, isNew, err := chainApp.UexecutorKeeper.VoteOnInboundBallot(ctx, voterAddr, inbound) + require.NoError(t, err) + require.True(t, isNew, "the vote must have created the ballot") + + ballotKey, err := uexecutortypes.GetInboundBallotKey(inbound) + require.NoError(t, err) + ballot, err := chainApp.UvalidatorKeeper.Ballots.Get(ctx, ballotKey) + require.NoError(t, err) + return ballot +} diff --git a/x/uvalidator/keeper/validator.go b/x/uvalidator/keeper/validator.go index 2ebddaa26..164da2f01 100644 --- a/x/uvalidator/keeper/validator.go +++ b/x/uvalidator/keeper/validator.go @@ -50,7 +50,7 @@ func (k Keeper) GetValidatorsByStatus(ctx context.Context, status types.UVStatus // // Eligibility requires BOTH: // - UV lifecycle status is ACTIVE or PENDING_JOIN; AND -// - the underlying Cosmos staking validator is bonded and not tombstoned. +// - the underlying Cosmos staking validator is bonded, not jailed and not tombstoned. // // The staking-state filter prevents stranded UVs (still ACTIVE on paper but // unbonded/jailed/tombstoned on the base chain) from inflating the ballot @@ -80,6 +80,18 @@ func (k Keeper) GetEligibleVoters(ctx context.Context) ([]types.UniversalValidat if !sv.IsBonded() { return false, nil } + // A jailed validator is NOT covered by the IsBonded() check above. + // Cosmos SDK's jailValidator sets Validator.Jailed and deletes the + // power index but never touches Validator.Status, and IsBonded() is + // only `GetStatus() == Bonded`. Slashing jails during BeginBlock while + // the bonded -> unbonding transition happens in staking's EndBlocker, + // so for the whole tx-processing phase in between a jailed validator + // still reports IsBonded() == true. Without this gate it is snapshotted + // into a ballot's EligibleVoters and inflates the threshold + // denominator ((2*N)/3 + 1), which strands the ballot at N <= 3. + if sv.IsJailed() { + return false, nil + } consAddr, caErr := sv.GetConsAddr() if caErr != nil { k.Logger().Debug("eligible voter filter: GetConsAddr failed", "validator", addr.String(), "err", caErr) From bd3c2d866cadf65c1e0e9b71671fc25262e305d1 Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Wed, 26 Aug 2026 20:26:16 +0530 Subject: [PATCH 46/60] fix: F-2026-18148 | [Dual Defense] Removed Universal Validator Retains ChainMeta Vote Authority (#348) Gate MsgVoteChainMeta on GetEligibleVoters (ACTIVE/PENDING_JOIN + bonded + not tombstoned) so a removed, still-bonded validator cannot reinsert votes. --- .../vote_chain_meta_eligibility_test.go | 252 ++++++++++++++++++ x/uexecutor/keeper/msg_server.go | 52 +++- 2 files changed, 296 insertions(+), 8 deletions(-) create mode 100644 test/integration/uexecutor/vote_chain_meta_eligibility_test.go diff --git a/test/integration/uexecutor/vote_chain_meta_eligibility_test.go b/test/integration/uexecutor/vote_chain_meta_eligibility_test.go new file mode 100644 index 000000000..637ff068a --- /dev/null +++ b/test/integration/uexecutor/vote_chain_meta_eligibility_test.go @@ -0,0 +1,252 @@ +package integrationtest + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/stretchr/testify/require" + + "github.com/pushchain/push-chain-node/app" + utils "github.com/pushchain/push-chain-node/test/utils" + uvalidatortypes "github.com/pushchain/push-chain-node/x/uvalidator/types" +) + +// coreAccOf returns the account bech32 that signs MsgVoteChainMeta on behalf of +// the given staking validator (the hotkey's grantee target). +func coreAccOf(t *testing.T, val stakingtypes.Validator) string { + t.Helper() + valAddr, err := sdk.ValAddressFromBech32(val.OperatorAddress) + require.NoError(t, err) + return sdk.AccAddress(valAddr).String() +} + +// forceUVLifecycleStatus overwrites only the lifecycle status of an already +// registered universal validator, leaving identity/network info intact and +// leaving the underlying staking validator bonded. It bypasses transition +// validation so INACTIVE can be reached directly. +func forceUVLifecycleStatus( + t *testing.T, + testApp *app.ChainApp, + ctx sdk.Context, + val stakingtypes.Validator, + status uvalidatortypes.UVStatus, +) { + t.Helper() + valAddr, err := sdk.ValAddressFromBech32(val.OperatorAddress) + require.NoError(t, err) + + uv, err := testApp.UvalidatorKeeper.UniversalValidatorSet.Get(ctx, valAddr) + require.NoError(t, err) + uv.LifecycleInfo.CurrentStatus = status + require.NoError(t, testApp.UvalidatorKeeper.UniversalValidatorSet.Set(ctx, valAddr, uv)) +} + +// requireStillBonded asserts the staking validator behind a universal validator +// is still bonded. This is the precondition the finding rests on: lifecycle +// removal does not unbond stake, so a bonded-only admission gate keeps letting +// the removed hotkey in. +func requireStillBonded(t *testing.T, testApp *app.ChainApp, ctx sdk.Context, val stakingtypes.Validator) { + t.Helper() + valAddr, err := sdk.ValAddressFromBech32(val.OperatorAddress) + require.NoError(t, err) + sv, err := testApp.StakingKeeper.GetValidator(ctx, valAddr) + require.NoError(t, err) + require.True(t, sv.IsBonded(), + "removal must leave the validator bonded -- otherwise the finding's vector would not exist") +} + +// TestVoteChainMeta_EligibilityGate is the F-2026-18148 regression suite. +// +// MsgVoteChainMeta used to admit any bonded, registered universal validator. +// Admin removal moves a universal validator to PENDING_LEAVE while its stake +// stays bonded, and AfterValidatorRemoved prunes its ChainMeta rows but revokes +// neither its AuthZ grant nor its membership in the set -- so the removed +// hotkey could reinsert votes straight after the prune. Admission is now gated +// on the same eligibility predicate (ACTIVE / PENDING_JOIN + bonded + not +// tombstoned) that uvalidator uses to snapshot ballot voters. +func TestVoteChainMeta_EligibilityGate(t *testing.T) { + chainId := "eip155:11155111" + + t.Run("removed PENDING_LEAVE validator cannot reinsert a vote after the prune", func(t *testing.T) { + testApp, ctx, uvals, vals := setupVoteChainMetaTest(t, 5) + + // Removal of an ACTIVE universal validator requires no ongoing TSS. + _ = testApp.UtssKeeper.CurrentTssProcess.Remove(ctx) + for _, v := range vals { + valAddr, err := sdk.ValAddressFromBech32(v.OperatorAddress) + require.NoError(t, err) + require.NoError(t, testApp.UvalidatorKeeper.UpdateValidatorStatus( + ctx, valAddr, + uvalidatortypes.UVStatus_UV_STATUS_ACTIVE, + uvalidatortypes.TransitionReason_TRANSITION_REASON_UNSPECIFIED, + )) + } + + coreAccs := make([]string, len(vals)) + for i := range vals { + coreAccs[i] = coreAccOf(t, vals[i]) + } + + // Five ACTIVE validators vote. Prices 100..500, heights 10..50. + // After the 3rd vote the oracle bootstraps; by the 5th the recorded + // upper median price is 300 and LastAppliedChainHeight is 30. + prices := []uint64{100, 200, 300, 400, 500} + heights := []uint64{10, 20, 30, 40, 50} + for i := range vals { + require.NoError(t, utils.ExecVoteChainMeta(t, ctx, testApp, uvals[i], coreAccs[i], chainId, prices[i], heights[i])) + } + + stored, found, err := testApp.UexecutorKeeper.GetChainMeta(ctx, chainId) + require.NoError(t, err) + require.True(t, found) + require.Len(t, stored.Signers, 5) + require.Equal(t, uint64(300), stored.Prices[stored.MedianIndex], "baseline recorded median price") + require.Equal(t, uint64(30), stored.LastAppliedChainHeight, "baseline applied chain height") + + // Admin removes validator 4: ACTIVE -> PENDING_LEAVE, ChainMeta pruned. + require.NoError(t, testApp.UvalidatorKeeper.RemoveUniversalValidator(ctx, vals[4].OperatorAddress)) + + removedValAddr, err := sdk.ValAddressFromBech32(vals[4].OperatorAddress) + require.NoError(t, err) + uv, uvFound, err := testApp.UvalidatorKeeper.GetUniversalValidator(ctx, removedValAddr) + require.NoError(t, err) + require.True(t, uvFound, "removal keeps the row in the set -- only the lifecycle status changes") + require.Equal(t, uvalidatortypes.UVStatus_UV_STATUS_PENDING_LEAVE, uv.LifecycleInfo.CurrentStatus) + + // The two halves of the vector: stake is still bonded, and the AuthZ + // grant was never revoked, so the hotkey can still build the tx. + requireStillBonded(t, testApp, ctx, vals[4]) + + pruned, _, err := testApp.UexecutorKeeper.GetChainMeta(ctx, chainId) + require.NoError(t, err) + require.Len(t, pruned.Signers, 4, "the removed validator's ChainMeta row must have been pruned") + + // The removed hotkey now tries to reinsert a vote. A price of 250 sits + // between the surviving 200 and 300, so if it landed it would drag the + // upper median down from 300 to 250. Height 35 clears the stale-height + // gate (LastAppliedChainHeight = 30). + reinsertErr := utils.ExecVoteChainMeta(t, ctx, testApp, uvals[4], coreAccs[4], chainId, 250, 35) + + after, _, err := testApp.UexecutorKeeper.GetChainMeta(ctx, chainId) + require.NoError(t, err) + + // State assertions first: an aborting error assertion must not be able + // to hide a vote that actually landed. + require.Equal(t, uint64(300), after.Prices[after.MedianIndex], + "the median must still be computed over the four surviving votes only") + require.NotContains(t, after.Signers, removedValAddr.String(), + "the removed validator must not reappear among the ChainMeta signers") + require.NotContains(t, after.Prices, uint64(250), "the rejected price must not be recorded") + require.Len(t, after.Signers, 4, "no new signer row may be inserted") + require.Equal(t, uint64(30), after.LastAppliedChainHeight, + "a rejected vote must not advance the applied chain height") + + require.Error(t, reinsertErr, "a PENDING_LEAVE universal validator must not be able to vote on chain meta") + require.Contains(t, reinsertErr.Error(), "is not an eligible voter") + }) + + t.Run("INACTIVE but still-bonded validator is rejected", func(t *testing.T) { + testApp, ctx, uvals, vals := setupVoteChainMetaTest(t, 3) + + for _, v := range vals { + valAddr, err := sdk.ValAddressFromBech32(v.OperatorAddress) + require.NoError(t, err) + require.NoError(t, testApp.UvalidatorKeeper.UpdateValidatorStatus( + ctx, valAddr, + uvalidatortypes.UVStatus_UV_STATUS_ACTIVE, + uvalidatortypes.TransitionReason_TRANSITION_REASON_UNSPECIFIED, + )) + } + forceUVLifecycleStatus(t, testApp, ctx, vals[2], uvalidatortypes.UVStatus_UV_STATUS_INACTIVE) + requireStillBonded(t, testApp, ctx, vals[2]) + + voteErr := utils.ExecVoteChainMeta(t, ctx, testApp, uvals[2], coreAccOf(t, vals[2]), chainId, 777, 7) + + _, found, err := testApp.UexecutorKeeper.GetChainMeta(ctx, chainId) + require.NoError(t, err) + require.False(t, found, "an INACTIVE validator's vote must not create a ChainMeta entry") + + require.Error(t, voteErr, "an INACTIVE universal validator must not be able to vote on chain meta") + require.Contains(t, voteErr.Error(), "is not an eligible voter") + }) + + t.Run("ACTIVE validator is still accepted", func(t *testing.T) { + testApp, ctx, uvals, vals := setupVoteChainMetaTest(t, 3) + + for _, v := range vals { + valAddr, err := sdk.ValAddressFromBech32(v.OperatorAddress) + require.NoError(t, err) + require.NoError(t, testApp.UvalidatorKeeper.UpdateValidatorStatus( + ctx, valAddr, + uvalidatortypes.UVStatus_UV_STATUS_ACTIVE, + uvalidatortypes.TransitionReason_TRANSITION_REASON_UNSPECIFIED, + )) + } + + require.NoError(t, utils.ExecVoteChainMeta(t, ctx, testApp, uvals[0], coreAccOf(t, vals[0]), chainId, 100, 1)) + + stored, found, err := testApp.UexecutorKeeper.GetChainMeta(ctx, chainId) + require.NoError(t, err) + require.True(t, found) + require.Len(t, stored.Signers, 1, "the ACTIVE validator's vote must be recorded") + }) + + t.Run("PENDING_JOIN validator is still accepted", func(t *testing.T) { + // setupVoteChainMetaTest registers every universal validator through + // AddUniversalValidator, which leaves them in PENDING_JOIN. + testApp, ctx, uvals, vals := setupVoteChainMetaTest(t, 3) + + valAddr, err := sdk.ValAddressFromBech32(vals[1].OperatorAddress) + require.NoError(t, err) + uv, found, err := testApp.UvalidatorKeeper.GetUniversalValidator(ctx, valAddr) + require.NoError(t, err) + require.True(t, found) + require.Equal(t, uvalidatortypes.UVStatus_UV_STATUS_PENDING_JOIN, uv.LifecycleInfo.CurrentStatus) + + require.NoError(t, utils.ExecVoteChainMeta(t, ctx, testApp, uvals[1], coreAccOf(t, vals[1]), chainId, 100, 1)) + + stored, found, err := testApp.UexecutorKeeper.GetChainMeta(ctx, chainId) + require.NoError(t, err) + require.True(t, found) + require.Len(t, stored.Signers, 1, "the PENDING_JOIN validator's vote must be recorded") + }) + + t.Run("fewer than three eligible validators cannot reach the bootstrap minimum", func(t *testing.T) { + // Documents the bootstrap interaction, it does not assert a defect: + // chainMetaMinVotesForFirstWrite = 3 counts fresh vote ROWS, and there + // is at most one row per validator. Tightening admission can only + // shrink the pool of validators able to produce a row, so a set with + // fewer than three ELIGIBLE universal validators can never bootstrap + // the oracle. That was already true of any topology with fewer than + // three bonded universal validators; this gate makes lifecycle state + // count towards it too. + testApp, ctx, uvals, vals := setupVoteChainMetaTest(t, 3) + + for _, v := range vals { + valAddr, err := sdk.ValAddressFromBech32(v.OperatorAddress) + require.NoError(t, err) + require.NoError(t, testApp.UvalidatorKeeper.UpdateValidatorStatus( + ctx, valAddr, + uvalidatortypes.UVStatus_UV_STATUS_ACTIVE, + uvalidatortypes.TransitionReason_TRANSITION_REASON_UNSPECIFIED, + )) + } + forceUVLifecycleStatus(t, testApp, ctx, vals[2], uvalidatortypes.UVStatus_UV_STATUS_PENDING_LEAVE) + requireStillBonded(t, testApp, ctx, vals[2]) + + require.NoError(t, utils.ExecVoteChainMeta(t, ctx, testApp, uvals[0], coreAccOf(t, vals[0]), chainId, 100, 1)) + require.NoError(t, utils.ExecVoteChainMeta(t, ctx, testApp, uvals[1], coreAccOf(t, vals[1]), chainId, 200, 2)) + thirdErr := utils.ExecVoteChainMeta(t, ctx, testApp, uvals[2], coreAccOf(t, vals[2]), chainId, 300, 3) + + stored, found, err := testApp.UexecutorKeeper.GetChainMeta(ctx, chainId) + require.NoError(t, err) + require.True(t, found) + require.Len(t, stored.Signers, 2, "only the two eligible validators may hold a vote row") + require.Equal(t, uint64(0), stored.LastAppliedChainHeight, + "two fresh votes are below chainMetaMinVotesForFirstWrite=3, so the oracle stays un-bootstrapped") + + require.Error(t, thirdErr) + require.Contains(t, thirdErr.Error(), "is not an eligible voter") + }) +} diff --git a/x/uexecutor/keeper/msg_server.go b/x/uexecutor/keeper/msg_server.go index 4db727697..e35ec08f8 100755 --- a/x/uexecutor/keeper/msg_server.go +++ b/x/uexecutor/keeper/msg_server.go @@ -153,14 +153,6 @@ func (ms msgServer) VoteChainMeta(ctx context.Context, msg *types.MsgVoteChainMe signerValAddr := sdk.ValAddress(signerAccAddr) - isBonded, err := ms.k.uvalidatorKeeper.IsBondedUniversalValidator(ctx, msg.Signer) - if err != nil { - return nil, errors.Wrapf(err, "failed to check bonded status for signer %s", msg.Signer) - } - if !isBonded { - return nil, fmt.Errorf("universal validator for signer %s is not bonded", msg.Signer) - } - isTombstoned, err := ms.k.uvalidatorKeeper.IsTombstonedUniversalValidator(ctx, msg.Signer) if err != nil { return nil, errors.Wrapf(err, "failed to check tombstoned status for signer %s", msg.Signer) @@ -169,6 +161,26 @@ func (ms msgServer) VoteChainMeta(ctx context.Context, msg *types.MsgVoteChainMe return nil, fmt.Errorf("universal validator for signer %s is tombstoned", msg.Signer) } + // Admission is gated on the same eligibility predicate ballot creation uses + // (lifecycle ACTIVE/PENDING_JOIN AND bonded AND not tombstoned) rather than + // the lifecycle-blind IsBondedUniversalValidator. Admin removal moves a + // universal validator to PENDING_LEAVE while its stake can remain bonded, + // and AfterValidatorRemoved prunes its ChainMeta rows but revokes neither + // its AuthZ grant nor its membership in the universal validator set -- so + // under the bonded-only gate the removed hotkey could reinsert votes right + // after the prune. + // + // Tightening admission is safe here, and only here, because ChainMeta is + // median-based rather than ballot-based: there is no CreateBallot, no + // snapshotted EligibleVoters and no frozen VotingThreshold. Every vote + // recomputes the median over whichever votes are currently fresh, so a + // narrower voter set cannot strand anything in flight. The ballot-based + // vote paths (VoteInbound/VoteOutbound above) deliberately keep the looser + // gate: tightening them would make already-frozen thresholds unreachable. + if err := ms.requireEligibleChainMetaVoter(ctx, signerValAddr); err != nil { + return nil, err + } + err = ms.k.VoteChainMeta(ctx, signerValAddr, msg.ObservedChainId, msg.Price, msg.ChainHeight) if err != nil { return nil, err @@ -176,6 +188,30 @@ func (ms msgServer) VoteChainMeta(ctx context.Context, msg *types.MsgVoteChainMe return &types.MsgVoteChainMetaResponse{}, nil } +// requireEligibleChainMetaVoter returns nil only when signerValAddr is present +// in the current eligible-voter set, i.e. it satisfies exactly the same +// predicate uvalidator applies when it snapshots a ballot's voters. Reusing +// GetEligibleVoters rather than re-deriving the checks keeps ChainMeta vote +// admission from drifting away from that definition. +func (ms msgServer) requireEligibleChainMetaVoter(ctx context.Context, signerValAddr sdk.ValAddress) error { + eligible, err := ms.k.uvalidatorKeeper.GetEligibleVoters(ctx) + if err != nil { + return errors.Wrapf(err, "failed to fetch eligible voters for signer %s", signerValAddr.String()) + } + + want := signerValAddr.String() + for _, uv := range eligible { + if uv.IdentifyInfo != nil && uv.IdentifyInfo.CoreValidatorAddress == want { + return nil + } + } + + return fmt.Errorf( + "universal validator %s is not an eligible voter; only ACTIVE or PENDING_JOIN universal validators with bonded, non-tombstoned staking state may vote on chain meta", + want, + ) +} + // RevertStuckInbound is the admin escape hatch — see Keeper.RevertStuckInbound. func (ms msgServer) RevertStuckInbound(ctx context.Context, msg *types.MsgRevertStuckInbound) (*types.MsgRevertStuckInboundResponse, error) { ms.k.Logger().Info("msg: RevertStuckInbound", "signer", msg.Signer) From 9fd3a39434c466f752611756382a6f4a06545e42 Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Wed, 26 Aug 2026 20:26:21 +0530 Subject: [PATCH 47/60] fix: F-2026-18146 | [Dual Defense] Unbounded Universal Payload Size and Unbounded gRPC Response in Universal Validator (#349) * fix: cap the universal payload at 128 KiB and bound the UV gRPC receive size * test: keep the huge-decimal assertion honest under the payload size cap --- .../uexecutor/payload_size_cap_test.go | 225 ++++++++++++++++++ universalClient/pushcore/pushCore.go | 19 +- universalClient/pushcore/recv_size_test.go | 97 ++++++++ x/uexecutor/keeper/msg_execute_payload.go | 11 +- x/uexecutor/keeper/msg_vote_inbound.go | 9 + x/uexecutor/types/constants.go | 14 ++ x/uexecutor/types/inbound.go | 30 +++ x/uexecutor/types/msg_execute_payload.go | 3 + x/uexecutor/types/payload_size_test.go | 188 +++++++++++++++ x/uexecutor/types/uint256_test.go | 13 +- x/uexecutor/types/universal_payload.go | 29 +++ 11 files changed, 634 insertions(+), 4 deletions(-) create mode 100644 test/integration/uexecutor/payload_size_cap_test.go create mode 100644 universalClient/pushcore/recv_size_test.go create mode 100644 x/uexecutor/types/payload_size_test.go diff --git a/test/integration/uexecutor/payload_size_cap_test.go b/test/integration/uexecutor/payload_size_cap_test.go new file mode 100644 index 000000000..ba6e99e00 --- /dev/null +++ b/test/integration/uexecutor/payload_size_cap_test.go @@ -0,0 +1,225 @@ +package integrationtest + +import ( + "strings" + "testing" + + "cosmossdk.io/collections" + sdk "github.com/cosmos/cosmos-sdk/types" + authz "github.com/cosmos/cosmos-sdk/x/authz" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + + "github.com/pushchain/push-chain-node/app" + utils "github.com/pushchain/push-chain-node/test/utils" + uexecutortypes "github.com/pushchain/push-chain-node/x/uexecutor/types" +) + +// hexBlobOfLen returns a lowercase 0x-prefixed blob exactly n characters long. +// Canonicalize leaves an even-bodied lowercase hex blob untouched, so the +// length the keeper sees is the length built here. +func hexBlobOfLen(n int) string { + body := strings.Repeat("ab", (n-2)/2) + if len(body)+2 < n { + body += "c" + } + return "0x" + body +} + +// universalPayloadOfSize builds a valid payload whose serialized size is +// exactly want bytes. +func universalPayloadOfSize(t *testing.T, want int) *uexecutortypes.UniversalPayload { + t.Helper() + + // tag byte + 3-byte varint length for every size used here. + const dataOverhead = 4 + + for _, nonce := range []string{"1", "11"} { + p := &uexecutortypes.UniversalPayload{ + To: utils.GetDefaultAddresses().HandlerAddr.Hex(), + Nonce: nonce, + } + dataLen := want - p.Size() - dataOverhead + if dataLen < 2 || dataLen%2 != 0 { + continue + } + p.Data = "0x" + strings.Repeat("ab", (dataLen-2)/2) + if p.Size() == want { + return p + } + } + + t.Fatalf("could not build a universal payload of exactly %d bytes", want) + return nil +} + +// TestVoteInboundPayloadSizeCap covers the vote half of the 128 KiB universal +// payload cap. +// +// The vote arrives wrapped in an authz.MsgExec — that is what the universal +// validator broadcasts (universalClient/pushsigner/pushsigner.go wrapWithAuthZ) +// and what utils.ExecVoteInbound reproduces. authz.MsgExec carries no +// ValidateBasic of its own, so baseapp does not reach the inner msg at CheckTx; +// the inner ValidateBasic runs later, inside authz's Exec msg server, and the +// keeper check runs after that. Both are exercised here. +func TestVoteInboundPayloadSizeCap(t *testing.T) { + usdcAddress := utils.GetDefaultAddresses().ExternalUSDCAddr + testAddress := utils.GetDefaultAddresses().DefaultTestAddr + + newInbound := func(txHash, rawPayload string) *uexecutortypes.Inbound { + return &uexecutortypes.Inbound{ + SourceChain: "eip155:11155111", + TxHash: txHash, + Sender: testAddress, + Amount: "1000000", + AssetAddr: usdcAddress.String(), + LogIndex: "1", + TxType: uexecutortypes.TxType_FUNDS_AND_PAYLOAD, + RawPayload: rawPayload, + } + } + + utxKeyOf := func(in *uexecutortypes.Inbound) string { + canon := *in + canon.Canonicalize() + return uexecutortypes.GetInboundUniversalTxKey(canon) + } + + t.Run("raw payload at the cap is voted on", func(t *testing.T) { + chainApp, ctx, vals, coreVals, _ := setupInboundValidationTest(t, 4) + + raw := hexBlobOfLen(uexecutortypes.MaxUniversalPayloadBytes) + require.Len(t, raw, uexecutortypes.MaxUniversalPayloadBytes) + + inbound := newInbound("0xatcap01", raw) + + valAddr, err := sdk.ValAddressFromBech32(coreVals[0].OperatorAddress) + require.NoError(t, err) + voteErr := utils.ExecVoteInbound(t, ctx, chainApp, vals[0], sdk.AccAddress(valAddr).String(), inbound) + + // State first: the vote was recorded, so the cap did not reject it. + entry, err := chainApp.UexecutorKeeper.PendingInbounds.Get(ctx, utxKeyOf(inbound)) + require.NoError(t, err, "a vote at the cap must be recorded") + require.Len(t, entry.Variants, 1) + require.Len(t, entry.Variants[0].Inbound.RawPayload, uexecutortypes.MaxUniversalPayloadBytes) + + require.NoError(t, voteErr) + }) + + t.Run("raw payload one byte over the cap is rejected on the authz path", func(t *testing.T) { + chainApp, ctx, vals, coreVals, _ := setupInboundValidationTest(t, 4) + + raw := hexBlobOfLen(uexecutortypes.MaxUniversalPayloadBytes + 1) + require.Len(t, raw, uexecutortypes.MaxUniversalPayloadBytes+1) + + inbound := newInbound("0xovercap01", raw) + + valAddr, err := sdk.ValAddressFromBech32(coreVals[0].OperatorAddress) + require.NoError(t, err) + voteErr := utils.ExecVoteInbound(t, ctx, chainApp, vals[0], sdk.AccAddress(valAddr).String(), inbound) + + // State first: nothing about this inbound reached consensus state. + _, err = chainApp.UexecutorKeeper.PendingInbounds.Get(ctx, utxKeyOf(inbound)) + require.ErrorIs(t, err, collections.ErrNotFound, "an oversized vote must not write PendingInbounds") + + _, found, err := chainApp.UexecutorKeeper.GetUniversalTx(ctx, utxKeyOf(inbound)) + require.NoError(t, err) + require.False(t, found, "an oversized vote must not create a UniversalTx") + + require.Error(t, voteErr) + require.Contains(t, voteErr.Error(), "raw_payload too large") + require.Contains(t, voteErr.Error(), "131073 bytes exceeds the 131072 byte limit") + }) + + t.Run("keeper rejects an oversized vote without any msg validation", func(t *testing.T) { + chainApp, ctx, _, coreVals, _ := setupInboundValidationTest(t, 4) + + inbound := newInbound("0xovercap02", hexBlobOfLen(uexecutortypes.MaxUniversalPayloadBytes+1)) + + valAddr, err := sdk.ValAddressFromBech32(coreVals[0].OperatorAddress) + require.NoError(t, err) + + // Straight into the keeper, so nothing but the keeper's own check can + // reject it. + voteErr := chainApp.UexecutorKeeper.VoteInbound(ctx, valAddr, *inbound) + + _, err = chainApp.UexecutorKeeper.PendingInbounds.Get(ctx, utxKeyOf(inbound)) + require.ErrorIs(t, err, collections.ErrNotFound, "the keeper must not write PendingInbounds for an oversized vote") + + require.Error(t, voteErr) + require.Contains(t, voteErr.Error(), "raw_payload too large") + }) +} + +// TestExecutePayloadSizeCap covers the direct half of the cap: MsgExecutePayload +// is fee exempt (app/txpolicy/gasless.go), so the payload it carries is not +// priced anywhere and only the size cap bounds it. +func TestExecutePayloadSizeCap(t *testing.T) { + testAddress := utils.GetDefaultAddresses().DefaultTestAddr + // A real 20-byte account, since MsgExecutePayload rejects any other length. + signerAcc := sdk.AccAddress(common.HexToAddress(testAddress).Bytes()) + signer := signerAcc.String() + + newMsg := func(payload *uexecutortypes.UniversalPayload) *uexecutortypes.MsgExecutePayload { + return &uexecutortypes.MsgExecutePayload{ + Signer: signer, + UniversalAccountId: &uexecutortypes.UniversalAccountId{ + ChainNamespace: "eip155", + ChainId: "11155111", + Owner: testAddress, + }, + UniversalPayload: payload, + VerificationData: "0x", + } + } + + execViaAuthz := func(chainApp *app.ChainApp, ctx sdk.Context, payload *uexecutortypes.UniversalPayload) error { + execMsg := authz.NewMsgExec(signerAcc, []sdk.Msg{newMsg(payload)}) + _, err := chainApp.AuthzKeeper.Exec(ctx, &execMsg) + return err + } + + t.Run("payload over the cap is rejected on the authz path", func(t *testing.T) { + chainApp, ctx, _, _, _ := setupInboundValidationTest(t, 1) + + oversized := universalPayloadOfSize(t, uexecutortypes.MaxUniversalPayloadBytes+1) + require.Equal(t, uexecutortypes.MaxUniversalPayloadBytes+1, oversized.Size()) + + err := execViaAuthz(chainApp, ctx, oversized) + require.Error(t, err) + require.Contains(t, err.Error(), "universal payload too large") + require.Contains(t, err.Error(), "131073 bytes exceeds the 131072 byte limit") + }) + + t.Run("keeper rejects an oversized payload without any msg validation", func(t *testing.T) { + chainApp, ctx, _, _, _ := setupInboundValidationTest(t, 1) + + oversized := universalPayloadOfSize(t, uexecutortypes.MaxUniversalPayloadBytes+1) + + // Straight into the keeper, so nothing but the keeper's own check can + // reject it — and before any EVM work. + err := chainApp.UexecutorKeeper.ExecutePayload( + ctx, + common.HexToAddress(testAddress), + newMsg(oversized).UniversalAccountId, + oversized, + "0x", + ) + require.Error(t, err) + require.Contains(t, err.Error(), "universal payload too large") + }) + + t.Run("payload at the cap passes the size gate", func(t *testing.T) { + chainApp, ctx, _, _, _ := setupInboundValidationTest(t, 1) + + atCap := universalPayloadOfSize(t, uexecutortypes.MaxUniversalPayloadBytes) + require.Equal(t, uexecutortypes.MaxUniversalPayloadBytes, atCap.Size()) + require.NoError(t, newMsg(atCap).ValidateBasic()) + + // Execution may still fail further down (this signer has no deployed + // UEA); what matters is that it is never the size gate. + if err := execViaAuthz(chainApp, ctx, atCap); err != nil { + require.NotContains(t, err.Error(), "too large") + } + }) +} diff --git a/universalClient/pushcore/pushCore.go b/universalClient/pushcore/pushCore.go index a6247078f..ecf58eff3 100644 --- a/universalClient/pushcore/pushCore.go +++ b/universalClient/pushcore/pushCore.go @@ -405,6 +405,21 @@ const ( chainConfigPageSize = 200 chainConfigMaxPages = 20 + + // maxPushCoreRecvMsgSize bounds a single gRPC response from a Push-core + // endpoint. grpc-go otherwise applies an implicit 4 MiB default that nobody + // chose and that is not tied to anything this client asks for; pinning it + // here makes the bound deliberate and keeps it from drifting with the + // library default. + // + // Sized off the largest poll: GetAllPendingOutbounds asks for + // pendingOutboundPageSize entries and gets the matching outbounds back, so + // 2 x 1000 rows in one response. A row costs roughly a kilobyte today, so a + // 4 KiB per-row budget leaves 4x headroom and lands on 8 MiB — above the + // 4 MiB the client has been running on, so no response that works today + // starts failing, and low enough that a hostile or broken endpoint cannot + // stream an unbounded body into the validator. + maxPushCoreRecvMsgSize = 8 * 1024 * 1024 ) // GetAllPendingOutbounds retrieves pending outbound transactions from Push Chain, @@ -487,7 +502,9 @@ func createGRPCConnection(endpoint string) (*grpc.ClientConn, error) { } } - var opts []grpc.DialOption + opts := []grpc.DialOption{ + grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(maxPushCoreRecvMsgSize)), + } if useTLS { opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(nil))) } else { diff --git a/universalClient/pushcore/recv_size_test.go b/universalClient/pushcore/recv_size_test.go new file mode 100644 index 000000000..c4e4a0a17 --- /dev/null +++ b/universalClient/pushcore/recv_size_test.go @@ -0,0 +1,97 @@ +package pushcore + +import ( + "context" + "net" + "strings" + "testing" + "time" + + uexecutortypes "github.com/pushchain/push-chain-node/x/uexecutor/types" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// grpcDefaultMaxRecvMsgSize is the receive cap grpc-go applies when a client +// sets none. The whole point of maxPushCoreRecvMsgSize is that the bound is a +// choice made here rather than this inherited default. +const grpcDefaultMaxRecvMsgSize = 4 * 1024 * 1024 + +// serveResponseOfSize starts a gRPC server on a loopback port that answers any +// request with a pending-outbounds response carrying a payload of payloadBytes, +// and returns its address. +func serveResponseOfSize(t *testing.T, payloadBytes int) string { + t.Helper() + + lis, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + + resp := &uexecutortypes.QueryAllPendingOutboundsResponse{ + Outbounds: []*uexecutortypes.OutboundTx{{ + Id: "oversized", + Payload: strings.Repeat("a", payloadBytes), + }}, + } + + srv := grpc.NewServer(grpc.UnknownServiceHandler(func(_ interface{}, stream grpc.ServerStream) error { + var req uexecutortypes.QueryAllPendingOutboundsRequest + if err := stream.RecvMsg(&req); err != nil { + return err + } + return stream.SendMsg(resp) + })) + + go func() { _ = srv.Serve(lis) }() + t.Cleanup(srv.Stop) + + return lis.Addr().String() +} + +func queryPendingOutbounds(t *testing.T, endpoint string) (*uexecutortypes.QueryAllPendingOutboundsResponse, error) { + t.Helper() + + conn, err := createGRPCConnection(endpoint) + require.NoError(t, err) + t.Cleanup(func() { _ = conn.Close() }) + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + return uexecutortypes.NewQueryClient(conn). + AllPendingOutbounds(ctx, &uexecutortypes.QueryAllPendingOutboundsRequest{}) +} + +func TestPushCoreRecvMsgSizeIsExplicit(t *testing.T) { + // A bound equal to the library default would be indistinguishable from + // setting nothing at all. + require.Greater(t, maxPushCoreRecvMsgSize, grpcDefaultMaxRecvMsgSize, + "the receive bound must be a deliberate value, not grpc-go's implicit default") + + t.Run("a response inside the bound is accepted", func(t *testing.T) { + // Above grpc-go's default, below ours: this only succeeds because the + // dial options carry an explicit MaxCallRecvMsgSize. + const size = 5 * 1024 * 1024 + require.Greater(t, size, grpcDefaultMaxRecvMsgSize) + require.Less(t, size, maxPushCoreRecvMsgSize) + + resp, err := queryPendingOutbounds(t, serveResponseOfSize(t, size)) + require.NoError(t, err) + require.Len(t, resp.Outbounds, 1) + require.Len(t, resp.Outbounds[0].Payload, size) + }) + + t.Run("a response past the bound fails as ResourceExhausted", func(t *testing.T) { + resp, err := queryPendingOutbounds(t, serveResponseOfSize(t, maxPushCoreRecvMsgSize+1)) + + require.Nil(t, resp, "no partial response may be handed to the caller") + require.Error(t, err) + + st, ok := status.FromError(err) + require.True(t, ok, "the failure must be a gRPC status, not an opaque error: %v", err) + require.Equal(t, codes.ResourceExhausted, st.Code()) + require.Contains(t, st.Message(), "8388608", + "the error must name the bound that was applied") + }) +} diff --git a/x/uexecutor/keeper/msg_execute_payload.go b/x/uexecutor/keeper/msg_execute_payload.go index 946b1e12a..d12e51c7a 100644 --- a/x/uexecutor/keeper/msg_execute_payload.go +++ b/x/uexecutor/keeper/msg_execute_payload.go @@ -25,7 +25,16 @@ func (k Keeper) ExecutePayload(ctx context.Context, evmFrom common.Address, univ "owner", universalAccountId.Owner, ) - // Step 1: Validate payload and verificationData early (fast-fail before EVM work) + // Step 1: Validate payload and verificationData early (fast-fail before EVM work). + // The size cap is re-applied here rather than left to MsgExecutePayload's + // ValidateBasic so it holds for every caller of this keeper method — the msg + // route is fee exempt, so nothing else prices these bytes. + if err := universalPayload.ValidateSize(); err != nil { + return err + } + if err := types.ValidatePayloadBlobSize("verificationData", verificationData); err != nil { + return err + } if _, err := types.NewAbiUniversalPayload(universalPayload); err != nil { return errors.Wrapf(err, "invalid universal payload") } diff --git a/x/uexecutor/keeper/msg_vote_inbound.go b/x/uexecutor/keeper/msg_vote_inbound.go index a20a2651d..25897bb99 100644 --- a/x/uexecutor/keeper/msg_vote_inbound.go +++ b/x/uexecutor/keeper/msg_vote_inbound.go @@ -16,6 +16,15 @@ import ( // query what happened to their cross-chain tx instead of having funds silently stuck // in the gateway contract. func (k Keeper) VoteInbound(ctx context.Context, universalValidator sdk.ValAddress, inbound types.Inbound) error { + // Bound the payload blobs before anything reads or writes state. The msg + // carrying this vote is fee exempt, so nothing charges the submitter for the + // bytes it puts into state. Repeated here rather than left to + // MsgVoteInbound.ValidateBasic so the cap holds for every caller of this + // keeper method, not just the one msg route. + if err := inbound.ValidateSize(); err != nil { + return err + } + // Canonicalize first so every derived key + the stored inbound use one // representation per logical event. inbound.Canonicalize() diff --git a/x/uexecutor/types/constants.go b/x/uexecutor/types/constants.go index 490280503..2d7e58384 100644 --- a/x/uexecutor/types/constants.go +++ b/x/uexecutor/types/constants.go @@ -55,3 +55,17 @@ var UniversalTxOutboundEventSig = crypto.Keccak256Hash([]byte( var RescueFundsOnSourceChainEventSig = crypto.Keccak256Hash([]byte( "RescueFundsOnSourceChain(bytes32,address,string,address,uint8,uint256,uint256,uint256)", )).Hex() + +// MaxUniversalPayloadBytes hard-caps the size of a universal payload, and of the +// hex blobs that carry one on the wire, at 128 KiB — the same limit the +// usigverifier precompile applies to a raw ed25519 message, so there is one +// payload size limit to reason about. +// +// A flat size cap rather than a gas price on purpose: MsgExecutePayload and +// MsgVoteInbound are fee exempt (app/txpolicy/gasless.go), so nothing charges +// the submitter for the bytes it puts into a block, into consensus state and +// into every node's memory. On a fee-exempt path the only defence that holds is +// a hard limit. It is a flat number rather than one derived from the Solidity +// UniversalPayload struct because that struct is variable length; a flat number +// is auditable and stable. +const MaxUniversalPayloadBytes = 128 * 1024 diff --git a/x/uexecutor/types/inbound.go b/x/uexecutor/types/inbound.go index c9959ff13..37261eedc 100644 --- a/x/uexecutor/types/inbound.go +++ b/x/uexecutor/types/inbound.go @@ -70,6 +70,29 @@ func (p *Inbound) NormalizeForTxType() error { return nil } +// ValidateSize enforces MaxUniversalPayloadBytes on every variable-length +// payload field an inbound carries. raw_payload is the wire form of the +// universal payload and universal_payload is what a validator submits before +// the core decodes raw_payload itself; both land in PendingInbounds state on +// the first vote, on a fee-exempt msg, so both are bounded here. +// +// Split out of ValidateBasic so the keeper can apply the cap on its own: a +// universal validator submits votes wrapped in authz.MsgExec +// (universalClient/pushsigner/pushsigner.go wrapWithAuthZ), which baseapp does +// not validate at CheckTx, so the cap must not depend on one call site. +func (p *Inbound) ValidateSize() error { + if p == nil { + return nil + } + if err := ValidatePayloadBlobSize("raw_payload", p.RawPayload); err != nil { + return err + } + if err := ValidatePayloadBlobSize("verification_data", p.VerificationData); err != nil { + return err + } + return p.UniversalPayload.ValidateSize() +} + // Stringer method for Params. func (p Inbound) String() string { bz, err := json.Marshal(p) @@ -87,6 +110,13 @@ func (p Inbound) String() string { // (with a failed PCTx / revert) instead of silently dropping the vote and leaving // user funds stuck in the gateway. func (p Inbound) ValidateBasic() error { + // Reject oversized payload blobs before anything else: unlike the + // execution-level checks below, this one is a resource bound, and the bytes + // are already in the block by the time execution validation runs. + if err := p.ValidateSize(); err != nil { + return err + } + // Validate source_chain (must follow CAIP-2 format) — needed for UTX key chain := strings.TrimSpace(p.SourceChain) if chain == "" { diff --git a/x/uexecutor/types/msg_execute_payload.go b/x/uexecutor/types/msg_execute_payload.go index 45281fafc..e49936818 100644 --- a/x/uexecutor/types/msg_execute_payload.go +++ b/x/uexecutor/types/msg_execute_payload.go @@ -77,6 +77,9 @@ func (msg *MsgExecutePayload) ValidateBasic() error { if len(msg.VerificationData) == 0 { return errors.Wrap(sdkerrors.ErrInvalidRequest, "verificationData cannot be empty") } + if err := ValidatePayloadBlobSize("verificationData", msg.VerificationData); err != nil { + return err + } if _, err := hex.DecodeString(strings.TrimPrefix(msg.VerificationData, "0x")); err != nil { return errors.Wrap(sdkerrors.ErrInvalidRequest, "invalid verificationData hex") } diff --git a/x/uexecutor/types/payload_size_test.go b/x/uexecutor/types/payload_size_test.go new file mode 100644 index 000000000..3d9b59b56 --- /dev/null +++ b/x/uexecutor/types/payload_size_test.go @@ -0,0 +1,188 @@ +package types_test + +import ( + "strings" + "testing" + + "github.com/pushchain/push-chain-node/x/uexecutor/types" + "github.com/stretchr/testify/require" +) + +// payloadWithSize builds a valid UniversalPayload whose serialized size is +// exactly want bytes, by sizing the (hex) data field to fill the remainder. +func payloadWithSize(t *testing.T, want int) types.UniversalPayload { + t.Helper() + + // The data field costs one tag byte plus a varint length plus the bytes + // themselves; every size this test uses falls in the 3-byte varint band. + const dataOverhead = 1 + 3 + + // "0x" plus an even number of hex characters, so the length of the data + // field is always even — the nonce absorbs the odd byte when needed. + for _, nonce := range []string{"1", "11"} { + p := types.UniversalPayload{To: mockHexAddress(), Nonce: nonce} + dataLen := want - p.Size() - dataOverhead + if dataLen < 2 || dataLen%2 != 0 { + continue + } + p.Data = "0x" + strings.Repeat("ab", (dataLen-2)/2) + if p.Size() == want { + return p + } + } + + t.Fatalf("could not build a universal payload of exactly %d bytes", want) + return types.UniversalPayload{} +} + +func TestUniversalPayload_SizeCap(t *testing.T) { + t.Run("at the cap is accepted", func(t *testing.T) { + p := payloadWithSize(t, types.MaxUniversalPayloadBytes) + require.Equal(t, types.MaxUniversalPayloadBytes, p.Size()) + require.NoError(t, p.ValidateSize()) + require.NoError(t, p.ValidateBasic()) + }) + + t.Run("one byte over the cap is rejected", func(t *testing.T) { + p := payloadWithSize(t, types.MaxUniversalPayloadBytes+1) + require.Equal(t, types.MaxUniversalPayloadBytes+1, p.Size()) + + sizeErr := p.ValidateSize() + basicErr := p.ValidateBasic() + + require.Error(t, sizeErr) + require.Contains(t, sizeErr.Error(), "universal payload too large") + require.Contains(t, sizeErr.Error(), "131073 bytes exceeds the 131072 byte limit") + + // ValidateBasic must reject it too — the payload is otherwise valid, so + // the size check is the only thing that can fail it. + require.Error(t, basicErr) + require.Contains(t, basicErr.Error(), "universal payload too large") + }) + + t.Run("nil payload has no size", func(t *testing.T) { + var p *types.UniversalPayload + require.NoError(t, p.ValidateSize()) + }) +} + +func TestValidatePayloadBlobSize(t *testing.T) { + atCap := strings.Repeat("a", types.MaxUniversalPayloadBytes) + overCap := atCap + "a" + + require.NoError(t, types.ValidatePayloadBlobSize("raw_payload", atCap)) + + err := types.ValidatePayloadBlobSize("raw_payload", overCap) + require.Error(t, err) + require.Contains(t, err.Error(), "raw_payload too large") + require.Contains(t, err.Error(), "131073 bytes exceeds the 131072 byte limit") +} + +func TestInbound_SizeCap(t *testing.T) { + base := func() types.Inbound { + return types.Inbound{ + SourceChain: "eip155:11155111", + TxHash: "0x" + strings.Repeat("11", 32), + Sender: mockHexAddress(), + LogIndex: "1", + TxType: types.TxType_FUNDS_AND_PAYLOAD, + } + } + + atCap := strings.Repeat("a", types.MaxUniversalPayloadBytes) + overCap := atCap + "a" + + t.Run("raw_payload at the cap is accepted", func(t *testing.T) { + in := base() + in.RawPayload = atCap + require.Len(t, in.RawPayload, types.MaxUniversalPayloadBytes) + require.NoError(t, in.ValidateSize()) + require.NoError(t, in.ValidateBasic()) + }) + + t.Run("raw_payload one byte over the cap is rejected", func(t *testing.T) { + in := base() + in.RawPayload = overCap + require.Len(t, in.RawPayload, types.MaxUniversalPayloadBytes+1) + + sizeErr := in.ValidateSize() + basicErr := in.ValidateBasic() + + require.Error(t, sizeErr) + require.Contains(t, sizeErr.Error(), "raw_payload too large") + require.Error(t, basicErr) + require.Contains(t, basicErr.Error(), "raw_payload too large") + }) + + t.Run("verification_data over the cap is rejected", func(t *testing.T) { + in := base() + in.VerificationData = overCap + + err := in.ValidateBasic() + require.Error(t, err) + require.Contains(t, err.Error(), "verification_data too large") + }) + + t.Run("embedded universal_payload over the cap is rejected", func(t *testing.T) { + in := base() + p := payloadWithSize(t, types.MaxUniversalPayloadBytes+1) + in.UniversalPayload = &p + + err := in.ValidateBasic() + require.Error(t, err) + require.Contains(t, err.Error(), "universal payload too large") + }) + + t.Run("nil inbound has no size", func(t *testing.T) { + var in *types.Inbound + require.NoError(t, in.ValidateSize()) + }) +} + +func TestMsgExecutePayload_SizeCap(t *testing.T) { + const signer = "push1fgaewhyd9fkwtqaj9c233letwcuey6dgly9gv9" + ua := &types.UniversalAccountId{ + ChainNamespace: "eip155", + ChainId: "11155111", + Owner: "0x000000000000000000000000000000000000dead", + } + + t.Run("payload at the cap is accepted", func(t *testing.T) { + p := payloadWithSize(t, types.MaxUniversalPayloadBytes) + msg := &types.MsgExecutePayload{ + Signer: signer, + UniversalAccountId: ua, + UniversalPayload: &p, + VerificationData: "abcdef0123456789", + } + require.NoError(t, msg.ValidateBasic()) + }) + + t.Run("payload over the cap is rejected", func(t *testing.T) { + p := payloadWithSize(t, types.MaxUniversalPayloadBytes+1) + msg := &types.MsgExecutePayload{ + Signer: signer, + UniversalAccountId: ua, + UniversalPayload: &p, + VerificationData: "abcdef0123456789", + } + + err := msg.ValidateBasic() + require.Error(t, err) + require.Contains(t, err.Error(), "universal payload too large") + }) + + t.Run("verificationData over the cap is rejected", func(t *testing.T) { + p := types.UniversalPayload{To: mockHexAddress(), Data: "0xabcdef"} + msg := &types.MsgExecutePayload{ + Signer: signer, + UniversalAccountId: ua, + UniversalPayload: &p, + VerificationData: strings.Repeat("ab", types.MaxUniversalPayloadBytes), + } + + err := msg.ValidateBasic() + require.Error(t, err) + require.Contains(t, err.Error(), "verificationData too large") + }) +} diff --git a/x/uexecutor/types/uint256_test.go b/x/uexecutor/types/uint256_test.go index 7aade8e4f..7af329908 100644 --- a/x/uexecutor/types/uint256_test.go +++ b/x/uexecutor/types/uint256_test.go @@ -194,8 +194,17 @@ func TestUniversalPayload_ValidateBasic_RejectsHugeDecimalFast(t *testing.T) { require.Less(t, elapsed, dosBudget, "%s: rejecting a %d-digit value took %s — the length cap must reject before big.Int parses", f.name, dosDigits, elapsed) - require.Contains(t, err.Error(), "exceeds the maximum of 80 characters", - "%s: must be rejected on length, before the parse", f.name) + // The payload-level size cap (F-2026-18146) rejects this input before + // the per-field cap gets to it — a 3M-digit field is a >128 KiB + // payload. Both rejections are O(1) on len(), which is the property + // this test exists to hold; the per-field message itself stays pinned + // by TestValidateUint256String_Bounds and by + // TestInboundAndOutbound_RejectHugeDecimalFast, neither of which is + // size capped. + require.True(t, + strings.Contains(err.Error(), "exceeds the maximum of 80 characters") || + strings.Contains(err.Error(), "universal payload too large"), + "%s: must be rejected on size or length, before the parse; got: %v", f.name, err) }) } } diff --git a/x/uexecutor/types/universal_payload.go b/x/uexecutor/types/universal_payload.go index cbab3c888..57a868e32 100644 --- a/x/uexecutor/types/universal_payload.go +++ b/x/uexecutor/types/universal_payload.go @@ -20,8 +20,37 @@ func (p UniversalPayload) String() string { return string(bz) } +// ValidateSize enforces the flat MaxUniversalPayloadBytes cap on the serialized +// payload. Split out of ValidateBasic so the keeper can apply the cap on its +// own, independently of whichever msg carried the payload in. +func (p *UniversalPayload) ValidateSize() error { + if p == nil { + return nil + } + if n := p.Size(); n > MaxUniversalPayloadBytes { + return errors.Wrapf(sdkerrors.ErrInvalidRequest, + "universal payload too large: %d bytes exceeds the %d byte limit", n, MaxUniversalPayloadBytes) + } + return nil +} + +// ValidatePayloadBlobSize enforces MaxUniversalPayloadBytes on a hex blob that +// carries a universal payload (or its verification data) before it is decoded. +func ValidatePayloadBlobSize(field, blob string) error { + if len(blob) > MaxUniversalPayloadBytes { + return errors.Wrapf(sdkerrors.ErrInvalidRequest, + "%s too large: %d bytes exceeds the %d byte limit", field, len(blob), MaxUniversalPayloadBytes) + } + return nil +} + // ValidateBasic does the sanity check on the UniversalPayload fields. func (p UniversalPayload) ValidateBasic() error { + // Reject oversized payloads before any of the per-field work below. + if err := p.ValidateSize(); err != nil { + return err + } + // Validate 'to' address if strings.TrimSpace(p.To) == "" { return errors.Wrap(sdkerrors.ErrInvalidAddress, "to address cannot be empty") From f96eed0899d91df9e968c1a7f20cc5d28875f720 Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Wed, 26 Aug 2026 22:13:58 +0530 Subject: [PATCH 48/60] chore: adapt push-chain-node to cosmos/evm v0.6.0 (#339) * chore: adapt to cosmos/evm v0.6.0 and bump the pin v0.6.0 removes cosmos/evm's custom x/ibc/transfer wrapper and adds stateDB / callFromPrecompile to CallEVM. Cherry-picked from #272 with the pin retargeted and one newer call site fixed; no upgrade handler (fresh-genesis branch). * chore: re-point evm pin at merged audit-fixes and adapt revert-outbound mocks Pin moves off the PR-branch commit onto evm audit-fixes (89c7e52b), which carries v0.6.0 plus every merged audit fix. build_revert_outbound_test.go arrived after this branch was opened and mocked the pre-v0.6.0 CallEVM signature: v0.6.0 inserts stateDB, so the mock needed NewStateDB and the method-name matcher moved to index 8. --- app/app.go | 16 ++++++++--- app/precompiles.go | 3 +- go.mod | 16 +++++------ go.sum | 28 +++++++++---------- .../inbound_cea_gas_and_payload_test.go | 4 ++- .../uexecutor/inbound_cea_payload_test.go | 14 ++++++---- .../inbound_cea_smart_contract_test.go | 4 ++- ...bound_multicall_outbound_atomicity_test.go | 2 ++ .../uexecutor/inbound_solana_test.go | 6 ++-- .../inbound_synthetic_bridge_test.go | 10 ++++--- .../uexecutor/vote_chain_meta_test.go | 6 ++-- test/integration/utss/fund_migration_test.go | 6 ++-- test/utils/contracts_setup.go | 18 ++++++++++-- .../keeper/build_revert_outbound_test.go | 10 +++++-- x/uexecutor/keeper/evm.go | 20 ++++++++----- x/uexecutor/keeper/gas_fee.go | 2 +- x/uexecutor/keeper/msg_server_test.go | 6 ++-- x/uexecutor/mocks/mock_evmkeeper.go | 22 ++++++++++++--- x/uexecutor/types/expected_keepers.go | 6 +++- 19 files changed, 132 insertions(+), 67 deletions(-) diff --git a/app/app.go b/app/app.go index 298da10a9..2ef99554f 100644 --- a/app/app.go +++ b/app/app.go @@ -115,8 +115,6 @@ import ( "github.com/cosmos/evm/x/feemarket" feemarketkeeper "github.com/cosmos/evm/x/feemarket/keeper" feemarkettypes "github.com/cosmos/evm/x/feemarket/types" - transfer "github.com/cosmos/evm/x/ibc/transfer" - ibctransferkeeper "github.com/cosmos/evm/x/ibc/transfer/keeper" "github.com/cosmos/evm/x/vm" // _ "github.com/ethereum/go-ethereum/core/tracers/js" @@ -142,6 +140,8 @@ import ( icahostkeeper "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/keeper" icahosttypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + transfer "github.com/cosmos/ibc-go/v10/modules/apps/transfer" + ibctransferkeeper "github.com/cosmos/ibc-go/v10/modules/apps/transfer/keeper" ibctransfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" ibc "github.com/cosmos/ibc-go/v10/modules/core" ibcclienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" //nolint:staticcheck @@ -856,17 +856,21 @@ func NewChainApp( app.IBCKeeper.ChannelKeeper, // Use ChannelKeeper as ICS4Wrapper ) - // Create Transfer Keepers : upgraded for ibc-go v10 + // Create Transfer Keepers : upgraded for ibc-go v10. + // cosmos/evm v0.6.0 removed its custom x/ibc/transfer wrapper, so this now uses + // the standard ibc-go transfer keeper. ERC-20<>IBC conversion that the custom + // keeper used to perform (it took an Erc20Keeper arg) is now done by the + // erc20 IBC middleware wrapped around the transfer stack below. app.TransferKeeper = ibctransferkeeper.NewKeeper( appCodec, runtime.NewKVStoreService(keys[ibctransfertypes.StoreKey]), + nil, // legacySubspace (no params subspace) app.RatelimitKeeper, // ICS4Wrapper //app.IBCFeeKeeper, app.IBCKeeper.ChannelKeeper, app.MsgServiceRouter(), app.AccountKeeper, app.BankKeeper, - app.Erc20Keeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) @@ -967,6 +971,10 @@ func NewChainApp( // Create Transfer Stack var transferStack porttypes.IBCModule transferStack = transfer.NewIBCModule(app.TransferKeeper) + // ERC-20 middleware converts IBC vouchers to/from ERC-20 tokens. In cosmos/evm + // v0.6.0 this replaced the ERC-20 conversion that the removed custom + // x/ibc/transfer keeper performed in its OnRecvPacket/msg-server override. + transferStack = erc20.NewIBCMiddleware(app.Erc20Keeper, transferStack) // callbacks wraps the transfer stack as its base app, and uses PacketForwardKeeper as the ICS4Wrapper // i.e. packet-forward-middleware is higher on the stack and sits between callbacks and the ibc channel keeper // Since this is the lowest level middleware of the transfer stack, it should be the first entrypoint for transfer keeper's diff --git a/app/precompiles.go b/app/precompiles.go index 58bc4c555..0cffa26ad 100644 --- a/app/precompiles.go +++ b/app/precompiles.go @@ -20,7 +20,7 @@ import ( slashingprecompile "github.com/cosmos/evm/precompiles/slashing" stakingprecompile "github.com/cosmos/evm/precompiles/staking" erc20Keeper "github.com/cosmos/evm/x/erc20/keeper" - transferkeeper "github.com/cosmos/evm/x/ibc/transfer/keeper" + transferkeeper "github.com/cosmos/ibc-go/v10/modules/apps/transfer/keeper" channelkeeper "github.com/cosmos/ibc-go/v10/modules/core/04-channel/keeper" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" @@ -113,6 +113,7 @@ func NewAvailableStaticPrecompiles( stakingKeeper, transferKeeper, channelKeeper, + erc20Kpr, ) bankPrecompile := bankprecompile.NewPrecompile(bankKeeper, erc20Kpr) diff --git a/go.mod b/go.mod index 62f7b78a7..42295a052 100755 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ replace ( cosmossdk.io/x/upgrade => cosmossdk.io/x/upgrade v0.1.4 github.com/CosmWasm/wasmd => github.com/CosmWasm/wasmd v0.55.0 // Keep v0.55.0 github.com/cosmos/cosmos-sdk => github.com/cosmos/cosmos-sdk v0.50.10 // Use stable v0.50.10 - github.com/cosmos/evm => github.com/pushchain/evm v1.0.0-rc2.0.20260616081105-96231e7a76c0 + github.com/cosmos/evm => github.com/pushchain/evm v1.0.0-rc2.0.20260826155700-89c7e52be541 github.com/ethereum/go-ethereum => github.com/cosmos/go-ethereum v0.0.0-20250806193535-2fc7571efa91 github.com/spf13/viper => github.com/spf13/viper v1.17.0 github.com/strangelove-ventures/tokenfactory => github.com/strangelove-ventures/tokenfactory v0.50.7-wasmvm2 @@ -56,7 +56,7 @@ require ( cosmossdk.io/x/tx v1.2.0-alpha.1 cosmossdk.io/x/upgrade v0.2.0 github.com/CosmWasm/wasmd v0.51.0 - github.com/cometbft/cometbft v0.38.19 + github.com/cometbft/cometbft v0.38.21 github.com/cosmos/cosmos-db v1.1.3 github.com/cosmos/cosmos-proto v1.0.0-beta.5 github.com/cosmos/cosmos-sdk v0.54.0-alpha.0.0.20250611155041-9fa93c9afe32 @@ -79,7 +79,7 @@ require ( github.com/rs/zerolog v1.34.0 github.com/spf13/cast v1.10.0 github.com/spf13/cobra v1.10.1 - github.com/spf13/viper v1.20.1 + github.com/spf13/viper v1.21.0 github.com/strangelove-ventures/tokenfactory v0.50.7-wasmvm2 github.com/stretchr/testify v1.11.1 google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 @@ -238,7 +238,7 @@ require ( github.com/cosmos/iavl v1.2.6 // indirect github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10 v10.4.0 github.com/cosmos/ics23/go v0.11.0 // indirect - github.com/cosmos/ledger-cosmos-go v0.16.0 // indirect + github.com/cosmos/ledger-cosmos-go v1.0.0 // indirect github.com/creachadair/atomicfile v0.3.7 // indirect github.com/creachadair/tomledit v0.0.28 // indirect github.com/danieljoos/wincred v1.2.1 // indirect @@ -294,7 +294,7 @@ require ( github.com/hashicorp/yamux v0.1.2 // indirect github.com/hdevalence/ed25519consensus v0.2.0 // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect - github.com/holiman/uint256 v1.3.2 // indirect + github.com/holiman/uint256 v1.3.2 github.com/huandu/skiplist v1.2.1 // indirect github.com/huin/goupnp v1.3.0 // indirect github.com/iancoleman/orderedmap v0.3.0 // indirect @@ -343,12 +343,12 @@ require ( github.com/rivo/uniseg v0.2.0 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rs/cors v1.11.1 // indirect - github.com/sagikazarmark/locafero v0.9.0 // indirect + github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sasha-s/go-deadlock v0.3.5 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect - github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/afero v1.14.0 // indirect + github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect + github.com/spf13/afero v1.15.0 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091 // indirect github.com/stretchr/objx v0.5.2 // indirect diff --git a/go.sum b/go.sum index 8aab20475..9ffc7c79b 100755 --- a/go.sum +++ b/go.sum @@ -862,8 +862,8 @@ github.com/cockroachdb/redact v1.1.6/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZ github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb h1:3bCgBvB8PbJVMX1ouCcSIxvsqKPYM7gs72o0zC76n9g= github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/cometbft/cometbft v0.38.19 h1:vNdtCkvhuwUlrcLPAyigV7lQpmmo+tAq8CsB8gZjEYw= -github.com/cometbft/cometbft v0.38.19/go.mod h1:UCu8dlHqvkAsmAFmWDRWNZJPlu6ya2fTWZlDrWsivwo= +github.com/cometbft/cometbft v0.38.21 h1:qcIJSH9LiwU5s6ZgKR5eRbsLNucbubfraDs5bzgjtOI= +github.com/cometbft/cometbft v0.38.21/go.mod h1:UCu8dlHqvkAsmAFmWDRWNZJPlu6ya2fTWZlDrWsivwo= github.com/cometbft/cometbft-db v1.0.4 h1:cezb8yx/ZWcF124wqUtAFjAuDksS1y1yXedvtprUFxs= github.com/cometbft/cometbft-db v1.0.4/go.mod h1:M+BtHAGU2XLrpUxo3Nn1nOCcnVCiLM9yx5OuT0u5SCA= github.com/consensys/gnark-crypto v0.18.0 h1:vIye/FqI50VeAr0B3dx+YjeIvmc3LWz4yEfbWBpTUf0= @@ -915,8 +915,8 @@ github.com/cosmos/ics23/go v0.11.0 h1:jk5skjT0TqX5e5QJbEnwXIS2yI2vnmLOgpQPeM5Rtn github.com/cosmos/ics23/go v0.11.0/go.mod h1:A8OjxPE67hHST4Icw94hOxxFEJMBG031xIGF/JHNIY0= github.com/cosmos/keyring v1.2.0 h1:8C1lBP9xhImmIabyXW4c3vFjjLiBdGCmfLUfeZlV1Yo= github.com/cosmos/keyring v1.2.0/go.mod h1:fc+wB5KTk9wQ9sDx0kFXB3A0MaeGHM9AwRStKOQ5vOA= -github.com/cosmos/ledger-cosmos-go v0.16.0 h1:YKlWPG9NnGZIEUb2bEfZ6zhON1CHlNTg0QKRRGcNEd0= -github.com/cosmos/ledger-cosmos-go v0.16.0/go.mod h1:WrM2xEa8koYoH2DgeIuZXNarF7FGuZl3mrIOnp3Dp0o= +github.com/cosmos/ledger-cosmos-go v1.0.0 h1:jNKW89nPf0vR0EkjHG8Zz16h6p3zqwYEOxlHArwgYtw= +github.com/cosmos/ledger-cosmos-go v1.0.0/go.mod h1:mGaw2wDOf+Z6SfRJsMGxU9DIrBa4du0MAiPlpPhLAOE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= @@ -1765,8 +1765,8 @@ github.com/prysmaticlabs/gohashtree v0.0.4-beta.0.20240624100937-73632381301b h1 github.com/prysmaticlabs/gohashtree v0.0.4-beta.0.20240624100937-73632381301b/go.mod h1:HRuvtXLZ4WkaB1MItToVH2e8ZwKwZPY5/Rcby+CvvLY= github.com/prysmaticlabs/prysm/v5 v5.3.0 h1:7Lr8ndapBTZg00YE+MgujN6+yvJR6Bdfn28ZDSJ00II= github.com/prysmaticlabs/prysm/v5 v5.3.0/go.mod h1:r1KhlduqDMIGZ1GhR5pjZ2Ko8Q89noTDYTRoPKwf1+c= -github.com/pushchain/evm v1.0.0-rc2.0.20260616081105-96231e7a76c0 h1:y4oaq20SC2hFSg2/AyLc4iSLu9i6z/mCgyKcsrVyVhg= -github.com/pushchain/evm v1.0.0-rc2.0.20260616081105-96231e7a76c0/go.mod h1:BjKknQX/cnH/v/i2AgtfsJY4g/gihm9n6ilXk2SExUo= +github.com/pushchain/evm v1.0.0-rc2.0.20260826155700-89c7e52be541 h1:Ols8viNso7TAphC5MYbl+GrmMuFVKvvfn8uzeGEnV0Q= +github.com/pushchain/evm v1.0.0-rc2.0.20260826155700-89c7e52be541/go.mod h1:QuenX5DgRhWeYdIg0J/p65cyS/ntpgnzpZOIajZ/SHk= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= github.com/quic-go/qtls-go1-20 v0.3.4 h1:MfFAPULvst4yoMgY9QmtpYmfij/em7O8UUi+bNVm7Cg= @@ -1809,8 +1809,8 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k= -github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk= +github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= +github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= @@ -1859,8 +1859,8 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9 github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= -github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= -github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= @@ -1868,8 +1868,8 @@ github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2 github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= -github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= -github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= +github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= +github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= @@ -2061,8 +2061,8 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= -go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= -go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= golang.org/x/arch v0.17.0 h1:4O3dfLzd+lQewptAHqjewQZQDyEdejz3VwgeYwkZneU= golang.org/x/arch v0.17.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= diff --git a/test/integration/uexecutor/inbound_cea_gas_and_payload_test.go b/test/integration/uexecutor/inbound_cea_gas_and_payload_test.go index a1df82cf8..c0f6f3c9e 100644 --- a/test/integration/uexecutor/inbound_cea_gas_and_payload_test.go +++ b/test/integration/uexecutor/inbound_cea_gas_and_payload_test.go @@ -468,10 +468,12 @@ func TestInboundCEAGasAndPayload(t *testing.T) { // Check that PRC20 was deposited into the UEA (recipient) res, err := chainApp.EVMKeeper.CallEVM( ctx, + chainApp.EVMKeeper.NewStateDB(ctx), prc20ABI, ueModuleAccAddress, prc20Address, false, + false, nil, "balanceOf", ueaAddrHex, @@ -497,7 +499,7 @@ func TestInboundCEAGasAndPayload(t *testing.T) { chainApp.UregistryKeeper.AddChainConfig(ctx, &uregistrytypes.ChainConfig{ Chain: "eip155:97", VmType: uregistrytypes.VmType_EVM, - PublicRpcUrl: "https://data-seed-prebsc-1-s1.binance.org:8545", + PublicRpcUrl: "https://data-seed-prebsc-1-s1.binance.org:8545", GatewayAddress: "0x0000000000000000000000000000000000000000", BlockConfirmation: &uregistrytypes.BlockConfirmation{ FastInbound: 5, diff --git a/test/integration/uexecutor/inbound_cea_payload_test.go b/test/integration/uexecutor/inbound_cea_payload_test.go index 1cb993af0..d69d69001 100644 --- a/test/integration/uexecutor/inbound_cea_payload_test.go +++ b/test/integration/uexecutor/inbound_cea_payload_test.go @@ -27,9 +27,9 @@ func setupInboundCEAPayloadTest(t *testing.T, numVals int) (*app.ChainApp, sdk.C chainApp, ctx, _, validators := utils.SetAppWithMultipleValidators(t, numVals) chainConfigTest := uregistrytypes.ChainConfig{ - Chain: "eip155:11155111", - VmType: uregistrytypes.VmType_EVM, - PublicRpcUrl: "https://sepolia.drpc.org", + Chain: "eip155:11155111", + VmType: uregistrytypes.VmType_EVM, + PublicRpcUrl: "https://sepolia.drpc.org", GatewayAddress: "0x28E0F09bE2321c1420Dc60Ee146aACbD68B335Fe", BlockConfirmation: &uregistrytypes.BlockConfirmation{ FastInbound: 5, @@ -227,10 +227,12 @@ func TestInboundCEAFundsAndPayload(t *testing.T) { // Check that PRC20 was deposited into the UEA (recipient) res, err := chainApp.EVMKeeper.CallEVM( ctx, + chainApp.EVMKeeper.NewStateDB(ctx), prc20ABI, ueModuleAccAddress, prc20Address, false, + false, nil, "balanceOf", ueaAddrHex, @@ -597,7 +599,7 @@ func TestInboundCEAFundsAndPayload(t *testing.T) { ceaInbound := &uexecutortypes.Inbound{ SourceChain: "eip155:11155111", TxHash: "0xcea07", - Sender: personBSender, // person B — no UEA + Sender: personBSender, // person B — no UEA Recipient: ueaAddrHex.String(), // person A's UEA Amount: "1000000", AssetAddr: usdcAddress.String(), @@ -633,10 +635,12 @@ func TestInboundCEAFundsAndPayload(t *testing.T) { // Confirm the PRC20 balance landed at the explicitly passed recipient (person A's UEA) res, err := chainApp.EVMKeeper.CallEVM( ctx, + chainApp.EVMKeeper.NewStateDB(ctx), prc20ABI, ueModuleAccAddress, prc20Address, false, + false, nil, "balanceOf", ueaAddrHex, @@ -726,7 +730,7 @@ func TestInboundCEAFundsAndPayload(t *testing.T) { chainApp.UregistryKeeper.AddChainConfig(ctx, &uregistrytypes.ChainConfig{ Chain: "eip155:97", VmType: uregistrytypes.VmType_EVM, - PublicRpcUrl: "https://data-seed-prebsc-1-s1.binance.org:8545", + PublicRpcUrl: "https://data-seed-prebsc-1-s1.binance.org:8545", GatewayAddress: "0x0000000000000000000000000000000000000000", BlockConfirmation: &uregistrytypes.BlockConfirmation{ FastInbound: 5, diff --git a/test/integration/uexecutor/inbound_cea_smart_contract_test.go b/test/integration/uexecutor/inbound_cea_smart_contract_test.go index b31dc182e..dfea7d685 100644 --- a/test/integration/uexecutor/inbound_cea_smart_contract_test.go +++ b/test/integration/uexecutor/inbound_cea_smart_contract_test.go @@ -239,10 +239,12 @@ func TestInboundCEASmartContractRecipient(t *testing.T) { res, err := chainApp.EVMKeeper.CallEVM( ctx, + chainApp.EVMKeeper.NewStateDB(ctx), prc20ABI, ueModuleAccAddress, prc20Address, false, + false, nil, "balanceOf", contractAddr, @@ -455,7 +457,7 @@ func TestInboundCEASmartContractRecipient(t *testing.T) { ueModuleAccAddress, _ := chainApp.UexecutorKeeper.GetUeModuleAddress(ctx) res, err := chainApp.EVMKeeper.CallEVM( - ctx, prc20ABI, ueModuleAccAddress, prc20Address, false, nil, "balanceOf", recipientAddr, + ctx, chainApp.EVMKeeper.NewStateDB(ctx), prc20ABI, ueModuleAccAddress, prc20Address, false, false, nil, "balanceOf", recipientAddr, ) require.NoError(t, err) balances, err := prc20ABI.Unpack("balanceOf", res.Ret) diff --git a/test/integration/uexecutor/inbound_multicall_outbound_atomicity_test.go b/test/integration/uexecutor/inbound_multicall_outbound_atomicity_test.go index 06c3ca9a5..e3075ec42 100644 --- a/test/integration/uexecutor/inbound_multicall_outbound_atomicity_test.go +++ b/test/integration/uexecutor/inbound_multicall_outbound_atomicity_test.go @@ -451,10 +451,12 @@ func prc20BalanceOf(t *testing.T, chainApp *app.ChainApp, ctx sdk.Context, holde ueModuleAccAddress, _ := chainApp.UexecutorKeeper.GetUeModuleAddress(ctx) res, err := chainApp.EVMKeeper.CallEVM( ctx, + chainApp.EVMKeeper.NewStateDB(ctx), prc20ABI, ueModuleAccAddress, utils.GetDefaultAddresses().PRC20USDCAddr, false, + false, nil, "balanceOf", holder, diff --git a/test/integration/uexecutor/inbound_solana_test.go b/test/integration/uexecutor/inbound_solana_test.go index 4fdd8d787..ca9d75ae2 100644 --- a/test/integration/uexecutor/inbound_solana_test.go +++ b/test/integration/uexecutor/inbound_solana_test.go @@ -142,7 +142,7 @@ func TestSolanaInboundFunds(t *testing.T) { recipient := common.HexToAddress(inbound.Recipient) // Check initial balance is 0 - res, err := app.EVMKeeper.CallEVM(ctx, prc20ABI, ueModuleAccAddress, prc20Address, false, nil, "balanceOf", recipient) + res, err := app.EVMKeeper.CallEVM(ctx, app.EVMKeeper.NewStateDB(ctx), prc20ABI, ueModuleAccAddress, prc20Address, false, false, nil, "balanceOf", recipient) require.NoError(t, err) balances, _ := prc20ABI.Unpack("balanceOf", res.Ret) require.Equal(t, int64(0), balances[0].(*big.Int).Int64()) @@ -156,7 +156,7 @@ func TestSolanaInboundFunds(t *testing.T) { require.False(t, isPending) // PRC20 balance should equal inbound amount - res, err = app.EVMKeeper.CallEVM(ctx, prc20ABI, ueModuleAccAddress, prc20Address, false, nil, "balanceOf", recipient) + res, err = app.EVMKeeper.CallEVM(ctx, app.EVMKeeper.NewStateDB(ctx), prc20ABI, ueModuleAccAddress, prc20Address, false, false, nil, "balanceOf", recipient) require.NoError(t, err) balances, _ = prc20ABI.Unpack("balanceOf", res.Ret) expected := new(big.Int) @@ -179,7 +179,7 @@ func TestSolanaInboundFunds(t *testing.T) { voteToQuorum(t, ctx, app, vals, coreVals, &inbound2) // Balance should be 2x - res, err := app.EVMKeeper.CallEVM(ctx, prc20ABI, ueModuleAccAddress, prc20Address, false, nil, "balanceOf", recipient) + res, err := app.EVMKeeper.CallEVM(ctx, app.EVMKeeper.NewStateDB(ctx), prc20ABI, ueModuleAccAddress, prc20Address, false, false, nil, "balanceOf", recipient) require.NoError(t, err) balances, _ := prc20ABI.Unpack("balanceOf", res.Ret) expected := new(big.Int) diff --git a/test/integration/uexecutor/inbound_synthetic_bridge_test.go b/test/integration/uexecutor/inbound_synthetic_bridge_test.go index 50fbeea39..c5263c259 100644 --- a/test/integration/uexecutor/inbound_synthetic_bridge_test.go +++ b/test/integration/uexecutor/inbound_synthetic_bridge_test.go @@ -179,10 +179,12 @@ func TestInboundSyntheticBridge(t *testing.T) { // --- Query PRC20 balanceOf(recipient) --- res, err := app.EVMKeeper.CallEVM( ctx, + app.EVMKeeper.NewStateDB(ctx), prc20ABI, ueModuleAccAddress, // "from" (doesn't matter for view) prc20Address, // contract address - false, // commit = false (read-only) + false, + false, // commit = false (read-only) nil, "balanceOf", recipient, @@ -261,7 +263,7 @@ func TestInboundSyntheticBridge(t *testing.T) { recipient := common.HexToAddress(inbound.Recipient) // check initial balance == 0 - res, err := app.EVMKeeper.CallEVM(ctx, prc20ABI, ueModuleAccAddress, prc20Address, false, nil, "balanceOf", recipient) + res, err := app.EVMKeeper.CallEVM(ctx, app.EVMKeeper.NewStateDB(ctx), prc20ABI, ueModuleAccAddress, prc20Address, false, false, nil, "balanceOf", recipient) require.NoError(t, err) balances, _ := prc20ABI.Unpack("balanceOf", res.Ret) balance := balances[0].(*big.Int) @@ -278,7 +280,7 @@ func TestInboundSyntheticBridge(t *testing.T) { } // balance should equal inbound amount - res, err = app.EVMKeeper.CallEVM(ctx, prc20ABI, ueModuleAccAddress, prc20Address, false, nil, "balanceOf", recipient) + res, err = app.EVMKeeper.CallEVM(ctx, app.EVMKeeper.NewStateDB(ctx), prc20ABI, ueModuleAccAddress, prc20Address, false, false, nil, "balanceOf", recipient) require.NoError(t, err) balances, _ = prc20ABI.Unpack("balanceOf", res.Ret) expected := new(big.Int) @@ -315,7 +317,7 @@ func TestInboundSyntheticBridge(t *testing.T) { } // balance should equal 2 * inbound.Amount - res, err := app.EVMKeeper.CallEVM(ctx, prc20ABI, ueModuleAccAddress, prc20Address, false, nil, "balanceOf", recipient) + res, err := app.EVMKeeper.CallEVM(ctx, app.EVMKeeper.NewStateDB(ctx), prc20ABI, ueModuleAccAddress, prc20Address, false, false, nil, "balanceOf", recipient) require.NoError(t, err) balances, _ := prc20ABI.Unpack("balanceOf", res.Ret) diff --git a/test/integration/uexecutor/vote_chain_meta_test.go b/test/integration/uexecutor/vote_chain_meta_test.go index 5c72966bc..bb6474256 100644 --- a/test/integration/uexecutor/vote_chain_meta_test.go +++ b/test/integration/uexecutor/vote_chain_meta_test.go @@ -299,7 +299,7 @@ func TestVoteChainMetaIntegration(t *testing.T) { ucABI, err := uexecutortypes.ParseUniversalCoreABI() require.NoError(t, err) caller, _ := testApp.UexecutorKeeper.GetUeModuleAddress(ctx) - res, err := testApp.EVMKeeper.CallEVM(ctx, ucABI, caller, universalCoreAddr, false, nil, "gasPriceByChainNamespace", chainId) + res, err := testApp.EVMKeeper.CallEVM(ctx, testApp.EVMKeeper.NewStateDB(ctx), ucABI, caller, universalCoreAddr, false, false, nil, "gasPriceByChainNamespace", chainId) require.NoError(t, err) appliedPrice := new(big.Int).SetBytes(res.Ret) require.Equal(t, new(big.Int).SetUint64(900), appliedPrice, "stale votes must not influence the applied median price") @@ -435,14 +435,14 @@ func TestVoteChainMetaContractState(t *testing.T) { caller, _ := testApp.UexecutorKeeper.GetUeModuleAddress(ctx) t.Run("gasPriceByChainNamespace matches voted price", func(t *testing.T) { - res, err := testApp.EVMKeeper.CallEVM(ctx, ucABI, caller, universalCoreAddr, false, nil, "gasPriceByChainNamespace", chainId) + res, err := testApp.EVMKeeper.CallEVM(ctx, testApp.EVMKeeper.NewStateDB(ctx), ucABI, caller, universalCoreAddr, false, false, nil, "gasPriceByChainNamespace", chainId) require.NoError(t, err) got := new(big.Int).SetBytes(res.Ret) require.Equal(t, new(big.Int).SetUint64(price), got) }) t.Run("chainHeightByChainNamespace matches voted height", func(t *testing.T) { - res, err := testApp.EVMKeeper.CallEVM(ctx, ucABI, caller, universalCoreAddr, false, nil, "chainHeightByChainNamespace", chainId) + res, err := testApp.EVMKeeper.CallEVM(ctx, testApp.EVMKeeper.NewStateDB(ctx), ucABI, caller, universalCoreAddr, false, false, nil, "chainHeightByChainNamespace", chainId) require.NoError(t, err) got := new(big.Int).SetBytes(res.Ret) require.Equal(t, new(big.Int).SetUint64(height), got) diff --git a/test/integration/utss/fund_migration_test.go b/test/integration/utss/fund_migration_test.go index ce0b30e1f..12f7dc001 100644 --- a/test/integration/utss/fund_migration_test.go +++ b/test/integration/utss/fund_migration_test.go @@ -90,13 +90,13 @@ func seedFundMigrationChainValues( var roleArg [32]byte copy(roleArg[:], managerRole.Bytes()) - _, err = chainApp.EVMKeeper.CallEVM(ctx, setupABI, admin, handlerAddr, true, nil, "grantRole", roleArg, admin) + _, err = chainApp.EVMKeeper.CallEVM(ctx, chainApp.EVMKeeper.NewStateDB(ctx), setupABI, admin, handlerAddr, true, false, nil, "grantRole", roleArg, admin) require.NoError(t, err, "grant MANAGER_ROLE") - _, err = chainApp.EVMKeeper.CallEVM(ctx, setupABI, admin, handlerAddr, true, nil, "setTssFundMigrationGasLimitByChain", chain, gasLimit) + _, err = chainApp.EVMKeeper.CallEVM(ctx, chainApp.EVMKeeper.NewStateDB(ctx), setupABI, admin, handlerAddr, true, false, nil, "setTssFundMigrationGasLimitByChain", chain, gasLimit) require.NoError(t, err, "seed tss fund migration gas limit") - _, err = chainApp.EVMKeeper.CallEVM(ctx, setupABI, admin, handlerAddr, true, nil, "setL1GasFeeByChain", chain, l1GasFee) + _, err = chainApp.EVMKeeper.CallEVM(ctx, chainApp.EVMKeeper.NewStateDB(ctx), setupABI, admin, handlerAddr, true, false, nil, "setL1GasFeeByChain", chain, l1GasFee) require.NoError(t, err, "seed l1 gas fee") } diff --git a/test/utils/contracts_setup.go b/test/utils/contracts_setup.go index edaca4cce..dc588b290 100644 --- a/test/utils/contracts_setup.go +++ b/test/utils/contracts_setup.go @@ -89,10 +89,12 @@ func setupHandlerContract( // Set UEA proxy implementation _, err := app.EVMKeeper.CallEVM( ctx, + app.EVMKeeper.NewStateDB(ctx), handlerABI, owner, handlerAddr, true, + false, nil, "initialize", common.HexToAddress(WPCAddress), @@ -116,16 +118,16 @@ func setupFactoryContract( owner := common.BytesToAddress(accounts.DefaultAccount.GetAddress().Bytes()) // Check initial factory owner - ownerResult, err := app.EVMKeeper.CallEVM(ctx, factoryABI, owner, factoryAddr, true, nil, "owner") + ownerResult, err := app.EVMKeeper.CallEVM(ctx, app.EVMKeeper.NewStateDB(ctx), factoryABI, owner, factoryAddr, true, false, nil, "owner") require.NoError(t, err) t.Logf("Factory owner after genesis: %s", common.BytesToAddress(ownerResult.Ret).Hex()) // Initialize factory with owner - _, err = app.EVMKeeper.CallEVM(ctx, factoryABI, owner, factoryAddr, true, nil, "initialize", owner) + _, err = app.EVMKeeper.CallEVM(ctx, app.EVMKeeper.NewStateDB(ctx), factoryABI, owner, factoryAddr, true, false, nil, "initialize", owner) require.NoError(t, err) // Verify owner is set - ownerResult, err = app.EVMKeeper.CallEVM(ctx, factoryABI, owner, factoryAddr, true, nil, "owner") + ownerResult, err = app.EVMKeeper.CallEVM(ctx, app.EVMKeeper.NewStateDB(ctx), factoryABI, owner, factoryAddr, true, false, nil, "owner") require.NoError(t, err) t.Logf("Factory owner after initialization: %s", common.BytesToAddress(ownerResult.Ret).Hex()) @@ -141,10 +143,12 @@ func setupFactoryContract( // Set UEA proxy implementation receipt, err := app.EVMKeeper.CallEVM( ctx, + app.EVMKeeper.NewStateDB(ctx), factoryABI, owner, factoryAddr, true, + false, nil, "setUEAProxyImplementation", ProxyAddress, @@ -178,10 +182,12 @@ func setupPrc20Contract( // Set UEA proxy implementation _, err := app.EVMKeeper.CallEVM( ctx, + app.EVMKeeper.NewStateDB(ctx), prc20ABI, ueModuleAccAddress, prc20Addr, true, + false, nil, "updateHandlerContract", opts.Addresses.HandlerAddr, @@ -218,10 +224,12 @@ func registerEVMChainAndUEA( // Register new EVM chain _, err = chainApp.EVMKeeper.CallEVM( ctx, + chainApp.EVMKeeper.NewStateDB(ctx), factoryABI, owner, factoryAddr, true, + false, nil, "registerNewChain", ChainHashEVM, @@ -249,10 +257,12 @@ func registerEVMChainAndUEA( // Register UEA : EVM _, err = chainApp.EVMKeeper.CallEVM( ctx, + chainApp.EVMKeeper.NewStateDB(ctx), factoryABI, owner, factoryAddr, true, + false, nil, "registerUEA", ChainHashEVM, @@ -264,10 +274,12 @@ func registerEVMChainAndUEA( // Get UEA (EVM) address ueaAddrResultEVM, err := chainApp.EVMKeeper.CallEVM( ctx, + chainApp.EVMKeeper.NewStateDB(ctx), factoryABI, owner, factoryAddr, true, + false, nil, "getUEA", ChainHashEVM, diff --git a/x/uexecutor/keeper/build_revert_outbound_test.go b/x/uexecutor/keeper/build_revert_outbound_test.go index 890262c42..e1d728f34 100644 --- a/x/uexecutor/keeper/build_revert_outbound_test.go +++ b/x/uexecutor/keeper/build_revert_outbound_test.go @@ -70,9 +70,12 @@ func expectGasFeeCall(t *testing.T, f *testFixture, gasFee, gasPrice, gasLimit * ) require.NoError(t, err) + // cosmos/evm v0.6.0: GetGasFeeInfoForRevertOutbound builds the StateDB itself + // and passes it into CallEVM, so the mock must expect that call too. + f.mockEVMKeeper.EXPECT().NewStateDB(gomock.Any()).Return(nil).AnyTimes() f.mockEVMKeeper.EXPECT(). CallEVM(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), - gomock.Eq("getOutboundTxGasAndFees"), gomock.Any(), gomock.Any()). + gomock.Any(), gomock.Any(), gomock.Eq("getOutboundTxGasAndFees"), gomock.Any(), gomock.Any()). Return(&evmtypes.MsgEthereumTxResponse{Ret: packed}, nil). AnyTimes() } @@ -151,9 +154,12 @@ func TestBuildRevertOutbound_GasFeeLookupFails(t *testing.T) { GetTokenConfig(gomock.Any(), revertSourceChain, revertAssetAddr). Return(revertTestTokenConfig(), nil). AnyTimes() + // cosmos/evm v0.6.0: GetGasFeeInfoForRevertOutbound builds the StateDB itself + // and passes it into CallEVM, so the mock must expect that call too. + f.mockEVMKeeper.EXPECT().NewStateDB(gomock.Any()).Return(nil).AnyTimes() f.mockEVMKeeper.EXPECT(). CallEVM(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), - gomock.Eq("getOutboundTxGasAndFees"), gomock.Any(), gomock.Any()). + gomock.Any(), gomock.Any(), gomock.Eq("getOutboundTxGasAndFees"), gomock.Any(), gomock.Any()). Return(nil, errors.New("execution reverted: ZeroGasPrice")). AnyTimes() diff --git a/x/uexecutor/keeper/evm.go b/x/uexecutor/keeper/evm.go index 4ec8d5174..1332a1db5 100644 --- a/x/uexecutor/keeper/evm.go +++ b/x/uexecutor/keeper/evm.go @@ -76,10 +76,12 @@ func (k Keeper) CallFactoryToGetUEAAddressForOrigin( receipt, err := k.evmKeeper.CallEVM( ctx, + k.evmKeeper.NewStateDB(ctx), abi, from, factoryAddr, false, // commit + false, // callFromPrecompile nil, "getUEAForOrigin", abiUniversalAccount, @@ -112,10 +114,12 @@ func (k Keeper) CallFactoryGetOriginForUEA( receipt, err := k.evmKeeper.CallEVM( ctx, + k.evmKeeper.NewStateDB(ctx), abi, from, factoryAddr, false, // commit + false, // callFromPrecompile nil, "getOriginForUEA", ueaAddr, @@ -284,10 +288,12 @@ func (k Keeper) CallUEADomainSeparator( // Call the view function domainSeparator() res, err := k.evmKeeper.CallEVM( ctx, + k.evmKeeper.NewStateDB(ctx), abi, from, ueaAddr, false, // commit = false (static call) + false, // callFromPrecompile nil, "domainSeparator", ) @@ -375,7 +381,7 @@ func (k Keeper) GetGasPriceByChain(ctx sdk.Context, chainNamespace string) (*big ueModuleAccAddress, _ := k.GetUeModuleAddress(ctx) - receipt, err := k.evmKeeper.CallEVM(ctx, abi, ueModuleAccAddress, handlerAddr, false, nil, "gasPriceByChainNamespace", chainNamespace) + receipt, err := k.evmKeeper.CallEVM(ctx, k.evmKeeper.NewStateDB(ctx), abi, ueModuleAccAddress, handlerAddr, false, false, nil,"gasPriceByChainNamespace", chainNamespace) if err != nil { return nil, errors.Wrap(err, "failed to call gasPriceByChainNamespace") } @@ -400,7 +406,7 @@ func (k Keeper) GetL1GasFeeByChain(ctx sdk.Context, chainNamespace string) (*big ueModuleAccAddress, _ := k.GetUeModuleAddress(ctx) - receipt, err := k.evmKeeper.CallEVM(ctx, abi, ueModuleAccAddress, handlerAddr, false, nil, "l1GasFeeByChainNamespace", chainNamespace) + receipt, err := k.evmKeeper.CallEVM(ctx, k.evmKeeper.NewStateDB(ctx), abi, ueModuleAccAddress, handlerAddr, false, false, nil,"l1GasFeeByChainNamespace", chainNamespace) if err != nil { return nil, errors.Wrap(err, "failed to call l1GasFeeByChainNamespace") } @@ -424,7 +430,7 @@ func (k Keeper) GetTssFundMigrationGasLimitByChain(ctx sdk.Context, chainNamespa ueModuleAccAddress, _ := k.GetUeModuleAddress(ctx) - receipt, err := k.evmKeeper.CallEVM(ctx, abi, ueModuleAccAddress, handlerAddr, false, nil, "tssFundMigrationGasLimitByChainNamespace", chainNamespace) + receipt, err := k.evmKeeper.CallEVM(ctx, k.evmKeeper.NewStateDB(ctx), abi, ueModuleAccAddress, handlerAddr, false, false, nil,"tssFundMigrationGasLimitByChainNamespace", chainNamespace) if err != nil { return nil, errors.Wrap(err, "failed to call tssFundMigrationGasLimitByChainNamespace") } @@ -448,7 +454,7 @@ func (k Keeper) GetUniversalCoreQuoterAddress(ctx sdk.Context) (common.Address, ueModuleAccAddress, _ := k.GetUeModuleAddress(ctx) - receipt, err := k.evmKeeper.CallEVM(ctx, abi, ueModuleAccAddress, handlerAddr, false, nil, "uniswapV3Quoter") + receipt, err := k.evmKeeper.CallEVM(ctx, k.evmKeeper.NewStateDB(ctx), abi, ueModuleAccAddress, handlerAddr, false, false, nil,"uniswapV3Quoter") if err != nil { return common.Address{}, errors.Wrap(err, "failed to call uniswapV3Quoter") } @@ -472,7 +478,7 @@ func (k Keeper) GetUniversalCoreWPCAddress(ctx sdk.Context) (common.Address, err ueModuleAccAddress, _ := k.GetUeModuleAddress(ctx) - receipt, err := k.evmKeeper.CallEVM(ctx, abi, ueModuleAccAddress, handlerAddr, false, nil, "WPC") + receipt, err := k.evmKeeper.CallEVM(ctx, k.evmKeeper.NewStateDB(ctx), abi, ueModuleAccAddress, handlerAddr, false, false, nil,"WPC") if err != nil { return common.Address{}, errors.Wrap(err, "failed to call WPC") } @@ -496,7 +502,7 @@ func (k Keeper) GetDefaultFeeTierForToken(ctx sdk.Context, prc20Address common.A ueModuleAccAddress, _ := k.GetUeModuleAddress(ctx) - receipt, err := k.evmKeeper.CallEVM(ctx, abi, ueModuleAccAddress, handlerAddr, false, nil, "defaultFeeTier", prc20Address) + receipt, err := k.evmKeeper.CallEVM(ctx, k.evmKeeper.NewStateDB(ctx), abi, ueModuleAccAddress, handlerAddr, false, false, nil,"defaultFeeTier", prc20Address) if err != nil { return nil, errors.Wrap(err, "failed to call defaultFeeTier") } @@ -537,7 +543,7 @@ func (k Keeper) GetSwapQuote( SqrtPriceLimitX96: big.NewInt(0), } - receipt, err := k.evmKeeper.CallEVM(ctx, quoterABI, ueModuleAccAddress, quoterAddr, false, nil, "quoteExactInputSingle", params) + receipt, err := k.evmKeeper.CallEVM(ctx, k.evmKeeper.NewStateDB(ctx), quoterABI, ueModuleAccAddress, quoterAddr, false, false, nil, "quoteExactInputSingle", params) if err != nil { return nil, errors.Wrap(err, "QuoterV2 quoteExactInputSingle failed") } diff --git a/x/uexecutor/keeper/gas_fee.go b/x/uexecutor/keeper/gas_fee.go index 183ba9ce2..3f8c249ec 100644 --- a/x/uexecutor/keeper/gas_fee.go +++ b/x/uexecutor/keeper/gas_fee.go @@ -33,7 +33,7 @@ func (k Keeper) GetOutboundTxGasAndFees(ctx sdk.Context, prc20 common.Address, g ueModuleAccAddress, _ := k.GetUeModuleAddress(ctx) - receipt, err := k.evmKeeper.CallEVM(ctx, ucABI, ueModuleAccAddress, handlerAddr, false, nil, + receipt, err := k.evmKeeper.CallEVM(ctx, k.evmKeeper.NewStateDB(ctx), ucABI, ueModuleAccAddress, handlerAddr, false, false, nil, "getOutboundTxGasAndFees", prc20, gasLimitWithBaseLimit) if err != nil { return nil, errors.Wrap(err, "failed to call getOutboundTxGasAndFees") diff --git a/x/uexecutor/keeper/msg_server_test.go b/x/uexecutor/keeper/msg_server_test.go index 6a63dc3f3..4465e1e42 100755 --- a/x/uexecutor/keeper/msg_server_test.go +++ b/x/uexecutor/keeper/msg_server_test.go @@ -143,7 +143,8 @@ func TestMsgServer_ExecutePayload(t *testing.T) { f.mockUregistryKeeper.EXPECT().GetChainConfig(gomock.Any(), "eip155:11155111").Return(chainConfigTest, nil) - f.mockEVMKeeper.EXPECT().CallEVM(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errors.New("CallFactoryToComputeUEAAddress Failed")) + f.mockEVMKeeper.EXPECT().NewStateDB(gomock.Any()).Return(nil).AnyTimes() + f.mockEVMKeeper.EXPECT().CallEVM(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errors.New("CallFactoryToComputeUEAAddress Failed")) _, err := f.msgServer.ExecutePayload(f.ctx, msg) require.ErrorContains(t, err, "CallFactoryToComputeUEAAddress Failed") @@ -257,7 +258,8 @@ func TestMsgServer_MigrateUEA(t *testing.T) { f.mockUregistryKeeper.EXPECT().GetChainConfig(gomock.Any(), "eip155:11155111").Return(chainConfigTest, nil) - f.mockEVMKeeper.EXPECT().CallEVM(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errors.New("CallFactoryToComputeUEAAddress Failed")).AnyTimes() + f.mockEVMKeeper.EXPECT().NewStateDB(gomock.Any()).Return(nil).AnyTimes() + f.mockEVMKeeper.EXPECT().CallEVM(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errors.New("CallFactoryToComputeUEAAddress Failed")).AnyTimes() _, err := f.msgServer.MigrateUEA(f.ctx, msg) require.ErrorContains(t, err, "CallFactoryToComputeUEAAddress Failed") diff --git a/x/uexecutor/mocks/mock_evmkeeper.go b/x/uexecutor/mocks/mock_evmkeeper.go index f33ed6019..ba1aa8333 100644 --- a/x/uexecutor/mocks/mock_evmkeeper.go +++ b/x/uexecutor/mocks/mock_evmkeeper.go @@ -40,9 +40,9 @@ func (m *MockEVMKeeper) EXPECT() *MockEVMKeeperMockRecorder { } // CallEVM mocks base method. -func (m *MockEVMKeeper) CallEVM(ctx types.Context, abi abi.ABI, from, contract common.Address, commit bool, gasCap *big.Int, method string, args ...interface{}) (*types0.MsgEthereumTxResponse, error) { +func (m *MockEVMKeeper) CallEVM(ctx types.Context, stateDB *statedb.StateDB, abi abi.ABI, from, contract common.Address, commit, callFromPrecompile bool, gasCap *big.Int, method string, args ...interface{}) (*types0.MsgEthereumTxResponse, error) { m.ctrl.T.Helper() - varargs := []interface{}{ctx, abi, from, contract, commit, gasCap, method} + varargs := []interface{}{ctx, stateDB, abi, from, contract, commit, callFromPrecompile, gasCap, method} for _, a := range args { varargs = append(varargs, a) } @@ -53,12 +53,26 @@ func (m *MockEVMKeeper) CallEVM(ctx types.Context, abi abi.ABI, from, contract c } // CallEVM indicates an expected call of CallEVM. -func (mr *MockEVMKeeperMockRecorder) CallEVM(ctx, abi, from, contract, commit, gasCap, method interface{}, args ...interface{}) *gomock.Call { +func (mr *MockEVMKeeperMockRecorder) CallEVM(ctx, stateDB, abi, from, contract, commit, callFromPrecompile, gasCap, method interface{}, args ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{ctx, abi, from, contract, commit, gasCap, method}, args...) + varargs := append([]interface{}{ctx, stateDB, abi, from, contract, commit, callFromPrecompile, gasCap, method}, args...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CallEVM", reflect.TypeOf((*MockEVMKeeper)(nil).CallEVM), varargs...) } +// NewStateDB mocks base method. +func (m *MockEVMKeeper) NewStateDB(ctx types.Context) *statedb.StateDB { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NewStateDB", ctx) + ret0, _ := ret[0].(*statedb.StateDB) + return ret0 +} + +// NewStateDB indicates an expected call of NewStateDB. +func (mr *MockEVMKeeperMockRecorder) NewStateDB(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewStateDB", reflect.TypeOf((*MockEVMKeeper)(nil).NewStateDB), ctx) +} + // GetCodeHash mocks base method. func (m *MockEVMKeeper) GetCodeHash(ctx types.Context, addr common.Address) common.Hash { m.ctrl.T.Helper() diff --git a/x/uexecutor/types/expected_keepers.go b/x/uexecutor/types/expected_keepers.go index 7577b4d3a..c03a68e58 100644 --- a/x/uexecutor/types/expected_keepers.go +++ b/x/uexecutor/types/expected_keepers.go @@ -30,11 +30,15 @@ type UregistryKeeper interface { // EVMKeeper defines the expected interface for the EVM module. type EVMKeeper interface { + // NewStateDB returns a fresh StateDB (empty TxConfig) to pass into CallEVM, + // which since cosmos/evm v0.6.0 requires a non-nil StateDB. + NewStateDB(ctx sdk.Context) *statedb.StateDB CallEVM( ctx sdk.Context, + stateDB *statedb.StateDB, abi abi.ABI, from, contract common.Address, - commit bool, + commit, callFromPrecompile bool, gasCap *big.Int, method string, args ...interface{}, From a5aead76225805bacebff31cb74cd8cee67a90ed Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Wed, 26 Aug 2026 22:27:36 +0530 Subject: [PATCH 49/60] fix: cap the gas a gasless tx may declare, as a governance parameter (F-2026-18144) (#350) Fee-paying txs are self-limiting: the ante handler requires ceil(minGasPrice * gasLimit), so an absurd gas limit costs absurd money. Gasless txs pay nothing, so nothing bounded the gas they declared, while that declared gas was still added to the fee market's cumulative gas wanted for the block. Under max_gas: -1 the per-tx block-limit check is inert, so two gasless txs each declaring MaxInt64 sum past what EndBlock can convert to int64 - an error that surfaces through FinalizeBlock after the block is decided. New uexecutor param max_gasless_tx_gas (default 100,000,000), enforced by GaslessGasLimitDecorator on the existing txpolicy.IsGaslessTx predicate, before NewGasWantedDecorator accumulates the declaration. A parameter and not a constant because "too low" stops the universal validators voting and must be fixable by proposal in minutes. Zero is rejected at genesis and on update, and an unreadable or unset parameter falls back to the default rather than to "no cap". universalClient/pushsigner declares 100,000,000 instead of 500,000,000. Live donut data: gas_wanted median/max 500,000,000 against a gas_used max of 5,011,877 - the largest real consumer used 1.0024% of what it declared. --- api/uexecutor/v1/types.pulsar.go | 682 ++++++++++-------- app/ante/ante_cosmos.go | 4 + app/ante/gasless_gas_limit.go | 79 ++ app/ante/gasless_gas_limit_test.go | 235 ++++++ app/ante/handler_options.go | 7 + app/app.go | 1 + proto/uexecutor/v1/types.proto | 7 + .../ante/gasless_gas_limit_test.go | 332 +++++++++ .../uexecutor/evm_hooks_and_outbound_test.go | 3 +- test/utils/contracts_setup.go | 2 +- universalClient/pushsigner/vote.go | 2 +- universalClient/pushsigner/vote_test.go | 6 +- x/uexecutor/keeper/msg_update_params.go | 9 + x/uexecutor/types/genesis_test.go | 7 +- x/uexecutor/types/params.go | 23 +- x/uexecutor/types/params_test.go | 42 ++ x/uexecutor/types/types.pb.go | 282 +++++--- 17 files changed, 1287 insertions(+), 436 deletions(-) create mode 100644 app/ante/gasless_gas_limit.go create mode 100644 app/ante/gasless_gas_limit_test.go create mode 100644 test/integration/ante/gasless_gas_limit_test.go create mode 100644 x/uexecutor/types/params_test.go diff --git a/api/uexecutor/v1/types.pulsar.go b/api/uexecutor/v1/types.pulsar.go index bb0aa1641..c615d1b46 100644 --- a/api/uexecutor/v1/types.pulsar.go +++ b/api/uexecutor/v1/types.pulsar.go @@ -15,14 +15,16 @@ import ( ) var ( - md_Params protoreflect.MessageDescriptor - fd_Params_some_value protoreflect.FieldDescriptor + md_Params protoreflect.MessageDescriptor + fd_Params_some_value protoreflect.FieldDescriptor + fd_Params_max_gasless_tx_gas protoreflect.FieldDescriptor ) func init() { file_uexecutor_v1_types_proto_init() md_Params = File_uexecutor_v1_types_proto.Messages().ByName("Params") fd_Params_some_value = md_Params.Fields().ByName("some_value") + fd_Params_max_gasless_tx_gas = md_Params.Fields().ByName("max_gasless_tx_gas") } var _ protoreflect.Message = (*fastReflection_Params)(nil) @@ -96,6 +98,12 @@ func (x *fastReflection_Params) Range(f func(protoreflect.FieldDescriptor, proto return } } + if x.MaxGaslessTxGas != uint64(0) { + value := protoreflect.ValueOfUint64(x.MaxGaslessTxGas) + if !f(fd_Params_max_gasless_tx_gas, value) { + return + } + } } // Has reports whether a field is populated. @@ -113,6 +121,8 @@ func (x *fastReflection_Params) Has(fd protoreflect.FieldDescriptor) bool { switch fd.FullName() { case "uexecutor.v1.Params.some_value": return x.SomeValue != false + case "uexecutor.v1.Params.max_gasless_tx_gas": + return x.MaxGaslessTxGas != uint64(0) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.Params")) @@ -131,6 +141,8 @@ func (x *fastReflection_Params) Clear(fd protoreflect.FieldDescriptor) { switch fd.FullName() { case "uexecutor.v1.Params.some_value": x.SomeValue = false + case "uexecutor.v1.Params.max_gasless_tx_gas": + x.MaxGaslessTxGas = uint64(0) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.Params")) @@ -150,6 +162,9 @@ func (x *fastReflection_Params) Get(descriptor protoreflect.FieldDescriptor) pro case "uexecutor.v1.Params.some_value": value := x.SomeValue return protoreflect.ValueOfBool(value) + case "uexecutor.v1.Params.max_gasless_tx_gas": + value := x.MaxGaslessTxGas + return protoreflect.ValueOfUint64(value) default: if descriptor.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.Params")) @@ -172,6 +187,8 @@ func (x *fastReflection_Params) Set(fd protoreflect.FieldDescriptor, value proto switch fd.FullName() { case "uexecutor.v1.Params.some_value": x.SomeValue = value.Bool() + case "uexecutor.v1.Params.max_gasless_tx_gas": + x.MaxGaslessTxGas = value.Uint() default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.Params")) @@ -194,6 +211,8 @@ func (x *fastReflection_Params) Mutable(fd protoreflect.FieldDescriptor) protore switch fd.FullName() { case "uexecutor.v1.Params.some_value": panic(fmt.Errorf("field some_value of message uexecutor.v1.Params is not mutable")) + case "uexecutor.v1.Params.max_gasless_tx_gas": + panic(fmt.Errorf("field max_gasless_tx_gas of message uexecutor.v1.Params is not mutable")) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.Params")) @@ -209,6 +228,8 @@ func (x *fastReflection_Params) NewField(fd protoreflect.FieldDescriptor) protor switch fd.FullName() { case "uexecutor.v1.Params.some_value": return protoreflect.ValueOfBool(false) + case "uexecutor.v1.Params.max_gasless_tx_gas": + return protoreflect.ValueOfUint64(uint64(0)) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.Params")) @@ -281,6 +302,9 @@ func (x *fastReflection_Params) ProtoMethods() *protoiface.Methods { if x.SomeValue { n += 2 } + if x.MaxGaslessTxGas != 0 { + n += 1 + runtime.Sov(uint64(x.MaxGaslessTxGas)) + } if x.unknownFields != nil { n += len(x.unknownFields) } @@ -310,6 +334,11 @@ func (x *fastReflection_Params) ProtoMethods() *protoiface.Methods { i -= len(x.unknownFields) copy(dAtA[i:], x.unknownFields) } + if x.MaxGaslessTxGas != 0 { + i = runtime.EncodeVarint(dAtA, i, uint64(x.MaxGaslessTxGas)) + i-- + dAtA[i] = 0x18 + } if x.SomeValue { i-- if x.SomeValue { @@ -389,6 +418,25 @@ func (x *fastReflection_Params) ProtoMethods() *protoiface.Methods { } } x.SomeValue = bool(v != 0) + case 3: + if wireType != 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field MaxGaslessTxGas", wireType) + } + x.MaxGaslessTxGas = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + x.MaxGaslessTxGas |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := runtime.Skip(dAtA[iNdEx:]) @@ -11167,6 +11215,12 @@ type Params struct { unknownFields protoimpl.UnknownFields SomeValue bool `protobuf:"varint,2,opt,name=some_value,json=someValue,proto3" json:"some_value,omitempty"` + // max_gasless_tx_gas is the maximum gas limit a fee-exempt (gasless) + // transaction is allowed to declare. Gasless transactions pay no fee, so + // their declared gas is not bounded by anything the sender has to spend; + // this cap is the only bound on how much a single gasless transaction can + // contribute to the block's cumulative gas wanted. + MaxGaslessTxGas uint64 `protobuf:"varint,3,opt,name=max_gasless_tx_gas,json=maxGaslessTxGas,proto3" json:"max_gasless_tx_gas,omitempty"` } func (x *Params) Reset() { @@ -11196,6 +11250,13 @@ func (x *Params) GetSomeValue() bool { return false } +func (x *Params) GetMaxGaslessTxGas() uint64 { + if x != nil { + return x.MaxGaslessTxGas + } + return 0 +} + // UniversalPayload mirrors the Solidity struct type UniversalPayload struct { state protoimpl.MessageState @@ -12254,315 +12315,318 @@ var file_uexecutor_v1_types_proto_rawDesc = []byte{ 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x1a, 0x14, 0x67, 0x6f, 0x67, 0x6f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x67, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x11, 0x61, 0x6d, 0x69, 0x6e, 0x6f, 0x2f, 0x61, 0x6d, 0x69, 0x6e, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x22, 0x46, 0x0a, 0x06, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, + 0x6f, 0x22, 0x73, 0x0a, 0x06, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x6d, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x09, 0x73, 0x6f, 0x6d, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x1d, 0x98, 0xa0, 0x1f, 0x00, - 0xe8, 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x10, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, - 0x6f, 0x72, 0x2f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x22, 0xdb, 0x02, 0x0a, 0x10, 0x55, 0x6e, - 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x0e, - 0x0a, 0x02, 0x74, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x74, 0x6f, 0x12, 0x14, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x61, 0x73, 0x5f, - 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x67, 0x61, 0x73, - 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x25, 0x0a, 0x0f, 0x6d, 0x61, 0x78, 0x5f, 0x66, 0x65, 0x65, - 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x67, 0x61, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, - 0x6d, 0x61, 0x78, 0x46, 0x65, 0x65, 0x50, 0x65, 0x72, 0x47, 0x61, 0x73, 0x12, 0x36, 0x0a, 0x18, - 0x6d, 0x61, 0x78, 0x5f, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x66, 0x65, 0x65, - 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x67, 0x61, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, - 0x6d, 0x61, 0x78, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x46, 0x65, 0x65, 0x50, 0x65, - 0x72, 0x47, 0x61, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, - 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, - 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x35, 0x0a, 0x06, 0x76, 0x5f, 0x74, 0x79, 0x70, 0x65, - 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, - 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x05, 0x76, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x28, 0x98, - 0xa0, 0x1f, 0x00, 0xe8, 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x1b, 0x75, 0x65, 0x78, 0x65, - 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x5f, - 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x8c, 0x01, 0x0a, 0x10, 0x4d, 0x69, 0x67, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x1c, 0x0a, 0x09, - 0x6d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x6d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, - 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, - 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x3a, 0x28, 0x98, 0xa0, - 0x1f, 0x00, 0xe8, 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x1b, 0x75, 0x65, 0x78, 0x65, 0x63, - 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x6d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, - 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x98, 0x01, 0x0a, 0x12, 0x55, 0x6e, 0x69, 0x76, 0x65, - 0x72, 0x73, 0x61, 0x6c, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x27, 0x0a, - 0x0f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x4e, 0x61, 0x6d, - 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, - 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, - 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x3a, 0x28, 0x98, 0xa0, 0x1f, 0x00, 0xe8, 0xa0, 0x1f, - 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x1b, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, - 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x22, 0x63, 0x0a, 0x12, 0x52, 0x65, 0x76, 0x65, 0x72, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x72, - 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x75, 0x6e, 0x64, 0x5f, - 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0d, 0x66, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x3a, 0x26, - 0xe8, 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x1d, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, - 0x6f, 0x72, 0x2f, 0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x72, 0x75, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x8c, 0x04, 0x0a, 0x07, 0x49, 0x6e, 0x62, 0x6f, 0x75, - 0x6e, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x68, 0x61, - 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x16, - 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, - 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, - 0x69, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, - 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x6c, - 0x6f, 0x67, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x6c, 0x6f, 0x67, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x2d, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x74, - 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x75, 0x65, 0x78, 0x65, - 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x78, 0x54, 0x79, 0x70, 0x65, 0x52, - 0x06, 0x74, 0x78, 0x54, 0x79, 0x70, 0x65, 0x12, 0x4b, 0x0a, 0x11, 0x75, 0x6e, 0x69, 0x76, 0x65, - 0x72, 0x73, 0x61, 0x6c, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x09, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x50, 0x61, 0x79, 0x6c, 0x6f, - 0x61, 0x64, 0x52, 0x10, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x50, 0x61, 0x79, - 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x2b, 0x0a, 0x11, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x10, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, - 0x61, 0x12, 0x51, 0x0a, 0x13, 0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x5f, 0x69, 0x6e, 0x73, 0x74, - 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, - 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, - 0x76, 0x65, 0x72, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x52, 0x12, 0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x73, 0x43, 0x45, 0x41, 0x18, 0x0c, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x05, 0x69, 0x73, 0x43, 0x45, 0x41, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x61, - 0x77, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0a, 0x72, 0x61, 0x77, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x3a, 0x08, 0x98, 0xa0, 0x1f, - 0x00, 0xe8, 0xa0, 0x1f, 0x01, 0x22, 0xc8, 0x01, 0x0a, 0x04, 0x50, 0x43, 0x54, 0x78, 0x12, 0x17, - 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, - 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, - 0x19, 0x0a, 0x08, 0x67, 0x61, 0x73, 0x5f, 0x75, 0x73, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x07, 0x67, 0x61, 0x73, 0x55, 0x73, 0x65, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, - 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x16, 0x0a, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, - 0x73, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, - 0x73, 0x67, 0x3a, 0x1c, 0x98, 0xa0, 0x1f, 0x00, 0xe8, 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, - 0x0f, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x70, 0x63, 0x5f, 0x74, 0x78, - 0x22, 0xd3, 0x01, 0x0a, 0x13, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x4f, 0x62, 0x73, - 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, - 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, - 0x73, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, - 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, - 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1b, - 0x0a, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x73, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x73, 0x67, 0x12, 0x20, 0x0a, 0x0c, 0x67, - 0x61, 0x73, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0a, 0x67, 0x61, 0x73, 0x46, 0x65, 0x65, 0x55, 0x73, 0x65, 0x64, 0x3a, 0x27, 0xe8, - 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x1e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, - 0x72, 0x2f, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x6f, 0x62, 0x73, 0x65, 0x72, - 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x6d, 0x0a, 0x0f, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, - 0x61, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x63, 0x54, 0x78, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x78, 0x5f, - 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, - 0x73, 0x68, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x6f, 0x67, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x3a, - 0x24, 0xe8, 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x1b, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, - 0x74, 0x6f, 0x72, 0x2f, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x5f, - 0x70, 0x63, 0x5f, 0x74, 0x78, 0x22, 0x95, 0x07, 0x0a, 0x0a, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, - 0x6e, 0x64, 0x54, 0x78, 0x12, 0x2b, 0x0a, 0x11, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x10, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x69, - 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x12, - 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x65, 0x78, 0x74, 0x65, 0x72, - 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x73, - 0x73, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x28, 0x0a, 0x10, 0x70, 0x72, 0x63, 0x32, 0x30, - 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0e, 0x70, 0x72, 0x63, 0x32, 0x30, 0x41, 0x73, 0x73, 0x65, 0x74, 0x41, 0x64, 0x64, - 0x72, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, - 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, - 0x6f, 0x61, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x61, 0x73, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x67, 0x61, 0x73, 0x4c, 0x69, 0x6d, 0x69, 0x74, - 0x12, 0x2d, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x14, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x54, 0x78, 0x54, 0x79, 0x70, 0x65, 0x52, 0x06, 0x74, 0x78, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x32, 0x0a, 0x05, 0x70, 0x63, 0x5f, 0x74, 0x78, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, - 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x72, - 0x69, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x63, 0x54, 0x78, 0x52, 0x04, 0x70, - 0x63, 0x54, 0x78, 0x12, 0x42, 0x0a, 0x0b, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x5f, - 0x74, 0x78, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, - 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, - 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x6f, 0x62, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x64, 0x54, 0x78, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x0c, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x3d, 0x0a, 0x0f, 0x6f, 0x75, 0x74, 0x62, 0x6f, - 0x75, 0x6e, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x14, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0e, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x51, 0x0a, 0x13, 0x72, 0x65, 0x76, 0x65, 0x72, 0x74, - 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0e, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x52, 0x65, 0x76, 0x65, 0x72, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x12, 0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x49, 0x6e, 0x73, - 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x42, 0x0a, 0x13, 0x70, 0x63, 0x5f, - 0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x5f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, - 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x43, 0x54, 0x78, 0x52, 0x11, 0x70, 0x63, 0x52, 0x65, - 0x76, 0x65, 0x72, 0x74, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, - 0x09, 0x67, 0x61, 0x73, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x67, 0x61, 0x73, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x61, - 0x73, 0x5f, 0x66, 0x65, 0x65, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x61, 0x73, - 0x46, 0x65, 0x65, 0x12, 0x42, 0x0a, 0x13, 0x70, 0x63, 0x5f, 0x72, 0x65, 0x66, 0x75, 0x6e, 0x64, - 0x5f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x12, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x50, 0x43, 0x54, 0x78, 0x52, 0x11, 0x70, 0x63, 0x52, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x45, 0x78, - 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x11, 0x72, 0x65, 0x66, 0x75, 0x6e, - 0x64, 0x5f, 0x73, 0x77, 0x61, 0x70, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x13, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0f, 0x72, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x53, 0x77, 0x61, 0x70, 0x45, 0x72, - 0x72, 0x6f, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x61, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, - 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x67, 0x61, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, - 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x62, 0x6f, 0x72, 0x74, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, - 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x62, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x61, - 0x73, 0x6f, 0x6e, 0x3a, 0x08, 0x98, 0xa0, 0x1f, 0x00, 0xe8, 0xa0, 0x1f, 0x01, 0x22, 0xff, 0x01, - 0x0a, 0x0b, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x54, 0x78, 0x12, 0x0e, 0x0a, - 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x34, 0x0a, - 0x0a, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x15, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x09, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, - 0x64, 0x54, 0x78, 0x12, 0x27, 0x0a, 0x05, 0x70, 0x63, 0x5f, 0x74, 0x78, 0x18, 0x03, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x50, 0x43, 0x54, 0x78, 0x52, 0x04, 0x70, 0x63, 0x54, 0x78, 0x12, 0x39, 0x0a, 0x0b, - 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x78, 0x18, 0x04, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x18, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x52, 0x0a, 0x6f, 0x75, 0x74, - 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x76, 0x65, 0x72, - 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, - 0x65, 0x76, 0x65, 0x72, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x3a, 0x23, 0x98, 0xa0, 0x1f, 0x00, - 0xe8, 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x16, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, - 0x6f, 0x72, 0x2f, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x5f, 0x74, 0x78, 0x22, - 0xab, 0x03, 0x0a, 0x0d, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x4c, 0x65, 0x67, 0x61, 0x63, - 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x69, - 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, - 0x68, 0x61, 0x69, 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, - 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, - 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, - 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, - 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, + 0x09, 0x73, 0x6f, 0x6d, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2b, 0x0a, 0x12, 0x6d, 0x61, + 0x78, 0x5f, 0x67, 0x61, 0x73, 0x6c, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x78, 0x5f, 0x67, 0x61, 0x73, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6d, 0x61, 0x78, 0x47, 0x61, 0x73, 0x6c, 0x65, + 0x73, 0x73, 0x54, 0x78, 0x47, 0x61, 0x73, 0x3a, 0x1d, 0x98, 0xa0, 0x1f, 0x00, 0xe8, 0xa0, 0x1f, + 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x10, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, + 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x22, 0xdb, 0x02, 0x0a, 0x10, 0x55, 0x6e, 0x69, 0x76, 0x65, + 0x72, 0x73, 0x61, 0x6c, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x74, + 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x74, 0x6f, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x61, 0x73, 0x5f, 0x6c, 0x69, 0x6d, + 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x67, 0x61, 0x73, 0x4c, 0x69, 0x6d, + 0x69, 0x74, 0x12, 0x25, 0x0a, 0x0f, 0x6d, 0x61, 0x78, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x70, 0x65, + 0x72, 0x5f, 0x67, 0x61, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x61, 0x78, + 0x46, 0x65, 0x65, 0x50, 0x65, 0x72, 0x47, 0x61, 0x73, 0x12, 0x36, 0x0a, 0x18, 0x6d, 0x61, 0x78, + 0x5f, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x70, 0x65, + 0x72, 0x5f, 0x67, 0x61, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x6d, 0x61, 0x78, + 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x46, 0x65, 0x65, 0x50, 0x65, 0x72, 0x47, 0x61, + 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, + 0x69, 0x6e, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, + 0x69, 0x6e, 0x65, 0x12, 0x35, 0x0a, 0x06, 0x76, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, + 0x79, 0x70, 0x65, 0x52, 0x05, 0x76, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x28, 0x98, 0xa0, 0x1f, 0x00, + 0xe8, 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x1b, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, + 0x6f, 0x72, 0x2f, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x5f, 0x70, 0x61, 0x79, + 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x8c, 0x01, 0x0a, 0x10, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x6d, 0x69, 0x67, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x69, + 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x1a, 0x0a, + 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x3a, 0x28, 0x98, 0xa0, 0x1f, 0x00, 0xe8, + 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x1b, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, + 0x72, 0x2f, 0x6d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x61, 0x79, 0x6c, + 0x6f, 0x61, 0x64, 0x22, 0x98, 0x01, 0x0a, 0x12, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, + 0x6c, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x68, + 0x61, 0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x14, + 0x0a, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, + 0x77, 0x6e, 0x65, 0x72, 0x3a, 0x28, 0x98, 0xa0, 0x1f, 0x00, 0xe8, 0xa0, 0x1f, 0x01, 0x8a, 0xe7, + 0xb0, 0x2a, 0x1b, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x75, 0x6e, 0x69, + 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x63, + 0x0a, 0x12, 0x52, 0x65, 0x76, 0x65, 0x72, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x75, 0x6e, 0x64, 0x5f, 0x72, 0x65, 0x63, + 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x66, 0x75, + 0x6e, 0x64, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x3a, 0x26, 0xe8, 0xa0, 0x1f, + 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x1d, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, + 0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x22, 0x8c, 0x04, 0x0a, 0x07, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, + 0x21, 0x0a, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x68, 0x61, + 0x69, 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x73, + 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x6e, + 0x64, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, + 0x74, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x73, 0x73, + 0x65, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, + 0x73, 0x73, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x6f, 0x67, 0x5f, + 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x6f, 0x67, + 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x2d, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, + 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x78, 0x54, 0x79, 0x70, 0x65, 0x52, 0x06, 0x74, 0x78, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x4b, 0x0a, 0x11, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, + 0x6c, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1e, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, + 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x52, + 0x10, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, + 0x64, 0x12, 0x2b, 0x0a, 0x11, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x76, 0x65, + 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, 0x51, + 0x0a, 0x13, 0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x75, 0x65, + 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x76, 0x65, 0x72, + 0x74, 0x49, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x12, 0x72, + 0x65, 0x76, 0x65, 0x72, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x73, 0x43, 0x45, 0x41, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x05, 0x69, 0x73, 0x43, 0x45, 0x41, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x61, 0x77, 0x5f, 0x70, + 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x61, + 0x77, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x3a, 0x08, 0x98, 0xa0, 0x1f, 0x00, 0xe8, 0xa0, + 0x1f, 0x01, 0x22, 0xc8, 0x01, 0x0a, 0x04, 0x50, 0x43, 0x54, 0x78, 0x12, 0x17, 0x0a, 0x07, 0x74, + 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x78, + 0x48, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x08, + 0x67, 0x61, 0x73, 0x5f, 0x75, 0x73, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, + 0x67, 0x61, 0x73, 0x55, 0x73, 0x65, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, + 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x62, + 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x73, 0x67, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x73, 0x67, 0x3a, + 0x1c, 0x98, 0xa0, 0x1f, 0x00, 0xe8, 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x0f, 0x75, 0x65, + 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x70, 0x63, 0x5f, 0x74, 0x78, 0x22, 0xd3, 0x01, + 0x0a, 0x13, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, + 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, + 0x68, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1b, 0x0a, 0x09, 0x65, + 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x73, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x73, 0x67, 0x12, 0x20, 0x0a, 0x0c, 0x67, 0x61, 0x73, 0x5f, + 0x66, 0x65, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x67, 0x61, 0x73, 0x46, 0x65, 0x65, 0x55, 0x73, 0x65, 0x64, 0x3a, 0x27, 0xe8, 0xa0, 0x1f, 0x01, + 0x8a, 0xe7, 0xb0, 0x2a, 0x1e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x6f, + 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x22, 0x6d, 0x0a, 0x0f, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, + 0x6e, 0x67, 0x50, 0x63, 0x54, 0x78, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, + 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, + 0x1b, 0x0a, 0x09, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x6c, 0x6f, 0x67, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x3a, 0x24, 0xe8, 0xa0, + 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x1b, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, + 0x2f, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x63, 0x5f, + 0x74, 0x78, 0x22, 0x95, 0x07, 0x0a, 0x0a, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, + 0x78, 0x12, 0x2b, 0x0a, 0x11, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x64, 0x65, + 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x1c, + 0x0a, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, + 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x11, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x73, 0x73, 0x65, 0x74, + 0x41, 0x64, 0x64, 0x72, 0x12, 0x28, 0x0a, 0x10, 0x70, 0x72, 0x63, 0x32, 0x30, 0x5f, 0x61, 0x73, + 0x73, 0x65, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, + 0x70, 0x72, 0x63, 0x32, 0x30, 0x41, 0x73, 0x73, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x16, + 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, + 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, + 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x61, 0x73, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x67, 0x61, 0x73, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x2d, 0x0a, + 0x07, 0x74, 0x78, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, + 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x78, + 0x54, 0x79, 0x70, 0x65, 0x52, 0x06, 0x74, 0x78, 0x54, 0x79, 0x70, 0x65, 0x12, 0x32, 0x0a, 0x05, + 0x70, 0x63, 0x5f, 0x74, 0x78, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x75, 0x65, + 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x72, 0x69, 0x67, 0x69, + 0x6e, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x63, 0x54, 0x78, 0x52, 0x04, 0x70, 0x63, 0x54, 0x78, + 0x12, 0x42, 0x0a, 0x0b, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x5f, 0x74, 0x78, 0x18, + 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x4f, 0x62, 0x73, + 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x64, 0x54, 0x78, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x02, 0x69, 0x64, 0x12, 0x3d, 0x0a, 0x0f, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, + 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, + 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x52, 0x0e, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x51, 0x0a, 0x13, 0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x5f, 0x69, 0x6e, + 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x20, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x52, 0x65, 0x76, 0x65, 0x72, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x52, 0x12, 0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x72, 0x75, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x42, 0x0a, 0x13, 0x70, 0x63, 0x5f, 0x72, 0x65, 0x76, + 0x65, 0x72, 0x74, 0x5f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0f, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x50, 0x43, 0x54, 0x78, 0x52, 0x11, 0x70, 0x63, 0x52, 0x65, 0x76, 0x65, 0x72, + 0x74, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x61, + 0x73, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x67, + 0x61, 0x73, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x61, 0x73, 0x5f, 0x66, + 0x65, 0x65, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x61, 0x73, 0x46, 0x65, 0x65, + 0x12, 0x42, 0x0a, 0x13, 0x70, 0x63, 0x5f, 0x72, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x5f, 0x65, 0x78, + 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, + 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x43, 0x54, + 0x78, 0x52, 0x11, 0x70, 0x63, 0x52, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x45, 0x78, 0x65, 0x63, 0x75, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x11, 0x72, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x5f, 0x73, + 0x77, 0x61, 0x70, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0f, 0x72, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x53, 0x77, 0x61, 0x70, 0x45, 0x72, 0x72, 0x6f, 0x72, + 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x61, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x14, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x67, 0x61, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x21, 0x0a, + 0x0c, 0x61, 0x62, 0x6f, 0x72, 0x74, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x15, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x62, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, + 0x3a, 0x08, 0x98, 0xa0, 0x1f, 0x00, 0xe8, 0xa0, 0x1f, 0x01, 0x22, 0xff, 0x01, 0x0a, 0x0b, 0x55, + 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x54, 0x78, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x34, 0x0a, 0x0a, 0x69, 0x6e, + 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, + 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, + 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x09, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, + 0x12, 0x27, 0x0a, 0x05, 0x70, 0x63, 0x5f, 0x74, 0x78, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x12, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, + 0x43, 0x54, 0x78, 0x52, 0x04, 0x70, 0x63, 0x54, 0x78, 0x12, 0x39, 0x0a, 0x0b, 0x6f, 0x75, 0x74, + 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x78, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, + 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x75, + 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x52, 0x0a, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, + 0x6e, 0x64, 0x54, 0x78, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x5f, 0x65, + 0x72, 0x72, 0x6f, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x76, 0x65, + 0x72, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x3a, 0x23, 0x98, 0xa0, 0x1f, 0x00, 0xe8, 0xa0, 0x1f, + 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x16, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, + 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x5f, 0x74, 0x78, 0x22, 0xab, 0x03, 0x0a, + 0x0d, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x12, 0x21, + 0x0a, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x68, 0x61, 0x69, + 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, + 0x6e, 0x64, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, + 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, + 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x73, 0x73, 0x65, + 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x73, + 0x73, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x6f, 0x67, 0x5f, 0x69, + 0x6e, 0x64, 0x65, 0x78, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x6f, 0x67, 0x49, + 0x6e, 0x64, 0x65, 0x78, 0x12, 0x3a, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x54, 0x79, + 0x70, 0x65, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x52, 0x06, 0x74, 0x78, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x4b, 0x0a, 0x11, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x5f, 0x70, 0x61, + 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x75, 0x65, + 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x69, 0x76, 0x65, + 0x72, 0x73, 0x61, 0x6c, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x10, 0x75, 0x6e, 0x69, + 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x2b, 0x0a, + 0x11, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x61, + 0x74, 0x61, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x3a, 0x1e, 0x98, 0xa0, 0x1f, 0x01, + 0xe8, 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x11, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, + 0x6f, 0x72, 0x2f, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0xd1, 0x01, 0x0a, 0x10, 0x4f, + 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x12, + 0x2b, 0x0a, 0x11, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, + 0x68, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x64, 0x65, 0x73, 0x74, + 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x17, 0x0a, 0x07, + 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, + 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, + 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, + 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x61, - 0x73, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x6f, - 0x67, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, - 0x6f, 0x67, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x3a, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x74, 0x79, - 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, - 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, - 0x78, 0x54, 0x79, 0x70, 0x65, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x52, 0x06, 0x74, 0x78, 0x54, - 0x79, 0x70, 0x65, 0x12, 0x4b, 0x0a, 0x11, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, - 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, - 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, - 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x10, - 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, - 0x12, 0x2b, 0x0a, 0x11, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x76, 0x65, 0x72, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x3a, 0x1e, 0x98, - 0xa0, 0x1f, 0x01, 0xe8, 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x11, 0x75, 0x65, 0x78, 0x65, - 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0xd1, 0x01, - 0x0a, 0x10, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x4c, 0x65, 0x67, 0x61, - 0x63, 0x79, 0x12, 0x2b, 0x0a, 0x11, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x64, - 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, - 0x17, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x63, 0x69, - 0x70, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x63, - 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, - 0x0a, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x3a, 0x22, 0x98, - 0xa0, 0x1f, 0x01, 0xe8, 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x15, 0x75, 0x65, 0x78, 0x65, - 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, - 0x78, 0x22, 0xaa, 0x02, 0x0a, 0x11, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x54, - 0x78, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x12, 0x3a, 0x0a, 0x0a, 0x69, 0x6e, 0x62, 0x6f, 0x75, - 0x6e, 0x64, 0x5f, 0x74, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x75, 0x65, - 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, - 0x6e, 0x64, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x52, 0x09, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, - 0x64, 0x54, 0x78, 0x12, 0x27, 0x0a, 0x05, 0x70, 0x63, 0x5f, 0x74, 0x78, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x50, 0x43, 0x54, 0x78, 0x52, 0x04, 0x70, 0x63, 0x54, 0x78, 0x12, 0x3f, 0x0a, 0x0b, - 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1e, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x4c, 0x65, 0x67, 0x61, 0x63, - 0x79, 0x52, 0x0a, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x12, 0x4a, 0x0a, - 0x10, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, - 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, - 0x54, 0x78, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0f, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, - 0x73, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x3a, 0x23, 0x98, 0xa0, 0x1f, 0x01, 0xe8, - 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x16, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, - 0x72, 0x2f, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x5f, 0x74, 0x78, 0x2a, 0x47, - 0x0a, 0x10, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x16, 0x0a, 0x12, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x56, 0x65, 0x72, 0x69, - 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x75, 0x6e, - 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x54, 0x78, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0x01, 0x2a, 0x83, 0x02, 0x0a, 0x11, 0x55, 0x6e, 0x69, 0x76, - 0x65, 0x72, 0x73, 0x61, 0x6c, 0x54, 0x78, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x23, 0x0a, - 0x1f, 0x55, 0x4e, 0x49, 0x56, 0x45, 0x52, 0x53, 0x41, 0x4c, 0x5f, 0x54, 0x58, 0x5f, 0x53, 0x54, - 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, - 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x49, 0x4e, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x53, 0x55, - 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x01, 0x12, 0x1d, 0x0a, 0x19, 0x50, 0x45, 0x4e, 0x44, 0x49, - 0x4e, 0x47, 0x5f, 0x49, 0x4e, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, - 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x02, 0x12, 0x17, 0x0a, 0x13, 0x50, 0x43, 0x5f, 0x45, 0x58, 0x45, - 0x43, 0x55, 0x54, 0x45, 0x44, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x03, 0x12, - 0x16, 0x0a, 0x12, 0x50, 0x43, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x45, 0x44, 0x5f, 0x46, - 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x04, 0x12, 0x15, 0x0a, 0x11, 0x50, 0x43, 0x5f, 0x50, 0x45, - 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x52, 0x45, 0x56, 0x45, 0x52, 0x54, 0x10, 0x05, 0x12, 0x14, - 0x0a, 0x10, 0x4f, 0x55, 0x54, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, - 0x4e, 0x47, 0x10, 0x06, 0x12, 0x14, 0x0a, 0x10, 0x4f, 0x55, 0x54, 0x42, 0x4f, 0x55, 0x4e, 0x44, - 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x07, 0x12, 0x13, 0x0a, 0x0f, 0x4f, 0x55, - 0x54, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x08, 0x12, - 0x0c, 0x0a, 0x08, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x45, 0x44, 0x10, 0x09, 0x2a, 0x4f, 0x0a, - 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x50, 0x45, - 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x45, 0x4e, 0x44, - 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x4f, 0x42, 0x53, 0x45, 0x52, 0x56, 0x45, - 0x44, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x56, 0x45, 0x52, 0x54, 0x45, 0x44, 0x10, - 0x03, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x10, 0x04, 0x2a, 0x8f, - 0x01, 0x0a, 0x06, 0x54, 0x78, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x0e, 0x55, 0x4e, 0x53, - 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x5f, 0x54, 0x58, 0x10, 0x00, 0x12, 0x07, 0x0a, - 0x03, 0x47, 0x41, 0x53, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x47, 0x41, 0x53, 0x5f, 0x41, 0x4e, - 0x44, 0x5f, 0x50, 0x41, 0x59, 0x4c, 0x4f, 0x41, 0x44, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x46, - 0x55, 0x4e, 0x44, 0x53, 0x10, 0x03, 0x12, 0x15, 0x0a, 0x11, 0x46, 0x55, 0x4e, 0x44, 0x53, 0x5f, - 0x41, 0x4e, 0x44, 0x5f, 0x50, 0x41, 0x59, 0x4c, 0x4f, 0x41, 0x44, 0x10, 0x04, 0x12, 0x0b, 0x0a, - 0x07, 0x50, 0x41, 0x59, 0x4c, 0x4f, 0x41, 0x44, 0x10, 0x05, 0x12, 0x12, 0x0a, 0x0e, 0x49, 0x4e, - 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x52, 0x45, 0x56, 0x45, 0x52, 0x54, 0x10, 0x06, 0x12, 0x10, - 0x0a, 0x0c, 0x52, 0x45, 0x53, 0x43, 0x55, 0x45, 0x5f, 0x46, 0x55, 0x4e, 0x44, 0x53, 0x10, 0x07, - 0x2a, 0xb4, 0x01, 0x0a, 0x13, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x54, 0x79, - 0x70, 0x65, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x12, 0x21, 0x0a, 0x1d, 0x49, 0x4e, 0x42, 0x4f, - 0x55, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, - 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x5f, 0x54, 0x58, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x49, - 0x4e, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x5f, 0x47, 0x41, - 0x53, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x49, 0x4e, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x4c, - 0x45, 0x47, 0x41, 0x43, 0x59, 0x5f, 0x46, 0x55, 0x4e, 0x44, 0x53, 0x10, 0x02, 0x12, 0x24, 0x0a, - 0x20, 0x49, 0x4e, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x5f, - 0x46, 0x55, 0x4e, 0x44, 0x53, 0x5f, 0x41, 0x4e, 0x44, 0x5f, 0x50, 0x41, 0x59, 0x4c, 0x4f, 0x41, - 0x44, 0x10, 0x03, 0x12, 0x22, 0x0a, 0x1e, 0x49, 0x4e, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x4c, - 0x45, 0x47, 0x41, 0x43, 0x59, 0x5f, 0x47, 0x41, 0x53, 0x5f, 0x41, 0x4e, 0x44, 0x5f, 0x50, 0x41, - 0x59, 0x4c, 0x4f, 0x41, 0x44, 0x10, 0x04, 0x42, 0xb2, 0x01, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, - 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x42, 0x0a, 0x54, 0x79, - 0x70, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x75, 0x73, 0x68, 0x63, 0x68, 0x61, 0x69, 0x6e, - 0x2f, 0x70, 0x75, 0x73, 0x68, 0x2d, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2d, 0x6e, 0x6f, 0x64, 0x65, - 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x76, - 0x31, 0x3b, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x76, 0x31, 0xa2, 0x02, 0x03, - 0x55, 0x58, 0x58, 0xaa, 0x02, 0x0c, 0x55, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, - 0x56, 0x31, 0xca, 0x02, 0x0c, 0x55, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x5c, 0x56, - 0x31, 0xe2, 0x02, 0x18, 0x55, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x5c, 0x56, 0x31, - 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0d, 0x55, - 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x3a, 0x22, 0x98, 0xa0, 0x1f, 0x01, + 0xe8, 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x15, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, + 0x6f, 0x72, 0x2f, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x78, 0x22, 0xaa, + 0x02, 0x0a, 0x11, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x54, 0x78, 0x4c, 0x65, + 0x67, 0x61, 0x63, 0x79, 0x12, 0x3a, 0x0a, 0x0a, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, + 0x74, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, + 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x4c, + 0x65, 0x67, 0x61, 0x63, 0x79, 0x52, 0x09, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, + 0x12, 0x27, 0x0a, 0x05, 0x70, 0x63, 0x5f, 0x74, 0x78, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x12, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, + 0x43, 0x54, 0x78, 0x52, 0x04, 0x70, 0x63, 0x54, 0x78, 0x12, 0x3f, 0x0a, 0x0b, 0x6f, 0x75, 0x74, + 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, + 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x75, + 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x52, 0x0a, + 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x12, 0x4a, 0x0a, 0x10, 0x75, 0x6e, + 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x54, 0x78, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0f, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x3a, 0x23, 0x98, 0xa0, 0x1f, 0x01, 0xe8, 0xa0, 0x1f, 0x01, + 0x8a, 0xe7, 0xb0, 0x2a, 0x16, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x75, + 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x5f, 0x74, 0x78, 0x2a, 0x47, 0x0a, 0x10, 0x56, + 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x16, 0x0a, 0x12, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x75, 0x6e, 0x69, 0x76, 0x65, + 0x72, 0x73, 0x61, 0x6c, 0x54, 0x78, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x10, 0x01, 0x2a, 0x83, 0x02, 0x0a, 0x11, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, + 0x61, 0x6c, 0x54, 0x78, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x23, 0x0a, 0x1f, 0x55, 0x4e, + 0x49, 0x56, 0x45, 0x52, 0x53, 0x41, 0x4c, 0x5f, 0x54, 0x58, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, + 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, + 0x13, 0x0a, 0x0f, 0x49, 0x4e, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, + 0x53, 0x53, 0x10, 0x01, 0x12, 0x1d, 0x0a, 0x19, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x5f, + 0x49, 0x4e, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x49, 0x4f, + 0x4e, 0x10, 0x02, 0x12, 0x17, 0x0a, 0x13, 0x50, 0x43, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, + 0x45, 0x44, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x03, 0x12, 0x16, 0x0a, 0x12, + 0x50, 0x43, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x45, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, + 0x45, 0x44, 0x10, 0x04, 0x12, 0x15, 0x0a, 0x11, 0x50, 0x43, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, + 0x4e, 0x47, 0x5f, 0x52, 0x45, 0x56, 0x45, 0x52, 0x54, 0x10, 0x05, 0x12, 0x14, 0x0a, 0x10, 0x4f, + 0x55, 0x54, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, + 0x06, 0x12, 0x14, 0x0a, 0x10, 0x4f, 0x55, 0x54, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x53, 0x55, + 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x07, 0x12, 0x13, 0x0a, 0x0f, 0x4f, 0x55, 0x54, 0x42, 0x4f, + 0x55, 0x4e, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x08, 0x12, 0x0c, 0x0a, 0x08, + 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x45, 0x44, 0x10, 0x09, 0x2a, 0x4f, 0x0a, 0x06, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, + 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, + 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x4f, 0x42, 0x53, 0x45, 0x52, 0x56, 0x45, 0x44, 0x10, 0x02, + 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x56, 0x45, 0x52, 0x54, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0b, + 0x0a, 0x07, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x10, 0x04, 0x2a, 0x8f, 0x01, 0x0a, 0x06, + 0x54, 0x78, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x0e, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, + 0x49, 0x46, 0x49, 0x45, 0x44, 0x5f, 0x54, 0x58, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x47, 0x41, + 0x53, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x47, 0x41, 0x53, 0x5f, 0x41, 0x4e, 0x44, 0x5f, 0x50, + 0x41, 0x59, 0x4c, 0x4f, 0x41, 0x44, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x46, 0x55, 0x4e, 0x44, + 0x53, 0x10, 0x03, 0x12, 0x15, 0x0a, 0x11, 0x46, 0x55, 0x4e, 0x44, 0x53, 0x5f, 0x41, 0x4e, 0x44, + 0x5f, 0x50, 0x41, 0x59, 0x4c, 0x4f, 0x41, 0x44, 0x10, 0x04, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x41, + 0x59, 0x4c, 0x4f, 0x41, 0x44, 0x10, 0x05, 0x12, 0x12, 0x0a, 0x0e, 0x49, 0x4e, 0x42, 0x4f, 0x55, + 0x4e, 0x44, 0x5f, 0x52, 0x45, 0x56, 0x45, 0x52, 0x54, 0x10, 0x06, 0x12, 0x10, 0x0a, 0x0c, 0x52, + 0x45, 0x53, 0x43, 0x55, 0x45, 0x5f, 0x46, 0x55, 0x4e, 0x44, 0x53, 0x10, 0x07, 0x2a, 0xb4, 0x01, + 0x0a, 0x13, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x54, 0x79, 0x70, 0x65, 0x4c, + 0x65, 0x67, 0x61, 0x63, 0x79, 0x12, 0x21, 0x0a, 0x1d, 0x49, 0x4e, 0x42, 0x4f, 0x55, 0x4e, 0x44, + 0x5f, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, + 0x49, 0x45, 0x44, 0x5f, 0x54, 0x58, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x49, 0x4e, 0x42, 0x4f, + 0x55, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x5f, 0x47, 0x41, 0x53, 0x10, 0x01, + 0x12, 0x18, 0x0a, 0x14, 0x49, 0x4e, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x47, 0x41, + 0x43, 0x59, 0x5f, 0x46, 0x55, 0x4e, 0x44, 0x53, 0x10, 0x02, 0x12, 0x24, 0x0a, 0x20, 0x49, 0x4e, + 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x5f, 0x46, 0x55, 0x4e, + 0x44, 0x53, 0x5f, 0x41, 0x4e, 0x44, 0x5f, 0x50, 0x41, 0x59, 0x4c, 0x4f, 0x41, 0x44, 0x10, 0x03, + 0x12, 0x22, 0x0a, 0x1e, 0x49, 0x4e, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x47, 0x41, + 0x43, 0x59, 0x5f, 0x47, 0x41, 0x53, 0x5f, 0x41, 0x4e, 0x44, 0x5f, 0x50, 0x41, 0x59, 0x4c, 0x4f, + 0x41, 0x44, 0x10, 0x04, 0x42, 0xb2, 0x01, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x75, 0x65, 0x78, + 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x42, 0x0a, 0x54, 0x79, 0x70, 0x65, 0x73, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x75, 0x73, 0x68, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2f, 0x70, 0x75, + 0x73, 0x68, 0x2d, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2d, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x61, 0x70, + 0x69, 0x2f, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x76, 0x31, 0x3b, 0x75, + 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x55, 0x58, 0x58, + 0xaa, 0x02, 0x0c, 0x55, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x56, 0x31, 0xca, + 0x02, 0x0c, 0x55, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x5c, 0x56, 0x31, 0xe2, 0x02, + 0x18, 0x55, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, + 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0d, 0x55, 0x65, 0x78, 0x65, + 0x63, 0x75, 0x74, 0x6f, 0x72, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( diff --git a/app/ante/ante_cosmos.go b/app/ante/ante_cosmos.go index 509d6e9ca..9e48da1a1 100755 --- a/app/ante/ante_cosmos.go +++ b/app/ante/ante_cosmos.go @@ -37,6 +37,10 @@ func NewCosmosAnteHandler(ctx sdk.Context, options HandlerOptions) sdk.AnteHandl ), ante.NewSetUpContextDecorator(), + // Gasless txs pay no fee, so the fee is not a bound on the gas they + // declare. Cap it explicitly, before NewGasWantedDecorator adds the + // declared gas to the block's cumulative gas wanted. + NewGaslessGasLimitDecorator(options.UexecutorKeeper), wasmkeeper.NewLimitSimulationGasDecorator(options.WasmConfig.SimulationGasLimit), // after setup context to enforce limits early wasmkeeper.NewCountTXDecorator(options.TXCounterStoreService), wasmkeeper.NewGasRegisterDecorator(options.WasmKeeper.GetGasRegister()), diff --git a/app/ante/gasless_gas_limit.go b/app/ante/gasless_gas_limit.go new file mode 100644 index 000000000..a750b4b47 --- /dev/null +++ b/app/ante/gasless_gas_limit.go @@ -0,0 +1,79 @@ +package ante + +import ( + "context" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + txpolicy "github.com/pushchain/push-chain-node/app/txpolicy" + uexecutortypes "github.com/pushchain/push-chain-node/x/uexecutor/types" +) + +// GaslessParamsKeeper reads the module parameters that bound fee-exempt txs. +type GaslessParamsKeeper interface { + GetParams(ctx context.Context) (uexecutortypes.Params, error) +} + +// GaslessGasLimitDecorator caps the gas limit a fee-exempt (gasless) tx may +// declare. +// +// A fee-paying tx is bounded by its own fee: the ante handler requires +// ceil(minGasPrice * gasLimit), so an absurd gas limit costs an absurd amount +// of tokens. A gasless tx pays nothing, so nothing bounds the gas it declares +// while that declared gas is still added to the block's cumulative gas wanted. +// Enough of them, or few enough with a large enough declaration, push the +// cumulative total past what the fee market can represent. +// +// CONTRACT: must run before the EVM GasWantedDecorator, which is what +// accumulates the declared gas into the fee market transient store. +type GaslessGasLimitDecorator struct { + paramsKeeper GaslessParamsKeeper +} + +func NewGaslessGasLimitDecorator(pk GaslessParamsKeeper) GaslessGasLimitDecorator { + return GaslessGasLimitDecorator{paramsKeeper: pk} +} + +func (ggd GaslessGasLimitDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { + if !txpolicy.IsGaslessTx(tx) { + return next(ctx, tx, simulate) + } + + feeTx, ok := tx.(sdk.FeeTx) + if !ok { + return ctx, errorsmod.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx") + } + + maxGas := ggd.maxGaslessTxGas(ctx) + if gas := feeTx.GetGas(); gas > maxGas { + ctx.Logger().Debug("gasless gas limit decorator: declared gas over cap", + "gas", gas, + "max_gas", maxGas, + ) + return ctx, errorsmod.Wrapf(sdkerrors.ErrInvalidGasLimit, + "gasless tx gas limit %d exceeds the maximum allowed %d", gas, maxGas) + } + + return next(ctx, tx, simulate) +} + +// maxGaslessTxGas resolves the governance-controlled cap, falling back to the +// default when it cannot be read or was never set. The fallback is deliberate: +// a missing parameter must not mean "no cap". +func (ggd GaslessGasLimitDecorator) maxGaslessTxGas(ctx sdk.Context) uint64 { + params, err := ggd.paramsKeeper.GetParams(ctx) + if err != nil { + ctx.Logger().Error("gasless gas limit decorator: failed to read uexecutor params, using default cap", + "error", err, + ) + return uexecutortypes.DefaultMaxGaslessTxGas + } + + if params.MaxGaslessTxGas == 0 { + return uexecutortypes.DefaultMaxGaslessTxGas + } + + return params.MaxGaslessTxGas +} diff --git a/app/ante/gasless_gas_limit_test.go b/app/ante/gasless_gas_limit_test.go new file mode 100644 index 000000000..a2f917a84 --- /dev/null +++ b/app/ante/gasless_gas_limit_test.go @@ -0,0 +1,235 @@ +package ante_test + +import ( + "context" + "errors" + "math" + "testing" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/x/authz" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/stretchr/testify/require" + + "github.com/pushchain/push-chain-node/app/ante" + uexecutortypes "github.com/pushchain/push-chain-node/x/uexecutor/types" +) + +// --------------------------------------------------------------------------- +// GaslessGasLimitDecorator — F-2026-18144 +// +// Gasless txs pay no fee, so the fee is not a bound on the gas they declare, +// and the declared gas is what accumulates into the block's cumulative gas +// wanted. These tests pin the cap that replaces the missing economic bound. +// --------------------------------------------------------------------------- + +// mockGaslessParamsKeeper satisfies ante.GaslessParamsKeeper. +type mockGaslessParamsKeeper struct { + params uexecutortypes.Params + err error + calls int +} + +func (m *mockGaslessParamsKeeper) GetParams(_ context.Context) (uexecutortypes.Params, error) { + m.calls++ + if m.err != nil { + return uexecutortypes.Params{}, m.err + } + return m.params, nil +} + +func paramsWithCap(cap uint64) *mockGaslessParamsKeeper { + return &mockGaslessParamsKeeper{params: uexecutortypes.Params{SomeValue: true, MaxGaslessTxGas: cap}} +} + +// gaslessTx returns a tx whose only msg is on the IsGaslessTx allowlist. +func gaslessTx(gas uint64) mockFeeTx { + return mockFeeTx{ + msgs: []sdk.Msg{&uexecutortypes.MsgVoteInbound{}}, + gas: gas, + fee: sdk.NewCoins(), + feePayer: sdk.AccAddress([]byte("payer")), + } +} + +// runDecorator returns (nextCalled, err). +func runDecorator(t *testing.T, pk ante.GaslessParamsKeeper, tx sdk.Tx, simulate bool) (bool, error) { + t.Helper() + ggd := ante.NewGaslessGasLimitDecorator(pk) + ctx := newAnteTestCtx(t, false) + nextCalled := false + _, err := ggd.AnteHandle(ctx, tx, simulate, func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { + nextCalled = true + return ctx, nil + }) + return nextCalled, err +} + +// TestGaslessGasLimit_AboveDefaultCapRejected is the core regression: a gasless +// tx declaring more than the cap must not reach the rest of the ante chain, so +// its declared gas is never added to the block's cumulative gas wanted. +func TestGaslessGasLimit_AboveDefaultCapRejected(t *testing.T) { + pk := paramsWithCap(uexecutortypes.DefaultMaxGaslessTxGas) + + nextCalled, err := runDecorator(t, pk, gaslessTx(uexecutortypes.DefaultMaxGaslessTxGas+1), false) + + require.False(t, nextCalled, "over-cap gasless tx must not reach the next decorator") + require.Error(t, err) + require.True(t, sdkerrors.ErrInvalidGasLimit.Is(err), "expected ErrInvalidGasLimit, got: %v", err) + require.Contains(t, err.Error(), "100000001") + require.Contains(t, err.Error(), "100000000") +} + +// TestGaslessGasLimit_AtCapAccepted pins the boundary: exactly the cap passes. +func TestGaslessGasLimit_AtCapAccepted(t *testing.T) { + pk := paramsWithCap(uexecutortypes.DefaultMaxGaslessTxGas) + + nextCalled, err := runDecorator(t, pk, gaslessTx(uexecutortypes.DefaultMaxGaslessTxGas), false) + + require.True(t, nextCalled, "gasless tx at exactly the cap must be accepted") + require.NoError(t, err) +} + +// TestGaslessGasLimit_BelowCapAccepted covers the ordinary case. +func TestGaslessGasLimit_BelowCapAccepted(t *testing.T) { + pk := paramsWithCap(uexecutortypes.DefaultMaxGaslessTxGas) + + nextCalled, err := runDecorator(t, pk, gaslessTx(200_000), false) + + require.True(t, nextCalled) + require.NoError(t, err) +} + +// TestGaslessGasLimit_MaxInt64Rejected is the shape from the finding: two txs +// each declaring MaxInt64 sum past what the fee market EndBlock can convert. +func TestGaslessGasLimit_MaxInt64Rejected(t *testing.T) { + pk := paramsWithCap(uexecutortypes.DefaultMaxGaslessTxGas) + + nextCalled, err := runDecorator(t, pk, gaslessTx(math.MaxInt64), false) + + require.False(t, nextCalled, "MaxInt64 gasless tx must not reach the next decorator") + require.Error(t, err) + require.True(t, sdkerrors.ErrInvalidGasLimit.Is(err), "expected ErrInvalidGasLimit, got: %v", err) +} + +// TestGaslessGasLimit_NonGaslessTxNotCapped proves the cap is scoped to +// fee-exempt txs: a fee-paying tx is bounded by its own fee, not by this cap, +// and the params are not even read for it. +func TestGaslessGasLimit_NonGaslessTxNotCapped(t *testing.T) { + pk := paramsWithCap(uexecutortypes.DefaultMaxGaslessTxGas) + + tx := mockFeeTx{ + msgs: []sdk.Msg{&banktypes.MsgSend{}}, + gas: math.MaxInt64, + fee: sdk.NewCoins(sdk.NewInt64Coin("upc", 1)), + feePayer: sdk.AccAddress([]byte("payer")), + } + + nextCalled, err := runDecorator(t, pk, tx, false) + + require.True(t, nextCalled, "fee-paying tx must not be capped here") + require.Equal(t, 0, pk.calls, "params must not be read for a non-gasless tx") + require.NoError(t, err) +} + +// TestGaslessGasLimit_AuthzExecVoteShape uses the exact wire shape the universal +// validators send: an authz.MsgExec wrapping a vote. This is what declared the +// hardcoded 500,000,000 on donut. +func TestGaslessGasLimit_AuthzExecVoteShape(t *testing.T) { + inner, err := codectypes.NewAnyWithValue(&uexecutortypes.MsgVoteInbound{}) + require.NoError(t, err) + + execTx := func(gas uint64) mockFeeTx { + return mockFeeTx{ + msgs: []sdk.Msg{&authz.MsgExec{Grantee: "push1grantee", Msgs: []*codectypes.Any{inner}}}, + gas: gas, + fee: sdk.NewCoins(), + feePayer: sdk.AccAddress([]byte("payer")), + } + } + + t.Run("500M vote is rejected", func(t *testing.T) { + pk := paramsWithCap(uexecutortypes.DefaultMaxGaslessTxGas) + + nextCalled, err := runDecorator(t, pk, execTx(500_000_000), false) + + require.False(t, nextCalled, "500M MsgExec vote must not reach the next decorator") + require.Error(t, err) + require.True(t, sdkerrors.ErrInvalidGasLimit.Is(err), "expected ErrInvalidGasLimit, got: %v", err) + }) + + t.Run("100M vote is accepted", func(t *testing.T) { + pk := paramsWithCap(uexecutortypes.DefaultMaxGaslessTxGas) + + nextCalled, err := runDecorator(t, pk, execTx(100_000_000), false) + + require.True(t, nextCalled, "100M MsgExec vote must be accepted") + require.NoError(t, err) + }) +} + +// TestGaslessGasLimit_GovernanceParamTakesEffect proves the cap is the +// governance parameter and not a compiled-in constant: it must bind both +// tighter and looser than the default. +func TestGaslessGasLimit_GovernanceParamTakesEffect(t *testing.T) { + t.Run("lowered cap binds below the default", func(t *testing.T) { + pk := paramsWithCap(30_000_000) + + acceptedNext, acceptedErr := runDecorator(t, pk, gaslessTx(30_000_000), false) + require.True(t, acceptedNext, "tx at the lowered cap must be accepted") + require.NoError(t, acceptedErr) + + rejectedNext, rejectedErr := runDecorator(t, pk, gaslessTx(30_000_001), false) + require.False(t, rejectedNext, "tx above the lowered cap must be rejected") + require.Error(t, rejectedErr) + require.Contains(t, rejectedErr.Error(), "30000000") + }) + + t.Run("raised cap admits gas the default would reject", func(t *testing.T) { + pk := paramsWithCap(500_000_000) + + nextCalled, err := runDecorator(t, pk, gaslessTx(400_000_000), false) + + require.True(t, nextCalled, "raised cap must admit 400M") + require.NoError(t, err) + }) +} + +// TestGaslessGasLimit_UnsetParamFallsBackToDefault: a missing parameter must +// never mean "no cap". +func TestGaslessGasLimit_UnsetParamFallsBackToDefault(t *testing.T) { + t.Run("zero param", func(t *testing.T) { + pk := paramsWithCap(0) + + acceptedNext, acceptedErr := runDecorator(t, pk, gaslessTx(uexecutortypes.DefaultMaxGaslessTxGas), false) + require.True(t, acceptedNext) + require.NoError(t, acceptedErr) + + rejectedNext, rejectedErr := runDecorator(t, pk, gaslessTx(uexecutortypes.DefaultMaxGaslessTxGas+1), false) + require.False(t, rejectedNext, "zero param must fall back to the default cap, not disable it") + require.Error(t, rejectedErr) + }) + + t.Run("params read failure", func(t *testing.T) { + pk := &mockGaslessParamsKeeper{err: errors.New("collections: not found")} + + nextCalled, err := runDecorator(t, pk, gaslessTx(uexecutortypes.DefaultMaxGaslessTxGas+1), false) + + require.False(t, nextCalled, "unreadable params must fall back to the default cap, not disable it") + require.Error(t, err) + require.True(t, sdkerrors.ErrInvalidGasLimit.Is(err), "expected ErrInvalidGasLimit, got: %v", err) + }) +} + +// TestGaslessGasLimit_SimulationIsAlsoCapped keeps simulation honest: a gas +// estimate that would be rejected on delivery must not come back clean. +func TestGaslessGasLimit_SimulationIsAlsoCapped(t *testing.T) { + pk := paramsWithCap(uexecutortypes.DefaultMaxGaslessTxGas) + + nextCalled, err := runDecorator(t, pk, gaslessTx(uexecutortypes.DefaultMaxGaslessTxGas+1), true) + + require.False(t, nextCalled, "simulation must apply the same cap") + require.Error(t, err) +} diff --git a/app/ante/handler_options.go b/app/ante/handler_options.go index dd12c51fc..52b563192 100755 --- a/app/ante/handler_options.go +++ b/app/ante/handler_options.go @@ -61,6 +61,10 @@ type HandlerOptions struct { FeeMarketKeeper anteinterfaces.FeeMarketKeeper EvmKeeper anteinterfaces.EVMKeeper + // UexecutorKeeper supplies the governance-controlled cap on the gas a + // fee-exempt (gasless) tx may declare. + UexecutorKeeper GaslessParamsKeeper + IBCKeeper *ibckeeper.Keeper CircuitKeeper *circuitkeeper.Keeper @@ -103,6 +107,9 @@ func (options HandlerOptions) Validate() error { if options.EvmKeeper == nil { return errorsmod.Wrap(errortypes.ErrLogic, "evm keeper is required for AnteHandler") } + if options.UexecutorKeeper == nil { + return errorsmod.Wrap(errortypes.ErrLogic, "uexecutor keeper is required for AnteHandler") + } return nil } diff --git a/app/app.go b/app/app.go index 2ef99554f..14fd71ac5 100644 --- a/app/app.go +++ b/app/app.go @@ -1251,6 +1251,7 @@ func NewChainApp( CircuitKeeper: &app.CircuitKeeper, EvmKeeper: app.EVMKeeper, + UexecutorKeeper: app.UexecutorKeeper, ExtensionOptionChecker: antetypes.HasDynamicFeeExtensionOption, SigGasConsumer: cosmosevmante.SigVerificationGasConsumer, MaxTxGasWanted: cast.ToUint64(appOpts.Get(srvflags.EVMMaxTxGasWanted)), diff --git a/proto/uexecutor/v1/types.proto b/proto/uexecutor/v1/types.proto index fc365bcdb..9b9fab47e 100644 --- a/proto/uexecutor/v1/types.proto +++ b/proto/uexecutor/v1/types.proto @@ -13,6 +13,13 @@ message Params { option (gogoproto.goproto_stringer) = false; bool some_value = 2; + + // max_gasless_tx_gas is the maximum gas limit a fee-exempt (gasless) + // transaction is allowed to declare. Gasless transactions pay no fee, so + // their declared gas is not bounded by anything the sender has to spend; + // this cap is the only bound on how much a single gasless transaction can + // contribute to the block's cumulative gas wanted. + uint64 max_gasless_tx_gas = 3; } // Signature verification types diff --git a/test/integration/ante/gasless_gas_limit_test.go b/test/integration/ante/gasless_gas_limit_test.go new file mode 100644 index 000000000..1ad5fe3c9 --- /dev/null +++ b/test/integration/ante/gasless_gas_limit_test.go @@ -0,0 +1,332 @@ +package ante_test + +import ( + "fmt" + "math" + "math/rand" + "testing" + "time" + + abci "github.com/cometbft/cometbft/abci/types" + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + cmttypes "github.com/cometbft/cometbft/types" + "github.com/stretchr/testify/require" + + sdkmath "cosmossdk.io/math" + + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/testutil/mock" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + "github.com/pushchain/push-chain-node/app" + uexecutorkeeper "github.com/pushchain/push-chain-node/x/uexecutor/keeper" + uexecutortypes "github.com/pushchain/push-chain-node/x/uexecutor/types" +) + +// --------------------------------------------------------------------------- +// F-2026-18144 — Unchecked Cumulative GasWanted Can Fail FinalizeBlock Under +// Unbounded Block Gas. +// +// Fee-paying txs are self-limiting: the ante handler requires +// ceil(minGasPrice * gasLimit), so a huge declared gas costs huge money. +// Gasless txs pay nothing, so the declared gas is free — and it is still added +// to the fee market's cumulative gas wanted for the block. Two gasless txs each +// declaring MaxInt64 sum to a value that x/feemarket EndBlock cannot convert +// back to int64; it returns an error, and that error comes out of +// FinalizeBlock, after the block has already been decided. +// +// These tests run real signed txs through the whole baseapp -> ante -> +// EndBlock pipeline with the block gas limit set to the unbounded value donut +// runs (max_gas: -1), which is what makes the per-tx block-limit check in the +// EVM ante inert. +// --------------------------------------------------------------------------- + +// setupGaslessAnteApp boots a chain app with a single validator and one funded +// genesis account whose private key we keep, so we can sign real gasless txs. +func setupGaslessAnteApp(t *testing.T) (*app.ChainApp, cryptotypes.PrivKey, sdk.AccAddress) { + t.Helper() + + privVal := mock.NewPV() + valPubKey, err := privVal.GetPubKey() + require.NoError(t, err) + + valSet := cmttypes.NewValidatorSet([]*cmttypes.Validator{cmttypes.NewValidator(valPubKey, 1)}) + + senderPrivKey := secp256k1.GenPrivKey() + senderAcc := authtypes.NewBaseAccount(senderPrivKey.PubKey().Address().Bytes(), senderPrivKey.PubKey(), 0, 0) + senderAddr := senderAcc.GetAddress() + + balance := banktypes.Balance{ + Address: senderAddr.String(), + Coins: sdk.NewCoins( + sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(100_000_000_000_000)), + sdk.NewCoin(app.BaseDenom, sdkmath.NewInt(1).MulRaw(1e18).MulRaw(100)), + ), + } + + chainApp := app.SetupWithGenesisValSet( + t, valSet, []authtypes.GenesisAccount{senderAcc}, testChainID, nil, balance, + ) + + setUnboundedBlockGas(t, chainApp) + + return chainApp, senderPrivKey, senderAddr +} + +// setUnboundedBlockGas reproduces donut's consensus configuration +// (update_max_block_gas.json sets max_gas to -1), under which +// ante/types.BlockGasLimit returns math.MaxUint64 and the per-tx block gas +// check can never fire. Written through an uncached context so it survives +// into FinalizeBlock. +func setUnboundedBlockGas(t *testing.T, chainApp *app.ChainApp) { + t.Helper() + + ctx := chainApp.BaseApp.NewUncachedContext(false, cmtproto.Header{}) + cp, err := chainApp.ConsensusParamsKeeper.ParamsStore.Get(ctx) + require.NoError(t, err) + cp.Block.MaxGas = -1 + require.NoError(t, chainApp.ConsensusParamsKeeper.ParamsStore.Set(ctx, cp)) +} + +// gaslessVoteMsg builds a MsgVoteInbound — one of the fee-exempt message types +// in app/txpolicy/gasless.go — that passes ValidateBasic, so the tx is only +// ever stopped by a gas decision and not by message validation. +func gaslessVoteMsg(signer sdk.AccAddress, nonce int) sdk.Msg { + return &uexecutortypes.MsgVoteInbound{ + Signer: signer.String(), + Inbound: &uexecutortypes.Inbound{ + SourceChain: "eip155:11155111", + TxHash: fmt.Sprintf("0xf18144000000000000000000000000000000000000000000000000000000%04d", nonce), + Sender: "0x1111111111111111111111111111111111111111", + Recipient: "0x2222222222222222222222222222222222222222", + Amount: "1", + AssetAddr: "0x3333333333333333333333333333333333333333", + LogIndex: "0", + TxType: uexecutortypes.TxType_FUNDS, + }, + } +} + +// deliverGaslessBlock signs one gasless tx per entry in gasLimits and delivers +// them as a single block. +func deliverGaslessBlock( + t *testing.T, + chainApp *app.ChainApp, + priv cryptotypes.PrivKey, + addr sdk.AccAddress, + gasLimits []uint64, +) (*abci.ResponseFinalizeBlock, error) { + t.Helper() + + ctx := chainApp.BaseApp.NewContext(true) + acc := chainApp.AccountKeeper.GetAccount(ctx, addr) + require.NotNil(t, acc) + + txBytes := make([][]byte, 0, len(gasLimits)) + for i, gas := range gasLimits { + tx, err := simtestutil.GenSignedMockTx( + rand.New(rand.NewSource(int64(i)+1)), + chainApp.TxConfig(), + []sdk.Msg{gaslessVoteMsg(addr, i)}, + sdk.NewCoins(), // gasless: no fee is offered at all + gas, + testChainID, + []uint64{acc.GetAccountNumber()}, + []uint64{acc.GetSequence() + uint64(i)}, + priv, + ) + require.NoError(t, err) + + bz, err := chainApp.TxConfig().TxEncoder()(tx) + require.NoError(t, err) + txBytes = append(txBytes, bz) + } + + return chainApp.FinalizeBlock(&abci.RequestFinalizeBlock{ + Height: chainApp.LastBlockHeight() + 1, + Time: time.Now(), + Txs: txBytes, + }) +} + +func blockGasWanted(t *testing.T, chainApp *app.ChainApp) uint64 { + t.Helper() + return chainApp.FeeMarketKeeper.GetBlockGasWanted(chainApp.BaseApp.NewContext(true)) +} + +// TestGaslessCumulativeGasWantedCannotFailFinalizeBlock is the chain-level +// regression for the finding itself. Two gasless txs each declaring MaxInt64 +// sum to 2^64-2 — a valid uint64 that x/feemarket EndBlock cannot convert to +// int64. Neither tx is individually rejectable without the cap. +func TestGaslessCumulativeGasWantedCannotFailFinalizeBlock(t *testing.T) { + chainApp, priv, addr := setupGaslessAnteApp(t) + + res, err := deliverGaslessBlock(t, chainApp, priv, addr, []uint64{math.MaxInt64, math.MaxInt64}) + + // The block must still be produced. Without the cap, x/feemarket EndBlock + // errors on the cumulative total and that error surfaces through + // FinalizeBlock, leaving CometBFT at height H and the app at H-1. + require.NoError(t, err, "FinalizeBlock must survive the cumulative gas wanted") + require.NotNil(t, res) + require.Len(t, res.TxResults, 2) + + require.Less(t, blockGasWanted(t, chainApp), uint64(1_000_000), + "rejected txs must not contribute their declared gas to the block") + + for i, txRes := range res.TxResults { + require.NotEqual(t, abci.CodeTypeOK, txRes.Code, "tx %d must be rejected, got success", i) + require.Contains(t, txRes.Log, "exceeds the maximum allowed", + "tx %d must be rejected by the gasless gas cap, got: %s", i, txRes.Log) + } +} + +// TestGaslessTxAboveCapRejected covers a single tx one unit over the cap. +func TestGaslessTxAboveCapRejected(t *testing.T) { + chainApp, priv, addr := setupGaslessAnteApp(t) + + res, err := deliverGaslessBlock(t, chainApp, priv, addr, + []uint64{uexecutortypes.DefaultMaxGaslessTxGas + 1}) + require.NoError(t, err) + require.Len(t, res.TxResults, 1) + + require.Less(t, blockGasWanted(t, chainApp), uint64(1_000_000), + "an over-cap tx must not have its declared gas counted") + + txRes := res.TxResults[0] + require.NotEqual(t, abci.CodeTypeOK, txRes.Code, "over-cap gasless tx must be rejected") + require.Contains(t, txRes.Log, "exceeds the maximum allowed", "got: %s", txRes.Log) +} + +// TestGaslessTxAtCapAccepted pins the other side of the boundary: a tx at +// exactly the cap passes the ante chain and its gas is counted normally. +// x/feemarket EndBlock records max(gasWanted*MinGasMultiplier, gasUsed), and +// MinGasMultiplier defaults to 0.5, so a 100,000,000 declaration must show up +// as at least 50,000,000. +func TestGaslessTxAtCapAccepted(t *testing.T) { + chainApp, priv, addr := setupGaslessAnteApp(t) + + res, err := deliverGaslessBlock(t, chainApp, priv, addr, + []uint64{uexecutortypes.DefaultMaxGaslessTxGas}) + require.NoError(t, err) + require.Len(t, res.TxResults, 1) + + require.GreaterOrEqual(t, blockGasWanted(t, chainApp), uint64(50_000_000), + "a tx at the cap must reach the fee market and have its gas counted") + require.NotContains(t, res.TxResults[0].Log, "exceeds the maximum allowed", + "a tx at the cap must not be rejected by the cap") +} + +// TestGaslessTxAtUniversalValidatorGasAccepted uses the gas limit the universal +// validators now declare (universalClient/pushsigner/vote.go). The fleet must +// keep voting under the cap. +func TestGaslessTxAtUniversalValidatorGasAccepted(t *testing.T) { + chainApp, priv, addr := setupGaslessAnteApp(t) + + res, err := deliverGaslessBlock(t, chainApp, priv, addr, []uint64{100_000_000}) + require.NoError(t, err) + require.Len(t, res.TxResults, 1) + + require.GreaterOrEqual(t, blockGasWanted(t, chainApp), uint64(50_000_000), + "the universal validator's declared gas must still be accepted") + require.NotContains(t, res.TxResults[0].Log, "exceeds the maximum allowed", "got: %s", res.TxResults[0].Log) +} + +// TestGaslessCapIsAGovernanceParameter proves the cap is state, not a compiled +// constant: a governance update to uexecutor params changes what the ante +// handler accepts on the very next block. +func TestGaslessCapIsAGovernanceParameter(t *testing.T) { + const loweredCap = uint64(30_000_000) + // Comfortably under the 100,000,000 default and comfortably over the + // lowered cap, so only the parameter can decide the outcome. + const declaredGas = uint64(40_000_000) + + t.Run("default cap admits 40M", func(t *testing.T) { + chainApp, priv, addr := setupGaslessAnteApp(t) + + res, err := deliverGaslessBlock(t, chainApp, priv, addr, []uint64{declaredGas}) + require.NoError(t, err) + + require.GreaterOrEqual(t, blockGasWanted(t, chainApp), uint64(20_000_000), + "40M must be accepted under the default cap") + require.NotContains(t, res.TxResults[0].Log, "exceeds the maximum allowed", "got: %s", res.TxResults[0].Log) + }) + + t.Run("governance lowers the cap and 40M is rejected", func(t *testing.T) { + chainApp, priv, addr := setupGaslessAnteApp(t) + setGaslessCapByGovernance(t, chainApp, loweredCap) + + res, err := deliverGaslessBlock(t, chainApp, priv, addr, []uint64{declaredGas}) + require.NoError(t, err) + + require.Less(t, blockGasWanted(t, chainApp), uint64(1_000_000), + "a tx over the lowered cap must not have its gas counted") + + txRes := res.TxResults[0] + require.NotEqual(t, abci.CodeTypeOK, txRes.Code) + require.Contains(t, txRes.Log, "exceeds the maximum allowed", "got: %s", txRes.Log) + require.Contains(t, txRes.Log, "30000000", "the error must quote the governance-set cap, got: %s", txRes.Log) + }) + + t.Run("governance lowers the cap and 30M is still accepted", func(t *testing.T) { + chainApp, priv, addr := setupGaslessAnteApp(t) + setGaslessCapByGovernance(t, chainApp, loweredCap) + + res, err := deliverGaslessBlock(t, chainApp, priv, addr, []uint64{loweredCap}) + require.NoError(t, err) + + require.GreaterOrEqual(t, blockGasWanted(t, chainApp), uint64(15_000_000), + "a tx at the lowered cap must still be accepted") + require.NotContains(t, res.TxResults[0].Log, "exceeds the maximum allowed", "got: %s", res.TxResults[0].Log) + }) + + t.Run("governance cannot brick voting with a zero cap", func(t *testing.T) { + chainApp, _, _ := setupGaslessAnteApp(t) + + ctx := chainApp.BaseApp.NewUncachedContext(false, cmtproto.Header{}) + params, err := chainApp.UexecutorKeeper.GetParams(ctx) + require.NoError(t, err) + params.MaxGaslessTxGas = 0 + + _, err = uexecutorkeeper.NewMsgServerImpl(chainApp.UexecutorKeeper).UpdateParams(ctx, + &uexecutortypes.MsgUpdateParams{ + Authority: authtypes.NewModuleAddress(govtypes.ModuleName).String(), + Params: params, + }) + + stored, getErr := chainApp.UexecutorKeeper.GetParams(ctx) + require.NoError(t, getErr) + require.Equal(t, uexecutortypes.DefaultMaxGaslessTxGas, stored.MaxGaslessTxGas, + "a rejected update must leave the cap untouched") + require.Error(t, err, "a zero cap must be rejected") + require.Contains(t, err.Error(), "max_gasless_tx_gas") + }) +} + +// setGaslessCapByGovernance applies a params update through the module's +// MsgServer with the real gov authority, i.e. exactly what a passed proposal +// executes. +func setGaslessCapByGovernance(t *testing.T, chainApp *app.ChainApp, cap uint64) { + t.Helper() + + ctx := chainApp.BaseApp.NewUncachedContext(false, cmtproto.Header{}) + + params, err := chainApp.UexecutorKeeper.GetParams(ctx) + require.NoError(t, err) + params.MaxGaslessTxGas = cap + + _, err = uexecutorkeeper.NewMsgServerImpl(chainApp.UexecutorKeeper).UpdateParams(ctx, + &uexecutortypes.MsgUpdateParams{ + Authority: authtypes.NewModuleAddress(govtypes.ModuleName).String(), + Params: params, + }) + require.NoError(t, err) + + stored, err := chainApp.UexecutorKeeper.GetParams(ctx) + require.NoError(t, err) + require.Equal(t, cap, stored.MaxGaslessTxGas) +} diff --git a/test/integration/uexecutor/evm_hooks_and_outbound_test.go b/test/integration/uexecutor/evm_hooks_and_outbound_test.go index 1e232c4e4..cf8a8f187 100644 --- a/test/integration/uexecutor/evm_hooks_and_outbound_test.go +++ b/test/integration/uexecutor/evm_hooks_and_outbound_test.go @@ -163,7 +163,8 @@ func TestUpdateParams(t *testing.T) { err := app.UexecutorKeeper.Params.Set(ctx, initialParams) require.NoError(t, err) - updatedParams := uexecutortypes.Params{SomeValue: !initialParams.SomeValue} + updatedParams := initialParams + updatedParams.SomeValue = !initialParams.SomeValue err = app.UexecutorKeeper.UpdateParams(ctx, updatedParams) require.NoError(t, err) diff --git a/test/utils/contracts_setup.go b/test/utils/contracts_setup.go index dc588b290..a4f9e3970 100644 --- a/test/utils/contracts_setup.go +++ b/test/utils/contracts_setup.go @@ -22,7 +22,7 @@ func setupUESystem( accounts TestAccounts, ) error { // Initialize UE genesis - app.UexecutorKeeper.InitGenesis(ctx, &uetypes.GenesisState{}) + app.UexecutorKeeper.InitGenesis(ctx, uetypes.DefaultGenesis()) // Parse factory ABI factoryABI, err := uetypes.ParseFactoryABI() diff --git a/universalClient/pushsigner/vote.go b/universalClient/pushsigner/vote.go index 3312f463c..830268447 100644 --- a/universalClient/pushsigner/vote.go +++ b/universalClient/pushsigner/vote.go @@ -13,7 +13,7 @@ import ( ) const ( - defaultGasLimit = uint64(500000000) + defaultGasLimit = uint64(100000000) defaultFeeAmount = "500000000000000upc" defaultVoteTimeout = 30 * time.Second txPollInterval = 500 * time.Millisecond diff --git a/universalClient/pushsigner/vote_test.go b/universalClient/pushsigner/vote_test.go index 19682c1b7..fe5949f65 100644 --- a/universalClient/pushsigner/vote_test.go +++ b/universalClient/pushsigner/vote_test.go @@ -17,7 +17,11 @@ import ( func TestVoteConstants(t *testing.T) { t.Run("default gas limit", func(t *testing.T) { - assert.Equal(t, uint64(500000000), defaultGasLimit) + assert.Equal(t, uint64(100000000), defaultGasLimit) + // Votes are gasless, so the chain caps what they may declare. Declaring + // more than the cap gets every vote rejected in the ante handler. + assert.LessOrEqual(t, defaultGasLimit, uexecutortypes.DefaultMaxGaslessTxGas, + "the universal validator must declare no more gas than the chain's gasless cap") }) t.Run("default fee amount is valid", func(t *testing.T) { diff --git a/x/uexecutor/keeper/msg_update_params.go b/x/uexecutor/keeper/msg_update_params.go index f071aeb49..92873a282 100644 --- a/x/uexecutor/keeper/msg_update_params.go +++ b/x/uexecutor/keeper/msg_update_params.go @@ -6,8 +6,17 @@ import ( "github.com/pushchain/push-chain-node/x/uexecutor/types" ) +// GetParams returns the current module parameters. +func (k Keeper) GetParams(ctx context.Context) (types.Params, error) { + return k.Params.Get(ctx) +} + // updateParams is for updating params collections of the module func (k Keeper) UpdateParams(ctx context.Context, params types.Params) error { + if err := params.ValidateBasic(); err != nil { + return err + } + oldParams, err := k.Params.Get(ctx) if err == nil { k.Logger().Info("params updated", diff --git a/x/uexecutor/types/genesis_test.go b/x/uexecutor/types/genesis_test.go index bdfc922a1..9c478f107 100755 --- a/x/uexecutor/types/genesis_test.go +++ b/x/uexecutor/types/genesis_test.go @@ -20,9 +20,12 @@ func TestGenesisState_Validate(t *testing.T) { valid: true, }, { - desc: "valid genesis state", + // An empty Params leaves max_gasless_tx_gas at 0, which would + // reject every gasless tx and stop the universal validators from + // voting. Fail at genesis rather than silently. + desc: "empty params are rejected", genState: &types.GenesisState{}, - valid: true, + valid: false, }, } for _, tc := range tests { diff --git a/x/uexecutor/types/params.go b/x/uexecutor/types/params.go index 6dadfa5a9..489649921 100755 --- a/x/uexecutor/types/params.go +++ b/x/uexecutor/types/params.go @@ -2,13 +2,26 @@ package types import ( "encoding/json" + "fmt" ) +// DefaultMaxGaslessTxGas is the default cap on the gas limit a fee-exempt +// (gasless) transaction may declare. +// +// Fee-paying transactions are self-limiting: the required fee is +// ceil(minGasPrice * gasLimit), so declaring a large gas limit costs real +// tokens. Gasless transactions pay nothing, so nothing bounds the gas they +// declare, and the declared gas is what accumulates into the block's +// cumulative gas wanted. 100,000,000 is roughly 20x the largest gas actually +// consumed by a gasless transaction observed on the network. +const DefaultMaxGaslessTxGas uint64 = 100_000_000 + // DefaultParams returns default module parameters. func DefaultParams() Params { // TODO: return Params{ - SomeValue: true, + SomeValue: true, + MaxGaslessTxGas: DefaultMaxGaslessTxGas, } } @@ -24,6 +37,12 @@ func (p Params) String() string { // Validate does the sanity check on the params. func (p Params) ValidateBasic() error { - // TODO: + // A zero cap would reject every gasless transaction, which would stop the + // universal validators from voting. Governance must always set a usable + // value. + if p.MaxGaslessTxGas == 0 { + return fmt.Errorf("max_gasless_tx_gas must be greater than 0") + } + return nil } diff --git a/x/uexecutor/types/params_test.go b/x/uexecutor/types/params_test.go new file mode 100644 index 000000000..111175f50 --- /dev/null +++ b/x/uexecutor/types/params_test.go @@ -0,0 +1,42 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/pushchain/push-chain-node/x/uexecutor/types" +) + +// TestDefaultParams_MaxGaslessTxGas pins the shipped default. Gasless txs pay +// no fee, so this cap is the only bound on the gas one of them can declare. +func TestDefaultParams_MaxGaslessTxGas(t *testing.T) { + params := types.DefaultParams() + + require.Equal(t, uint64(100_000_000), params.MaxGaslessTxGas) + require.Equal(t, types.DefaultMaxGaslessTxGas, params.MaxGaslessTxGas) + require.NoError(t, params.ValidateBasic()) +} + +// TestParams_ValidateBasic_RejectsZeroCap: a zero cap would reject every +// gasless tx, which would stop the universal validators from voting. +func TestParams_ValidateBasic_RejectsZeroCap(t *testing.T) { + params := types.DefaultParams() + params.MaxGaslessTxGas = 0 + + err := params.ValidateBasic() + + require.Error(t, err) + require.Contains(t, err.Error(), "max_gasless_tx_gas") +} + +// TestParams_ValidateBasic_AcceptsGovernanceChosenCaps: the cap has to be +// movable in both directions by proposal. +func TestParams_ValidateBasic_AcceptsGovernanceChosenCaps(t *testing.T) { + for _, cap := range []uint64{1, 21_000, 30_000_000, 100_000_000, 500_000_000} { + params := types.DefaultParams() + params.MaxGaslessTxGas = cap + + require.NoError(t, params.ValidateBasic(), "cap %d must be settable", cap) + } +} diff --git a/x/uexecutor/types/types.pb.go b/x/uexecutor/types/types.pb.go index 9ee225bbf..70303acee 100644 --- a/x/uexecutor/types/types.pb.go +++ b/x/uexecutor/types/types.pb.go @@ -214,6 +214,12 @@ func (InboundTxTypeLegacy) EnumDescriptor() ([]byte, []int) { // Params defines the set of module parameters. type Params struct { SomeValue bool `protobuf:"varint,2,opt,name=some_value,json=someValue,proto3" json:"some_value,omitempty"` + // max_gasless_tx_gas is the maximum gas limit a fee-exempt (gasless) + // transaction is allowed to declare. Gasless transactions pay no fee, so + // their declared gas is not bounded by anything the sender has to spend; + // this cap is the only bound on how much a single gasless transaction can + // contribute to the block's cumulative gas wanted. + MaxGaslessTxGas uint64 `protobuf:"varint,3,opt,name=max_gasless_tx_gas,json=maxGaslessTxGas,proto3" json:"max_gasless_tx_gas,omitempty"` } func (m *Params) Reset() { *m = Params{} } @@ -255,6 +261,13 @@ func (m *Params) GetSomeValue() bool { return false } +func (m *Params) GetMaxGaslessTxGas() uint64 { + if m != nil { + return m.MaxGaslessTxGas + } + return 0 +} + // UniversalPayload mirrors the Solidity struct type UniversalPayload struct { To string `protobuf:"bytes,1,opt,name=to,proto3" json:"to,omitempty"` @@ -1440,125 +1453,126 @@ func init() { func init() { proto.RegisterFile("uexecutor/v1/types.proto", fileDescriptor_fab6d3ca71d1e2a5) } var fileDescriptor_fab6d3ca71d1e2a5 = []byte{ - // 1875 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x58, 0xcf, 0x6f, 0xdb, 0xd8, - 0xf1, 0x37, 0xf5, 0x5b, 0x23, 0xc7, 0xa2, 0x9e, 0xed, 0x84, 0x49, 0xd6, 0xb2, 0xad, 0xdd, 0xfd, - 0xc6, 0xf0, 0x17, 0xb1, 0xbb, 0x69, 0xbb, 0x40, 0x05, 0x14, 0x85, 0x2c, 0xd3, 0x5e, 0xb5, 0x5e, - 0x49, 0xa5, 0x24, 0x23, 0xdb, 0x0b, 0xf1, 0x4c, 0xbe, 0xd0, 0xc4, 0x5a, 0xa4, 0x40, 0x52, 0x32, - 0x7d, 0xee, 0xad, 0x58, 0xa0, 0xbd, 0x14, 0xc8, 0x31, 0xe7, 0xf6, 0xd2, 0x43, 0xff, 0x88, 0x3d, - 0x6e, 0xd1, 0x4b, 0x81, 0x5e, 0x8a, 0xe4, 0xd0, 0xfe, 0x17, 0x2d, 0xde, 0x0f, 0x8a, 0xa4, 0x2c, - 0x65, 0xb7, 0xe8, 0xa1, 0x40, 0x2f, 0x31, 0x67, 0xde, 0xbc, 0x79, 0x33, 0xf3, 0xf9, 0xcc, 0xbc, - 0x17, 0x81, 0x32, 0x25, 0x21, 0x31, 0xa6, 0x81, 0xeb, 0x1d, 0xcf, 0x3e, 0x39, 0x0e, 0xee, 0x26, - 0xc4, 0x3f, 0x9a, 0x78, 0x6e, 0xe0, 0xa2, 0xf5, 0xf9, 0xca, 0xd1, 0xec, 0x93, 0x27, 0x5b, 0x96, - 0x6b, 0xb9, 0x6c, 0xe1, 0x98, 0x7e, 0x71, 0x9b, 0x27, 0x35, 0x3c, 0xb6, 0x1d, 0xf7, 0x98, 0xfd, - 0xcb, 0x55, 0x8d, 0x33, 0x28, 0xf4, 0xb1, 0x87, 0xc7, 0x3e, 0xda, 0x01, 0xf0, 0xdd, 0x31, 0xd1, - 0x67, 0xf8, 0x66, 0x4a, 0x94, 0xcc, 0x9e, 0x74, 0x50, 0xd2, 0xca, 0x54, 0x73, 0x49, 0x15, 0xcd, - 0x9d, 0xd7, 0x6f, 0x76, 0xd7, 0xfe, 0xf1, 0x66, 0x57, 0xfa, 0xd5, 0xdf, 0xff, 0x70, 0x28, 0xc7, - 0x61, 0x4c, 0xd8, 0xee, 0xc6, 0x5f, 0x33, 0x20, 0x8f, 0x1c, 0x7b, 0x46, 0x3c, 0x1f, 0xdf, 0xf4, - 0xf1, 0xdd, 0x8d, 0x8b, 0x4d, 0xb4, 0x01, 0x99, 0xc0, 0x55, 0xa4, 0x3d, 0xe9, 0xa0, 0xac, 0x65, - 0x02, 0x17, 0x6d, 0x41, 0x3e, 0xf6, 0x5e, 0xd6, 0xb8, 0x80, 0x10, 0xe4, 0x4c, 0x1c, 0x60, 0x25, - 0xcb, 0x94, 0xec, 0x1b, 0x3d, 0x85, 0xb2, 0x85, 0x7d, 0xfd, 0xc6, 0x1e, 0xdb, 0x81, 0x92, 0x63, - 0x0b, 0x25, 0x0b, 0xfb, 0x17, 0x54, 0x46, 0x1f, 0x43, 0x75, 0x8c, 0x43, 0xfd, 0x15, 0x21, 0xfa, - 0x84, 0x78, 0xba, 0x85, 0x7d, 0x25, 0xcf, 0x4c, 0xd6, 0xc7, 0x38, 0x3c, 0x23, 0xa4, 0x4f, 0xbc, - 0x73, 0xec, 0xa3, 0x4f, 0x41, 0xa1, 0x66, 0x13, 0xcf, 0x76, 0x3d, 0x3b, 0xb8, 0x4b, 0xd9, 0x17, - 0x98, 0xfd, 0xd6, 0x18, 0x87, 0x7d, 0xb1, 0x1c, 0xef, 0xdb, 0x82, 0xbc, 0xe3, 0x3a, 0x06, 0x51, - 0x8a, 0x3c, 0x4a, 0x26, 0xa0, 0x27, 0x50, 0x32, 0x09, 0x36, 0x6f, 0x6c, 0x87, 0x28, 0x25, 0x1e, - 0x50, 0x24, 0xa3, 0x1f, 0x42, 0x61, 0xa6, 0x53, 0x30, 0x94, 0xf2, 0x9e, 0x74, 0xb0, 0xf1, 0xa2, - 0x7e, 0x94, 0x04, 0xe3, 0xe8, 0x92, 0x78, 0xf6, 0x2b, 0xdb, 0xc0, 0x81, 0xed, 0x3a, 0xc3, 0xbb, - 0x09, 0xd1, 0xf2, 0x33, 0xfa, 0xa7, 0x79, 0x90, 0x2c, 0xe9, 0xd3, 0xb8, 0xa4, 0xd3, 0xa8, 0x8e, - 0xfa, 0x84, 0x17, 0xb2, 0xf1, 0x95, 0x04, 0xf2, 0xe7, 0xb6, 0xe5, 0x31, 0x17, 0x51, 0x75, 0x3f, - 0x80, 0xf2, 0x38, 0xd2, 0x89, 0x22, 0xc7, 0x8a, 0x38, 0x8b, 0xcc, 0xaa, 0x2c, 0xb2, 0xe9, 0x2c, - 0x56, 0x86, 0x33, 0xf7, 0x39, 0x0f, 0xe7, 0xb5, 0x04, 0x68, 0x0e, 0x76, 0xcb, 0x30, 0xdc, 0xa9, - 0x13, 0x74, 0x4c, 0xf4, 0x0c, 0xaa, 0xc6, 0x35, 0xb6, 0x1d, 0xdd, 0xc1, 0x63, 0xe2, 0x4f, 0xb0, - 0x41, 0x44, 0x58, 0x1b, 0x4c, 0xdd, 0x8d, 0xb4, 0xe8, 0x31, 0x94, 0xb8, 0xa1, 0x6d, 0x8a, 0xf0, - 0x8a, 0x4c, 0xee, 0x98, 0x34, 0x6c, 0xf7, 0xd6, 0x21, 0x9e, 0x88, 0x8e, 0x0b, 0xdf, 0xa1, 0x52, - 0x98, 0x47, 0xd1, 0x30, 0x00, 0x69, 0x64, 0x46, 0xbc, 0xa0, 0xe3, 0xf8, 0x81, 0x37, 0x35, 0x68, - 0xdc, 0x3e, 0xfa, 0x18, 0x36, 0x5e, 0x4d, 0x1d, 0x53, 0xf7, 0x88, 0x61, 0x4f, 0x6c, 0xe2, 0x04, - 0x22, 0xb0, 0x07, 0x54, 0xab, 0x45, 0xca, 0xe6, 0xff, 0x45, 0x47, 0xec, 0xc4, 0x47, 0x78, 0xcc, - 0x9b, 0x6e, 0x27, 0xdc, 0x35, 0xbe, 0xca, 0x41, 0xb1, 0xe3, 0x5c, 0xb9, 0x53, 0xc7, 0x44, 0xfb, - 0xb0, 0xee, 0xbb, 0x53, 0xcf, 0x20, 0x3a, 0x4b, 0x41, 0x38, 0xae, 0x70, 0x5d, 0x9b, 0xaa, 0xd0, - 0x23, 0x28, 0x06, 0xa1, 0x7e, 0x8d, 0xfd, 0x6b, 0x91, 0x6d, 0x21, 0x08, 0x3f, 0xc3, 0xfe, 0x35, - 0x7a, 0x08, 0x05, 0x9f, 0x38, 0xe6, 0x3c, 0x5b, 0x21, 0x51, 0x64, 0xe3, 0x48, 0x39, 0xfb, 0x63, - 0x05, 0xdd, 0x85, 0xc7, 0x34, 0x59, 0xc1, 0x7a, 0x21, 0xd1, 0x06, 0xc6, 0xbe, 0x4f, 0x02, 0x1d, - 0x9b, 0xa6, 0x27, 0x18, 0x5e, 0x66, 0x9a, 0x96, 0x69, 0x7a, 0xb4, 0xa5, 0x6e, 0x5c, 0x4b, 0xb7, - 0x1d, 0x93, 0x84, 0x82, 0xda, 0xa5, 0x1b, 0xd7, 0xea, 0x50, 0x19, 0x3d, 0x67, 0x21, 0x32, 0x0a, - 0x97, 0x18, 0x85, 0xb7, 0xd2, 0x14, 0x1e, 0x86, 0x8c, 0xb8, 0x85, 0x80, 0xfd, 0x45, 0x3f, 0x83, - 0xda, 0x3d, 0x92, 0x32, 0xee, 0x57, 0x16, 0xb9, 0xbf, 0x38, 0x13, 0x34, 0x79, 0xba, 0x38, 0x25, - 0xfe, 0x1f, 0x6a, 0xb3, 0x44, 0x87, 0xe8, 0x6c, 0x18, 0x00, 0x0b, 0x50, 0x4e, 0x2e, 0x9c, 0xd2, - 0xc1, 0xf0, 0x73, 0xd8, 0x5c, 0x82, 0x88, 0x52, 0x61, 0x67, 0xef, 0xa5, 0xcf, 0xbe, 0x4f, 0x04, - 0x0d, 0x79, 0xf7, 0xc9, 0xb1, 0x05, 0x79, 0xdb, 0x6f, 0xab, 0x2d, 0x65, 0x9d, 0xcd, 0x3c, 0x2e, - 0xa0, 0x5d, 0xa8, 0x78, 0xf8, 0x76, 0x9e, 0xdc, 0x03, 0x16, 0x0f, 0x78, 0xf8, 0x56, 0x84, 0xdd, - 0x2c, 0x45, 0x9c, 0x6c, 0x7c, 0x2d, 0x41, 0xae, 0xdf, 0x1e, 0x86, 0x49, 0xa0, 0xa5, 0x15, 0x40, - 0x67, 0x52, 0x40, 0x3f, 0x06, 0x3a, 0xd5, 0xf4, 0xa9, 0x4f, 0x4c, 0x46, 0x81, 0x9c, 0x56, 0xb4, - 0xb0, 0x3f, 0xf2, 0x09, 0xe3, 0xd5, 0xd5, 0x8d, 0x6b, 0x7c, 0xa9, 0x5f, 0x13, 0xdb, 0xba, 0xe6, - 0x34, 0xc8, 0x69, 0x15, 0xa6, 0xfb, 0x8c, 0xa9, 0x98, 0xd7, 0x00, 0x07, 0xd3, 0x68, 0x9c, 0x09, - 0x89, 0x22, 0x4d, 0x3c, 0xcf, 0xf5, 0xf4, 0xb1, 0x6f, 0x45, 0x48, 0x33, 0xc5, 0xe7, 0xbe, 0xd5, - 0xfc, 0x20, 0xd9, 0x4a, 0xd5, 0xc4, 0x1c, 0x37, 0xf4, 0x20, 0x6c, 0xfc, 0x59, 0x82, 0xcd, 0xde, - 0x34, 0x60, 0xd4, 0xee, 0x5d, 0xf9, 0xc4, 0x9b, 0xf1, 0x69, 0xa2, 0x40, 0xd1, 0x9f, 0x1a, 0x06, - 0xf1, 0x7d, 0x96, 0x59, 0x49, 0x8b, 0xc4, 0x7b, 0x71, 0x66, 0xee, 0xc7, 0x99, 0x28, 0x4b, 0x36, - 0x55, 0x96, 0x54, 0xa0, 0xb9, 0x74, 0xa0, 0x68, 0x0f, 0xd6, 0x69, 0x6d, 0xe8, 0xd4, 0x66, 0xf5, - 0xe1, 0x64, 0x07, 0x0b, 0xfb, 0x67, 0x84, 0xd0, 0x12, 0x35, 0x9f, 0x45, 0x69, 0xd4, 0xe3, 0x34, - 0x5c, 0x11, 0xbc, 0xee, 0xc6, 0xd1, 0x37, 0xc6, 0x50, 0xed, 0x79, 0xb6, 0x65, 0x3b, 0x38, 0xb0, - 0x1d, 0xab, 0x6f, 0xbc, 0x0f, 0xaa, 0x54, 0x9b, 0x64, 0xd2, 0x6d, 0xd2, 0xfc, 0x68, 0xc9, 0x0c, - 0x72, 0x63, 0xcf, 0x3a, 0x2f, 0xe2, 0x6f, 0x8b, 0x00, 0x51, 0x11, 0x87, 0x21, 0xe5, 0xb7, 0x49, - 0xfc, 0x80, 0xd9, 0xb8, 0x4e, 0x6a, 0x4c, 0xc8, 0x89, 0x05, 0x3e, 0x2b, 0x52, 0xad, 0x9f, 0x59, - 0xdd, 0xfa, 0xd9, 0x54, 0xeb, 0x1f, 0xc1, 0x26, 0x09, 0x03, 0xe2, 0x39, 0x74, 0x12, 0xc6, 0x33, - 0x80, 0x97, 0xb4, 0x16, 0x2d, 0xb5, 0xe6, 0xb3, 0xe0, 0x00, 0xe4, 0x89, 0x67, 0xbc, 0xf8, 0x5e, - 0xd2, 0x98, 0xd7, 0x77, 0x83, 0xe9, 0x63, 0xcb, 0x98, 0xb9, 0x85, 0x14, 0x73, 0x15, 0x28, 0x46, - 0xad, 0xc1, 0x19, 0x16, 0x89, 0xe9, 0xab, 0xbb, 0xb4, 0x70, 0x75, 0x27, 0xe6, 0x4c, 0xf9, 0x3b, - 0xcc, 0x99, 0x17, 0x90, 0x67, 0x25, 0x65, 0xe3, 0xa0, 0xf2, 0x62, 0x27, 0x6d, 0xbc, 0x80, 0xa9, - 0x96, 0x9b, 0x50, 0x64, 0x4f, 0xa0, 0xc2, 0xb1, 0x27, 0x26, 0xdd, 0xc9, 0x27, 0xc3, 0xfe, 0xc2, - 0xce, 0xfb, 0x14, 0xd7, 0x20, 0xda, 0x35, 0x0c, 0xe9, 0xc3, 0xc5, 0x36, 0xd9, 0x3c, 0x28, 0x6b, - 0x19, 0xdb, 0x44, 0x3f, 0x86, 0xea, 0x9c, 0x58, 0xa2, 0xe5, 0x1e, 0x2c, 0x0b, 0x7f, 0xc0, 0xd6, - 0xb4, 0x8d, 0xc8, 0x98, 0xcb, 0xab, 0x86, 0xd6, 0xc6, 0x7f, 0x30, 0xb4, 0x4e, 0x60, 0x73, 0x62, - 0xe8, 0xc2, 0x2b, 0xdf, 0x4f, 0x9f, 0x01, 0x55, 0xe6, 0x12, 0xa5, 0x5d, 0xd2, 0xd9, 0xa4, 0xd5, - 0x26, 0x06, 0x77, 0xad, 0x46, 0xc6, 0x11, 0x52, 0x13, 0xcf, 0x36, 0x88, 0x22, 0xcf, 0x91, 0xea, - 0x53, 0x99, 0x36, 0x88, 0x68, 0x3f, 0xa5, 0xc6, 0x91, 0xe7, 0x9d, 0x37, 0x3f, 0x99, 0x5d, 0xa8, - 0xf1, 0xc9, 0xe8, 0xfd, 0x27, 0x53, 0xeb, 0xf8, 0xe4, 0x43, 0xa8, 0x09, 0x07, 0xfe, 0x2d, 0x9e, - 0xe8, 0xac, 0xe7, 0x95, 0x4d, 0x76, 0x4c, 0x95, 0x2f, 0x0c, 0x6e, 0xf1, 0x44, 0xa5, 0xea, 0x28, - 0xca, 0xc0, 0xfd, 0x92, 0x38, 0xca, 0xd6, 0x3c, 0xca, 0x21, 0x95, 0xe9, 0xf4, 0xc1, 0x57, 0xae, - 0x17, 0xe8, 0x1e, 0xc1, 0xbe, 0xeb, 0x28, 0xdb, 0xfc, 0xf6, 0x65, 0x3a, 0x8d, 0xa9, 0x12, 0x73, - 0xfa, 0x9f, 0x12, 0x54, 0xe6, 0xf7, 0xd1, 0x1c, 0x65, 0x69, 0x8e, 0xf2, 0x0f, 0x00, 0x6c, 0x7e, - 0xab, 0x53, 0xe2, 0x64, 0x58, 0x42, 0xdb, 0xe9, 0x84, 0xc4, 0xad, 0xaf, 0x95, 0x85, 0xe1, 0x30, - 0x44, 0xcf, 0x22, 0x8e, 0x66, 0xf7, 0xb2, 0x2b, 0x2a, 0xc0, 0x89, 0xf9, 0x23, 0xa8, 0xcc, 0x49, - 0x14, 0x84, 0x4a, 0x8e, 0x99, 0x2b, 0xcb, 0x89, 0x39, 0x0c, 0x35, 0x70, 0xe3, 0x11, 0xb2, 0x0f, - 0xeb, 0x11, 0xd4, 0xac, 0x54, 0xbc, 0x17, 0x2b, 0x5c, 0xc7, 0xca, 0xd4, 0xfc, 0x30, 0x39, 0xd7, - 0x1f, 0x2e, 0x7b, 0x22, 0x05, 0x61, 0xe3, 0xf7, 0x59, 0x78, 0x20, 0x52, 0xb8, 0x20, 0x16, 0x36, - 0xee, 0xfe, 0x47, 0x9e, 0x2f, 0xcd, 0xc5, 0xe7, 0xcb, 0xfe, 0x52, 0xd8, 0xf8, 0x74, 0xe1, 0x99, - 0xff, 0xf7, 0xdf, 0x32, 0xcd, 0xfa, 0xeb, 0x37, 0xbb, 0x52, 0x04, 0x59, 0x2d, 0x86, 0x4c, 0x70, - 0xab, 0xf1, 0x27, 0x09, 0xe4, 0x98, 0x10, 0x02, 0xb0, 0x7f, 0xeb, 0x36, 0x59, 0x09, 0x5d, 0x0a, - 0xa2, 0xec, 0x6a, 0x88, 0x72, 0xef, 0x81, 0x28, 0xbf, 0x00, 0x51, 0xb3, 0x91, 0xcc, 0x67, 0x7b, - 0xc9, 0x9d, 0x1c, 0x84, 0x8d, 0xdf, 0x65, 0xa0, 0x96, 0xe8, 0x41, 0x91, 0x54, 0x33, 0xd5, 0x79, - 0x12, 0x2b, 0xfe, 0xd3, 0xa5, 0x10, 0x0a, 0xf0, 0x96, 0xf5, 0x5f, 0xe6, 0x5b, 0xfa, 0xef, 0x27, - 0xe9, 0xfe, 0xcb, 0x2e, 0x83, 0x78, 0xb1, 0xdc, 0xa9, 0x2e, 0xfc, 0x29, 0xc4, 0x80, 0x47, 0xd7, - 0x40, 0x8e, 0xd1, 0x6d, 0x77, 0x05, 0x51, 0x86, 0xa1, 0xb8, 0x11, 0xaa, 0xf3, 0x8d, 0x5c, 0xc1, - 0xda, 0x55, 0xfa, 0x96, 0x76, 0x3d, 0x3c, 0x07, 0x79, 0xf1, 0xff, 0x8e, 0xe8, 0x21, 0x20, 0xdf, - 0xb6, 0x1c, 0x62, 0x26, 0x57, 0xe4, 0x35, 0xf4, 0x14, 0x1e, 0x4d, 0xe3, 0x63, 0x53, 0x8b, 0xd2, - 0xe1, 0x2f, 0xd3, 0x55, 0x17, 0xd7, 0xd2, 0x87, 0xb0, 0x3b, 0xea, 0x76, 0x2e, 0x55, 0x6d, 0xd0, - 0xba, 0xd0, 0x87, 0x2f, 0xf5, 0xc1, 0xb0, 0x35, 0x1c, 0x0d, 0xf4, 0x51, 0x77, 0xd0, 0x57, 0xdb, - 0x9d, 0xb3, 0x8e, 0x7a, 0x2a, 0xaf, 0xa1, 0x4d, 0xa8, 0x76, 0xba, 0x27, 0xbd, 0x51, 0xf7, 0x54, - 0x1f, 0x8c, 0xda, 0x6d, 0x75, 0x30, 0x90, 0x25, 0xb4, 0x03, 0x8f, 0xfb, 0x6a, 0xf7, 0xb4, 0xd3, - 0x3d, 0xd7, 0xa3, 0x45, 0xf5, 0xa5, 0xda, 0x1e, 0x0d, 0x3b, 0xbd, 0xae, 0x9c, 0x41, 0x8f, 0x60, - 0xb3, 0xdf, 0x16, 0x1a, 0x35, 0xde, 0x97, 0xa5, 0xc1, 0x27, 0x17, 0xce, 0x5a, 0x9d, 0x0b, 0xf5, - 0x54, 0xce, 0xa1, 0x6d, 0xa8, 0xf5, 0xdb, 0x7a, 0xe4, 0x52, 0x53, 0x2f, 0x55, 0x6d, 0x28, 0xe7, - 0xd1, 0x16, 0xc8, 0xbd, 0xd1, 0x90, 0xfb, 0x17, 0x8b, 0x72, 0x21, 0xa5, 0x8d, 0x5c, 0x17, 0x69, - 0x9c, 0x73, 0xad, 0xf0, 0x5b, 0x42, 0xeb, 0x50, 0x6a, 0xb7, 0xba, 0x6d, 0x95, 0x4a, 0xe5, 0xc3, - 0x1e, 0x14, 0x44, 0xe6, 0x55, 0xa8, 0xa4, 0xb3, 0xac, 0x40, 0x31, 0x3a, 0x40, 0xa2, 0xbb, 0x7a, - 0x27, 0x03, 0x55, 0xbb, 0x54, 0x4f, 0xe5, 0x0c, 0x95, 0x78, 0x40, 0xea, 0xa9, 0x9c, 0xa5, 0x86, - 0xad, 0x93, 0x1e, 0x13, 0x72, 0x87, 0xbf, 0x96, 0xa0, 0xc0, 0x67, 0x0a, 0x42, 0xb0, 0x91, 0xf0, - 0xa8, 0x0f, 0x5f, 0xca, 0x6b, 0xa8, 0x08, 0xd9, 0xf3, 0x16, 0x2d, 0xd7, 0x26, 0x54, 0xcf, 0x5b, - 0x03, 0xbd, 0x45, 0xd3, 0x68, 0x7d, 0x71, 0xd1, 0x6b, 0x51, 0xbf, 0x65, 0xc8, 0x9f, 0x8d, 0xba, - 0xa7, 0xb4, 0x2c, 0xdb, 0x50, 0x63, 0x9f, 0x29, 0x8b, 0x1c, 0x0b, 0x4a, 0x08, 0x79, 0x7a, 0x40, - 0x54, 0x6a, 0x51, 0x9f, 0x02, 0x92, 0x61, 0x5d, 0x53, 0x07, 0xed, 0x91, 0xaa, 0x73, 0x4f, 0xc5, - 0xc3, 0x3f, 0x4a, 0xb0, 0xb9, 0x64, 0xd8, 0xa1, 0x7d, 0xd8, 0x89, 0x76, 0x5f, 0xa8, 0xe7, 0xad, - 0xf6, 0x17, 0xfa, 0xbd, 0x68, 0x1f, 0x02, 0x5a, 0x30, 0xe1, 0xc1, 0x2b, 0xb0, 0xb5, 0xa0, 0xe7, - 0x87, 0x65, 0xd0, 0x47, 0xb0, 0xb7, 0x6c, 0x25, 0x95, 0x45, 0x16, 0x35, 0xa0, 0x7e, 0xdf, 0x6f, - 0x3a, 0xd3, 0x93, 0xfe, 0xd7, 0x6f, 0xeb, 0xd2, 0x37, 0x6f, 0xeb, 0xd2, 0xdf, 0xde, 0xd6, 0xa5, - 0xdf, 0xbc, 0xab, 0xaf, 0x7d, 0xf3, 0xae, 0xbe, 0xf6, 0x97, 0x77, 0xf5, 0xb5, 0x5f, 0x7c, 0x6a, - 0xd9, 0xc1, 0xf5, 0xf4, 0xea, 0xc8, 0x70, 0xc7, 0xc7, 0x93, 0xa9, 0x7f, 0xcd, 0xe6, 0x1d, 0xfb, - 0x7a, 0xce, 0x3e, 0x9f, 0x3b, 0xae, 0x49, 0x8e, 0xc3, 0xe3, 0xb8, 0x83, 0xd8, 0x8f, 0x62, 0x57, - 0x05, 0xf6, 0xf3, 0xd6, 0xf7, 0xff, 0x15, 0x00, 0x00, 0xff, 0xff, 0x8e, 0xb9, 0x1d, 0xbf, 0x31, - 0x13, 0x00, 0x00, + // 1897 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x58, 0xcd, 0x6f, 0xe3, 0xc6, + 0x15, 0x37, 0xf5, 0xad, 0x27, 0xaf, 0x45, 0x8d, 0xed, 0x0d, 0x77, 0x37, 0x96, 0x6d, 0x25, 0xe9, + 0x1a, 0x0e, 0xd6, 0x6e, 0xb6, 0x6d, 0x80, 0x0a, 0x28, 0x0a, 0x59, 0xa6, 0x1d, 0xb5, 0x8e, 0xa4, + 0x52, 0x92, 0xb1, 0xe9, 0x85, 0x18, 0x93, 0xb3, 0x32, 0x11, 0x8b, 0x14, 0x38, 0x94, 0x4d, 0x9f, + 0x7b, 0x2b, 0x02, 0xb4, 0x97, 0x02, 0x7b, 0xdc, 0x73, 0x7b, 0xe9, 0xa1, 0x7f, 0x44, 0x8e, 0x29, + 0x7a, 0x29, 0xd0, 0x4b, 0xb1, 0x7b, 0x68, 0xff, 0x8b, 0x16, 0xf3, 0x41, 0x91, 0x94, 0xe5, 0x4d, + 0x8a, 0x1e, 0x0a, 0xf4, 0xb2, 0x9e, 0xf7, 0xe6, 0xcd, 0x9b, 0xdf, 0xfb, 0xfa, 0x0d, 0x57, 0xa0, + 0xcd, 0x48, 0x48, 0xac, 0x59, 0xe0, 0xf9, 0x87, 0xd7, 0x9f, 0x1c, 0x06, 0xb7, 0x53, 0x42, 0x0f, + 0xa6, 0xbe, 0x17, 0x78, 0x68, 0x75, 0xbe, 0x73, 0x70, 0xfd, 0xc9, 0xe3, 0x8d, 0xb1, 0x37, 0xf6, + 0xf8, 0xc6, 0x21, 0x5b, 0x09, 0x9b, 0xc7, 0x35, 0x3c, 0x71, 0x5c, 0xef, 0x90, 0xff, 0x2b, 0x54, + 0x0d, 0x0a, 0x85, 0x3e, 0xf6, 0xf1, 0x84, 0xa2, 0x2d, 0x00, 0xea, 0x4d, 0x88, 0x79, 0x8d, 0xaf, + 0x66, 0x44, 0xcb, 0xec, 0x28, 0x7b, 0x25, 0xa3, 0xcc, 0x34, 0xe7, 0x4c, 0x81, 0x3e, 0x06, 0x34, + 0xc1, 0xa1, 0x39, 0xc6, 0xf4, 0x8a, 0x50, 0x6a, 0x06, 0x7c, 0xa9, 0x65, 0x77, 0x94, 0xbd, 0x9c, + 0x51, 0x9d, 0xe0, 0xf0, 0x54, 0x6c, 0x0c, 0xd9, 0xa2, 0xb9, 0xf5, 0xea, 0xf5, 0xf6, 0xca, 0x3f, + 0x5f, 0x6f, 0x2b, 0xbf, 0xfe, 0xc7, 0x1f, 0xf7, 0xd5, 0x18, 0xf3, 0x94, 0x5f, 0xd5, 0xf8, 0x5b, + 0x06, 0xd4, 0x91, 0xeb, 0x5c, 0x13, 0x9f, 0xe2, 0xab, 0x3e, 0xbe, 0xbd, 0xf2, 0xb0, 0x8d, 0xd6, + 0x20, 0x13, 0x78, 0x9a, 0xb2, 0xa3, 0xec, 0x95, 0x8d, 0x4c, 0xe0, 0xa1, 0x0d, 0xc8, 0xc7, 0x50, + 0xca, 0x86, 0x10, 0x10, 0x82, 0x9c, 0x8d, 0x03, 0xcc, 0x2f, 0x2e, 0x1b, 0x7c, 0x8d, 0x9e, 0x40, + 0x79, 0x8c, 0xa9, 0x79, 0xe5, 0x4c, 0x9c, 0x40, 0xcb, 0xf1, 0x8d, 0xd2, 0x18, 0xd3, 0x33, 0x26, + 0xa3, 0x8f, 0x80, 0xa1, 0x33, 0x5f, 0x12, 0x62, 0x4e, 0x89, 0xcf, 0x41, 0xe7, 0xb9, 0xc9, 0xea, + 0x04, 0x87, 0x27, 0x84, 0xf4, 0x89, 0x7f, 0x8a, 0x29, 0xfa, 0x14, 0x34, 0x66, 0x36, 0xf5, 0x1d, + 0xcf, 0x77, 0x82, 0xdb, 0x94, 0x7d, 0x81, 0xdb, 0x6f, 0x4c, 0x70, 0xd8, 0x97, 0xdb, 0xf1, 0xb9, + 0x0d, 0xc8, 0xbb, 0x9e, 0x6b, 0x11, 0xad, 0x28, 0x50, 0x72, 0x01, 0x3d, 0x86, 0x92, 0x4d, 0xb0, + 0x7d, 0xe5, 0xb8, 0x44, 0x2b, 0x09, 0x40, 0x91, 0x8c, 0x7e, 0x04, 0x85, 0x6b, 0x93, 0x55, 0x4e, + 0x2b, 0xef, 0x28, 0x7b, 0x6b, 0xcf, 0xeb, 0x07, 0xc9, 0xca, 0x1d, 0x9c, 0x13, 0xdf, 0x79, 0xe9, + 0x58, 0x38, 0x70, 0x3c, 0x77, 0x78, 0x3b, 0x25, 0x46, 0xfe, 0x9a, 0xfd, 0x69, 0xee, 0x25, 0x53, + 0xfa, 0x24, 0x4e, 0xe9, 0x2c, 0xca, 0xa3, 0x39, 0x15, 0x89, 0x6c, 0x7c, 0xa5, 0x80, 0xfa, 0xb9, + 0x33, 0xf6, 0xb9, 0x8b, 0x28, 0xbb, 0xef, 0x43, 0x79, 0x12, 0xe9, 0x64, 0x92, 0x63, 0x45, 0x1c, + 0x45, 0xe6, 0xbe, 0x28, 0xb2, 0xe9, 0x28, 0xee, 0x85, 0x33, 0xf7, 0x39, 0x87, 0xf3, 0x4a, 0x01, + 0x34, 0x2f, 0x76, 0xcb, 0xb2, 0xbc, 0x99, 0x1b, 0x74, 0x6c, 0xf4, 0x14, 0xaa, 0xd6, 0x25, 0x76, + 0x5c, 0xd3, 0xc5, 0x13, 0x42, 0xa7, 0xd8, 0x22, 0x12, 0xd6, 0x1a, 0x57, 0x77, 0x23, 0x2d, 0x7a, + 0x04, 0x25, 0x61, 0xe8, 0xd8, 0x12, 0x5e, 0x91, 0xcb, 0x1d, 0x9b, 0xc1, 0xf6, 0x6e, 0x5c, 0xe2, + 0x4b, 0x74, 0x42, 0xf8, 0x0e, 0x99, 0xc2, 0x02, 0x45, 0xc3, 0x02, 0x64, 0x90, 0x6b, 0xe2, 0x07, + 0x1d, 0x97, 0x06, 0xfe, 0xcc, 0x62, 0xb8, 0x29, 0xfa, 0x08, 0xd6, 0x5e, 0xce, 0x5c, 0xdb, 0xf4, + 0x89, 0xe5, 0x4c, 0x1d, 0xe2, 0x06, 0x12, 0xd8, 0x03, 0xa6, 0x35, 0x22, 0x65, 0xf3, 0x7b, 0xd1, + 0x15, 0x5b, 0xf1, 0x15, 0x3e, 0xf7, 0x66, 0x3a, 0x09, 0x77, 0x8d, 0xaf, 0x72, 0x50, 0xec, 0xb8, + 0x17, 0xde, 0xcc, 0xb5, 0xd1, 0x2e, 0xac, 0x52, 0x6f, 0xe6, 0x5b, 0xc4, 0xe4, 0x21, 0x48, 0xc7, + 0x15, 0xa1, 0x6b, 0x33, 0x15, 0x7a, 0x0f, 0x8a, 0x41, 0x68, 0x5e, 0x62, 0x7a, 0x29, 0xa3, 0x2d, + 0x04, 0xe1, 0x67, 0x98, 0x5e, 0xa2, 0x87, 0x50, 0xa0, 0xc4, 0xb5, 0xe7, 0xd1, 0x4a, 0x89, 0x55, + 0x36, 0x46, 0x2a, 0xba, 0x3f, 0x56, 0xb0, 0x53, 0x78, 0xc2, 0x82, 0x95, 0x5d, 0x2f, 0x25, 0x36, + 0xed, 0x98, 0x52, 0x12, 0x98, 0xd8, 0xb6, 0x7d, 0xd9, 0xe1, 0x65, 0xae, 0x69, 0xd9, 0xb6, 0xcf, + 0x46, 0xea, 0xca, 0x1b, 0x9b, 0x8e, 0x6b, 0x93, 0x50, 0xb6, 0x76, 0xe9, 0xca, 0x1b, 0x77, 0x98, + 0x8c, 0x9e, 0x71, 0x88, 0xbc, 0x85, 0x4b, 0xbc, 0x85, 0x37, 0xd2, 0x2d, 0x3c, 0x0c, 0x79, 0xe3, + 0x16, 0x02, 0xfe, 0x17, 0xfd, 0x1c, 0x6a, 0x77, 0x9a, 0x94, 0xf7, 0x7e, 0x65, 0xb1, 0xf7, 0x17, + 0x39, 0xc1, 0x50, 0x67, 0x8b, 0x2c, 0xf1, 0x31, 0xd4, 0xae, 0x13, 0x13, 0x62, 0x72, 0x32, 0x00, + 0x0e, 0x50, 0x4d, 0x6e, 0x1c, 0x33, 0x62, 0xf8, 0x05, 0xac, 0x2f, 0xa9, 0x88, 0x56, 0xe1, 0x77, + 0xef, 0xa4, 0xef, 0xbe, 0xdb, 0x08, 0x06, 0xf2, 0xef, 0x36, 0xc7, 0x06, 0xe4, 0x1d, 0xda, 0xd6, + 0x5b, 0xda, 0x2a, 0x27, 0x48, 0x21, 0xa0, 0x6d, 0xa8, 0xf8, 0xf8, 0x66, 0x1e, 0xdc, 0x03, 0x8e, + 0x07, 0x7c, 0x7c, 0x23, 0x61, 0x37, 0x4b, 0x51, 0x4f, 0x36, 0xbe, 0x56, 0x20, 0xd7, 0x6f, 0x0f, + 0xc3, 0x64, 0xa1, 0x95, 0x7b, 0x0a, 0x9d, 0x49, 0x15, 0xfa, 0x11, 0x30, 0x56, 0x33, 0x67, 0x94, + 0xd8, 0x92, 0x77, 0x8b, 0x63, 0x4c, 0x47, 0x94, 0xf0, 0xbe, 0xba, 0xb8, 0xf2, 0xac, 0x2f, 0xcd, + 0x4b, 0xe2, 0x8c, 0x2f, 0x45, 0x1b, 0xe4, 0x8c, 0x0a, 0xd7, 0x7d, 0xc6, 0x55, 0xdc, 0x6b, 0x80, + 0x83, 0x59, 0x44, 0x67, 0x52, 0x62, 0x95, 0x26, 0xbe, 0xef, 0xf9, 0xe6, 0x84, 0x8e, 0xa3, 0x4a, + 0x73, 0xc5, 0xe7, 0x74, 0xdc, 0x7c, 0x3f, 0x39, 0x4a, 0xd5, 0x04, 0x8f, 0x5b, 0x66, 0x10, 0x36, + 0xfe, 0xa2, 0xc0, 0x7a, 0x6f, 0x16, 0xf0, 0xd6, 0xee, 0x5d, 0x50, 0xe2, 0x5f, 0x0b, 0x36, 0xd1, + 0xa0, 0x48, 0x67, 0x96, 0x45, 0x28, 0xe5, 0x91, 0x95, 0x8c, 0x48, 0xbc, 0x83, 0x33, 0x73, 0x17, + 0x67, 0x22, 0x2d, 0xd9, 0x54, 0x5a, 0x52, 0x40, 0x73, 0x69, 0xa0, 0x68, 0x07, 0x56, 0x59, 0x6e, + 0x18, 0x6b, 0xf3, 0xfc, 0x88, 0x66, 0x87, 0x31, 0xa6, 0x27, 0x84, 0xb0, 0x14, 0x35, 0x9f, 0x46, + 0x61, 0xd4, 0xe3, 0x30, 0x3c, 0x09, 0xde, 0xf4, 0x62, 0xf4, 0x8d, 0x09, 0x54, 0x7b, 0xbe, 0x33, + 0x76, 0x5c, 0x1c, 0x38, 0xee, 0xb8, 0x6f, 0xbd, 0xab, 0x54, 0xa9, 0x31, 0xc9, 0xa4, 0xc7, 0xa4, + 0xf9, 0xe1, 0x12, 0x0e, 0xf2, 0x62, 0xcf, 0xa6, 0x48, 0xe2, 0xef, 0x8a, 0x00, 0x51, 0x12, 0x87, + 0x21, 0xeb, 0x6f, 0x9b, 0xd0, 0x80, 0xdb, 0x78, 0x6e, 0x8a, 0x26, 0xd4, 0xc4, 0x86, 0xe0, 0x8a, + 0xd4, 0xe8, 0x67, 0xee, 0x1f, 0xfd, 0x6c, 0x6a, 0xf4, 0x0f, 0x60, 0x9d, 0x84, 0x01, 0xf1, 0x5d, + 0xc6, 0x84, 0x31, 0x07, 0x88, 0x94, 0xd6, 0xa2, 0xad, 0xd6, 0x9c, 0x0b, 0xf6, 0x40, 0x9d, 0xfa, + 0xd6, 0xf3, 0xef, 0x27, 0x8d, 0x45, 0x7e, 0xd7, 0xb8, 0x3e, 0xb6, 0x8c, 0x3b, 0xb7, 0x90, 0xea, + 0x5c, 0x0d, 0x8a, 0xd1, 0x68, 0x88, 0x0e, 0x8b, 0xc4, 0xf4, 0xd3, 0x5d, 0x5a, 0x78, 0xba, 0x13, + 0x3c, 0x53, 0xfe, 0x0e, 0x3c, 0xf3, 0x1c, 0xf2, 0x3c, 0xa5, 0x9c, 0x0e, 0x2a, 0xcf, 0xb7, 0xd2, + 0xc6, 0x0b, 0x35, 0x35, 0x72, 0x53, 0x56, 0xd9, 0x23, 0xa8, 0x88, 0xda, 0x13, 0x9b, 0x9d, 0x14, + 0xcc, 0xb0, 0xbb, 0x70, 0xf2, 0x6e, 0x8b, 0x1b, 0x10, 0x9d, 0x1a, 0x86, 0xec, 0xc3, 0xc5, 0xb1, + 0x39, 0x1f, 0x94, 0x8d, 0x8c, 0x63, 0xa3, 0x9f, 0x40, 0x75, 0xde, 0x58, 0x72, 0xe4, 0x1e, 0x2c, + 0x83, 0x3f, 0xe0, 0x7b, 0xc6, 0x5a, 0x64, 0x2c, 0xe4, 0xfb, 0x48, 0x6b, 0xed, 0xbf, 0x20, 0xad, + 0x23, 0x58, 0x9f, 0x5a, 0xa6, 0xf4, 0x2a, 0xce, 0xb3, 0xcf, 0x80, 0x2a, 0x77, 0x89, 0xd2, 0x2e, + 0x19, 0x37, 0x19, 0xb5, 0xa9, 0x25, 0x5c, 0xeb, 0x91, 0x71, 0x54, 0xa9, 0xa9, 0xef, 0x58, 0x44, + 0x53, 0xe7, 0x95, 0xea, 0x33, 0x99, 0x0d, 0x88, 0x1c, 0x3f, 0xad, 0x26, 0x2a, 0x2f, 0x26, 0x6f, + 0x7e, 0x33, 0x7f, 0x50, 0xe3, 0x9b, 0xd1, 0xbb, 0x6f, 0x66, 0xd6, 0xf1, 0xcd, 0xfb, 0x50, 0x93, + 0x0e, 0xe8, 0x0d, 0x9e, 0x9a, 0x7c, 0xe6, 0xb5, 0x75, 0x7e, 0x4d, 0x55, 0x6c, 0x0c, 0x6e, 0xf0, + 0x54, 0x67, 0xea, 0x08, 0x65, 0xe0, 0x7d, 0x49, 0x5c, 0x6d, 0x63, 0x8e, 0x72, 0xc8, 0x64, 0xc6, + 0x3e, 0xf8, 0xc2, 0xf3, 0x03, 0xd3, 0x27, 0x98, 0x7a, 0xae, 0xb6, 0x29, 0x5e, 0x5f, 0xae, 0x33, + 0xb8, 0x2a, 0xc1, 0xd3, 0xff, 0x52, 0xa0, 0x32, 0x7f, 0x8f, 0xe6, 0x55, 0x56, 0xe6, 0x55, 0xfe, + 0x21, 0x80, 0x23, 0x5e, 0x75, 0xd6, 0x38, 0x19, 0x1e, 0xd0, 0x66, 0x3a, 0x20, 0xf9, 0xea, 0x1b, + 0x65, 0x69, 0x38, 0x0c, 0xd1, 0xd3, 0xa8, 0x47, 0xb3, 0x3b, 0xd9, 0x7b, 0x32, 0x20, 0x1a, 0xf3, + 0xc7, 0x50, 0x99, 0x37, 0x51, 0x10, 0x6a, 0x39, 0x6e, 0xae, 0x2d, 0x6f, 0xcc, 0x61, 0x68, 0x80, + 0x17, 0x53, 0xc8, 0x2e, 0xac, 0x46, 0xa5, 0xe6, 0xa9, 0x12, 0xb3, 0x58, 0x11, 0x3a, 0x9e, 0xa6, + 0xe6, 0x07, 0x49, 0x5e, 0x7f, 0xb8, 0xec, 0x13, 0x29, 0x08, 0x1b, 0x7f, 0xc8, 0xc2, 0x03, 0x19, + 0xc2, 0x19, 0x19, 0x63, 0xeb, 0xf6, 0xff, 0xe4, 0xf3, 0xa5, 0xb9, 0xf8, 0xf9, 0xb2, 0xbb, 0xb4, + 0x6c, 0x82, 0x5d, 0x44, 0xe4, 0xff, 0xfb, 0x6f, 0x99, 0x66, 0xfd, 0xd5, 0xeb, 0x6d, 0x25, 0x2a, + 0x59, 0x2d, 0x2e, 0x99, 0xec, 0xad, 0xc6, 0x9f, 0x15, 0x50, 0xe3, 0x86, 0x90, 0x05, 0xfb, 0x8f, + 0x5e, 0x93, 0x7b, 0x4b, 0x97, 0x2a, 0x51, 0xf6, 0xfe, 0x12, 0xe5, 0xde, 0x51, 0xa2, 0xfc, 0x42, + 0x89, 0x9a, 0x8d, 0x64, 0x3c, 0x9b, 0x4b, 0xde, 0xe4, 0x20, 0x6c, 0xfc, 0x3e, 0x03, 0xb5, 0xc4, + 0x0c, 0xca, 0xa0, 0x9a, 0xa9, 0xc9, 0x53, 0x78, 0xf2, 0x9f, 0x2c, 0x2d, 0xa1, 0x2c, 0xde, 0xb2, + 0xf9, 0xcb, 0x7c, 0xcb, 0xfc, 0xfd, 0x34, 0x3d, 0x7f, 0xd9, 0x65, 0x25, 0x5e, 0x4c, 0x77, 0x6a, + 0x0a, 0x7f, 0x06, 0x71, 0xc1, 0xa3, 0x67, 0x20, 0xc7, 0xdb, 0x6d, 0xfb, 0x9e, 0x46, 0x19, 0x86, + 0xf2, 0x45, 0xa8, 0xce, 0x0f, 0x0a, 0x05, 0x1f, 0x57, 0xe5, 0x5b, 0xc6, 0x75, 0xff, 0x14, 0xd4, + 0xc5, 0xff, 0x3b, 0xa2, 0x87, 0x80, 0xa8, 0x33, 0x76, 0x89, 0x9d, 0xdc, 0x51, 0x57, 0xd0, 0x13, + 0x78, 0x6f, 0x16, 0x5f, 0x9b, 0xda, 0x54, 0xf6, 0x7f, 0x95, 0xce, 0xba, 0x7c, 0x96, 0x3e, 0x80, + 0xed, 0x51, 0xb7, 0x73, 0xae, 0x1b, 0x83, 0xd6, 0x99, 0x39, 0x7c, 0x61, 0x0e, 0x86, 0xad, 0xe1, + 0x68, 0x60, 0x8e, 0xba, 0x83, 0xbe, 0xde, 0xee, 0x9c, 0x74, 0xf4, 0x63, 0x75, 0x05, 0xad, 0x43, + 0xb5, 0xd3, 0x3d, 0xea, 0x8d, 0xba, 0xc7, 0xe6, 0x60, 0xd4, 0x6e, 0xeb, 0x83, 0x81, 0xaa, 0xa0, + 0x2d, 0x78, 0xd4, 0xd7, 0xbb, 0xc7, 0x9d, 0xee, 0xa9, 0x19, 0x6d, 0xea, 0x2f, 0xf4, 0xf6, 0x68, + 0xd8, 0xe9, 0x75, 0xd5, 0x0c, 0x7a, 0x0f, 0xd6, 0xfb, 0x6d, 0xa9, 0xd1, 0xe3, 0x73, 0x59, 0x06, + 0x3e, 0xb9, 0x71, 0xd2, 0xea, 0x9c, 0xe9, 0xc7, 0x6a, 0x0e, 0x6d, 0x42, 0xad, 0xdf, 0x36, 0x23, + 0x97, 0x86, 0x7e, 0xae, 0x1b, 0x43, 0x35, 0x8f, 0x36, 0x40, 0xed, 0x8d, 0x86, 0xc2, 0xbf, 0xdc, + 0x54, 0x0b, 0x29, 0x6d, 0xe4, 0xba, 0xc8, 0x70, 0xce, 0xb5, 0xd2, 0x6f, 0x09, 0xad, 0x42, 0xa9, + 0xdd, 0xea, 0xb6, 0x75, 0x26, 0x95, 0xf7, 0x7b, 0x50, 0x90, 0x91, 0x57, 0xa1, 0x92, 0x8e, 0xb2, + 0x02, 0xc5, 0xe8, 0x02, 0x85, 0x9d, 0xea, 0x1d, 0x0d, 0x74, 0xe3, 0x5c, 0x3f, 0x56, 0x33, 0x4c, + 0x12, 0x80, 0xf4, 0x63, 0x35, 0xcb, 0x0c, 0x5b, 0x47, 0x3d, 0x2e, 0xe4, 0xf6, 0x7f, 0xa3, 0x40, + 0x41, 0x70, 0x0a, 0x42, 0xb0, 0x96, 0xf0, 0x68, 0x0e, 0x5f, 0xa8, 0x2b, 0xa8, 0x08, 0xd9, 0xd3, + 0x16, 0x4b, 0xd7, 0x3a, 0x54, 0x4f, 0x5b, 0x03, 0xb3, 0xc5, 0xc2, 0x68, 0x7d, 0x71, 0xd6, 0x6b, + 0x31, 0xbf, 0x65, 0xc8, 0x9f, 0x8c, 0xba, 0xc7, 0x2c, 0x2d, 0x9b, 0x50, 0xe3, 0xcb, 0x94, 0x45, + 0x8e, 0x83, 0x92, 0x42, 0x9e, 0x5d, 0x10, 0xa5, 0x5a, 0xe6, 0xa7, 0x80, 0x54, 0x58, 0x35, 0xf4, + 0x41, 0x7b, 0xa4, 0x9b, 0xc2, 0x53, 0x71, 0xff, 0x4f, 0x0a, 0xac, 0x2f, 0x21, 0x3b, 0xb4, 0x0b, + 0x5b, 0xd1, 0xe9, 0x33, 0xfd, 0xb4, 0xd5, 0xfe, 0xc2, 0xbc, 0x83, 0xf6, 0x21, 0xa0, 0x05, 0x13, + 0x01, 0x5e, 0x83, 0x8d, 0x05, 0xbd, 0xb8, 0x2c, 0x83, 0x3e, 0x84, 0x9d, 0x65, 0x3b, 0xa9, 0x28, + 0xb2, 0xa8, 0x01, 0xf5, 0xbb, 0x7e, 0xd3, 0x91, 0x1e, 0xf5, 0xbf, 0x7e, 0x53, 0x57, 0xbe, 0x79, + 0x53, 0x57, 0xfe, 0xfe, 0xa6, 0xae, 0xfc, 0xf6, 0x6d, 0x7d, 0xe5, 0x9b, 0xb7, 0xf5, 0x95, 0xbf, + 0xbe, 0xad, 0xaf, 0xfc, 0xf2, 0xd3, 0xb1, 0x13, 0x5c, 0xce, 0x2e, 0x0e, 0x2c, 0x6f, 0x72, 0x38, + 0x9d, 0xd1, 0x4b, 0xce, 0x77, 0x7c, 0xf5, 0x8c, 0x2f, 0x9f, 0xb9, 0x9e, 0x4d, 0x0e, 0xc3, 0xc3, + 0x78, 0x82, 0xf8, 0x2f, 0x68, 0x17, 0x05, 0xfe, 0x5b, 0xd8, 0x0f, 0xfe, 0x1d, 0x00, 0x00, 0xff, + 0xff, 0xd0, 0xb5, 0x5e, 0x65, 0x5e, 0x13, 0x00, 0x00, } func (this *Params) Equal(that interface{}) bool { @@ -1583,6 +1597,9 @@ func (this *Params) Equal(that interface{}) bool { if this.SomeValue != that1.SomeValue { return false } + if this.MaxGaslessTxGas != that1.MaxGaslessTxGas { + return false + } return true } func (this *UniversalPayload) Equal(that interface{}) bool { @@ -2154,6 +2171,11 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.MaxGaslessTxGas != 0 { + i = encodeVarintTypes(dAtA, i, uint64(m.MaxGaslessTxGas)) + i-- + dAtA[i] = 0x18 + } if m.SomeValue { i-- if m.SomeValue { @@ -3171,6 +3193,9 @@ func (m *Params) Size() (n int) { if m.SomeValue { n += 2 } + if m.MaxGaslessTxGas != 0 { + n += 1 + sovTypes(uint64(m.MaxGaslessTxGas)) + } return n } @@ -3689,6 +3714,25 @@ func (m *Params) Unmarshal(dAtA []byte) error { } } m.SomeValue = bool(v != 0) + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MaxGaslessTxGas", wireType) + } + m.MaxGaslessTxGas = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MaxGaslessTxGas |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipTypes(dAtA[iNdEx:]) From c2a8e3faca85e6aa190b987d7baf8486d7abe07b Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Wed, 26 Aug 2026 22:27:49 +0530 Subject: [PATCH 50/60] fix(ante): gate gasless new-account creation on bonded universal validator for vote msgs (#351) F-2026-18186 rec 3: a fresh key could send a gasless vote, get its account committed by the ante cache, and have the message fail afterwards, leaving the row behind. Reject the five validator-only vote msgs before any account is written. --- app/ante/account_init_decorator.go | 89 +++++- app/ante/account_init_decorator_test.go | 6 +- app/ante/account_init_signer_binding_test.go | 10 +- app/ante/account_init_validator_gate_test.go | 319 +++++++++++++++++++ app/ante/ante_cosmos.go | 4 +- app/ante/handler_options.go | 11 + app/app.go | 1 + 7 files changed, 434 insertions(+), 6 deletions(-) create mode 100644 app/ante/account_init_validator_gate_test.go diff --git a/app/ante/account_init_decorator.go b/app/ante/account_init_decorator.go index b117c938c..2149dccf7 100644 --- a/app/ante/account_init_decorator.go +++ b/app/ante/account_init_decorator.go @@ -16,16 +16,55 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth/ante" authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" txpolicy "github.com/pushchain/push-chain-node/app/txpolicy" + uexecutortypes "github.com/pushchain/push-chain-node/x/uexecutor/types" + utsstypes "github.com/pushchain/push-chain-node/x/utss/types" ) +// validatorOnlyGaslessMsgTypes is the subset of the gasless allowlist that only +// a bonded universal validator can ever execute successfully: every one of these +// msg servers gates on IsBondedUniversalValidator (VoteChainMeta gates on the +// strictly narrower eligible-voter set, of which bonded is a component). +// +// The remaining gasless types - MsgExecutePayload and MsgMigrateUEA - are +// deliberately absent: they are permissionless by design and creating an account +// for a first-time universal user is the intended behaviour of this decorator. +var validatorOnlyGaslessMsgTypes = map[string]struct{}{ + sdk.MsgTypeURL(&uexecutortypes.MsgVoteInbound{}): {}, + sdk.MsgTypeURL(&uexecutortypes.MsgVoteOutbound{}): {}, + sdk.MsgTypeURL(&uexecutortypes.MsgVoteChainMeta{}): {}, + sdk.MsgTypeURL(&utsstypes.MsgVoteTssKeyProcess{}): {}, + sdk.MsgTypeURL(&utsstypes.MsgVoteFundMigration{}): {}, +} + +// isValidatorOnlyGaslessTx reports whether tx carries at least one message that +// only a bonded universal validator can execute. +// +// authz.MsgExec is deliberately NOT unwrapped. A universal validator submits its +// votes wrapped in authz.MsgExec (universalClient/pushsigner wrapWithAuthZ), and +// there the tx signer is the grantee hotkey while the vote's own signer - the one +// the msg server checks - is the granter. That hotkey is legitimately not a +// universal validator itself, so unwrapping here would reject the real voting +// path. Only a top-level vote message declares the universal validator as the tx +// signer, and that is exactly the case this gate covers. +func isValidatorOnlyGaslessTx(tx sdk.Tx) bool { + for _, msg := range tx.GetMsgs() { + if _, ok := validatorOnlyGaslessMsgTypes[sdk.MsgTypeURL(msg)]; ok { + return true + } + } + return false +} + type AccountInitDecorator struct { ak AccountKeeper + uvk UValidatorKeeper signModeHandler *txsigning.HandlerMap } -func NewAccountInitDecorator(ak AccountKeeper, signModeHandler *txsigning.HandlerMap) AccountInitDecorator { +func NewAccountInitDecorator(ak AccountKeeper, uvk UValidatorKeeper, signModeHandler *txsigning.HandlerMap) AccountInitDecorator { return AccountInitDecorator{ ak: ak, + uvk: uvk, signModeHandler: signModeHandler, } } @@ -57,6 +96,27 @@ func (aid AccountInitDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate "address", sdk.AccAddress(newAccAddr).String(), "simulate", simulate, ) + // F-2026-18186: this decorator writes the account row and then returns + // without running the message, so the row survives even when the message + // later fails. For the validator-only vote messages that is a free, + // repeatable state-bloat primitive: a fresh key sends a gasless vote, the + // account is committed by the ante cache, and the msg server then rejects + // it for not being a bonded universal validator. + // + // Reject those before any account is created - and before the expensive + // signature verification below. A universal validator that can legitimately + // vote is bonded and therefore already has an account, so this path should + // never legitimately create one for a vote. + if isValidatorOnlyGaslessTx(tx) { + if err := aid.requireBondedUniversalValidator(ctx, newAccAddr); err != nil { + ctx.Logger().Debug("account init decorator: rejecting validator-only gasless tx from non-validator signer", + "address", sdk.AccAddress(newAccAddr).String(), + "error", err, + ) + return ctx, err + } + } + // if account does not exist on chain, bypass rest of ante chain here. // Perform signature verification on account number e and sequence number e instead. if err := aid.verifySignatureForNewAccount(ctx, tx, simulate); err != nil { @@ -82,6 +142,33 @@ func (aid AccountInitDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate return next(ctx, tx, simulate) } +// requireBondedUniversalValidator returns nil only when signer is a bonded +// universal validator. +// +// IsBondedUniversalValidator takes the bech32 ACCOUNT address (it derives the +// operator address from those bytes itself), which is the same string the vote +// msg servers hand it as msg.Signer. It returns an error - not (false, nil) - +// when the signer is absent from the universal validator set, so both branches +// have to be treated as a rejection; failing closed is correct here because the +// only thing being denied is the creation of an account row for a message that +// cannot succeed. +func (aid AccountInitDecorator) requireBondedUniversalValidator(ctx sdk.Context, signer sdk.AccAddress) error { + if aid.uvk == nil { + return errorsmod.Wrap(sdkerrors.ErrLogic, "uvalidator keeper not configured on account init decorator") + } + + bonded, err := aid.uvk.IsBondedUniversalValidator(ctx, signer.String()) + if err != nil { + return errorsmod.Wrapf(sdkerrors.ErrUnauthorized, + "signer %s may not create an account with a validator-only gasless message: %s", signer.String(), err.Error()) + } + if !bonded { + return errorsmod.Wrapf(sdkerrors.ErrUnauthorized, + "signer %s may not create an account with a validator-only gasless message: not a bonded universal validator", signer.String()) + } + return nil +} + func (aid AccountInitDecorator) verifySignatureForNewAccount(ctx sdk.Context, tx sdk.Tx, simulate bool) error { sigTx, ok := tx.(authsigning.Tx) if !ok { diff --git a/app/ante/account_init_decorator_test.go b/app/ante/account_init_decorator_test.go index 8b128431c..1d6715f7b 100644 --- a/app/ante/account_init_decorator_test.go +++ b/app/ante/account_init_decorator_test.go @@ -18,7 +18,7 @@ import ( // gasless message type list). func TestAccountInitDecorator_NonGaslessTxPassesThrough(t *testing.T) { ak := newMockAccountKeeperAnte(sdk.AccAddress([]byte("feeCollector"))) - aid := ante.NewAccountInitDecorator(ak, nil /*signModeHandler not needed for non-gasless*/) + aid := ante.NewAccountInitDecorator(ak, newMockUValidatorKeeperAnte(), nil /*signModeHandler not needed for non-gasless*/) // banktypes.MsgSend is not gasless. tx := mockFeeTx{ @@ -45,7 +45,7 @@ func TestAccountInitDecorator_GaslessTxExistingAccountPassesThrough(t *testing.T // Pre-register the account. ak.SetAccount(context.Background(), authtypes.NewBaseAccountWithAddress(existingAddr)) - aid := ante.NewAccountInitDecorator(ak, nil) + aid := ante.NewAccountInitDecorator(ak, newMockUValidatorKeeperAnte(), nil) // Use a non-authsigning tx — the decorator skips signature verification // for existing accounts only when it can parse signers. Since mockFeeTx doesn't @@ -76,7 +76,7 @@ func TestAccountInitDecorator_GaslessTxExistingAccountPassesThrough(t *testing.T // tx that does not implement authsigning.Tx is rejected with ErrTxDecode. func TestAccountInitDecorator_NonAuthSigningTxReturnsError(t *testing.T) { ak := newMockAccountKeeperAnte(sdk.AccAddress([]byte("feeCollector"))) - aid := ante.NewAccountInitDecorator(ak, nil) + aid := ante.NewAccountInitDecorator(ak, newMockUValidatorKeeperAnte(), nil) // MsgVoteInbound is gasless. tx := mockFeeTx{ diff --git a/app/ante/account_init_signer_binding_test.go b/app/ante/account_init_signer_binding_test.go index 4173e17f8..4ab2d7458 100644 --- a/app/ante/account_init_signer_binding_test.go +++ b/app/ante/account_init_signer_binding_test.go @@ -18,10 +18,12 @@ import ( "github.com/cosmos/cosmos-sdk/types/tx/signing" authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/authz" "github.com/pushchain/push-chain-node/app/ante" appparams "github.com/pushchain/push-chain-node/app/params" uexecutortypes "github.com/pushchain/push-chain-node/x/uexecutor/types" + utsstypes "github.com/pushchain/push-chain-node/x/utss/types" ) // uexecutorModuleEVMAddr is the EVM address of the uexecutor module account - @@ -39,6 +41,8 @@ func newSignerBindingEncodingConfig(t *testing.T) appparams.EncodingConfig { std.RegisterInterfaces(encCfg.InterfaceRegistry) authtypes.RegisterInterfaces(encCfg.InterfaceRegistry) uexecutortypes.RegisterInterfaces(encCfg.InterfaceRegistry) + utsstypes.RegisterInterfaces(encCfg.InterfaceRegistry) + authz.RegisterInterfaces(encCfg.InterfaceRegistry) return encCfg } @@ -141,7 +145,11 @@ func buildSignedTx(t *testing.T, encCfg appparams.EncodingConfig, msg sdk.Msg, d func newSignerBindingDecorator(t *testing.T, encCfg appparams.EncodingConfig) (ante.AccountInitDecorator, *mockAccountKeeperAnte) { t.Helper() ak := newMockAccountKeeperAnte(sdk.AccAddress([]byte("feeCollector"))) - return ante.NewAccountInitDecorator(ak, encCfg.TxConfig.SignModeHandler()), ak + // The uvalidator mock knows about nobody, so it rejects every address it is + // asked about. Every test in this file uses MsgExecutePayload / MsgMigrateUEA, + // which are deliberately NOT gated on validator status (F-2026-18186), so they + // must keep working against it. + return ante.NewAccountInitDecorator(ak, newMockUValidatorKeeperAnte(), encCfg.TxConfig.SignModeHandler()), ak } // TestAccountInitDecorator_RejectsAliasedModuleSigner is the regression test for diff --git a/app/ante/account_init_validator_gate_test.go b/app/ante/account_init_validator_gate_test.go new file mode 100644 index 000000000..cc13b1ae4 --- /dev/null +++ b/app/ante/account_init_validator_gate_test.go @@ -0,0 +1,319 @@ +package ante_test + +import ( + "context" + "fmt" + "testing" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/authz" + "github.com/stretchr/testify/require" + + "github.com/pushchain/push-chain-node/app/ante" + appparams "github.com/pushchain/push-chain-node/app/params" + uexecutortypes "github.com/pushchain/push-chain-node/x/uexecutor/types" + utsstypes "github.com/pushchain/push-chain-node/x/utss/types" +) + +// --------------------------------------------------------------------------- +// mock uvalidator keeper +// --------------------------------------------------------------------------- + +// mockUValidatorKeeperAnte satisfies ante.UValidatorKeeper and mirrors the real +// keeper's return shape, which matters: x/uvalidator's +// IsBondedUniversalValidator returns an ERROR (not (false, nil)) for an address +// that is absent from the universal validator set, and (false, nil) only for a +// registered-but-unbonded one. Both have to be treated as a rejection. +type mockUValidatorKeeperAnte struct { + // registered maps bech32 account address -> bonded. + registered map[string]bool +} + +func newMockUValidatorKeeperAnte(bonded ...sdk.AccAddress) *mockUValidatorKeeperAnte { + m := &mockUValidatorKeeperAnte{registered: map[string]bool{}} + for _, addr := range bonded { + m.registered[addr.String()] = true + } + return m +} + +// withUnbonded registers an address that is in the universal validator set but +// whose stake is not bonded - the (false, nil) branch of the real keeper. +func (m *mockUValidatorKeeperAnte) withUnbonded(addr sdk.AccAddress) *mockUValidatorKeeperAnte { + m.registered[addr.String()] = false + return m +} + +func (m *mockUValidatorKeeperAnte) IsBondedUniversalValidator(_ context.Context, universalValidator string) (bool, error) { + bonded, ok := m.registered[universalValidator] + if !ok { + return false, fmt.Errorf("validator %s not present in the registered universal validators set", universalValidator) + } + return bonded, nil +} + +// --------------------------------------------------------------------------- +// helpers +// --------------------------------------------------------------------------- + +// validatorOnlyMsgTypes are the five gasless message types that only a bonded +// universal validator can ever execute successfully. +var validatorOnlyMsgTypes = []string{ + "MsgVoteInbound", + "MsgVoteOutbound", + "MsgVoteChainMeta", + "MsgVoteTssKeyProcess", + "MsgVoteFundMigration", +} + +// voteMsgFor builds one of the five validator-only gasless messages with the +// given declared signer. +func voteMsgFor(t *testing.T, msgType string, signer sdk.AccAddress) sdk.Msg { + t.Helper() + switch msgType { + case "MsgVoteInbound": + return &uexecutortypes.MsgVoteInbound{Signer: signer.String()} + case "MsgVoteOutbound": + return &uexecutortypes.MsgVoteOutbound{Signer: signer.String(), TxId: "0xdead", UtxId: "0xbeef"} + case "MsgVoteChainMeta": + return &uexecutortypes.MsgVoteChainMeta{ + Signer: signer.String(), + ObservedChainId: "eip155:11155111", + Price: 1, + ChainHeight: 2, + } + case "MsgVoteTssKeyProcess": + return &utsstypes.MsgVoteTssKeyProcess{Signer: signer.String(), TssPubkey: "0xpub", KeyId: "key-1", ProcessId: 1} + case "MsgVoteFundMigration": + return &utsstypes.MsgVoteFundMigration{Signer: signer.String(), MigrationId: 1, TxHash: "0xdead", Success: true} + default: + t.Fatalf("unknown vote msg type %q", msgType) + return nil + } +} + +// newGateDecorator builds the decorator under test with a uvalidator mock that +// knows only about `bondedUVs`. +func newGateDecorator(t *testing.T, encCfg appparams.EncodingConfig, uvk *mockUValidatorKeeperAnte) (ante.AccountInitDecorator, *mockAccountKeeperAnte) { + t.Helper() + ak := newMockAccountKeeperAnte(sdk.AccAddress([]byte("feeCollector"))) + return ante.NewAccountInitDecorator(ak, uvk, encCfg.TxConfig.SignModeHandler()), ak +} + +// --------------------------------------------------------------------------- +// F-2026-18186 - the finding itself +// --------------------------------------------------------------------------- + +// TestAccountInitDecorator_VoteFromFreshSignerCreatesNoAccount is the regression +// test for F-2026-18186 (remediation 3). +// +// AccountInitDecorator writes the account row and then returns WITHOUT running +// the message, so the row survives even though the message subsequently fails. +// For the five validator-only vote messages that is a free, repeatable +// state-bloat primitive: a fresh key sends a gasless vote, the ante cache +// commits the account, and the msg server then rejects the vote because the +// signer is not a bonded universal validator. +// +// The load-bearing assertion is HasAccount == false; it is asserted BEFORE the +// error assertion on purpose, because require.Error aborts the subtest and would +// otherwise mask a vacuous pass. +func TestAccountInitDecorator_VoteFromFreshSignerCreatesNoAccount(t *testing.T) { + encCfg := newSignerBindingEncodingConfig(t) + + for _, msgType := range validatorOnlyMsgTypes { + t.Run(msgType, func(t *testing.T) { + key := secp256k1.GenPrivKey() + signer := sdk.AccAddress(key.PubKey().Address()) + + // Correctly signed by its own key: the tx is valid in every respect + // except that the signer is not a universal validator. + tx := buildSignedTx(t, encCfg, voteMsgFor(t, msgType, signer), signer, key) + + // The uvalidator mock knows about nobody: this signer is a fresh key. + aid, ak := newGateDecorator(t, encCfg, newMockUValidatorKeeperAnte()) + ctx := newAnteTestCtx(t, false).WithChainID(anteTestChainID) + + nextCalled := false + _, err := aid.AnteHandle(ctx, tx, false, func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { + nextCalled = true + return ctx, nil + }) + + // THE finding: no account row may be written for a message that + // cannot succeed. Asserted first so a vacuous test cannot hide. + require.False(t, ak.HasAccount(context.Background(), signer), + "F-2026-18186: no account row may be persisted for a gasless vote from a non-validator signer") + + require.Error(t, err, "a gasless vote from a non-validator signer must be rejected") + require.True(t, sdkerrors.ErrUnauthorized.Is(err), "expected ErrUnauthorized, got: %v", err) + require.False(t, nextCalled, "the message must never reach execution") + }) + } +} + +// TestAccountInitDecorator_VoteFromRegisteredButUnbondedSigner covers the other +// rejection branch of the real keeper: an address that IS in the universal +// validator set but whose stake is not bonded returns (false, nil) rather than +// an error, and must be rejected just the same. +func TestAccountInitDecorator_VoteFromRegisteredButUnbondedSigner(t *testing.T) { + encCfg := newSignerBindingEncodingConfig(t) + + key := secp256k1.GenPrivKey() + signer := sdk.AccAddress(key.PubKey().Address()) + tx := buildSignedTx(t, encCfg, voteMsgFor(t, "MsgVoteInbound", signer), signer, key) + + aid, ak := newGateDecorator(t, encCfg, newMockUValidatorKeeperAnte().withUnbonded(signer)) + ctx := newAnteTestCtx(t, false).WithChainID(anteTestChainID) + + _, err := aid.AnteHandle(ctx, tx, false, emptyNext) + + require.False(t, ak.HasAccount(context.Background(), signer), + "a registered-but-unbonded signer must not get an account row either") + require.Error(t, err) + require.True(t, sdkerrors.ErrUnauthorized.Is(err), "expected ErrUnauthorized, got: %v", err) + require.Contains(t, err.Error(), "not a bonded universal validator") +} + +// TestAccountInitDecorator_GateRunsBeforeSignatureVerification pins the ordering. +// The gate is meant to reject before the expensive signature verification, so a +// vote tx that is BOTH signed by an unrelated key AND sent from a non-validator +// signer must come back with the validator rejection, not ErrInvalidPubKey. +func TestAccountInitDecorator_GateRunsBeforeSignatureVerification(t *testing.T) { + encCfg := newSignerBindingEncodingConfig(t) + + attackerKey := secp256k1.GenPrivKey() + victimKey := secp256k1.GenPrivKey() + declaredSigner := sdk.AccAddress(victimKey.PubKey().Address()) + + tx := buildSignedTx(t, encCfg, voteMsgFor(t, "MsgVoteInbound", declaredSigner), declaredSigner, attackerKey) + + aid, ak := newGateDecorator(t, encCfg, newMockUValidatorKeeperAnte()) + ctx := newAnteTestCtx(t, false).WithChainID(anteTestChainID) + + _, err := aid.AnteHandle(ctx, tx, false, emptyNext) + + require.False(t, ak.HasAccount(context.Background(), declaredSigner)) + require.Error(t, err) + require.True(t, sdkerrors.ErrUnauthorized.Is(err), + "the validator gate must fire before signature verification, got: %v", err) + require.False(t, sdkerrors.ErrInvalidPubKey.Is(err)) +} + +// --------------------------------------------------------------------------- +// no regression: the legitimate paths +// --------------------------------------------------------------------------- + +// TestAccountInitDecorator_BondedValidatorVoteStillWorks is the positive control +// for the gate: a bonded universal validator's vote passes it, for all five +// message types. +// +// Both sub-cases matter. In practice a bonded universal validator already has an +// account, so it takes the "existing account" branch and reaches next(); the +// no-account variant proves the gate itself is not what would reject it if it +// somehow did not. +func TestAccountInitDecorator_BondedValidatorVoteStillWorks(t *testing.T) { + encCfg := newSignerBindingEncodingConfig(t) + + for _, msgType := range validatorOnlyMsgTypes { + t.Run(msgType+"/no_account_yet", func(t *testing.T) { + key := secp256k1.GenPrivKey() + signer := sdk.AccAddress(key.PubKey().Address()) + tx := buildSignedTx(t, encCfg, voteMsgFor(t, msgType, signer), signer, key) + + aid, ak := newGateDecorator(t, encCfg, newMockUValidatorKeeperAnte(signer)) + ctx := newAnteTestCtx(t, false).WithChainID(anteTestChainID) + + _, err := aid.AnteHandle(ctx, tx, false, emptyNext) + require.NoError(t, err, "a bonded universal validator must not be rejected by the gate") + + acc := ak.GetAccount(context.Background(), signer) + require.NotNil(t, acc, "the bonded validator's account is still created") + require.Equal(t, uint64(1), acc.GetSequence()) + }) + + t.Run(msgType+"/existing_account", func(t *testing.T) { + key := secp256k1.GenPrivKey() + signer := sdk.AccAddress(key.PubKey().Address()) + tx := buildSignedTx(t, encCfg, voteMsgFor(t, msgType, signer), signer, key) + + aid, ak := newGateDecorator(t, encCfg, newMockUValidatorKeeperAnte(signer)) + ak.SetAccount(context.Background(), authtypes.NewBaseAccountWithAddress(signer)) + ctx := newAnteTestCtx(t, false).WithChainID(anteTestChainID) + + nextCalled := false + _, err := aid.AnteHandle(ctx, tx, false, func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { + nextCalled = true + return ctx, nil + }) + require.NoError(t, err) + require.True(t, nextCalled, "an existing account must still fall through to the rest of the ante chain") + }) + } +} + +// TestAccountInitDecorator_PermissionlessGaslessMsgsUngated proves the scoping. +// MsgExecutePayload and MsgMigrateUEA are permissionless by design: a first-time +// universal user has no account and no validator status, and creating the account +// for them is the intended behaviour of this decorator. Gating them would break +// real users, so they must still work against a uvalidator keeper that rejects +// every address. +func TestAccountInitDecorator_PermissionlessGaslessMsgsUngated(t *testing.T) { + encCfg := newSignerBindingEncodingConfig(t) + + for _, msgType := range []string{"MsgExecutePayload", "MsgMigrateUEA"} { + t.Run(msgType, func(t *testing.T) { + key := secp256k1.GenPrivKey() + signer := sdk.AccAddress(key.PubKey().Address()) + tx := buildSignedTx(t, encCfg, gaslessMsgFor(t, msgType, signer), signer, key) + + // Knows about nobody: it would reject the signer if it were consulted. + aid, ak := newGateDecorator(t, encCfg, newMockUValidatorKeeperAnte()) + ctx := newAnteTestCtx(t, false).WithChainID(anteTestChainID) + + _, err := aid.AnteHandle(ctx, tx, false, emptyNext) + require.NoError(t, err, "%s is permissionless and must not be gated on validator status", msgType) + + acc := ak.GetAccount(context.Background(), signer) + require.NotNil(t, acc, "a first-time universal user must still get an account") + require.Equal(t, uint64(1), acc.GetSequence()) + }) + } +} + +// TestAccountInitDecorator_AuthzWrappedVoteUngated pins the deliberate decision +// not to unwrap authz.MsgExec. +// +// A universal validator submits its votes wrapped in authz.MsgExec +// (universalClient/pushsigner wrapWithAuthZ). There the TX signer is the grantee +// hotkey while the vote's own signer - the address the msg server checks - is the +// granter. The hotkey is legitimately not a universal validator, so unwrapping +// here would reject the real voting path. +func TestAccountInitDecorator_AuthzWrappedVoteUngated(t *testing.T) { + encCfg := newSignerBindingEncodingConfig(t) + + hotKey := secp256k1.GenPrivKey() + grantee := sdk.AccAddress(hotKey.PubKey().Address()) + granter := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()) + + inner, err := codectypes.NewAnyWithValue(voteMsgFor(t, "MsgVoteInbound", granter)) + require.NoError(t, err) + execMsg := &authz.MsgExec{Grantee: grantee.String(), Msgs: []*codectypes.Any{inner}} + + tx := buildSignedTx(t, encCfg, execMsg, grantee, hotKey) + + // Neither the hotkey nor the granter is known to the mock; only the absence of + // the gate can let this through. + aid, ak := newGateDecorator(t, encCfg, newMockUValidatorKeeperAnte()) + ctx := newAnteTestCtx(t, false).WithChainID(anteTestChainID) + + _, err = aid.AnteHandle(ctx, tx, false, emptyNext) + require.NoError(t, err, "the authz-wrapped voting path must keep working for a fresh grantee hotkey") + + acc := ak.GetAccount(context.Background(), grantee) + require.NotNil(t, acc, "the grantee hotkey must still get its account created") + require.Equal(t, uint64(1), acc.GetSequence()) +} diff --git a/app/ante/ante_cosmos.go b/app/ante/ante_cosmos.go index 9e48da1a1..47b6acfda 100755 --- a/app/ante/ante_cosmos.go +++ b/app/ante/ante_cosmos.go @@ -57,10 +57,12 @@ func NewCosmosAnteHandler(ctx sdk.Context, options HandlerOptions) sdk.AnteHandl // NewAccountInitDecorator must be called before all signature verification decorators and SetPubKeyDecorator // - this // 1. generates the account for the new accounts only for gasless transactions, + // refusing to do so for the validator-only vote messages, whose signer + // must already be a bonded universal validator (F-2026-18186), // 2. binds the declared signer to the signing key, enforces the signature // count limit and verifies the sig, and // 3. bypasses the rest of the ante chain - NewAccountInitDecorator(options.AccountKeeper, options.SignModeHandler), + NewAccountInitDecorator(options.AccountKeeper, options.UValidatorKeeper, options.SignModeHandler), // SetPubKeyDecorator must be called before all signature verification decorators ante.NewSetPubKeyDecorator(options.AccountKeeper), ante.NewValidateSigCountDecorator(options.AccountKeeper), diff --git a/app/ante/handler_options.go b/app/ante/handler_options.go index 52b563192..4374f9d93 100755 --- a/app/ante/handler_options.go +++ b/app/ante/handler_options.go @@ -43,12 +43,20 @@ type AccountKeeper interface { // UnorderedTransactionsEnabled() bool } +// UValidatorKeeper is the minimal slice of the uvalidator keeper the ante chain +// needs. Declared locally, like AccountKeeper/BankKeeper above, so the ante +// package does not depend on a concrete keeper. +type UValidatorKeeper interface { + IsBondedUniversalValidator(ctx context.Context, universalValidator string) (bool, error) +} + // HandlerOptions defines the list of module keepers required to run the EVM // AnteHandler decorators. type HandlerOptions struct { Cdc codec.BinaryCodec AccountKeeper AccountKeeper BankKeeper BankKeeper + UValidatorKeeper UValidatorKeeper FeegrantKeeper ante.FeegrantKeeper ExtensionOptionChecker ante.ExtensionOptionChecker SignModeHandler *txsigning.HandlerMap @@ -81,6 +89,9 @@ func (options HandlerOptions) Validate() error { if options.BankKeeper == nil { return errorsmod.Wrap(errortypes.ErrLogic, "bank keeper is required for AnteHandler") } + if options.UValidatorKeeper == nil { + return errorsmod.Wrap(errortypes.ErrLogic, "uvalidator keeper is required for AnteHandler") + } if options.SigGasConsumer == nil { return errorsmod.Wrap(errortypes.ErrLogic, "signature gas consumer is required for AnteHandler") } diff --git a/app/app.go b/app/app.go index 14fd71ac5..37e023bd5 100644 --- a/app/app.go +++ b/app/app.go @@ -1241,6 +1241,7 @@ func NewChainApp( Cdc: app.appCodec, AccountKeeper: app.AccountKeeper, BankKeeper: app.BankKeeper, + UValidatorKeeper: app.UvalidatorKeeper, FeegrantKeeper: app.FeeGrantKeeper, FeeMarketKeeper: app.FeeMarketKeeper, SignModeHandler: txConfig.SignModeHandler(), From 7f9e50f52b4578212320b5276329e6565588eb1b Mon Sep 17 00:00:00 2001 From: Aman Gupta Date: Tue, 1 Sep 2026 15:53:58 +0530 Subject: [PATCH 51/60] fix: F-2026-18817 | read SVM gateway events from emit_cpi inner instructions (#357) * fix(svm): read gateway events from emit_cpi inner instructions * refactor(svm): read the shared outbound fields at fixed offsets * refactor(svm): drop the redundant signature-level failure check * test(svm): pin gateway attribution across lookup-table account segments * docs(svm): trim the event observation comments --- universalClient/chains/svm/event_listener.go | 199 +++----- .../chains/svm/event_listener_test.go | 481 +++++++++--------- universalClient/chains/svm/event_parser.go | 59 +-- .../chains/svm/event_parser_test.go | 97 +++- 4 files changed, 426 insertions(+), 410 deletions(-) diff --git a/universalClient/chains/svm/event_listener.go b/universalClient/chains/svm/event_listener.go index 6c2292584..f363a3411 100644 --- a/universalClient/chains/svm/event_listener.go +++ b/universalClient/chains/svm/event_listener.go @@ -1,6 +1,7 @@ package svm import ( + "bytes" "context" "encoding/base64" "encoding/hex" @@ -295,55 +296,42 @@ func (el *EventListener) processSignatureBatch( continue } - // Process each log in the transaction. - // getSignaturesForAddress returns any tx that merely references the - // gateway in accountKeys, and a discriminator is a schema tag rather than - // an authenticator. So track the invocation stack and accept a - // "Program data:" line only while the gateway is the executing program; - // otherwise any program could emit a forged gateway event. - if tx != nil && tx.Meta != nil && len(tx.Meta.LogMessages) > 0 { - // Surface truncation loudly: a gateway event may have been dropped and - // is unrecoverable from RPC, so the deposit needs manual reconciliation. - // Visible logs are still processed, since events before the cut are real. - if logsTruncated(tx.Meta.LogMessages) { - el.logger.Error(). - Str("signature", sig.Signature.String()). - Uint64("slot", sig.Slot). - Msg("solana log buffer truncated; a gateway event may have been dropped and needs manual review") + // Events come from emit_cpi inner instructions, not logs, so log + // truncation cannot drop one. + // A failed tx still records what ran before it aborted, and all of it was + // rolled back. + if tx != nil && tx.Meta != nil && tx.Meta.Err != nil { + el.logger.Debug(). + Str("signature", sig.Signature.String()). + Msg("skipping failed transaction") + continue + } + + for payloadIndex, payload := range gatewayEventPayloads(tx, el.gatewayAddress) { + eventType := el.determineEventType(payload) + if eventType == "" { + continue } - fromGateway := gatewayEmittedLogs(tx.Meta.LogMessages, el.gatewayAddress) - for logIndex, log := range tx.Meta.LogMessages { - if !fromGateway[logIndex] { - continue - } - - // Determine event type based on discriminator - eventType := el.determineEventType(log) - if eventType == "" { - continue - } - - // Parse gateway event from individual log - event := ParseEvent(log, sig.Signature.String(), sig.Slot, uint(logIndex), eventType, el.chainID, el.logger) - if event != nil { - // Insert event if it doesn't already exist - if stored, err := el.chainStore.InsertEventIfNotExists(event); err != nil { - el.logger.Error(). - Err(err). - Str("event_id", event.EventID). - Str("type", event.Type). - Uint64("slot", event.BlockHeight). - Msg("failed to store event") - } else if stored { - el.logger.Debug(). - Str("event_id", event.EventID). - Str("type", event.Type). - Uint64("slot", event.BlockHeight). - Str("confirmation_type", event.ConfirmationType). - Msg("stored new event") - } - } + event := ParseEvent(payload, sig.Signature.String(), sig.Slot, uint(payloadIndex), eventType, el.chainID, el.logger) + if event == nil { + continue + } + + if stored, err := el.chainStore.InsertEventIfNotExists(event); err != nil { + el.logger.Error(). + Err(err). + Str("event_id", event.EventID). + Str("type", event.Type). + Uint64("slot", event.BlockHeight). + Msg("failed to store event") + } else if stored { + el.logger.Debug(). + Str("event_id", event.EventID). + Str("type", event.Type). + Uint64("slot", event.BlockHeight). + Str("confirmation_type", event.ConfirmationType). + Msg("stored new event") } } } @@ -415,99 +403,48 @@ func (el *EventListener) getPollingInterval() time.Duration { return 5 * time.Second // default } -// invokedProgram returns the program ID from a "Program invoke []" -// runtime log. Programs cannot emit these: sol_log and sol_log_data are always -// prefixed with "Program log: " / "Program data: ", so the invoke and exit lines -// are runtime-generated and safe to build an attribution stack from. -func invokedProgram(log string) (string, bool) { - const prefix = "Program " - if !strings.HasPrefix(log, prefix) { - return "", false +// eventIxTag prefixes the data of every Anchor emit_cpi instruction. +var eventIxTag = []byte{0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d} + +// gatewayEventPayloads returns the gateway's emit_cpi events in the +// "Program data:" form the parsers take. Event data is eventIxTag || +// discriminator || borsh, so dropping the tag leaves the old payload. +// +// Only instructions the gateway itself ran are read. A discriminator is a +// schema tag, not proof of who emitted it. +func gatewayEventPayloads(tx *solanarpc.GetTransactionResult, gatewayAddress string) []string { + if tx == nil || tx.Meta == nil || len(tx.Meta.InnerInstructions) == 0 { + return nil } - rest := log[len(prefix):] - idx := strings.Index(rest, " invoke [") - if idx <= 0 { - return "", false + gateway, err := solana.PublicKeyFromBase58(gatewayAddress) + if err != nil { + return nil } - programID := rest[:idx] - if _, err := solana.PublicKeyFromBase58(programID); err != nil { - return "", false + parsed, txErr := tx.Transaction.GetTransaction() + if txErr != nil || parsed == nil { + return nil } - return programID, true -} -// gatewayEmittedLogs returns the indexes of "Program data:" lines emitted while -// gatewayAddress was the executing program, walking the invoke/exit stack. -// Lines emitted by any other program are excluded: a discriminator identifies an -// encoding schema, not the emitter, so without this any program could log a -// well-formed gateway event and have it observed as a real deposit. -func gatewayEmittedLogs(logs []string, gatewayAddress string) map[int]bool { - emitted := make(map[int]bool) - var stack []string - for i, log := range logs { - if programID, ok := invokedProgram(log); ok { - stack = append(stack, programID) - continue - } - if isProgramExit(log) { - if len(stack) > 0 { - stack = stack[:len(stack)-1] - } - continue - } - if !strings.HasPrefix(log, "Program data: ") { - continue - } - if len(stack) > 0 && stack[len(stack)-1] == gatewayAddress { - emitted[i] = true - } - } - return emitted -} + // Index order: static keys, then ALT writable, then ALT readonly. + keys := append(solana.PublicKeySlice{}, parsed.Message.AccountKeys...) + keys = append(keys, tx.Meta.LoadedAddresses.Writable...) + keys = append(keys, tx.Meta.LoadedAddresses.ReadOnly...) -// logsTruncated reports whether the runtime dropped part of this transaction's -// log buffer. Programs can only emit "Program log:" and "Program data:" lines, -// so a bare line is runtime-generated and cannot be spoofed. Matching on the -// word rather than one exact literal keeps this working if the wording changes. -// -// It matters because gateway events are emitted with sol_log_data: once the -// buffer overflows the event line is gone, and no RPC call can recover it. The -// deposit would otherwise be missed in silence. -func logsTruncated(logs []string) bool { - for _, log := range logs { - if strings.HasPrefix(log, "Program ") { - continue - } - if strings.Contains(strings.ToLower(log), "truncated") { - return true + var payloads []string + for _, group := range tx.Meta.InnerInstructions { + for _, ix := range group.Instructions { + if int(ix.ProgramIDIndex) >= len(keys) || !keys[ix.ProgramIDIndex].Equals(gateway) { + continue + } + if len(ix.Data) < len(eventIxTag) || !bytes.Equal(ix.Data[:len(eventIxTag)], eventIxTag) { + continue + } + payloads = append(payloads, "Program data: "+base64.StdEncoding.EncodeToString(ix.Data[len(eventIxTag):])) } } - return false -} - -// isProgramExit reports whether log ends an invocation frame, i.e. -// "Program success" or "Program failed: ...". -// The program ID must parse as a pubkey: otherwise a program logging "success" -// emits "Program log: success", which would pop a frame it does not own and let -// a later log be attributed to its caller. -func isProgramExit(log string) bool { - const prefix = "Program " - if !strings.HasPrefix(log, prefix) { - return false - } - rest := log[len(prefix):] - sp := strings.IndexByte(rest, ' ') - if sp <= 0 { - return false - } - if _, err := solana.PublicKeyFromBase58(rest[:sp]); err != nil { - return false - } - tail := rest[sp+1:] - return tail == "success" || strings.HasPrefix(tail, "failed") + return payloads } -// determineEventType determines the event type based on the log discriminator func (el *EventListener) determineEventType(log string) string { if !strings.HasPrefix(log, "Program data: ") { return "" diff --git a/universalClient/chains/svm/event_listener_test.go b/universalClient/chains/svm/event_listener_test.go index 84c20e36f..e19c5cbc5 100644 --- a/universalClient/chains/svm/event_listener_test.go +++ b/universalClient/chains/svm/event_listener_test.go @@ -5,18 +5,22 @@ import ( "context" "encoding/base64" "encoding/hex" + "encoding/json" + "fmt" "strings" "testing" "time" "github.com/gagliardetto/solana-go" solanarpc "github.com/gagliardetto/solana-go/rpc" + "github.com/mr-tron/base58" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/pushchain/push-chain-node/universalClient/chains/common" "github.com/pushchain/push-chain-node/universalClient/db" + "github.com/pushchain/push-chain-node/universalClient/store" uregistrytypes "github.com/pushchain/push-chain-node/x/uregistry/types" ) @@ -683,172 +687,10 @@ const ( testAttackerProgram = "AttackerProgram1111111111111111111111111111" ) -// getSignaturesForAddress returns any tx that merely references the gateway in -// accountKeys, and a discriminator is a schema tag, not an authenticator. So a -// gateway-shaped log must only be trusted when the gateway is the executing -// program. Otherwise any program can forge a deposit that every honest UV -// deterministically votes for. -func TestGatewayEmittedLogs(t *testing.T) { - const data = "Program data: q83vEjRWeJA=" - - t.Run("accepts log emitted by the gateway", func(t *testing.T) { - logs := []string{ - "Program " + testGatewayProgram + " invoke [1]", - data, - "Program " + testGatewayProgram + " success", - } - assert.Equal(t, map[int]bool{1: true}, gatewayEmittedLogs(logs, testGatewayProgram)) - }) - - // The reported attack: attacker program lists the gateway as an unused - // read-only account and emits a correctly encoded gateway event. - t.Run("rejects forged log from an attacker program", func(t *testing.T) { - logs := []string{ - "Program " + testAttackerProgram + " invoke [1]", - data, - "Program " + testAttackerProgram + " success", - } - assert.Empty(t, gatewayEmittedLogs(logs, testGatewayProgram)) - }) - - t.Run("accepts gateway frame reached via CPI", func(t *testing.T) { - logs := []string{ - "Program " + testAttackerProgram + " invoke [1]", - "Program " + testGatewayProgram + " invoke [2]", - data, - "Program " + testGatewayProgram + " success", - "Program " + testAttackerProgram + " success", - } - assert.Equal(t, map[int]bool{2: true}, gatewayEmittedLogs(logs, testGatewayProgram)) - }) - - // After the gateway frame exits, control is back with the caller, so a log - // there is not the gateway's. - t.Run("rejects log emitted after the gateway frame exits", func(t *testing.T) { - logs := []string{ - "Program " + testAttackerProgram + " invoke [1]", - "Program " + testGatewayProgram + " invoke [2]", - "Program " + testGatewayProgram + " success", - data, - "Program " + testAttackerProgram + " success", - } - assert.Empty(t, gatewayEmittedLogs(logs, testGatewayProgram)) - }) - - t.Run("rejects log from a failed gateway invocation's caller", func(t *testing.T) { - logs := []string{ - "Program " + testGatewayProgram + " invoke [1]", - "Program " + testGatewayProgram + " failed: custom program error: 0x1", - data, - } - assert.Empty(t, gatewayEmittedLogs(logs, testGatewayProgram)) - }) - - // A program can only emit "Program log: ..." or "Program data: ...", so it - // cannot fake an invoke line to push a gateway frame onto the stack. - t.Run("cannot spoof an invoke line via program log", func(t *testing.T) { - logs := []string{ - "Program " + testAttackerProgram + " invoke [1]", - "Program log: Program " + testGatewayProgram + " invoke [1]", - data, - "Program " + testAttackerProgram + " success", - } - assert.Empty(t, gatewayEmittedLogs(logs, testGatewayProgram)) - }) - - // A callee that logs "success" emits "Program log: success". If that were - // treated as a frame exit it would pop its own frame and the next data log - // would be attributed to its caller, the gateway. - t.Run("callee cannot pop its frame by logging success", func(t *testing.T) { - logs := []string{ - "Program " + testGatewayProgram + " invoke [1]", - "Program " + testAttackerProgram + " invoke [2]", - "Program log: success", - data, - "Program " + testAttackerProgram + " success", - "Program " + testGatewayProgram + " success", - } - assert.Empty(t, gatewayEmittedLogs(logs, testGatewayProgram), - "data logged inside the callee must not be attributed to the gateway") - }) - - t.Run("no logs or no invocation yields nothing", func(t *testing.T) { - assert.Empty(t, gatewayEmittedLogs(nil, testGatewayProgram)) - assert.Empty(t, gatewayEmittedLogs([]string{data}, testGatewayProgram)) - }) - - // Unbalanced logs are reachable: truncation can cut a frame's exit line, and - // the parser must not underflow or start attributing to a frame nobody owns. - t.Run("more exits than invokes does not underflow", func(t *testing.T) { - assert.Empty(t, gatewayEmittedLogs([]string{ - "Program " + testGatewayProgram + " success", - "Program " + testGatewayProgram + " success", - data, - }, testGatewayProgram)) - - assert.Empty(t, gatewayEmittedLogs([]string{ - "Program " + testGatewayProgram + " invoke [1]", - "Program " + testGatewayProgram + " success", - "Program " + testGatewayProgram + " success", - data, - }, testGatewayProgram)) - }) - - t.Run("attacker exits cannot expose an outer gateway frame", func(t *testing.T) { - // Gateway CPIs into the attacker, who emits surplus exits hoping to pop - // back to the gateway frame and have its own data log attributed to it. - assert.Empty(t, gatewayEmittedLogs([]string{ - "Program " + testGatewayProgram + " invoke [1]", - "Program " + testAttackerProgram + " invoke [2]", - "Program " + testAttackerProgram + " success", - "Program " + testGatewayProgram + " success", - data, - }, testGatewayProgram)) - }) - - t.Run("gateway frame left open still attributes", func(t *testing.T) { - // What a mid-frame truncation looks like: the exit line never arrives. - assert.Equal(t, map[int]bool{1: true}, gatewayEmittedLogs([]string{ - "Program " + testGatewayProgram + " invoke [1]", - data, - }, testGatewayProgram)) - }) - - t.Run("unrelated runtime lines do not disturb the stack", func(t *testing.T) { - logs := []string{ - "Program " + testGatewayProgram + " invoke [1]", - "Program log: Instruction: SendFunds", - "Program return: " + testGatewayProgram + " AQID", - "Program " + testGatewayProgram + " consumed 12345 of 200000 compute units", - data, - "Program " + testGatewayProgram + " success", - } - assert.Equal(t, map[int]bool{4: true}, gatewayEmittedLogs(logs, testGatewayProgram)) - }) -} - -func TestInvokedProgramAndExit(t *testing.T) { - id, ok := invokedProgram("Program " + testGatewayProgram + " invoke [1]") - assert.True(t, ok) - assert.Equal(t, testGatewayProgram, id) - - _, ok = invokedProgram("Program log: hello") - assert.False(t, ok) - _, ok = invokedProgram("Program data: AQID") - assert.False(t, ok) - - assert.True(t, isProgramExit("Program "+testGatewayProgram+" success")) - assert.True(t, isProgramExit("Program "+testGatewayProgram+" failed: custom program error: 0x1")) - assert.False(t, isProgramExit("Program log: success")) - assert.False(t, isProgramExit("Program data: AQID")) - assert.False(t, isProgramExit("Program "+testGatewayProgram+" consumed 1 of 2 compute units")) -} - -// forgeryRPC serves one transaction whose logs the test controls. type forgeryRPC struct { - slot uint64 - sig solana.Signature - logs []string + slot uint64 + sig solana.Signature + txJSON string } func (m *forgeryRPC) GetLatestSlot(context.Context) (uint64, error) { return m.slot, nil } @@ -858,25 +700,52 @@ func (m *forgeryRPC) GetSignaturesForAddress(context.Context, solana.PublicKey, } func (m *forgeryRPC) GetTransaction(context.Context, solana.Signature) (*solanarpc.GetTransactionResult, error) { - return &solanarpc.GetTransactionResult{ - Slot: m.slot, - Meta: &solanarpc.TransactionMeta{LogMessages: m.logs}, - }, nil + var tx solanarpc.GetTransactionResult + if err := json.Unmarshal([]byte(m.txJSON), &tx); err != nil { + return nil, err + } + return &tx, nil +} + +// txWithEmittedEvent builds a tx whose only inner instruction is an emit_cpi +// event from `emitter`. +func txWithEmittedEvent(t *testing.T, emitter string, payload []byte, logs []string) string { + t.Helper() + data := append(append([]byte{}, eventIxTag...), payload...) + logJSON, err := json.Marshal(logs) + require.NoError(t, err) + return fmt.Sprintf(`{ + "slot": 100, + "transaction": { + "signatures": ["%s"], + "message": { + "header": {"numRequiredSignatures":1,"numReadonlySignedAccounts":0,"numReadonlyUnsignedAccounts":1}, + "accountKeys": ["%s","%s"], + "recentBlockhash": "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM", + "instructions": [] + } + }, + "meta": { + "err": null, + "logMessages": %s, + "innerInstructions": [ + {"index":0,"instructions":[{"programIdIndex":1,"accounts":[],"data":"%s","stackHeight":2}]} + ] + } + }`, mkSig(7).String(), testGatewayProgram, emitter, string(logJSON), base58.Encode(data)) } // End-to-end proof that the listener drops a forged event. The same valid // send_funds payload is served twice: emitted by an attacker program it must be -// ignored, emitted by the gateway it must be stored. Running both with one -// payload shows attribution is what rejects it, not a decode failure. +// ignored, emitted by the gateway it must be stored. func TestProcessSignatureBatch_RejectsForgedGatewayEvent(t *testing.T) { discriminator := "0000000000000000" // buildSendFundsPayload zeroes the discriminator payload := buildSendFundsPayload( [32]byte{1}, [20]byte{2}, [32]byte{3}, 1_000_000, nil, [32]byte{4}, 0, nil, false, ) - dataLog := "Program data: " + base64.StdEncoding.EncodeToString(payload) - run := func(t *testing.T, logs []string) int { + run := func(t *testing.T, emitter string) int { t.Helper() database, err := db.OpenInMemoryDB(true) require.NoError(t, err) @@ -885,7 +754,7 @@ func TestProcessSignatureBatch_RejectsForgedGatewayEvent(t *testing.T) { methods := []*uregistrytypes.GatewayMethods{ {Name: EventTypeSendFunds, EventIdentifier: discriminator}, } - rpc := &forgeryRPC{slot: 100, sig: mkSig(7), logs: logs} + rpc := &forgeryRPC{slot: 100, sig: mkSig(7), txJSON: txWithEmittedEvent(t, emitter, payload, nil)} el, err := NewEventListener(rpc, testGatewayProgram, "solana:test", methods, database, 10, nil, zerolog.Nop()) require.NoError(t, err) @@ -900,94 +769,236 @@ func TestProcessSignatureBatch_RejectsForgedGatewayEvent(t *testing.T) { } t.Run("forged by attacker program is not stored", func(t *testing.T) { - stored := run(t, []string{ - "Program " + testAttackerProgram + " invoke [1]", - dataLog, - "Program " + testAttackerProgram + " success", - }) - assert.Zero(t, stored, "forged gateway event must not become an inbound") + assert.Zero(t, run(t, testAttackerProgram), "forged gateway event must not become an inbound") }) t.Run("same payload from the gateway is stored", func(t *testing.T) { - stored := run(t, []string{ - "Program " + testGatewayProgram + " invoke [1]", - dataLog, - "Program " + testGatewayProgram + " success", - }) - assert.Equal(t, 1, stored, "genuine gateway event must be observed") + assert.Equal(t, 1, run(t, testGatewayProgram), "genuine gateway event must be observed") }) } -// Gateway events are emitted with sol_log_data, so once the runtime truncates -// the log buffer the event line is gone and no RPC call recovers it. Detect it -// so a missed deposit is alertable instead of silent. -func TestLogsTruncated(t *testing.T) { - t.Run("detects the runtime marker", func(t *testing.T) { - assert.True(t, logsTruncated([]string{ - "Program " + testGatewayProgram + " invoke [1]", - "Program log: Instruction: SendFunds", - "Log truncated", - })) - }) +// A failed tx still lists what ran before it aborted, and all of it was +// rolled back. +func TestProcessSignatureBatch_SkipsFailedTransactions(t *testing.T) { + payload := buildSendFundsPayload( + [32]byte{1}, [20]byte{2}, [32]byte{3}, 1_000_000, + nil, [32]byte{4}, 0, nil, false, + ) + methods := []*uregistrytypes.GatewayMethods{ + {Name: EventTypeSendFunds, EventIdentifier: "0000000000000000"}, + } - t.Run("matches wording variants and case", func(t *testing.T) { - assert.True(t, logsTruncated([]string{"log truncated"})) - assert.True(t, logsTruncated([]string{"Log Truncated"})) - }) + run := func(t *testing.T, txJSON string) int { + t.Helper() + database, err := db.OpenInMemoryDB(true) + require.NoError(t, err) + t.Cleanup(func() { database.Close() }) + + rpc := &forgeryRPC{slot: 100, sig: mkSig(7), txJSON: txJSON} + el, err := NewEventListener(rpc, testGatewayProgram, "solana:test", methods, database, 10, nil, zerolog.Nop()) + require.NoError(t, err) + + _, err = el.processSignatureBatch(context.Background(), []*solanarpc.TransactionSignature{ + {Signature: mkSig(7), Slot: 100}, + }, 0, 200) + require.NoError(t, err) + + events, err := common.NewChainStore(database).GetPendingEvents(100) + require.NoError(t, err) + return len(events) + } + + ok := txWithEmittedEvent(t, testGatewayProgram, payload, nil) - t.Run("normal logs are not flagged", func(t *testing.T) { - assert.False(t, logsTruncated([]string{ - "Program " + testGatewayProgram + " invoke [1]", - "Program log: Instruction: SendFunds", - "Program data: q83vEjRWeJA=", - "Program " + testGatewayProgram + " success", - })) - assert.False(t, logsTruncated(nil)) + t.Run("succeeded transaction is stored", func(t *testing.T) { + assert.Equal(t, 1, run(t, ok)) }) - // A program cannot emit a bare line, so it cannot fake the marker. Its own - // output is always prefixed and must not trip detection. - t.Run("program cannot spoof the marker", func(t *testing.T) { - assert.False(t, logsTruncated([]string{ - "Program " + testAttackerProgram + " invoke [1]", - "Program log: Log truncated", - "Program data: dHJ1bmNhdGVk", - "Program " + testAttackerProgram + " success", - })) + t.Run("failed transaction is skipped", func(t *testing.T) { + failed := strings.Replace(ok, `"err": null`, `"err": {"InstructionError":[0,{"Custom":6020}]}`, 1) + require.NotEqual(t, ok, failed, "the fixture must actually carry a failure") + assert.Zero(t, run(t, failed), + "an event emitted before the abort must not be observed") }) } -// Truncation must not discard the events that did survive: anything logged -// before the cut is genuine and attributable. -func TestProcessSignatureBatch_TruncatedLogsStillStoreVisibleEvents(t *testing.T) { - discriminator := "0000000000000000" +// The observation half of F-2026-18817: the event is an inner instruction, so +// a flooded log buffer cannot take it down. +func TestProcessSignatureBatch_TruncatedLogsStillYieldEvent(t *testing.T) { + database, err := db.OpenInMemoryDB(true) + require.NoError(t, err) + t.Cleanup(func() { database.Close() }) + payload := buildSendFundsPayload( [32]byte{1}, [20]byte{2}, [32]byte{3}, 1_000_000, nil, [32]byte{4}, 0, nil, false, ) - - database, err := db.OpenInMemoryDB(true) - require.NoError(t, err) - defer database.Close() + truncated := []string{ + "Program " + testGatewayProgram + " invoke [1]", + "Program log: truncated", + } methods := []*uregistrytypes.GatewayMethods{ - {Name: EventTypeSendFunds, EventIdentifier: discriminator}, + {Name: EventTypeSendFunds, EventIdentifier: "0000000000000000"}, } - rpc := &forgeryRPC{slot: 100, sig: mkSig(9), logs: []string{ - "Program " + testGatewayProgram + " invoke [1]", - "Program data: " + base64.StdEncoding.EncodeToString(payload), - "Program " + testGatewayProgram + " success", - "Log truncated", - }} + rpc := &forgeryRPC{slot: 100, sig: mkSig(7), txJSON: txWithEmittedEvent(t, testGatewayProgram, payload, truncated)} el, err := NewEventListener(rpc, testGatewayProgram, "solana:test", methods, database, 10, nil, zerolog.Nop()) require.NoError(t, err) _, err = el.processSignatureBatch(context.Background(), []*solanarpc.TransactionSignature{ - {Signature: mkSig(9), Slot: 100}, + {Signature: mkSig(7), Slot: 100}, }, 0, 200) require.NoError(t, err) events, err := common.NewChainStore(database).GetPendingEvents(100) require.NoError(t, err) - assert.Len(t, events, 1, "events logged before the cut must still be stored") + assert.Len(t, events, 1, "a truncated log buffer must not cost us the event") +} + +// A real devnet finalize, signature 4ye6nTo4oKcEctDza44Zr7QAwHmqhH4qfeBkDjHqE2aFtgxuhdF9dfs1EmBbYiTfwXMvWUupJ592DQQQAGx5Abus. +// It carries no "Program data:" line at all. +const devnetFinalizeTx = `{"blockTime":1788253699,"meta":{"computeUnitsConsumed":132988,"costUnits":136994,"err":null,"fee":5000,"innerInstructions":[{"index":0,"instructions":[{"accounts":[0,5],"data":"11114pZy3PBZenKB1vn2UptrP91MCBmdVNUDneZKAWH7B8Sg5eCB3E67uKRhm1xbvr4ACz","programIdIndex":10,"stackHeight":2},{"accounts":[0,9],"data":"1111NuBxPLg6vZ28hQWMLnv7auFnJFYGTC4L1MUjrNkN9xikCBvdVStP4KiJr9vvBcWPa","programIdIndex":10,"stackHeight":2},{"accounts":[9,15],"data":"18ukwGkxoTJPx4HkLTz6ZybMUmX1yt47MVERA5H6yQGUdgK","programIdIndex":16,"stackHeight":2},{"accounts":[0,3],"data":"11113ahNe3Yfn6gi8hZhcH9k4YUezY3FJNRHx6tPLUTkr868BMzwWAZDTUtWcrGK2cw1Fx","programIdIndex":10,"stackHeight":2},{"accounts":[0,4,7,9,10,16],"data":"1","programIdIndex":13,"stackHeight":2},{"accounts":[9],"data":"84eT","programIdIndex":16,"stackHeight":3},{"accounts":[0,4],"data":"11113z11NKiYBjwDfL71F9myuy3cwdtTaatpiC6rj9zomYP4qbzHXZVjhEFkM5qi31QeQg","programIdIndex":10,"stackHeight":3},{"accounts":[4],"data":"P","programIdIndex":16,"stackHeight":3},{"accounts":[4,9],"data":"6VKrKvV2EjfdgaesMejJQDnusTVKHbdt2BPiddZq2WzGf","programIdIndex":16,"stackHeight":3},{"accounts":[9,4,9],"data":"6YF7VVXZihvw","programIdIndex":16,"stackHeight":2},{"accounts":[2,0],"data":"3Bxs4R98mv6mmz5M","programIdIndex":10,"stackHeight":2},{"accounts":[2,0],"data":"3Bxs4PckVVt51W8w","programIdIndex":10,"stackHeight":2},{"accounts":[11],"data":"9opCxkAgBxqeR8UTbeow8YC7933mDbBMCEMKiYsmMRqLs1N8zRudTsxXqkeWaXpNdpt2QZhCyZprcPNHfS7EwMkBnfrzuFhd3zMg8ZLA6Uk1BVxrx5nq9s2EYueLPKVCWCvzyKsZqph76dduPdkHBQs7TdFtP5FtvJssMKvwPu5zShUSegxsJLaYKZCjZAcrYscVbEmLzrvehE2s4qYdMGyW58rkamNjPC4BgKoZpPt136NYv31ftYXB4sVrTdjkJogbp9HpWA1BnewRmXuWddeYyGxyiFG3X44mG5ALa7rTuPgcZukdFmahFVfE2Txj","programIdIndex":14,"stackHeight":2}]}],"loadedAddresses":{"readonly":[],"writable":[]},"logMessages":["Program DJoFYDpgbTfxbXBv1QYhYGc9FK4J5FUKpYXAfSkHryXp invoke [1]","Program log: Instruction: FinalizeUniversalTxWithIxDataRef","Program 11111111111111111111111111111111 invoke [2]","Program 11111111111111111111111111111111 success","Program 11111111111111111111111111111111 invoke [2]","Program 11111111111111111111111111111111 success","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 86 of 148619 compute units","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success","Program 11111111111111111111111111111111 invoke [2]","Program 11111111111111111111111111111111 success","Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL invoke [2]","Program log: Create","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 179 of 93967 compute units","Program return: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA pQAAAAAAAAA=","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success","Program 11111111111111111111111111111111 invoke [3]","Program 11111111111111111111111111111111 success","Program log: Initialize the associated token account","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 37 of 88878 compute units","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 233 of 86415 compute units","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success","Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL consumed 15010 of 100888 compute units","Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL success","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 122 of 82575 compute units","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success","Program 11111111111111111111111111111111 invoke [2]","Program 11111111111111111111111111111111 success","Program 11111111111111111111111111111111 invoke [2]","Program 11111111111111111111111111111111 success","Program DJoFYDpgbTfxbXBv1QYhYGc9FK4J5FUKpYXAfSkHryXp invoke [2]","Program DJoFYDpgbTfxbXBv1QYhYGc9FK4J5FUKpYXAfSkHryXp consumed 2517 of 71342 compute units","Program DJoFYDpgbTfxbXBv1QYhYGc9FK4J5FUKpYXAfSkHryXp success","Program DJoFYDpgbTfxbXBv1QYhYGc9FK4J5FUKpYXAfSkHryXp consumed 132988 of 200000 compute units","Program DJoFYDpgbTfxbXBv1QYhYGc9FK4J5FUKpYXAfSkHryXp success"],"postBalances":[5010154600,2793266460,17563563083,1203270,1855569,861288,0,0,1566000,1329930,1,0,2832720,5938070540,1141440,1009200,15367267856],"postTokenBalances":[{"accountIndex":4,"mint":"ZTgXiGpKZjEopH1mSqZ1GjY8k9G6dQaJoCm8iUkab7V","owner":"9C9ezHVSSpMrKAmqZa74jpUUxbjUBtcKUUYn8z9DDqqh","programId":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","uiTokenAmount":{"amount":"10000000","decimals":6,"uiAmount":10.0,"uiAmountString":"10"}}],"preBalances":[5006672783,2793266460,17568823140,0,0,0,3476817,0,1566000,0,1,0,2832720,5938070540,1141440,1009200,15367267856],"preTokenBalances":[],"rewards":[],"status":{"Ok":null}},"slot":491383584,"transaction":{"message":{"accountKeys":["4QbAt2CJ8QqCHeps2RmMjG1nWUCmPqjkEyYQMjrJaVtH","2EEYH6e1PtCdWzZaag9buJmDDS79gvrm1aQm9yEcgWdR","4sQLizYQ1ZJjc2doLqTQsk1Kj8XVS5uviJykpHNNMSi5","4VC5j3WgPU7TTF8YzgikNdpF8zwnGkqAjf9iWfA5xCi7","59oiUm1Anavheg38XWDDdv3GAjD4XYSUhQBY8qyxXnTc","5BT6kxRRU2jSVbvVdeEBAszUoCXTzUpHWeruS5kYkqz","5PZEHeEx9mhMNPneusoQvLunGDLgHAQcyGmnoT1jJCEk","9C9ezHVSSpMrKAmqZa74jpUUxbjUBtcKUUYn8z9DDqqh","FDxeNn8YT8DoWrJ5GzqNTW8rjx8cLFNFBgHpBTMifeYJ","ZTgXiGpKZjEopH1mSqZ1GjY8k9G6dQaJoCm8iUkab7V","11111111111111111111111111111111","5FRwYKUHLYoSq6uNgrjZ2sq436AAzPnv77fv9e7EiFPi","7QAS73zgRm7KMt85XMGWbWDnmhZR1UyTULhhxR254YYR","ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL","DJoFYDpgbTfxbXBv1QYhYGc9FK4J5FUKpYXAfSkHryXp","SysvarRent111111111111111111111111111111111","TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"],"header":{"numReadonlySignedAccounts":0,"numReadonlyUnsignedAccounts":7,"numRequiredSignatures":1},"instructions":[{"accounts":[0,12,2,7,8,5,10,10,1,14,4,14,16,15,13,14,14,14,6,0,11,14,3,9],"data":"42AXCSarXAyth9mmjqkpxW8qtkNjcbj5KuupCERvjRCapw5ydhidmiLmMnypfiZFedPUhVNQyeLPfF17ZtqeBJUS83v6DJRgwFHuVMK1dDy4MFW7QhMwVxfJwuZ1hgv9TtXcmdmixaHukCq77ikrN37w3UR2P12aY9ERa5tXuQGkxXAenYcsNHQuw7y99mXT2icCjvcVd3yGRtWgvnbrd4Gf36pzqRpZkrNgKdJzvSDYgoarEXdPq6m3GjZxRkvsgQJ2sZTsKbNCeRrL5tdxTRgd7YZXqXVDJ4ShaYAKqLXBqMGf1LbynGK2G8HUriKn41xVEFK2Rg37E3dykeHSDS","programIdIndex":14,"stackHeight":1}],"recentBlockhash":"2CEDFZcp6d5ug1BgDjuNzMPRXqi4WX8QTZskNr9yJicY"},"signatures":["4ye6nTo4oKcEctDza44Zr7QAwHmqhH4qfeBkDjHqE2aFtgxuhdF9dfs1EmBbYiTfwXMvWUupJ592DQQQAGx5Abus"]},"transactionIndex":12,"version":"legacy"}` + +func TestGatewayEventPayloads_Rejects(t *testing.T) { + var tx solanarpc.GetTransactionResult + require.NoError(t, json.Unmarshal([]byte(devnetFinalizeTx), &tx)) + + t.Run("unparseable gateway address", func(t *testing.T) { + assert.Nil(t, gatewayEventPayloads(&tx, "not-a-pubkey")) + }) + + t.Run("nil transaction and nil meta", func(t *testing.T) { + assert.Nil(t, gatewayEventPayloads(nil, testGatewayProgram)) + assert.Nil(t, gatewayEventPayloads(&solanarpc.GetTransactionResult{}, testGatewayProgram)) + }) + + t.Run("inner instruction that is not an event", func(t *testing.T) { + // Right emitter, no EVENT_IX_TAG: every ordinary gateway self-CPI. + var plain solanarpc.GetTransactionResult + require.NoError(t, json.Unmarshal([]byte(txWithRawInnerData(t, testGatewayProgram, []byte{1, 2, 3})), &plain)) + assert.Empty(t, gatewayEventPayloads(&plain, testGatewayProgram)) + }) +} + +// Index order is static keys, then ALT writable, then ALT readonly. The +// gateway comes through the ALT, so a wrong order misattributes events. +func TestGatewayEventPayloads_ResolvesLookupTableKeys(t *testing.T) { + payload := buildSendFundsPayload( + [32]byte{1}, [20]byte{2}, [32]byte{3}, 1_000_000, + nil, [32]byte{4}, 0, nil, false, + ) + data := append(append([]byte{}, eventIxTag...), payload...) + other := "11111111111111111111111111111111" + + // index 0 = static, 1 = ALT writable, 2 = ALT readonly (the gateway). + mk := func(programIdIndex int) string { + return fmt.Sprintf(`{ + "slot": 100, + "transaction": { + "signatures": ["%s"], + "message": { + "header": {"numRequiredSignatures":1,"numReadonlySignedAccounts":0,"numReadonlyUnsignedAccounts":0}, + "accountKeys": ["%s"], + "recentBlockhash": "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM", + "instructions": [] + } + }, + "meta": { + "err": null, + "logMessages": [], + "loadedAddresses": {"writable": ["%s"], "readonly": ["%s"]}, + "innerInstructions": [ + {"index":0,"instructions":[{"programIdIndex":%d,"accounts":[],"data":"%s","stackHeight":2}]} + ] + } + }`, mkSig(7).String(), other, testAttackerProgram, testGatewayProgram, programIdIndex, base58.Encode(data)) + } + + t.Run("gateway resolved from the readonly segment", func(t *testing.T) { + var tx solanarpc.GetTransactionResult + require.NoError(t, json.Unmarshal([]byte(mk(2)), &tx)) + assert.Len(t, gatewayEventPayloads(&tx, testGatewayProgram), 1) + }) + + t.Run("writable segment is not mistaken for the gateway", func(t *testing.T) { + var tx solanarpc.GetTransactionResult + require.NoError(t, json.Unmarshal([]byte(mk(1)), &tx)) + assert.Empty(t, gatewayEventPayloads(&tx, testGatewayProgram), + "index 1 is the ALT writable entry, not the gateway") + }) + + t.Run("an index past the key list is ignored", func(t *testing.T) { + var tx solanarpc.GetTransactionResult + require.NoError(t, json.Unmarshal([]byte(mk(99)), &tx)) + assert.Empty(t, gatewayEventPayloads(&tx, testGatewayProgram)) + }) +} + +// txWithRawInnerData builds a tx whose inner instruction data has no tag. +func txWithRawInnerData(t *testing.T, emitter string, data []byte) string { + t.Helper() + return fmt.Sprintf(`{ + "slot": 100, + "transaction": { + "signatures": ["%s"], + "message": { + "header": {"numRequiredSignatures":1,"numReadonlySignedAccounts":0,"numReadonlyUnsignedAccounts":1}, + "accountKeys": ["%s","%s"], + "recentBlockhash": "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM", + "instructions": [] + } + }, + "meta": { + "err": null, + "logMessages": [], + "innerInstructions": [ + {"index":0,"instructions":[{"programIdIndex":1,"accounts":[],"data":"%s","stackHeight":2}]} + ] + } + }`, mkSig(7).String(), testGatewayProgram, emitter, base58.Encode(data)) +} + +// Pinned to a real tx: the layout is the deployed program's, not ours. Here +// gas_fee - gas_used == gas_to_refund, so the offsets check themselves. +func TestGatewayEventPayloads_RealDevnetTransaction(t *testing.T) { + const gateway = "DJoFYDpgbTfxbXBv1QYhYGc9FK4J5FUKpYXAfSkHryXp" + + var tx solanarpc.GetTransactionResult + require.NoError(t, json.Unmarshal([]byte(devnetFinalizeTx), &tx)) + + require.NotNil(t, tx.Meta) + for _, l := range tx.Meta.LogMessages { + require.False(t, strings.HasPrefix(l, "Program data: "), + "this transaction predates emit_cpi if it still logs event data") + } + + payloads := gatewayEventPayloads(&tx, gateway) + require.Len(t, payloads, 1, "the finalize emits exactly one gateway event") + + el := &EventListener{ + gatewayAddress: gateway, + // sha256("event:UniversalTxFinalized")[:8], as emitted on chain. + discriminatorToEventType: map[string]string{"b3409670758c9c25": EventTypeFinalizeUniversalTx}, + chainID: "solana:devnet", + logger: zerolog.Nop(), + } + eventType := el.determineEventType(payloads[0]) + require.Equal(t, EventTypeFinalizeUniversalTx, eventType) + + event := ParseEvent(payloads[0], "sig", 42, 0, eventType, "solana:devnet", zerolog.Nop()) + require.NotNil(t, event) + + var payload common.OutboundEvent + require.NoError(t, json.Unmarshal(event.EventData, &payload)) + assert.Equal(t, "5260057", payload.GasFeeUsed, "gas_used must come from offset 112, not from wrapper_address") + assert.Equal(t, store.EventTypeOutbound, event.Type) +} + +// Any program can put a gateway discriminator in its own inner instruction. +func TestGatewayEventPayloads_IgnoresForeignEmitter(t *testing.T) { + var tx solanarpc.GetTransactionResult + require.NoError(t, json.Unmarshal([]byte(devnetFinalizeTx), &tx)) + + assert.Empty(t, gatewayEventPayloads(&tx, "11111111111111111111111111111111"), + "an event emitted by another program must not be read as the gateway's") } diff --git a/universalClient/chains/svm/event_parser.go b/universalClient/chains/svm/event_parser.go index 9df446175..cc56e8124 100644 --- a/universalClient/chains/svm/event_parser.go +++ b/universalClient/chains/svm/event_parser.go @@ -48,7 +48,7 @@ func ParseEvent(log string, signature string, slot uint64, logIndex uint, eventT case EventTypeSendFunds: return parseSendFundsEvent(log, signature, slot, logIndex, chainID, logger) case EventTypeFinalizeUniversalTx, EventTypeRevertUniversalTx, EventTypeFundsRescued: - return parseOutboundObservationEvent(log, signature, slot, logIndex, chainID, logger) + return parseOutboundObservationEvent(log, signature, slot, logIndex, eventType, chainID, logger) default: logger.Debug(). Str("event_type", eventType). @@ -111,16 +111,13 @@ func parseSendFundsEvent(log string, signature string, slot uint64, logIndex uin // - discriminator (8 bytes) // - sub_tx_id (32 bytes) // - universal_tx_id (32 bytes) -// - gas_fee (8 bytes, u64 lamports) — prepaid budget -// - gas_used (8 bytes, u64 lamports) — actual lamports consumed -// - gas_to_refund (8 bytes, u64 lamports) — gas_fee - gas_used returned to caller -// - ata_created (1 byte, bool) — true if SPL ATA was newly created -// - push_account (20 bytes) -// - target (32 bytes, Pubkey) -// - token (32 bytes, Pubkey) -// - amount (8 bytes, u64) -// - payload (4 bytes length + data, Vec) -func parseOutboundObservationEvent(log string, signature string, slot uint64, logIndex uint, chainID string, logger zerolog.Logger) *store.Event { +// The three outbound events diverge after universal_tx_id, so gas_used sits at +// a different offset in each: +// +// UniversalTxFinalized ... wrapper_address(32) gas_fee(8) gas_used -> 112 +// RevertUniversalTx ... revert_recipient(32) token(32) amount(8) -> 144 +// FundsRescued ... token(32) amount(8) -> 112 +func parseOutboundObservationEvent(log string, signature string, slot uint64, logIndex uint, eventType string, chainID string, logger zerolog.Logger) *store.Event { if !strings.HasPrefix(log, "Program data: ") { return nil } @@ -131,12 +128,23 @@ func parseOutboundObservationEvent(log string, signature string, slot uint64, lo return nil } - // Minimum: 8 disc + 32 sub_tx_id + 32 universal_tx_id + 8 gas_fee + 8 gas_used - // + 8 gas_to_refund + 1 ata_created = 97 bytes. - if len(decoded) < 97 { + gasUsedOffset := 0 + switch eventType { + case EventTypeFinalizeUniversalTx, EventTypeFundsRescued: + gasUsedOffset = 112 + case EventTypeRevertUniversalTx: + gasUsedOffset = 144 + default: + return nil + } + + // gas_used is the last field read, so one check covers the earlier ones. + if len(decoded) < gasUsedOffset+8 { logger.Warn(). Int("data_len", len(decoded)). - Msg("data too short for outboundObservation event; need at least 97 bytes") + Int("need", gasUsedOffset+8). + Str("event_type", eventType). + Msg("data too short for outboundObservation event") return nil } @@ -150,23 +158,10 @@ func parseOutboundObservationEvent(log string, signature string, slot uint64, lo Uint64("slot", slot). Msg("processing outboundObservation event") - // Skip discriminator (8 bytes) - offset := 8 - - // Extract txID (32 bytes) - txID := "0x" + hex.EncodeToString(decoded[offset:offset+32]) - offset += 32 - - // Extract universalTxID (32 bytes) - universalTxID := "0x" + hex.EncodeToString(decoded[offset:offset+32]) - offset += 32 - - // Skip gas_fee (prepaid budget, 8 bytes); the audited finalize event reports - // gas_used separately and that's the value we want to surface as GasFeeUsed. - offset += 8 - - // Extract gas_used (8 bytes, u64 little-endian lamports) — actual gas consumed. - gasUsed := binary.LittleEndian.Uint64(decoded[offset : offset+8]) + // Shared prefix: disc(8) sub_tx_id(32) universal_tx_id(32). + txID := "0x" + hex.EncodeToString(decoded[8:40]) + universalTxID := "0x" + hex.EncodeToString(decoded[40:72]) + gasUsed := binary.LittleEndian.Uint64(decoded[gasUsedOffset : gasUsedOffset+8]) // Create OutboundEvent payload payload := common.OutboundEvent{ diff --git a/universalClient/chains/svm/event_parser_test.go b/universalClient/chains/svm/event_parser_test.go index e8630f401..2c31e3fbd 100644 --- a/universalClient/chains/svm/event_parser_test.go +++ b/universalClient/chains/svm/event_parser_test.go @@ -92,14 +92,24 @@ func wrapAsLog(data []byte) string { // the parser reports as GasFeeUsed; gas_fee (offset 72..80) is the prepaid // budget and is skipped. Tests pass `gasUsed` to match what the parser will // extract; gas_fee in the payload is left zero. +// UniversalTxFinalized: disc(8) sub_tx_id(32) universal_tx_id(32) +// wrapper_address(32) gas_fee(8) gas_used(8) gas_to_refund(8) ... func buildOutboundPayload(txID [32]byte, universalTxID [32]byte, gasUsed uint64) []byte { - data := make([]byte, 97) - // discriminator (8 bytes, zeroed is fine) + data := make([]byte, 130) copy(data[8:40], txID[:]) copy(data[40:72], universalTxID[:]) - // gas_fee at 72..80 (prepaid budget, left zero in tests) - binary.LittleEndian.PutUint64(data[80:88], gasUsed) - // gas_to_refund at 88..96 (left zero); ata_created at 96 (left zero) + // wrapper_address at 72..104 stays zero, which is the non-PC20 case. + binary.LittleEndian.PutUint64(data[112:120], gasUsed) + return data +} + +// RevertUniversalTx: disc(8) sub_tx_id(32) universal_tx_id(32) +// revert_recipient(32) token(32) amount(8) gas_used(8) ... +func buildRevertPayload(txID [32]byte, universalTxID [32]byte, gasUsed uint64) []byte { + data := make([]byte, 152) + copy(data[8:40], txID[:]) + copy(data[40:72], universalTxID[:]) + binary.LittleEndian.PutUint64(data[144:152], gasUsed) return data } @@ -195,9 +205,20 @@ func TestParseEvent_Routing(t *testing.T) { }) t.Run("revert_universal_tx routes to outbound parser", func(t *testing.T) { - event := ParseEvent(outboundLog, sig, 100, 0, EventTypeRevertUniversalTx, chainID, logger) + // Revert puts gas_used further in than finalize does. + revertLog := wrapAsLog(buildRevertPayload(txID, utxID, 5000)) + event := ParseEvent(revertLog, sig, 100, 0, EventTypeRevertUniversalTx, chainID, logger) require.NotNil(t, event) assert.Equal(t, store.EventTypeOutbound, event.Type) + + var outbound common.OutboundEvent + require.NoError(t, json.Unmarshal(event.EventData, &outbound)) + assert.Equal(t, "5000", outbound.GasFeeUsed) + }) + + t.Run("a finalize-shaped payload is too short to be read as a revert", func(t *testing.T) { + assert.Nil(t, ParseEvent(outboundLog, sig, 100, 0, EventTypeRevertUniversalTx, chainID, logger), + "reading a revert at the finalize offset would report the wrong gas_used") }) t.Run("unknown event type returns nil", func(t *testing.T) { @@ -211,6 +232,59 @@ func TestParseEvent_Routing(t *testing.T) { }) } +// Each outbound event has its own gas_used offset, so "long enough" differs +// by type. +func TestParseOutboundObservationEvent_LengthIsPerEventType(t *testing.T) { + logger := nopLogger() + chainID := "solana:devnet" + sig := "sig" + var txID, utxID [32]byte + + t.Run("finalize needs 120 bytes", func(t *testing.T) { + assert.Nil(t, ParseEvent(wrapAsLog(make([]byte, 119)), sig, 1, 0, EventTypeFinalizeUniversalTx, chainID, logger)) + assert.NotNil(t, ParseEvent(wrapAsLog(make([]byte, 120)), sig, 1, 0, EventTypeFinalizeUniversalTx, chainID, logger)) + }) + + t.Run("rescue needs 120 bytes", func(t *testing.T) { + assert.Nil(t, ParseEvent(wrapAsLog(make([]byte, 119)), sig, 1, 0, EventTypeFundsRescued, chainID, logger)) + assert.NotNil(t, ParseEvent(wrapAsLog(make([]byte, 120)), sig, 1, 0, EventTypeFundsRescued, chainID, logger)) + }) + + t.Run("revert needs 152 bytes", func(t *testing.T) { + assert.Nil(t, ParseEvent(wrapAsLog(make([]byte, 151)), sig, 1, 0, EventTypeRevertUniversalTx, chainID, logger)) + assert.NotNil(t, ParseEvent(wrapAsLog(buildRevertPayload(txID, utxID, 1)), sig, 1, 0, EventTypeRevertUniversalTx, chainID, logger)) + }) + + t.Run("an unroutable event type is refused", func(t *testing.T) { + assert.Nil(t, parseOutboundObservationEvent( + wrapAsLog(buildOutboundPayload(txID, utxID, 1)), sig, 1, 0, "send_funds", chainID, logger), + "only the three outbound events have a known gas_used offset") + }) +} + +// Each event type must read gas_used from its own offset. +func TestParseOutboundObservationEvent_ReadsItsOwnOffset(t *testing.T) { + logger := nopLogger() + var txID, utxID [32]byte + + for _, tc := range []struct { + eventType string + payload []byte + }{ + {EventTypeFinalizeUniversalTx, buildOutboundPayload(txID, utxID, 4242)}, + {EventTypeFundsRescued, buildOutboundPayload(txID, utxID, 4242)}, + {EventTypeRevertUniversalTx, buildRevertPayload(txID, utxID, 4242)}, + } { + t.Run(tc.eventType, func(t *testing.T) { + event := ParseEvent(wrapAsLog(tc.payload), "sig", 1, 0, tc.eventType, "solana:devnet", logger) + require.NotNil(t, event) + var outbound common.OutboundEvent + require.NoError(t, json.Unmarshal(event.EventData, &outbound)) + assert.Equal(t, "4242", outbound.GasFeeUsed) + }) + } +} + func TestParseSendFundsEvent(t *testing.T) { logger := nopLogger() chainID := "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" @@ -421,21 +495,20 @@ func TestParseOutboundObservationEvent(t *testing.T) { }) t.Run("returns nil for data too short", func(t *testing.T) { - shortData := make([]byte, 96) // needs 97 + shortData := make([]byte, 119) // gas_used ends at 120 event := ParseEvent(wrapAsLog(shortData), signature, 12345, 0, EventTypeFinalizeUniversalTx, chainID, logger) assert.Nil(t, event) }) - t.Run("parses minimum valid data (exactly 97 bytes)", func(t *testing.T) { - data := make([]byte, 97) + t.Run("parses minimum valid data (exactly 120 bytes)", func(t *testing.T) { + data := make([]byte, 120) for i := 8; i < 40; i++ { data[i] = 0x11 } for i := 40; i < 72; i++ { data[i] = 0x22 } - // gas_used at 80..88 - binary.LittleEndian.PutUint64(data[80:88], 12345) + binary.LittleEndian.PutUint64(data[112:120], 12345) event := ParseEvent(wrapAsLog(data), signature, 100, 0, EventTypeFinalizeUniversalTx, chainID, logger) require.NotNil(t, event) @@ -447,7 +520,7 @@ func TestParseOutboundObservationEvent(t *testing.T) { assert.Equal(t, "12345", outbound.GasFeeUsed) }) - t.Run("handles data longer than 97 bytes", func(t *testing.T) { + t.Run("handles data longer than the fixed fields", func(t *testing.T) { var txID, utxID [32]byte for i := range txID { txID[i] = 0xAA From f38bf133220422a28dabd0107ff1fec75da683d2 Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Wed, 2 Sep 2026 12:12:06 +0530 Subject: [PATCH 52/60] fix(uexecutor): bill reverted inbound payloads for gas used (F-2026-18824 rec 2) (#360) * fix(uexecutor): bill reverted inbound payloads for gas used Inbound routes only; charge clamped to balance and never fails the inbound. * chore: pin evm to 7ac130d5 (DerivedEVMCall response propagation) * chore: trim comments --- go.mod | 2 +- go.sum | 4 +- .../inbound_reverted_payload_gas_test.go | 50 +++++++++++++++ .../execute_inbound_funds_and_payload.go | 5 ++ .../keeper/execute_inbound_gas_and_payload.go | 5 ++ x/uexecutor/keeper/execute_payload.go | 2 + x/uexecutor/keeper/fees.go | 61 +++++++++++++++++++ 7 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 test/integration/uexecutor/inbound_reverted_payload_gas_test.go diff --git a/go.mod b/go.mod index 42295a052..4989333ce 100755 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ replace ( cosmossdk.io/x/upgrade => cosmossdk.io/x/upgrade v0.1.4 github.com/CosmWasm/wasmd => github.com/CosmWasm/wasmd v0.55.0 // Keep v0.55.0 github.com/cosmos/cosmos-sdk => github.com/cosmos/cosmos-sdk v0.50.10 // Use stable v0.50.10 - github.com/cosmos/evm => github.com/pushchain/evm v1.0.0-rc2.0.20260826155700-89c7e52be541 + github.com/cosmos/evm => github.com/pushchain/evm v1.0.0-rc2.0.20260902062048-7ac130d5db3e github.com/ethereum/go-ethereum => github.com/cosmos/go-ethereum v0.0.0-20250806193535-2fc7571efa91 github.com/spf13/viper => github.com/spf13/viper v1.17.0 github.com/strangelove-ventures/tokenfactory => github.com/strangelove-ventures/tokenfactory v0.50.7-wasmvm2 diff --git a/go.sum b/go.sum index 9ffc7c79b..7df1f6a4e 100755 --- a/go.sum +++ b/go.sum @@ -1765,8 +1765,8 @@ github.com/prysmaticlabs/gohashtree v0.0.4-beta.0.20240624100937-73632381301b h1 github.com/prysmaticlabs/gohashtree v0.0.4-beta.0.20240624100937-73632381301b/go.mod h1:HRuvtXLZ4WkaB1MItToVH2e8ZwKwZPY5/Rcby+CvvLY= github.com/prysmaticlabs/prysm/v5 v5.3.0 h1:7Lr8ndapBTZg00YE+MgujN6+yvJR6Bdfn28ZDSJ00II= github.com/prysmaticlabs/prysm/v5 v5.3.0/go.mod h1:r1KhlduqDMIGZ1GhR5pjZ2Ko8Q89noTDYTRoPKwf1+c= -github.com/pushchain/evm v1.0.0-rc2.0.20260826155700-89c7e52be541 h1:Ols8viNso7TAphC5MYbl+GrmMuFVKvvfn8uzeGEnV0Q= -github.com/pushchain/evm v1.0.0-rc2.0.20260826155700-89c7e52be541/go.mod h1:QuenX5DgRhWeYdIg0J/p65cyS/ntpgnzpZOIajZ/SHk= +github.com/pushchain/evm v1.0.0-rc2.0.20260902062048-7ac130d5db3e h1:cCYU0JsTi2t+vmYWQWq1CzwMrgioqqUvNDWbyIc/eF0= +github.com/pushchain/evm v1.0.0-rc2.0.20260902062048-7ac130d5db3e/go.mod h1:QuenX5DgRhWeYdIg0J/p65cyS/ntpgnzpZOIajZ/SHk= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= github.com/quic-go/qtls-go1-20 v0.3.4 h1:MfFAPULvst4yoMgY9QmtpYmfij/em7O8UUi+bNVm7Cg= diff --git a/test/integration/uexecutor/inbound_reverted_payload_gas_test.go b/test/integration/uexecutor/inbound_reverted_payload_gas_test.go new file mode 100644 index 000000000..9b1a7ca58 --- /dev/null +++ b/test/integration/uexecutor/inbound_reverted_payload_gas_test.go @@ -0,0 +1,50 @@ +package integrationtest + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + utils "github.com/pushchain/push-chain-node/test/utils" + uexecutortypes "github.com/pushchain/push-chain-node/x/uexecutor/types" +) + +// TestInboundRevertedPayloadBillsGas covers F-2026-18824 rec 2: a reverted inbound +// payload used to pay no gas at all. The UEA is funded with upc by the setup and the +// deposit is a PRC20, so upc moves for exactly one reason here — gas. +func TestInboundRevertedPayloadBillsGas(t *testing.T) { + prc20 := utils.GetDefaultAddresses().PRC20USDCAddr + + chainApp, ctx, vals, coreVals, ueaAddr := setupMulticallOutboundTest(t, 4) + ueaAcc := sdk.AccAddress(ueaAddr.Bytes()) + upcBefore := chainApp.BankKeeper.GetBalance(ctx, ueaAcc, "upc") + + // 0xdeadbeef matches no selector on the PRC20, which has no fallback -> revert. + inbound := multicallInbound("0xreverted-payload-gas-01", uexecutortypes.TxType_FUNDS_AND_PAYLOAD, "1000000", "0xdeadbeef") + inbound.UniversalPayload.To = prc20.Hex() + + voteToQuorum(t, ctx, chainApp, vals, coreVals, inbound) + + utxKey := uexecutortypes.GetInboundUniversalTxKey(*inbound) + utx, found, err := chainApp.UexecutorKeeper.GetUniversalTx(ctx, utxKey) + require.NoError(t, err) + require.True(t, found, "the inbound must be recorded even though its payload reverted") + + // Guard the premise: a payload that stopped reverting, or never ran, would make + // the balance assertion below meaningless. + pcTx := payloadPcTx(t, utx) + require.Equal(t, "FAILED", pcTx.Status, "the payload must have reverted for this test to mean anything") + require.NotContains(t, pcTx.ErrorMsg, "depositAutoSwap failed", + "the payload must be what failed, not the funding step before it") + + upcAfter := chainApp.BankKeeper.GetBalance(ctx, ueaAcc, "upc") + + // The fix. + require.True(t, upcAfter.Amount.LT(upcBefore.Amount), + "a reverted payload must still be billed for the gas it burned (before=%s, after=%s)", + upcBefore.Amount, upcAfter.Amount) + + // Billing is clamped to the available balance. + require.False(t, upcAfter.Amount.IsNegative(), "billing must never drive the UEA balance negative") +} diff --git a/x/uexecutor/keeper/execute_inbound_funds_and_payload.go b/x/uexecutor/keeper/execute_inbound_funds_and_payload.go index fadd38373..86107f9cf 100644 --- a/x/uexecutor/keeper/execute_inbound_funds_and_payload.go +++ b/x/uexecutor/keeper/execute_inbound_funds_and_payload.go @@ -259,6 +259,11 @@ func (k Keeper) ExecuteInboundFundsAndPayload(ctx context.Context, utx types.Uni prc20Addr, txId, ) + if contractErr != nil { + // Reverted: cacheCtx is discarded, so bill the gas on the + // parent sdkCtx. + k.ChargeRevertedPayloadGas(ctx, sdkCtx, ueaAddr, contractReceipt, utx.InboundTx.UniversalPayload) + } if contractErr == nil { feeErr = k.DeductGasFeesFromReceipt(cacheCtx, cacheCtx, ueaAddr, contractReceipt, utx.InboundTx.UniversalPayload) if feeErr == nil { diff --git a/x/uexecutor/keeper/execute_inbound_gas_and_payload.go b/x/uexecutor/keeper/execute_inbound_gas_and_payload.go index 158c9f4ec..fde576ef4 100644 --- a/x/uexecutor/keeper/execute_inbound_gas_and_payload.go +++ b/x/uexecutor/keeper/execute_inbound_gas_and_payload.go @@ -260,6 +260,11 @@ func (k Keeper) ExecuteInboundGasAndPayload(ctx context.Context, utx types.Unive var feeErr error var attachErr error + if contractErr != nil { + // Reverted: cacheCtx is discarded, so bill on the parent sdkCtx. The + // gas deposit committed before the cache opened, so there is a balance. + k.ChargeRevertedPayloadGas(ctx, sdkCtx, ueaAddr, contractReceipt, utx.InboundTx.UniversalPayload) + } if contractErr == nil && contractReceipt != nil { feeErr = k.DeductGasFeesFromReceipt(cacheCtx, cacheCtx, ueaAddr, contractReceipt, utx.InboundTx.UniversalPayload) if feeErr == nil { diff --git a/x/uexecutor/keeper/execute_payload.go b/x/uexecutor/keeper/execute_payload.go index c95c440fb..900147280 100644 --- a/x/uexecutor/keeper/execute_payload.go +++ b/x/uexecutor/keeper/execute_payload.go @@ -50,6 +50,8 @@ func (k Keeper) ExecutePayloadV2(ctx context.Context, evmFrom common.Address, ue if execErr != nil { // EVM execution failed — cache discarded by not calling writeCache. + // Bill the gas on the parent sdkCtx so the charge survives the discard. + k.ChargeRevertedPayloadGas(ctx, sdkCtx, ueaAddr, receipt, universalPayload) return receipt, execErr } diff --git a/x/uexecutor/keeper/fees.go b/x/uexecutor/keeper/fees.go index bee45f344..76ec82640 100644 --- a/x/uexecutor/keeper/fees.go +++ b/x/uexecutor/keeper/fees.go @@ -90,6 +90,67 @@ func (k Keeper) CalculateGasCost( return gasCost, nil } +// ChargeRevertedPayloadGas bills a reverted payload for the gas it burned. It +// charges the parent ctx so the amount survives the caller's discarded EVM cache, +// clamps to the balance held, and never returns an error — callers record a FAILED +// PcTx and carry on. +// +// INBOUND ROUTES ONLY. Never call this from MsgExecutePayload: submission there is +// permissionless and a revert rolls back the UEA nonce, so one captured owner +// signature stays replayable and billing it would let anyone drain the UEA. +func (k Keeper) ChargeRevertedPayloadGas( + ctx context.Context, + sdkCtx sdk.Context, + recipient common.Address, + receipt *evmtypes.MsgEthereumTxResponse, + universalPayload *types.UniversalPayload, +) { + if receipt == nil || receipt.GasUsed == 0 || universalPayload == nil { + return + } + + abiPayload, err := types.NewAbiUniversalPayload(universalPayload) + if err != nil { + return + } + baseFee := k.feemarketKeeper.GetBaseFee(sdkCtx) + if baseFee.IsNil() { + return + } + gasCost, err := k.CalculateGasCost(baseFee, abiPayload.MaxFeePerGas, abiPayload.MaxPriorityFeePerGas, receipt.GasUsed) + if err != nil || gasCost.Sign() <= 0 { + return + } + + recipientAccAddr := sdk.AccAddress(recipient.Bytes()) + available := k.bankKeeper.GetBalance(sdkCtx, recipientAccAddr, pchaintypes.BaseDenom).Amount.BigInt() + + charge := gasCost + if available.Cmp(gasCost) < 0 { + charge = available + } + if charge.Sign() <= 0 { + k.Logger().Info("reverted payload not billed: no balance", + "recipient", recipient.Hex(), "gas_used", receipt.GasUsed, "gas_cost", gasCost.String()) + return + } + + if err := k.DeductAndBurnFees(ctx, recipientAccAddr, charge); err != nil { + // Best effort: never fail the message over the revert-path charge. + k.Logger().Error("failed to bill reverted payload gas", + "recipient", recipient.Hex(), "charge", charge.String(), "error", err) + return + } + + k.Logger().Info("reverted payload gas billed", + "recipient", recipient.Hex(), + "gas_used", receipt.GasUsed, + "gas_cost", gasCost.String(), + "charged", charge.String(), + "partial", charge.Cmp(gasCost) < 0, + ) +} + // DeductGasFeesFromReceipt calculates and deducts gas fees from a recipient address // based on the EVM receipt and universal payload parameters. // Returns nil if receipt is nil (Go-level error, no EVM tx was created). From 05996bb84a99c855941f111382a8f6ce39b53724 Mon Sep 17 00:00:00 2001 From: Aman Gupta Date: Wed, 2 Sep 2026 12:50:37 +0530 Subject: [PATCH 53/60] fix: F-2026-18815 | follow the gateway recipient ATA creation on SVM revert and rescue (#361) * fix(svm): follow the gateway's recipient ATA creation on revert and rescue * fix(svm): report the revert gas fee from the gateway's reimbursement event * Revert "fix(svm): report the revert gas fee from the gateway's reimbursement event" This reverts commit 2565b875bf963703dad424d69662b40f63156515. * docs(svm): drop the stale instruction-order comment and trim the rest * test(svm): pin that a revert votes zero gas rather than empty * test: drop the vote validation test --- test/integration/uexecutor/gas_refund_test.go | 27 ++++ .../chains/common/event_processor_test.go | 28 ++++ universalClient/chains/svm/event_parser.go | 30 ++-- .../chains/svm/event_parser_test.go | 20 +-- universalClient/chains/svm/tx_builder.go | 12 +- universalClient/chains/svm/tx_builder_test.go | 148 ++++++++++++++---- 6 files changed, 208 insertions(+), 57 deletions(-) diff --git a/test/integration/uexecutor/gas_refund_test.go b/test/integration/uexecutor/gas_refund_test.go index a5d238f2b..237267a90 100644 --- a/test/integration/uexecutor/gas_refund_test.go +++ b/test/integration/uexecutor/gas_refund_test.go @@ -38,3 +38,30 @@ func TestInboundRevertGasNotRefunded(t *testing.T) { require.Nil(t, utx.OutboundTx[0].PcRefundExecution, "INBOUND_REVERT must not attempt a gas refund — the user was never charged for it") } + +// The SVM gateway dropped gas_used from RevertUniversalTx, so a revert now votes +// "0". That has to settle exactly like any other revert: accepted by the vote +// handler and refunding nothing. "0" rather than "" matters — the vote handler +// rejects an empty gas_fee_used, and the value feeds the outbound ballot key. +func TestInboundRevertSettlesWithZeroGasFeeUsed(t *testing.T) { + chainApp, ctx, vals, utxId, ob, coreVals := setupOutboundVotingTest(t, 4) + + ob.TxType = uexecutortypes.TxType_INBOUND_REVERT + ob.GasFee = "1000" + ob.GasToken = "0x000000000000000000000000000000000000C0dE" + require.NoError(t, chainApp.UexecutorKeeper.UpdateOutbound(ctx, utxId, *ob)) + + for i := 0; i < 3; i++ { + valAddr, err := sdk.ValAddressFromBech32(coreVals[i].OperatorAddress) + require.NoError(t, err) + require.NoError(t, utils.ExecVoteOutbound( + t, ctx, chainApp, vals[i], sdk.AccAddress(valAddr).String(), utxId, ob, true, "", "0"), + "a revert voting gas_fee_used=0 must be accepted") + } + + utx, found, err := chainApp.UexecutorKeeper.GetUniversalTx(ctx, utxId) + require.NoError(t, err) + require.True(t, found) + require.Nil(t, utx.OutboundTx[0].PcRefundExecution, + "a revert refunds nothing regardless of the reported gas fee") +} diff --git a/universalClient/chains/common/event_processor_test.go b/universalClient/chains/common/event_processor_test.go index bd300c485..01606ca4b 100644 --- a/universalClient/chains/common/event_processor_test.go +++ b/universalClient/chains/common/event_processor_test.go @@ -1128,3 +1128,31 @@ func TestConstructInbound_RejectsEventWithoutData(t *testing.T) { require.Error(t, err) assert.Contains(t, err.Error(), "event data is missing") } + +// A revert observation carries no gas fee from the chain: the gateway dropped +// gas_used from RevertUniversalTx. The vote must still say "0" rather than empty, +// because core rejects an empty gas_fee_used outright and the value is part of +// the outbound ballot key, so every validator has to produce the same one. +func TestBuildOutboundObservation_EmptyGasFeeUsedBecomesZero(t *testing.T) { + processor := NewEventProcessor(nil, nil, "solana:devnet", true, true, zerolog.Nop()) + event := &store.Event{EventID: "sig:0", BlockHeight: 100} + + obs, err := processor.buildOutboundObservation(event, &OutboundEvent{ + TxID: "0x1234", + UniversalTxID: "0xabcd", + // GasFeeUsed intentionally unset, as a revert leaves it. + }) + require.NoError(t, err) + require.NotNil(t, obs) + + assert.Equal(t, "0", obs.GasFeeUsed, "an empty gas fee must not reach the vote") + assert.NotEmpty(t, obs.GasFeeUsed, "core rejects observed_tx.gas_fee_used when empty") + + // Same input twice must give the same value: it feeds the ballot key, so a + // non-deterministic default would split validators across ballots. + again, err := processor.buildOutboundObservation(event, &OutboundEvent{ + TxID: "0x1234", UniversalTxID: "0xabcd", + }) + require.NoError(t, err) + assert.Equal(t, obs.GasFeeUsed, again.GasFeeUsed) +} diff --git a/universalClient/chains/svm/event_parser.go b/universalClient/chains/svm/event_parser.go index cc56e8124..93fe1a567 100644 --- a/universalClient/chains/svm/event_parser.go +++ b/universalClient/chains/svm/event_parser.go @@ -111,12 +111,14 @@ func parseSendFundsEvent(log string, signature string, slot uint64, logIndex uin // - discriminator (8 bytes) // - sub_tx_id (32 bytes) // - universal_tx_id (32 bytes) -// The three outbound events diverge after universal_tx_id, so gas_used sits at -// a different offset in each: +// The events diverge after universal_tx_id: // // UniversalTxFinalized ... wrapper_address(32) gas_fee(8) gas_used -> 112 -// RevertUniversalTx ... revert_recipient(32) token(32) amount(8) -> 144 // FundsRescued ... token(32) amount(8) -> 112 +// RevertUniversalTx ... revert_recipient(32) token(32) amount(8) -> no gas_used +// +// Revert carries no gas_used, and core never refunds one (applyGasRefund returns +// early for INBOUND_REVERT), so nothing needs the value. func parseOutboundObservationEvent(log string, signature string, slot uint64, logIndex uint, eventType string, chainID string, logger zerolog.Logger) *store.Event { if !strings.HasPrefix(log, "Program data: ") { return nil @@ -128,21 +130,24 @@ func parseOutboundObservationEvent(log string, signature string, slot uint64, lo return nil } - gasUsedOffset := 0 + // -1 means the event carries no gas_used field. + gasUsedOffset := -1 switch eventType { case EventTypeFinalizeUniversalTx, EventTypeFundsRescued: gasUsedOffset = 112 case EventTypeRevertUniversalTx: - gasUsedOffset = 144 default: return nil } - // gas_used is the last field read, so one check covers the earlier ones. - if len(decoded) < gasUsedOffset+8 { + need := 72 // the shared prefix + if gasUsedOffset >= 0 { + need = gasUsedOffset + 8 + } + if len(decoded) < need { logger.Warn(). Int("data_len", len(decoded)). - Int("need", gasUsedOffset+8). + Int("need", need). Str("event_type", eventType). Msg("data too short for outboundObservation event") return nil @@ -161,13 +166,16 @@ func parseOutboundObservationEvent(log string, signature string, slot uint64, lo // Shared prefix: disc(8) sub_tx_id(32) universal_tx_id(32). txID := "0x" + hex.EncodeToString(decoded[8:40]) universalTxID := "0x" + hex.EncodeToString(decoded[40:72]) - gasUsed := binary.LittleEndian.Uint64(decoded[gasUsedOffset : gasUsedOffset+8]) + gasFeeUsed := "" + if gasUsedOffset >= 0 { + gasFeeUsed = fmt.Sprintf("%d", binary.LittleEndian.Uint64(decoded[gasUsedOffset:gasUsedOffset+8])) + } // Create OutboundEvent payload payload := common.OutboundEvent{ TxID: txID, UniversalTxID: universalTxID, - GasFeeUsed: fmt.Sprintf("%d", gasUsed), + GasFeeUsed: gasFeeUsed, } // Marshal payload to JSON @@ -195,7 +203,7 @@ func parseOutboundObservationEvent(log string, signature string, slot uint64, lo Str("event_id", eventID). Str("tx_id", txID). Str("universal_tx_id", universalTxID). - Str("gas_used", fmt.Sprintf("%d", gasUsed)). + Str("gas_fee_used", gasFeeUsed). Msg("parsed outboundObservation event") return event diff --git a/universalClient/chains/svm/event_parser_test.go b/universalClient/chains/svm/event_parser_test.go index 2c31e3fbd..cbaf6edef 100644 --- a/universalClient/chains/svm/event_parser_test.go +++ b/universalClient/chains/svm/event_parser_test.go @@ -204,8 +204,7 @@ func TestParseEvent_Routing(t *testing.T) { assert.Equal(t, store.EventTypeOutbound, event.Type) }) - t.Run("revert_universal_tx routes to outbound parser", func(t *testing.T) { - // Revert puts gas_used further in than finalize does. + t.Run("revert_universal_tx routes to outbound parser without a gas fee", func(t *testing.T) { revertLog := wrapAsLog(buildRevertPayload(txID, utxID, 5000)) event := ParseEvent(revertLog, sig, 100, 0, EventTypeRevertUniversalTx, chainID, logger) require.NotNil(t, event) @@ -213,12 +212,8 @@ func TestParseEvent_Routing(t *testing.T) { var outbound common.OutboundEvent require.NoError(t, json.Unmarshal(event.EventData, &outbound)) - assert.Equal(t, "5000", outbound.GasFeeUsed) - }) - - t.Run("a finalize-shaped payload is too short to be read as a revert", func(t *testing.T) { - assert.Nil(t, ParseEvent(outboundLog, sig, 100, 0, EventTypeRevertUniversalTx, chainID, logger), - "reading a revert at the finalize offset would report the wrong gas_used") + assert.Empty(t, outbound.GasFeeUsed, + "the revert event carries no gas_used; reading one would report revert_instruction bytes") }) t.Run("unknown event type returns nil", func(t *testing.T) { @@ -250,15 +245,15 @@ func TestParseOutboundObservationEvent_LengthIsPerEventType(t *testing.T) { assert.NotNil(t, ParseEvent(wrapAsLog(make([]byte, 120)), sig, 1, 0, EventTypeFundsRescued, chainID, logger)) }) - t.Run("revert needs 152 bytes", func(t *testing.T) { - assert.Nil(t, ParseEvent(wrapAsLog(make([]byte, 151)), sig, 1, 0, EventTypeRevertUniversalTx, chainID, logger)) - assert.NotNil(t, ParseEvent(wrapAsLog(buildRevertPayload(txID, utxID, 1)), sig, 1, 0, EventTypeRevertUniversalTx, chainID, logger)) + t.Run("revert needs only the shared prefix", func(t *testing.T) { + assert.Nil(t, ParseEvent(wrapAsLog(make([]byte, 71)), sig, 1, 0, EventTypeRevertUniversalTx, chainID, logger)) + assert.NotNil(t, ParseEvent(wrapAsLog(make([]byte, 72)), sig, 1, 0, EventTypeRevertUniversalTx, chainID, logger)) }) t.Run("an unroutable event type is refused", func(t *testing.T) { assert.Nil(t, parseOutboundObservationEvent( wrapAsLog(buildOutboundPayload(txID, utxID, 1)), sig, 1, 0, "send_funds", chainID, logger), - "only the three outbound events have a known gas_used offset") + "only the three outbound events are routable here") }) } @@ -273,7 +268,6 @@ func TestParseOutboundObservationEvent_ReadsItsOwnOffset(t *testing.T) { }{ {EventTypeFinalizeUniversalTx, buildOutboundPayload(txID, utxID, 4242)}, {EventTypeFundsRescued, buildOutboundPayload(txID, utxID, 4242)}, - {EventTypeRevertUniversalTx, buildRevertPayload(txID, utxID, 4242)}, } { t.Run(tc.eventType, func(t *testing.T) { event := ParseEvent(wrapAsLog(tc.payload), "sig", 1, 0, tc.eventType, "solana:devnet", logger) diff --git a/universalClient/chains/svm/tx_builder.go b/universalClient/chains/svm/tx_builder.go index cb472e06d..c211f8778 100644 --- a/universalClient/chains/svm/tx_builder.go +++ b/universalClient/chains/svm/tx_builder.go @@ -923,9 +923,6 @@ func (tb *TxBuilder) BuildOutboundTransaction( } // --- Assemble the Solana transaction --- - // Instructions in order: - // 1. SetComputeUnitLimit — tells the runtime how many compute units to allocate - // 2. The actual gateway instruction (withdraw/execute/revert) gatewayInstruction := solana.NewInstruction( tb.gatewayAddress, @@ -938,8 +935,8 @@ func (tb *TxBuilder) BuildOutboundTransaction( computeLimitIx := tb.buildSetComputeUnitLimitInstruction(defaultComputeUnitLimit) // Build the instruction list. - // The recipient ATA is created by the gateway, which meters the rent into - // gas_used. Creating it here left that cost outside the metered path. + // The gateway creates the recipient ATA and meters the rent, so creating it + // here would leave that cost outside gas_used. instructions := []solana.Instruction{computeLimitIx, gatewayInstruction} // Get a recent blockhash — Solana uses this instead of nonces for transaction expiry. @@ -2158,7 +2155,7 @@ func (tb *TxBuilder) buildRevertAccounts( if isNative { // SOL: optional SPL accounts are None (gateway program ID sentinel) - for i := 0; i < 4; i++ { + for i := 0; i < 6; i++ { accounts = append(accounts, &solana.AccountMeta{PublicKey: tb.gatewayAddress, IsWritable: false, IsSigner: false}) } } else { @@ -2176,6 +2173,9 @@ func (tb *TxBuilder) buildRevertAccounts( &solana.AccountMeta{PublicKey: recipientATA, IsWritable: true, IsSigner: false}, &solana.AccountMeta{PublicKey: mintPubkey, IsWritable: false, IsSigner: false}, &solana.AccountMeta{PublicKey: solana.TokenProgramID, IsWritable: false, IsSigner: false}, + // The gateway needs these to create the recipient ATA. + &solana.AccountMeta{PublicKey: solana.SPLAssociatedTokenAccountProgramID, IsWritable: false, IsSigner: false}, + &solana.AccountMeta{PublicKey: solana.SysVarRentPubkey, IsWritable: false, IsSigner: false}, ) } diff --git a/universalClient/chains/svm/tx_builder_test.go b/universalClient/chains/svm/tx_builder_test.go index c1f5cb3e6..d25a98dee 100644 --- a/universalClient/chains/svm/tx_builder_test.go +++ b/universalClient/chains/svm/tx_builder_test.go @@ -1,11 +1,11 @@ package svm import ( - "encoding/base64" "context" "crypto/ecdsa" crand "crypto/rand" "crypto/sha256" + "encoding/base64" "encoding/binary" "encoding/hex" "fmt" @@ -1227,6 +1227,72 @@ func TestBuildWithdrawAndExecuteAccounts(t *testing.T) { }) } +// The gateway can only create the recipient ATA if we hand it recipient_ata, +// rent and the ATA program. Dropping any of them fails only on chain. +func TestBuildWithdrawAndExecuteAccounts_SPLSlots(t *testing.T) { + builder := newTestBuilder(t) + + caller := solana.NewWallet().PublicKey() + config := solana.NewWallet().PublicKey() + vault := solana.NewWallet().PublicKey() + cea := solana.NewWallet().PublicKey() + tss := solana.NewWallet().PublicKey() + executed := solana.NewWallet().PublicKey() + recipient := solana.NewWallet().PublicKey() + mint := solana.NewWallet().PublicKey() + + accounts := builder.buildWithdrawAndExecuteAccounts( + caller, config, vault, cea, tss, executed, + solana.SystemProgramID, + false, 1, + recipient, mint, + nil, + solana.PublicKey{}, solana.PublicKey{}, + ) + require.Len(t, accounts, 20) + + wantVaultATA, _, err := solana.FindAssociatedTokenAddress(vault, mint) + require.NoError(t, err) + wantCeaATA, _, err := solana.FindAssociatedTokenAddress(cea, mint) + require.NoError(t, err) + wantRecipientATA, _, err := solana.FindAssociatedTokenAddress(recipient, mint) + require.NoError(t, err) + + for _, tc := range []struct { + slot int + name string + want solana.PublicKey + writable bool + }{ + {8, "recipient", recipient, true}, + {9, "vault_ata", wantVaultATA, true}, + {10, "cea_ata", wantCeaATA, true}, + {11, "mint", mint, false}, + {12, "token_program", solana.TokenProgramID, false}, + {13, "rent", solana.SysVarRentPubkey, false}, + {14, "associated_token_program", solana.SPLAssociatedTokenAccountProgramID, false}, + {15, "recipient_ata", wantRecipientATA, true}, + } { + assert.Equal(t, tc.want, accounts[tc.slot].PublicKey, "slot %d is %s", tc.slot, tc.name) + assert.Equal(t, tc.writable, accounts[tc.slot].IsWritable, "slot %d (%s) writability", tc.slot, tc.name) + assert.False(t, accounts[tc.slot].IsSigner, "slot %d (%s) must not sign", tc.slot, tc.name) + } + + t.Run("execute leaves recipient and recipient_ata unset", func(t *testing.T) { + exec := builder.buildWithdrawAndExecuteAccounts( + caller, config, vault, cea, tss, executed, + solana.NewWallet().PublicKey(), + false, 2, + recipient, mint, + nil, + solana.PublicKey{}, solana.PublicKey{}, + ) + assert.Equal(t, builder.gatewayAddress, exec[8].PublicKey, "recipient is None for execute") + assert.Equal(t, builder.gatewayAddress, exec[15].PublicKey, "recipient_ata is None for execute") + assert.Equal(t, wantCeaATA, exec[10].PublicKey, "cea_ata is still real for execute") + }) +} + func TestBuildRevertAccounts(t *testing.T) { builder := newTestBuilder(t) @@ -1239,10 +1305,10 @@ func TestBuildRevertAccounts(t *testing.T) { caller := solana.NewWallet().PublicKey() tokenMint := solana.NewWallet().PublicKey() - t.Run("SOL revert has 12 accounts (8 required + 4 None sentinels)", func(t *testing.T) { + t.Run("SOL revert has 14 accounts (8 required + 6 None sentinels)", func(t *testing.T) { accounts := builder.buildRevertAccounts(config, vault, feeVault, tss, recipient, executed, caller, true, solana.PublicKey{}) - assert.Len(t, accounts, 12) + assert.Len(t, accounts, 14) assert.Equal(t, config, accounts[0].PublicKey, "config") assert.False(t, accounts[0].IsWritable) assert.Equal(t, vault, accounts[1].PublicKey, "vault") @@ -1258,16 +1324,16 @@ func TestBuildRevertAccounts(t *testing.T) { assert.Equal(t, caller, accounts[6].PublicKey, "caller") assert.True(t, accounts[6].IsSigner) assert.Equal(t, solana.SystemProgramID, accounts[7].PublicKey, "system_program") - // SOL: 4 optional SPL accounts are gateway sentinel (None) - for i := 8; i < 12; i++ { + // SOL: 6 optional SPL accounts are gateway sentinel (None) + for i := 8; i < 14; i++ { assert.Equal(t, builder.gatewayAddress, accounts[i].PublicKey, "SOL sentinel account %d", i) } }) - t.Run("SPL revert has 12 accounts (8 required + 4 SPL accounts)", func(t *testing.T) { + t.Run("SPL revert has 14 accounts (8 required + 6 SPL accounts)", func(t *testing.T) { accounts := builder.buildRevertAccounts(config, vault, feeVault, tss, recipient, executed, caller, false, tokenMint) - assert.Len(t, accounts, 12) + assert.Len(t, accounts, 14) // First 8 same as SOL assert.Equal(t, config, accounts[0].PublicKey, "config") assert.Equal(t, vault, accounts[1].PublicKey, "vault") @@ -1277,11 +1343,18 @@ func TestBuildRevertAccounts(t *testing.T) { assert.Equal(t, executed, accounts[5].PublicKey, "executed_tx") assert.Equal(t, caller, accounts[6].PublicKey, "caller") assert.Equal(t, solana.SystemProgramID, accounts[7].PublicKey, "system_program") - // SPL: token_vault, recipient_token_account, token_mint, token_program + // token_vault, recipient_token_account, token_mint, token_program, + // associated_token_program, rent. assert.True(t, accounts[8].IsWritable, "token_vault should be writable") assert.True(t, accounts[9].IsWritable, "recipient_token_account should be writable") assert.Equal(t, tokenMint, accounts[10].PublicKey, "token_mint") assert.Equal(t, solana.TokenProgramID, accounts[11].PublicKey, "token_program") + assert.Equal(t, solana.SPLAssociatedTokenAccountProgramID, accounts[12].PublicKey, "associated_token_program") + assert.Equal(t, solana.SysVarRentPubkey, accounts[13].PublicKey, "rent") + + wantRecipientATA, _, err := solana.FindAssociatedTokenAddress(recipient, tokenMint) + require.NoError(t, err) + assert.Equal(t, wantRecipientATA, accounts[9].PublicKey, "recipient_token_account must be the canonical ATA") }) } @@ -2810,31 +2883,40 @@ func TestVerifyBroadcastedTx_NotFoundVersusRPCFailure(t *testing.T) { }) } -// The gateway creates the recipient ATA and meters the rent into gas_used. -// A create prepended here would put that cost outside the metered path, so the -// built transaction must carry only the compute limit and the gateway call. -func TestBuildOutboundTransaction_NoRecipientATACreate(t *testing.T) { +// The gateway creates the recipient ATA and meters the rent. Creating it here +// makes the gateway see it already present, leaving the rent outside gas_used +// (F-2026-18815). +func TestBuildOutboundTransaction_NoClientSideATACreate(t *testing.T) { ataProgram := solana.MustPublicKeyFromBase58("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL") for _, tc := range []struct { - name string - txType string - assetAddr string + name string + txType string + spl bool }{ - {"SPL withdraw", "FUNDS", solana.NewWallet().PublicKey().String()}, - {"SPL revert", "INBOUND_REVERT", solana.NewWallet().PublicKey().String()}, - {"native withdraw", "FUNDS", ""}, + {"SPL withdraw", "FUNDS", true}, + {"SPL revert", "INBOUND_REVERT", true}, + {"SPL rescue", "RESCUE_FUNDS", true}, + {"native withdraw", "FUNDS", false}, + {"native revert", "INBOUND_REVERT", false}, + {"native rescue", "RESCUE_FUNDS", false}, } { t.Run(tc.name, func(t *testing.T) { builder := newBlockhashOnlyBuilder(t) + recipient := solana.NewWallet().PublicKey() + mint := solana.NewWallet().PublicKey() + assetAddr := "" + if tc.spl { + assetAddr = mint.String() + } data := &uetypes.OutboundCreatedEvent{ TxID: "0x" + strings.Repeat("11", 32), UniversalTxId: "0x" + strings.Repeat("22", 32), DestinationChain: "solana:devnet", Sender: "0x" + strings.Repeat("33", 20), - Recipient: solana.NewWallet().PublicKey().String(), + Recipient: recipient.String(), Amount: "1000", - AssetAddr: tc.assetAddr, + AssetAddr: assetAddr, GasLimit: "400000", GasFee: "3000000", TxType: tc.txType, @@ -2842,16 +2924,30 @@ func TestBuildOutboundTransaction_NoRecipientATACreate(t *testing.T) { } req := &common.UnsignedSigningReq{SigningHash: make([]byte, 32), Nonce: 0} - tx, _, err := builder.BuildOutboundTransaction(context.Background(), req, data, make([]byte, 65)) + tx, instructionID, err := builder.BuildOutboundTransaction(context.Background(), req, data, make([]byte, 65)) require.NoError(t, err) require.NotNil(t, tx) - require.Len(t, tx.Message.Instructions, 2, "expected only compute limit and the gateway call") - for i, ix := range tx.Message.Instructions { - program, err := tx.Message.Program(ix.ProgramIDIndex) + require.Len(t, tx.Message.Instructions, 2, "compute limit and the gateway call only") + for i := range tx.Message.Instructions { + program, err := tx.Message.Program(tx.Message.Instructions[i].ProgramIDIndex) require.NoError(t, err) assert.NotEqual(t, ataProgram, program, "instruction %d creates an ATA", i) } + + // Revert and rescue still hand the gateway what it needs to create it. + if tc.spl && (instructionID == 3 || instructionID == 4) { + gatewayIx := tx.Message.Instructions[len(tx.Message.Instructions)-1] + metas, err := gatewayIx.ResolveInstructionAccounts(&tx.Message) + require.NoError(t, err) + require.Len(t, metas, 14) + + wantATA, _, err := solana.FindAssociatedTokenAddress(recipient, mint) + require.NoError(t, err) + assert.Equal(t, wantATA, metas[9].PublicKey, "recipient_token_account") + assert.Equal(t, solana.SPLAssociatedTokenAccountProgramID, metas[12].PublicKey, "associated_token_program") + assert.Equal(t, solana.SysVarRentPubkey, metas[13].PublicKey, "rent") + } }) } } @@ -2889,8 +2985,6 @@ func newBlockhashOnlyBuilder(t *testing.T) *TxBuilder { return builder } - - // --------------------------------------------------------------------------- // Empty-recipient parking sentinel (F-2026-18184) // @@ -3335,4 +3429,4 @@ func TestCheckParkedRecipientScope_AllowsEveryInstructionWhenNotParked(t *testin for id := uint8(0); id <= 5; id++ { require.NoError(t, checkParkedRecipientScope(false, id), "instruction_id=%d", id) } -} \ No newline at end of file +} From bbad4a6ae25c144a4183581eeec734458cf48d82 Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Wed, 2 Sep 2026 15:56:06 +0530 Subject: [PATCH 54/60] fix(uexecutor): bound remaining uint256 string parses; remove MsgMigrateUEA (#362) gas_limit and gas_fee_used now go through ValidateUint256String. MsgMigrateUEA and MigrationPayload are removed; migration no longer uses them. --- api/uexecutor/v1/tx.pulsar.go | 1491 ++---------------- api/uexecutor/v1/tx_grpc.pb.go | 39 - api/uexecutor/v1/types.pulsar.go | 1278 ++++----------- app/ante/account_init_decorator.go | 6 +- app/ante/account_init_signer_binding_test.go | 27 +- app/ante/account_init_validator_gate_test.go | 11 +- app/txpolicy/gasless.go | 1 - proto/uexecutor/v1/tx.proto | 24 - proto/uexecutor/v1/types.proto | 11 - x/uexecutor/keeper/evm.go | 34 - x/uexecutor/keeper/msg_migrate_uea.go | 84 - x/uexecutor/keeper/msg_server.go | 15 - x/uexecutor/keeper/msg_server_test.go | 77 - x/uexecutor/types/abi.go | 19 - x/uexecutor/types/migration_payload.go | 49 - x/uexecutor/types/migration_payload_test.go | 107 -- x/uexecutor/types/msg_migrate_uea.go | 89 -- x/uexecutor/types/msg_migrate_uea_test.go | 68 - x/uexecutor/types/msg_signer_length_test.go | 30 +- x/uexecutor/types/msg_vote_outbound.go | 5 + x/uexecutor/types/outbound_tx.go | 6 +- x/uexecutor/types/tx.pb.go | 634 +------- x/uexecutor/types/types.pb.go | 562 ++----- x/uexecutor/types/uint256_test.go | 177 +++ 24 files changed, 896 insertions(+), 3948 deletions(-) delete mode 100644 x/uexecutor/keeper/msg_migrate_uea.go delete mode 100644 x/uexecutor/types/migration_payload.go delete mode 100644 x/uexecutor/types/migration_payload_test.go delete mode 100644 x/uexecutor/types/msg_migrate_uea.go delete mode 100644 x/uexecutor/types/msg_migrate_uea_test.go diff --git a/api/uexecutor/v1/tx.pulsar.go b/api/uexecutor/v1/tx.pulsar.go index c6a198080..12b1f75da 100644 --- a/api/uexecutor/v1/tx.pulsar.go +++ b/api/uexecutor/v1/tx.pulsar.go @@ -1869,1004 +1869,6 @@ func (x *fastReflection_MsgExecutePayloadResponse) ProtoMethods() *protoiface.Me } } -var ( - md_MsgMigrateUEA protoreflect.MessageDescriptor - fd_MsgMigrateUEA_signer protoreflect.FieldDescriptor - fd_MsgMigrateUEA_universal_account_id protoreflect.FieldDescriptor - fd_MsgMigrateUEA_migration_payload protoreflect.FieldDescriptor - fd_MsgMigrateUEA_signature protoreflect.FieldDescriptor -) - -func init() { - file_uexecutor_v1_tx_proto_init() - md_MsgMigrateUEA = File_uexecutor_v1_tx_proto.Messages().ByName("MsgMigrateUEA") - fd_MsgMigrateUEA_signer = md_MsgMigrateUEA.Fields().ByName("signer") - fd_MsgMigrateUEA_universal_account_id = md_MsgMigrateUEA.Fields().ByName("universal_account_id") - fd_MsgMigrateUEA_migration_payload = md_MsgMigrateUEA.Fields().ByName("migration_payload") - fd_MsgMigrateUEA_signature = md_MsgMigrateUEA.Fields().ByName("signature") -} - -var _ protoreflect.Message = (*fastReflection_MsgMigrateUEA)(nil) - -type fastReflection_MsgMigrateUEA MsgMigrateUEA - -func (x *MsgMigrateUEA) ProtoReflect() protoreflect.Message { - return (*fastReflection_MsgMigrateUEA)(x) -} - -func (x *MsgMigrateUEA) slowProtoReflect() protoreflect.Message { - mi := &file_uexecutor_v1_tx_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -var _fastReflection_MsgMigrateUEA_messageType fastReflection_MsgMigrateUEA_messageType -var _ protoreflect.MessageType = fastReflection_MsgMigrateUEA_messageType{} - -type fastReflection_MsgMigrateUEA_messageType struct{} - -func (x fastReflection_MsgMigrateUEA_messageType) Zero() protoreflect.Message { - return (*fastReflection_MsgMigrateUEA)(nil) -} -func (x fastReflection_MsgMigrateUEA_messageType) New() protoreflect.Message { - return new(fastReflection_MsgMigrateUEA) -} -func (x fastReflection_MsgMigrateUEA_messageType) Descriptor() protoreflect.MessageDescriptor { - return md_MsgMigrateUEA -} - -// Descriptor returns message descriptor, which contains only the protobuf -// type information for the message. -func (x *fastReflection_MsgMigrateUEA) Descriptor() protoreflect.MessageDescriptor { - return md_MsgMigrateUEA -} - -// Type returns the message type, which encapsulates both Go and protobuf -// type information. If the Go type information is not needed, -// it is recommended that the message descriptor be used instead. -func (x *fastReflection_MsgMigrateUEA) Type() protoreflect.MessageType { - return _fastReflection_MsgMigrateUEA_messageType -} - -// New returns a newly allocated and mutable empty message. -func (x *fastReflection_MsgMigrateUEA) New() protoreflect.Message { - return new(fastReflection_MsgMigrateUEA) -} - -// Interface unwraps the message reflection interface and -// returns the underlying ProtoMessage interface. -func (x *fastReflection_MsgMigrateUEA) Interface() protoreflect.ProtoMessage { - return (*MsgMigrateUEA)(x) -} - -// Range iterates over every populated field in an undefined order, -// calling f for each field descriptor and value encountered. -// Range returns immediately if f returns false. -// While iterating, mutating operations may only be performed -// on the current field descriptor. -func (x *fastReflection_MsgMigrateUEA) Range(f func(protoreflect.FieldDescriptor, protoreflect.Value) bool) { - if x.Signer != "" { - value := protoreflect.ValueOfString(x.Signer) - if !f(fd_MsgMigrateUEA_signer, value) { - return - } - } - if x.UniversalAccountId != nil { - value := protoreflect.ValueOfMessage(x.UniversalAccountId.ProtoReflect()) - if !f(fd_MsgMigrateUEA_universal_account_id, value) { - return - } - } - if x.MigrationPayload != nil { - value := protoreflect.ValueOfMessage(x.MigrationPayload.ProtoReflect()) - if !f(fd_MsgMigrateUEA_migration_payload, value) { - return - } - } - if x.Signature != "" { - value := protoreflect.ValueOfString(x.Signature) - if !f(fd_MsgMigrateUEA_signature, value) { - return - } - } -} - -// Has reports whether a field is populated. -// -// Some fields have the property of nullability where it is possible to -// distinguish between the default value of a field and whether the field -// was explicitly populated with the default value. Singular message fields, -// member fields of a oneof, and proto2 scalar fields are nullable. Such -// fields are populated only if explicitly set. -// -// In other cases (aside from the nullable cases above), -// a proto3 scalar field is populated if it contains a non-zero value, and -// a repeated field is populated if it is non-empty. -func (x *fastReflection_MsgMigrateUEA) Has(fd protoreflect.FieldDescriptor) bool { - switch fd.FullName() { - case "uexecutor.v1.MsgMigrateUEA.signer": - return x.Signer != "" - case "uexecutor.v1.MsgMigrateUEA.universal_account_id": - return x.UniversalAccountId != nil - case "uexecutor.v1.MsgMigrateUEA.migration_payload": - return x.MigrationPayload != nil - case "uexecutor.v1.MsgMigrateUEA.signature": - return x.Signature != "" - default: - if fd.IsExtension() { - panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgMigrateUEA")) - } - panic(fmt.Errorf("message uexecutor.v1.MsgMigrateUEA does not contain field %s", fd.FullName())) - } -} - -// Clear clears the field such that a subsequent Has call reports false. -// -// Clearing an extension field clears both the extension type and value -// associated with the given field number. -// -// Clear is a mutating operation and unsafe for concurrent use. -func (x *fastReflection_MsgMigrateUEA) Clear(fd protoreflect.FieldDescriptor) { - switch fd.FullName() { - case "uexecutor.v1.MsgMigrateUEA.signer": - x.Signer = "" - case "uexecutor.v1.MsgMigrateUEA.universal_account_id": - x.UniversalAccountId = nil - case "uexecutor.v1.MsgMigrateUEA.migration_payload": - x.MigrationPayload = nil - case "uexecutor.v1.MsgMigrateUEA.signature": - x.Signature = "" - default: - if fd.IsExtension() { - panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgMigrateUEA")) - } - panic(fmt.Errorf("message uexecutor.v1.MsgMigrateUEA does not contain field %s", fd.FullName())) - } -} - -// Get retrieves the value for a field. -// -// For unpopulated scalars, it returns the default value, where -// the default value of a bytes scalar is guaranteed to be a copy. -// For unpopulated composite types, it returns an empty, read-only view -// of the value; to obtain a mutable reference, use Mutable. -func (x *fastReflection_MsgMigrateUEA) Get(descriptor protoreflect.FieldDescriptor) protoreflect.Value { - switch descriptor.FullName() { - case "uexecutor.v1.MsgMigrateUEA.signer": - value := x.Signer - return protoreflect.ValueOfString(value) - case "uexecutor.v1.MsgMigrateUEA.universal_account_id": - value := x.UniversalAccountId - return protoreflect.ValueOfMessage(value.ProtoReflect()) - case "uexecutor.v1.MsgMigrateUEA.migration_payload": - value := x.MigrationPayload - return protoreflect.ValueOfMessage(value.ProtoReflect()) - case "uexecutor.v1.MsgMigrateUEA.signature": - value := x.Signature - return protoreflect.ValueOfString(value) - default: - if descriptor.IsExtension() { - panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgMigrateUEA")) - } - panic(fmt.Errorf("message uexecutor.v1.MsgMigrateUEA does not contain field %s", descriptor.FullName())) - } -} - -// Set stores the value for a field. -// -// For a field belonging to a oneof, it implicitly clears any other field -// that may be currently set within the same oneof. -// For extension fields, it implicitly stores the provided ExtensionType. -// When setting a composite type, it is unspecified whether the stored value -// aliases the source's memory in any way. If the composite value is an -// empty, read-only value, then it panics. -// -// Set is a mutating operation and unsafe for concurrent use. -func (x *fastReflection_MsgMigrateUEA) Set(fd protoreflect.FieldDescriptor, value protoreflect.Value) { - switch fd.FullName() { - case "uexecutor.v1.MsgMigrateUEA.signer": - x.Signer = value.Interface().(string) - case "uexecutor.v1.MsgMigrateUEA.universal_account_id": - x.UniversalAccountId = value.Message().Interface().(*UniversalAccountId) - case "uexecutor.v1.MsgMigrateUEA.migration_payload": - x.MigrationPayload = value.Message().Interface().(*MigrationPayload) - case "uexecutor.v1.MsgMigrateUEA.signature": - x.Signature = value.Interface().(string) - default: - if fd.IsExtension() { - panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgMigrateUEA")) - } - panic(fmt.Errorf("message uexecutor.v1.MsgMigrateUEA does not contain field %s", fd.FullName())) - } -} - -// Mutable returns a mutable reference to a composite type. -// -// If the field is unpopulated, it may allocate a composite value. -// For a field belonging to a oneof, it implicitly clears any other field -// that may be currently set within the same oneof. -// For extension fields, it implicitly stores the provided ExtensionType -// if not already stored. -// It panics if the field does not contain a composite type. -// -// Mutable is a mutating operation and unsafe for concurrent use. -func (x *fastReflection_MsgMigrateUEA) Mutable(fd protoreflect.FieldDescriptor) protoreflect.Value { - switch fd.FullName() { - case "uexecutor.v1.MsgMigrateUEA.universal_account_id": - if x.UniversalAccountId == nil { - x.UniversalAccountId = new(UniversalAccountId) - } - return protoreflect.ValueOfMessage(x.UniversalAccountId.ProtoReflect()) - case "uexecutor.v1.MsgMigrateUEA.migration_payload": - if x.MigrationPayload == nil { - x.MigrationPayload = new(MigrationPayload) - } - return protoreflect.ValueOfMessage(x.MigrationPayload.ProtoReflect()) - case "uexecutor.v1.MsgMigrateUEA.signer": - panic(fmt.Errorf("field signer of message uexecutor.v1.MsgMigrateUEA is not mutable")) - case "uexecutor.v1.MsgMigrateUEA.signature": - panic(fmt.Errorf("field signature of message uexecutor.v1.MsgMigrateUEA is not mutable")) - default: - if fd.IsExtension() { - panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgMigrateUEA")) - } - panic(fmt.Errorf("message uexecutor.v1.MsgMigrateUEA does not contain field %s", fd.FullName())) - } -} - -// NewField returns a new value that is assignable to the field -// for the given descriptor. For scalars, this returns the default value. -// For lists, maps, and messages, this returns a new, empty, mutable value. -func (x *fastReflection_MsgMigrateUEA) NewField(fd protoreflect.FieldDescriptor) protoreflect.Value { - switch fd.FullName() { - case "uexecutor.v1.MsgMigrateUEA.signer": - return protoreflect.ValueOfString("") - case "uexecutor.v1.MsgMigrateUEA.universal_account_id": - m := new(UniversalAccountId) - return protoreflect.ValueOfMessage(m.ProtoReflect()) - case "uexecutor.v1.MsgMigrateUEA.migration_payload": - m := new(MigrationPayload) - return protoreflect.ValueOfMessage(m.ProtoReflect()) - case "uexecutor.v1.MsgMigrateUEA.signature": - return protoreflect.ValueOfString("") - default: - if fd.IsExtension() { - panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgMigrateUEA")) - } - panic(fmt.Errorf("message uexecutor.v1.MsgMigrateUEA does not contain field %s", fd.FullName())) - } -} - -// WhichOneof reports which field within the oneof is populated, -// returning nil if none are populated. -// It panics if the oneof descriptor does not belong to this message. -func (x *fastReflection_MsgMigrateUEA) WhichOneof(d protoreflect.OneofDescriptor) protoreflect.FieldDescriptor { - switch d.FullName() { - default: - panic(fmt.Errorf("%s is not a oneof field in uexecutor.v1.MsgMigrateUEA", d.FullName())) - } - panic("unreachable") -} - -// GetUnknown retrieves the entire list of unknown fields. -// The caller may only mutate the contents of the RawFields -// if the mutated bytes are stored back into the message with SetUnknown. -func (x *fastReflection_MsgMigrateUEA) GetUnknown() protoreflect.RawFields { - return x.unknownFields -} - -// SetUnknown stores an entire list of unknown fields. -// The raw fields must be syntactically valid according to the wire format. -// An implementation may panic if this is not the case. -// Once stored, the caller must not mutate the content of the RawFields. -// An empty RawFields may be passed to clear the fields. -// -// SetUnknown is a mutating operation and unsafe for concurrent use. -func (x *fastReflection_MsgMigrateUEA) SetUnknown(fields protoreflect.RawFields) { - x.unknownFields = fields -} - -// IsValid reports whether the message is valid. -// -// An invalid message is an empty, read-only value. -// -// An invalid message often corresponds to a nil pointer of the concrete -// message type, but the details are implementation dependent. -// Validity is not part of the protobuf data model, and may not -// be preserved in marshaling or other operations. -func (x *fastReflection_MsgMigrateUEA) IsValid() bool { - return x != nil -} - -// ProtoMethods returns optional fastReflectionFeature-path implementations of various operations. -// This method may return nil. -// -// The returned methods type is identical to -// "google.golang.org/protobuf/runtime/protoiface".Methods. -// Consult the protoiface package documentation for details. -func (x *fastReflection_MsgMigrateUEA) ProtoMethods() *protoiface.Methods { - size := func(input protoiface.SizeInput) protoiface.SizeOutput { - x := input.Message.Interface().(*MsgMigrateUEA) - if x == nil { - return protoiface.SizeOutput{ - NoUnkeyedLiterals: input.NoUnkeyedLiterals, - Size: 0, - } - } - options := runtime.SizeInputToOptions(input) - _ = options - var n int - var l int - _ = l - l = len(x.Signer) - if l > 0 { - n += 1 + l + runtime.Sov(uint64(l)) - } - if x.UniversalAccountId != nil { - l = options.Size(x.UniversalAccountId) - n += 1 + l + runtime.Sov(uint64(l)) - } - if x.MigrationPayload != nil { - l = options.Size(x.MigrationPayload) - n += 1 + l + runtime.Sov(uint64(l)) - } - l = len(x.Signature) - if l > 0 { - n += 1 + l + runtime.Sov(uint64(l)) - } - if x.unknownFields != nil { - n += len(x.unknownFields) - } - return protoiface.SizeOutput{ - NoUnkeyedLiterals: input.NoUnkeyedLiterals, - Size: n, - } - } - - marshal := func(input protoiface.MarshalInput) (protoiface.MarshalOutput, error) { - x := input.Message.Interface().(*MsgMigrateUEA) - if x == nil { - return protoiface.MarshalOutput{ - NoUnkeyedLiterals: input.NoUnkeyedLiterals, - Buf: input.Buf, - }, nil - } - options := runtime.MarshalInputToOptions(input) - _ = options - size := options.Size(x) - dAtA := make([]byte, size) - i := len(dAtA) - _ = i - var l int - _ = l - if x.unknownFields != nil { - i -= len(x.unknownFields) - copy(dAtA[i:], x.unknownFields) - } - if len(x.Signature) > 0 { - i -= len(x.Signature) - copy(dAtA[i:], x.Signature) - i = runtime.EncodeVarint(dAtA, i, uint64(len(x.Signature))) - i-- - dAtA[i] = 0x22 - } - if x.MigrationPayload != nil { - encoded, err := options.Marshal(x.MigrationPayload) - if err != nil { - return protoiface.MarshalOutput{ - NoUnkeyedLiterals: input.NoUnkeyedLiterals, - Buf: input.Buf, - }, err - } - i -= len(encoded) - copy(dAtA[i:], encoded) - i = runtime.EncodeVarint(dAtA, i, uint64(len(encoded))) - i-- - dAtA[i] = 0x1a - } - if x.UniversalAccountId != nil { - encoded, err := options.Marshal(x.UniversalAccountId) - if err != nil { - return protoiface.MarshalOutput{ - NoUnkeyedLiterals: input.NoUnkeyedLiterals, - Buf: input.Buf, - }, err - } - i -= len(encoded) - copy(dAtA[i:], encoded) - i = runtime.EncodeVarint(dAtA, i, uint64(len(encoded))) - i-- - dAtA[i] = 0x12 - } - if len(x.Signer) > 0 { - i -= len(x.Signer) - copy(dAtA[i:], x.Signer) - i = runtime.EncodeVarint(dAtA, i, uint64(len(x.Signer))) - i-- - dAtA[i] = 0xa - } - if input.Buf != nil { - input.Buf = append(input.Buf, dAtA...) - } else { - input.Buf = dAtA - } - return protoiface.MarshalOutput{ - NoUnkeyedLiterals: input.NoUnkeyedLiterals, - Buf: input.Buf, - }, nil - } - unmarshal := func(input protoiface.UnmarshalInput) (protoiface.UnmarshalOutput, error) { - x := input.Message.Interface().(*MsgMigrateUEA) - if x == nil { - return protoiface.UnmarshalOutput{ - NoUnkeyedLiterals: input.NoUnkeyedLiterals, - Flags: input.Flags, - }, nil - } - options := runtime.UnmarshalInputToOptions(input) - _ = options - dAtA := input.Buf - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow - } - if iNdEx >= l { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: MsgMigrateUEA: wiretype end group for non-group") - } - if fieldNum <= 0 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: MsgMigrateUEA: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow - } - if iNdEx >= l { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength - } - if postIndex > l { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF - } - x.Signer = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field UniversalAccountId", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow - } - if iNdEx >= l { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength - } - if postIndex > l { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF - } - if x.UniversalAccountId == nil { - x.UniversalAccountId = &UniversalAccountId{} - } - if err := options.Unmarshal(dAtA[iNdEx:postIndex], x.UniversalAccountId); err != nil { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, err - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field MigrationPayload", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow - } - if iNdEx >= l { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength - } - if postIndex > l { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF - } - if x.MigrationPayload == nil { - x.MigrationPayload = &MigrationPayload{} - } - if err := options.Unmarshal(dAtA[iNdEx:postIndex], x.MigrationPayload); err != nil { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, err - } - iNdEx = postIndex - case 4: - if wireType != 2 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field Signature", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow - } - if iNdEx >= l { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength - } - if postIndex > l { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF - } - x.Signature = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := runtime.Skip(dAtA[iNdEx:]) - if err != nil { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength - } - if (iNdEx + skippy) > l { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF - } - if !options.DiscardUnknown { - x.unknownFields = append(x.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - } - iNdEx += skippy - } - } - - if iNdEx > l { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF - } - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, nil - } - return &protoiface.Methods{ - NoUnkeyedLiterals: struct{}{}, - Flags: protoiface.SupportMarshalDeterministic | protoiface.SupportUnmarshalDiscardUnknown, - Size: size, - Marshal: marshal, - Unmarshal: unmarshal, - Merge: nil, - CheckInitialized: nil, - } -} - -var ( - md_MsgMigrateUEAResponse protoreflect.MessageDescriptor -) - -func init() { - file_uexecutor_v1_tx_proto_init() - md_MsgMigrateUEAResponse = File_uexecutor_v1_tx_proto.Messages().ByName("MsgMigrateUEAResponse") -} - -var _ protoreflect.Message = (*fastReflection_MsgMigrateUEAResponse)(nil) - -type fastReflection_MsgMigrateUEAResponse MsgMigrateUEAResponse - -func (x *MsgMigrateUEAResponse) ProtoReflect() protoreflect.Message { - return (*fastReflection_MsgMigrateUEAResponse)(x) -} - -func (x *MsgMigrateUEAResponse) slowProtoReflect() protoreflect.Message { - mi := &file_uexecutor_v1_tx_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -var _fastReflection_MsgMigrateUEAResponse_messageType fastReflection_MsgMigrateUEAResponse_messageType -var _ protoreflect.MessageType = fastReflection_MsgMigrateUEAResponse_messageType{} - -type fastReflection_MsgMigrateUEAResponse_messageType struct{} - -func (x fastReflection_MsgMigrateUEAResponse_messageType) Zero() protoreflect.Message { - return (*fastReflection_MsgMigrateUEAResponse)(nil) -} -func (x fastReflection_MsgMigrateUEAResponse_messageType) New() protoreflect.Message { - return new(fastReflection_MsgMigrateUEAResponse) -} -func (x fastReflection_MsgMigrateUEAResponse_messageType) Descriptor() protoreflect.MessageDescriptor { - return md_MsgMigrateUEAResponse -} - -// Descriptor returns message descriptor, which contains only the protobuf -// type information for the message. -func (x *fastReflection_MsgMigrateUEAResponse) Descriptor() protoreflect.MessageDescriptor { - return md_MsgMigrateUEAResponse -} - -// Type returns the message type, which encapsulates both Go and protobuf -// type information. If the Go type information is not needed, -// it is recommended that the message descriptor be used instead. -func (x *fastReflection_MsgMigrateUEAResponse) Type() protoreflect.MessageType { - return _fastReflection_MsgMigrateUEAResponse_messageType -} - -// New returns a newly allocated and mutable empty message. -func (x *fastReflection_MsgMigrateUEAResponse) New() protoreflect.Message { - return new(fastReflection_MsgMigrateUEAResponse) -} - -// Interface unwraps the message reflection interface and -// returns the underlying ProtoMessage interface. -func (x *fastReflection_MsgMigrateUEAResponse) Interface() protoreflect.ProtoMessage { - return (*MsgMigrateUEAResponse)(x) -} - -// Range iterates over every populated field in an undefined order, -// calling f for each field descriptor and value encountered. -// Range returns immediately if f returns false. -// While iterating, mutating operations may only be performed -// on the current field descriptor. -func (x *fastReflection_MsgMigrateUEAResponse) Range(f func(protoreflect.FieldDescriptor, protoreflect.Value) bool) { -} - -// Has reports whether a field is populated. -// -// Some fields have the property of nullability where it is possible to -// distinguish between the default value of a field and whether the field -// was explicitly populated with the default value. Singular message fields, -// member fields of a oneof, and proto2 scalar fields are nullable. Such -// fields are populated only if explicitly set. -// -// In other cases (aside from the nullable cases above), -// a proto3 scalar field is populated if it contains a non-zero value, and -// a repeated field is populated if it is non-empty. -func (x *fastReflection_MsgMigrateUEAResponse) Has(fd protoreflect.FieldDescriptor) bool { - switch fd.FullName() { - default: - if fd.IsExtension() { - panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgMigrateUEAResponse")) - } - panic(fmt.Errorf("message uexecutor.v1.MsgMigrateUEAResponse does not contain field %s", fd.FullName())) - } -} - -// Clear clears the field such that a subsequent Has call reports false. -// -// Clearing an extension field clears both the extension type and value -// associated with the given field number. -// -// Clear is a mutating operation and unsafe for concurrent use. -func (x *fastReflection_MsgMigrateUEAResponse) Clear(fd protoreflect.FieldDescriptor) { - switch fd.FullName() { - default: - if fd.IsExtension() { - panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgMigrateUEAResponse")) - } - panic(fmt.Errorf("message uexecutor.v1.MsgMigrateUEAResponse does not contain field %s", fd.FullName())) - } -} - -// Get retrieves the value for a field. -// -// For unpopulated scalars, it returns the default value, where -// the default value of a bytes scalar is guaranteed to be a copy. -// For unpopulated composite types, it returns an empty, read-only view -// of the value; to obtain a mutable reference, use Mutable. -func (x *fastReflection_MsgMigrateUEAResponse) Get(descriptor protoreflect.FieldDescriptor) protoreflect.Value { - switch descriptor.FullName() { - default: - if descriptor.IsExtension() { - panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgMigrateUEAResponse")) - } - panic(fmt.Errorf("message uexecutor.v1.MsgMigrateUEAResponse does not contain field %s", descriptor.FullName())) - } -} - -// Set stores the value for a field. -// -// For a field belonging to a oneof, it implicitly clears any other field -// that may be currently set within the same oneof. -// For extension fields, it implicitly stores the provided ExtensionType. -// When setting a composite type, it is unspecified whether the stored value -// aliases the source's memory in any way. If the composite value is an -// empty, read-only value, then it panics. -// -// Set is a mutating operation and unsafe for concurrent use. -func (x *fastReflection_MsgMigrateUEAResponse) Set(fd protoreflect.FieldDescriptor, value protoreflect.Value) { - switch fd.FullName() { - default: - if fd.IsExtension() { - panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgMigrateUEAResponse")) - } - panic(fmt.Errorf("message uexecutor.v1.MsgMigrateUEAResponse does not contain field %s", fd.FullName())) - } -} - -// Mutable returns a mutable reference to a composite type. -// -// If the field is unpopulated, it may allocate a composite value. -// For a field belonging to a oneof, it implicitly clears any other field -// that may be currently set within the same oneof. -// For extension fields, it implicitly stores the provided ExtensionType -// if not already stored. -// It panics if the field does not contain a composite type. -// -// Mutable is a mutating operation and unsafe for concurrent use. -func (x *fastReflection_MsgMigrateUEAResponse) Mutable(fd protoreflect.FieldDescriptor) protoreflect.Value { - switch fd.FullName() { - default: - if fd.IsExtension() { - panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgMigrateUEAResponse")) - } - panic(fmt.Errorf("message uexecutor.v1.MsgMigrateUEAResponse does not contain field %s", fd.FullName())) - } -} - -// NewField returns a new value that is assignable to the field -// for the given descriptor. For scalars, this returns the default value. -// For lists, maps, and messages, this returns a new, empty, mutable value. -func (x *fastReflection_MsgMigrateUEAResponse) NewField(fd protoreflect.FieldDescriptor) protoreflect.Value { - switch fd.FullName() { - default: - if fd.IsExtension() { - panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgMigrateUEAResponse")) - } - panic(fmt.Errorf("message uexecutor.v1.MsgMigrateUEAResponse does not contain field %s", fd.FullName())) - } -} - -// WhichOneof reports which field within the oneof is populated, -// returning nil if none are populated. -// It panics if the oneof descriptor does not belong to this message. -func (x *fastReflection_MsgMigrateUEAResponse) WhichOneof(d protoreflect.OneofDescriptor) protoreflect.FieldDescriptor { - switch d.FullName() { - default: - panic(fmt.Errorf("%s is not a oneof field in uexecutor.v1.MsgMigrateUEAResponse", d.FullName())) - } - panic("unreachable") -} - -// GetUnknown retrieves the entire list of unknown fields. -// The caller may only mutate the contents of the RawFields -// if the mutated bytes are stored back into the message with SetUnknown. -func (x *fastReflection_MsgMigrateUEAResponse) GetUnknown() protoreflect.RawFields { - return x.unknownFields -} - -// SetUnknown stores an entire list of unknown fields. -// The raw fields must be syntactically valid according to the wire format. -// An implementation may panic if this is not the case. -// Once stored, the caller must not mutate the content of the RawFields. -// An empty RawFields may be passed to clear the fields. -// -// SetUnknown is a mutating operation and unsafe for concurrent use. -func (x *fastReflection_MsgMigrateUEAResponse) SetUnknown(fields protoreflect.RawFields) { - x.unknownFields = fields -} - -// IsValid reports whether the message is valid. -// -// An invalid message is an empty, read-only value. -// -// An invalid message often corresponds to a nil pointer of the concrete -// message type, but the details are implementation dependent. -// Validity is not part of the protobuf data model, and may not -// be preserved in marshaling or other operations. -func (x *fastReflection_MsgMigrateUEAResponse) IsValid() bool { - return x != nil -} - -// ProtoMethods returns optional fastReflectionFeature-path implementations of various operations. -// This method may return nil. -// -// The returned methods type is identical to -// "google.golang.org/protobuf/runtime/protoiface".Methods. -// Consult the protoiface package documentation for details. -func (x *fastReflection_MsgMigrateUEAResponse) ProtoMethods() *protoiface.Methods { - size := func(input protoiface.SizeInput) protoiface.SizeOutput { - x := input.Message.Interface().(*MsgMigrateUEAResponse) - if x == nil { - return protoiface.SizeOutput{ - NoUnkeyedLiterals: input.NoUnkeyedLiterals, - Size: 0, - } - } - options := runtime.SizeInputToOptions(input) - _ = options - var n int - var l int - _ = l - if x.unknownFields != nil { - n += len(x.unknownFields) - } - return protoiface.SizeOutput{ - NoUnkeyedLiterals: input.NoUnkeyedLiterals, - Size: n, - } - } - - marshal := func(input protoiface.MarshalInput) (protoiface.MarshalOutput, error) { - x := input.Message.Interface().(*MsgMigrateUEAResponse) - if x == nil { - return protoiface.MarshalOutput{ - NoUnkeyedLiterals: input.NoUnkeyedLiterals, - Buf: input.Buf, - }, nil - } - options := runtime.MarshalInputToOptions(input) - _ = options - size := options.Size(x) - dAtA := make([]byte, size) - i := len(dAtA) - _ = i - var l int - _ = l - if x.unknownFields != nil { - i -= len(x.unknownFields) - copy(dAtA[i:], x.unknownFields) - } - if input.Buf != nil { - input.Buf = append(input.Buf, dAtA...) - } else { - input.Buf = dAtA - } - return protoiface.MarshalOutput{ - NoUnkeyedLiterals: input.NoUnkeyedLiterals, - Buf: input.Buf, - }, nil - } - unmarshal := func(input protoiface.UnmarshalInput) (protoiface.UnmarshalOutput, error) { - x := input.Message.Interface().(*MsgMigrateUEAResponse) - if x == nil { - return protoiface.UnmarshalOutput{ - NoUnkeyedLiterals: input.NoUnkeyedLiterals, - Flags: input.Flags, - }, nil - } - options := runtime.UnmarshalInputToOptions(input) - _ = options - dAtA := input.Buf - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow - } - if iNdEx >= l { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: MsgMigrateUEAResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: MsgMigrateUEAResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := runtime.Skip(dAtA[iNdEx:]) - if err != nil { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength - } - if (iNdEx + skippy) > l { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF - } - if !options.DiscardUnknown { - x.unknownFields = append(x.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - } - iNdEx += skippy - } - } - - if iNdEx > l { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF - } - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, nil - } - return &protoiface.Methods{ - NoUnkeyedLiterals: struct{}{}, - Flags: protoiface.SupportMarshalDeterministic | protoiface.SupportUnmarshalDiscardUnknown, - Size: size, - Marshal: marshal, - Unmarshal: unmarshal, - Merge: nil, - CheckInitialized: nil, - } -} - var ( md_MsgVoteInbound protoreflect.MessageDescriptor fd_MsgVoteInbound_signer protoreflect.FieldDescriptor @@ -2889,7 +1891,7 @@ func (x *MsgVoteInbound) ProtoReflect() protoreflect.Message { } func (x *MsgVoteInbound) slowProtoReflect() protoreflect.Message { - mi := &file_uexecutor_v1_tx_proto_msgTypes[6] + mi := &file_uexecutor_v1_tx_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3384,7 +2386,7 @@ func (x *MsgVoteInboundResponse) ProtoReflect() protoreflect.Message { } func (x *MsgVoteInboundResponse) slowProtoReflect() protoreflect.Message { - mi := &file_uexecutor_v1_tx_proto_msgTypes[7] + mi := &file_uexecutor_v1_tx_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3748,7 +2750,7 @@ func (x *MsgVoteOutbound) ProtoReflect() protoreflect.Message { } func (x *MsgVoteOutbound) slowProtoReflect() protoreflect.Message { - mi := &file_uexecutor_v1_tx_proto_msgTypes[8] + mi := &file_uexecutor_v1_tx_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4367,7 +3369,7 @@ func (x *MsgVoteOutboundResponse) ProtoReflect() protoreflect.Message { } func (x *MsgVoteOutboundResponse) slowProtoReflect() protoreflect.Message { - mi := &file_uexecutor_v1_tx_proto_msgTypes[9] + mi := &file_uexecutor_v1_tx_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4731,7 +3733,7 @@ func (x *MsgVoteChainMeta) ProtoReflect() protoreflect.Message { } func (x *MsgVoteChainMeta) slowProtoReflect() protoreflect.Message { - mi := &file_uexecutor_v1_tx_proto_msgTypes[10] + mi := &file_uexecutor_v1_tx_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5303,7 +4305,7 @@ func (x *MsgVoteChainMetaResponse) ProtoReflect() protoreflect.Message { } func (x *MsgVoteChainMetaResponse) slowProtoReflect() protoreflect.Message { - mi := &file_uexecutor_v1_tx_proto_msgTypes[11] + mi := &file_uexecutor_v1_tx_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5663,7 +4665,7 @@ func (x *MsgRevertStuckInbound) ProtoReflect() protoreflect.Message { } func (x *MsgRevertStuckInbound) slowProtoReflect() protoreflect.Message { - mi := &file_uexecutor_v1_tx_proto_msgTypes[12] + mi := &file_uexecutor_v1_tx_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6162,7 +5164,7 @@ func (x *MsgRevertStuckInboundResponse) ProtoReflect() protoreflect.Message { } func (x *MsgRevertStuckInboundResponse) slowProtoReflect() protoreflect.Message { - mi := &file_uexecutor_v1_tx_proto_msgTypes[13] + mi := &file_uexecutor_v1_tx_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6808,97 +5810,6 @@ func (*MsgExecutePayloadResponse) Descriptor() ([]byte, []int) { return file_uexecutor_v1_tx_proto_rawDescGZIP(), []int{3} } -// MsgMigrateUEA defines a message for migarting Universal Executor Account (UEA) -type MsgMigrateUEA struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // signer is the Cosmos address initiating the tx (used for tx signing) - Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` - // universal_account_id is the identifier of the owner account - UniversalAccountId *UniversalAccountId `protobuf:"bytes,2,opt,name=universal_account_id,json=universalAccountId,proto3" json:"universal_account_id,omitempty"` - // payload is the migration payload to be executed - MigrationPayload *MigrationPayload `protobuf:"bytes,3,opt,name=migration_payload,json=migrationPayload,proto3" json:"migration_payload,omitempty"` - // signature is the bytes passed as verifier data for the given payload. - Signature string `protobuf:"bytes,4,opt,name=signature,proto3" json:"signature,omitempty"` -} - -func (x *MsgMigrateUEA) Reset() { - *x = MsgMigrateUEA{} - if protoimpl.UnsafeEnabled { - mi := &file_uexecutor_v1_tx_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *MsgMigrateUEA) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*MsgMigrateUEA) ProtoMessage() {} - -// Deprecated: Use MsgMigrateUEA.ProtoReflect.Descriptor instead. -func (*MsgMigrateUEA) Descriptor() ([]byte, []int) { - return file_uexecutor_v1_tx_proto_rawDescGZIP(), []int{4} -} - -func (x *MsgMigrateUEA) GetSigner() string { - if x != nil { - return x.Signer - } - return "" -} - -func (x *MsgMigrateUEA) GetUniversalAccountId() *UniversalAccountId { - if x != nil { - return x.UniversalAccountId - } - return nil -} - -func (x *MsgMigrateUEA) GetMigrationPayload() *MigrationPayload { - if x != nil { - return x.MigrationPayload - } - return nil -} - -func (x *MsgMigrateUEA) GetSignature() string { - if x != nil { - return x.Signature - } - return "" -} - -// MsgMigrateUEAResponse defines the response for MsgMigrateUEA. -type MsgMigrateUEAResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *MsgMigrateUEAResponse) Reset() { - *x = MsgMigrateUEAResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_uexecutor_v1_tx_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *MsgMigrateUEAResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*MsgMigrateUEAResponse) ProtoMessage() {} - -// Deprecated: Use MsgMigrateUEAResponse.ProtoReflect.Descriptor instead. -func (*MsgMigrateUEAResponse) Descriptor() ([]byte, []int) { - return file_uexecutor_v1_tx_proto_rawDescGZIP(), []int{5} -} - // MsgVoteInbound allows a universal validator to vote on an inbound transfer. type MsgVoteInbound struct { state protoimpl.MessageState @@ -6913,7 +5824,7 @@ type MsgVoteInbound struct { func (x *MsgVoteInbound) Reset() { *x = MsgVoteInbound{} if protoimpl.UnsafeEnabled { - mi := &file_uexecutor_v1_tx_proto_msgTypes[6] + mi := &file_uexecutor_v1_tx_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6927,7 +5838,7 @@ func (*MsgVoteInbound) ProtoMessage() {} // Deprecated: Use MsgVoteInbound.ProtoReflect.Descriptor instead. func (*MsgVoteInbound) Descriptor() ([]byte, []int) { - return file_uexecutor_v1_tx_proto_rawDescGZIP(), []int{6} + return file_uexecutor_v1_tx_proto_rawDescGZIP(), []int{4} } func (x *MsgVoteInbound) GetSigner() string { @@ -6954,7 +5865,7 @@ type MsgVoteInboundResponse struct { func (x *MsgVoteInboundResponse) Reset() { *x = MsgVoteInboundResponse{} if protoimpl.UnsafeEnabled { - mi := &file_uexecutor_v1_tx_proto_msgTypes[7] + mi := &file_uexecutor_v1_tx_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6968,7 +5879,7 @@ func (*MsgVoteInboundResponse) ProtoMessage() {} // Deprecated: Use MsgVoteInboundResponse.ProtoReflect.Descriptor instead. func (*MsgVoteInboundResponse) Descriptor() ([]byte, []int) { - return file_uexecutor_v1_tx_proto_rawDescGZIP(), []int{7} + return file_uexecutor_v1_tx_proto_rawDescGZIP(), []int{5} } // MsgVoteOutbound allows a universal validator to vote on an outbound tx observation. @@ -6987,7 +5898,7 @@ type MsgVoteOutbound struct { func (x *MsgVoteOutbound) Reset() { *x = MsgVoteOutbound{} if protoimpl.UnsafeEnabled { - mi := &file_uexecutor_v1_tx_proto_msgTypes[8] + mi := &file_uexecutor_v1_tx_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7001,7 +5912,7 @@ func (*MsgVoteOutbound) ProtoMessage() {} // Deprecated: Use MsgVoteOutbound.ProtoReflect.Descriptor instead. func (*MsgVoteOutbound) Descriptor() ([]byte, []int) { - return file_uexecutor_v1_tx_proto_rawDescGZIP(), []int{8} + return file_uexecutor_v1_tx_proto_rawDescGZIP(), []int{6} } func (x *MsgVoteOutbound) GetSigner() string { @@ -7042,7 +5953,7 @@ type MsgVoteOutboundResponse struct { func (x *MsgVoteOutboundResponse) Reset() { *x = MsgVoteOutboundResponse{} if protoimpl.UnsafeEnabled { - mi := &file_uexecutor_v1_tx_proto_msgTypes[9] + mi := &file_uexecutor_v1_tx_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7056,7 +5967,7 @@ func (*MsgVoteOutboundResponse) ProtoMessage() {} // Deprecated: Use MsgVoteOutboundResponse.ProtoReflect.Descriptor instead. func (*MsgVoteOutboundResponse) Descriptor() ([]byte, []int) { - return file_uexecutor_v1_tx_proto_rawDescGZIP(), []int{9} + return file_uexecutor_v1_tx_proto_rawDescGZIP(), []int{7} } // MsgVoteChainMeta is broadcasted by Universal Validators to submit observed chain metadata (gas price + block height) @@ -7074,7 +5985,7 @@ type MsgVoteChainMeta struct { func (x *MsgVoteChainMeta) Reset() { *x = MsgVoteChainMeta{} if protoimpl.UnsafeEnabled { - mi := &file_uexecutor_v1_tx_proto_msgTypes[10] + mi := &file_uexecutor_v1_tx_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7088,7 +5999,7 @@ func (*MsgVoteChainMeta) ProtoMessage() {} // Deprecated: Use MsgVoteChainMeta.ProtoReflect.Descriptor instead. func (*MsgVoteChainMeta) Descriptor() ([]byte, []int) { - return file_uexecutor_v1_tx_proto_rawDescGZIP(), []int{10} + return file_uexecutor_v1_tx_proto_rawDescGZIP(), []int{8} } func (x *MsgVoteChainMeta) GetSigner() string { @@ -7129,7 +6040,7 @@ type MsgVoteChainMetaResponse struct { func (x *MsgVoteChainMetaResponse) Reset() { *x = MsgVoteChainMetaResponse{} if protoimpl.UnsafeEnabled { - mi := &file_uexecutor_v1_tx_proto_msgTypes[11] + mi := &file_uexecutor_v1_tx_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7143,7 +6054,7 @@ func (*MsgVoteChainMetaResponse) ProtoMessage() {} // Deprecated: Use MsgVoteChainMetaResponse.ProtoReflect.Descriptor instead. func (*MsgVoteChainMetaResponse) Descriptor() ([]byte, []int) { - return file_uexecutor_v1_tx_proto_rawDescGZIP(), []int{11} + return file_uexecutor_v1_tx_proto_rawDescGZIP(), []int{9} } // MsgRevertStuckInbound is an admin escape hatch. For an inbound whose ballot @@ -7165,7 +6076,7 @@ type MsgRevertStuckInbound struct { func (x *MsgRevertStuckInbound) Reset() { *x = MsgRevertStuckInbound{} if protoimpl.UnsafeEnabled { - mi := &file_uexecutor_v1_tx_proto_msgTypes[12] + mi := &file_uexecutor_v1_tx_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7179,7 +6090,7 @@ func (*MsgRevertStuckInbound) ProtoMessage() {} // Deprecated: Use MsgRevertStuckInbound.ProtoReflect.Descriptor instead. func (*MsgRevertStuckInbound) Descriptor() ([]byte, []int) { - return file_uexecutor_v1_tx_proto_rawDescGZIP(), []int{12} + return file_uexecutor_v1_tx_proto_rawDescGZIP(), []int{10} } func (x *MsgRevertStuckInbound) GetSigner() string { @@ -7208,7 +6119,7 @@ type MsgRevertStuckInboundResponse struct { func (x *MsgRevertStuckInboundResponse) Reset() { *x = MsgRevertStuckInboundResponse{} if protoimpl.UnsafeEnabled { - mi := &file_uexecutor_v1_tx_proto_msgTypes[13] + mi := &file_uexecutor_v1_tx_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7222,7 +6133,7 @@ func (*MsgRevertStuckInboundResponse) ProtoMessage() {} // Deprecated: Use MsgRevertStuckInboundResponse.ProtoReflect.Descriptor instead. func (*MsgRevertStuckInboundResponse) Descriptor() ([]byte, []int) { - return file_uexecutor_v1_tx_proto_rawDescGZIP(), []int{13} + return file_uexecutor_v1_tx_proto_rawDescGZIP(), []int{11} } func (x *MsgRevertStuckInboundResponse) GetUtxId() string { @@ -7288,137 +6199,112 @@ var file_uexecutor_v1_tx_proto_rawDesc = []byte{ 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x4d, 0x73, 0x67, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x1b, 0x0a, 0x19, 0x4d, 0x73, 0x67, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xa9, 0x02, 0x0a, 0x0d, 0x4d, 0x73, 0x67, 0x4d, 0x69, 0x67, 0x72, - 0x61, 0x74, 0x65, 0x55, 0x45, 0x41, 0x12, 0x30, 0x0a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, - 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, - 0x52, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x12, 0x52, 0x0a, 0x14, 0x75, 0x6e, 0x69, 0x76, - 0x65, 0x72, 0x73, 0x61, 0x6c, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, - 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x41, - 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x52, 0x12, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, - 0x73, 0x61, 0x6c, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x11, - 0x6d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, - 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, - 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x10, 0x6d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, - 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x69, - 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x3a, 0x27, 0x82, 0xe7, 0xb0, 0x2a, 0x06, 0x73, 0x69, - 0x67, 0x6e, 0x65, 0x72, 0x8a, 0xe7, 0xb0, 0x2a, 0x17, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, - 0x6f, 0x72, 0x2f, 0x4d, 0x73, 0x67, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x65, 0x55, 0x45, 0x41, - 0x22, 0x17, 0x0a, 0x15, 0x4d, 0x73, 0x67, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x65, 0x55, 0x45, - 0x41, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x96, 0x01, 0x0a, 0x0e, 0x4d, 0x73, - 0x67, 0x56, 0x6f, 0x74, 0x65, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x30, 0x0a, 0x06, - 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x18, 0xd2, 0xb4, - 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x12, 0x2f, - 0x0a, 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x15, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, - 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x3a, - 0x21, 0x82, 0xe7, 0xb0, 0x2a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x8a, 0xe7, 0xb0, 0x2a, - 0x11, 0x75, 0x65, 0x2f, 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x49, 0x6e, 0x62, 0x6f, 0x75, - 0x6e, 0x64, 0x22, 0x18, 0x0a, 0x16, 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x49, 0x6e, 0x62, - 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xde, 0x01, 0x0a, - 0x0f, 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, - 0x12, 0x30, 0x0a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x06, 0x73, 0x69, 0x67, 0x6e, - 0x65, 0x72, 0x12, 0x13, 0x0a, 0x05, 0x74, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x74, 0x78, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x75, 0x74, 0x78, 0x5f, 0x69, - 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x75, 0x74, 0x78, 0x49, 0x64, 0x12, 0x42, - 0x0a, 0x0b, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x5f, 0x74, 0x78, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x4f, 0x62, 0x73, 0x65, 0x72, - 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, - 0x54, 0x78, 0x3a, 0x29, 0x82, 0xe7, 0xb0, 0x2a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x8a, - 0xe7, 0xb0, 0x2a, 0x19, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x4d, 0x73, - 0x67, 0x56, 0x6f, 0x74, 0x65, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x19, 0x0a, - 0x17, 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xd5, 0x01, 0x0a, 0x10, 0x4d, 0x73, 0x67, - 0x56, 0x6f, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x30, 0x0a, - 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x18, 0xd2, - 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x12, - 0x2a, 0x0a, 0x11, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x5f, 0x63, 0x68, 0x61, 0x69, - 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6f, 0x62, 0x73, 0x65, - 0x72, 0x76, 0x65, 0x64, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x70, - 0x72, 0x69, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x70, 0x72, 0x69, 0x63, - 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, - 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x48, 0x65, - 0x69, 0x67, 0x68, 0x74, 0x3a, 0x2a, 0x82, 0xe7, 0xb0, 0x2a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, - 0x72, 0x8a, 0xe7, 0xb0, 0x2a, 0x1a, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, - 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x4d, 0x65, 0x74, 0x61, - 0x22, 0x1a, 0x0a, 0x18, 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, - 0x4d, 0x65, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xab, 0x01, 0x0a, - 0x15, 0x4d, 0x73, 0x67, 0x52, 0x65, 0x76, 0x65, 0x72, 0x74, 0x53, 0x74, 0x75, 0x63, 0x6b, 0x49, - 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x30, 0x0a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, - 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, - 0x52, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x12, 0x2f, 0x0a, 0x07, 0x69, 0x6e, 0x62, 0x6f, - 0x75, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x75, 0x65, 0x78, 0x65, - 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, - 0x52, 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x2f, 0x82, 0xe7, 0xb0, 0x2a, 0x06, - 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x8a, 0xe7, 0xb0, 0x2a, 0x1f, 0x75, 0x65, 0x78, 0x65, 0x63, - 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x4d, 0x73, 0x67, 0x52, 0x65, 0x76, 0x65, 0x72, 0x74, 0x53, 0x74, - 0x75, 0x63, 0x6b, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x57, 0x0a, 0x1d, 0x4d, 0x73, - 0x67, 0x52, 0x65, 0x76, 0x65, 0x72, 0x74, 0x53, 0x74, 0x75, 0x63, 0x6b, 0x49, 0x6e, 0x62, 0x6f, - 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x75, - 0x74, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x75, 0x74, 0x78, - 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, - 0x64, 0x49, 0x64, 0x32, 0xf8, 0x04, 0x0a, 0x03, 0x4d, 0x73, 0x67, 0x12, 0x54, 0x0a, 0x0c, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x1d, 0x2e, 0x75, 0x65, - 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x1a, 0x25, 0x2e, 0x75, 0x65, 0x78, - 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x5a, 0x0a, 0x0e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6c, - 0x6f, 0x61, 0x64, 0x12, 0x1f, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x50, 0x61, 0x79, - 0x6c, 0x6f, 0x61, 0x64, 0x1a, 0x27, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x50, 0x61, - 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, - 0x0a, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x65, 0x55, 0x45, 0x41, 0x12, 0x1b, 0x2e, 0x75, 0x65, - 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x4d, 0x69, - 0x67, 0x72, 0x61, 0x74, 0x65, 0x55, 0x45, 0x41, 0x1a, 0x23, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, - 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x4d, 0x69, 0x67, 0x72, 0x61, - 0x74, 0x65, 0x55, 0x45, 0x41, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51, 0x0a, - 0x0b, 0x56, 0x6f, 0x74, 0x65, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x1c, 0x2e, 0x75, - 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x56, - 0x6f, 0x74, 0x65, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x1a, 0x24, 0x2e, 0x75, 0x65, 0x78, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x96, 0x01, 0x0a, 0x0e, 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, + 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x30, 0x0a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, + 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, + 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, + 0x67, 0x52, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x12, 0x2f, 0x0a, 0x07, 0x69, 0x6e, 0x62, + 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x75, 0x65, 0x78, + 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, + 0x64, 0x52, 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x21, 0x82, 0xe7, 0xb0, 0x2a, + 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x8a, 0xe7, 0xb0, 0x2a, 0x11, 0x75, 0x65, 0x2f, 0x4d, + 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x18, 0x0a, + 0x16, 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xde, 0x01, 0x0a, 0x0f, 0x4d, 0x73, 0x67, 0x56, + 0x6f, 0x74, 0x65, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x30, 0x0a, 0x06, 0x73, + 0x69, 0x67, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x18, 0xd2, 0xb4, 0x2d, + 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x53, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x12, 0x13, 0x0a, + 0x05, 0x74, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, + 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x75, 0x74, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x75, 0x74, 0x78, 0x49, 0x64, 0x12, 0x42, 0x0a, 0x0b, 0x6f, 0x62, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x64, 0x5f, 0x74, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, + 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x75, + 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x0a, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x54, 0x78, 0x3a, 0x29, 0x82, + 0xe7, 0xb0, 0x2a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x8a, 0xe7, 0xb0, 0x2a, 0x19, 0x75, + 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, + 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x19, 0x0a, 0x17, 0x4d, 0x73, 0x67, 0x56, + 0x6f, 0x74, 0x65, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0xd5, 0x01, 0x0a, 0x10, 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x43, + 0x68, 0x61, 0x69, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x30, 0x0a, 0x06, 0x73, 0x69, 0x67, 0x6e, + 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, + 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x52, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x62, + 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x43, + 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x12, 0x21, 0x0a, 0x0c, + 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x0b, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, + 0x2a, 0x82, 0xe7, 0xb0, 0x2a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x8a, 0xe7, 0xb0, 0x2a, + 0x1a, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x4d, 0x73, 0x67, 0x56, 0x6f, + 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x22, 0x1a, 0x0a, 0x18, 0x4d, + 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xab, 0x01, 0x0a, 0x15, 0x4d, 0x73, 0x67, 0x52, + 0x65, 0x76, 0x65, 0x72, 0x74, 0x53, 0x74, 0x75, 0x63, 0x6b, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, + 0x64, 0x12, 0x30, 0x0a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x06, 0x73, 0x69, 0x67, + 0x6e, 0x65, 0x72, 0x12, 0x2f, 0x0a, 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x07, 0x69, 0x6e, 0x62, + 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x2f, 0x82, 0xe7, 0xb0, 0x2a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, + 0x72, 0x8a, 0xe7, 0xb0, 0x2a, 0x1f, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, + 0x4d, 0x73, 0x67, 0x52, 0x65, 0x76, 0x65, 0x72, 0x74, 0x53, 0x74, 0x75, 0x63, 0x6b, 0x49, 0x6e, + 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x57, 0x0a, 0x1d, 0x4d, 0x73, 0x67, 0x52, 0x65, 0x76, 0x65, + 0x72, 0x74, 0x53, 0x74, 0x75, 0x63, 0x6b, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x75, 0x74, 0x78, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x75, 0x74, 0x78, 0x49, 0x64, 0x12, 0x1f, 0x0a, + 0x0b, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x49, 0x64, 0x32, 0xa8, + 0x04, 0x0a, 0x03, 0x4d, 0x73, 0x67, 0x12, 0x54, 0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x1d, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, + 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, + 0x61, 0x72, 0x61, 0x6d, 0x73, 0x1a, 0x25, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, + 0x72, 0x61, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0e, + 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x1f, + 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, + 0x67, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x1a, + 0x27, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, + 0x73, 0x67, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51, 0x0a, 0x0b, 0x56, 0x6f, 0x74, 0x65, + 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x1c, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, + 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x49, 0x6e, + 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x1a, 0x24, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x49, 0x6e, 0x62, 0x6f, + 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0c, 0x56, + 0x6f, 0x74, 0x65, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x1d, 0x2e, 0x75, 0x65, + 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x56, 0x6f, + 0x74, 0x65, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x1a, 0x25, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, - 0x65, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x54, 0x0a, 0x0c, 0x56, 0x6f, 0x74, 0x65, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, - 0x12, 0x1d, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x1a, - 0x25, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, - 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0d, 0x56, 0x6f, 0x74, 0x65, 0x43, 0x68, - 0x61, 0x69, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1e, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, - 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x43, 0x68, - 0x61, 0x69, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x1a, 0x26, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, - 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x43, 0x68, - 0x61, 0x69, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x66, 0x0a, 0x12, 0x52, 0x65, 0x76, 0x65, 0x72, 0x74, 0x53, 0x74, 0x75, 0x63, 0x6b, 0x49, 0x6e, - 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x23, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, + 0x65, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x57, 0x0a, 0x0d, 0x56, 0x6f, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x4d, 0x65, + 0x74, 0x61, 0x12, 0x1e, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x4d, 0x65, + 0x74, 0x61, 0x1a, 0x26, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x4d, 0x65, + 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x66, 0x0a, 0x12, 0x52, 0x65, + 0x76, 0x65, 0x72, 0x74, 0x53, 0x74, 0x75, 0x63, 0x6b, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, + 0x12, 0x23, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x4d, 0x73, 0x67, 0x52, 0x65, 0x76, 0x65, 0x72, 0x74, 0x53, 0x74, 0x75, 0x63, 0x6b, 0x49, 0x6e, + 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x1a, 0x2b, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x52, 0x65, 0x76, 0x65, 0x72, 0x74, 0x53, 0x74, - 0x75, 0x63, 0x6b, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x1a, 0x2b, 0x2e, 0x75, 0x65, 0x78, - 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x52, 0x65, 0x76, - 0x65, 0x72, 0x74, 0x53, 0x74, 0x75, 0x63, 0x6b, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x05, 0x80, 0xe7, 0xb0, 0x2a, 0x01, 0x42, 0xaf, - 0x01, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, - 0x2e, 0x76, 0x31, 0x42, 0x07, 0x54, 0x78, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x41, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x75, 0x73, 0x68, 0x63, - 0x68, 0x61, 0x69, 0x6e, 0x2f, 0x70, 0x75, 0x73, 0x68, 0x2d, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2d, - 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, - 0x6f, 0x72, 0x2f, 0x76, 0x31, 0x3b, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x76, - 0x31, 0xa2, 0x02, 0x03, 0x55, 0x58, 0x58, 0xaa, 0x02, 0x0c, 0x55, 0x65, 0x78, 0x65, 0x63, 0x75, - 0x74, 0x6f, 0x72, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0c, 0x55, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, - 0x6f, 0x72, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x18, 0x55, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, - 0x72, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0xea, 0x02, 0x0d, 0x55, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x3a, 0x3a, 0x56, 0x31, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x75, 0x63, 0x6b, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x1a, 0x05, 0x80, 0xe7, 0xb0, 0x2a, 0x01, 0x42, 0xaf, 0x01, 0x0a, 0x10, 0x63, 0x6f, + 0x6d, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x42, 0x07, + 0x54, 0x78, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x75, 0x73, 0x68, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2f, + 0x70, 0x75, 0x73, 0x68, 0x2d, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2d, 0x6e, 0x6f, 0x64, 0x65, 0x2f, + 0x61, 0x70, 0x69, 0x2f, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x76, 0x31, + 0x3b, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x55, + 0x58, 0x58, 0xaa, 0x02, 0x0c, 0x55, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x56, + 0x31, 0xca, 0x02, 0x0c, 0x55, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x5c, 0x56, 0x31, + 0xe2, 0x02, 0x18, 0x55, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x5c, 0x56, 0x31, 0x5c, + 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0d, 0x55, 0x65, + 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( @@ -7433,57 +6319,50 @@ func file_uexecutor_v1_tx_proto_rawDescGZIP() []byte { return file_uexecutor_v1_tx_proto_rawDescData } -var file_uexecutor_v1_tx_proto_msgTypes = make([]protoimpl.MessageInfo, 14) +var file_uexecutor_v1_tx_proto_msgTypes = make([]protoimpl.MessageInfo, 12) var file_uexecutor_v1_tx_proto_goTypes = []interface{}{ (*MsgUpdateParams)(nil), // 0: uexecutor.v1.MsgUpdateParams (*MsgUpdateParamsResponse)(nil), // 1: uexecutor.v1.MsgUpdateParamsResponse (*MsgExecutePayload)(nil), // 2: uexecutor.v1.MsgExecutePayload (*MsgExecutePayloadResponse)(nil), // 3: uexecutor.v1.MsgExecutePayloadResponse - (*MsgMigrateUEA)(nil), // 4: uexecutor.v1.MsgMigrateUEA - (*MsgMigrateUEAResponse)(nil), // 5: uexecutor.v1.MsgMigrateUEAResponse - (*MsgVoteInbound)(nil), // 6: uexecutor.v1.MsgVoteInbound - (*MsgVoteInboundResponse)(nil), // 7: uexecutor.v1.MsgVoteInboundResponse - (*MsgVoteOutbound)(nil), // 8: uexecutor.v1.MsgVoteOutbound - (*MsgVoteOutboundResponse)(nil), // 9: uexecutor.v1.MsgVoteOutboundResponse - (*MsgVoteChainMeta)(nil), // 10: uexecutor.v1.MsgVoteChainMeta - (*MsgVoteChainMetaResponse)(nil), // 11: uexecutor.v1.MsgVoteChainMetaResponse - (*MsgRevertStuckInbound)(nil), // 12: uexecutor.v1.MsgRevertStuckInbound - (*MsgRevertStuckInboundResponse)(nil), // 13: uexecutor.v1.MsgRevertStuckInboundResponse - (*Params)(nil), // 14: uexecutor.v1.Params - (*UniversalAccountId)(nil), // 15: uexecutor.v1.UniversalAccountId - (*UniversalPayload)(nil), // 16: uexecutor.v1.UniversalPayload - (*MigrationPayload)(nil), // 17: uexecutor.v1.MigrationPayload - (*Inbound)(nil), // 18: uexecutor.v1.Inbound - (*OutboundObservation)(nil), // 19: uexecutor.v1.OutboundObservation + (*MsgVoteInbound)(nil), // 4: uexecutor.v1.MsgVoteInbound + (*MsgVoteInboundResponse)(nil), // 5: uexecutor.v1.MsgVoteInboundResponse + (*MsgVoteOutbound)(nil), // 6: uexecutor.v1.MsgVoteOutbound + (*MsgVoteOutboundResponse)(nil), // 7: uexecutor.v1.MsgVoteOutboundResponse + (*MsgVoteChainMeta)(nil), // 8: uexecutor.v1.MsgVoteChainMeta + (*MsgVoteChainMetaResponse)(nil), // 9: uexecutor.v1.MsgVoteChainMetaResponse + (*MsgRevertStuckInbound)(nil), // 10: uexecutor.v1.MsgRevertStuckInbound + (*MsgRevertStuckInboundResponse)(nil), // 11: uexecutor.v1.MsgRevertStuckInboundResponse + (*Params)(nil), // 12: uexecutor.v1.Params + (*UniversalAccountId)(nil), // 13: uexecutor.v1.UniversalAccountId + (*UniversalPayload)(nil), // 14: uexecutor.v1.UniversalPayload + (*Inbound)(nil), // 15: uexecutor.v1.Inbound + (*OutboundObservation)(nil), // 16: uexecutor.v1.OutboundObservation } var file_uexecutor_v1_tx_proto_depIdxs = []int32{ - 14, // 0: uexecutor.v1.MsgUpdateParams.params:type_name -> uexecutor.v1.Params - 15, // 1: uexecutor.v1.MsgExecutePayload.universal_account_id:type_name -> uexecutor.v1.UniversalAccountId - 16, // 2: uexecutor.v1.MsgExecutePayload.universal_payload:type_name -> uexecutor.v1.UniversalPayload - 15, // 3: uexecutor.v1.MsgMigrateUEA.universal_account_id:type_name -> uexecutor.v1.UniversalAccountId - 17, // 4: uexecutor.v1.MsgMigrateUEA.migration_payload:type_name -> uexecutor.v1.MigrationPayload - 18, // 5: uexecutor.v1.MsgVoteInbound.inbound:type_name -> uexecutor.v1.Inbound - 19, // 6: uexecutor.v1.MsgVoteOutbound.observed_tx:type_name -> uexecutor.v1.OutboundObservation - 18, // 7: uexecutor.v1.MsgRevertStuckInbound.inbound:type_name -> uexecutor.v1.Inbound - 0, // 8: uexecutor.v1.Msg.UpdateParams:input_type -> uexecutor.v1.MsgUpdateParams - 2, // 9: uexecutor.v1.Msg.ExecutePayload:input_type -> uexecutor.v1.MsgExecutePayload - 4, // 10: uexecutor.v1.Msg.MigrateUEA:input_type -> uexecutor.v1.MsgMigrateUEA - 6, // 11: uexecutor.v1.Msg.VoteInbound:input_type -> uexecutor.v1.MsgVoteInbound - 8, // 12: uexecutor.v1.Msg.VoteOutbound:input_type -> uexecutor.v1.MsgVoteOutbound - 10, // 13: uexecutor.v1.Msg.VoteChainMeta:input_type -> uexecutor.v1.MsgVoteChainMeta - 12, // 14: uexecutor.v1.Msg.RevertStuckInbound:input_type -> uexecutor.v1.MsgRevertStuckInbound - 1, // 15: uexecutor.v1.Msg.UpdateParams:output_type -> uexecutor.v1.MsgUpdateParamsResponse - 3, // 16: uexecutor.v1.Msg.ExecutePayload:output_type -> uexecutor.v1.MsgExecutePayloadResponse - 5, // 17: uexecutor.v1.Msg.MigrateUEA:output_type -> uexecutor.v1.MsgMigrateUEAResponse - 7, // 18: uexecutor.v1.Msg.VoteInbound:output_type -> uexecutor.v1.MsgVoteInboundResponse - 9, // 19: uexecutor.v1.Msg.VoteOutbound:output_type -> uexecutor.v1.MsgVoteOutboundResponse - 11, // 20: uexecutor.v1.Msg.VoteChainMeta:output_type -> uexecutor.v1.MsgVoteChainMetaResponse - 13, // 21: uexecutor.v1.Msg.RevertStuckInbound:output_type -> uexecutor.v1.MsgRevertStuckInboundResponse - 15, // [15:22] is the sub-list for method output_type - 8, // [8:15] is the sub-list for method input_type - 8, // [8:8] is the sub-list for extension type_name - 8, // [8:8] is the sub-list for extension extendee - 0, // [0:8] is the sub-list for field type_name + 12, // 0: uexecutor.v1.MsgUpdateParams.params:type_name -> uexecutor.v1.Params + 13, // 1: uexecutor.v1.MsgExecutePayload.universal_account_id:type_name -> uexecutor.v1.UniversalAccountId + 14, // 2: uexecutor.v1.MsgExecutePayload.universal_payload:type_name -> uexecutor.v1.UniversalPayload + 15, // 3: uexecutor.v1.MsgVoteInbound.inbound:type_name -> uexecutor.v1.Inbound + 16, // 4: uexecutor.v1.MsgVoteOutbound.observed_tx:type_name -> uexecutor.v1.OutboundObservation + 15, // 5: uexecutor.v1.MsgRevertStuckInbound.inbound:type_name -> uexecutor.v1.Inbound + 0, // 6: uexecutor.v1.Msg.UpdateParams:input_type -> uexecutor.v1.MsgUpdateParams + 2, // 7: uexecutor.v1.Msg.ExecutePayload:input_type -> uexecutor.v1.MsgExecutePayload + 4, // 8: uexecutor.v1.Msg.VoteInbound:input_type -> uexecutor.v1.MsgVoteInbound + 6, // 9: uexecutor.v1.Msg.VoteOutbound:input_type -> uexecutor.v1.MsgVoteOutbound + 8, // 10: uexecutor.v1.Msg.VoteChainMeta:input_type -> uexecutor.v1.MsgVoteChainMeta + 10, // 11: uexecutor.v1.Msg.RevertStuckInbound:input_type -> uexecutor.v1.MsgRevertStuckInbound + 1, // 12: uexecutor.v1.Msg.UpdateParams:output_type -> uexecutor.v1.MsgUpdateParamsResponse + 3, // 13: uexecutor.v1.Msg.ExecutePayload:output_type -> uexecutor.v1.MsgExecutePayloadResponse + 5, // 14: uexecutor.v1.Msg.VoteInbound:output_type -> uexecutor.v1.MsgVoteInboundResponse + 7, // 15: uexecutor.v1.Msg.VoteOutbound:output_type -> uexecutor.v1.MsgVoteOutboundResponse + 9, // 16: uexecutor.v1.Msg.VoteChainMeta:output_type -> uexecutor.v1.MsgVoteChainMetaResponse + 11, // 17: uexecutor.v1.Msg.RevertStuckInbound:output_type -> uexecutor.v1.MsgRevertStuckInboundResponse + 12, // [12:18] is the sub-list for method output_type + 6, // [6:12] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name } func init() { file_uexecutor_v1_tx_proto_init() } @@ -7543,30 +6422,6 @@ func file_uexecutor_v1_tx_proto_init() { } } file_uexecutor_v1_tx_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*MsgMigrateUEA); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_uexecutor_v1_tx_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*MsgMigrateUEAResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_uexecutor_v1_tx_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*MsgVoteInbound); i { case 0: return &v.state @@ -7578,7 +6433,7 @@ func file_uexecutor_v1_tx_proto_init() { return nil } } - file_uexecutor_v1_tx_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + file_uexecutor_v1_tx_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*MsgVoteInboundResponse); i { case 0: return &v.state @@ -7590,7 +6445,7 @@ func file_uexecutor_v1_tx_proto_init() { return nil } } - file_uexecutor_v1_tx_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + file_uexecutor_v1_tx_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*MsgVoteOutbound); i { case 0: return &v.state @@ -7602,7 +6457,7 @@ func file_uexecutor_v1_tx_proto_init() { return nil } } - file_uexecutor_v1_tx_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + file_uexecutor_v1_tx_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*MsgVoteOutboundResponse); i { case 0: return &v.state @@ -7614,7 +6469,7 @@ func file_uexecutor_v1_tx_proto_init() { return nil } } - file_uexecutor_v1_tx_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + file_uexecutor_v1_tx_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*MsgVoteChainMeta); i { case 0: return &v.state @@ -7626,7 +6481,7 @@ func file_uexecutor_v1_tx_proto_init() { return nil } } - file_uexecutor_v1_tx_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + file_uexecutor_v1_tx_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*MsgVoteChainMetaResponse); i { case 0: return &v.state @@ -7638,7 +6493,7 @@ func file_uexecutor_v1_tx_proto_init() { return nil } } - file_uexecutor_v1_tx_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + file_uexecutor_v1_tx_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*MsgRevertStuckInbound); i { case 0: return &v.state @@ -7650,7 +6505,7 @@ func file_uexecutor_v1_tx_proto_init() { return nil } } - file_uexecutor_v1_tx_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + file_uexecutor_v1_tx_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*MsgRevertStuckInboundResponse); i { case 0: return &v.state @@ -7669,7 +6524,7 @@ func file_uexecutor_v1_tx_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_uexecutor_v1_tx_proto_rawDesc, NumEnums: 0, - NumMessages: 14, + NumMessages: 12, NumExtensions: 0, NumServices: 1, }, diff --git a/api/uexecutor/v1/tx_grpc.pb.go b/api/uexecutor/v1/tx_grpc.pb.go index 9959cb728..a0cf9fbf1 100644 --- a/api/uexecutor/v1/tx_grpc.pb.go +++ b/api/uexecutor/v1/tx_grpc.pb.go @@ -21,7 +21,6 @@ const _ = grpc.SupportPackageIsVersion7 const ( Msg_UpdateParams_FullMethodName = "/uexecutor.v1.Msg/UpdateParams" Msg_ExecutePayload_FullMethodName = "/uexecutor.v1.Msg/ExecutePayload" - Msg_MigrateUEA_FullMethodName = "/uexecutor.v1.Msg/MigrateUEA" Msg_VoteInbound_FullMethodName = "/uexecutor.v1.Msg/VoteInbound" Msg_VoteOutbound_FullMethodName = "/uexecutor.v1.Msg/VoteOutbound" Msg_VoteChainMeta_FullMethodName = "/uexecutor.v1.Msg/VoteChainMeta" @@ -38,8 +37,6 @@ type MsgClient interface { UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) // ExecutePayload defines a message for executing a universal payload ExecutePayload(ctx context.Context, in *MsgExecutePayload, opts ...grpc.CallOption) (*MsgExecutePayloadResponse, error) - // MigrateUEA defines a message for migrating UEA - MigrateUEA(ctx context.Context, in *MsgMigrateUEA, opts ...grpc.CallOption) (*MsgMigrateUEAResponse, error) // VoteInbound defines a message for voting on synthetic assets bridging from external chain to PC VoteInbound(ctx context.Context, in *MsgVoteInbound, opts ...grpc.CallOption) (*MsgVoteInboundResponse, error) // VoteOutbound defines a message for voting on a observed outbound tx on external chain @@ -78,15 +75,6 @@ func (c *msgClient) ExecutePayload(ctx context.Context, in *MsgExecutePayload, o return out, nil } -func (c *msgClient) MigrateUEA(ctx context.Context, in *MsgMigrateUEA, opts ...grpc.CallOption) (*MsgMigrateUEAResponse, error) { - out := new(MsgMigrateUEAResponse) - err := c.cc.Invoke(ctx, Msg_MigrateUEA_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - func (c *msgClient) VoteInbound(ctx context.Context, in *MsgVoteInbound, opts ...grpc.CallOption) (*MsgVoteInboundResponse, error) { out := new(MsgVoteInboundResponse) err := c.cc.Invoke(ctx, Msg_VoteInbound_FullMethodName, in, out, opts...) @@ -133,8 +121,6 @@ type MsgServer interface { UpdateParams(context.Context, *MsgUpdateParams) (*MsgUpdateParamsResponse, error) // ExecutePayload defines a message for executing a universal payload ExecutePayload(context.Context, *MsgExecutePayload) (*MsgExecutePayloadResponse, error) - // MigrateUEA defines a message for migrating UEA - MigrateUEA(context.Context, *MsgMigrateUEA) (*MsgMigrateUEAResponse, error) // VoteInbound defines a message for voting on synthetic assets bridging from external chain to PC VoteInbound(context.Context, *MsgVoteInbound) (*MsgVoteInboundResponse, error) // VoteOutbound defines a message for voting on a observed outbound tx on external chain @@ -158,9 +144,6 @@ func (UnimplementedMsgServer) UpdateParams(context.Context, *MsgUpdateParams) (* func (UnimplementedMsgServer) ExecutePayload(context.Context, *MsgExecutePayload) (*MsgExecutePayloadResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ExecutePayload not implemented") } -func (UnimplementedMsgServer) MigrateUEA(context.Context, *MsgMigrateUEA) (*MsgMigrateUEAResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method MigrateUEA not implemented") -} func (UnimplementedMsgServer) VoteInbound(context.Context, *MsgVoteInbound) (*MsgVoteInboundResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method VoteInbound not implemented") } @@ -222,24 +205,6 @@ func _Msg_ExecutePayload_Handler(srv interface{}, ctx context.Context, dec func( return interceptor(ctx, in, info, handler) } -func _Msg_MigrateUEA_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MsgMigrateUEA) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(MsgServer).MigrateUEA(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: Msg_MigrateUEA_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MsgServer).MigrateUEA(ctx, req.(*MsgMigrateUEA)) - } - return interceptor(ctx, in, info, handler) -} - func _Msg_VoteInbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(MsgVoteInbound) if err := dec(in); err != nil { @@ -327,10 +292,6 @@ var Msg_ServiceDesc = grpc.ServiceDesc{ MethodName: "ExecutePayload", Handler: _Msg_ExecutePayload_Handler, }, - { - MethodName: "MigrateUEA", - Handler: _Msg_MigrateUEA_Handler, - }, { MethodName: "VoteInbound", Handler: _Msg_VoteInbound_Handler, diff --git a/api/uexecutor/v1/types.pulsar.go b/api/uexecutor/v1/types.pulsar.go index c615d1b46..b6d8d99a9 100644 --- a/api/uexecutor/v1/types.pulsar.go +++ b/api/uexecutor/v1/types.pulsar.go @@ -1388,554 +1388,6 @@ func (x *fastReflection_UniversalPayload) ProtoMethods() *protoiface.Methods { } } -var ( - md_MigrationPayload protoreflect.MessageDescriptor - fd_MigrationPayload_migration protoreflect.FieldDescriptor - fd_MigrationPayload_nonce protoreflect.FieldDescriptor - fd_MigrationPayload_deadline protoreflect.FieldDescriptor -) - -func init() { - file_uexecutor_v1_types_proto_init() - md_MigrationPayload = File_uexecutor_v1_types_proto.Messages().ByName("MigrationPayload") - fd_MigrationPayload_migration = md_MigrationPayload.Fields().ByName("migration") - fd_MigrationPayload_nonce = md_MigrationPayload.Fields().ByName("nonce") - fd_MigrationPayload_deadline = md_MigrationPayload.Fields().ByName("deadline") -} - -var _ protoreflect.Message = (*fastReflection_MigrationPayload)(nil) - -type fastReflection_MigrationPayload MigrationPayload - -func (x *MigrationPayload) ProtoReflect() protoreflect.Message { - return (*fastReflection_MigrationPayload)(x) -} - -func (x *MigrationPayload) slowProtoReflect() protoreflect.Message { - mi := &file_uexecutor_v1_types_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -var _fastReflection_MigrationPayload_messageType fastReflection_MigrationPayload_messageType -var _ protoreflect.MessageType = fastReflection_MigrationPayload_messageType{} - -type fastReflection_MigrationPayload_messageType struct{} - -func (x fastReflection_MigrationPayload_messageType) Zero() protoreflect.Message { - return (*fastReflection_MigrationPayload)(nil) -} -func (x fastReflection_MigrationPayload_messageType) New() protoreflect.Message { - return new(fastReflection_MigrationPayload) -} -func (x fastReflection_MigrationPayload_messageType) Descriptor() protoreflect.MessageDescriptor { - return md_MigrationPayload -} - -// Descriptor returns message descriptor, which contains only the protobuf -// type information for the message. -func (x *fastReflection_MigrationPayload) Descriptor() protoreflect.MessageDescriptor { - return md_MigrationPayload -} - -// Type returns the message type, which encapsulates both Go and protobuf -// type information. If the Go type information is not needed, -// it is recommended that the message descriptor be used instead. -func (x *fastReflection_MigrationPayload) Type() protoreflect.MessageType { - return _fastReflection_MigrationPayload_messageType -} - -// New returns a newly allocated and mutable empty message. -func (x *fastReflection_MigrationPayload) New() protoreflect.Message { - return new(fastReflection_MigrationPayload) -} - -// Interface unwraps the message reflection interface and -// returns the underlying ProtoMessage interface. -func (x *fastReflection_MigrationPayload) Interface() protoreflect.ProtoMessage { - return (*MigrationPayload)(x) -} - -// Range iterates over every populated field in an undefined order, -// calling f for each field descriptor and value encountered. -// Range returns immediately if f returns false. -// While iterating, mutating operations may only be performed -// on the current field descriptor. -func (x *fastReflection_MigrationPayload) Range(f func(protoreflect.FieldDescriptor, protoreflect.Value) bool) { - if x.Migration != "" { - value := protoreflect.ValueOfString(x.Migration) - if !f(fd_MigrationPayload_migration, value) { - return - } - } - if x.Nonce != "" { - value := protoreflect.ValueOfString(x.Nonce) - if !f(fd_MigrationPayload_nonce, value) { - return - } - } - if x.Deadline != "" { - value := protoreflect.ValueOfString(x.Deadline) - if !f(fd_MigrationPayload_deadline, value) { - return - } - } -} - -// Has reports whether a field is populated. -// -// Some fields have the property of nullability where it is possible to -// distinguish between the default value of a field and whether the field -// was explicitly populated with the default value. Singular message fields, -// member fields of a oneof, and proto2 scalar fields are nullable. Such -// fields are populated only if explicitly set. -// -// In other cases (aside from the nullable cases above), -// a proto3 scalar field is populated if it contains a non-zero value, and -// a repeated field is populated if it is non-empty. -func (x *fastReflection_MigrationPayload) Has(fd protoreflect.FieldDescriptor) bool { - switch fd.FullName() { - case "uexecutor.v1.MigrationPayload.migration": - return x.Migration != "" - case "uexecutor.v1.MigrationPayload.nonce": - return x.Nonce != "" - case "uexecutor.v1.MigrationPayload.deadline": - return x.Deadline != "" - default: - if fd.IsExtension() { - panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MigrationPayload")) - } - panic(fmt.Errorf("message uexecutor.v1.MigrationPayload does not contain field %s", fd.FullName())) - } -} - -// Clear clears the field such that a subsequent Has call reports false. -// -// Clearing an extension field clears both the extension type and value -// associated with the given field number. -// -// Clear is a mutating operation and unsafe for concurrent use. -func (x *fastReflection_MigrationPayload) Clear(fd protoreflect.FieldDescriptor) { - switch fd.FullName() { - case "uexecutor.v1.MigrationPayload.migration": - x.Migration = "" - case "uexecutor.v1.MigrationPayload.nonce": - x.Nonce = "" - case "uexecutor.v1.MigrationPayload.deadline": - x.Deadline = "" - default: - if fd.IsExtension() { - panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MigrationPayload")) - } - panic(fmt.Errorf("message uexecutor.v1.MigrationPayload does not contain field %s", fd.FullName())) - } -} - -// Get retrieves the value for a field. -// -// For unpopulated scalars, it returns the default value, where -// the default value of a bytes scalar is guaranteed to be a copy. -// For unpopulated composite types, it returns an empty, read-only view -// of the value; to obtain a mutable reference, use Mutable. -func (x *fastReflection_MigrationPayload) Get(descriptor protoreflect.FieldDescriptor) protoreflect.Value { - switch descriptor.FullName() { - case "uexecutor.v1.MigrationPayload.migration": - value := x.Migration - return protoreflect.ValueOfString(value) - case "uexecutor.v1.MigrationPayload.nonce": - value := x.Nonce - return protoreflect.ValueOfString(value) - case "uexecutor.v1.MigrationPayload.deadline": - value := x.Deadline - return protoreflect.ValueOfString(value) - default: - if descriptor.IsExtension() { - panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MigrationPayload")) - } - panic(fmt.Errorf("message uexecutor.v1.MigrationPayload does not contain field %s", descriptor.FullName())) - } -} - -// Set stores the value for a field. -// -// For a field belonging to a oneof, it implicitly clears any other field -// that may be currently set within the same oneof. -// For extension fields, it implicitly stores the provided ExtensionType. -// When setting a composite type, it is unspecified whether the stored value -// aliases the source's memory in any way. If the composite value is an -// empty, read-only value, then it panics. -// -// Set is a mutating operation and unsafe for concurrent use. -func (x *fastReflection_MigrationPayload) Set(fd protoreflect.FieldDescriptor, value protoreflect.Value) { - switch fd.FullName() { - case "uexecutor.v1.MigrationPayload.migration": - x.Migration = value.Interface().(string) - case "uexecutor.v1.MigrationPayload.nonce": - x.Nonce = value.Interface().(string) - case "uexecutor.v1.MigrationPayload.deadline": - x.Deadline = value.Interface().(string) - default: - if fd.IsExtension() { - panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MigrationPayload")) - } - panic(fmt.Errorf("message uexecutor.v1.MigrationPayload does not contain field %s", fd.FullName())) - } -} - -// Mutable returns a mutable reference to a composite type. -// -// If the field is unpopulated, it may allocate a composite value. -// For a field belonging to a oneof, it implicitly clears any other field -// that may be currently set within the same oneof. -// For extension fields, it implicitly stores the provided ExtensionType -// if not already stored. -// It panics if the field does not contain a composite type. -// -// Mutable is a mutating operation and unsafe for concurrent use. -func (x *fastReflection_MigrationPayload) Mutable(fd protoreflect.FieldDescriptor) protoreflect.Value { - switch fd.FullName() { - case "uexecutor.v1.MigrationPayload.migration": - panic(fmt.Errorf("field migration of message uexecutor.v1.MigrationPayload is not mutable")) - case "uexecutor.v1.MigrationPayload.nonce": - panic(fmt.Errorf("field nonce of message uexecutor.v1.MigrationPayload is not mutable")) - case "uexecutor.v1.MigrationPayload.deadline": - panic(fmt.Errorf("field deadline of message uexecutor.v1.MigrationPayload is not mutable")) - default: - if fd.IsExtension() { - panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MigrationPayload")) - } - panic(fmt.Errorf("message uexecutor.v1.MigrationPayload does not contain field %s", fd.FullName())) - } -} - -// NewField returns a new value that is assignable to the field -// for the given descriptor. For scalars, this returns the default value. -// For lists, maps, and messages, this returns a new, empty, mutable value. -func (x *fastReflection_MigrationPayload) NewField(fd protoreflect.FieldDescriptor) protoreflect.Value { - switch fd.FullName() { - case "uexecutor.v1.MigrationPayload.migration": - return protoreflect.ValueOfString("") - case "uexecutor.v1.MigrationPayload.nonce": - return protoreflect.ValueOfString("") - case "uexecutor.v1.MigrationPayload.deadline": - return protoreflect.ValueOfString("") - default: - if fd.IsExtension() { - panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MigrationPayload")) - } - panic(fmt.Errorf("message uexecutor.v1.MigrationPayload does not contain field %s", fd.FullName())) - } -} - -// WhichOneof reports which field within the oneof is populated, -// returning nil if none are populated. -// It panics if the oneof descriptor does not belong to this message. -func (x *fastReflection_MigrationPayload) WhichOneof(d protoreflect.OneofDescriptor) protoreflect.FieldDescriptor { - switch d.FullName() { - default: - panic(fmt.Errorf("%s is not a oneof field in uexecutor.v1.MigrationPayload", d.FullName())) - } - panic("unreachable") -} - -// GetUnknown retrieves the entire list of unknown fields. -// The caller may only mutate the contents of the RawFields -// if the mutated bytes are stored back into the message with SetUnknown. -func (x *fastReflection_MigrationPayload) GetUnknown() protoreflect.RawFields { - return x.unknownFields -} - -// SetUnknown stores an entire list of unknown fields. -// The raw fields must be syntactically valid according to the wire format. -// An implementation may panic if this is not the case. -// Once stored, the caller must not mutate the content of the RawFields. -// An empty RawFields may be passed to clear the fields. -// -// SetUnknown is a mutating operation and unsafe for concurrent use. -func (x *fastReflection_MigrationPayload) SetUnknown(fields protoreflect.RawFields) { - x.unknownFields = fields -} - -// IsValid reports whether the message is valid. -// -// An invalid message is an empty, read-only value. -// -// An invalid message often corresponds to a nil pointer of the concrete -// message type, but the details are implementation dependent. -// Validity is not part of the protobuf data model, and may not -// be preserved in marshaling or other operations. -func (x *fastReflection_MigrationPayload) IsValid() bool { - return x != nil -} - -// ProtoMethods returns optional fastReflectionFeature-path implementations of various operations. -// This method may return nil. -// -// The returned methods type is identical to -// "google.golang.org/protobuf/runtime/protoiface".Methods. -// Consult the protoiface package documentation for details. -func (x *fastReflection_MigrationPayload) ProtoMethods() *protoiface.Methods { - size := func(input protoiface.SizeInput) protoiface.SizeOutput { - x := input.Message.Interface().(*MigrationPayload) - if x == nil { - return protoiface.SizeOutput{ - NoUnkeyedLiterals: input.NoUnkeyedLiterals, - Size: 0, - } - } - options := runtime.SizeInputToOptions(input) - _ = options - var n int - var l int - _ = l - l = len(x.Migration) - if l > 0 { - n += 1 + l + runtime.Sov(uint64(l)) - } - l = len(x.Nonce) - if l > 0 { - n += 1 + l + runtime.Sov(uint64(l)) - } - l = len(x.Deadline) - if l > 0 { - n += 1 + l + runtime.Sov(uint64(l)) - } - if x.unknownFields != nil { - n += len(x.unknownFields) - } - return protoiface.SizeOutput{ - NoUnkeyedLiterals: input.NoUnkeyedLiterals, - Size: n, - } - } - - marshal := func(input protoiface.MarshalInput) (protoiface.MarshalOutput, error) { - x := input.Message.Interface().(*MigrationPayload) - if x == nil { - return protoiface.MarshalOutput{ - NoUnkeyedLiterals: input.NoUnkeyedLiterals, - Buf: input.Buf, - }, nil - } - options := runtime.MarshalInputToOptions(input) - _ = options - size := options.Size(x) - dAtA := make([]byte, size) - i := len(dAtA) - _ = i - var l int - _ = l - if x.unknownFields != nil { - i -= len(x.unknownFields) - copy(dAtA[i:], x.unknownFields) - } - if len(x.Deadline) > 0 { - i -= len(x.Deadline) - copy(dAtA[i:], x.Deadline) - i = runtime.EncodeVarint(dAtA, i, uint64(len(x.Deadline))) - i-- - dAtA[i] = 0x1a - } - if len(x.Nonce) > 0 { - i -= len(x.Nonce) - copy(dAtA[i:], x.Nonce) - i = runtime.EncodeVarint(dAtA, i, uint64(len(x.Nonce))) - i-- - dAtA[i] = 0x12 - } - if len(x.Migration) > 0 { - i -= len(x.Migration) - copy(dAtA[i:], x.Migration) - i = runtime.EncodeVarint(dAtA, i, uint64(len(x.Migration))) - i-- - dAtA[i] = 0xa - } - if input.Buf != nil { - input.Buf = append(input.Buf, dAtA...) - } else { - input.Buf = dAtA - } - return protoiface.MarshalOutput{ - NoUnkeyedLiterals: input.NoUnkeyedLiterals, - Buf: input.Buf, - }, nil - } - unmarshal := func(input protoiface.UnmarshalInput) (protoiface.UnmarshalOutput, error) { - x := input.Message.Interface().(*MigrationPayload) - if x == nil { - return protoiface.UnmarshalOutput{ - NoUnkeyedLiterals: input.NoUnkeyedLiterals, - Flags: input.Flags, - }, nil - } - options := runtime.UnmarshalInputToOptions(input) - _ = options - dAtA := input.Buf - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow - } - if iNdEx >= l { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: MigrationPayload: wiretype end group for non-group") - } - if fieldNum <= 0 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: MigrationPayload: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field Migration", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow - } - if iNdEx >= l { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength - } - if postIndex > l { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF - } - x.Migration = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field Nonce", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow - } - if iNdEx >= l { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength - } - if postIndex > l { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF - } - x.Nonce = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field Deadline", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow - } - if iNdEx >= l { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength - } - if postIndex > l { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF - } - x.Deadline = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := runtime.Skip(dAtA[iNdEx:]) - if err != nil { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength - } - if (iNdEx + skippy) > l { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF - } - if !options.DiscardUnknown { - x.unknownFields = append(x.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - } - iNdEx += skippy - } - } - - if iNdEx > l { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF - } - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, nil - } - return &protoiface.Methods{ - NoUnkeyedLiterals: struct{}{}, - Flags: protoiface.SupportMarshalDeterministic | protoiface.SupportUnmarshalDiscardUnknown, - Size: size, - Marshal: marshal, - Unmarshal: unmarshal, - Merge: nil, - CheckInitialized: nil, - } -} - var ( md_UniversalAccountId protoreflect.MessageDescriptor fd_UniversalAccountId_chain_namespace protoreflect.FieldDescriptor @@ -1960,7 +1412,7 @@ func (x *UniversalAccountId) ProtoReflect() protoreflect.Message { } func (x *UniversalAccountId) slowProtoReflect() protoreflect.Message { - mi := &file_uexecutor_v1_types_proto_msgTypes[3] + mi := &file_uexecutor_v1_types_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2504,7 +1956,7 @@ func (x *RevertInstructions) ProtoReflect() protoreflect.Message { } func (x *RevertInstructions) slowProtoReflect() protoreflect.Message { - mi := &file_uexecutor_v1_types_proto_msgTypes[4] + mi := &file_uexecutor_v1_types_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2948,7 +2400,7 @@ func (x *Inbound) ProtoReflect() protoreflect.Message { } func (x *Inbound) slowProtoReflect() protoreflect.Message { - mi := &file_uexecutor_v1_types_proto_msgTypes[5] + mi := &file_uexecutor_v1_types_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4126,7 +3578,7 @@ func (x *PCTx) ProtoReflect() protoreflect.Message { } func (x *PCTx) slowProtoReflect() protoreflect.Message { - mi := &file_uexecutor_v1_types_proto_msgTypes[6] + mi := &file_uexecutor_v1_types_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4832,7 +4284,7 @@ func (x *OutboundObservation) ProtoReflect() protoreflect.Message { } func (x *OutboundObservation) slowProtoReflect() protoreflect.Message { - mi := &file_uexecutor_v1_types_proto_msgTypes[7] + mi := &file_uexecutor_v1_types_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5476,7 +4928,7 @@ func (x *OriginatingPcTx) ProtoReflect() protoreflect.Message { } func (x *OriginatingPcTx) slowProtoReflect() protoreflect.Message { - mi := &file_uexecutor_v1_types_proto_msgTypes[8] + mi := &file_uexecutor_v1_types_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5998,7 +5450,7 @@ func (x *OutboundTx) ProtoReflect() protoreflect.Message { } func (x *OutboundTx) slowProtoReflect() protoreflect.Message { - mi := &file_uexecutor_v1_types_proto_msgTypes[9] + mi := &file_uexecutor_v1_types_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7823,7 +7275,7 @@ func (x *UniversalTx) ProtoReflect() protoreflect.Message { } func (x *UniversalTx) slowProtoReflect() protoreflect.Message { - mi := &file_uexecutor_v1_types_proto_msgTypes[10] + mi := &file_uexecutor_v1_types_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8570,7 +8022,7 @@ func (x *InboundLegacy) ProtoReflect() protoreflect.Message { } func (x *InboundLegacy) slowProtoReflect() protoreflect.Message { - mi := &file_uexecutor_v1_types_proto_msgTypes[11] + mi := &file_uexecutor_v1_types_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9555,7 +9007,7 @@ func (x *OutboundTxLegacy) ProtoReflect() protoreflect.Message { } func (x *OutboundTxLegacy) slowProtoReflect() protoreflect.Message { - mi := &file_uexecutor_v1_types_proto_msgTypes[12] + mi := &file_uexecutor_v1_types_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10280,7 +9732,7 @@ func (x *UniversalTxLegacy) ProtoReflect() protoreflect.Message { } func (x *UniversalTxLegacy) slowProtoReflect() protoreflect.Message { - mi := &file_uexecutor_v1_types_proto_msgTypes[13] + mi := &file_uexecutor_v1_types_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11357,58 +10809,6 @@ func (x *UniversalPayload) GetVType() VerificationType { return VerificationType_signedVerification } -// MigrationPayload mirrors the Solidity struct -type MigrationPayload struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Migration string `protobuf:"bytes,1,opt,name=migration,proto3" json:"migration,omitempty"` // Migration Address - Nonce string `protobuf:"bytes,2,opt,name=nonce,proto3" json:"nonce,omitempty"` // unit256 as string - Deadline string `protobuf:"bytes,3,opt,name=deadline,proto3" json:"deadline,omitempty"` // unit256 as string -} - -func (x *MigrationPayload) Reset() { - *x = MigrationPayload{} - if protoimpl.UnsafeEnabled { - mi := &file_uexecutor_v1_types_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *MigrationPayload) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*MigrationPayload) ProtoMessage() {} - -// Deprecated: Use MigrationPayload.ProtoReflect.Descriptor instead. -func (*MigrationPayload) Descriptor() ([]byte, []int) { - return file_uexecutor_v1_types_proto_rawDescGZIP(), []int{2} -} - -func (x *MigrationPayload) GetMigration() string { - if x != nil { - return x.Migration - } - return "" -} - -func (x *MigrationPayload) GetNonce() string { - if x != nil { - return x.Nonce - } - return "" -} - -func (x *MigrationPayload) GetDeadline() string { - if x != nil { - return x.Deadline - } - return "" -} - // UniversalAccountId is the identifier of a owner account type UniversalAccountId struct { state protoimpl.MessageState @@ -11423,7 +10823,7 @@ type UniversalAccountId struct { func (x *UniversalAccountId) Reset() { *x = UniversalAccountId{} if protoimpl.UnsafeEnabled { - mi := &file_uexecutor_v1_types_proto_msgTypes[3] + mi := &file_uexecutor_v1_types_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11437,7 +10837,7 @@ func (*UniversalAccountId) ProtoMessage() {} // Deprecated: Use UniversalAccountId.ProtoReflect.Descriptor instead. func (*UniversalAccountId) Descriptor() ([]byte, []int) { - return file_uexecutor_v1_types_proto_rawDescGZIP(), []int{3} + return file_uexecutor_v1_types_proto_rawDescGZIP(), []int{2} } func (x *UniversalAccountId) GetChainNamespace() string { @@ -11472,7 +10872,7 @@ type RevertInstructions struct { func (x *RevertInstructions) Reset() { *x = RevertInstructions{} if protoimpl.UnsafeEnabled { - mi := &file_uexecutor_v1_types_proto_msgTypes[4] + mi := &file_uexecutor_v1_types_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11486,7 +10886,7 @@ func (*RevertInstructions) ProtoMessage() {} // Deprecated: Use RevertInstructions.ProtoReflect.Descriptor instead. func (*RevertInstructions) Descriptor() ([]byte, []int) { - return file_uexecutor_v1_types_proto_rawDescGZIP(), []int{4} + return file_uexecutor_v1_types_proto_rawDescGZIP(), []int{3} } func (x *RevertInstructions) GetFundRecipient() string { @@ -11519,7 +10919,7 @@ type Inbound struct { func (x *Inbound) Reset() { *x = Inbound{} if protoimpl.UnsafeEnabled { - mi := &file_uexecutor_v1_types_proto_msgTypes[5] + mi := &file_uexecutor_v1_types_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11533,7 +10933,7 @@ func (*Inbound) ProtoMessage() {} // Deprecated: Use Inbound.ProtoReflect.Descriptor instead. func (*Inbound) Descriptor() ([]byte, []int) { - return file_uexecutor_v1_types_proto_rawDescGZIP(), []int{5} + return file_uexecutor_v1_types_proto_rawDescGZIP(), []int{4} } func (x *Inbound) GetSourceChain() string { @@ -11643,7 +11043,7 @@ type PCTx struct { func (x *PCTx) Reset() { *x = PCTx{} if protoimpl.UnsafeEnabled { - mi := &file_uexecutor_v1_types_proto_msgTypes[6] + mi := &file_uexecutor_v1_types_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11657,7 +11057,7 @@ func (*PCTx) ProtoMessage() {} // Deprecated: Use PCTx.ProtoReflect.Descriptor instead. func (*PCTx) Descriptor() ([]byte, []int) { - return file_uexecutor_v1_types_proto_rawDescGZIP(), []int{6} + return file_uexecutor_v1_types_proto_rawDescGZIP(), []int{5} } func (x *PCTx) GetTxHash() string { @@ -11717,7 +11117,7 @@ type OutboundObservation struct { func (x *OutboundObservation) Reset() { *x = OutboundObservation{} if protoimpl.UnsafeEnabled { - mi := &file_uexecutor_v1_types_proto_msgTypes[7] + mi := &file_uexecutor_v1_types_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11731,7 +11131,7 @@ func (*OutboundObservation) ProtoMessage() {} // Deprecated: Use OutboundObservation.ProtoReflect.Descriptor instead. func (*OutboundObservation) Descriptor() ([]byte, []int) { - return file_uexecutor_v1_types_proto_rawDescGZIP(), []int{7} + return file_uexecutor_v1_types_proto_rawDescGZIP(), []int{6} } func (x *OutboundObservation) GetSuccess() bool { @@ -11781,7 +11181,7 @@ type OriginatingPcTx struct { func (x *OriginatingPcTx) Reset() { *x = OriginatingPcTx{} if protoimpl.UnsafeEnabled { - mi := &file_uexecutor_v1_types_proto_msgTypes[8] + mi := &file_uexecutor_v1_types_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11795,7 +11195,7 @@ func (*OriginatingPcTx) ProtoMessage() {} // Deprecated: Use OriginatingPcTx.ProtoReflect.Descriptor instead. func (*OriginatingPcTx) Descriptor() ([]byte, []int) { - return file_uexecutor_v1_types_proto_rawDescGZIP(), []int{8} + return file_uexecutor_v1_types_proto_rawDescGZIP(), []int{7} } func (x *OriginatingPcTx) GetTxHash() string { @@ -11843,7 +11243,7 @@ type OutboundTx struct { func (x *OutboundTx) Reset() { *x = OutboundTx{} if protoimpl.UnsafeEnabled { - mi := &file_uexecutor_v1_types_proto_msgTypes[9] + mi := &file_uexecutor_v1_types_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11857,7 +11257,7 @@ func (*OutboundTx) ProtoMessage() {} // Deprecated: Use OutboundTx.ProtoReflect.Descriptor instead. func (*OutboundTx) Descriptor() ([]byte, []int) { - return file_uexecutor_v1_types_proto_rawDescGZIP(), []int{9} + return file_uexecutor_v1_types_proto_rawDescGZIP(), []int{8} } func (x *OutboundTx) GetDestinationChain() string { @@ -12022,7 +11422,7 @@ type UniversalTx struct { func (x *UniversalTx) Reset() { *x = UniversalTx{} if protoimpl.UnsafeEnabled { - mi := &file_uexecutor_v1_types_proto_msgTypes[10] + mi := &file_uexecutor_v1_types_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12036,7 +11436,7 @@ func (*UniversalTx) ProtoMessage() {} // Deprecated: Use UniversalTx.ProtoReflect.Descriptor instead. func (*UniversalTx) Descriptor() ([]byte, []int) { - return file_uexecutor_v1_types_proto_rawDescGZIP(), []int{10} + return file_uexecutor_v1_types_proto_rawDescGZIP(), []int{9} } func (x *UniversalTx) GetId() string { @@ -12094,7 +11494,7 @@ type InboundLegacy struct { func (x *InboundLegacy) Reset() { *x = InboundLegacy{} if protoimpl.UnsafeEnabled { - mi := &file_uexecutor_v1_types_proto_msgTypes[11] + mi := &file_uexecutor_v1_types_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12108,7 +11508,7 @@ func (*InboundLegacy) ProtoMessage() {} // Deprecated: Use InboundLegacy.ProtoReflect.Descriptor instead. func (*InboundLegacy) Descriptor() ([]byte, []int) { - return file_uexecutor_v1_types_proto_rawDescGZIP(), []int{11} + return file_uexecutor_v1_types_proto_rawDescGZIP(), []int{10} } func (x *InboundLegacy) GetSourceChain() string { @@ -12196,7 +11596,7 @@ type OutboundTxLegacy struct { func (x *OutboundTxLegacy) Reset() { *x = OutboundTxLegacy{} if protoimpl.UnsafeEnabled { - mi := &file_uexecutor_v1_types_proto_msgTypes[12] + mi := &file_uexecutor_v1_types_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12210,7 +11610,7 @@ func (*OutboundTxLegacy) ProtoMessage() {} // Deprecated: Use OutboundTxLegacy.ProtoReflect.Descriptor instead. func (*OutboundTxLegacy) Descriptor() ([]byte, []int) { - return file_uexecutor_v1_types_proto_rawDescGZIP(), []int{12} + return file_uexecutor_v1_types_proto_rawDescGZIP(), []int{11} } func (x *OutboundTxLegacy) GetDestinationChain() string { @@ -12262,7 +11662,7 @@ type UniversalTxLegacy struct { func (x *UniversalTxLegacy) Reset() { *x = UniversalTxLegacy{} if protoimpl.UnsafeEnabled { - mi := &file_uexecutor_v1_types_proto_msgTypes[13] + mi := &file_uexecutor_v1_types_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12276,7 +11676,7 @@ func (*UniversalTxLegacy) ProtoMessage() {} // Deprecated: Use UniversalTxLegacy.ProtoReflect.Descriptor instead. func (*UniversalTxLegacy) Descriptor() ([]byte, []int) { - return file_uexecutor_v1_types_proto_rawDescGZIP(), []int{13} + return file_uexecutor_v1_types_proto_rawDescGZIP(), []int{12} } func (x *UniversalTxLegacy) GetInboundTx() *InboundLegacy { @@ -12344,32 +11744,163 @@ var file_uexecutor_v1_types_proto_rawDesc = []byte{ 0x79, 0x70, 0x65, 0x52, 0x05, 0x76, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x28, 0x98, 0xa0, 0x1f, 0x00, 0xe8, 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x1b, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x5f, 0x70, 0x61, 0x79, - 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x8c, 0x01, 0x0a, 0x10, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x6d, 0x69, 0x67, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x69, - 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x1a, 0x0a, - 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x3a, 0x28, 0x98, 0xa0, 0x1f, 0x00, 0xe8, + 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x98, 0x01, 0x0a, 0x12, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, + 0x61, 0x6c, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x63, + 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, + 0x14, 0x0a, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x3a, 0x28, 0x98, 0xa0, 0x1f, 0x00, 0xe8, 0xa0, 0x1f, 0x01, 0x8a, + 0xe7, 0xb0, 0x2a, 0x1b, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x75, 0x6e, + 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, + 0x63, 0x0a, 0x12, 0x52, 0x65, 0x76, 0x65, 0x72, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x75, 0x6e, 0x64, 0x5f, 0x72, 0x65, + 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x66, + 0x75, 0x6e, 0x64, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x3a, 0x26, 0xe8, 0xa0, + 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x1d, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, + 0x2f, 0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x8c, 0x04, 0x0a, 0x07, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, + 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x68, + 0x61, 0x69, 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, + 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, + 0x6e, 0x64, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, + 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, + 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x73, + 0x73, 0x65, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x61, 0x73, 0x73, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x6f, 0x67, + 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x6f, + 0x67, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x2d, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, + 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x78, 0x54, 0x79, 0x70, 0x65, 0x52, 0x06, 0x74, + 0x78, 0x54, 0x79, 0x70, 0x65, 0x12, 0x4b, 0x0a, 0x11, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, + 0x61, 0x6c, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1e, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, + 0x52, 0x10, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x50, 0x61, 0x79, 0x6c, 0x6f, + 0x61, 0x64, 0x12, 0x2b, 0x0a, 0x11, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x76, + 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, + 0x51, 0x0a, 0x13, 0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x72, 0x75, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x75, + 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x76, 0x65, + 0x72, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x12, + 0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x73, 0x43, 0x45, 0x41, 0x18, 0x0c, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x05, 0x69, 0x73, 0x43, 0x45, 0x41, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x61, 0x77, 0x5f, + 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, + 0x61, 0x77, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x3a, 0x08, 0x98, 0xa0, 0x1f, 0x00, 0xe8, + 0xa0, 0x1f, 0x01, 0x22, 0xc8, 0x01, 0x0a, 0x04, 0x50, 0x43, 0x54, 0x78, 0x12, 0x17, 0x0a, 0x07, + 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, + 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x19, 0x0a, + 0x08, 0x67, 0x61, 0x73, 0x5f, 0x75, 0x73, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x07, 0x67, 0x61, 0x73, 0x55, 0x73, 0x65, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, + 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, + 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x73, 0x67, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x73, 0x67, + 0x3a, 0x1c, 0x98, 0xa0, 0x1f, 0x00, 0xe8, 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x0f, 0x75, + 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x70, 0x63, 0x5f, 0x74, 0x78, 0x22, 0xd3, + 0x01, 0x0a, 0x13, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x4f, 0x62, 0x73, 0x65, 0x72, + 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1b, 0x0a, 0x09, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x73, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x73, 0x67, 0x12, 0x20, 0x0a, 0x0c, 0x67, 0x61, 0x73, + 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x67, 0x61, 0x73, 0x46, 0x65, 0x65, 0x55, 0x73, 0x65, 0x64, 0x3a, 0x27, 0xe8, 0xa0, 0x1f, + 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x1e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, + 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x6d, 0x0a, 0x0f, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x74, + 0x69, 0x6e, 0x67, 0x50, 0x63, 0x54, 0x78, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, + 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, + 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x6f, 0x67, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x3a, 0x24, 0xe8, 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x1b, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, - 0x72, 0x2f, 0x6d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x61, 0x79, 0x6c, - 0x6f, 0x61, 0x64, 0x22, 0x98, 0x01, 0x0a, 0x12, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, - 0x6c, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x68, - 0x61, 0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x14, - 0x0a, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, - 0x77, 0x6e, 0x65, 0x72, 0x3a, 0x28, 0x98, 0xa0, 0x1f, 0x00, 0xe8, 0xa0, 0x1f, 0x01, 0x8a, 0xe7, - 0xb0, 0x2a, 0x1b, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x75, 0x6e, 0x69, - 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x63, - 0x0a, 0x12, 0x52, 0x65, 0x76, 0x65, 0x72, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x75, 0x6e, 0x64, 0x5f, 0x72, 0x65, 0x63, - 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x66, 0x75, - 0x6e, 0x64, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x3a, 0x26, 0xe8, 0xa0, 0x1f, - 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x1d, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, - 0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x22, 0x8c, 0x04, 0x0a, 0x07, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, + 0x72, 0x2f, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x63, + 0x5f, 0x74, 0x78, 0x22, 0x95, 0x07, 0x0a, 0x0a, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, + 0x54, 0x78, 0x12, 0x2b, 0x0a, 0x11, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x64, + 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, + 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, + 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x11, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x73, 0x73, 0x65, + 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x28, 0x0a, 0x10, 0x70, 0x72, 0x63, 0x32, 0x30, 0x5f, 0x61, + 0x73, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0e, 0x70, 0x72, 0x63, 0x32, 0x30, 0x41, 0x73, 0x73, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, + 0x16, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, + 0x61, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, + 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x61, 0x73, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x67, 0x61, 0x73, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x2d, + 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x14, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x54, + 0x78, 0x54, 0x79, 0x70, 0x65, 0x52, 0x06, 0x74, 0x78, 0x54, 0x79, 0x70, 0x65, 0x12, 0x32, 0x0a, + 0x05, 0x70, 0x63, 0x5f, 0x74, 0x78, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x75, + 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x72, 0x69, 0x67, + 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x63, 0x54, 0x78, 0x52, 0x04, 0x70, 0x63, 0x54, + 0x78, 0x12, 0x42, 0x0a, 0x0b, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x5f, 0x74, 0x78, + 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, + 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x4f, 0x62, + 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x6f, 0x62, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x64, 0x54, 0x78, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x3d, 0x0a, 0x0f, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, + 0x64, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, + 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x52, 0x0e, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x51, 0x0a, 0x13, 0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x5f, 0x69, + 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x20, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x65, 0x76, 0x65, 0x72, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x52, 0x12, 0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x72, + 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x42, 0x0a, 0x13, 0x70, 0x63, 0x5f, 0x72, 0x65, + 0x76, 0x65, 0x72, 0x74, 0x5f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0f, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x43, 0x54, 0x78, 0x52, 0x11, 0x70, 0x63, 0x52, 0x65, 0x76, 0x65, + 0x72, 0x74, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x67, + 0x61, 0x73, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x67, 0x61, 0x73, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x61, 0x73, 0x5f, + 0x66, 0x65, 0x65, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x61, 0x73, 0x46, 0x65, + 0x65, 0x12, 0x42, 0x0a, 0x13, 0x70, 0x63, 0x5f, 0x72, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x5f, 0x65, + 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, + 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x43, + 0x54, 0x78, 0x52, 0x11, 0x70, 0x63, 0x52, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x45, 0x78, 0x65, 0x63, + 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x11, 0x72, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x5f, + 0x73, 0x77, 0x61, 0x70, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0f, 0x72, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x53, 0x77, 0x61, 0x70, 0x45, 0x72, 0x72, 0x6f, + 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x61, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x14, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x67, 0x61, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x21, + 0x0a, 0x0c, 0x61, 0x62, 0x6f, 0x72, 0x74, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x15, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x62, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x3a, 0x08, 0x98, 0xa0, 0x1f, 0x00, 0xe8, 0xa0, 0x1f, 0x01, 0x22, 0xff, 0x01, 0x0a, 0x0b, + 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x54, 0x78, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x34, 0x0a, 0x0a, 0x69, + 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x15, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, + 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x09, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, + 0x78, 0x12, 0x27, 0x0a, 0x05, 0x70, 0x63, 0x5f, 0x74, 0x78, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x12, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x50, 0x43, 0x54, 0x78, 0x52, 0x04, 0x70, 0x63, 0x54, 0x78, 0x12, 0x39, 0x0a, 0x0b, 0x6f, 0x75, + 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x78, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x18, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4f, + 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x52, 0x0a, 0x6f, 0x75, 0x74, 0x62, 0x6f, + 0x75, 0x6e, 0x64, 0x54, 0x78, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x5f, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x76, + 0x65, 0x72, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x3a, 0x23, 0x98, 0xa0, 0x1f, 0x00, 0xe8, 0xa0, + 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x16, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, + 0x2f, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x5f, 0x74, 0x78, 0x22, 0xab, 0x03, + 0x0a, 0x0d, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, @@ -12382,251 +11913,111 @@ var file_uexecutor_v1_types_proto_rawDesc = []byte{ 0x65, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x6f, 0x67, - 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x2d, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x74, 0x79, 0x70, 0x65, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, - 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x78, 0x54, 0x79, 0x70, 0x65, 0x52, 0x06, 0x74, 0x78, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x4b, 0x0a, 0x11, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, - 0x6c, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1e, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, - 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x52, - 0x10, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, - 0x64, 0x12, 0x2b, 0x0a, 0x11, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x76, 0x65, - 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, 0x51, - 0x0a, 0x13, 0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x75, 0x65, - 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x76, 0x65, 0x72, - 0x74, 0x49, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x12, 0x72, - 0x65, 0x76, 0x65, 0x72, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x73, 0x43, 0x45, 0x41, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x05, 0x69, 0x73, 0x43, 0x45, 0x41, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x61, 0x77, 0x5f, 0x70, - 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x61, - 0x77, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x3a, 0x08, 0x98, 0xa0, 0x1f, 0x00, 0xe8, 0xa0, - 0x1f, 0x01, 0x22, 0xc8, 0x01, 0x0a, 0x04, 0x50, 0x43, 0x54, 0x78, 0x12, 0x17, 0x0a, 0x07, 0x74, - 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x78, - 0x48, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x08, - 0x67, 0x61, 0x73, 0x5f, 0x75, 0x73, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, - 0x67, 0x61, 0x73, 0x55, 0x73, 0x65, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, - 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x62, - 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x73, 0x67, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x73, 0x67, 0x3a, - 0x1c, 0x98, 0xa0, 0x1f, 0x00, 0xe8, 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x0f, 0x75, 0x65, - 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x70, 0x63, 0x5f, 0x74, 0x78, 0x22, 0xd3, 0x01, - 0x0a, 0x13, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, - 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, - 0x68, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1b, 0x0a, 0x09, 0x65, - 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x73, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x73, 0x67, 0x12, 0x20, 0x0a, 0x0c, 0x67, 0x61, 0x73, 0x5f, - 0x66, 0x65, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, - 0x67, 0x61, 0x73, 0x46, 0x65, 0x65, 0x55, 0x73, 0x65, 0x64, 0x3a, 0x27, 0xe8, 0xa0, 0x1f, 0x01, - 0x8a, 0xe7, 0xb0, 0x2a, 0x1e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x6f, - 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x22, 0x6d, 0x0a, 0x0f, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, - 0x6e, 0x67, 0x50, 0x63, 0x54, 0x78, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, - 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, - 0x1b, 0x0a, 0x09, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x6c, 0x6f, 0x67, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x3a, 0x24, 0xe8, 0xa0, - 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x1b, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, - 0x2f, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x63, 0x5f, - 0x74, 0x78, 0x22, 0x95, 0x07, 0x0a, 0x0a, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, - 0x78, 0x12, 0x2b, 0x0a, 0x11, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x64, 0x65, - 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x1c, - 0x0a, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, - 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x6d, - 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, - 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x11, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x73, 0x73, 0x65, 0x74, - 0x41, 0x64, 0x64, 0x72, 0x12, 0x28, 0x0a, 0x10, 0x70, 0x72, 0x63, 0x32, 0x30, 0x5f, 0x61, 0x73, - 0x73, 0x65, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, - 0x70, 0x72, 0x63, 0x32, 0x30, 0x41, 0x73, 0x73, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x16, - 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, - 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, - 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x61, 0x73, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x67, 0x61, 0x73, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x2d, 0x0a, - 0x07, 0x74, 0x78, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, - 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x78, - 0x54, 0x79, 0x70, 0x65, 0x52, 0x06, 0x74, 0x78, 0x54, 0x79, 0x70, 0x65, 0x12, 0x32, 0x0a, 0x05, - 0x70, 0x63, 0x5f, 0x74, 0x78, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x75, 0x65, - 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x72, 0x69, 0x67, 0x69, - 0x6e, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x63, 0x54, 0x78, 0x52, 0x04, 0x70, 0x63, 0x54, 0x78, - 0x12, 0x42, 0x0a, 0x0b, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x5f, 0x74, 0x78, 0x18, - 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x4f, 0x62, 0x73, - 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, - 0x65, 0x64, 0x54, 0x78, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x02, 0x69, 0x64, 0x12, 0x3d, 0x0a, 0x0f, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, - 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, - 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x52, 0x0e, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x51, 0x0a, 0x13, 0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x5f, 0x69, 0x6e, - 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x20, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x65, 0x76, 0x65, 0x72, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x52, 0x12, 0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x72, 0x75, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x42, 0x0a, 0x13, 0x70, 0x63, 0x5f, 0x72, 0x65, 0x76, - 0x65, 0x72, 0x74, 0x5f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0f, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x50, 0x43, 0x54, 0x78, 0x52, 0x11, 0x70, 0x63, 0x52, 0x65, 0x76, 0x65, 0x72, - 0x74, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x61, - 0x73, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x67, - 0x61, 0x73, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x61, 0x73, 0x5f, 0x66, - 0x65, 0x65, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x61, 0x73, 0x46, 0x65, 0x65, - 0x12, 0x42, 0x0a, 0x13, 0x70, 0x63, 0x5f, 0x72, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x5f, 0x65, 0x78, - 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, - 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x43, 0x54, - 0x78, 0x52, 0x11, 0x70, 0x63, 0x52, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x45, 0x78, 0x65, 0x63, 0x75, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x11, 0x72, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x5f, 0x73, - 0x77, 0x61, 0x70, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0f, 0x72, 0x65, 0x66, 0x75, 0x6e, 0x64, 0x53, 0x77, 0x61, 0x70, 0x45, 0x72, 0x72, 0x6f, 0x72, - 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x61, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x14, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x67, 0x61, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x21, 0x0a, - 0x0c, 0x61, 0x62, 0x6f, 0x72, 0x74, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x15, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x62, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, - 0x3a, 0x08, 0x98, 0xa0, 0x1f, 0x00, 0xe8, 0xa0, 0x1f, 0x01, 0x22, 0xff, 0x01, 0x0a, 0x0b, 0x55, - 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x54, 0x78, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x34, 0x0a, 0x0a, 0x69, 0x6e, - 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, - 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, - 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x09, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, - 0x12, 0x27, 0x0a, 0x05, 0x70, 0x63, 0x5f, 0x74, 0x78, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x12, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, - 0x43, 0x54, 0x78, 0x52, 0x04, 0x70, 0x63, 0x54, 0x78, 0x12, 0x39, 0x0a, 0x0b, 0x6f, 0x75, 0x74, - 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x78, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, - 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x75, - 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x52, 0x0a, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, - 0x6e, 0x64, 0x54, 0x78, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x5f, 0x65, - 0x72, 0x72, 0x6f, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x76, 0x65, - 0x72, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x3a, 0x23, 0x98, 0xa0, 0x1f, 0x00, 0xe8, 0xa0, 0x1f, + 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x3a, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, + 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x54, + 0x79, 0x70, 0x65, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x52, 0x06, 0x74, 0x78, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x4b, 0x0a, 0x11, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x5f, 0x70, + 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x75, + 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x69, 0x76, + 0x65, 0x72, 0x73, 0x61, 0x6c, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x10, 0x75, 0x6e, + 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x2b, + 0x0a, 0x11, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, + 0x61, 0x74, 0x61, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x76, 0x65, 0x72, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x3a, 0x1e, 0x98, 0xa0, 0x1f, + 0x01, 0xe8, 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x11, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, + 0x74, 0x6f, 0x72, 0x2f, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0xd1, 0x01, 0x0a, 0x10, + 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, + 0x12, 0x2b, 0x0a, 0x11, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x64, 0x65, 0x73, + 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x17, 0x0a, + 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, + 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, + 0x69, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, + 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x3a, 0x22, 0x98, 0xa0, 0x1f, + 0x01, 0xe8, 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x15, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, + 0x74, 0x6f, 0x72, 0x2f, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x78, 0x22, + 0xaa, 0x02, 0x0a, 0x11, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x54, 0x78, 0x4c, + 0x65, 0x67, 0x61, 0x63, 0x79, 0x12, 0x3a, 0x0a, 0x0a, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, + 0x5f, 0x74, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x75, 0x65, 0x78, 0x65, + 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, + 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x52, 0x09, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, + 0x78, 0x12, 0x27, 0x0a, 0x05, 0x70, 0x63, 0x5f, 0x74, 0x78, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x12, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x50, 0x43, 0x54, 0x78, 0x52, 0x04, 0x70, 0x63, 0x54, 0x78, 0x12, 0x3f, 0x0a, 0x0b, 0x6f, 0x75, + 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1e, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4f, + 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x52, + 0x0a, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x12, 0x4a, 0x0a, 0x10, 0x75, + 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x54, 0x78, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0f, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, + 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x3a, 0x23, 0x98, 0xa0, 0x1f, 0x01, 0xe8, 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x16, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, - 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x5f, 0x74, 0x78, 0x22, 0xab, 0x03, 0x0a, - 0x0d, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x12, 0x21, - 0x0a, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x68, 0x61, 0x69, - 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, - 0x6e, 0x64, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, - 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, - 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x73, 0x73, 0x65, - 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x73, - 0x73, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x6f, 0x67, 0x5f, 0x69, - 0x6e, 0x64, 0x65, 0x78, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x6f, 0x67, 0x49, - 0x6e, 0x64, 0x65, 0x78, 0x12, 0x3a, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x54, 0x79, - 0x70, 0x65, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x52, 0x06, 0x74, 0x78, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x4b, 0x0a, 0x11, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x5f, 0x70, 0x61, - 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x75, 0x65, - 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x69, 0x76, 0x65, - 0x72, 0x73, 0x61, 0x6c, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x10, 0x75, 0x6e, 0x69, - 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x2b, 0x0a, - 0x11, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x61, - 0x74, 0x61, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x3a, 0x1e, 0x98, 0xa0, 0x1f, 0x01, - 0xe8, 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x11, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, - 0x6f, 0x72, 0x2f, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0xd1, 0x01, 0x0a, 0x10, 0x4f, - 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x12, - 0x2b, 0x0a, 0x11, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, - 0x68, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x64, 0x65, 0x73, 0x74, - 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x17, 0x0a, 0x07, - 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, - 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, - 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, - 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x61, - 0x73, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x3a, 0x22, 0x98, 0xa0, 0x1f, 0x01, - 0xe8, 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x15, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, - 0x6f, 0x72, 0x2f, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x78, 0x22, 0xaa, - 0x02, 0x0a, 0x11, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x54, 0x78, 0x4c, 0x65, - 0x67, 0x61, 0x63, 0x79, 0x12, 0x3a, 0x0a, 0x0a, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, - 0x74, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, - 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x4c, - 0x65, 0x67, 0x61, 0x63, 0x79, 0x52, 0x09, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, - 0x12, 0x27, 0x0a, 0x05, 0x70, 0x63, 0x5f, 0x74, 0x78, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x12, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, - 0x43, 0x54, 0x78, 0x52, 0x04, 0x70, 0x63, 0x54, 0x78, 0x12, 0x3f, 0x0a, 0x0b, 0x6f, 0x75, 0x74, - 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, - 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x75, - 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x52, 0x0a, - 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x12, 0x4a, 0x0a, 0x10, 0x75, 0x6e, - 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x54, 0x78, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0f, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x3a, 0x23, 0x98, 0xa0, 0x1f, 0x01, 0xe8, 0xa0, 0x1f, 0x01, - 0x8a, 0xe7, 0xb0, 0x2a, 0x16, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x75, - 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x5f, 0x74, 0x78, 0x2a, 0x47, 0x0a, 0x10, 0x56, - 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x16, 0x0a, 0x12, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x75, 0x6e, 0x69, 0x76, 0x65, - 0x72, 0x73, 0x61, 0x6c, 0x54, 0x78, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x10, 0x01, 0x2a, 0x83, 0x02, 0x0a, 0x11, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, - 0x61, 0x6c, 0x54, 0x78, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x23, 0x0a, 0x1f, 0x55, 0x4e, - 0x49, 0x56, 0x45, 0x52, 0x53, 0x41, 0x4c, 0x5f, 0x54, 0x58, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, - 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, - 0x13, 0x0a, 0x0f, 0x49, 0x4e, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, - 0x53, 0x53, 0x10, 0x01, 0x12, 0x1d, 0x0a, 0x19, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x5f, - 0x49, 0x4e, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x49, 0x4f, - 0x4e, 0x10, 0x02, 0x12, 0x17, 0x0a, 0x13, 0x50, 0x43, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, - 0x45, 0x44, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x03, 0x12, 0x16, 0x0a, 0x12, - 0x50, 0x43, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x45, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, - 0x45, 0x44, 0x10, 0x04, 0x12, 0x15, 0x0a, 0x11, 0x50, 0x43, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, - 0x4e, 0x47, 0x5f, 0x52, 0x45, 0x56, 0x45, 0x52, 0x54, 0x10, 0x05, 0x12, 0x14, 0x0a, 0x10, 0x4f, - 0x55, 0x54, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, - 0x06, 0x12, 0x14, 0x0a, 0x10, 0x4f, 0x55, 0x54, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x53, 0x55, - 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x07, 0x12, 0x13, 0x0a, 0x0f, 0x4f, 0x55, 0x54, 0x42, 0x4f, - 0x55, 0x4e, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x08, 0x12, 0x0c, 0x0a, 0x08, - 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x45, 0x44, 0x10, 0x09, 0x2a, 0x4f, 0x0a, 0x06, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, - 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, - 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x4f, 0x42, 0x53, 0x45, 0x52, 0x56, 0x45, 0x44, 0x10, 0x02, - 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x56, 0x45, 0x52, 0x54, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0b, - 0x0a, 0x07, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x10, 0x04, 0x2a, 0x8f, 0x01, 0x0a, 0x06, - 0x54, 0x78, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x0e, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, - 0x49, 0x46, 0x49, 0x45, 0x44, 0x5f, 0x54, 0x58, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x47, 0x41, - 0x53, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x47, 0x41, 0x53, 0x5f, 0x41, 0x4e, 0x44, 0x5f, 0x50, - 0x41, 0x59, 0x4c, 0x4f, 0x41, 0x44, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x46, 0x55, 0x4e, 0x44, - 0x53, 0x10, 0x03, 0x12, 0x15, 0x0a, 0x11, 0x46, 0x55, 0x4e, 0x44, 0x53, 0x5f, 0x41, 0x4e, 0x44, - 0x5f, 0x50, 0x41, 0x59, 0x4c, 0x4f, 0x41, 0x44, 0x10, 0x04, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x41, - 0x59, 0x4c, 0x4f, 0x41, 0x44, 0x10, 0x05, 0x12, 0x12, 0x0a, 0x0e, 0x49, 0x4e, 0x42, 0x4f, 0x55, - 0x4e, 0x44, 0x5f, 0x52, 0x45, 0x56, 0x45, 0x52, 0x54, 0x10, 0x06, 0x12, 0x10, 0x0a, 0x0c, 0x52, - 0x45, 0x53, 0x43, 0x55, 0x45, 0x5f, 0x46, 0x55, 0x4e, 0x44, 0x53, 0x10, 0x07, 0x2a, 0xb4, 0x01, - 0x0a, 0x13, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x54, 0x79, 0x70, 0x65, 0x4c, - 0x65, 0x67, 0x61, 0x63, 0x79, 0x12, 0x21, 0x0a, 0x1d, 0x49, 0x4e, 0x42, 0x4f, 0x55, 0x4e, 0x44, - 0x5f, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, - 0x49, 0x45, 0x44, 0x5f, 0x54, 0x58, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x49, 0x4e, 0x42, 0x4f, - 0x55, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x5f, 0x47, 0x41, 0x53, 0x10, 0x01, - 0x12, 0x18, 0x0a, 0x14, 0x49, 0x4e, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x47, 0x41, - 0x43, 0x59, 0x5f, 0x46, 0x55, 0x4e, 0x44, 0x53, 0x10, 0x02, 0x12, 0x24, 0x0a, 0x20, 0x49, 0x4e, - 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x5f, 0x46, 0x55, 0x4e, - 0x44, 0x53, 0x5f, 0x41, 0x4e, 0x44, 0x5f, 0x50, 0x41, 0x59, 0x4c, 0x4f, 0x41, 0x44, 0x10, 0x03, - 0x12, 0x22, 0x0a, 0x1e, 0x49, 0x4e, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x47, 0x41, - 0x43, 0x59, 0x5f, 0x47, 0x41, 0x53, 0x5f, 0x41, 0x4e, 0x44, 0x5f, 0x50, 0x41, 0x59, 0x4c, 0x4f, - 0x41, 0x44, 0x10, 0x04, 0x42, 0xb2, 0x01, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x75, 0x65, 0x78, - 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x42, 0x0a, 0x54, 0x79, 0x70, 0x65, 0x73, - 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x75, 0x73, 0x68, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2f, 0x70, 0x75, - 0x73, 0x68, 0x2d, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2d, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x61, 0x70, - 0x69, 0x2f, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x76, 0x31, 0x3b, 0x75, - 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x55, 0x58, 0x58, - 0xaa, 0x02, 0x0c, 0x55, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x56, 0x31, 0xca, - 0x02, 0x0c, 0x55, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x5c, 0x56, 0x31, 0xe2, 0x02, - 0x18, 0x55, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, - 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0d, 0x55, 0x65, 0x78, 0x65, - 0x63, 0x75, 0x74, 0x6f, 0x72, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x5f, 0x74, 0x78, 0x2a, 0x47, 0x0a, 0x10, + 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x16, 0x0a, 0x12, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x75, 0x6e, 0x69, 0x76, + 0x65, 0x72, 0x73, 0x61, 0x6c, 0x54, 0x78, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x10, 0x01, 0x2a, 0x83, 0x02, 0x0a, 0x11, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, + 0x73, 0x61, 0x6c, 0x54, 0x78, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x23, 0x0a, 0x1f, 0x55, + 0x4e, 0x49, 0x56, 0x45, 0x52, 0x53, 0x41, 0x4c, 0x5f, 0x54, 0x58, 0x5f, 0x53, 0x54, 0x41, 0x54, + 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, + 0x12, 0x13, 0x0a, 0x0f, 0x49, 0x4e, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x53, 0x55, 0x43, 0x43, + 0x45, 0x53, 0x53, 0x10, 0x01, 0x12, 0x1d, 0x0a, 0x19, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, + 0x5f, 0x49, 0x4e, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x49, + 0x4f, 0x4e, 0x10, 0x02, 0x12, 0x17, 0x0a, 0x13, 0x50, 0x43, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, + 0x54, 0x45, 0x44, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x03, 0x12, 0x16, 0x0a, + 0x12, 0x50, 0x43, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x45, 0x44, 0x5f, 0x46, 0x41, 0x49, + 0x4c, 0x45, 0x44, 0x10, 0x04, 0x12, 0x15, 0x0a, 0x11, 0x50, 0x43, 0x5f, 0x50, 0x45, 0x4e, 0x44, + 0x49, 0x4e, 0x47, 0x5f, 0x52, 0x45, 0x56, 0x45, 0x52, 0x54, 0x10, 0x05, 0x12, 0x14, 0x0a, 0x10, + 0x4f, 0x55, 0x54, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, + 0x10, 0x06, 0x12, 0x14, 0x0a, 0x10, 0x4f, 0x55, 0x54, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x53, + 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x07, 0x12, 0x13, 0x0a, 0x0f, 0x4f, 0x55, 0x54, 0x42, + 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x08, 0x12, 0x0c, 0x0a, + 0x08, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x45, 0x44, 0x10, 0x09, 0x2a, 0x4f, 0x0a, 0x06, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, + 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, + 0x47, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x4f, 0x42, 0x53, 0x45, 0x52, 0x56, 0x45, 0x44, 0x10, + 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x56, 0x45, 0x52, 0x54, 0x45, 0x44, 0x10, 0x03, 0x12, + 0x0b, 0x0a, 0x07, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x10, 0x04, 0x2a, 0x8f, 0x01, 0x0a, + 0x06, 0x54, 0x78, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x0e, 0x55, 0x4e, 0x53, 0x50, 0x45, + 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x5f, 0x54, 0x58, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x47, + 0x41, 0x53, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x47, 0x41, 0x53, 0x5f, 0x41, 0x4e, 0x44, 0x5f, + 0x50, 0x41, 0x59, 0x4c, 0x4f, 0x41, 0x44, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x46, 0x55, 0x4e, + 0x44, 0x53, 0x10, 0x03, 0x12, 0x15, 0x0a, 0x11, 0x46, 0x55, 0x4e, 0x44, 0x53, 0x5f, 0x41, 0x4e, + 0x44, 0x5f, 0x50, 0x41, 0x59, 0x4c, 0x4f, 0x41, 0x44, 0x10, 0x04, 0x12, 0x0b, 0x0a, 0x07, 0x50, + 0x41, 0x59, 0x4c, 0x4f, 0x41, 0x44, 0x10, 0x05, 0x12, 0x12, 0x0a, 0x0e, 0x49, 0x4e, 0x42, 0x4f, + 0x55, 0x4e, 0x44, 0x5f, 0x52, 0x45, 0x56, 0x45, 0x52, 0x54, 0x10, 0x06, 0x12, 0x10, 0x0a, 0x0c, + 0x52, 0x45, 0x53, 0x43, 0x55, 0x45, 0x5f, 0x46, 0x55, 0x4e, 0x44, 0x53, 0x10, 0x07, 0x2a, 0xb4, + 0x01, 0x0a, 0x13, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x54, 0x79, 0x70, 0x65, + 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x12, 0x21, 0x0a, 0x1d, 0x49, 0x4e, 0x42, 0x4f, 0x55, 0x4e, + 0x44, 0x5f, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, + 0x46, 0x49, 0x45, 0x44, 0x5f, 0x54, 0x58, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x49, 0x4e, 0x42, + 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x5f, 0x47, 0x41, 0x53, 0x10, + 0x01, 0x12, 0x18, 0x0a, 0x14, 0x49, 0x4e, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x47, + 0x41, 0x43, 0x59, 0x5f, 0x46, 0x55, 0x4e, 0x44, 0x53, 0x10, 0x02, 0x12, 0x24, 0x0a, 0x20, 0x49, + 0x4e, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x5f, 0x46, 0x55, + 0x4e, 0x44, 0x53, 0x5f, 0x41, 0x4e, 0x44, 0x5f, 0x50, 0x41, 0x59, 0x4c, 0x4f, 0x41, 0x44, 0x10, + 0x03, 0x12, 0x22, 0x0a, 0x1e, 0x49, 0x4e, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x47, + 0x41, 0x43, 0x59, 0x5f, 0x47, 0x41, 0x53, 0x5f, 0x41, 0x4e, 0x44, 0x5f, 0x50, 0x41, 0x59, 0x4c, + 0x4f, 0x41, 0x44, 0x10, 0x04, 0x42, 0xb2, 0x01, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x75, 0x65, + 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x42, 0x0a, 0x54, 0x79, 0x70, 0x65, + 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x75, 0x73, 0x68, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2f, 0x70, + 0x75, 0x73, 0x68, 0x2d, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2d, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x61, + 0x70, 0x69, 0x2f, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x76, 0x31, 0x3b, + 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x55, 0x58, + 0x58, 0xaa, 0x02, 0x0c, 0x55, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x56, 0x31, + 0xca, 0x02, 0x0c, 0x55, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x5c, 0x56, 0x31, 0xe2, + 0x02, 0x18, 0x55, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x5c, 0x56, 0x31, 0x5c, 0x47, + 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0d, 0x55, 0x65, 0x78, + 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, } var ( @@ -12642,7 +12033,7 @@ func file_uexecutor_v1_types_proto_rawDescGZIP() []byte { } var file_uexecutor_v1_types_proto_enumTypes = make([]protoimpl.EnumInfo, 5) -var file_uexecutor_v1_types_proto_msgTypes = make([]protoimpl.MessageInfo, 14) +var file_uexecutor_v1_types_proto_msgTypes = make([]protoimpl.MessageInfo, 13) var file_uexecutor_v1_types_proto_goTypes = []interface{}{ (VerificationType)(0), // 0: uexecutor.v1.VerificationType (UniversalTxStatus)(0), // 1: uexecutor.v1.UniversalTxStatus @@ -12651,39 +12042,38 @@ var file_uexecutor_v1_types_proto_goTypes = []interface{}{ (InboundTxTypeLegacy)(0), // 4: uexecutor.v1.InboundTxTypeLegacy (*Params)(nil), // 5: uexecutor.v1.Params (*UniversalPayload)(nil), // 6: uexecutor.v1.UniversalPayload - (*MigrationPayload)(nil), // 7: uexecutor.v1.MigrationPayload - (*UniversalAccountId)(nil), // 8: uexecutor.v1.UniversalAccountId - (*RevertInstructions)(nil), // 9: uexecutor.v1.RevertInstructions - (*Inbound)(nil), // 10: uexecutor.v1.Inbound - (*PCTx)(nil), // 11: uexecutor.v1.PCTx - (*OutboundObservation)(nil), // 12: uexecutor.v1.OutboundObservation - (*OriginatingPcTx)(nil), // 13: uexecutor.v1.OriginatingPcTx - (*OutboundTx)(nil), // 14: uexecutor.v1.OutboundTx - (*UniversalTx)(nil), // 15: uexecutor.v1.UniversalTx - (*InboundLegacy)(nil), // 16: uexecutor.v1.InboundLegacy - (*OutboundTxLegacy)(nil), // 17: uexecutor.v1.OutboundTxLegacy - (*UniversalTxLegacy)(nil), // 18: uexecutor.v1.UniversalTxLegacy + (*UniversalAccountId)(nil), // 7: uexecutor.v1.UniversalAccountId + (*RevertInstructions)(nil), // 8: uexecutor.v1.RevertInstructions + (*Inbound)(nil), // 9: uexecutor.v1.Inbound + (*PCTx)(nil), // 10: uexecutor.v1.PCTx + (*OutboundObservation)(nil), // 11: uexecutor.v1.OutboundObservation + (*OriginatingPcTx)(nil), // 12: uexecutor.v1.OriginatingPcTx + (*OutboundTx)(nil), // 13: uexecutor.v1.OutboundTx + (*UniversalTx)(nil), // 14: uexecutor.v1.UniversalTx + (*InboundLegacy)(nil), // 15: uexecutor.v1.InboundLegacy + (*OutboundTxLegacy)(nil), // 16: uexecutor.v1.OutboundTxLegacy + (*UniversalTxLegacy)(nil), // 17: uexecutor.v1.UniversalTxLegacy } var file_uexecutor_v1_types_proto_depIdxs = []int32{ 0, // 0: uexecutor.v1.UniversalPayload.v_type:type_name -> uexecutor.v1.VerificationType 3, // 1: uexecutor.v1.Inbound.tx_type:type_name -> uexecutor.v1.TxType 6, // 2: uexecutor.v1.Inbound.universal_payload:type_name -> uexecutor.v1.UniversalPayload - 9, // 3: uexecutor.v1.Inbound.revert_instructions:type_name -> uexecutor.v1.RevertInstructions + 8, // 3: uexecutor.v1.Inbound.revert_instructions:type_name -> uexecutor.v1.RevertInstructions 3, // 4: uexecutor.v1.OutboundTx.tx_type:type_name -> uexecutor.v1.TxType - 13, // 5: uexecutor.v1.OutboundTx.pc_tx:type_name -> uexecutor.v1.OriginatingPcTx - 12, // 6: uexecutor.v1.OutboundTx.observed_tx:type_name -> uexecutor.v1.OutboundObservation + 12, // 5: uexecutor.v1.OutboundTx.pc_tx:type_name -> uexecutor.v1.OriginatingPcTx + 11, // 6: uexecutor.v1.OutboundTx.observed_tx:type_name -> uexecutor.v1.OutboundObservation 2, // 7: uexecutor.v1.OutboundTx.outbound_status:type_name -> uexecutor.v1.Status - 9, // 8: uexecutor.v1.OutboundTx.revert_instructions:type_name -> uexecutor.v1.RevertInstructions - 11, // 9: uexecutor.v1.OutboundTx.pc_revert_execution:type_name -> uexecutor.v1.PCTx - 11, // 10: uexecutor.v1.OutboundTx.pc_refund_execution:type_name -> uexecutor.v1.PCTx - 10, // 11: uexecutor.v1.UniversalTx.inbound_tx:type_name -> uexecutor.v1.Inbound - 11, // 12: uexecutor.v1.UniversalTx.pc_tx:type_name -> uexecutor.v1.PCTx - 14, // 13: uexecutor.v1.UniversalTx.outbound_tx:type_name -> uexecutor.v1.OutboundTx + 8, // 8: uexecutor.v1.OutboundTx.revert_instructions:type_name -> uexecutor.v1.RevertInstructions + 10, // 9: uexecutor.v1.OutboundTx.pc_revert_execution:type_name -> uexecutor.v1.PCTx + 10, // 10: uexecutor.v1.OutboundTx.pc_refund_execution:type_name -> uexecutor.v1.PCTx + 9, // 11: uexecutor.v1.UniversalTx.inbound_tx:type_name -> uexecutor.v1.Inbound + 10, // 12: uexecutor.v1.UniversalTx.pc_tx:type_name -> uexecutor.v1.PCTx + 13, // 13: uexecutor.v1.UniversalTx.outbound_tx:type_name -> uexecutor.v1.OutboundTx 4, // 14: uexecutor.v1.InboundLegacy.tx_type:type_name -> uexecutor.v1.InboundTxTypeLegacy 6, // 15: uexecutor.v1.InboundLegacy.universal_payload:type_name -> uexecutor.v1.UniversalPayload - 16, // 16: uexecutor.v1.UniversalTxLegacy.inbound_tx:type_name -> uexecutor.v1.InboundLegacy - 11, // 17: uexecutor.v1.UniversalTxLegacy.pc_tx:type_name -> uexecutor.v1.PCTx - 17, // 18: uexecutor.v1.UniversalTxLegacy.outbound_tx:type_name -> uexecutor.v1.OutboundTxLegacy + 15, // 16: uexecutor.v1.UniversalTxLegacy.inbound_tx:type_name -> uexecutor.v1.InboundLegacy + 10, // 17: uexecutor.v1.UniversalTxLegacy.pc_tx:type_name -> uexecutor.v1.PCTx + 16, // 18: uexecutor.v1.UniversalTxLegacy.outbound_tx:type_name -> uexecutor.v1.OutboundTxLegacy 1, // 19: uexecutor.v1.UniversalTxLegacy.universal_status:type_name -> uexecutor.v1.UniversalTxStatus 20, // [20:20] is the sub-list for method output_type 20, // [20:20] is the sub-list for method input_type @@ -12723,18 +12113,6 @@ func file_uexecutor_v1_types_proto_init() { } } file_uexecutor_v1_types_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*MigrationPayload); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_uexecutor_v1_types_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*UniversalAccountId); i { case 0: return &v.state @@ -12746,7 +12124,7 @@ func file_uexecutor_v1_types_proto_init() { return nil } } - file_uexecutor_v1_types_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + file_uexecutor_v1_types_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RevertInstructions); i { case 0: return &v.state @@ -12758,7 +12136,7 @@ func file_uexecutor_v1_types_proto_init() { return nil } } - file_uexecutor_v1_types_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + file_uexecutor_v1_types_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Inbound); i { case 0: return &v.state @@ -12770,7 +12148,7 @@ func file_uexecutor_v1_types_proto_init() { return nil } } - file_uexecutor_v1_types_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + file_uexecutor_v1_types_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PCTx); i { case 0: return &v.state @@ -12782,7 +12160,7 @@ func file_uexecutor_v1_types_proto_init() { return nil } } - file_uexecutor_v1_types_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + file_uexecutor_v1_types_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*OutboundObservation); i { case 0: return &v.state @@ -12794,7 +12172,7 @@ func file_uexecutor_v1_types_proto_init() { return nil } } - file_uexecutor_v1_types_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + file_uexecutor_v1_types_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*OriginatingPcTx); i { case 0: return &v.state @@ -12806,7 +12184,7 @@ func file_uexecutor_v1_types_proto_init() { return nil } } - file_uexecutor_v1_types_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + file_uexecutor_v1_types_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*OutboundTx); i { case 0: return &v.state @@ -12818,7 +12196,7 @@ func file_uexecutor_v1_types_proto_init() { return nil } } - file_uexecutor_v1_types_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + file_uexecutor_v1_types_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*UniversalTx); i { case 0: return &v.state @@ -12830,7 +12208,7 @@ func file_uexecutor_v1_types_proto_init() { return nil } } - file_uexecutor_v1_types_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + file_uexecutor_v1_types_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*InboundLegacy); i { case 0: return &v.state @@ -12842,7 +12220,7 @@ func file_uexecutor_v1_types_proto_init() { return nil } } - file_uexecutor_v1_types_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + file_uexecutor_v1_types_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*OutboundTxLegacy); i { case 0: return &v.state @@ -12854,7 +12232,7 @@ func file_uexecutor_v1_types_proto_init() { return nil } } - file_uexecutor_v1_types_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + file_uexecutor_v1_types_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*UniversalTxLegacy); i { case 0: return &v.state @@ -12873,7 +12251,7 @@ func file_uexecutor_v1_types_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_uexecutor_v1_types_proto_rawDesc, NumEnums: 5, - NumMessages: 14, + NumMessages: 13, NumExtensions: 0, NumServices: 0, }, diff --git a/app/ante/account_init_decorator.go b/app/ante/account_init_decorator.go index 2149dccf7..2c0b52815 100644 --- a/app/ante/account_init_decorator.go +++ b/app/ante/account_init_decorator.go @@ -25,9 +25,9 @@ import ( // msg servers gates on IsBondedUniversalValidator (VoteChainMeta gates on the // strictly narrower eligible-voter set, of which bonded is a component). // -// The remaining gasless types - MsgExecutePayload and MsgMigrateUEA - are -// deliberately absent: they are permissionless by design and creating an account -// for a first-time universal user is the intended behaviour of this decorator. +// The remaining gasless type - MsgExecutePayload - is deliberately absent: it is +// permissionless by design and creating an account for a first-time universal +// user is the intended behaviour of this decorator. var validatorOnlyGaslessMsgTypes = map[string]struct{}{ sdk.MsgTypeURL(&uexecutortypes.MsgVoteInbound{}): {}, sdk.MsgTypeURL(&uexecutortypes.MsgVoteOutbound{}): {}, diff --git a/app/ante/account_init_signer_binding_test.go b/app/ante/account_init_signer_binding_test.go index 4ab2d7458..80ac6e6c8 100644 --- a/app/ante/account_init_signer_binding_test.go +++ b/app/ante/account_init_signer_binding_test.go @@ -68,8 +68,8 @@ func aliasedSigner(t *testing.T, length int) sdk.AccAddress { return addr } -// gaslessMsgFor builds one of the two user-facing gasless messages with the -// given declared signer. +// gaslessMsgFor builds a user-facing gasless message with the given declared +// signer. func gaslessMsgFor(t *testing.T, msgType string, signer sdk.AccAddress) sdk.Msg { t.Helper() ua := &uexecutortypes.UniversalAccountId{ @@ -89,17 +89,6 @@ func gaslessMsgFor(t *testing.T, msgType string, signer sdk.AccAddress) sdk.Msg }, VerificationData: "0xabcdef", } - case "MsgMigrateUEA": - return &uexecutortypes.MsgMigrateUEA{ - Signer: signer.String(), - UniversalAccountId: ua, - MigrationPayload: &uexecutortypes.MigrationPayload{ - Migration: "0x000000000000000000000000000000000000beef", - Nonce: "0", - Deadline: "1", - }, - Signature: "0xabcdef", - } default: t.Fatalf("unknown msg type %q", msgType) return nil @@ -146,9 +135,9 @@ func newSignerBindingDecorator(t *testing.T, encCfg appparams.EncodingConfig) (a t.Helper() ak := newMockAccountKeeperAnte(sdk.AccAddress([]byte("feeCollector"))) // The uvalidator mock knows about nobody, so it rejects every address it is - // asked about. Every test in this file uses MsgExecutePayload / MsgMigrateUEA, - // which are deliberately NOT gated on validator status (F-2026-18186), so they - // must keep working against it. + // asked about. Every test in this file uses MsgExecutePayload, which is + // deliberately NOT gated on validator status (F-2026-18186), so it must keep + // working against it. return ante.NewAccountInitDecorator(ak, newMockUValidatorKeeperAnte(), encCfg.TxConfig.SignModeHandler()), ak } @@ -157,11 +146,11 @@ func newSignerBindingDecorator(t *testing.T, encCfg appparams.EncodingConfig) (a // onto the uexecutor module address while being signed by an unrelated key. // // Hacken's PoC only used the 21-byte case; truncation works for ANY length > 20, -// so 21, 22 and 32 bytes are all covered, against both gasless messages. +// so 21, 22 and 32 bytes are all covered. func TestAccountInitDecorator_RejectsAliasedModuleSigner(t *testing.T) { encCfg := newSignerBindingEncodingConfig(t) - for _, msgType := range []string{"MsgExecutePayload", "MsgMigrateUEA"} { + for _, msgType := range []string{"MsgExecutePayload"} { for _, length := range []int{21, 22, 32} { t.Run(fmt.Sprintf("%s/%dbytes", msgType, length), func(t *testing.T) { attackerKey := secp256k1.GenPrivKey() @@ -214,7 +203,7 @@ func TestAccountInitDecorator_RejectsMismatchedSigner(t *testing.T) { func TestAccountInitDecorator_AcceptsMatchingSigner(t *testing.T) { encCfg := newSignerBindingEncodingConfig(t) - for _, msgType := range []string{"MsgExecutePayload", "MsgMigrateUEA"} { + for _, msgType := range []string{"MsgExecutePayload"} { t.Run(msgType, func(t *testing.T) { key := secp256k1.GenPrivKey() signer := sdk.AccAddress(key.PubKey().Address()) diff --git a/app/ante/account_init_validator_gate_test.go b/app/ante/account_init_validator_gate_test.go index cc13b1ae4..953d78715 100644 --- a/app/ante/account_init_validator_gate_test.go +++ b/app/ante/account_init_validator_gate_test.go @@ -256,15 +256,14 @@ func TestAccountInitDecorator_BondedValidatorVoteStillWorks(t *testing.T) { } // TestAccountInitDecorator_PermissionlessGaslessMsgsUngated proves the scoping. -// MsgExecutePayload and MsgMigrateUEA are permissionless by design: a first-time -// universal user has no account and no validator status, and creating the account -// for them is the intended behaviour of this decorator. Gating them would break -// real users, so they must still work against a uvalidator keeper that rejects -// every address. +// MsgExecutePayload is permissionless by design: a first-time universal user has +// no account and no validator status, and creating the account for them is the +// intended behaviour of this decorator. Gating it would break real users, so it +// must still work against a uvalidator keeper that rejects every address. func TestAccountInitDecorator_PermissionlessGaslessMsgsUngated(t *testing.T) { encCfg := newSignerBindingEncodingConfig(t) - for _, msgType := range []string{"MsgExecutePayload", "MsgMigrateUEA"} { + for _, msgType := range []string{"MsgExecutePayload"} { t.Run(msgType, func(t *testing.T) { key := secp256k1.GenPrivKey() signer := sdk.AccAddress(key.PubKey().Address()) diff --git a/app/txpolicy/gasless.go b/app/txpolicy/gasless.go index b77fe52e6..3648edf72 100644 --- a/app/txpolicy/gasless.go +++ b/app/txpolicy/gasless.go @@ -15,7 +15,6 @@ func IsGaslessTx(tx sdk.Tx) bool { var ( // GaslessMsgTypes defines the message types that are allowed in gasless transactions GaslessMsgTypes = []string{ - sdk.MsgTypeURL(&uexecutortypes.MsgMigrateUEA{}), sdk.MsgTypeURL(&uexecutortypes.MsgExecutePayload{}), sdk.MsgTypeURL(&uexecutortypes.MsgVoteInbound{}), sdk.MsgTypeURL(&uexecutortypes.MsgVoteOutbound{}), diff --git a/proto/uexecutor/v1/tx.proto b/proto/uexecutor/v1/tx.proto index 48ea21115..bd3014797 100755 --- a/proto/uexecutor/v1/tx.proto +++ b/proto/uexecutor/v1/tx.proto @@ -22,9 +22,6 @@ service Msg { // ExecutePayload defines a message for executing a universal payload rpc ExecutePayload(MsgExecutePayload) returns (MsgExecutePayloadResponse); - // MigrateUEA defines a message for migrating UEA - rpc MigrateUEA(MsgMigrateUEA) returns (MsgMigrateUEAResponse); - // VoteInbound defines a message for voting on synthetic assets bridging from external chain to PC rpc VoteInbound(MsgVoteInbound) returns (MsgVoteInboundResponse); @@ -83,27 +80,6 @@ message MsgExecutePayload { // MsgExecutePayloadResponse defines the response for MsgExecutePayload. message MsgExecutePayloadResponse {} -// MsgMigrateUEA defines a message for migarting Universal Executor Account (UEA) -message MsgMigrateUEA { - option (amino.name) = "uexecutor/MsgMigrateUEA"; - option (cosmos.msg.v1.signer) = "signer"; - - // signer is the Cosmos address initiating the tx (used for tx signing) - string signer = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; - - // universal_account_id is the identifier of the owner account - UniversalAccountId universal_account_id = 2; - - // payload is the migration payload to be executed - MigrationPayload migration_payload = 3; - - // signature is the bytes passed as verifier data for the given payload. - string signature = 4; -} - -// MsgMigrateUEAResponse defines the response for MsgMigrateUEA. -message MsgMigrateUEAResponse {} - // MsgVoteInbound allows a universal validator to vote on an inbound transfer. message MsgVoteInbound { option (amino.name) = "ue/MsgVoteInbound"; diff --git a/proto/uexecutor/v1/types.proto b/proto/uexecutor/v1/types.proto index 9b9fab47e..209f18130 100644 --- a/proto/uexecutor/v1/types.proto +++ b/proto/uexecutor/v1/types.proto @@ -45,17 +45,6 @@ message UniversalPayload { VerificationType v_type = 9; // Type of verification to use before execution } -// MigrationPayload mirrors the Solidity struct -message MigrationPayload { - option (amino.name) = "uexecutor/migration_payload"; - option (gogoproto.equal) = true; - option (gogoproto.goproto_stringer) = false; - - string migration = 1; // Migration Address - string nonce = 2; // unit256 as string - string deadline = 3; // unit256 as string -} - // UniversalAccountId is the identifier of a owner account message UniversalAccountId { option (amino.name) = "uexecutor/universal_account"; diff --git a/x/uexecutor/keeper/evm.go b/x/uexecutor/keeper/evm.go index 1332a1db5..1a6048953 100644 --- a/x/uexecutor/keeper/evm.go +++ b/x/uexecutor/keeper/evm.go @@ -242,40 +242,6 @@ func (k Keeper) CallUEAExecutePayload( ) } -// CallUEAMigrateUEA migrates UEA through existing UEA -func (k Keeper) CallUEAMigrateUEA( - ctx sdk.Context, - from, ueaAddr common.Address, - migration_payload *types.MigrationPayload, - signature []byte, -) (*evmtypes.MsgEthereumTxResponse, error) { - abi, err := types.ParseUeaABI() - if err != nil { - return nil, errors.Wrap(err, "failed to parse UEA ABI") - } - - abiMigrationPayload, err := types.NewAbiMigrationPayload(migration_payload) - if err != nil { - return nil, errors.Wrapf(err, "failed to create universal payload") - } - - return k.evmKeeper.DerivedEVMCall( - ctx, - abi, - from, - ueaAddr, - big.NewInt(0), - nil, - true, // commit = true (real tx, not simulation) - false, // gasless = false (@dev: we need gas to be emitted in the tx receipt) - false, // not a module sender - nil, - "migrateUEA", - abiMigrationPayload, - signature, - ) -} - // CallUEADomainSeparator fetches the domainSeparator from the UEA contract func (k Keeper) CallUEADomainSeparator( ctx sdk.Context, diff --git a/x/uexecutor/keeper/msg_migrate_uea.go b/x/uexecutor/keeper/msg_migrate_uea.go deleted file mode 100644 index f59d4a301..000000000 --- a/x/uexecutor/keeper/msg_migrate_uea.go +++ /dev/null @@ -1,84 +0,0 @@ -package keeper - -import ( - "context" - "fmt" - - "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/common" - "github.com/pushchain/push-chain-node/utils" - "github.com/pushchain/push-chain-node/x/uexecutor/types" -) - -// updateParams is for updating params collections of the module -func (k Keeper) MigrateUEA(ctx context.Context, evmFrom common.Address, universalAccountId *types.UniversalAccountId, migrationPayload *types.MigrationPayload, signature string) error { - sdkCtx := sdk.UnwrapSDKContext(ctx) - - // Get Caip2Identifier for the universal account - caip2Identifier := universalAccountId.GetCAIP2() - - k.Logger().Info("migrate UEA", - "from", evmFrom.Hex(), - "chain", caip2Identifier, - "owner", universalAccountId.Owner, - ) - - // Step 1: Parse and validate payload and signature - _, err := types.NewAbiMigrationPayload(migrationPayload) - if err != nil { - return errors.Wrapf(err, "invalid migration payload") - } - - // add signature verification - signatureVal, err := utils.HexToBytes(signature) - if err != nil { - return errors.Wrapf(err, "invalid signature format") - } - - chainConfig, err := k.uregistryKeeper.GetChainConfig(sdkCtx, caip2Identifier) - if err != nil { - return errors.Wrapf(err, "failed to get chain config for chain %s", caip2Identifier) - } - - // TODO: Decide later if migration should be disabled if inbound is disabled - if !chainConfig.Enabled.IsInboundEnabled { - k.Logger().Warn("migrate UEA rejected: chain not enabled", "chain", caip2Identifier) - return fmt.Errorf("chain %s is not enabled", caip2Identifier) - } - - factoryAddress := common.HexToAddress(types.FACTORY_PROXY_ADDRESS_HEX) - - // Step 2: Compute smart account address - // Calling factory contract to compute the UEA address - ueaAddr, isDeployed, err := k.CallFactoryToGetUEAAddressForOrigin(sdkCtx, evmFrom, factoryAddress, universalAccountId) - if err != nil { - return err - } - - if !isDeployed { - k.Logger().Warn("migrate UEA rejected: UEA not deployed", "chain", caip2Identifier, "owner", universalAccountId.Owner) - return fmt.Errorf("UEA is not deployed") - } - - k.Logger().Debug("migrating UEA", - "uea", ueaAddr.Hex(), - "chain", caip2Identifier, - "from", evmFrom.Hex(), - ) - - // Step 3: Migrate UEA through UEA - receipt, err := k.CallUEAMigrateUEA(sdkCtx, evmFrom, ueaAddr, migrationPayload, signatureVal) - if err != nil { - return err - } - - k.Logger().Info("UEA migrated", - "chain", caip2Identifier, - "uea", ueaAddr.Hex(), - "tx_hash", receipt.Hash, - "gas_used", receipt.GasUsed, - ) - - return nil -} diff --git a/x/uexecutor/keeper/msg_server.go b/x/uexecutor/keeper/msg_server.go index e35ec08f8..97c292ad3 100755 --- a/x/uexecutor/keeper/msg_server.go +++ b/x/uexecutor/keeper/msg_server.go @@ -54,21 +54,6 @@ func (ms msgServer) ExecutePayload(ctx context.Context, msg *types.MsgExecutePay return &types.MsgExecutePayloadResponse{}, nil } -// MigrateUEA handles UEA Migration. -func (ms msgServer) MigrateUEA(ctx context.Context, msg *types.MsgMigrateUEA) (*types.MsgMigrateUEAResponse, error) { - _, evmFromAddress, err := utils.GetAddressPair(msg.Signer) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse signer address") - } - - err = ms.k.MigrateUEA(ctx, evmFromAddress, msg.UniversalAccountId, msg.MigrationPayload, msg.Signature) - if err != nil { - return nil, err - } - - return &types.MsgMigrateUEAResponse{}, nil -} - // VoteInbound implements types.MsgServer. func (ms msgServer) VoteInbound(ctx context.Context, msg *types.MsgVoteInbound) (*types.MsgVoteInboundResponse, error) { signerAccAddr, err := sdk.AccAddressFromBech32(msg.Signer) diff --git a/x/uexecutor/keeper/msg_server_test.go b/x/uexecutor/keeper/msg_server_test.go index 4465e1e42..c69634f1f 100755 --- a/x/uexecutor/keeper/msg_server_test.go +++ b/x/uexecutor/keeper/msg_server_test.go @@ -188,80 +188,3 @@ func TestMsgServer_ExecutePayload(t *testing.T) { }) } - -func TestMsgServer_MigrateUEA(t *testing.T) { - f := SetupTest(t) - - validSigner := f.addrs[0] - validUA := &types.UniversalAccountId{ - ChainNamespace: "eip155", - ChainId: "11155111", - Owner: "0x000000000000000000000000000000000000dead", - } - validMP := &types.MigrationPayload{ - Migration: "0x1234567890abcdef1234567890abcdef12345670", - Nonce: "1", - Deadline: "some-deadline", - } - - t.Run("fail; invalid signer address", func(t *testing.T) { - msg := &types.MsgMigrateUEA{ - Signer: "invalid_address", - UniversalAccountId: validUA, - MigrationPayload: validMP, - Signature: "0x", - } - - _, err := f.msgServer.MigrateUEA(f.ctx, msg) - require.ErrorContains(t, err, "failed to parse signer address") - }) - - t.Run("Fail : ChainConfig for Universal Accout not set", func(t *testing.T) { - // You can inject failure in f.app or f.k.utvKeeper if mockable - msg := &types.MsgMigrateUEA{ - Signer: validSigner.String(), - UniversalAccountId: validUA, - MigrationPayload: validMP, - Signature: "0x", - } - - f.mockUregistryKeeper.EXPECT().GetChainConfig(gomock.Any(), "eip155:11155111").Return(uregistrytypes.ChainConfig{}, errors.New("failed to get chain config for chain eip155:11155111")) - - _, err := f.msgServer.MigrateUEA(f.ctx, msg) - require.ErrorContains(t, err, "failed to get chain config") - }) - - t.Run("Fail: CallFactoryToComputeUEAAddress", func(t *testing.T) { - // You can inject failure in f.app or f.k.utvKeeper if mockable - msg := &types.MsgMigrateUEA{ - Signer: validSigner.String(), - UniversalAccountId: validUA, - MigrationPayload: validMP, - Signature: "0x", - } - - chainConfigTest := uregistrytypes.ChainConfig{ - Chain: "eip155:11155111", - VmType: uregistrytypes.VmType_EVM, // replace with appropriate VM_TYPE enum value - PublicRpcUrl: "https://mainnet.infura.io/v3/YOUR_PROJECT_ID", - GatewayAddress: "0x1234567890abcdef1234567890abcdef12345678", - BlockConfirmation: &uregistrytypes.BlockConfirmation{ - FastInbound: 3, - StandardInbound: 10, - }, - GatewayMethods: []*uregistrytypes.GatewayMethods{}, - Enabled: &uregistrytypes.ChainEnabled{ - IsInboundEnabled: true, - IsOutboundEnabled: true, - }, - } - - f.mockUregistryKeeper.EXPECT().GetChainConfig(gomock.Any(), "eip155:11155111").Return(chainConfigTest, nil) - - f.mockEVMKeeper.EXPECT().NewStateDB(gomock.Any()).Return(nil).AnyTimes() - f.mockEVMKeeper.EXPECT().CallEVM(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errors.New("CallFactoryToComputeUEAAddress Failed")).AnyTimes() - - _, err := f.msgServer.MigrateUEA(f.ctx, msg) - require.ErrorContains(t, err, "CallFactoryToComputeUEAAddress Failed") - }) -} diff --git a/x/uexecutor/types/abi.go b/x/uexecutor/types/abi.go index 23ab91fa9..867109c49 100644 --- a/x/uexecutor/types/abi.go +++ b/x/uexecutor/types/abi.go @@ -4,8 +4,6 @@ import ( "math/big" "strings" - "errors" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/pushchain/push-chain-node/utils" @@ -919,23 +917,6 @@ func NewAbiUniversalPayload(proto *UniversalPayload) (AbiUniversalPayload, error }, nil } -type AbiMigrationPayload struct { - Migration common.Address - Nonce *big.Int - Deadline *big.Int -} - -func NewAbiMigrationPayload(proto *MigrationPayload) (AbiMigrationPayload, error) { - if proto.Migration == "" { - return AbiMigrationPayload{}, errors.New("invalid migration payload") - } - return AbiMigrationPayload{ - Migration: common.HexToAddress(proto.Migration), - Nonce: utils.StringToBigInt(proto.Nonce), - Deadline: utils.StringToBigInt(proto.Deadline), - }, nil -} - type AbiUniversalAccountId struct { ChainNamespace string ChainId string diff --git a/x/uexecutor/types/migration_payload.go b/x/uexecutor/types/migration_payload.go deleted file mode 100644 index bd5533f4c..000000000 --- a/x/uexecutor/types/migration_payload.go +++ /dev/null @@ -1,49 +0,0 @@ -package types - -import ( - "encoding/json" - "math/big" - "strings" - - "cosmossdk.io/errors" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/pushchain/push-chain-node/utils" -) - -// Stringer method for Params. -func (p MigrationPayload) String() string { - bz, err := json.Marshal(p) - if err != nil { - panic(err) - } - - return string(bz) -} - -// ValidateBasic does the sanity check on the UniversalPayload fields. -func (p MigrationPayload) ValidateBasic() error { - // Validate 'migration' address - if strings.TrimSpace(p.Migration) == "" { - return errors.Wrap(sdkerrors.ErrInvalidAddress, "migration address cannot be empty") - } - if !utils.IsValidAddress(p.Migration, utils.HEX) { - return errors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid migration contract address format: %s", p.Migration) - } - - // Validate all numeric string fields as uint256 - uintFields := map[string]string{ - "nonce": p.Nonce, - "deadline": p.Deadline, - } - - for fieldName, value := range uintFields { - if value != "" { - bi, ok := new(big.Int).SetString(value, 10) - if !ok || bi.Sign() < 0 { - return errors.Wrapf(sdkerrors.ErrInvalidRequest, "%s must be a valid unsigned integer", fieldName) - } - } - } - - return nil -} diff --git a/x/uexecutor/types/migration_payload_test.go b/x/uexecutor/types/migration_payload_test.go deleted file mode 100644 index fe33c71b2..000000000 --- a/x/uexecutor/types/migration_payload_test.go +++ /dev/null @@ -1,107 +0,0 @@ -package types_test - -import ( - "testing" - - "github.com/pushchain/push-chain-node/x/uexecutor/types" - "github.com/stretchr/testify/require" -) - -func TestMsgMigrationPayload_ValidateBasic(t *testing.T) { - validSigner := "push1fgaewhyd9fkwtqaj9c233letwcuey6dgly9gv9" - invalidSigner := "invalid_bech32" - validUA := &types.UniversalAccountId{ - ChainNamespace: "eip155", - ChainId: "11155111", - Owner: "0x000000000000000000000000000000000000dead", - } - - validPayload := &types.MigrationPayload{ - Migration: "0x000000000000000000000000000000000000dead", - } - - invalidPayload := &types.MigrationPayload{ - Migration: "invalid_address", - } - - validSig := "abcdef0123456789" - // invalidSig := "zzzzzz" - - tests := []struct { - name string - msg *types.MsgMigrateUEA - expectErr bool - }{ - { - name: "valid msg", - msg: &types.MsgMigrateUEA{ - Signer: validSigner, - UniversalAccountId: validUA, - MigrationPayload: validPayload, - Signature: validSig, - }, - expectErr: false, - }, - { - name: "invalid signer", - msg: &types.MsgMigrateUEA{ - Signer: invalidSigner, - UniversalAccountId: validUA, - MigrationPayload: validPayload, - Signature: validSig, - }, - expectErr: true, - }, - { - name: "nil universal account", - msg: &types.MsgMigrateUEA{ - Signer: validSigner, - UniversalAccountId: nil, - MigrationPayload: validPayload, - Signature: validSig, - }, - expectErr: true, - }, - { - name: "nil universal payload", - msg: &types.MsgMigrateUEA{ - Signer: validSigner, - UniversalAccountId: validUA, - MigrationPayload: nil, - Signature: validSig, - }, - expectErr: true, - }, - { - name: "empty Signature", - msg: &types.MsgMigrateUEA{ - Signer: validSigner, - UniversalAccountId: validUA, - MigrationPayload: validPayload, - Signature: "", - }, - expectErr: true, - }, - { - name: "invalid universal payload data", - msg: &types.MsgMigrateUEA{ - Signer: validSigner, - UniversalAccountId: validUA, - MigrationPayload: invalidPayload, - Signature: validSig, - }, - expectErr: true, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - err := tc.msg.ValidateBasic() - if tc.expectErr { - require.Error(t, err, "expected error but got none") - } else { - require.NoError(t, err, "expected no error but got: %v", err) - } - }) - } -} diff --git a/x/uexecutor/types/msg_migrate_uea.go b/x/uexecutor/types/msg_migrate_uea.go deleted file mode 100644 index 25ec7db32..000000000 --- a/x/uexecutor/types/msg_migrate_uea.go +++ /dev/null @@ -1,89 +0,0 @@ -package types - -import ( - "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/ethereum/go-ethereum/common" -) - -var ( - _ sdk.Msg = &MsgMigrateUEA{} -) - -// NewMsgMigrateUEA creates new instance of MsgMigrateUEA -func NewMsgMigrateUEA( - sender sdk.Address, - universalAccountId *UniversalAccountId, - migrationPayload *MigrationPayload, - signature string, -) *MsgMigrateUEA { - return &MsgMigrateUEA{ - Signer: sender.String(), - UniversalAccountId: universalAccountId, - MigrationPayload: migrationPayload, - Signature: signature, - } -} - -// Route returns the name of the module -func (msg MsgMigrateUEA) Route() string { return ModuleName } - -// Type returns the action -func (msg MsgMigrateUEA) Type() string { return "migrate_uea" } - -// GetSignBytes implements the LegacyMsg interface. -func (msg MsgMigrateUEA) GetSignBytes() []byte { - return sdk.MustSortJSON(AminoCdc.MustMarshalJSON(&msg)) -} - -// GetSigners returns the expected signers for a MsgExecutePayload message. -func (msg *MsgMigrateUEA) GetSigners() []sdk.AccAddress { - addr, _ := sdk.AccAddressFromBech32(msg.Signer) - return []sdk.AccAddress{addr} -} - -// ValidateBasic does a sanity check on the provided data. -func (msg *MsgMigrateUEA) ValidateBasic() error { - // Validate signer. - // The length check is deliberate: bech32 account addresses may carry up to - // 255 bytes, and this signer is later converted to a 20-byte EVM address - // that keeps only the rightmost bytes. A longer signer would therefore - // collapse onto an unrelated EVM address, including module addresses that - // the UEA trusts. Reject it here, at CheckTx, before the ante chain runs. - signerBz, err := sdk.AccAddressFromBech32(msg.Signer) - if err != nil { - return errors.Wrap(err, "invalid signer address") - } - if len(signerBz) != common.AddressLength { - return errors.Wrapf(sdkerrors.ErrInvalidAddress, - "invalid signer address length: got %d bytes, want %d", len(signerBz), common.AddressLength) - } - - // Validate universalAccountId - if msg.UniversalAccountId == nil { - return errors.Wrap(sdkerrors.ErrInvalidRequest, "universal account cannot be nil") - } - - // Validate migration payload - if msg.MigrationPayload == nil { - return errors.Wrap(sdkerrors.ErrInvalidRequest, "migration payload cannot be nil") - } - - // Validate Signature - if len(msg.Signature) == 0 { - return errors.Wrap(sdkerrors.ErrInvalidRequest, "signature cannot be empty") - } - - // Validate universalAccountId structure - if err := msg.UniversalAccountId.ValidateBasic(); err != nil { - return errors.Wrap(err, "invalid universalAccountId") - } - - // Validate migration payload structure - if err := msg.MigrationPayload.ValidateBasic(); err != nil { - return errors.Wrap(err, "invalid migration payload") - } - - return nil -} diff --git a/x/uexecutor/types/msg_migrate_uea_test.go b/x/uexecutor/types/msg_migrate_uea_test.go deleted file mode 100644 index d566fd72f..000000000 --- a/x/uexecutor/types/msg_migrate_uea_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package types_test - -import ( - "testing" - - "github.com/pushchain/push-chain-node/x/uexecutor/types" - "github.com/stretchr/testify/require" -) - -func TestMsgMigrateUEA_ValidateBasic(t *testing.T) { - validSigner := "push1fgaewhyd9fkwtqaj9c233letwcuey6dgly9gv9" - - validUA := &types.UniversalAccountId{ - ChainNamespace: "eip155", - ChainId: "11155111", - Owner: "0x000000000000000000000000000000000000dead", - } - - validMigrationPayload := &types.MigrationPayload{ - Migration: "0x000000000000000000000000000000000000dead", - Nonce: "1", - Deadline: "9999999999", - } - - invalidMigrationPayload := &types.MigrationPayload{ - Migration: "bad_address", - Nonce: "1", - Deadline: "1", - } - - tests := []struct { - name string - msg *types.MsgMigrateUEA - expectErr bool - }{ - { - name: "valid msg", - msg: &types.MsgMigrateUEA{ - Signer: validSigner, - UniversalAccountId: validUA, - MigrationPayload: validMigrationPayload, - Signature: "0xabcdef", - }, - expectErr: false, - }, - { - name: "fails when migration payload validation fails (delegation)", - msg: &types.MsgMigrateUEA{ - Signer: validSigner, - UniversalAccountId: validUA, - MigrationPayload: invalidMigrationPayload, - Signature: "0xabcdef", - }, - expectErr: true, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - err := tc.msg.ValidateBasic() - if tc.expectErr { - require.Error(t, err) - } else { - require.NoError(t, err) - } - }) - } -} diff --git a/x/uexecutor/types/msg_signer_length_test.go b/x/uexecutor/types/msg_signer_length_test.go index 8b5f5c0a1..f81ca06b4 100644 --- a/x/uexecutor/types/msg_signer_length_test.go +++ b/x/uexecutor/types/msg_signer_length_test.go @@ -32,8 +32,8 @@ func aliasedModuleSigner(t *testing.T, length int) string { } // TestGaslessMsgs_RejectOverlongSigner is the CheckTx-time guard for -// F-2026-18200: both gasless messages must reject a signer that does not decode -// to exactly 20 bytes, before the ante chain ever runs. +// F-2026-18200: a gasless message must reject a signer that does not decode to +// exactly 20 bytes, before the ante chain ever runs. func TestGaslessMsgs_RejectOverlongSigner(t *testing.T) { validUA := &types.UniversalAccountId{ ChainNamespace: "eip155", @@ -56,20 +56,6 @@ func TestGaslessMsgs_RejectOverlongSigner(t *testing.T) { err := execMsg.ValidateBasic() require.Error(t, err, "MsgExecutePayload must reject a %d-byte signer", length) require.Contains(t, err.Error(), "invalid signer address length") - - migrateMsg := &types.MsgMigrateUEA{ - Signer: signer, - UniversalAccountId: validUA, - MigrationPayload: &types.MigrationPayload{ - Migration: "0x000000000000000000000000000000000000beef", - Nonce: "0", - Deadline: "1", - }, - Signature: "abcdef", - } - err = migrateMsg.ValidateBasic() - require.Error(t, err, "MsgMigrateUEA must reject a %d-byte signer", length) - require.Contains(t, err.Error(), "invalid signer address length") } } @@ -92,16 +78,4 @@ func TestGaslessMsgs_Accept20ByteSigner(t *testing.T) { VerificationData: "abcdef", } require.NoError(t, execMsg.ValidateBasic()) - - migrateMsg := &types.MsgMigrateUEA{ - Signer: signer, - UniversalAccountId: validUA, - MigrationPayload: &types.MigrationPayload{ - Migration: "0x000000000000000000000000000000000000beef", - Nonce: "0", - Deadline: "1", - }, - Signature: "abcdef", - } - require.NoError(t, migrateMsg.ValidateBasic()) } diff --git a/x/uexecutor/types/msg_vote_outbound.go b/x/uexecutor/types/msg_vote_outbound.go index 8b1c8d9a7..ae73d4327 100644 --- a/x/uexecutor/types/msg_vote_outbound.go +++ b/x/uexecutor/types/msg_vote_outbound.go @@ -74,6 +74,11 @@ func (msg *MsgVoteOutbound) ValidateBasic() error { return errors.Wrap(sdkerrors.ErrInvalidRequest, "observed_tx.gas_fee_used is required") } + // Length-capped, range-checked uint256 parse — see F-2026-18798. The value + // also feeds the outbound ballot key, so a malformed one must never be voted. + if _, err := ValidateUint256String(obs.GasFeeUsed, "observed_tx.gas_fee_used must be a valid uint256"); err != nil { + return err + } if obs.Success { // Success additionally requires tx_hash and block_height. diff --git a/x/uexecutor/types/outbound_tx.go b/x/uexecutor/types/outbound_tx.go index c1b8586c5..ae54c0703 100644 --- a/x/uexecutor/types/outbound_tx.go +++ b/x/uexecutor/types/outbound_tx.go @@ -2,7 +2,6 @@ package types import ( "encoding/json" - "math/big" "strings" "cosmossdk.io/errors" @@ -87,8 +86,9 @@ func (p OutboundTx) ValidateBasic() error { // gas_limit (uint) if strings.TrimSpace(p.GasLimit) != "" { - if _, ok := new(big.Int).SetString(p.GasLimit, 10); !ok { - return errors.Wrap(sdkerrors.ErrInvalidRequest, "gas_limit must be a valid uint") + // Length-capped, range-checked uint256 parse — see F-2026-18798. + if _, err := ValidateUint256String(p.GasLimit, "gas_limit must be a valid uint"); err != nil { + return err } } diff --git a/x/uexecutor/types/tx.pb.go b/x/uexecutor/types/tx.pb.go index 6a642042b..afcb1b1c5 100644 --- a/x/uexecutor/types/tx.pb.go +++ b/x/uexecutor/types/tx.pb.go @@ -240,116 +240,6 @@ func (m *MsgExecutePayloadResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgExecutePayloadResponse proto.InternalMessageInfo -// MsgMigrateUEA defines a message for migarting Universal Executor Account (UEA) -type MsgMigrateUEA struct { - // signer is the Cosmos address initiating the tx (used for tx signing) - Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` - // universal_account_id is the identifier of the owner account - UniversalAccountId *UniversalAccountId `protobuf:"bytes,2,opt,name=universal_account_id,json=universalAccountId,proto3" json:"universal_account_id,omitempty"` - // payload is the migration payload to be executed - MigrationPayload *MigrationPayload `protobuf:"bytes,3,opt,name=migration_payload,json=migrationPayload,proto3" json:"migration_payload,omitempty"` - // signature is the bytes passed as verifier data for the given payload. - Signature string `protobuf:"bytes,4,opt,name=signature,proto3" json:"signature,omitempty"` -} - -func (m *MsgMigrateUEA) Reset() { *m = MsgMigrateUEA{} } -func (m *MsgMigrateUEA) String() string { return proto.CompactTextString(m) } -func (*MsgMigrateUEA) ProtoMessage() {} -func (*MsgMigrateUEA) Descriptor() ([]byte, []int) { - return fileDescriptor_88d6216044506365, []int{4} -} -func (m *MsgMigrateUEA) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *MsgMigrateUEA) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_MsgMigrateUEA.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *MsgMigrateUEA) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgMigrateUEA.Merge(m, src) -} -func (m *MsgMigrateUEA) XXX_Size() int { - return m.Size() -} -func (m *MsgMigrateUEA) XXX_DiscardUnknown() { - xxx_messageInfo_MsgMigrateUEA.DiscardUnknown(m) -} - -var xxx_messageInfo_MsgMigrateUEA proto.InternalMessageInfo - -func (m *MsgMigrateUEA) GetSigner() string { - if m != nil { - return m.Signer - } - return "" -} - -func (m *MsgMigrateUEA) GetUniversalAccountId() *UniversalAccountId { - if m != nil { - return m.UniversalAccountId - } - return nil -} - -func (m *MsgMigrateUEA) GetMigrationPayload() *MigrationPayload { - if m != nil { - return m.MigrationPayload - } - return nil -} - -func (m *MsgMigrateUEA) GetSignature() string { - if m != nil { - return m.Signature - } - return "" -} - -// MsgMigrateUEAResponse defines the response for MsgMigrateUEA. -type MsgMigrateUEAResponse struct { -} - -func (m *MsgMigrateUEAResponse) Reset() { *m = MsgMigrateUEAResponse{} } -func (m *MsgMigrateUEAResponse) String() string { return proto.CompactTextString(m) } -func (*MsgMigrateUEAResponse) ProtoMessage() {} -func (*MsgMigrateUEAResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_88d6216044506365, []int{5} -} -func (m *MsgMigrateUEAResponse) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *MsgMigrateUEAResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_MsgMigrateUEAResponse.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *MsgMigrateUEAResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgMigrateUEAResponse.Merge(m, src) -} -func (m *MsgMigrateUEAResponse) XXX_Size() int { - return m.Size() -} -func (m *MsgMigrateUEAResponse) XXX_DiscardUnknown() { - xxx_messageInfo_MsgMigrateUEAResponse.DiscardUnknown(m) -} - -var xxx_messageInfo_MsgMigrateUEAResponse proto.InternalMessageInfo - // MsgVoteInbound allows a universal validator to vote on an inbound transfer. type MsgVoteInbound struct { // signer is the Cosmos address initiating the tx (used for tx signing) @@ -361,7 +251,7 @@ func (m *MsgVoteInbound) Reset() { *m = MsgVoteInbound{} } func (m *MsgVoteInbound) String() string { return proto.CompactTextString(m) } func (*MsgVoteInbound) ProtoMessage() {} func (*MsgVoteInbound) Descriptor() ([]byte, []int) { - return fileDescriptor_88d6216044506365, []int{6} + return fileDescriptor_88d6216044506365, []int{4} } func (m *MsgVoteInbound) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -412,7 +302,7 @@ func (m *MsgVoteInboundResponse) Reset() { *m = MsgVoteInboundResponse{} func (m *MsgVoteInboundResponse) String() string { return proto.CompactTextString(m) } func (*MsgVoteInboundResponse) ProtoMessage() {} func (*MsgVoteInboundResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_88d6216044506365, []int{7} + return fileDescriptor_88d6216044506365, []int{5} } func (m *MsgVoteInboundResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -454,7 +344,7 @@ func (m *MsgVoteOutbound) Reset() { *m = MsgVoteOutbound{} } func (m *MsgVoteOutbound) String() string { return proto.CompactTextString(m) } func (*MsgVoteOutbound) ProtoMessage() {} func (*MsgVoteOutbound) Descriptor() ([]byte, []int) { - return fileDescriptor_88d6216044506365, []int{8} + return fileDescriptor_88d6216044506365, []int{6} } func (m *MsgVoteOutbound) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -519,7 +409,7 @@ func (m *MsgVoteOutboundResponse) Reset() { *m = MsgVoteOutboundResponse func (m *MsgVoteOutboundResponse) String() string { return proto.CompactTextString(m) } func (*MsgVoteOutboundResponse) ProtoMessage() {} func (*MsgVoteOutboundResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_88d6216044506365, []int{9} + return fileDescriptor_88d6216044506365, []int{7} } func (m *MsgVoteOutboundResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -560,7 +450,7 @@ func (m *MsgVoteChainMeta) Reset() { *m = MsgVoteChainMeta{} } func (m *MsgVoteChainMeta) String() string { return proto.CompactTextString(m) } func (*MsgVoteChainMeta) ProtoMessage() {} func (*MsgVoteChainMeta) Descriptor() ([]byte, []int) { - return fileDescriptor_88d6216044506365, []int{10} + return fileDescriptor_88d6216044506365, []int{8} } func (m *MsgVoteChainMeta) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -625,7 +515,7 @@ func (m *MsgVoteChainMetaResponse) Reset() { *m = MsgVoteChainMetaRespon func (m *MsgVoteChainMetaResponse) String() string { return proto.CompactTextString(m) } func (*MsgVoteChainMetaResponse) ProtoMessage() {} func (*MsgVoteChainMetaResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_88d6216044506365, []int{11} + return fileDescriptor_88d6216044506365, []int{9} } func (m *MsgVoteChainMetaResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -670,7 +560,7 @@ func (m *MsgRevertStuckInbound) Reset() { *m = MsgRevertStuckInbound{} } func (m *MsgRevertStuckInbound) String() string { return proto.CompactTextString(m) } func (*MsgRevertStuckInbound) ProtoMessage() {} func (*MsgRevertStuckInbound) Descriptor() ([]byte, []int) { - return fileDescriptor_88d6216044506365, []int{12} + return fileDescriptor_88d6216044506365, []int{10} } func (m *MsgRevertStuckInbound) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -722,7 +612,7 @@ func (m *MsgRevertStuckInboundResponse) Reset() { *m = MsgRevertStuckInb func (m *MsgRevertStuckInboundResponse) String() string { return proto.CompactTextString(m) } func (*MsgRevertStuckInboundResponse) ProtoMessage() {} func (*MsgRevertStuckInboundResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_88d6216044506365, []int{13} + return fileDescriptor_88d6216044506365, []int{11} } func (m *MsgRevertStuckInboundResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -770,8 +660,6 @@ func init() { proto.RegisterType((*MsgUpdateParamsResponse)(nil), "uexecutor.v1.MsgUpdateParamsResponse") proto.RegisterType((*MsgExecutePayload)(nil), "uexecutor.v1.MsgExecutePayload") proto.RegisterType((*MsgExecutePayloadResponse)(nil), "uexecutor.v1.MsgExecutePayloadResponse") - proto.RegisterType((*MsgMigrateUEA)(nil), "uexecutor.v1.MsgMigrateUEA") - proto.RegisterType((*MsgMigrateUEAResponse)(nil), "uexecutor.v1.MsgMigrateUEAResponse") proto.RegisterType((*MsgVoteInbound)(nil), "uexecutor.v1.MsgVoteInbound") proto.RegisterType((*MsgVoteInboundResponse)(nil), "uexecutor.v1.MsgVoteInboundResponse") proto.RegisterType((*MsgVoteOutbound)(nil), "uexecutor.v1.MsgVoteOutbound") @@ -785,65 +673,61 @@ func init() { func init() { proto.RegisterFile("uexecutor/v1/tx.proto", fileDescriptor_88d6216044506365) } var fileDescriptor_88d6216044506365 = []byte{ - // 928 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x56, 0xbf, 0x6f, 0xdb, 0x46, - 0x14, 0x36, 0x6d, 0xd9, 0x85, 0x9f, 0x9c, 0xc4, 0x62, 0xe5, 0x5a, 0xa6, 0x63, 0xd9, 0x56, 0x7f, - 0x24, 0x95, 0x6b, 0xb1, 0x71, 0x81, 0x0c, 0xda, 0xec, 0x36, 0x40, 0x85, 0x42, 0x8d, 0xcb, 0xd8, - 0x0d, 0x90, 0x45, 0x38, 0x91, 0x17, 0x8a, 0x68, 0xc8, 0x23, 0x78, 0x47, 0x41, 0xde, 0x8a, 0x8e, - 0x9d, 0x3a, 0xf5, 0x7f, 0x28, 0xb2, 0x78, 0xe8, 0x1f, 0xd0, 0x31, 0x63, 0x50, 0xa0, 0x40, 0xa7, - 0xa0, 0xb0, 0x07, 0xff, 0x0b, 0x1d, 0x0b, 0x1e, 0xc9, 0x23, 0x8f, 0x54, 0xe5, 0xc2, 0x43, 0x91, - 0x45, 0x38, 0x7d, 0xdf, 0x7b, 0x8f, 0xef, 0xfb, 0xee, 0xee, 0x91, 0xb0, 0x16, 0xe2, 0x09, 0x36, - 0x43, 0x46, 0x02, 0x7d, 0xfc, 0x40, 0x67, 0x93, 0x8e, 0x1f, 0x10, 0x46, 0xd4, 0x15, 0x01, 0x77, - 0xc6, 0x0f, 0xb4, 0x1a, 0x72, 0x1d, 0x8f, 0xe8, 0xfc, 0x37, 0x0e, 0xd0, 0xd6, 0x4d, 0x42, 0x5d, - 0x42, 0x75, 0x97, 0xda, 0x51, 0xa2, 0x4b, 0xed, 0x84, 0x68, 0xc8, 0x05, 0xcf, 0x7c, 0x4c, 0x13, - 0x66, 0x4b, 0x62, 0xcc, 0x11, 0x72, 0xbc, 0x81, 0x8b, 0x19, 0x4a, 0xe8, 0xba, 0x4d, 0x6c, 0xc2, - 0x97, 0x7a, 0xb4, 0x4a, 0xd0, 0x8d, 0xf8, 0x39, 0x83, 0x98, 0x88, 0xff, 0xc4, 0x54, 0xeb, 0xa5, - 0x02, 0x77, 0xfa, 0xd4, 0x3e, 0xf5, 0x2d, 0xc4, 0xf0, 0x31, 0x0a, 0x90, 0x4b, 0xd5, 0x87, 0xb0, - 0x8c, 0x42, 0x36, 0x22, 0x81, 0xc3, 0xce, 0x1a, 0xca, 0x8e, 0x72, 0x7f, 0xf9, 0xa8, 0xf1, 0xfb, - 0xaf, 0xfb, 0xf5, 0x24, 0xf1, 0xd0, 0xb2, 0x02, 0x4c, 0xe9, 0x13, 0x16, 0x38, 0x9e, 0x6d, 0x64, - 0xa1, 0xea, 0x01, 0x2c, 0xf9, 0xbc, 0x42, 0x63, 0x7e, 0x47, 0xb9, 0x5f, 0x3d, 0xa8, 0x77, 0xf2, - 0x06, 0x74, 0xe2, 0xea, 0x47, 0x95, 0x57, 0x6f, 0xb6, 0xe7, 0x8c, 0x24, 0xb2, 0xfb, 0xc9, 0x0f, - 0x57, 0xe7, 0xed, 0xac, 0xc6, 0x8f, 0x57, 0xe7, 0xed, 0x8d, 0x4c, 0x62, 0xa1, 0xb3, 0xd6, 0x06, - 0xac, 0x17, 0x20, 0x03, 0x53, 0x9f, 0x78, 0x14, 0xb7, 0x7e, 0x9b, 0x87, 0x5a, 0x9f, 0xda, 0x8f, - 0x78, 0x2a, 0x3e, 0x46, 0x67, 0x2f, 0x08, 0xb2, 0xd4, 0x4f, 0x61, 0x89, 0x3a, 0xb6, 0x87, 0x83, - 0x6b, 0x75, 0x24, 0x71, 0xaa, 0x01, 0xf5, 0xd0, 0x73, 0xc6, 0x38, 0xa0, 0xe8, 0xc5, 0x00, 0x99, - 0x26, 0x09, 0x3d, 0x36, 0x70, 0xac, 0x44, 0xd2, 0x8e, 0x2c, 0xe9, 0x34, 0x8d, 0x3c, 0x8c, 0x03, - 0x7b, 0x96, 0xa1, 0x86, 0x25, 0x4c, 0xfd, 0x0a, 0x6a, 0x59, 0x4d, 0x3f, 0x6e, 0xad, 0xb1, 0xc0, - 0x0b, 0x36, 0xff, 0xa5, 0x60, 0x22, 0xc0, 0x58, 0x0d, 0x0b, 0x88, 0xba, 0x07, 0xb5, 0x31, 0x0e, - 0x9c, 0xe7, 0x8e, 0x89, 0x98, 0x43, 0xbc, 0x81, 0x85, 0x18, 0x6a, 0x54, 0x22, 0x75, 0xc6, 0x6a, - 0x9e, 0xf8, 0x02, 0x31, 0xd4, 0xdd, 0x8b, 0xec, 0x4d, 0xa4, 0x45, 0xde, 0x6e, 0x4a, 0xde, 0xca, - 0x66, 0xb5, 0x36, 0x61, 0xa3, 0x04, 0x0a, 0x7f, 0x7f, 0x99, 0x87, 0x5b, 0x7d, 0x6a, 0xf7, 0x1d, - 0x3b, 0x40, 0x0c, 0x9f, 0x3e, 0x3a, 0x7c, 0x7b, 0xbc, 0x75, 0x79, 0x4f, 0x91, 0x17, 0x33, 0xbd, - 0xed, 0xa7, 0x61, 0xc2, 0x5b, 0xb7, 0x80, 0xa8, 0x77, 0x61, 0x39, 0x6a, 0x15, 0xb1, 0x30, 0xc0, - 0x89, 0xa7, 0x19, 0xd0, 0xbd, 0x57, 0x30, 0x73, 0x5d, 0x32, 0x33, 0x73, 0xa6, 0xb5, 0x0e, 0x6b, - 0x12, 0x20, 0x4c, 0xfc, 0x59, 0x81, 0xdb, 0x7d, 0x6a, 0x7f, 0x4b, 0x18, 0xee, 0x79, 0x43, 0x12, - 0x7a, 0x37, 0x39, 0xa1, 0x3a, 0xbc, 0xe3, 0xc4, 0xc9, 0x89, 0x71, 0x6b, 0xb2, 0xce, 0xa4, 0xb2, - 0x91, 0x46, 0x75, 0x77, 0x0b, 0x7d, 0xd7, 0x42, 0xac, 0xcb, 0x5d, 0xb4, 0x1a, 0xf0, 0x9e, 0x8c, - 0x88, 0x96, 0xdf, 0xc4, 0x03, 0x22, 0xa2, 0x1e, 0x87, 0xec, 0xa6, 0x3d, 0xbf, 0x0b, 0x8b, 0x6c, - 0x92, 0x6e, 0xf5, 0xb2, 0x51, 0x61, 0x93, 0x9e, 0xa5, 0xae, 0xc1, 0x52, 0x18, 0xa3, 0x0b, 0x1c, - 0x5d, 0x0c, 0x39, 0x7c, 0x04, 0x55, 0x32, 0xa4, 0x38, 0x18, 0x63, 0x6b, 0xc0, 0x26, 0x7c, 0x1b, - 0xaa, 0x07, 0xbb, 0xb2, 0xc6, 0xb4, 0x95, 0xc7, 0x3c, 0x90, 0xef, 0xa1, 0x01, 0x69, 0xd6, 0xc9, - 0xa4, 0xfb, 0x71, 0x41, 0xb2, 0x3c, 0x53, 0xf2, 0x62, 0x92, 0x99, 0x92, 0x87, 0x84, 0xf6, 0x3f, - 0x14, 0x58, 0x4d, 0xb8, 0xcf, 0xa3, 0x49, 0xdb, 0xc7, 0x0c, 0xdd, 0x40, 0x7c, 0x1b, 0x6a, 0x42, - 0x50, 0x3c, 0xb1, 0x85, 0x11, 0x77, 0x52, 0x82, 0xd7, 0xef, 0x59, 0x6a, 0x1d, 0x16, 0xfd, 0xc0, - 0x31, 0x31, 0xb7, 0xa4, 0x62, 0xc4, 0x7f, 0xd4, 0x5d, 0x58, 0x89, 0x13, 0x47, 0xd8, 0xb1, 0x47, - 0x8c, 0x7b, 0x52, 0x31, 0xaa, 0x1c, 0xfb, 0x92, 0x43, 0xdd, 0x76, 0x41, 0xb1, 0x56, 0x52, 0x2c, - 0x24, 0xb4, 0x34, 0x68, 0x14, 0x31, 0xa1, 0xf9, 0xa5, 0xc2, 0x0f, 0xaf, 0x81, 0xc7, 0x38, 0x60, - 0x4f, 0x58, 0x68, 0x7e, 0xf7, 0x3f, 0x9e, 0x54, 0xbd, 0x20, 0x62, 0x5b, 0x12, 0x51, 0xee, 0xa9, - 0xf5, 0x14, 0xb6, 0xa6, 0x12, 0xa9, 0x9c, 0xdc, 0x19, 0x53, 0xf2, 0x67, 0x6c, 0x1b, 0xaa, 0x24, - 0xd9, 0xed, 0x6c, 0x33, 0x20, 0x85, 0x7a, 0xd6, 0xc1, 0xdf, 0x15, 0x58, 0xe8, 0x53, 0x5b, 0x3d, - 0x81, 0x15, 0xe9, 0xdd, 0xb8, 0x55, 0x98, 0x29, 0xf2, 0xdb, 0x48, 0xfb, 0x70, 0x26, 0x2d, 0xba, - 0x7a, 0x06, 0xb7, 0x0b, 0x2f, 0xaa, 0xed, 0x52, 0xa2, 0x1c, 0xa0, 0xdd, 0xbb, 0x26, 0x40, 0xd4, - 0xfe, 0x1a, 0x20, 0x37, 0xa4, 0x37, 0x4b, 0x69, 0x19, 0xa9, 0xbd, 0x3f, 0x83, 0x14, 0xf5, 0xbe, - 0x81, 0x6a, 0x7e, 0x5e, 0xdd, 0x2d, 0xe5, 0xe4, 0x58, 0xed, 0x83, 0x59, 0xac, 0x28, 0x79, 0x02, - 0x2b, 0xd2, 0x3c, 0xd9, 0x9a, 0x9a, 0x95, 0xd2, 0x53, 0x4c, 0x9d, 0x76, 0x5b, 0xd5, 0xa7, 0x70, - 0x4b, 0xbe, 0xa9, 0xcd, 0xa9, 0x79, 0x82, 0xd7, 0x3e, 0x9a, 0xcd, 0x8b, 0xc2, 0xcf, 0x41, 0x9d, - 0x72, 0x1d, 0xca, 0xe6, 0x95, 0x83, 0xb4, 0xbd, 0xff, 0x10, 0x94, 0x3e, 0x47, 0x5b, 0xfc, 0xfe, - 0xea, 0xbc, 0xad, 0x1c, 0x1d, 0xbf, 0xba, 0x68, 0x2a, 0xaf, 0x2f, 0x9a, 0xca, 0x5f, 0x17, 0x4d, - 0xe5, 0xa7, 0xcb, 0xe6, 0xdc, 0xeb, 0xcb, 0xe6, 0xdc, 0x9f, 0x97, 0xcd, 0xb9, 0x67, 0x0f, 0x6d, - 0x87, 0x8d, 0xc2, 0x61, 0xc7, 0x24, 0xae, 0xee, 0x87, 0x74, 0xc4, 0xef, 0x3f, 0x5f, 0xed, 0xf3, - 0xe5, 0xbe, 0x47, 0x2c, 0xac, 0x4f, 0xf4, 0xec, 0xd6, 0xf0, 0x4f, 0xc7, 0xe1, 0x12, 0xff, 0xd6, - 0xfb, 0xec, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x5b, 0x71, 0xf9, 0x2d, 0xa8, 0x0a, 0x00, 0x00, + // 850 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x56, 0xcf, 0x6f, 0xe3, 0x44, + 0x14, 0x8e, 0xb7, 0x49, 0x50, 0x5f, 0xc2, 0x6e, 0x63, 0x12, 0xd6, 0xf1, 0x12, 0xa7, 0x0d, 0xbf, + 0x96, 0x94, 0xc6, 0x6c, 0x90, 0xf6, 0x90, 0xdb, 0x06, 0x90, 0x88, 0x50, 0xb4, 0xc5, 0xdb, 0x65, + 0xa5, 0xbd, 0x44, 0x13, 0x7b, 0xd6, 0xb1, 0xd8, 0x78, 0x2c, 0xcf, 0x38, 0x4a, 0x6f, 0x88, 0x23, + 0x27, 0x4e, 0xfc, 0x0d, 0x48, 0x7b, 0xe9, 0x81, 0x3f, 0x80, 0x63, 0x8f, 0x15, 0x12, 0x12, 0xa7, + 0x0a, 0xb5, 0x87, 0xfe, 0x1b, 0x28, 0xe3, 0x1f, 0xf1, 0xd8, 0xa1, 0x45, 0x3d, 0x70, 0xb1, 0xc6, + 0xdf, 0xf7, 0xde, 0xf3, 0xfb, 0xbe, 0x79, 0x33, 0x32, 0x34, 0x02, 0xbc, 0xc4, 0x66, 0xc0, 0x88, + 0xaf, 0x2f, 0x1e, 0xe9, 0x6c, 0xd9, 0xf3, 0x7c, 0xc2, 0x88, 0x5c, 0x4d, 0xe0, 0xde, 0xe2, 0x91, + 0x5a, 0x43, 0x73, 0xc7, 0x25, 0x3a, 0x7f, 0x86, 0x01, 0xea, 0x7d, 0x93, 0xd0, 0x39, 0xa1, 0xfa, + 0x9c, 0xda, 0xab, 0xc4, 0x39, 0xb5, 0x23, 0x42, 0x11, 0x0b, 0x1e, 0x7b, 0x98, 0x46, 0x4c, 0x4b, + 0x60, 0xcc, 0x19, 0x72, 0xdc, 0xc9, 0x1c, 0x33, 0x14, 0xd1, 0x75, 0x9b, 0xd8, 0x84, 0x2f, 0xf5, + 0xd5, 0x2a, 0x42, 0x9b, 0xe1, 0x77, 0x26, 0x21, 0x11, 0xbe, 0x84, 0x54, 0xe7, 0x8d, 0x04, 0xf7, + 0xc6, 0xd4, 0x7e, 0xee, 0x59, 0x88, 0xe1, 0x43, 0xe4, 0xa3, 0x39, 0x95, 0x1f, 0xc3, 0x36, 0x0a, + 0xd8, 0x8c, 0xf8, 0x0e, 0x3b, 0x56, 0xa4, 0x5d, 0xe9, 0xe1, 0xf6, 0x50, 0xf9, 0xe3, 0xb7, 0x83, + 0x7a, 0x94, 0xf8, 0xc4, 0xb2, 0x7c, 0x4c, 0xe9, 0x33, 0xe6, 0x3b, 0xae, 0x6d, 0xac, 0x43, 0xe5, + 0x3e, 0x94, 0x3d, 0x5e, 0x41, 0xb9, 0xb3, 0x2b, 0x3d, 0xac, 0xf4, 0xeb, 0xbd, 0xb4, 0x01, 0xbd, + 0xb0, 0xfa, 0xb0, 0x78, 0x7a, 0xde, 0x2e, 0x18, 0x51, 0xe4, 0xe0, 0xd3, 0x1f, 0xaf, 0x4e, 0xba, + 0xeb, 0x1a, 0x3f, 0x5d, 0x9d, 0x74, 0x9b, 0x6b, 0x89, 0x99, 0xce, 0x3a, 0x4d, 0xb8, 0x9f, 0x81, + 0x0c, 0x4c, 0x3d, 0xe2, 0x52, 0xdc, 0xf9, 0xfd, 0x0e, 0xd4, 0xc6, 0xd4, 0xfe, 0x8a, 0xa7, 0xe2, + 0x43, 0x74, 0xfc, 0x9a, 0x20, 0x4b, 0xfe, 0x0c, 0xca, 0xd4, 0xb1, 0x5d, 0xec, 0xdf, 0xa8, 0x23, + 0x8a, 0x93, 0x0d, 0xa8, 0x07, 0xae, 0xb3, 0xc0, 0x3e, 0x45, 0xaf, 0x27, 0xc8, 0x34, 0x49, 0xe0, + 0xb2, 0x89, 0x63, 0x45, 0x92, 0x76, 0x45, 0x49, 0xcf, 0xe3, 0xc8, 0x27, 0x61, 0xe0, 0xc8, 0x32, + 0xe4, 0x20, 0x87, 0xc9, 0xdf, 0x40, 0x6d, 0x5d, 0xd3, 0x0b, 0x5b, 0x53, 0xb6, 0x78, 0x41, 0xed, + 0x5f, 0x0a, 0x46, 0x02, 0x8c, 0x9d, 0x20, 0x83, 0xc8, 0xfb, 0x50, 0x5b, 0x60, 0xdf, 0x79, 0xe5, + 0x98, 0x88, 0x39, 0xc4, 0x9d, 0x58, 0x88, 0x21, 0xa5, 0xb8, 0x52, 0x67, 0xec, 0xa4, 0x89, 0x2f, + 0x11, 0x43, 0x83, 0xfd, 0x95, 0xbd, 0x91, 0xb4, 0x95, 0xb7, 0x0f, 0x04, 0x6f, 0x45, 0xb3, 0x3a, + 0x0f, 0xa0, 0x99, 0x03, 0x13, 0x7f, 0x7f, 0x91, 0xe0, 0xee, 0x98, 0xda, 0xdf, 0x11, 0x86, 0x47, + 0xee, 0x94, 0x04, 0xee, 0x6d, 0xcc, 0xd5, 0xe1, 0x2d, 0x27, 0x4c, 0x8e, 0xfc, 0x6c, 0x88, 0xf2, + 0xa3, 0xca, 0x46, 0x1c, 0x35, 0xd8, 0xcb, 0xf4, 0x5f, 0x0b, 0xb0, 0x2e, 0x76, 0xd1, 0x51, 0xe0, + 0x5d, 0x11, 0x49, 0x5a, 0x3e, 0x0f, 0x67, 0x7b, 0x45, 0x3d, 0x0d, 0xd8, 0x6d, 0x7b, 0x7e, 0x07, + 0x4a, 0x6c, 0x19, 0x4f, 0xc0, 0xb6, 0x51, 0x64, 0xcb, 0x91, 0x25, 0x37, 0xa0, 0x1c, 0x84, 0xe8, + 0x16, 0x47, 0x4b, 0x01, 0x87, 0x87, 0x50, 0x21, 0x53, 0x8a, 0xfd, 0x05, 0xb6, 0x26, 0x6c, 0xc9, + 0x77, 0xa5, 0xd2, 0xdf, 0x13, 0x35, 0xc6, 0xad, 0x3c, 0xe5, 0x81, 0x7c, 0xab, 0x0c, 0x88, 0xb3, + 0x8e, 0x96, 0x83, 0x4f, 0x32, 0x92, 0xc5, 0xe3, 0x90, 0x16, 0x13, 0x1d, 0x87, 0x34, 0x94, 0x68, + 0xff, 0x53, 0x82, 0x9d, 0x88, 0xfb, 0x62, 0x75, 0x49, 0x8c, 0x31, 0x43, 0xb7, 0x10, 0xdf, 0x85, + 0x5a, 0x22, 0x28, 0xbc, 0x6c, 0x12, 0x23, 0xee, 0xc5, 0x04, 0xaf, 0x3f, 0xb2, 0xe4, 0x3a, 0x94, + 0x3c, 0xdf, 0x31, 0x31, 0xb7, 0xa4, 0x68, 0x84, 0x2f, 0xf2, 0x1e, 0x54, 0xc3, 0xc4, 0x19, 0x76, + 0xec, 0x19, 0xe3, 0x9e, 0x14, 0x8d, 0x0a, 0xc7, 0xbe, 0xe6, 0xd0, 0xa0, 0x9b, 0x51, 0xac, 0xe6, + 0x14, 0x27, 0x12, 0x3a, 0x2a, 0x28, 0x59, 0x2c, 0xd1, 0xfc, 0x46, 0x82, 0xc6, 0x98, 0xda, 0x06, + 0x5e, 0x60, 0x9f, 0x3d, 0x63, 0x81, 0xf9, 0xfd, 0xff, 0x38, 0xa9, 0x7a, 0x46, 0x44, 0x5b, 0x10, + 0x91, 0xef, 0xa9, 0xf3, 0x02, 0x5a, 0x1b, 0x89, 0x58, 0x4e, 0x6a, 0xc6, 0xa4, 0xf4, 0x8c, 0xb5, + 0xa1, 0x42, 0xa2, 0xdd, 0x5e, 0x6f, 0x06, 0xc4, 0xd0, 0xc8, 0xea, 0xff, 0x5a, 0x84, 0xad, 0x31, + 0xb5, 0xe5, 0x23, 0xa8, 0x0a, 0xd7, 0x7a, 0x4b, 0x54, 0x90, 0xb9, 0x48, 0xd5, 0x0f, 0xaf, 0xa5, + 0x93, 0xae, 0x5e, 0xc2, 0xdd, 0xcc, 0x1d, 0xdb, 0xce, 0x25, 0x8a, 0x01, 0xea, 0xc7, 0x37, 0x04, + 0x24, 0xb5, 0xbf, 0x85, 0x4a, 0xfa, 0x7e, 0x79, 0x2f, 0x97, 0x97, 0x62, 0xd5, 0x0f, 0xae, 0x63, + 0x93, 0x92, 0x47, 0x50, 0x15, 0xce, 0x7f, 0x6b, 0x63, 0x56, 0x4c, 0x6f, 0x30, 0x61, 0xd3, 0xe9, + 0x92, 0x5f, 0xc0, 0xdb, 0xe2, 0xc9, 0xd2, 0x36, 0xe6, 0x25, 0xbc, 0xfa, 0xd1, 0xf5, 0x7c, 0x52, + 0xf8, 0x15, 0xc8, 0x1b, 0xc6, 0xf7, 0xfd, 0x5c, 0x76, 0x3e, 0x48, 0xdd, 0xff, 0x0f, 0x41, 0xf1, + 0x77, 0xd4, 0xd2, 0x0f, 0x57, 0x27, 0x5d, 0x69, 0x78, 0x78, 0x7a, 0xa1, 0x49, 0x67, 0x17, 0x9a, + 0xf4, 0xf7, 0x85, 0x26, 0xfd, 0x7c, 0xa9, 0x15, 0xce, 0x2e, 0xb5, 0xc2, 0x5f, 0x97, 0x5a, 0xe1, + 0xe5, 0x63, 0xdb, 0x61, 0xb3, 0x60, 0xda, 0x33, 0xc9, 0x5c, 0xf7, 0x02, 0x3a, 0xe3, 0xe7, 0x95, + 0xaf, 0x0e, 0xf8, 0xf2, 0xc0, 0x25, 0x16, 0xd6, 0x97, 0xfa, 0x7a, 0xca, 0xf9, 0x5f, 0xca, 0xb4, + 0xcc, 0x7f, 0x2b, 0x3e, 0xff, 0x27, 0x00, 0x00, 0xff, 0xff, 0x31, 0xef, 0x9f, 0x75, 0x13, 0x09, + 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -864,8 +748,6 @@ type MsgClient interface { UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) // ExecutePayload defines a message for executing a universal payload ExecutePayload(ctx context.Context, in *MsgExecutePayload, opts ...grpc.CallOption) (*MsgExecutePayloadResponse, error) - // MigrateUEA defines a message for migrating UEA - MigrateUEA(ctx context.Context, in *MsgMigrateUEA, opts ...grpc.CallOption) (*MsgMigrateUEAResponse, error) // VoteInbound defines a message for voting on synthetic assets bridging from external chain to PC VoteInbound(ctx context.Context, in *MsgVoteInbound, opts ...grpc.CallOption) (*MsgVoteInboundResponse, error) // VoteOutbound defines a message for voting on a observed outbound tx on external chain @@ -904,15 +786,6 @@ func (c *msgClient) ExecutePayload(ctx context.Context, in *MsgExecutePayload, o return out, nil } -func (c *msgClient) MigrateUEA(ctx context.Context, in *MsgMigrateUEA, opts ...grpc.CallOption) (*MsgMigrateUEAResponse, error) { - out := new(MsgMigrateUEAResponse) - err := c.cc.Invoke(ctx, "/uexecutor.v1.Msg/MigrateUEA", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - func (c *msgClient) VoteInbound(ctx context.Context, in *MsgVoteInbound, opts ...grpc.CallOption) (*MsgVoteInboundResponse, error) { out := new(MsgVoteInboundResponse) err := c.cc.Invoke(ctx, "/uexecutor.v1.Msg/VoteInbound", in, out, opts...) @@ -957,8 +830,6 @@ type MsgServer interface { UpdateParams(context.Context, *MsgUpdateParams) (*MsgUpdateParamsResponse, error) // ExecutePayload defines a message for executing a universal payload ExecutePayload(context.Context, *MsgExecutePayload) (*MsgExecutePayloadResponse, error) - // MigrateUEA defines a message for migrating UEA - MigrateUEA(context.Context, *MsgMigrateUEA) (*MsgMigrateUEAResponse, error) // VoteInbound defines a message for voting on synthetic assets bridging from external chain to PC VoteInbound(context.Context, *MsgVoteInbound) (*MsgVoteInboundResponse, error) // VoteOutbound defines a message for voting on a observed outbound tx on external chain @@ -981,9 +852,6 @@ func (*UnimplementedMsgServer) UpdateParams(ctx context.Context, req *MsgUpdateP func (*UnimplementedMsgServer) ExecutePayload(ctx context.Context, req *MsgExecutePayload) (*MsgExecutePayloadResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ExecutePayload not implemented") } -func (*UnimplementedMsgServer) MigrateUEA(ctx context.Context, req *MsgMigrateUEA) (*MsgMigrateUEAResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method MigrateUEA not implemented") -} func (*UnimplementedMsgServer) VoteInbound(ctx context.Context, req *MsgVoteInbound) (*MsgVoteInboundResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method VoteInbound not implemented") } @@ -1037,24 +905,6 @@ func _Msg_ExecutePayload_Handler(srv interface{}, ctx context.Context, dec func( return interceptor(ctx, in, info, handler) } -func _Msg_MigrateUEA_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MsgMigrateUEA) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(MsgServer).MigrateUEA(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/uexecutor.v1.Msg/MigrateUEA", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MsgServer).MigrateUEA(ctx, req.(*MsgMigrateUEA)) - } - return interceptor(ctx, in, info, handler) -} - func _Msg_VoteInbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(MsgVoteInbound) if err := dec(in); err != nil { @@ -1139,10 +989,6 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "ExecutePayload", Handler: _Msg_ExecutePayload_Handler, }, - { - MethodName: "MigrateUEA", - Handler: _Msg_MigrateUEA_Handler, - }, { MethodName: "VoteInbound", Handler: _Msg_VoteInbound_Handler, @@ -1311,90 +1157,6 @@ func (m *MsgExecutePayloadResponse) MarshalToSizedBuffer(dAtA []byte) (int, erro return len(dAtA) - i, nil } -func (m *MsgMigrateUEA) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MsgMigrateUEA) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MsgMigrateUEA) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.Signature) > 0 { - i -= len(m.Signature) - copy(dAtA[i:], m.Signature) - i = encodeVarintTx(dAtA, i, uint64(len(m.Signature))) - i-- - dAtA[i] = 0x22 - } - if m.MigrationPayload != nil { - { - size, err := m.MigrationPayload.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintTx(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - } - if m.UniversalAccountId != nil { - { - size, err := m.UniversalAccountId.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintTx(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - if len(m.Signer) > 0 { - i -= len(m.Signer) - copy(dAtA[i:], m.Signer) - i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *MsgMigrateUEAResponse) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MsgMigrateUEAResponse) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MsgMigrateUEAResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - return len(dAtA) - i, nil -} - func (m *MsgVoteInbound) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -1757,40 +1519,6 @@ func (m *MsgExecutePayloadResponse) Size() (n int) { return n } -func (m *MsgMigrateUEA) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Signer) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) - } - if m.UniversalAccountId != nil { - l = m.UniversalAccountId.Size() - n += 1 + l + sovTx(uint64(l)) - } - if m.MigrationPayload != nil { - l = m.MigrationPayload.Size() - n += 1 + l + sovTx(uint64(l)) - } - l = len(m.Signature) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) - } - return n -} - -func (m *MsgMigrateUEAResponse) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - return n -} - func (m *MsgVoteInbound) Size() (n int) { if m == nil { return 0 @@ -2324,242 +2052,6 @@ func (m *MsgExecutePayloadResponse) Unmarshal(dAtA []byte) error { } return nil } -func (m *MsgMigrateUEA) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MsgMigrateUEA: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MsgMigrateUEA: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Signer = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field UniversalAccountId", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.UniversalAccountId == nil { - m.UniversalAccountId = &UniversalAccountId{} - } - if err := m.UniversalAccountId.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field MigrationPayload", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.MigrationPayload == nil { - m.MigrationPayload = &MigrationPayload{} - } - if err := m.MigrationPayload.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Signature", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Signature = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipTx(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTx - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *MsgMigrateUEAResponse) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MsgMigrateUEAResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MsgMigrateUEAResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipTx(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTx - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func (m *MsgVoteInbound) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/x/uexecutor/types/types.pb.go b/x/uexecutor/types/types.pb.go index 70303acee..ce5c81fe6 100644 --- a/x/uexecutor/types/types.pb.go +++ b/x/uexecutor/types/types.pb.go @@ -376,66 +376,6 @@ func (m *UniversalPayload) GetVType() VerificationType { return VerificationType_signedVerification } -// MigrationPayload mirrors the Solidity struct -type MigrationPayload struct { - Migration string `protobuf:"bytes,1,opt,name=migration,proto3" json:"migration,omitempty"` - Nonce string `protobuf:"bytes,2,opt,name=nonce,proto3" json:"nonce,omitempty"` - Deadline string `protobuf:"bytes,3,opt,name=deadline,proto3" json:"deadline,omitempty"` -} - -func (m *MigrationPayload) Reset() { *m = MigrationPayload{} } -func (*MigrationPayload) ProtoMessage() {} -func (*MigrationPayload) Descriptor() ([]byte, []int) { - return fileDescriptor_fab6d3ca71d1e2a5, []int{2} -} -func (m *MigrationPayload) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *MigrationPayload) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_MigrationPayload.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *MigrationPayload) XXX_Merge(src proto.Message) { - xxx_messageInfo_MigrationPayload.Merge(m, src) -} -func (m *MigrationPayload) XXX_Size() int { - return m.Size() -} -func (m *MigrationPayload) XXX_DiscardUnknown() { - xxx_messageInfo_MigrationPayload.DiscardUnknown(m) -} - -var xxx_messageInfo_MigrationPayload proto.InternalMessageInfo - -func (m *MigrationPayload) GetMigration() string { - if m != nil { - return m.Migration - } - return "" -} - -func (m *MigrationPayload) GetNonce() string { - if m != nil { - return m.Nonce - } - return "" -} - -func (m *MigrationPayload) GetDeadline() string { - if m != nil { - return m.Deadline - } - return "" -} - // UniversalAccountId is the identifier of a owner account type UniversalAccountId struct { ChainNamespace string `protobuf:"bytes,1,opt,name=chain_namespace,json=chainNamespace,proto3" json:"chain_namespace,omitempty"` @@ -446,7 +386,7 @@ type UniversalAccountId struct { func (m *UniversalAccountId) Reset() { *m = UniversalAccountId{} } func (*UniversalAccountId) ProtoMessage() {} func (*UniversalAccountId) Descriptor() ([]byte, []int) { - return fileDescriptor_fab6d3ca71d1e2a5, []int{3} + return fileDescriptor_fab6d3ca71d1e2a5, []int{2} } func (m *UniversalAccountId) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -504,7 +444,7 @@ func (m *RevertInstructions) Reset() { *m = RevertInstructions{} } func (m *RevertInstructions) String() string { return proto.CompactTextString(m) } func (*RevertInstructions) ProtoMessage() {} func (*RevertInstructions) Descriptor() ([]byte, []int) { - return fileDescriptor_fab6d3ca71d1e2a5, []int{4} + return fileDescriptor_fab6d3ca71d1e2a5, []int{3} } func (m *RevertInstructions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -559,7 +499,7 @@ type Inbound struct { func (m *Inbound) Reset() { *m = Inbound{} } func (*Inbound) ProtoMessage() {} func (*Inbound) Descriptor() ([]byte, []int) { - return fileDescriptor_fab6d3ca71d1e2a5, []int{5} + return fileDescriptor_fab6d3ca71d1e2a5, []int{4} } func (m *Inbound) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -691,7 +631,7 @@ type PCTx struct { func (m *PCTx) Reset() { *m = PCTx{} } func (*PCTx) ProtoMessage() {} func (*PCTx) Descriptor() ([]byte, []int) { - return fileDescriptor_fab6d3ca71d1e2a5, []int{6} + return fileDescriptor_fab6d3ca71d1e2a5, []int{5} } func (m *PCTx) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -774,7 +714,7 @@ func (m *OutboundObservation) Reset() { *m = OutboundObservation{} } func (m *OutboundObservation) String() string { return proto.CompactTextString(m) } func (*OutboundObservation) ProtoMessage() {} func (*OutboundObservation) Descriptor() ([]byte, []int) { - return fileDescriptor_fab6d3ca71d1e2a5, []int{7} + return fileDescriptor_fab6d3ca71d1e2a5, []int{6} } func (m *OutboundObservation) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -847,7 +787,7 @@ func (m *OriginatingPcTx) Reset() { *m = OriginatingPcTx{} } func (m *OriginatingPcTx) String() string { return proto.CompactTextString(m) } func (*OriginatingPcTx) ProtoMessage() {} func (*OriginatingPcTx) Descriptor() ([]byte, []int) { - return fileDescriptor_fab6d3ca71d1e2a5, []int{8} + return fileDescriptor_fab6d3ca71d1e2a5, []int{7} } func (m *OriginatingPcTx) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -917,7 +857,7 @@ type OutboundTx struct { func (m *OutboundTx) Reset() { *m = OutboundTx{} } func (*OutboundTx) ProtoMessage() {} func (*OutboundTx) Descriptor() ([]byte, []int) { - return fileDescriptor_fab6d3ca71d1e2a5, []int{9} + return fileDescriptor_fab6d3ca71d1e2a5, []int{8} } func (m *OutboundTx) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1104,7 +1044,7 @@ type UniversalTx struct { func (m *UniversalTx) Reset() { *m = UniversalTx{} } func (*UniversalTx) ProtoMessage() {} func (*UniversalTx) Descriptor() ([]byte, []int) { - return fileDescriptor_fab6d3ca71d1e2a5, []int{10} + return fileDescriptor_fab6d3ca71d1e2a5, []int{9} } func (m *UniversalTx) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1185,7 +1125,7 @@ func (m *InboundLegacy) Reset() { *m = InboundLegacy{} } func (m *InboundLegacy) String() string { return proto.CompactTextString(m) } func (*InboundLegacy) ProtoMessage() {} func (*InboundLegacy) Descriptor() ([]byte, []int) { - return fileDescriptor_fab6d3ca71d1e2a5, []int{11} + return fileDescriptor_fab6d3ca71d1e2a5, []int{10} } func (m *InboundLegacy) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1296,7 +1236,7 @@ func (m *OutboundTxLegacy) Reset() { *m = OutboundTxLegacy{} } func (m *OutboundTxLegacy) String() string { return proto.CompactTextString(m) } func (*OutboundTxLegacy) ProtoMessage() {} func (*OutboundTxLegacy) Descriptor() ([]byte, []int) { - return fileDescriptor_fab6d3ca71d1e2a5, []int{12} + return fileDescriptor_fab6d3ca71d1e2a5, []int{11} } func (m *OutboundTxLegacy) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1371,7 +1311,7 @@ func (m *UniversalTxLegacy) Reset() { *m = UniversalTxLegacy{} } func (m *UniversalTxLegacy) String() string { return proto.CompactTextString(m) } func (*UniversalTxLegacy) ProtoMessage() {} func (*UniversalTxLegacy) Descriptor() ([]byte, []int) { - return fileDescriptor_fab6d3ca71d1e2a5, []int{13} + return fileDescriptor_fab6d3ca71d1e2a5, []int{12} } func (m *UniversalTxLegacy) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1436,7 +1376,6 @@ func init() { proto.RegisterEnum("uexecutor.v1.InboundTxTypeLegacy", InboundTxTypeLegacy_name, InboundTxTypeLegacy_value) proto.RegisterType((*Params)(nil), "uexecutor.v1.Params") proto.RegisterType((*UniversalPayload)(nil), "uexecutor.v1.UniversalPayload") - proto.RegisterType((*MigrationPayload)(nil), "uexecutor.v1.MigrationPayload") proto.RegisterType((*UniversalAccountId)(nil), "uexecutor.v1.UniversalAccountId") proto.RegisterType((*RevertInstructions)(nil), "uexecutor.v1.RevertInstructions") proto.RegisterType((*Inbound)(nil), "uexecutor.v1.Inbound") @@ -1453,126 +1392,124 @@ func init() { func init() { proto.RegisterFile("uexecutor/v1/types.proto", fileDescriptor_fab6d3ca71d1e2a5) } var fileDescriptor_fab6d3ca71d1e2a5 = []byte{ - // 1897 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x58, 0xcd, 0x6f, 0xe3, 0xc6, - 0x15, 0x37, 0xf5, 0xad, 0x27, 0xaf, 0x45, 0x8d, 0xed, 0x0d, 0x77, 0x37, 0x96, 0x6d, 0x25, 0xe9, - 0x1a, 0x0e, 0xd6, 0x6e, 0xb6, 0x6d, 0x80, 0x0a, 0x28, 0x0a, 0x59, 0xa6, 0x1d, 0xb5, 0x8e, 0xa4, - 0x52, 0x92, 0xb1, 0xe9, 0x85, 0x18, 0x93, 0xb3, 0x32, 0x11, 0x8b, 0x14, 0x38, 0x94, 0x4d, 0x9f, - 0x7b, 0x2b, 0x02, 0xb4, 0x97, 0x02, 0x7b, 0xdc, 0x73, 0x7b, 0xe9, 0xa1, 0x7f, 0x44, 0x8e, 0x29, - 0x7a, 0x29, 0xd0, 0x4b, 0xb1, 0x7b, 0x68, 0xff, 0x8b, 0x16, 0xf3, 0x41, 0x91, 0x94, 0xe5, 0x4d, - 0x8a, 0x1e, 0x0a, 0xf4, 0xb2, 0x9e, 0xf7, 0xe6, 0xcd, 0x9b, 0xdf, 0xfb, 0xfa, 0x0d, 0x57, 0xa0, - 0xcd, 0x48, 0x48, 0xac, 0x59, 0xe0, 0xf9, 0x87, 0xd7, 0x9f, 0x1c, 0x06, 0xb7, 0x53, 0x42, 0x0f, - 0xa6, 0xbe, 0x17, 0x78, 0x68, 0x75, 0xbe, 0x73, 0x70, 0xfd, 0xc9, 0xe3, 0x8d, 0xb1, 0x37, 0xf6, - 0xf8, 0xc6, 0x21, 0x5b, 0x09, 0x9b, 0xc7, 0x35, 0x3c, 0x71, 0x5c, 0xef, 0x90, 0xff, 0x2b, 0x54, - 0x0d, 0x0a, 0x85, 0x3e, 0xf6, 0xf1, 0x84, 0xa2, 0x2d, 0x00, 0xea, 0x4d, 0x88, 0x79, 0x8d, 0xaf, - 0x66, 0x44, 0xcb, 0xec, 0x28, 0x7b, 0x25, 0xa3, 0xcc, 0x34, 0xe7, 0x4c, 0x81, 0x3e, 0x06, 0x34, - 0xc1, 0xa1, 0x39, 0xc6, 0xf4, 0x8a, 0x50, 0x6a, 0x06, 0x7c, 0xa9, 0x65, 0x77, 0x94, 0xbd, 0x9c, - 0x51, 0x9d, 0xe0, 0xf0, 0x54, 0x6c, 0x0c, 0xd9, 0xa2, 0xb9, 0xf5, 0xea, 0xf5, 0xf6, 0xca, 0x3f, - 0x5f, 0x6f, 0x2b, 0xbf, 0xfe, 0xc7, 0x1f, 0xf7, 0xd5, 0x18, 0xf3, 0x94, 0x5f, 0xd5, 0xf8, 0x5b, - 0x06, 0xd4, 0x91, 0xeb, 0x5c, 0x13, 0x9f, 0xe2, 0xab, 0x3e, 0xbe, 0xbd, 0xf2, 0xb0, 0x8d, 0xd6, - 0x20, 0x13, 0x78, 0x9a, 0xb2, 0xa3, 0xec, 0x95, 0x8d, 0x4c, 0xe0, 0xa1, 0x0d, 0xc8, 0xc7, 0x50, - 0xca, 0x86, 0x10, 0x10, 0x82, 0x9c, 0x8d, 0x03, 0xcc, 0x2f, 0x2e, 0x1b, 0x7c, 0x8d, 0x9e, 0x40, - 0x79, 0x8c, 0xa9, 0x79, 0xe5, 0x4c, 0x9c, 0x40, 0xcb, 0xf1, 0x8d, 0xd2, 0x18, 0xd3, 0x33, 0x26, - 0xa3, 0x8f, 0x80, 0xa1, 0x33, 0x5f, 0x12, 0x62, 0x4e, 0x89, 0xcf, 0x41, 0xe7, 0xb9, 0xc9, 0xea, - 0x04, 0x87, 0x27, 0x84, 0xf4, 0x89, 0x7f, 0x8a, 0x29, 0xfa, 0x14, 0x34, 0x66, 0x36, 0xf5, 0x1d, - 0xcf, 0x77, 0x82, 0xdb, 0x94, 0x7d, 0x81, 0xdb, 0x6f, 0x4c, 0x70, 0xd8, 0x97, 0xdb, 0xf1, 0xb9, - 0x0d, 0xc8, 0xbb, 0x9e, 0x6b, 0x11, 0xad, 0x28, 0x50, 0x72, 0x01, 0x3d, 0x86, 0x92, 0x4d, 0xb0, - 0x7d, 0xe5, 0xb8, 0x44, 0x2b, 0x09, 0x40, 0x91, 0x8c, 0x7e, 0x04, 0x85, 0x6b, 0x93, 0x55, 0x4e, - 0x2b, 0xef, 0x28, 0x7b, 0x6b, 0xcf, 0xeb, 0x07, 0xc9, 0xca, 0x1d, 0x9c, 0x13, 0xdf, 0x79, 0xe9, - 0x58, 0x38, 0x70, 0x3c, 0x77, 0x78, 0x3b, 0x25, 0x46, 0xfe, 0x9a, 0xfd, 0x69, 0xee, 0x25, 0x53, - 0xfa, 0x24, 0x4e, 0xe9, 0x2c, 0xca, 0xa3, 0x39, 0x15, 0x89, 0x6c, 0x7c, 0xa5, 0x80, 0xfa, 0xb9, - 0x33, 0xf6, 0xb9, 0x8b, 0x28, 0xbb, 0xef, 0x43, 0x79, 0x12, 0xe9, 0x64, 0x92, 0x63, 0x45, 0x1c, - 0x45, 0xe6, 0xbe, 0x28, 0xb2, 0xe9, 0x28, 0xee, 0x85, 0x33, 0xf7, 0x39, 0x87, 0xf3, 0x4a, 0x01, - 0x34, 0x2f, 0x76, 0xcb, 0xb2, 0xbc, 0x99, 0x1b, 0x74, 0x6c, 0xf4, 0x14, 0xaa, 0xd6, 0x25, 0x76, - 0x5c, 0xd3, 0xc5, 0x13, 0x42, 0xa7, 0xd8, 0x22, 0x12, 0xd6, 0x1a, 0x57, 0x77, 0x23, 0x2d, 0x7a, - 0x04, 0x25, 0x61, 0xe8, 0xd8, 0x12, 0x5e, 0x91, 0xcb, 0x1d, 0x9b, 0xc1, 0xf6, 0x6e, 0x5c, 0xe2, - 0x4b, 0x74, 0x42, 0xf8, 0x0e, 0x99, 0xc2, 0x02, 0x45, 0xc3, 0x02, 0x64, 0x90, 0x6b, 0xe2, 0x07, - 0x1d, 0x97, 0x06, 0xfe, 0xcc, 0x62, 0xb8, 0x29, 0xfa, 0x08, 0xd6, 0x5e, 0xce, 0x5c, 0xdb, 0xf4, - 0x89, 0xe5, 0x4c, 0x1d, 0xe2, 0x06, 0x12, 0xd8, 0x03, 0xa6, 0x35, 0x22, 0x65, 0xf3, 0x7b, 0xd1, - 0x15, 0x5b, 0xf1, 0x15, 0x3e, 0xf7, 0x66, 0x3a, 0x09, 0x77, 0x8d, 0xaf, 0x72, 0x50, 0xec, 0xb8, - 0x17, 0xde, 0xcc, 0xb5, 0xd1, 0x2e, 0xac, 0x52, 0x6f, 0xe6, 0x5b, 0xc4, 0xe4, 0x21, 0x48, 0xc7, - 0x15, 0xa1, 0x6b, 0x33, 0x15, 0x7a, 0x0f, 0x8a, 0x41, 0x68, 0x5e, 0x62, 0x7a, 0x29, 0xa3, 0x2d, - 0x04, 0xe1, 0x67, 0x98, 0x5e, 0xa2, 0x87, 0x50, 0xa0, 0xc4, 0xb5, 0xe7, 0xd1, 0x4a, 0x89, 0x55, - 0x36, 0x46, 0x2a, 0xba, 0x3f, 0x56, 0xb0, 0x53, 0x78, 0xc2, 0x82, 0x95, 0x5d, 0x2f, 0x25, 0x36, - 0xed, 0x98, 0x52, 0x12, 0x98, 0xd8, 0xb6, 0x7d, 0xd9, 0xe1, 0x65, 0xae, 0x69, 0xd9, 0xb6, 0xcf, - 0x46, 0xea, 0xca, 0x1b, 0x9b, 0x8e, 0x6b, 0x93, 0x50, 0xb6, 0x76, 0xe9, 0xca, 0x1b, 0x77, 0x98, - 0x8c, 0x9e, 0x71, 0x88, 0xbc, 0x85, 0x4b, 0xbc, 0x85, 0x37, 0xd2, 0x2d, 0x3c, 0x0c, 0x79, 0xe3, - 0x16, 0x02, 0xfe, 0x17, 0xfd, 0x1c, 0x6a, 0x77, 0x9a, 0x94, 0xf7, 0x7e, 0x65, 0xb1, 0xf7, 0x17, - 0x39, 0xc1, 0x50, 0x67, 0x8b, 0x2c, 0xf1, 0x31, 0xd4, 0xae, 0x13, 0x13, 0x62, 0x72, 0x32, 0x00, - 0x0e, 0x50, 0x4d, 0x6e, 0x1c, 0x33, 0x62, 0xf8, 0x05, 0xac, 0x2f, 0xa9, 0x88, 0x56, 0xe1, 0x77, - 0xef, 0xa4, 0xef, 0xbe, 0xdb, 0x08, 0x06, 0xf2, 0xef, 0x36, 0xc7, 0x06, 0xe4, 0x1d, 0xda, 0xd6, - 0x5b, 0xda, 0x2a, 0x27, 0x48, 0x21, 0xa0, 0x6d, 0xa8, 0xf8, 0xf8, 0x66, 0x1e, 0xdc, 0x03, 0x8e, - 0x07, 0x7c, 0x7c, 0x23, 0x61, 0x37, 0x4b, 0x51, 0x4f, 0x36, 0xbe, 0x56, 0x20, 0xd7, 0x6f, 0x0f, - 0xc3, 0x64, 0xa1, 0x95, 0x7b, 0x0a, 0x9d, 0x49, 0x15, 0xfa, 0x11, 0x30, 0x56, 0x33, 0x67, 0x94, - 0xd8, 0x92, 0x77, 0x8b, 0x63, 0x4c, 0x47, 0x94, 0xf0, 0xbe, 0xba, 0xb8, 0xf2, 0xac, 0x2f, 0xcd, - 0x4b, 0xe2, 0x8c, 0x2f, 0x45, 0x1b, 0xe4, 0x8c, 0x0a, 0xd7, 0x7d, 0xc6, 0x55, 0xdc, 0x6b, 0x80, - 0x83, 0x59, 0x44, 0x67, 0x52, 0x62, 0x95, 0x26, 0xbe, 0xef, 0xf9, 0xe6, 0x84, 0x8e, 0xa3, 0x4a, - 0x73, 0xc5, 0xe7, 0x74, 0xdc, 0x7c, 0x3f, 0x39, 0x4a, 0xd5, 0x04, 0x8f, 0x5b, 0x66, 0x10, 0x36, - 0xfe, 0xa2, 0xc0, 0x7a, 0x6f, 0x16, 0xf0, 0xd6, 0xee, 0x5d, 0x50, 0xe2, 0x5f, 0x0b, 0x36, 0xd1, - 0xa0, 0x48, 0x67, 0x96, 0x45, 0x28, 0xe5, 0x91, 0x95, 0x8c, 0x48, 0xbc, 0x83, 0x33, 0x73, 0x17, - 0x67, 0x22, 0x2d, 0xd9, 0x54, 0x5a, 0x52, 0x40, 0x73, 0x69, 0xa0, 0x68, 0x07, 0x56, 0x59, 0x6e, - 0x18, 0x6b, 0xf3, 0xfc, 0x88, 0x66, 0x87, 0x31, 0xa6, 0x27, 0x84, 0xb0, 0x14, 0x35, 0x9f, 0x46, - 0x61, 0xd4, 0xe3, 0x30, 0x3c, 0x09, 0xde, 0xf4, 0x62, 0xf4, 0x8d, 0x09, 0x54, 0x7b, 0xbe, 0x33, - 0x76, 0x5c, 0x1c, 0x38, 0xee, 0xb8, 0x6f, 0xbd, 0xab, 0x54, 0xa9, 0x31, 0xc9, 0xa4, 0xc7, 0xa4, - 0xf9, 0xe1, 0x12, 0x0e, 0xf2, 0x62, 0xcf, 0xa6, 0x48, 0xe2, 0xef, 0x8a, 0x00, 0x51, 0x12, 0x87, - 0x21, 0xeb, 0x6f, 0x9b, 0xd0, 0x80, 0xdb, 0x78, 0x6e, 0x8a, 0x26, 0xd4, 0xc4, 0x86, 0xe0, 0x8a, - 0xd4, 0xe8, 0x67, 0xee, 0x1f, 0xfd, 0x6c, 0x6a, 0xf4, 0x0f, 0x60, 0x9d, 0x84, 0x01, 0xf1, 0x5d, - 0xc6, 0x84, 0x31, 0x07, 0x88, 0x94, 0xd6, 0xa2, 0xad, 0xd6, 0x9c, 0x0b, 0xf6, 0x40, 0x9d, 0xfa, - 0xd6, 0xf3, 0xef, 0x27, 0x8d, 0x45, 0x7e, 0xd7, 0xb8, 0x3e, 0xb6, 0x8c, 0x3b, 0xb7, 0x90, 0xea, - 0x5c, 0x0d, 0x8a, 0xd1, 0x68, 0x88, 0x0e, 0x8b, 0xc4, 0xf4, 0xd3, 0x5d, 0x5a, 0x78, 0xba, 0x13, - 0x3c, 0x53, 0xfe, 0x0e, 0x3c, 0xf3, 0x1c, 0xf2, 0x3c, 0xa5, 0x9c, 0x0e, 0x2a, 0xcf, 0xb7, 0xd2, - 0xc6, 0x0b, 0x35, 0x35, 0x72, 0x53, 0x56, 0xd9, 0x23, 0xa8, 0x88, 0xda, 0x13, 0x9b, 0x9d, 0x14, - 0xcc, 0xb0, 0xbb, 0x70, 0xf2, 0x6e, 0x8b, 0x1b, 0x10, 0x9d, 0x1a, 0x86, 0xec, 0xc3, 0xc5, 0xb1, - 0x39, 0x1f, 0x94, 0x8d, 0x8c, 0x63, 0xa3, 0x9f, 0x40, 0x75, 0xde, 0x58, 0x72, 0xe4, 0x1e, 0x2c, - 0x83, 0x3f, 0xe0, 0x7b, 0xc6, 0x5a, 0x64, 0x2c, 0xe4, 0xfb, 0x48, 0x6b, 0xed, 0xbf, 0x20, 0xad, - 0x23, 0x58, 0x9f, 0x5a, 0xa6, 0xf4, 0x2a, 0xce, 0xb3, 0xcf, 0x80, 0x2a, 0x77, 0x89, 0xd2, 0x2e, - 0x19, 0x37, 0x19, 0xb5, 0xa9, 0x25, 0x5c, 0xeb, 0x91, 0x71, 0x54, 0xa9, 0xa9, 0xef, 0x58, 0x44, - 0x53, 0xe7, 0x95, 0xea, 0x33, 0x99, 0x0d, 0x88, 0x1c, 0x3f, 0xad, 0x26, 0x2a, 0x2f, 0x26, 0x6f, - 0x7e, 0x33, 0x7f, 0x50, 0xe3, 0x9b, 0xd1, 0xbb, 0x6f, 0x66, 0xd6, 0xf1, 0xcd, 0xfb, 0x50, 0x93, - 0x0e, 0xe8, 0x0d, 0x9e, 0x9a, 0x7c, 0xe6, 0xb5, 0x75, 0x7e, 0x4d, 0x55, 0x6c, 0x0c, 0x6e, 0xf0, - 0x54, 0x67, 0xea, 0x08, 0x65, 0xe0, 0x7d, 0x49, 0x5c, 0x6d, 0x63, 0x8e, 0x72, 0xc8, 0x64, 0xc6, - 0x3e, 0xf8, 0xc2, 0xf3, 0x03, 0xd3, 0x27, 0x98, 0x7a, 0xae, 0xb6, 0x29, 0x5e, 0x5f, 0xae, 0x33, - 0xb8, 0x2a, 0xc1, 0xd3, 0xff, 0x52, 0xa0, 0x32, 0x7f, 0x8f, 0xe6, 0x55, 0x56, 0xe6, 0x55, 0xfe, - 0x21, 0x80, 0x23, 0x5e, 0x75, 0xd6, 0x38, 0x19, 0x1e, 0xd0, 0x66, 0x3a, 0x20, 0xf9, 0xea, 0x1b, - 0x65, 0x69, 0x38, 0x0c, 0xd1, 0xd3, 0xa8, 0x47, 0xb3, 0x3b, 0xd9, 0x7b, 0x32, 0x20, 0x1a, 0xf3, - 0xc7, 0x50, 0x99, 0x37, 0x51, 0x10, 0x6a, 0x39, 0x6e, 0xae, 0x2d, 0x6f, 0xcc, 0x61, 0x68, 0x80, - 0x17, 0x53, 0xc8, 0x2e, 0xac, 0x46, 0xa5, 0xe6, 0xa9, 0x12, 0xb3, 0x58, 0x11, 0x3a, 0x9e, 0xa6, - 0xe6, 0x07, 0x49, 0x5e, 0x7f, 0xb8, 0xec, 0x13, 0x29, 0x08, 0x1b, 0x7f, 0xc8, 0xc2, 0x03, 0x19, - 0xc2, 0x19, 0x19, 0x63, 0xeb, 0xf6, 0xff, 0xe4, 0xf3, 0xa5, 0xb9, 0xf8, 0xf9, 0xb2, 0xbb, 0xb4, - 0x6c, 0x82, 0x5d, 0x44, 0xe4, 0xff, 0xfb, 0x6f, 0x99, 0x66, 0xfd, 0xd5, 0xeb, 0x6d, 0x25, 0x2a, - 0x59, 0x2d, 0x2e, 0x99, 0xec, 0xad, 0xc6, 0x9f, 0x15, 0x50, 0xe3, 0x86, 0x90, 0x05, 0xfb, 0x8f, - 0x5e, 0x93, 0x7b, 0x4b, 0x97, 0x2a, 0x51, 0xf6, 0xfe, 0x12, 0xe5, 0xde, 0x51, 0xa2, 0xfc, 0x42, - 0x89, 0x9a, 0x8d, 0x64, 0x3c, 0x9b, 0x4b, 0xde, 0xe4, 0x20, 0x6c, 0xfc, 0x3e, 0x03, 0xb5, 0xc4, - 0x0c, 0xca, 0xa0, 0x9a, 0xa9, 0xc9, 0x53, 0x78, 0xf2, 0x9f, 0x2c, 0x2d, 0xa1, 0x2c, 0xde, 0xb2, - 0xf9, 0xcb, 0x7c, 0xcb, 0xfc, 0xfd, 0x34, 0x3d, 0x7f, 0xd9, 0x65, 0x25, 0x5e, 0x4c, 0x77, 0x6a, - 0x0a, 0x7f, 0x06, 0x71, 0xc1, 0xa3, 0x67, 0x20, 0xc7, 0xdb, 0x6d, 0xfb, 0x9e, 0x46, 0x19, 0x86, - 0xf2, 0x45, 0xa8, 0xce, 0x0f, 0x0a, 0x05, 0x1f, 0x57, 0xe5, 0x5b, 0xc6, 0x75, 0xff, 0x14, 0xd4, - 0xc5, 0xff, 0x3b, 0xa2, 0x87, 0x80, 0xa8, 0x33, 0x76, 0x89, 0x9d, 0xdc, 0x51, 0x57, 0xd0, 0x13, - 0x78, 0x6f, 0x16, 0x5f, 0x9b, 0xda, 0x54, 0xf6, 0x7f, 0x95, 0xce, 0xba, 0x7c, 0x96, 0x3e, 0x80, - 0xed, 0x51, 0xb7, 0x73, 0xae, 0x1b, 0x83, 0xd6, 0x99, 0x39, 0x7c, 0x61, 0x0e, 0x86, 0xad, 0xe1, - 0x68, 0x60, 0x8e, 0xba, 0x83, 0xbe, 0xde, 0xee, 0x9c, 0x74, 0xf4, 0x63, 0x75, 0x05, 0xad, 0x43, - 0xb5, 0xd3, 0x3d, 0xea, 0x8d, 0xba, 0xc7, 0xe6, 0x60, 0xd4, 0x6e, 0xeb, 0x83, 0x81, 0xaa, 0xa0, - 0x2d, 0x78, 0xd4, 0xd7, 0xbb, 0xc7, 0x9d, 0xee, 0xa9, 0x19, 0x6d, 0xea, 0x2f, 0xf4, 0xf6, 0x68, - 0xd8, 0xe9, 0x75, 0xd5, 0x0c, 0x7a, 0x0f, 0xd6, 0xfb, 0x6d, 0xa9, 0xd1, 0xe3, 0x73, 0x59, 0x06, - 0x3e, 0xb9, 0x71, 0xd2, 0xea, 0x9c, 0xe9, 0xc7, 0x6a, 0x0e, 0x6d, 0x42, 0xad, 0xdf, 0x36, 0x23, - 0x97, 0x86, 0x7e, 0xae, 0x1b, 0x43, 0x35, 0x8f, 0x36, 0x40, 0xed, 0x8d, 0x86, 0xc2, 0xbf, 0xdc, - 0x54, 0x0b, 0x29, 0x6d, 0xe4, 0xba, 0xc8, 0x70, 0xce, 0xb5, 0xd2, 0x6f, 0x09, 0xad, 0x42, 0xa9, - 0xdd, 0xea, 0xb6, 0x75, 0x26, 0x95, 0xf7, 0x7b, 0x50, 0x90, 0x91, 0x57, 0xa1, 0x92, 0x8e, 0xb2, - 0x02, 0xc5, 0xe8, 0x02, 0x85, 0x9d, 0xea, 0x1d, 0x0d, 0x74, 0xe3, 0x5c, 0x3f, 0x56, 0x33, 0x4c, - 0x12, 0x80, 0xf4, 0x63, 0x35, 0xcb, 0x0c, 0x5b, 0x47, 0x3d, 0x2e, 0xe4, 0xf6, 0x7f, 0xa3, 0x40, - 0x41, 0x70, 0x0a, 0x42, 0xb0, 0x96, 0xf0, 0x68, 0x0e, 0x5f, 0xa8, 0x2b, 0xa8, 0x08, 0xd9, 0xd3, - 0x16, 0x4b, 0xd7, 0x3a, 0x54, 0x4f, 0x5b, 0x03, 0xb3, 0xc5, 0xc2, 0x68, 0x7d, 0x71, 0xd6, 0x6b, - 0x31, 0xbf, 0x65, 0xc8, 0x9f, 0x8c, 0xba, 0xc7, 0x2c, 0x2d, 0x9b, 0x50, 0xe3, 0xcb, 0x94, 0x45, - 0x8e, 0x83, 0x92, 0x42, 0x9e, 0x5d, 0x10, 0xa5, 0x5a, 0xe6, 0xa7, 0x80, 0x54, 0x58, 0x35, 0xf4, - 0x41, 0x7b, 0xa4, 0x9b, 0xc2, 0x53, 0x71, 0xff, 0x4f, 0x0a, 0xac, 0x2f, 0x21, 0x3b, 0xb4, 0x0b, - 0x5b, 0xd1, 0xe9, 0x33, 0xfd, 0xb4, 0xd5, 0xfe, 0xc2, 0xbc, 0x83, 0xf6, 0x21, 0xa0, 0x05, 0x13, - 0x01, 0x5e, 0x83, 0x8d, 0x05, 0xbd, 0xb8, 0x2c, 0x83, 0x3e, 0x84, 0x9d, 0x65, 0x3b, 0xa9, 0x28, - 0xb2, 0xa8, 0x01, 0xf5, 0xbb, 0x7e, 0xd3, 0x91, 0x1e, 0xf5, 0xbf, 0x7e, 0x53, 0x57, 0xbe, 0x79, - 0x53, 0x57, 0xfe, 0xfe, 0xa6, 0xae, 0xfc, 0xf6, 0x6d, 0x7d, 0xe5, 0x9b, 0xb7, 0xf5, 0x95, 0xbf, - 0xbe, 0xad, 0xaf, 0xfc, 0xf2, 0xd3, 0xb1, 0x13, 0x5c, 0xce, 0x2e, 0x0e, 0x2c, 0x6f, 0x72, 0x38, - 0x9d, 0xd1, 0x4b, 0xce, 0x77, 0x7c, 0xf5, 0x8c, 0x2f, 0x9f, 0xb9, 0x9e, 0x4d, 0x0e, 0xc3, 0xc3, - 0x78, 0x82, 0xf8, 0x2f, 0x68, 0x17, 0x05, 0xfe, 0x5b, 0xd8, 0x0f, 0xfe, 0x1d, 0x00, 0x00, 0xff, - 0xff, 0xd0, 0xb5, 0x5e, 0x65, 0x5e, 0x13, 0x00, 0x00, + // 1866 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x58, 0x4f, 0x6f, 0xdb, 0xc8, + 0x15, 0x37, 0xf5, 0x5f, 0x4f, 0x8e, 0x45, 0x8d, 0xe4, 0x84, 0x49, 0xd6, 0xb2, 0xad, 0xdd, 0x6d, + 0x0c, 0x2f, 0x62, 0x77, 0xd3, 0x76, 0x81, 0x0a, 0x28, 0x0a, 0x59, 0xa6, 0xbd, 0x6a, 0x5d, 0x49, + 0xa5, 0x24, 0x23, 0xdb, 0x0b, 0x31, 0x26, 0x27, 0x32, 0xb1, 0x12, 0x29, 0x70, 0x28, 0x99, 0x3e, + 0xf7, 0x56, 0x14, 0x68, 0x2f, 0x05, 0x72, 0xcc, 0xb9, 0xbd, 0xf4, 0xd0, 0x0f, 0xb1, 0xc7, 0x2d, + 0x7a, 0x29, 0xd0, 0x4b, 0x91, 0x1c, 0xda, 0x6f, 0xd1, 0x62, 0x66, 0x48, 0x91, 0x94, 0xe5, 0xec, + 0x16, 0x3d, 0x14, 0xe8, 0x25, 0x9e, 0xf7, 0xe6, 0xcd, 0x9b, 0xf7, 0xde, 0xef, 0xf7, 0xde, 0x30, + 0x02, 0x65, 0x4e, 0x7c, 0x62, 0xcc, 0x3d, 0xc7, 0x3d, 0x5e, 0x7c, 0x7a, 0xec, 0xdd, 0xce, 0x08, + 0x3d, 0x9a, 0xb9, 0x8e, 0xe7, 0xa0, 0xcd, 0xe5, 0xce, 0xd1, 0xe2, 0xd3, 0x27, 0xb5, 0xb1, 0x33, + 0x76, 0xf8, 0xc6, 0x31, 0x5b, 0x09, 0x9b, 0x27, 0x15, 0x3c, 0xb5, 0x6c, 0xe7, 0x98, 0xff, 0x2b, + 0x54, 0x0d, 0x0a, 0xb9, 0x3e, 0x76, 0xf1, 0x94, 0xa2, 0x1d, 0x00, 0xea, 0x4c, 0x89, 0xbe, 0xc0, + 0x93, 0x39, 0x51, 0x52, 0x7b, 0xd2, 0x41, 0x41, 0x2b, 0x32, 0xcd, 0x25, 0x53, 0xa0, 0x4f, 0x00, + 0x4d, 0xb1, 0xaf, 0x8f, 0x31, 0x9d, 0x10, 0x4a, 0x75, 0x8f, 0x2f, 0x95, 0xf4, 0x9e, 0x74, 0x90, + 0xd1, 0xca, 0x53, 0xec, 0x9f, 0x8b, 0x8d, 0x21, 0x5b, 0x34, 0x77, 0x5e, 0xbf, 0xd9, 0xdd, 0xf8, + 0xe7, 0x9b, 0x5d, 0xe9, 0x57, 0xff, 0xf8, 0xe3, 0xa1, 0x1c, 0xc5, 0x3c, 0xe3, 0x57, 0x35, 0xfe, + 0x96, 0x02, 0x79, 0x64, 0x5b, 0x0b, 0xe2, 0x52, 0x3c, 0xe9, 0xe3, 0xdb, 0x89, 0x83, 0x4d, 0xb4, + 0x05, 0x29, 0xcf, 0x51, 0xa4, 0x3d, 0xe9, 0xa0, 0xa8, 0xa5, 0x3c, 0x07, 0xd5, 0x20, 0x1b, 0x85, + 0x52, 0xd4, 0x84, 0x80, 0x10, 0x64, 0x4c, 0xec, 0x61, 0x7e, 0x71, 0x51, 0xe3, 0x6b, 0xf4, 0x14, + 0x8a, 0x63, 0x4c, 0xf5, 0x89, 0x35, 0xb5, 0x3c, 0x25, 0xc3, 0x37, 0x0a, 0x63, 0x4c, 0x2f, 0x98, + 0x8c, 0x3e, 0x06, 0x16, 0x9d, 0xfe, 0x8a, 0x10, 0x7d, 0x46, 0x5c, 0x1e, 0x74, 0x96, 0x9b, 0x6c, + 0x4e, 0xb1, 0x7f, 0x46, 0x48, 0x9f, 0xb8, 0xe7, 0x98, 0xa2, 0xcf, 0x40, 0x61, 0x66, 0x33, 0xd7, + 0x72, 0x5c, 0xcb, 0xbb, 0x4d, 0xd8, 0xe7, 0xb8, 0x7d, 0x6d, 0x8a, 0xfd, 0x7e, 0xb0, 0x1d, 0x9d, + 0xab, 0x41, 0xd6, 0x76, 0x6c, 0x83, 0x28, 0x79, 0x11, 0x25, 0x17, 0xd0, 0x13, 0x28, 0x98, 0x04, + 0x9b, 0x13, 0xcb, 0x26, 0x4a, 0x41, 0x04, 0x14, 0xca, 0xe8, 0x07, 0x90, 0x5b, 0xe8, 0x0c, 0x39, + 0xa5, 0xb8, 0x27, 0x1d, 0x6c, 0xbd, 0xa8, 0x1f, 0xc5, 0x91, 0x3b, 0xba, 0x24, 0xae, 0xf5, 0xca, + 0x32, 0xb0, 0x67, 0x39, 0xf6, 0xf0, 0x76, 0x46, 0xb4, 0xec, 0x82, 0xfd, 0x69, 0x1e, 0xc4, 0x4b, + 0xfa, 0x34, 0x2a, 0xe9, 0x3c, 0xac, 0xa3, 0x3e, 0x13, 0x85, 0x6c, 0xbc, 0x96, 0x00, 0x2d, 0xab, + 0xdb, 0x32, 0x0c, 0x67, 0x6e, 0x7b, 0x1d, 0x13, 0x3d, 0x83, 0xb2, 0x71, 0x8d, 0x2d, 0x5b, 0xb7, + 0xf1, 0x94, 0xd0, 0x19, 0x36, 0x48, 0x50, 0xec, 0x2d, 0xae, 0xee, 0x86, 0x5a, 0xf4, 0x18, 0x0a, + 0xc2, 0xd0, 0x32, 0x83, 0xda, 0xe7, 0xb9, 0xdc, 0x31, 0x59, 0xb6, 0xce, 0x8d, 0x4d, 0xdc, 0xa0, + 0xfc, 0x42, 0xf8, 0x16, 0xa1, 0x61, 0x11, 0x45, 0xc3, 0x00, 0xa4, 0x91, 0x05, 0x71, 0xbd, 0x8e, + 0x4d, 0x3d, 0x77, 0x6e, 0xb0, 0x24, 0x29, 0xfa, 0x18, 0xb6, 0x5e, 0xcd, 0x6d, 0x53, 0x77, 0x89, + 0x61, 0xcd, 0x2c, 0x62, 0x7b, 0x41, 0x60, 0x0f, 0x98, 0x56, 0x0b, 0x95, 0xcd, 0xef, 0x84, 0x57, + 0xec, 0x44, 0x57, 0xb8, 0xdc, 0x9b, 0x6e, 0xc5, 0xdc, 0x35, 0x7e, 0x9d, 0x81, 0x7c, 0xc7, 0xbe, + 0x72, 0xe6, 0xb6, 0x89, 0xf6, 0x61, 0x93, 0x3a, 0x73, 0xd7, 0x20, 0x3a, 0x4f, 0x21, 0x70, 0x5c, + 0x12, 0xba, 0x36, 0x53, 0xa1, 0x47, 0x90, 0xf7, 0x7c, 0xfd, 0x1a, 0xd3, 0xeb, 0x20, 0xdb, 0x9c, + 0xe7, 0x7f, 0x8e, 0xe9, 0x35, 0x7a, 0x08, 0x39, 0x4a, 0x6c, 0x73, 0x99, 0x6d, 0x20, 0xa1, 0x0f, + 0xa0, 0x18, 0x45, 0x2a, 0xe8, 0x16, 0x29, 0xd8, 0x29, 0x3c, 0x65, 0xc9, 0x06, 0x34, 0x0b, 0x24, + 0xd6, 0x5e, 0x98, 0x52, 0xe2, 0xe9, 0xd8, 0x34, 0xdd, 0x80, 0x52, 0x45, 0xae, 0x69, 0x99, 0xa6, + 0xcb, 0x38, 0x3c, 0x71, 0xc6, 0xba, 0x65, 0x9b, 0xc4, 0x0f, 0xb8, 0x54, 0x98, 0x38, 0xe3, 0x0e, + 0x93, 0xd1, 0x73, 0x1e, 0x22, 0xe7, 0x4c, 0x81, 0x73, 0xa6, 0x96, 0xe4, 0xcc, 0xd0, 0xe7, 0x4c, + 0xc9, 0x79, 0xfc, 0x2f, 0xfa, 0x29, 0x54, 0xee, 0xb0, 0x82, 0x93, 0xad, 0xb4, 0x4a, 0xb6, 0xd5, + 0x26, 0xd4, 0xe4, 0xf9, 0x6a, 0x5b, 0x7e, 0x02, 0x95, 0x45, 0x8c, 0x92, 0x3a, 0xef, 0x3e, 0xe0, + 0x01, 0xca, 0xf1, 0x8d, 0x53, 0xd6, 0x89, 0x3f, 0x87, 0xea, 0x1a, 0x44, 0x94, 0x12, 0xbf, 0x7b, + 0x2f, 0x79, 0xf7, 0x5d, 0x22, 0x68, 0xc8, 0xbd, 0x4b, 0x8e, 0x1a, 0x64, 0x2d, 0xda, 0x56, 0x5b, + 0xca, 0x26, 0x9f, 0x48, 0x42, 0x40, 0xbb, 0x50, 0x72, 0xf1, 0xcd, 0x32, 0xb9, 0x07, 0x3c, 0x1e, + 0x70, 0xf1, 0x4d, 0x10, 0x76, 0xb3, 0x10, 0x72, 0xb2, 0xf1, 0x95, 0x04, 0x99, 0x7e, 0x7b, 0xe8, + 0xc7, 0x81, 0x96, 0xee, 0x01, 0x3a, 0x95, 0x00, 0xfa, 0x31, 0xb0, 0x31, 0xa2, 0xcf, 0x29, 0x31, + 0x83, 0x41, 0x97, 0x1f, 0x63, 0x3a, 0xa2, 0x84, 0xf3, 0xea, 0x6a, 0xe2, 0x18, 0x5f, 0xea, 0xd7, + 0xc4, 0x1a, 0x5f, 0x0b, 0x1a, 0x64, 0xb4, 0x12, 0xd7, 0x7d, 0xce, 0x55, 0xdc, 0xab, 0x87, 0xbd, + 0x79, 0x38, 0x3f, 0x02, 0x89, 0x21, 0x4d, 0x5c, 0xd7, 0x71, 0xf5, 0x29, 0x1d, 0x87, 0x48, 0x73, + 0xc5, 0xcf, 0xe8, 0xb8, 0xf9, 0x41, 0xbc, 0x95, 0xca, 0xb1, 0xc1, 0x69, 0xe8, 0x9e, 0xdf, 0xf8, + 0x8b, 0x04, 0xd5, 0xde, 0xdc, 0xe3, 0xd4, 0xee, 0x5d, 0x51, 0xe2, 0x2e, 0x78, 0xe9, 0x91, 0x02, + 0x79, 0x3a, 0x37, 0x0c, 0x42, 0x29, 0xcf, 0xac, 0xa0, 0x85, 0xe2, 0x9d, 0x38, 0x53, 0x77, 0xe3, + 0x8c, 0x95, 0x25, 0x9d, 0x28, 0x4b, 0x22, 0xd0, 0x4c, 0x32, 0x50, 0xb4, 0x07, 0x9b, 0xac, 0x36, + 0x6c, 0x4c, 0xf2, 0xfa, 0x08, 0xb2, 0xc3, 0x18, 0xd3, 0x33, 0x42, 0x58, 0x89, 0x9a, 0xcf, 0xc2, + 0x34, 0xea, 0x51, 0x1a, 0x4e, 0x10, 0xbc, 0xee, 0x44, 0xd1, 0x37, 0xa6, 0x50, 0xee, 0xb9, 0xd6, + 0xd8, 0xb2, 0xb1, 0x67, 0xd9, 0xe3, 0xbe, 0xf1, 0x3e, 0xa8, 0x12, 0x6d, 0x92, 0x4a, 0xb6, 0x49, + 0xf3, 0xa3, 0x35, 0x33, 0xc8, 0x89, 0x3c, 0xeb, 0xa2, 0x88, 0xbf, 0xcb, 0x03, 0x84, 0x45, 0x1c, + 0xfa, 0x8c, 0xdf, 0x26, 0xa1, 0x1e, 0xb7, 0x71, 0xec, 0xc4, 0x98, 0x90, 0x63, 0x1b, 0x62, 0x56, + 0x24, 0x5a, 0x3f, 0x75, 0x7f, 0xeb, 0xa7, 0x13, 0xad, 0x7f, 0x04, 0x55, 0xe2, 0x7b, 0xc4, 0xb5, + 0xd9, 0x24, 0x8c, 0x66, 0x80, 0x28, 0x69, 0x25, 0xdc, 0x6a, 0x2d, 0x67, 0xc1, 0x01, 0xc8, 0x33, + 0xd7, 0x78, 0xf1, 0xdd, 0xb8, 0xb1, 0xa8, 0xef, 0x16, 0xd7, 0x47, 0x96, 0x11, 0x73, 0x73, 0x09, + 0xe6, 0x2a, 0x90, 0x0f, 0x5b, 0x43, 0x30, 0x2c, 0x14, 0x93, 0x6f, 0x65, 0x61, 0xe5, 0xad, 0x8c, + 0xcd, 0x99, 0xe2, 0xb7, 0x98, 0x33, 0x2f, 0x20, 0xcb, 0x4b, 0xca, 0xc7, 0x41, 0xe9, 0xc5, 0x4e, + 0xd2, 0x78, 0x05, 0x53, 0x2d, 0x33, 0x63, 0xc8, 0x9e, 0x40, 0x49, 0x60, 0x4f, 0x4c, 0x76, 0x52, + 0x4c, 0x86, 0xfd, 0x95, 0x93, 0x77, 0x29, 0xae, 0x41, 0x78, 0x6a, 0xe8, 0xb3, 0x2f, 0x05, 0xcb, + 0xe4, 0xf3, 0xa0, 0xa8, 0xa5, 0x2c, 0x13, 0xfd, 0x08, 0xca, 0x4b, 0x62, 0x05, 0x2d, 0xf7, 0x60, + 0x5d, 0xf8, 0x03, 0xbe, 0xa7, 0x6d, 0x85, 0xc6, 0x42, 0xbe, 0x6f, 0x68, 0x6d, 0xfd, 0x17, 0x43, + 0xeb, 0x04, 0xaa, 0x33, 0x43, 0x0f, 0xbc, 0x8a, 0xf3, 0x96, 0x63, 0x2b, 0x65, 0xee, 0x12, 0x25, + 0x5d, 0xb2, 0xd9, 0xa4, 0x55, 0x66, 0x86, 0x70, 0xad, 0x86, 0xc6, 0x21, 0x52, 0x33, 0xd7, 0x32, + 0x88, 0x22, 0x2f, 0x91, 0xea, 0x33, 0x99, 0x35, 0x48, 0xd0, 0x7e, 0x4a, 0x45, 0x20, 0x2f, 0x3a, + 0x6f, 0x79, 0x33, 0x7f, 0x50, 0xa3, 0x9b, 0xd1, 0xfb, 0x6f, 0x66, 0xd6, 0xd1, 0xcd, 0x87, 0x50, + 0x09, 0x1c, 0xd0, 0x1b, 0x3c, 0xd3, 0x79, 0xcf, 0x2b, 0x55, 0x7e, 0x4d, 0x59, 0x6c, 0x0c, 0x6e, + 0xf0, 0x4c, 0x65, 0xea, 0x30, 0x4a, 0xcf, 0xf9, 0x92, 0xd8, 0x4a, 0x6d, 0x19, 0xe5, 0x90, 0xc9, + 0x6c, 0xfa, 0xe0, 0x2b, 0xc7, 0xf5, 0x74, 0x97, 0x60, 0xea, 0xd8, 0xca, 0xb6, 0x78, 0x7d, 0xb9, + 0x4e, 0xe3, 0xaa, 0xd8, 0x9c, 0xfe, 0x97, 0x04, 0xa5, 0xe5, 0x7b, 0xb4, 0x44, 0x59, 0x5a, 0xa2, + 0xfc, 0x7d, 0x00, 0x4b, 0xbc, 0xea, 0x8c, 0x38, 0x29, 0x9e, 0xd0, 0x76, 0x32, 0xa1, 0xe0, 0xd5, + 0xd7, 0x8a, 0x81, 0xe1, 0xd0, 0x47, 0xcf, 0x42, 0x8e, 0xa6, 0xf7, 0xd2, 0xf7, 0x54, 0x40, 0x10, + 0xf3, 0x87, 0x50, 0x5a, 0x92, 0xc8, 0xf3, 0x95, 0x0c, 0x37, 0x57, 0xd6, 0x13, 0x73, 0xe8, 0x6b, + 0xe0, 0x44, 0x23, 0x64, 0x1f, 0x36, 0x43, 0xa8, 0x79, 0xa9, 0x44, 0x2f, 0x96, 0x84, 0x8e, 0x97, + 0xa9, 0xf9, 0x61, 0x7c, 0xae, 0x3f, 0x5c, 0xf7, 0x89, 0xe4, 0xf9, 0x8d, 0x3f, 0xa4, 0xe1, 0x41, + 0x90, 0xc2, 0x05, 0x19, 0x63, 0xe3, 0xf6, 0xff, 0xe4, 0xf3, 0xa5, 0xb9, 0xfa, 0xf9, 0xb2, 0xbf, + 0x16, 0x36, 0x31, 0x5d, 0x44, 0xe6, 0xff, 0xfb, 0x6f, 0x99, 0x66, 0xfd, 0xf5, 0x9b, 0x5d, 0x29, + 0x84, 0xac, 0x12, 0x41, 0x16, 0x70, 0xab, 0xf1, 0x67, 0x09, 0xe4, 0x88, 0x10, 0x01, 0x60, 0xff, + 0xd1, 0x6b, 0x72, 0x2f, 0x74, 0x09, 0x88, 0xd2, 0xf7, 0x43, 0x94, 0x79, 0x0f, 0x44, 0xd9, 0x15, + 0x88, 0x9a, 0x8d, 0x78, 0x3e, 0xdb, 0x6b, 0xde, 0x64, 0xcf, 0x6f, 0xfc, 0x3e, 0x05, 0x95, 0x58, + 0x0f, 0x06, 0x49, 0x35, 0x13, 0x9d, 0x27, 0xf1, 0xe2, 0x3f, 0x5d, 0x0b, 0x61, 0x00, 0xde, 0xba, + 0xfe, 0x4b, 0x7d, 0x43, 0xff, 0xfd, 0x38, 0xd9, 0x7f, 0xe9, 0x75, 0x10, 0xaf, 0x96, 0x3b, 0xd1, + 0x85, 0x3f, 0x81, 0x08, 0xf0, 0xf0, 0x19, 0xc8, 0x70, 0xba, 0xed, 0xde, 0x43, 0x94, 0xa1, 0x1f, + 0xbc, 0x08, 0xe5, 0xe5, 0x41, 0xa1, 0xe0, 0xed, 0x2a, 0x7d, 0x43, 0xbb, 0x1e, 0x9e, 0x83, 0xbc, + 0xfa, 0x9f, 0x35, 0xf4, 0x10, 0x10, 0xb5, 0xc6, 0x36, 0x31, 0xe3, 0x3b, 0xf2, 0x06, 0x7a, 0x0a, + 0x8f, 0xe6, 0xd1, 0xb5, 0x89, 0x4d, 0xe9, 0xf0, 0x97, 0xc9, 0xaa, 0x07, 0xcf, 0xd2, 0x87, 0xb0, + 0x3b, 0xea, 0x76, 0x2e, 0x55, 0x6d, 0xd0, 0xba, 0xd0, 0x87, 0x2f, 0xf5, 0xc1, 0xb0, 0x35, 0x1c, + 0x0d, 0xf4, 0x51, 0x77, 0xd0, 0x57, 0xdb, 0x9d, 0xb3, 0x8e, 0x7a, 0x2a, 0x6f, 0xa0, 0x2a, 0x94, + 0x3b, 0xdd, 0x93, 0xde, 0xa8, 0x7b, 0xaa, 0x0f, 0x46, 0xed, 0xb6, 0x3a, 0x18, 0xc8, 0x12, 0xda, + 0x81, 0xc7, 0x7d, 0xb5, 0x7b, 0xda, 0xe9, 0x9e, 0xeb, 0xe1, 0xa6, 0xfa, 0x52, 0x6d, 0x8f, 0x86, + 0x9d, 0x5e, 0x57, 0x4e, 0xa1, 0x47, 0x50, 0xed, 0xb7, 0x03, 0x8d, 0x1a, 0x9d, 0x4b, 0xb3, 0xe0, + 0xe3, 0x1b, 0x67, 0xad, 0xce, 0x85, 0x7a, 0x2a, 0x67, 0xd0, 0x36, 0x54, 0xfa, 0x6d, 0x3d, 0x74, + 0xa9, 0xa9, 0x97, 0xaa, 0x36, 0x94, 0xb3, 0xa8, 0x06, 0x72, 0x6f, 0x34, 0x14, 0xfe, 0x83, 0x4d, + 0x39, 0x97, 0xd0, 0x86, 0xae, 0xf3, 0x2c, 0xce, 0xa5, 0x36, 0xf0, 0x5b, 0x40, 0x9b, 0x50, 0x68, + 0xb7, 0xba, 0x6d, 0x95, 0x49, 0xc5, 0xc3, 0x1e, 0xe4, 0x82, 0xcc, 0xcb, 0x50, 0x4a, 0x66, 0x59, + 0x82, 0x7c, 0x78, 0x81, 0xc4, 0x4e, 0xf5, 0x4e, 0x06, 0xaa, 0x76, 0xa9, 0x9e, 0xca, 0x29, 0x26, + 0x89, 0x80, 0xd4, 0x53, 0x39, 0xcd, 0x0c, 0x5b, 0x27, 0x3d, 0x2e, 0x64, 0x0e, 0x7f, 0x23, 0x41, + 0x4e, 0xcc, 0x14, 0x84, 0x60, 0x2b, 0xe6, 0x51, 0x1f, 0xbe, 0x94, 0x37, 0x50, 0x1e, 0xd2, 0xe7, + 0x2d, 0x56, 0xae, 0x2a, 0x94, 0xcf, 0x5b, 0x03, 0xbd, 0xc5, 0xd2, 0x68, 0x7d, 0x71, 0xd1, 0x6b, + 0x31, 0xbf, 0x45, 0xc8, 0x9e, 0x8d, 0xba, 0xa7, 0xac, 0x2c, 0xdb, 0x50, 0xe1, 0xcb, 0x84, 0x45, + 0x86, 0x07, 0x15, 0x08, 0x59, 0x76, 0x41, 0x58, 0xea, 0xa0, 0x3e, 0x39, 0x24, 0xc3, 0xa6, 0xa6, + 0x0e, 0xda, 0x23, 0x55, 0x17, 0x9e, 0xf2, 0x87, 0x7f, 0x92, 0xa0, 0xba, 0x66, 0xd8, 0xa1, 0x7d, + 0xd8, 0x09, 0x4f, 0x5f, 0xa8, 0xe7, 0xad, 0xf6, 0x17, 0xfa, 0x9d, 0x68, 0x1f, 0x02, 0x5a, 0x31, + 0x11, 0xc1, 0x2b, 0x50, 0x5b, 0xd1, 0x8b, 0xcb, 0x52, 0xe8, 0x23, 0xd8, 0x5b, 0xb7, 0x93, 0xc8, + 0x22, 0x8d, 0x1a, 0x50, 0xbf, 0xeb, 0x37, 0x99, 0xe9, 0x49, 0xff, 0xab, 0xb7, 0x75, 0xe9, 0xeb, + 0xb7, 0x75, 0xe9, 0xef, 0x6f, 0xeb, 0xd2, 0x6f, 0xdf, 0xd5, 0x37, 0xbe, 0x7e, 0x57, 0xdf, 0xf8, + 0xeb, 0xbb, 0xfa, 0xc6, 0x2f, 0x3e, 0x1b, 0x5b, 0xde, 0xf5, 0xfc, 0xea, 0xc8, 0x70, 0xa6, 0xc7, + 0xb3, 0x39, 0xbd, 0xe6, 0xf3, 0x8e, 0xaf, 0x9e, 0xf3, 0xe5, 0x73, 0xdb, 0x31, 0xc9, 0xb1, 0x7f, + 0x1c, 0x75, 0x10, 0xff, 0xc9, 0xea, 0x2a, 0xc7, 0x7f, 0x7c, 0xfa, 0xde, 0xbf, 0x03, 0x00, 0x00, + 0xff, 0xff, 0x2c, 0x1d, 0x76, 0x71, 0xcf, 0x12, 0x00, 0x00, } func (this *Params) Equal(that interface{}) bool { @@ -1650,36 +1587,6 @@ func (this *UniversalPayload) Equal(that interface{}) bool { } return true } -func (this *MigrationPayload) Equal(that interface{}) bool { - if that == nil { - return this == nil - } - - that1, ok := that.(*MigrationPayload) - if !ok { - that2, ok := that.(MigrationPayload) - if ok { - that1 = &that2 - } else { - return false - } - } - if that1 == nil { - return this == nil - } else if this == nil { - return false - } - if this.Migration != that1.Migration { - return false - } - if this.Nonce != that1.Nonce { - return false - } - if this.Deadline != that1.Deadline { - return false - } - return true -} func (this *UniversalAccountId) Equal(that interface{}) bool { if that == nil { return this == nil @@ -2273,50 +2180,6 @@ func (m *UniversalPayload) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *MigrationPayload) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MigrationPayload) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MigrationPayload) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.Deadline) > 0 { - i -= len(m.Deadline) - copy(dAtA[i:], m.Deadline) - i = encodeVarintTypes(dAtA, i, uint64(len(m.Deadline))) - i-- - dAtA[i] = 0x1a - } - if len(m.Nonce) > 0 { - i -= len(m.Nonce) - copy(dAtA[i:], m.Nonce) - i = encodeVarintTypes(dAtA, i, uint64(len(m.Nonce))) - i-- - dAtA[i] = 0x12 - } - if len(m.Migration) > 0 { - i -= len(m.Migration) - copy(dAtA[i:], m.Migration) - i = encodeVarintTypes(dAtA, i, uint64(len(m.Migration))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - func (m *UniversalAccountId) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -3243,27 +3106,6 @@ func (m *UniversalPayload) Size() (n int) { return n } -func (m *MigrationPayload) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Migration) - if l > 0 { - n += 1 + l + sovTypes(uint64(l)) - } - l = len(m.Nonce) - if l > 0 { - n += 1 + l + sovTypes(uint64(l)) - } - l = len(m.Deadline) - if l > 0 { - n += 1 + l + sovTypes(uint64(l)) - } - return n -} - func (m *UniversalAccountId) Size() (n int) { if m == nil { return 0 @@ -4079,152 +3921,6 @@ func (m *UniversalPayload) Unmarshal(dAtA []byte) error { } return nil } -func (m *MigrationPayload) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MigrationPayload: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MigrationPayload: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Migration", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTypes - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTypes - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Migration = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Nonce", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTypes - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTypes - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Nonce = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Deadline", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTypes - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTypes - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Deadline = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipTypes(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTypes - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func (m *UniversalAccountId) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/x/uexecutor/types/uint256_test.go b/x/uexecutor/types/uint256_test.go index 7af329908..fd3e6365e 100644 --- a/x/uexecutor/types/uint256_test.go +++ b/x/uexecutor/types/uint256_test.go @@ -363,3 +363,180 @@ func TestOutboundTx_ValidateBasic_Uint256Range(t *testing.T) { }) } } + +// baseValidVoteOutbound is a well-formed success vote; only gas_fee_used varies +// in the tests below. +func baseValidVoteOutbound() types.MsgVoteOutbound { + return types.MsgVoteOutbound{ + Signer: "push1fgaewhyd9fkwtqaj9c233letwcuey6dgly9gv9", + TxId: "ob-1", + UtxId: "utx-1", + ObservedTx: &types.OutboundObservation{ + Success: true, + BlockHeight: 100, + TxHash: "0xb28f49668e7e76dc96d7aabe5b7f63fecfbd1c3574774c05e8204e749fd96fbd", + GasFeeUsed: "21000", + }, + } +} + +// F-2026-18798, DoS half, for the two gas fields that were missed in the first +// pass. Neither message is size capped, so the per-field length cap is the only +// thing standing between a validator-signed multi-million-digit string and the +// superlinear parse — and both messages are gasless. +func TestGasFields_RejectHugeDecimalFast(t *testing.T) { + huge := hugeDecimal() + + t.Run("outbound gas_limit", func(t *testing.T) { + ob := baseValidOutbound() + ob.GasLimit = huge + + start := time.Now() + err := ob.ValidateBasic() + elapsed := time.Since(start) + + require.Error(t, err) + require.Less(t, elapsed, dosBudget, "rejecting a %d-digit gas_limit took %s", dosDigits, elapsed) + require.Contains(t, err.Error(), "exceeds the maximum of 80 characters") + }) + + t.Run("vote outbound gas_fee_used", func(t *testing.T) { + msg := baseValidVoteOutbound() + msg.ObservedTx.GasFeeUsed = huge + + start := time.Now() + err := msg.ValidateBasic() + elapsed := time.Since(start) + + require.Error(t, err) + require.Less(t, elapsed, dosBudget, "rejecting a %d-digit gas_fee_used took %s", dosDigits, elapsed) + require.Contains(t, err.Error(), "exceeds the maximum of 80 characters") + }) +} + +func TestOutboundTx_ValidateBasic_GasLimitUint256(t *testing.T) { + tests := []struct { + name string + gasLimit string + expectError bool + errContains string + }{ + {name: "normal gas_limit accepted", gasLimit: "21000"}, + {name: "zero accepted", gasLimit: "0"}, + {name: "empty gas_limit still skipped", gasLimit: ""}, + {name: "max uint256 accepted", gasLimit: maxUint256Dec}, + { + name: "2^256 rejected", + gasLimit: overMaxUint256Dec, + expectError: true, + errContains: "exceeds the uint256 range", + }, + { + name: "78 nines rejected despite fitting the length cap", + gasLimit: nines78(), + expectError: true, + errContains: "exceeds the uint256 range", + }, + { + name: "over length cap rejected", + gasLimit: strings.Repeat("1", types.MaxUint256DecimalLen+1), + expectError: true, + errContains: "exceeds the maximum of 80 characters", + }, + // Regression: the old check discarded the parsed value, so it never looked + // at the sign and accepted a negative gas_limit. + { + name: "negative rejected", + gasLimit: "-5", + expectError: true, + errContains: "gas_limit must be a valid uint", + }, + { + name: "non-numeric rejected", + gasLimit: "abc", + expectError: true, + errContains: "gas_limit must be a valid uint", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ob := baseValidOutbound() + ob.GasLimit = tc.gasLimit + + err := ob.ValidateBasic() + if tc.expectError { + require.Error(t, err) + require.Contains(t, err.Error(), tc.errContains) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestMsgVoteOutbound_ValidateBasic_GasFeeUsedUint256(t *testing.T) { + tests := []struct { + name string + gasFeeUsed string + expectError bool + errContains string + }{ + {name: "normal gas_fee_used accepted", gasFeeUsed: "21000"}, + {name: "zero accepted", gasFeeUsed: "0"}, + {name: "max uint256 accepted", gasFeeUsed: maxUint256Dec}, + // Pre-existing semantics preserved: the field is required, and keeps its + // own message ahead of the uint256 parse. + { + name: "empty still rejected as required", + gasFeeUsed: "", + expectError: true, + errContains: "observed_tx.gas_fee_used is required", + }, + { + name: "2^256 rejected", + gasFeeUsed: overMaxUint256Dec, + expectError: true, + errContains: "exceeds the uint256 range", + }, + { + name: "78 nines rejected despite fitting the length cap", + gasFeeUsed: nines78(), + expectError: true, + errContains: "exceeds the uint256 range", + }, + { + name: "over length cap rejected", + gasFeeUsed: strings.Repeat("1", types.MaxUint256DecimalLen+1), + expectError: true, + errContains: "exceeds the maximum of 80 characters", + }, + { + name: "negative rejected", + gasFeeUsed: "-1", + expectError: true, + errContains: "observed_tx.gas_fee_used must be a valid uint256", + }, + { + name: "non-numeric rejected", + gasFeeUsed: "abc", + expectError: true, + errContains: "observed_tx.gas_fee_used must be a valid uint256", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + msg := baseValidVoteOutbound() + msg.ObservedTx.GasFeeUsed = tc.gasFeeUsed + + err := msg.ValidateBasic() + if tc.expectError { + require.Error(t, err) + require.Contains(t, err.Error(), tc.errContains) + } else { + require.NoError(t, err) + } + }) + } +} From 9a3a53677e215cfd68c5ff084a7edba9726cf828 Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Wed, 2 Sep 2026 17:28:55 +0530 Subject: [PATCH 55/60] feat(uexecutor): add MsgExecuteStuckInbound admin hatch (F-2026-18147) (#363) * feat(uexecutor): add MsgExecuteStuckInbound admin hatch Executes an inbound whose ballot is PENDING-unreachable with the vote threshold already met, instead of only being able to refund it. * feat(uexecutor): add MsgExecuteStuckOutbound admin hatch Settles a stuck outbound per its observed_tx, for EXPIRED or PENDING-unreachable ballots. Observation validation is now shared with MsgVoteOutbound so the two cannot drift. * chore: trim comments * chore: trim comments --- api/uexecutor/v1/tx.pulsar.go | 2442 ++++++++++++++++- api/uexecutor/v1/tx_grpc.pb.go | 100 +- proto/uexecutor/v1/tx.proto | 59 + .../uexecutor/execute_stuck_inbound_test.go | 484 ++++ .../uexecutor/execute_stuck_outbound_test.go | 466 ++++ x/uexecutor/keeper/admin_execute.go | 103 + x/uexecutor/keeper/admin_execute_outbound.go | 119 + x/uexecutor/keeper/admin_revert.go | 4 +- x/uexecutor/keeper/msg_server.go | 74 + x/uexecutor/keeper/msg_vote_inbound.go | 13 + x/uexecutor/keeper/msg_vote_outbound.go | 17 + x/uexecutor/types/expected_keepers.go | 4 + .../types/msg_execute_stuck_outbound.go | 35 + .../types/msg_execute_stuck_outbound_test.go | 130 + x/uexecutor/types/msg_vote_outbound.go | 35 +- x/uexecutor/types/outbound_observation.go | 45 + x/uexecutor/types/tx.pb.go | 1189 +++++++- 17 files changed, 5102 insertions(+), 217 deletions(-) create mode 100644 test/integration/uexecutor/execute_stuck_inbound_test.go create mode 100644 test/integration/uexecutor/execute_stuck_outbound_test.go create mode 100644 x/uexecutor/keeper/admin_execute.go create mode 100644 x/uexecutor/keeper/admin_execute_outbound.go create mode 100644 x/uexecutor/types/msg_execute_stuck_outbound.go create mode 100644 x/uexecutor/types/msg_execute_stuck_outbound_test.go create mode 100644 x/uexecutor/types/outbound_observation.go diff --git a/api/uexecutor/v1/tx.pulsar.go b/api/uexecutor/v1/tx.pulsar.go index 12b1f75da..1f497a19f 100644 --- a/api/uexecutor/v1/tx.pulsar.go +++ b/api/uexecutor/v1/tx.pulsar.go @@ -5626,6 +5626,1972 @@ func (x *fastReflection_MsgRevertStuckInboundResponse) ProtoMethods() *protoifac } } +var ( + md_MsgExecuteStuckInbound protoreflect.MessageDescriptor + fd_MsgExecuteStuckInbound_signer protoreflect.FieldDescriptor + fd_MsgExecuteStuckInbound_inbound protoreflect.FieldDescriptor +) + +func init() { + file_uexecutor_v1_tx_proto_init() + md_MsgExecuteStuckInbound = File_uexecutor_v1_tx_proto.Messages().ByName("MsgExecuteStuckInbound") + fd_MsgExecuteStuckInbound_signer = md_MsgExecuteStuckInbound.Fields().ByName("signer") + fd_MsgExecuteStuckInbound_inbound = md_MsgExecuteStuckInbound.Fields().ByName("inbound") +} + +var _ protoreflect.Message = (*fastReflection_MsgExecuteStuckInbound)(nil) + +type fastReflection_MsgExecuteStuckInbound MsgExecuteStuckInbound + +func (x *MsgExecuteStuckInbound) ProtoReflect() protoreflect.Message { + return (*fastReflection_MsgExecuteStuckInbound)(x) +} + +func (x *MsgExecuteStuckInbound) slowProtoReflect() protoreflect.Message { + mi := &file_uexecutor_v1_tx_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +var _fastReflection_MsgExecuteStuckInbound_messageType fastReflection_MsgExecuteStuckInbound_messageType +var _ protoreflect.MessageType = fastReflection_MsgExecuteStuckInbound_messageType{} + +type fastReflection_MsgExecuteStuckInbound_messageType struct{} + +func (x fastReflection_MsgExecuteStuckInbound_messageType) Zero() protoreflect.Message { + return (*fastReflection_MsgExecuteStuckInbound)(nil) +} +func (x fastReflection_MsgExecuteStuckInbound_messageType) New() protoreflect.Message { + return new(fastReflection_MsgExecuteStuckInbound) +} +func (x fastReflection_MsgExecuteStuckInbound_messageType) Descriptor() protoreflect.MessageDescriptor { + return md_MsgExecuteStuckInbound +} + +// Descriptor returns message descriptor, which contains only the protobuf +// type information for the message. +func (x *fastReflection_MsgExecuteStuckInbound) Descriptor() protoreflect.MessageDescriptor { + return md_MsgExecuteStuckInbound +} + +// Type returns the message type, which encapsulates both Go and protobuf +// type information. If the Go type information is not needed, +// it is recommended that the message descriptor be used instead. +func (x *fastReflection_MsgExecuteStuckInbound) Type() protoreflect.MessageType { + return _fastReflection_MsgExecuteStuckInbound_messageType +} + +// New returns a newly allocated and mutable empty message. +func (x *fastReflection_MsgExecuteStuckInbound) New() protoreflect.Message { + return new(fastReflection_MsgExecuteStuckInbound) +} + +// Interface unwraps the message reflection interface and +// returns the underlying ProtoMessage interface. +func (x *fastReflection_MsgExecuteStuckInbound) Interface() protoreflect.ProtoMessage { + return (*MsgExecuteStuckInbound)(x) +} + +// Range iterates over every populated field in an undefined order, +// calling f for each field descriptor and value encountered. +// Range returns immediately if f returns false. +// While iterating, mutating operations may only be performed +// on the current field descriptor. +func (x *fastReflection_MsgExecuteStuckInbound) Range(f func(protoreflect.FieldDescriptor, protoreflect.Value) bool) { + if x.Signer != "" { + value := protoreflect.ValueOfString(x.Signer) + if !f(fd_MsgExecuteStuckInbound_signer, value) { + return + } + } + if x.Inbound != nil { + value := protoreflect.ValueOfMessage(x.Inbound.ProtoReflect()) + if !f(fd_MsgExecuteStuckInbound_inbound, value) { + return + } + } +} + +// Has reports whether a field is populated. +// +// Some fields have the property of nullability where it is possible to +// distinguish between the default value of a field and whether the field +// was explicitly populated with the default value. Singular message fields, +// member fields of a oneof, and proto2 scalar fields are nullable. Such +// fields are populated only if explicitly set. +// +// In other cases (aside from the nullable cases above), +// a proto3 scalar field is populated if it contains a non-zero value, and +// a repeated field is populated if it is non-empty. +func (x *fastReflection_MsgExecuteStuckInbound) Has(fd protoreflect.FieldDescriptor) bool { + switch fd.FullName() { + case "uexecutor.v1.MsgExecuteStuckInbound.signer": + return x.Signer != "" + case "uexecutor.v1.MsgExecuteStuckInbound.inbound": + return x.Inbound != nil + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgExecuteStuckInbound")) + } + panic(fmt.Errorf("message uexecutor.v1.MsgExecuteStuckInbound does not contain field %s", fd.FullName())) + } +} + +// Clear clears the field such that a subsequent Has call reports false. +// +// Clearing an extension field clears both the extension type and value +// associated with the given field number. +// +// Clear is a mutating operation and unsafe for concurrent use. +func (x *fastReflection_MsgExecuteStuckInbound) Clear(fd protoreflect.FieldDescriptor) { + switch fd.FullName() { + case "uexecutor.v1.MsgExecuteStuckInbound.signer": + x.Signer = "" + case "uexecutor.v1.MsgExecuteStuckInbound.inbound": + x.Inbound = nil + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgExecuteStuckInbound")) + } + panic(fmt.Errorf("message uexecutor.v1.MsgExecuteStuckInbound does not contain field %s", fd.FullName())) + } +} + +// Get retrieves the value for a field. +// +// For unpopulated scalars, it returns the default value, where +// the default value of a bytes scalar is guaranteed to be a copy. +// For unpopulated composite types, it returns an empty, read-only view +// of the value; to obtain a mutable reference, use Mutable. +func (x *fastReflection_MsgExecuteStuckInbound) Get(descriptor protoreflect.FieldDescriptor) protoreflect.Value { + switch descriptor.FullName() { + case "uexecutor.v1.MsgExecuteStuckInbound.signer": + value := x.Signer + return protoreflect.ValueOfString(value) + case "uexecutor.v1.MsgExecuteStuckInbound.inbound": + value := x.Inbound + return protoreflect.ValueOfMessage(value.ProtoReflect()) + default: + if descriptor.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgExecuteStuckInbound")) + } + panic(fmt.Errorf("message uexecutor.v1.MsgExecuteStuckInbound does not contain field %s", descriptor.FullName())) + } +} + +// Set stores the value for a field. +// +// For a field belonging to a oneof, it implicitly clears any other field +// that may be currently set within the same oneof. +// For extension fields, it implicitly stores the provided ExtensionType. +// When setting a composite type, it is unspecified whether the stored value +// aliases the source's memory in any way. If the composite value is an +// empty, read-only value, then it panics. +// +// Set is a mutating operation and unsafe for concurrent use. +func (x *fastReflection_MsgExecuteStuckInbound) Set(fd protoreflect.FieldDescriptor, value protoreflect.Value) { + switch fd.FullName() { + case "uexecutor.v1.MsgExecuteStuckInbound.signer": + x.Signer = value.Interface().(string) + case "uexecutor.v1.MsgExecuteStuckInbound.inbound": + x.Inbound = value.Message().Interface().(*Inbound) + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgExecuteStuckInbound")) + } + panic(fmt.Errorf("message uexecutor.v1.MsgExecuteStuckInbound does not contain field %s", fd.FullName())) + } +} + +// Mutable returns a mutable reference to a composite type. +// +// If the field is unpopulated, it may allocate a composite value. +// For a field belonging to a oneof, it implicitly clears any other field +// that may be currently set within the same oneof. +// For extension fields, it implicitly stores the provided ExtensionType +// if not already stored. +// It panics if the field does not contain a composite type. +// +// Mutable is a mutating operation and unsafe for concurrent use. +func (x *fastReflection_MsgExecuteStuckInbound) Mutable(fd protoreflect.FieldDescriptor) protoreflect.Value { + switch fd.FullName() { + case "uexecutor.v1.MsgExecuteStuckInbound.inbound": + if x.Inbound == nil { + x.Inbound = new(Inbound) + } + return protoreflect.ValueOfMessage(x.Inbound.ProtoReflect()) + case "uexecutor.v1.MsgExecuteStuckInbound.signer": + panic(fmt.Errorf("field signer of message uexecutor.v1.MsgExecuteStuckInbound is not mutable")) + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgExecuteStuckInbound")) + } + panic(fmt.Errorf("message uexecutor.v1.MsgExecuteStuckInbound does not contain field %s", fd.FullName())) + } +} + +// NewField returns a new value that is assignable to the field +// for the given descriptor. For scalars, this returns the default value. +// For lists, maps, and messages, this returns a new, empty, mutable value. +func (x *fastReflection_MsgExecuteStuckInbound) NewField(fd protoreflect.FieldDescriptor) protoreflect.Value { + switch fd.FullName() { + case "uexecutor.v1.MsgExecuteStuckInbound.signer": + return protoreflect.ValueOfString("") + case "uexecutor.v1.MsgExecuteStuckInbound.inbound": + m := new(Inbound) + return protoreflect.ValueOfMessage(m.ProtoReflect()) + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgExecuteStuckInbound")) + } + panic(fmt.Errorf("message uexecutor.v1.MsgExecuteStuckInbound does not contain field %s", fd.FullName())) + } +} + +// WhichOneof reports which field within the oneof is populated, +// returning nil if none are populated. +// It panics if the oneof descriptor does not belong to this message. +func (x *fastReflection_MsgExecuteStuckInbound) WhichOneof(d protoreflect.OneofDescriptor) protoreflect.FieldDescriptor { + switch d.FullName() { + default: + panic(fmt.Errorf("%s is not a oneof field in uexecutor.v1.MsgExecuteStuckInbound", d.FullName())) + } + panic("unreachable") +} + +// GetUnknown retrieves the entire list of unknown fields. +// The caller may only mutate the contents of the RawFields +// if the mutated bytes are stored back into the message with SetUnknown. +func (x *fastReflection_MsgExecuteStuckInbound) GetUnknown() protoreflect.RawFields { + return x.unknownFields +} + +// SetUnknown stores an entire list of unknown fields. +// The raw fields must be syntactically valid according to the wire format. +// An implementation may panic if this is not the case. +// Once stored, the caller must not mutate the content of the RawFields. +// An empty RawFields may be passed to clear the fields. +// +// SetUnknown is a mutating operation and unsafe for concurrent use. +func (x *fastReflection_MsgExecuteStuckInbound) SetUnknown(fields protoreflect.RawFields) { + x.unknownFields = fields +} + +// IsValid reports whether the message is valid. +// +// An invalid message is an empty, read-only value. +// +// An invalid message often corresponds to a nil pointer of the concrete +// message type, but the details are implementation dependent. +// Validity is not part of the protobuf data model, and may not +// be preserved in marshaling or other operations. +func (x *fastReflection_MsgExecuteStuckInbound) IsValid() bool { + return x != nil +} + +// ProtoMethods returns optional fastReflectionFeature-path implementations of various operations. +// This method may return nil. +// +// The returned methods type is identical to +// "google.golang.org/protobuf/runtime/protoiface".Methods. +// Consult the protoiface package documentation for details. +func (x *fastReflection_MsgExecuteStuckInbound) ProtoMethods() *protoiface.Methods { + size := func(input protoiface.SizeInput) protoiface.SizeOutput { + x := input.Message.Interface().(*MsgExecuteStuckInbound) + if x == nil { + return protoiface.SizeOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Size: 0, + } + } + options := runtime.SizeInputToOptions(input) + _ = options + var n int + var l int + _ = l + l = len(x.Signer) + if l > 0 { + n += 1 + l + runtime.Sov(uint64(l)) + } + if x.Inbound != nil { + l = options.Size(x.Inbound) + n += 1 + l + runtime.Sov(uint64(l)) + } + if x.unknownFields != nil { + n += len(x.unknownFields) + } + return protoiface.SizeOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Size: n, + } + } + + marshal := func(input protoiface.MarshalInput) (protoiface.MarshalOutput, error) { + x := input.Message.Interface().(*MsgExecuteStuckInbound) + if x == nil { + return protoiface.MarshalOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Buf: input.Buf, + }, nil + } + options := runtime.MarshalInputToOptions(input) + _ = options + size := options.Size(x) + dAtA := make([]byte, size) + i := len(dAtA) + _ = i + var l int + _ = l + if x.unknownFields != nil { + i -= len(x.unknownFields) + copy(dAtA[i:], x.unknownFields) + } + if x.Inbound != nil { + encoded, err := options.Marshal(x.Inbound) + if err != nil { + return protoiface.MarshalOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Buf: input.Buf, + }, err + } + i -= len(encoded) + copy(dAtA[i:], encoded) + i = runtime.EncodeVarint(dAtA, i, uint64(len(encoded))) + i-- + dAtA[i] = 0x12 + } + if len(x.Signer) > 0 { + i -= len(x.Signer) + copy(dAtA[i:], x.Signer) + i = runtime.EncodeVarint(dAtA, i, uint64(len(x.Signer))) + i-- + dAtA[i] = 0xa + } + if input.Buf != nil { + input.Buf = append(input.Buf, dAtA...) + } else { + input.Buf = dAtA + } + return protoiface.MarshalOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Buf: input.Buf, + }, nil + } + unmarshal := func(input protoiface.UnmarshalInput) (protoiface.UnmarshalOutput, error) { + x := input.Message.Interface().(*MsgExecuteStuckInbound) + if x == nil { + return protoiface.UnmarshalOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Flags: input.Flags, + }, nil + } + options := runtime.UnmarshalInputToOptions(input) + _ = options + dAtA := input.Buf + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: MsgExecuteStuckInbound: wiretype end group for non-group") + } + if fieldNum <= 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: MsgExecuteStuckInbound: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + if postIndex > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + x.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field Inbound", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + if postIndex > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + if x.Inbound == nil { + x.Inbound = &Inbound{} + } + if err := options.Unmarshal(dAtA[iNdEx:postIndex], x.Inbound); err != nil { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := runtime.Skip(dAtA[iNdEx:]) + if err != nil { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + if !options.DiscardUnknown { + x.unknownFields = append(x.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + } + iNdEx += skippy + } + } + + if iNdEx > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, nil + } + return &protoiface.Methods{ + NoUnkeyedLiterals: struct{}{}, + Flags: protoiface.SupportMarshalDeterministic | protoiface.SupportUnmarshalDiscardUnknown, + Size: size, + Marshal: marshal, + Unmarshal: unmarshal, + Merge: nil, + CheckInitialized: nil, + } +} + +var ( + md_MsgExecuteStuckInboundResponse protoreflect.MessageDescriptor + fd_MsgExecuteStuckInboundResponse_utx_id protoreflect.FieldDescriptor +) + +func init() { + file_uexecutor_v1_tx_proto_init() + md_MsgExecuteStuckInboundResponse = File_uexecutor_v1_tx_proto.Messages().ByName("MsgExecuteStuckInboundResponse") + fd_MsgExecuteStuckInboundResponse_utx_id = md_MsgExecuteStuckInboundResponse.Fields().ByName("utx_id") +} + +var _ protoreflect.Message = (*fastReflection_MsgExecuteStuckInboundResponse)(nil) + +type fastReflection_MsgExecuteStuckInboundResponse MsgExecuteStuckInboundResponse + +func (x *MsgExecuteStuckInboundResponse) ProtoReflect() protoreflect.Message { + return (*fastReflection_MsgExecuteStuckInboundResponse)(x) +} + +func (x *MsgExecuteStuckInboundResponse) slowProtoReflect() protoreflect.Message { + mi := &file_uexecutor_v1_tx_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +var _fastReflection_MsgExecuteStuckInboundResponse_messageType fastReflection_MsgExecuteStuckInboundResponse_messageType +var _ protoreflect.MessageType = fastReflection_MsgExecuteStuckInboundResponse_messageType{} + +type fastReflection_MsgExecuteStuckInboundResponse_messageType struct{} + +func (x fastReflection_MsgExecuteStuckInboundResponse_messageType) Zero() protoreflect.Message { + return (*fastReflection_MsgExecuteStuckInboundResponse)(nil) +} +func (x fastReflection_MsgExecuteStuckInboundResponse_messageType) New() protoreflect.Message { + return new(fastReflection_MsgExecuteStuckInboundResponse) +} +func (x fastReflection_MsgExecuteStuckInboundResponse_messageType) Descriptor() protoreflect.MessageDescriptor { + return md_MsgExecuteStuckInboundResponse +} + +// Descriptor returns message descriptor, which contains only the protobuf +// type information for the message. +func (x *fastReflection_MsgExecuteStuckInboundResponse) Descriptor() protoreflect.MessageDescriptor { + return md_MsgExecuteStuckInboundResponse +} + +// Type returns the message type, which encapsulates both Go and protobuf +// type information. If the Go type information is not needed, +// it is recommended that the message descriptor be used instead. +func (x *fastReflection_MsgExecuteStuckInboundResponse) Type() protoreflect.MessageType { + return _fastReflection_MsgExecuteStuckInboundResponse_messageType +} + +// New returns a newly allocated and mutable empty message. +func (x *fastReflection_MsgExecuteStuckInboundResponse) New() protoreflect.Message { + return new(fastReflection_MsgExecuteStuckInboundResponse) +} + +// Interface unwraps the message reflection interface and +// returns the underlying ProtoMessage interface. +func (x *fastReflection_MsgExecuteStuckInboundResponse) Interface() protoreflect.ProtoMessage { + return (*MsgExecuteStuckInboundResponse)(x) +} + +// Range iterates over every populated field in an undefined order, +// calling f for each field descriptor and value encountered. +// Range returns immediately if f returns false. +// While iterating, mutating operations may only be performed +// on the current field descriptor. +func (x *fastReflection_MsgExecuteStuckInboundResponse) Range(f func(protoreflect.FieldDescriptor, protoreflect.Value) bool) { + if x.UtxId != "" { + value := protoreflect.ValueOfString(x.UtxId) + if !f(fd_MsgExecuteStuckInboundResponse_utx_id, value) { + return + } + } +} + +// Has reports whether a field is populated. +// +// Some fields have the property of nullability where it is possible to +// distinguish between the default value of a field and whether the field +// was explicitly populated with the default value. Singular message fields, +// member fields of a oneof, and proto2 scalar fields are nullable. Such +// fields are populated only if explicitly set. +// +// In other cases (aside from the nullable cases above), +// a proto3 scalar field is populated if it contains a non-zero value, and +// a repeated field is populated if it is non-empty. +func (x *fastReflection_MsgExecuteStuckInboundResponse) Has(fd protoreflect.FieldDescriptor) bool { + switch fd.FullName() { + case "uexecutor.v1.MsgExecuteStuckInboundResponse.utx_id": + return x.UtxId != "" + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgExecuteStuckInboundResponse")) + } + panic(fmt.Errorf("message uexecutor.v1.MsgExecuteStuckInboundResponse does not contain field %s", fd.FullName())) + } +} + +// Clear clears the field such that a subsequent Has call reports false. +// +// Clearing an extension field clears both the extension type and value +// associated with the given field number. +// +// Clear is a mutating operation and unsafe for concurrent use. +func (x *fastReflection_MsgExecuteStuckInboundResponse) Clear(fd protoreflect.FieldDescriptor) { + switch fd.FullName() { + case "uexecutor.v1.MsgExecuteStuckInboundResponse.utx_id": + x.UtxId = "" + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgExecuteStuckInboundResponse")) + } + panic(fmt.Errorf("message uexecutor.v1.MsgExecuteStuckInboundResponse does not contain field %s", fd.FullName())) + } +} + +// Get retrieves the value for a field. +// +// For unpopulated scalars, it returns the default value, where +// the default value of a bytes scalar is guaranteed to be a copy. +// For unpopulated composite types, it returns an empty, read-only view +// of the value; to obtain a mutable reference, use Mutable. +func (x *fastReflection_MsgExecuteStuckInboundResponse) Get(descriptor protoreflect.FieldDescriptor) protoreflect.Value { + switch descriptor.FullName() { + case "uexecutor.v1.MsgExecuteStuckInboundResponse.utx_id": + value := x.UtxId + return protoreflect.ValueOfString(value) + default: + if descriptor.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgExecuteStuckInboundResponse")) + } + panic(fmt.Errorf("message uexecutor.v1.MsgExecuteStuckInboundResponse does not contain field %s", descriptor.FullName())) + } +} + +// Set stores the value for a field. +// +// For a field belonging to a oneof, it implicitly clears any other field +// that may be currently set within the same oneof. +// For extension fields, it implicitly stores the provided ExtensionType. +// When setting a composite type, it is unspecified whether the stored value +// aliases the source's memory in any way. If the composite value is an +// empty, read-only value, then it panics. +// +// Set is a mutating operation and unsafe for concurrent use. +func (x *fastReflection_MsgExecuteStuckInboundResponse) Set(fd protoreflect.FieldDescriptor, value protoreflect.Value) { + switch fd.FullName() { + case "uexecutor.v1.MsgExecuteStuckInboundResponse.utx_id": + x.UtxId = value.Interface().(string) + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgExecuteStuckInboundResponse")) + } + panic(fmt.Errorf("message uexecutor.v1.MsgExecuteStuckInboundResponse does not contain field %s", fd.FullName())) + } +} + +// Mutable returns a mutable reference to a composite type. +// +// If the field is unpopulated, it may allocate a composite value. +// For a field belonging to a oneof, it implicitly clears any other field +// that may be currently set within the same oneof. +// For extension fields, it implicitly stores the provided ExtensionType +// if not already stored. +// It panics if the field does not contain a composite type. +// +// Mutable is a mutating operation and unsafe for concurrent use. +func (x *fastReflection_MsgExecuteStuckInboundResponse) Mutable(fd protoreflect.FieldDescriptor) protoreflect.Value { + switch fd.FullName() { + case "uexecutor.v1.MsgExecuteStuckInboundResponse.utx_id": + panic(fmt.Errorf("field utx_id of message uexecutor.v1.MsgExecuteStuckInboundResponse is not mutable")) + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgExecuteStuckInboundResponse")) + } + panic(fmt.Errorf("message uexecutor.v1.MsgExecuteStuckInboundResponse does not contain field %s", fd.FullName())) + } +} + +// NewField returns a new value that is assignable to the field +// for the given descriptor. For scalars, this returns the default value. +// For lists, maps, and messages, this returns a new, empty, mutable value. +func (x *fastReflection_MsgExecuteStuckInboundResponse) NewField(fd protoreflect.FieldDescriptor) protoreflect.Value { + switch fd.FullName() { + case "uexecutor.v1.MsgExecuteStuckInboundResponse.utx_id": + return protoreflect.ValueOfString("") + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgExecuteStuckInboundResponse")) + } + panic(fmt.Errorf("message uexecutor.v1.MsgExecuteStuckInboundResponse does not contain field %s", fd.FullName())) + } +} + +// WhichOneof reports which field within the oneof is populated, +// returning nil if none are populated. +// It panics if the oneof descriptor does not belong to this message. +func (x *fastReflection_MsgExecuteStuckInboundResponse) WhichOneof(d protoreflect.OneofDescriptor) protoreflect.FieldDescriptor { + switch d.FullName() { + default: + panic(fmt.Errorf("%s is not a oneof field in uexecutor.v1.MsgExecuteStuckInboundResponse", d.FullName())) + } + panic("unreachable") +} + +// GetUnknown retrieves the entire list of unknown fields. +// The caller may only mutate the contents of the RawFields +// if the mutated bytes are stored back into the message with SetUnknown. +func (x *fastReflection_MsgExecuteStuckInboundResponse) GetUnknown() protoreflect.RawFields { + return x.unknownFields +} + +// SetUnknown stores an entire list of unknown fields. +// The raw fields must be syntactically valid according to the wire format. +// An implementation may panic if this is not the case. +// Once stored, the caller must not mutate the content of the RawFields. +// An empty RawFields may be passed to clear the fields. +// +// SetUnknown is a mutating operation and unsafe for concurrent use. +func (x *fastReflection_MsgExecuteStuckInboundResponse) SetUnknown(fields protoreflect.RawFields) { + x.unknownFields = fields +} + +// IsValid reports whether the message is valid. +// +// An invalid message is an empty, read-only value. +// +// An invalid message often corresponds to a nil pointer of the concrete +// message type, but the details are implementation dependent. +// Validity is not part of the protobuf data model, and may not +// be preserved in marshaling or other operations. +func (x *fastReflection_MsgExecuteStuckInboundResponse) IsValid() bool { + return x != nil +} + +// ProtoMethods returns optional fastReflectionFeature-path implementations of various operations. +// This method may return nil. +// +// The returned methods type is identical to +// "google.golang.org/protobuf/runtime/protoiface".Methods. +// Consult the protoiface package documentation for details. +func (x *fastReflection_MsgExecuteStuckInboundResponse) ProtoMethods() *protoiface.Methods { + size := func(input protoiface.SizeInput) protoiface.SizeOutput { + x := input.Message.Interface().(*MsgExecuteStuckInboundResponse) + if x == nil { + return protoiface.SizeOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Size: 0, + } + } + options := runtime.SizeInputToOptions(input) + _ = options + var n int + var l int + _ = l + l = len(x.UtxId) + if l > 0 { + n += 1 + l + runtime.Sov(uint64(l)) + } + if x.unknownFields != nil { + n += len(x.unknownFields) + } + return protoiface.SizeOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Size: n, + } + } + + marshal := func(input protoiface.MarshalInput) (protoiface.MarshalOutput, error) { + x := input.Message.Interface().(*MsgExecuteStuckInboundResponse) + if x == nil { + return protoiface.MarshalOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Buf: input.Buf, + }, nil + } + options := runtime.MarshalInputToOptions(input) + _ = options + size := options.Size(x) + dAtA := make([]byte, size) + i := len(dAtA) + _ = i + var l int + _ = l + if x.unknownFields != nil { + i -= len(x.unknownFields) + copy(dAtA[i:], x.unknownFields) + } + if len(x.UtxId) > 0 { + i -= len(x.UtxId) + copy(dAtA[i:], x.UtxId) + i = runtime.EncodeVarint(dAtA, i, uint64(len(x.UtxId))) + i-- + dAtA[i] = 0xa + } + if input.Buf != nil { + input.Buf = append(input.Buf, dAtA...) + } else { + input.Buf = dAtA + } + return protoiface.MarshalOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Buf: input.Buf, + }, nil + } + unmarshal := func(input protoiface.UnmarshalInput) (protoiface.UnmarshalOutput, error) { + x := input.Message.Interface().(*MsgExecuteStuckInboundResponse) + if x == nil { + return protoiface.UnmarshalOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Flags: input.Flags, + }, nil + } + options := runtime.UnmarshalInputToOptions(input) + _ = options + dAtA := input.Buf + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: MsgExecuteStuckInboundResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: MsgExecuteStuckInboundResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field UtxId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + if postIndex > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + x.UtxId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := runtime.Skip(dAtA[iNdEx:]) + if err != nil { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + if !options.DiscardUnknown { + x.unknownFields = append(x.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + } + iNdEx += skippy + } + } + + if iNdEx > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, nil + } + return &protoiface.Methods{ + NoUnkeyedLiterals: struct{}{}, + Flags: protoiface.SupportMarshalDeterministic | protoiface.SupportUnmarshalDiscardUnknown, + Size: size, + Marshal: marshal, + Unmarshal: unmarshal, + Merge: nil, + CheckInitialized: nil, + } +} + +var ( + md_MsgExecuteStuckOutbound protoreflect.MessageDescriptor + fd_MsgExecuteStuckOutbound_signer protoreflect.FieldDescriptor + fd_MsgExecuteStuckOutbound_tx_id protoreflect.FieldDescriptor + fd_MsgExecuteStuckOutbound_utx_id protoreflect.FieldDescriptor + fd_MsgExecuteStuckOutbound_observed_tx protoreflect.FieldDescriptor +) + +func init() { + file_uexecutor_v1_tx_proto_init() + md_MsgExecuteStuckOutbound = File_uexecutor_v1_tx_proto.Messages().ByName("MsgExecuteStuckOutbound") + fd_MsgExecuteStuckOutbound_signer = md_MsgExecuteStuckOutbound.Fields().ByName("signer") + fd_MsgExecuteStuckOutbound_tx_id = md_MsgExecuteStuckOutbound.Fields().ByName("tx_id") + fd_MsgExecuteStuckOutbound_utx_id = md_MsgExecuteStuckOutbound.Fields().ByName("utx_id") + fd_MsgExecuteStuckOutbound_observed_tx = md_MsgExecuteStuckOutbound.Fields().ByName("observed_tx") +} + +var _ protoreflect.Message = (*fastReflection_MsgExecuteStuckOutbound)(nil) + +type fastReflection_MsgExecuteStuckOutbound MsgExecuteStuckOutbound + +func (x *MsgExecuteStuckOutbound) ProtoReflect() protoreflect.Message { + return (*fastReflection_MsgExecuteStuckOutbound)(x) +} + +func (x *MsgExecuteStuckOutbound) slowProtoReflect() protoreflect.Message { + mi := &file_uexecutor_v1_tx_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +var _fastReflection_MsgExecuteStuckOutbound_messageType fastReflection_MsgExecuteStuckOutbound_messageType +var _ protoreflect.MessageType = fastReflection_MsgExecuteStuckOutbound_messageType{} + +type fastReflection_MsgExecuteStuckOutbound_messageType struct{} + +func (x fastReflection_MsgExecuteStuckOutbound_messageType) Zero() protoreflect.Message { + return (*fastReflection_MsgExecuteStuckOutbound)(nil) +} +func (x fastReflection_MsgExecuteStuckOutbound_messageType) New() protoreflect.Message { + return new(fastReflection_MsgExecuteStuckOutbound) +} +func (x fastReflection_MsgExecuteStuckOutbound_messageType) Descriptor() protoreflect.MessageDescriptor { + return md_MsgExecuteStuckOutbound +} + +// Descriptor returns message descriptor, which contains only the protobuf +// type information for the message. +func (x *fastReflection_MsgExecuteStuckOutbound) Descriptor() protoreflect.MessageDescriptor { + return md_MsgExecuteStuckOutbound +} + +// Type returns the message type, which encapsulates both Go and protobuf +// type information. If the Go type information is not needed, +// it is recommended that the message descriptor be used instead. +func (x *fastReflection_MsgExecuteStuckOutbound) Type() protoreflect.MessageType { + return _fastReflection_MsgExecuteStuckOutbound_messageType +} + +// New returns a newly allocated and mutable empty message. +func (x *fastReflection_MsgExecuteStuckOutbound) New() protoreflect.Message { + return new(fastReflection_MsgExecuteStuckOutbound) +} + +// Interface unwraps the message reflection interface and +// returns the underlying ProtoMessage interface. +func (x *fastReflection_MsgExecuteStuckOutbound) Interface() protoreflect.ProtoMessage { + return (*MsgExecuteStuckOutbound)(x) +} + +// Range iterates over every populated field in an undefined order, +// calling f for each field descriptor and value encountered. +// Range returns immediately if f returns false. +// While iterating, mutating operations may only be performed +// on the current field descriptor. +func (x *fastReflection_MsgExecuteStuckOutbound) Range(f func(protoreflect.FieldDescriptor, protoreflect.Value) bool) { + if x.Signer != "" { + value := protoreflect.ValueOfString(x.Signer) + if !f(fd_MsgExecuteStuckOutbound_signer, value) { + return + } + } + if x.TxId != "" { + value := protoreflect.ValueOfString(x.TxId) + if !f(fd_MsgExecuteStuckOutbound_tx_id, value) { + return + } + } + if x.UtxId != "" { + value := protoreflect.ValueOfString(x.UtxId) + if !f(fd_MsgExecuteStuckOutbound_utx_id, value) { + return + } + } + if x.ObservedTx != nil { + value := protoreflect.ValueOfMessage(x.ObservedTx.ProtoReflect()) + if !f(fd_MsgExecuteStuckOutbound_observed_tx, value) { + return + } + } +} + +// Has reports whether a field is populated. +// +// Some fields have the property of nullability where it is possible to +// distinguish between the default value of a field and whether the field +// was explicitly populated with the default value. Singular message fields, +// member fields of a oneof, and proto2 scalar fields are nullable. Such +// fields are populated only if explicitly set. +// +// In other cases (aside from the nullable cases above), +// a proto3 scalar field is populated if it contains a non-zero value, and +// a repeated field is populated if it is non-empty. +func (x *fastReflection_MsgExecuteStuckOutbound) Has(fd protoreflect.FieldDescriptor) bool { + switch fd.FullName() { + case "uexecutor.v1.MsgExecuteStuckOutbound.signer": + return x.Signer != "" + case "uexecutor.v1.MsgExecuteStuckOutbound.tx_id": + return x.TxId != "" + case "uexecutor.v1.MsgExecuteStuckOutbound.utx_id": + return x.UtxId != "" + case "uexecutor.v1.MsgExecuteStuckOutbound.observed_tx": + return x.ObservedTx != nil + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgExecuteStuckOutbound")) + } + panic(fmt.Errorf("message uexecutor.v1.MsgExecuteStuckOutbound does not contain field %s", fd.FullName())) + } +} + +// Clear clears the field such that a subsequent Has call reports false. +// +// Clearing an extension field clears both the extension type and value +// associated with the given field number. +// +// Clear is a mutating operation and unsafe for concurrent use. +func (x *fastReflection_MsgExecuteStuckOutbound) Clear(fd protoreflect.FieldDescriptor) { + switch fd.FullName() { + case "uexecutor.v1.MsgExecuteStuckOutbound.signer": + x.Signer = "" + case "uexecutor.v1.MsgExecuteStuckOutbound.tx_id": + x.TxId = "" + case "uexecutor.v1.MsgExecuteStuckOutbound.utx_id": + x.UtxId = "" + case "uexecutor.v1.MsgExecuteStuckOutbound.observed_tx": + x.ObservedTx = nil + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgExecuteStuckOutbound")) + } + panic(fmt.Errorf("message uexecutor.v1.MsgExecuteStuckOutbound does not contain field %s", fd.FullName())) + } +} + +// Get retrieves the value for a field. +// +// For unpopulated scalars, it returns the default value, where +// the default value of a bytes scalar is guaranteed to be a copy. +// For unpopulated composite types, it returns an empty, read-only view +// of the value; to obtain a mutable reference, use Mutable. +func (x *fastReflection_MsgExecuteStuckOutbound) Get(descriptor protoreflect.FieldDescriptor) protoreflect.Value { + switch descriptor.FullName() { + case "uexecutor.v1.MsgExecuteStuckOutbound.signer": + value := x.Signer + return protoreflect.ValueOfString(value) + case "uexecutor.v1.MsgExecuteStuckOutbound.tx_id": + value := x.TxId + return protoreflect.ValueOfString(value) + case "uexecutor.v1.MsgExecuteStuckOutbound.utx_id": + value := x.UtxId + return protoreflect.ValueOfString(value) + case "uexecutor.v1.MsgExecuteStuckOutbound.observed_tx": + value := x.ObservedTx + return protoreflect.ValueOfMessage(value.ProtoReflect()) + default: + if descriptor.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgExecuteStuckOutbound")) + } + panic(fmt.Errorf("message uexecutor.v1.MsgExecuteStuckOutbound does not contain field %s", descriptor.FullName())) + } +} + +// Set stores the value for a field. +// +// For a field belonging to a oneof, it implicitly clears any other field +// that may be currently set within the same oneof. +// For extension fields, it implicitly stores the provided ExtensionType. +// When setting a composite type, it is unspecified whether the stored value +// aliases the source's memory in any way. If the composite value is an +// empty, read-only value, then it panics. +// +// Set is a mutating operation and unsafe for concurrent use. +func (x *fastReflection_MsgExecuteStuckOutbound) Set(fd protoreflect.FieldDescriptor, value protoreflect.Value) { + switch fd.FullName() { + case "uexecutor.v1.MsgExecuteStuckOutbound.signer": + x.Signer = value.Interface().(string) + case "uexecutor.v1.MsgExecuteStuckOutbound.tx_id": + x.TxId = value.Interface().(string) + case "uexecutor.v1.MsgExecuteStuckOutbound.utx_id": + x.UtxId = value.Interface().(string) + case "uexecutor.v1.MsgExecuteStuckOutbound.observed_tx": + x.ObservedTx = value.Message().Interface().(*OutboundObservation) + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgExecuteStuckOutbound")) + } + panic(fmt.Errorf("message uexecutor.v1.MsgExecuteStuckOutbound does not contain field %s", fd.FullName())) + } +} + +// Mutable returns a mutable reference to a composite type. +// +// If the field is unpopulated, it may allocate a composite value. +// For a field belonging to a oneof, it implicitly clears any other field +// that may be currently set within the same oneof. +// For extension fields, it implicitly stores the provided ExtensionType +// if not already stored. +// It panics if the field does not contain a composite type. +// +// Mutable is a mutating operation and unsafe for concurrent use. +func (x *fastReflection_MsgExecuteStuckOutbound) Mutable(fd protoreflect.FieldDescriptor) protoreflect.Value { + switch fd.FullName() { + case "uexecutor.v1.MsgExecuteStuckOutbound.observed_tx": + if x.ObservedTx == nil { + x.ObservedTx = new(OutboundObservation) + } + return protoreflect.ValueOfMessage(x.ObservedTx.ProtoReflect()) + case "uexecutor.v1.MsgExecuteStuckOutbound.signer": + panic(fmt.Errorf("field signer of message uexecutor.v1.MsgExecuteStuckOutbound is not mutable")) + case "uexecutor.v1.MsgExecuteStuckOutbound.tx_id": + panic(fmt.Errorf("field tx_id of message uexecutor.v1.MsgExecuteStuckOutbound is not mutable")) + case "uexecutor.v1.MsgExecuteStuckOutbound.utx_id": + panic(fmt.Errorf("field utx_id of message uexecutor.v1.MsgExecuteStuckOutbound is not mutable")) + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgExecuteStuckOutbound")) + } + panic(fmt.Errorf("message uexecutor.v1.MsgExecuteStuckOutbound does not contain field %s", fd.FullName())) + } +} + +// NewField returns a new value that is assignable to the field +// for the given descriptor. For scalars, this returns the default value. +// For lists, maps, and messages, this returns a new, empty, mutable value. +func (x *fastReflection_MsgExecuteStuckOutbound) NewField(fd protoreflect.FieldDescriptor) protoreflect.Value { + switch fd.FullName() { + case "uexecutor.v1.MsgExecuteStuckOutbound.signer": + return protoreflect.ValueOfString("") + case "uexecutor.v1.MsgExecuteStuckOutbound.tx_id": + return protoreflect.ValueOfString("") + case "uexecutor.v1.MsgExecuteStuckOutbound.utx_id": + return protoreflect.ValueOfString("") + case "uexecutor.v1.MsgExecuteStuckOutbound.observed_tx": + m := new(OutboundObservation) + return protoreflect.ValueOfMessage(m.ProtoReflect()) + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgExecuteStuckOutbound")) + } + panic(fmt.Errorf("message uexecutor.v1.MsgExecuteStuckOutbound does not contain field %s", fd.FullName())) + } +} + +// WhichOneof reports which field within the oneof is populated, +// returning nil if none are populated. +// It panics if the oneof descriptor does not belong to this message. +func (x *fastReflection_MsgExecuteStuckOutbound) WhichOneof(d protoreflect.OneofDescriptor) protoreflect.FieldDescriptor { + switch d.FullName() { + default: + panic(fmt.Errorf("%s is not a oneof field in uexecutor.v1.MsgExecuteStuckOutbound", d.FullName())) + } + panic("unreachable") +} + +// GetUnknown retrieves the entire list of unknown fields. +// The caller may only mutate the contents of the RawFields +// if the mutated bytes are stored back into the message with SetUnknown. +func (x *fastReflection_MsgExecuteStuckOutbound) GetUnknown() protoreflect.RawFields { + return x.unknownFields +} + +// SetUnknown stores an entire list of unknown fields. +// The raw fields must be syntactically valid according to the wire format. +// An implementation may panic if this is not the case. +// Once stored, the caller must not mutate the content of the RawFields. +// An empty RawFields may be passed to clear the fields. +// +// SetUnknown is a mutating operation and unsafe for concurrent use. +func (x *fastReflection_MsgExecuteStuckOutbound) SetUnknown(fields protoreflect.RawFields) { + x.unknownFields = fields +} + +// IsValid reports whether the message is valid. +// +// An invalid message is an empty, read-only value. +// +// An invalid message often corresponds to a nil pointer of the concrete +// message type, but the details are implementation dependent. +// Validity is not part of the protobuf data model, and may not +// be preserved in marshaling or other operations. +func (x *fastReflection_MsgExecuteStuckOutbound) IsValid() bool { + return x != nil +} + +// ProtoMethods returns optional fastReflectionFeature-path implementations of various operations. +// This method may return nil. +// +// The returned methods type is identical to +// "google.golang.org/protobuf/runtime/protoiface".Methods. +// Consult the protoiface package documentation for details. +func (x *fastReflection_MsgExecuteStuckOutbound) ProtoMethods() *protoiface.Methods { + size := func(input protoiface.SizeInput) protoiface.SizeOutput { + x := input.Message.Interface().(*MsgExecuteStuckOutbound) + if x == nil { + return protoiface.SizeOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Size: 0, + } + } + options := runtime.SizeInputToOptions(input) + _ = options + var n int + var l int + _ = l + l = len(x.Signer) + if l > 0 { + n += 1 + l + runtime.Sov(uint64(l)) + } + l = len(x.TxId) + if l > 0 { + n += 1 + l + runtime.Sov(uint64(l)) + } + l = len(x.UtxId) + if l > 0 { + n += 1 + l + runtime.Sov(uint64(l)) + } + if x.ObservedTx != nil { + l = options.Size(x.ObservedTx) + n += 1 + l + runtime.Sov(uint64(l)) + } + if x.unknownFields != nil { + n += len(x.unknownFields) + } + return protoiface.SizeOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Size: n, + } + } + + marshal := func(input protoiface.MarshalInput) (protoiface.MarshalOutput, error) { + x := input.Message.Interface().(*MsgExecuteStuckOutbound) + if x == nil { + return protoiface.MarshalOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Buf: input.Buf, + }, nil + } + options := runtime.MarshalInputToOptions(input) + _ = options + size := options.Size(x) + dAtA := make([]byte, size) + i := len(dAtA) + _ = i + var l int + _ = l + if x.unknownFields != nil { + i -= len(x.unknownFields) + copy(dAtA[i:], x.unknownFields) + } + if x.ObservedTx != nil { + encoded, err := options.Marshal(x.ObservedTx) + if err != nil { + return protoiface.MarshalOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Buf: input.Buf, + }, err + } + i -= len(encoded) + copy(dAtA[i:], encoded) + i = runtime.EncodeVarint(dAtA, i, uint64(len(encoded))) + i-- + dAtA[i] = 0x22 + } + if len(x.UtxId) > 0 { + i -= len(x.UtxId) + copy(dAtA[i:], x.UtxId) + i = runtime.EncodeVarint(dAtA, i, uint64(len(x.UtxId))) + i-- + dAtA[i] = 0x1a + } + if len(x.TxId) > 0 { + i -= len(x.TxId) + copy(dAtA[i:], x.TxId) + i = runtime.EncodeVarint(dAtA, i, uint64(len(x.TxId))) + i-- + dAtA[i] = 0x12 + } + if len(x.Signer) > 0 { + i -= len(x.Signer) + copy(dAtA[i:], x.Signer) + i = runtime.EncodeVarint(dAtA, i, uint64(len(x.Signer))) + i-- + dAtA[i] = 0xa + } + if input.Buf != nil { + input.Buf = append(input.Buf, dAtA...) + } else { + input.Buf = dAtA + } + return protoiface.MarshalOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Buf: input.Buf, + }, nil + } + unmarshal := func(input protoiface.UnmarshalInput) (protoiface.UnmarshalOutput, error) { + x := input.Message.Interface().(*MsgExecuteStuckOutbound) + if x == nil { + return protoiface.UnmarshalOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Flags: input.Flags, + }, nil + } + options := runtime.UnmarshalInputToOptions(input) + _ = options + dAtA := input.Buf + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: MsgExecuteStuckOutbound: wiretype end group for non-group") + } + if fieldNum <= 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: MsgExecuteStuckOutbound: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + if postIndex > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + x.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field TxId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + if postIndex > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + x.TxId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field UtxId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + if postIndex > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + x.UtxId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field ObservedTx", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + if postIndex > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + if x.ObservedTx == nil { + x.ObservedTx = &OutboundObservation{} + } + if err := options.Unmarshal(dAtA[iNdEx:postIndex], x.ObservedTx); err != nil { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := runtime.Skip(dAtA[iNdEx:]) + if err != nil { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + if !options.DiscardUnknown { + x.unknownFields = append(x.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + } + iNdEx += skippy + } + } + + if iNdEx > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, nil + } + return &protoiface.Methods{ + NoUnkeyedLiterals: struct{}{}, + Flags: protoiface.SupportMarshalDeterministic | protoiface.SupportUnmarshalDiscardUnknown, + Size: size, + Marshal: marshal, + Unmarshal: unmarshal, + Merge: nil, + CheckInitialized: nil, + } +} + +var ( + md_MsgExecuteStuckOutboundResponse protoreflect.MessageDescriptor + fd_MsgExecuteStuckOutboundResponse_outbound_id protoreflect.FieldDescriptor +) + +func init() { + file_uexecutor_v1_tx_proto_init() + md_MsgExecuteStuckOutboundResponse = File_uexecutor_v1_tx_proto.Messages().ByName("MsgExecuteStuckOutboundResponse") + fd_MsgExecuteStuckOutboundResponse_outbound_id = md_MsgExecuteStuckOutboundResponse.Fields().ByName("outbound_id") +} + +var _ protoreflect.Message = (*fastReflection_MsgExecuteStuckOutboundResponse)(nil) + +type fastReflection_MsgExecuteStuckOutboundResponse MsgExecuteStuckOutboundResponse + +func (x *MsgExecuteStuckOutboundResponse) ProtoReflect() protoreflect.Message { + return (*fastReflection_MsgExecuteStuckOutboundResponse)(x) +} + +func (x *MsgExecuteStuckOutboundResponse) slowProtoReflect() protoreflect.Message { + mi := &file_uexecutor_v1_tx_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +var _fastReflection_MsgExecuteStuckOutboundResponse_messageType fastReflection_MsgExecuteStuckOutboundResponse_messageType +var _ protoreflect.MessageType = fastReflection_MsgExecuteStuckOutboundResponse_messageType{} + +type fastReflection_MsgExecuteStuckOutboundResponse_messageType struct{} + +func (x fastReflection_MsgExecuteStuckOutboundResponse_messageType) Zero() protoreflect.Message { + return (*fastReflection_MsgExecuteStuckOutboundResponse)(nil) +} +func (x fastReflection_MsgExecuteStuckOutboundResponse_messageType) New() protoreflect.Message { + return new(fastReflection_MsgExecuteStuckOutboundResponse) +} +func (x fastReflection_MsgExecuteStuckOutboundResponse_messageType) Descriptor() protoreflect.MessageDescriptor { + return md_MsgExecuteStuckOutboundResponse +} + +// Descriptor returns message descriptor, which contains only the protobuf +// type information for the message. +func (x *fastReflection_MsgExecuteStuckOutboundResponse) Descriptor() protoreflect.MessageDescriptor { + return md_MsgExecuteStuckOutboundResponse +} + +// Type returns the message type, which encapsulates both Go and protobuf +// type information. If the Go type information is not needed, +// it is recommended that the message descriptor be used instead. +func (x *fastReflection_MsgExecuteStuckOutboundResponse) Type() protoreflect.MessageType { + return _fastReflection_MsgExecuteStuckOutboundResponse_messageType +} + +// New returns a newly allocated and mutable empty message. +func (x *fastReflection_MsgExecuteStuckOutboundResponse) New() protoreflect.Message { + return new(fastReflection_MsgExecuteStuckOutboundResponse) +} + +// Interface unwraps the message reflection interface and +// returns the underlying ProtoMessage interface. +func (x *fastReflection_MsgExecuteStuckOutboundResponse) Interface() protoreflect.ProtoMessage { + return (*MsgExecuteStuckOutboundResponse)(x) +} + +// Range iterates over every populated field in an undefined order, +// calling f for each field descriptor and value encountered. +// Range returns immediately if f returns false. +// While iterating, mutating operations may only be performed +// on the current field descriptor. +func (x *fastReflection_MsgExecuteStuckOutboundResponse) Range(f func(protoreflect.FieldDescriptor, protoreflect.Value) bool) { + if x.OutboundId != "" { + value := protoreflect.ValueOfString(x.OutboundId) + if !f(fd_MsgExecuteStuckOutboundResponse_outbound_id, value) { + return + } + } +} + +// Has reports whether a field is populated. +// +// Some fields have the property of nullability where it is possible to +// distinguish between the default value of a field and whether the field +// was explicitly populated with the default value. Singular message fields, +// member fields of a oneof, and proto2 scalar fields are nullable. Such +// fields are populated only if explicitly set. +// +// In other cases (aside from the nullable cases above), +// a proto3 scalar field is populated if it contains a non-zero value, and +// a repeated field is populated if it is non-empty. +func (x *fastReflection_MsgExecuteStuckOutboundResponse) Has(fd protoreflect.FieldDescriptor) bool { + switch fd.FullName() { + case "uexecutor.v1.MsgExecuteStuckOutboundResponse.outbound_id": + return x.OutboundId != "" + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgExecuteStuckOutboundResponse")) + } + panic(fmt.Errorf("message uexecutor.v1.MsgExecuteStuckOutboundResponse does not contain field %s", fd.FullName())) + } +} + +// Clear clears the field such that a subsequent Has call reports false. +// +// Clearing an extension field clears both the extension type and value +// associated with the given field number. +// +// Clear is a mutating operation and unsafe for concurrent use. +func (x *fastReflection_MsgExecuteStuckOutboundResponse) Clear(fd protoreflect.FieldDescriptor) { + switch fd.FullName() { + case "uexecutor.v1.MsgExecuteStuckOutboundResponse.outbound_id": + x.OutboundId = "" + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgExecuteStuckOutboundResponse")) + } + panic(fmt.Errorf("message uexecutor.v1.MsgExecuteStuckOutboundResponse does not contain field %s", fd.FullName())) + } +} + +// Get retrieves the value for a field. +// +// For unpopulated scalars, it returns the default value, where +// the default value of a bytes scalar is guaranteed to be a copy. +// For unpopulated composite types, it returns an empty, read-only view +// of the value; to obtain a mutable reference, use Mutable. +func (x *fastReflection_MsgExecuteStuckOutboundResponse) Get(descriptor protoreflect.FieldDescriptor) protoreflect.Value { + switch descriptor.FullName() { + case "uexecutor.v1.MsgExecuteStuckOutboundResponse.outbound_id": + value := x.OutboundId + return protoreflect.ValueOfString(value) + default: + if descriptor.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgExecuteStuckOutboundResponse")) + } + panic(fmt.Errorf("message uexecutor.v1.MsgExecuteStuckOutboundResponse does not contain field %s", descriptor.FullName())) + } +} + +// Set stores the value for a field. +// +// For a field belonging to a oneof, it implicitly clears any other field +// that may be currently set within the same oneof. +// For extension fields, it implicitly stores the provided ExtensionType. +// When setting a composite type, it is unspecified whether the stored value +// aliases the source's memory in any way. If the composite value is an +// empty, read-only value, then it panics. +// +// Set is a mutating operation and unsafe for concurrent use. +func (x *fastReflection_MsgExecuteStuckOutboundResponse) Set(fd protoreflect.FieldDescriptor, value protoreflect.Value) { + switch fd.FullName() { + case "uexecutor.v1.MsgExecuteStuckOutboundResponse.outbound_id": + x.OutboundId = value.Interface().(string) + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgExecuteStuckOutboundResponse")) + } + panic(fmt.Errorf("message uexecutor.v1.MsgExecuteStuckOutboundResponse does not contain field %s", fd.FullName())) + } +} + +// Mutable returns a mutable reference to a composite type. +// +// If the field is unpopulated, it may allocate a composite value. +// For a field belonging to a oneof, it implicitly clears any other field +// that may be currently set within the same oneof. +// For extension fields, it implicitly stores the provided ExtensionType +// if not already stored. +// It panics if the field does not contain a composite type. +// +// Mutable is a mutating operation and unsafe for concurrent use. +func (x *fastReflection_MsgExecuteStuckOutboundResponse) Mutable(fd protoreflect.FieldDescriptor) protoreflect.Value { + switch fd.FullName() { + case "uexecutor.v1.MsgExecuteStuckOutboundResponse.outbound_id": + panic(fmt.Errorf("field outbound_id of message uexecutor.v1.MsgExecuteStuckOutboundResponse is not mutable")) + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgExecuteStuckOutboundResponse")) + } + panic(fmt.Errorf("message uexecutor.v1.MsgExecuteStuckOutboundResponse does not contain field %s", fd.FullName())) + } +} + +// NewField returns a new value that is assignable to the field +// for the given descriptor. For scalars, this returns the default value. +// For lists, maps, and messages, this returns a new, empty, mutable value. +func (x *fastReflection_MsgExecuteStuckOutboundResponse) NewField(fd protoreflect.FieldDescriptor) protoreflect.Value { + switch fd.FullName() { + case "uexecutor.v1.MsgExecuteStuckOutboundResponse.outbound_id": + return protoreflect.ValueOfString("") + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: uexecutor.v1.MsgExecuteStuckOutboundResponse")) + } + panic(fmt.Errorf("message uexecutor.v1.MsgExecuteStuckOutboundResponse does not contain field %s", fd.FullName())) + } +} + +// WhichOneof reports which field within the oneof is populated, +// returning nil if none are populated. +// It panics if the oneof descriptor does not belong to this message. +func (x *fastReflection_MsgExecuteStuckOutboundResponse) WhichOneof(d protoreflect.OneofDescriptor) protoreflect.FieldDescriptor { + switch d.FullName() { + default: + panic(fmt.Errorf("%s is not a oneof field in uexecutor.v1.MsgExecuteStuckOutboundResponse", d.FullName())) + } + panic("unreachable") +} + +// GetUnknown retrieves the entire list of unknown fields. +// The caller may only mutate the contents of the RawFields +// if the mutated bytes are stored back into the message with SetUnknown. +func (x *fastReflection_MsgExecuteStuckOutboundResponse) GetUnknown() protoreflect.RawFields { + return x.unknownFields +} + +// SetUnknown stores an entire list of unknown fields. +// The raw fields must be syntactically valid according to the wire format. +// An implementation may panic if this is not the case. +// Once stored, the caller must not mutate the content of the RawFields. +// An empty RawFields may be passed to clear the fields. +// +// SetUnknown is a mutating operation and unsafe for concurrent use. +func (x *fastReflection_MsgExecuteStuckOutboundResponse) SetUnknown(fields protoreflect.RawFields) { + x.unknownFields = fields +} + +// IsValid reports whether the message is valid. +// +// An invalid message is an empty, read-only value. +// +// An invalid message often corresponds to a nil pointer of the concrete +// message type, but the details are implementation dependent. +// Validity is not part of the protobuf data model, and may not +// be preserved in marshaling or other operations. +func (x *fastReflection_MsgExecuteStuckOutboundResponse) IsValid() bool { + return x != nil +} + +// ProtoMethods returns optional fastReflectionFeature-path implementations of various operations. +// This method may return nil. +// +// The returned methods type is identical to +// "google.golang.org/protobuf/runtime/protoiface".Methods. +// Consult the protoiface package documentation for details. +func (x *fastReflection_MsgExecuteStuckOutboundResponse) ProtoMethods() *protoiface.Methods { + size := func(input protoiface.SizeInput) protoiface.SizeOutput { + x := input.Message.Interface().(*MsgExecuteStuckOutboundResponse) + if x == nil { + return protoiface.SizeOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Size: 0, + } + } + options := runtime.SizeInputToOptions(input) + _ = options + var n int + var l int + _ = l + l = len(x.OutboundId) + if l > 0 { + n += 1 + l + runtime.Sov(uint64(l)) + } + if x.unknownFields != nil { + n += len(x.unknownFields) + } + return protoiface.SizeOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Size: n, + } + } + + marshal := func(input protoiface.MarshalInput) (protoiface.MarshalOutput, error) { + x := input.Message.Interface().(*MsgExecuteStuckOutboundResponse) + if x == nil { + return protoiface.MarshalOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Buf: input.Buf, + }, nil + } + options := runtime.MarshalInputToOptions(input) + _ = options + size := options.Size(x) + dAtA := make([]byte, size) + i := len(dAtA) + _ = i + var l int + _ = l + if x.unknownFields != nil { + i -= len(x.unknownFields) + copy(dAtA[i:], x.unknownFields) + } + if len(x.OutboundId) > 0 { + i -= len(x.OutboundId) + copy(dAtA[i:], x.OutboundId) + i = runtime.EncodeVarint(dAtA, i, uint64(len(x.OutboundId))) + i-- + dAtA[i] = 0xa + } + if input.Buf != nil { + input.Buf = append(input.Buf, dAtA...) + } else { + input.Buf = dAtA + } + return protoiface.MarshalOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Buf: input.Buf, + }, nil + } + unmarshal := func(input protoiface.UnmarshalInput) (protoiface.UnmarshalOutput, error) { + x := input.Message.Interface().(*MsgExecuteStuckOutboundResponse) + if x == nil { + return protoiface.UnmarshalOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Flags: input.Flags, + }, nil + } + options := runtime.UnmarshalInputToOptions(input) + _ = options + dAtA := input.Buf + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: MsgExecuteStuckOutboundResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: MsgExecuteStuckOutboundResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field OutboundId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + if postIndex > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + x.OutboundId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := runtime.Skip(dAtA[iNdEx:]) + if err != nil { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + if !options.DiscardUnknown { + x.unknownFields = append(x.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + } + iNdEx += skippy + } + } + + if iNdEx > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, nil + } + return &protoiface.Methods{ + NoUnkeyedLiterals: struct{}{}, + Flags: protoiface.SupportMarshalDeterministic | protoiface.SupportUnmarshalDiscardUnknown, + Size: size, + Marshal: marshal, + Unmarshal: unmarshal, + Merge: nil, + CheckInitialized: nil, + } +} + // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.0 @@ -6150,6 +8116,199 @@ func (x *MsgRevertStuckInboundResponse) GetOutboundId() string { return "" } +// MsgExecuteStuckInbound is an admin escape hatch and the sibling of +// MsgRevertStuckInbound. For an inbound whose ballot is stored PENDING but can +// never finalize on its own, and whose YES votes already meet the recomputed +// threshold, this marks the ballot PASSED and runs the same post-finalization +// pipeline a finalizing vote would have run - so the user receives the bridged +// funds on Push instead of a source-chain refund. +type MsgExecuteStuckInbound struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // signer must equal uvalidator Params.Admin + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + // inbound is the original payload the stuck ballot was voting on. Admin + // supplies this from off-chain UV observation logs since the chain does not + // persist ballot payloads. + Inbound *Inbound `protobuf:"bytes,2,opt,name=inbound,proto3" json:"inbound,omitempty"` +} + +func (x *MsgExecuteStuckInbound) Reset() { + *x = MsgExecuteStuckInbound{} + if protoimpl.UnsafeEnabled { + mi := &file_uexecutor_v1_tx_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MsgExecuteStuckInbound) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MsgExecuteStuckInbound) ProtoMessage() {} + +// Deprecated: Use MsgExecuteStuckInbound.ProtoReflect.Descriptor instead. +func (*MsgExecuteStuckInbound) Descriptor() ([]byte, []int) { + return file_uexecutor_v1_tx_proto_rawDescGZIP(), []int{12} +} + +func (x *MsgExecuteStuckInbound) GetSigner() string { + if x != nil { + return x.Signer + } + return "" +} + +func (x *MsgExecuteStuckInbound) GetInbound() *Inbound { + if x != nil { + return x.Inbound + } + return nil +} + +type MsgExecuteStuckInboundResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UtxId string `protobuf:"bytes,1,opt,name=utx_id,json=utxId,proto3" json:"utx_id,omitempty"` // ID of the UTX created for the executed inbound +} + +func (x *MsgExecuteStuckInboundResponse) Reset() { + *x = MsgExecuteStuckInboundResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_uexecutor_v1_tx_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MsgExecuteStuckInboundResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MsgExecuteStuckInboundResponse) ProtoMessage() {} + +// Deprecated: Use MsgExecuteStuckInboundResponse.ProtoReflect.Descriptor instead. +func (*MsgExecuteStuckInboundResponse) Descriptor() ([]byte, []int) { + return file_uexecutor_v1_tx_proto_rawDescGZIP(), []int{13} +} + +func (x *MsgExecuteStuckInboundResponse) GetUtxId() string { + if x != nil { + return x.UtxId + } + return "" +} + +// MsgExecuteStuckOutbound is an admin escape hatch for an outbound whose ballot +// can no longer reach a terminal-and-settled state — EXPIRED, or PENDING with +// every eligible voter already voted and the YES votes at the stored threshold. +// It runs the same settlement pipeline a finalizing vote would have run, so the +// outcome follows observed_tx.success: a success settles, a failure mints the +// bridged tokens back to the revert recipient and refunds the excess gas. +type MsgExecuteStuckOutbound struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // signer must equal uvalidator Params.Admin + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + TxId string `protobuf:"bytes,2,opt,name=tx_id,json=txId,proto3" json:"tx_id,omitempty"` // txId of outbound tx + UtxId string `protobuf:"bytes,3,opt,name=utx_id,json=utxId,proto3" json:"utx_id,omitempty"` // UniversalTx Id + // observed_tx is the destination-chain observation the stuck ballot was voting + // on. Admin supplies this from off-chain UV observation logs since the chain + // does not persist ballot payloads; it must match field-for-field or the + // derived ballot key finds no ballot. + ObservedTx *OutboundObservation `protobuf:"bytes,4,opt,name=observed_tx,json=observedTx,proto3" json:"observed_tx,omitempty"` +} + +func (x *MsgExecuteStuckOutbound) Reset() { + *x = MsgExecuteStuckOutbound{} + if protoimpl.UnsafeEnabled { + mi := &file_uexecutor_v1_tx_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MsgExecuteStuckOutbound) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MsgExecuteStuckOutbound) ProtoMessage() {} + +// Deprecated: Use MsgExecuteStuckOutbound.ProtoReflect.Descriptor instead. +func (*MsgExecuteStuckOutbound) Descriptor() ([]byte, []int) { + return file_uexecutor_v1_tx_proto_rawDescGZIP(), []int{14} +} + +func (x *MsgExecuteStuckOutbound) GetSigner() string { + if x != nil { + return x.Signer + } + return "" +} + +func (x *MsgExecuteStuckOutbound) GetTxId() string { + if x != nil { + return x.TxId + } + return "" +} + +func (x *MsgExecuteStuckOutbound) GetUtxId() string { + if x != nil { + return x.UtxId + } + return "" +} + +func (x *MsgExecuteStuckOutbound) GetObservedTx() *OutboundObservation { + if x != nil { + return x.ObservedTx + } + return nil +} + +type MsgExecuteStuckOutboundResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + OutboundId string `protobuf:"bytes,1,opt,name=outbound_id,json=outboundId,proto3" json:"outbound_id,omitempty"` // ID of the outbound that was settled +} + +func (x *MsgExecuteStuckOutboundResponse) Reset() { + *x = MsgExecuteStuckOutboundResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_uexecutor_v1_tx_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MsgExecuteStuckOutboundResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MsgExecuteStuckOutboundResponse) ProtoMessage() {} + +// Deprecated: Use MsgExecuteStuckOutboundResponse.ProtoReflect.Descriptor instead. +func (*MsgExecuteStuckOutboundResponse) Descriptor() ([]byte, []int) { + return file_uexecutor_v1_tx_proto_rawDescGZIP(), []int{15} +} + +func (x *MsgExecuteStuckOutboundResponse) GetOutboundId() string { + if x != nil { + return x.OutboundId + } + return "" +} + var File_uexecutor_v1_tx_proto protoreflect.FileDescriptor var file_uexecutor_v1_tx_proto_rawDesc = []byte{ @@ -6257,54 +8416,101 @@ var file_uexecutor_v1_tx_proto_rawDesc = []byte{ 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x75, 0x74, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x75, 0x74, 0x78, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x49, 0x64, 0x32, 0xa8, - 0x04, 0x0a, 0x03, 0x4d, 0x73, 0x67, 0x12, 0x54, 0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x1d, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, - 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, - 0x61, 0x72, 0x61, 0x6d, 0x73, 0x1a, 0x25, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, - 0x72, 0x61, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0e, - 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x1f, - 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, - 0x67, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x1a, - 0x27, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, - 0x73, 0x67, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51, 0x0a, 0x0b, 0x56, 0x6f, 0x74, 0x65, - 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x1c, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, - 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x49, 0x6e, - 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x1a, 0x24, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, + 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x49, 0x64, 0x22, 0xad, + 0x01, 0x0a, 0x16, 0x4d, 0x73, 0x67, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x53, 0x74, 0x75, + 0x63, 0x6b, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x30, 0x0a, 0x06, 0x73, 0x69, 0x67, + 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, + 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, + 0x69, 0x6e, 0x67, 0x52, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x12, 0x2f, 0x0a, 0x07, 0x69, + 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x75, + 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x62, 0x6f, + 0x75, 0x6e, 0x64, 0x52, 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x30, 0x82, 0xe7, + 0xb0, 0x2a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x8a, 0xe7, 0xb0, 0x2a, 0x20, 0x75, 0x65, + 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x4d, 0x73, 0x67, 0x45, 0x78, 0x65, 0x63, 0x75, + 0x74, 0x65, 0x53, 0x74, 0x75, 0x63, 0x6b, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x37, + 0x0a, 0x1e, 0x4d, 0x73, 0x67, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x53, 0x74, 0x75, 0x63, + 0x6b, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x15, 0x0a, 0x06, 0x75, 0x74, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x75, 0x74, 0x78, 0x49, 0x64, 0x22, 0xee, 0x01, 0x0a, 0x17, 0x4d, 0x73, 0x67, 0x45, + 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x53, 0x74, 0x75, 0x63, 0x6b, 0x4f, 0x75, 0x74, 0x62, 0x6f, + 0x75, 0x6e, 0x64, 0x12, 0x30, 0x0a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, + 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x06, 0x73, + 0x69, 0x67, 0x6e, 0x65, 0x72, 0x12, 0x13, 0x0a, 0x05, 0x74, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x75, 0x74, + 0x78, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x75, 0x74, 0x78, 0x49, + 0x64, 0x12, 0x42, 0x0a, 0x0b, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x5f, 0x74, 0x78, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, + 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x4f, 0x62, + 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x6f, 0x62, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x64, 0x54, 0x78, 0x3a, 0x31, 0x82, 0xe7, 0xb0, 0x2a, 0x06, 0x73, 0x69, 0x67, 0x6e, + 0x65, 0x72, 0x8a, 0xe7, 0xb0, 0x2a, 0x21, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, + 0x2f, 0x4d, 0x73, 0x67, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x53, 0x74, 0x75, 0x63, 0x6b, + 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x42, 0x0a, 0x1f, 0x4d, 0x73, 0x67, 0x45, + 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x53, 0x74, 0x75, 0x63, 0x6b, 0x4f, 0x75, 0x74, 0x62, 0x6f, + 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, + 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x49, 0x64, 0x32, 0x81, 0x06, 0x0a, + 0x03, 0x4d, 0x73, 0x67, 0x12, 0x54, 0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, + 0x72, 0x61, 0x6d, 0x73, 0x12, 0x1d, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, 0x72, + 0x61, 0x6d, 0x73, 0x1a, 0x25, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, 0x72, 0x61, + 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0e, 0x45, 0x78, + 0x65, 0x63, 0x75, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x1f, 0x2e, 0x75, + 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x45, + 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x1a, 0x27, 0x2e, + 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, + 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51, 0x0a, 0x0b, 0x56, 0x6f, 0x74, 0x65, 0x49, 0x6e, + 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x1c, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x49, 0x6e, 0x62, 0x6f, - 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0c, 0x56, - 0x6f, 0x74, 0x65, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x1d, 0x2e, 0x75, 0x65, - 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x56, 0x6f, - 0x74, 0x65, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x1a, 0x25, 0x2e, 0x75, 0x65, 0x78, - 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, - 0x65, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x57, 0x0a, 0x0d, 0x56, 0x6f, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x4d, 0x65, - 0x74, 0x61, 0x12, 0x1e, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x4d, 0x65, - 0x74, 0x61, 0x1a, 0x26, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x4d, 0x65, - 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x66, 0x0a, 0x12, 0x52, 0x65, - 0x76, 0x65, 0x72, 0x74, 0x53, 0x74, 0x75, 0x63, 0x6b, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, - 0x12, 0x23, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x4d, 0x73, 0x67, 0x52, 0x65, 0x76, 0x65, 0x72, 0x74, 0x53, 0x74, 0x75, 0x63, 0x6b, 0x49, 0x6e, - 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x1a, 0x2b, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x52, 0x65, 0x76, 0x65, 0x72, 0x74, 0x53, 0x74, - 0x75, 0x63, 0x6b, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x1a, 0x05, 0x80, 0xe7, 0xb0, 0x2a, 0x01, 0x42, 0xaf, 0x01, 0x0a, 0x10, 0x63, 0x6f, - 0x6d, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x42, 0x07, - 0x54, 0x78, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x75, 0x73, 0x68, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2f, - 0x70, 0x75, 0x73, 0x68, 0x2d, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2d, 0x6e, 0x6f, 0x64, 0x65, 0x2f, - 0x61, 0x70, 0x69, 0x2f, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x76, 0x31, - 0x3b, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x55, - 0x58, 0x58, 0xaa, 0x02, 0x0c, 0x55, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x56, - 0x31, 0xca, 0x02, 0x0c, 0x55, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x5c, 0x56, 0x31, - 0xe2, 0x02, 0x18, 0x55, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x5c, 0x56, 0x31, 0x5c, - 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0d, 0x55, 0x65, - 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x75, 0x6e, 0x64, 0x1a, 0x24, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, + 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0c, 0x56, 0x6f, 0x74, + 0x65, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x1d, 0x2e, 0x75, 0x65, 0x78, 0x65, + 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, + 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x1a, 0x25, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, + 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x4f, + 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x57, 0x0a, 0x0d, 0x56, 0x6f, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x4d, 0x65, 0x74, 0x61, + 0x12, 0x1e, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x4d, 0x65, 0x74, 0x61, + 0x1a, 0x26, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x4d, 0x73, 0x67, 0x56, 0x6f, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x4d, 0x65, 0x74, 0x61, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x66, 0x0a, 0x12, 0x52, 0x65, 0x76, 0x65, + 0x72, 0x74, 0x53, 0x74, 0x75, 0x63, 0x6b, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x23, + 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, + 0x67, 0x52, 0x65, 0x76, 0x65, 0x72, 0x74, 0x53, 0x74, 0x75, 0x63, 0x6b, 0x49, 0x6e, 0x62, 0x6f, + 0x75, 0x6e, 0x64, 0x1a, 0x2b, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x52, 0x65, 0x76, 0x65, 0x72, 0x74, 0x53, 0x74, 0x75, 0x63, + 0x6b, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x69, 0x0a, 0x13, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x53, 0x74, 0x75, 0x63, 0x6b, + 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x24, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, + 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, + 0x65, 0x53, 0x74, 0x75, 0x63, 0x6b, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x1a, 0x2c, 0x2e, + 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, + 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x53, 0x74, 0x75, 0x63, 0x6b, 0x49, 0x6e, 0x62, 0x6f, + 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x14, 0x45, + 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x53, 0x74, 0x75, 0x63, 0x6b, 0x4f, 0x75, 0x74, 0x62, 0x6f, + 0x75, 0x6e, 0x64, 0x12, 0x25, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x53, 0x74, 0x75, + 0x63, 0x6b, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x1a, 0x2d, 0x2e, 0x75, 0x65, 0x78, + 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x73, 0x67, 0x45, 0x78, 0x65, + 0x63, 0x75, 0x74, 0x65, 0x53, 0x74, 0x75, 0x63, 0x6b, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, + 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x05, 0x80, 0xe7, 0xb0, 0x2a, 0x01, + 0x42, 0xaf, 0x01, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, + 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x42, 0x07, 0x54, 0x78, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, + 0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x75, 0x73, + 0x68, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x2f, 0x70, 0x75, 0x73, 0x68, 0x2d, 0x63, 0x68, 0x61, 0x69, + 0x6e, 0x2d, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x75, 0x65, 0x78, 0x65, 0x63, + 0x75, 0x74, 0x6f, 0x72, 0x2f, 0x76, 0x31, 0x3b, 0x75, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, + 0x72, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x55, 0x58, 0x58, 0xaa, 0x02, 0x0c, 0x55, 0x65, 0x78, 0x65, + 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0c, 0x55, 0x65, 0x78, 0x65, 0x63, + 0x75, 0x74, 0x6f, 0x72, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x18, 0x55, 0x65, 0x78, 0x65, 0x63, 0x75, + 0x74, 0x6f, 0x72, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0xea, 0x02, 0x0d, 0x55, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x3a, 0x3a, + 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -6319,50 +8525,60 @@ func file_uexecutor_v1_tx_proto_rawDescGZIP() []byte { return file_uexecutor_v1_tx_proto_rawDescData } -var file_uexecutor_v1_tx_proto_msgTypes = make([]protoimpl.MessageInfo, 12) +var file_uexecutor_v1_tx_proto_msgTypes = make([]protoimpl.MessageInfo, 16) var file_uexecutor_v1_tx_proto_goTypes = []interface{}{ - (*MsgUpdateParams)(nil), // 0: uexecutor.v1.MsgUpdateParams - (*MsgUpdateParamsResponse)(nil), // 1: uexecutor.v1.MsgUpdateParamsResponse - (*MsgExecutePayload)(nil), // 2: uexecutor.v1.MsgExecutePayload - (*MsgExecutePayloadResponse)(nil), // 3: uexecutor.v1.MsgExecutePayloadResponse - (*MsgVoteInbound)(nil), // 4: uexecutor.v1.MsgVoteInbound - (*MsgVoteInboundResponse)(nil), // 5: uexecutor.v1.MsgVoteInboundResponse - (*MsgVoteOutbound)(nil), // 6: uexecutor.v1.MsgVoteOutbound - (*MsgVoteOutboundResponse)(nil), // 7: uexecutor.v1.MsgVoteOutboundResponse - (*MsgVoteChainMeta)(nil), // 8: uexecutor.v1.MsgVoteChainMeta - (*MsgVoteChainMetaResponse)(nil), // 9: uexecutor.v1.MsgVoteChainMetaResponse - (*MsgRevertStuckInbound)(nil), // 10: uexecutor.v1.MsgRevertStuckInbound - (*MsgRevertStuckInboundResponse)(nil), // 11: uexecutor.v1.MsgRevertStuckInboundResponse - (*Params)(nil), // 12: uexecutor.v1.Params - (*UniversalAccountId)(nil), // 13: uexecutor.v1.UniversalAccountId - (*UniversalPayload)(nil), // 14: uexecutor.v1.UniversalPayload - (*Inbound)(nil), // 15: uexecutor.v1.Inbound - (*OutboundObservation)(nil), // 16: uexecutor.v1.OutboundObservation + (*MsgUpdateParams)(nil), // 0: uexecutor.v1.MsgUpdateParams + (*MsgUpdateParamsResponse)(nil), // 1: uexecutor.v1.MsgUpdateParamsResponse + (*MsgExecutePayload)(nil), // 2: uexecutor.v1.MsgExecutePayload + (*MsgExecutePayloadResponse)(nil), // 3: uexecutor.v1.MsgExecutePayloadResponse + (*MsgVoteInbound)(nil), // 4: uexecutor.v1.MsgVoteInbound + (*MsgVoteInboundResponse)(nil), // 5: uexecutor.v1.MsgVoteInboundResponse + (*MsgVoteOutbound)(nil), // 6: uexecutor.v1.MsgVoteOutbound + (*MsgVoteOutboundResponse)(nil), // 7: uexecutor.v1.MsgVoteOutboundResponse + (*MsgVoteChainMeta)(nil), // 8: uexecutor.v1.MsgVoteChainMeta + (*MsgVoteChainMetaResponse)(nil), // 9: uexecutor.v1.MsgVoteChainMetaResponse + (*MsgRevertStuckInbound)(nil), // 10: uexecutor.v1.MsgRevertStuckInbound + (*MsgRevertStuckInboundResponse)(nil), // 11: uexecutor.v1.MsgRevertStuckInboundResponse + (*MsgExecuteStuckInbound)(nil), // 12: uexecutor.v1.MsgExecuteStuckInbound + (*MsgExecuteStuckInboundResponse)(nil), // 13: uexecutor.v1.MsgExecuteStuckInboundResponse + (*MsgExecuteStuckOutbound)(nil), // 14: uexecutor.v1.MsgExecuteStuckOutbound + (*MsgExecuteStuckOutboundResponse)(nil), // 15: uexecutor.v1.MsgExecuteStuckOutboundResponse + (*Params)(nil), // 16: uexecutor.v1.Params + (*UniversalAccountId)(nil), // 17: uexecutor.v1.UniversalAccountId + (*UniversalPayload)(nil), // 18: uexecutor.v1.UniversalPayload + (*Inbound)(nil), // 19: uexecutor.v1.Inbound + (*OutboundObservation)(nil), // 20: uexecutor.v1.OutboundObservation } var file_uexecutor_v1_tx_proto_depIdxs = []int32{ - 12, // 0: uexecutor.v1.MsgUpdateParams.params:type_name -> uexecutor.v1.Params - 13, // 1: uexecutor.v1.MsgExecutePayload.universal_account_id:type_name -> uexecutor.v1.UniversalAccountId - 14, // 2: uexecutor.v1.MsgExecutePayload.universal_payload:type_name -> uexecutor.v1.UniversalPayload - 15, // 3: uexecutor.v1.MsgVoteInbound.inbound:type_name -> uexecutor.v1.Inbound - 16, // 4: uexecutor.v1.MsgVoteOutbound.observed_tx:type_name -> uexecutor.v1.OutboundObservation - 15, // 5: uexecutor.v1.MsgRevertStuckInbound.inbound:type_name -> uexecutor.v1.Inbound - 0, // 6: uexecutor.v1.Msg.UpdateParams:input_type -> uexecutor.v1.MsgUpdateParams - 2, // 7: uexecutor.v1.Msg.ExecutePayload:input_type -> uexecutor.v1.MsgExecutePayload - 4, // 8: uexecutor.v1.Msg.VoteInbound:input_type -> uexecutor.v1.MsgVoteInbound - 6, // 9: uexecutor.v1.Msg.VoteOutbound:input_type -> uexecutor.v1.MsgVoteOutbound - 8, // 10: uexecutor.v1.Msg.VoteChainMeta:input_type -> uexecutor.v1.MsgVoteChainMeta - 10, // 11: uexecutor.v1.Msg.RevertStuckInbound:input_type -> uexecutor.v1.MsgRevertStuckInbound - 1, // 12: uexecutor.v1.Msg.UpdateParams:output_type -> uexecutor.v1.MsgUpdateParamsResponse - 3, // 13: uexecutor.v1.Msg.ExecutePayload:output_type -> uexecutor.v1.MsgExecutePayloadResponse - 5, // 14: uexecutor.v1.Msg.VoteInbound:output_type -> uexecutor.v1.MsgVoteInboundResponse - 7, // 15: uexecutor.v1.Msg.VoteOutbound:output_type -> uexecutor.v1.MsgVoteOutboundResponse - 9, // 16: uexecutor.v1.Msg.VoteChainMeta:output_type -> uexecutor.v1.MsgVoteChainMetaResponse - 11, // 17: uexecutor.v1.Msg.RevertStuckInbound:output_type -> uexecutor.v1.MsgRevertStuckInboundResponse - 12, // [12:18] is the sub-list for method output_type - 6, // [6:12] is the sub-list for method input_type - 6, // [6:6] is the sub-list for extension type_name - 6, // [6:6] is the sub-list for extension extendee - 0, // [0:6] is the sub-list for field type_name + 16, // 0: uexecutor.v1.MsgUpdateParams.params:type_name -> uexecutor.v1.Params + 17, // 1: uexecutor.v1.MsgExecutePayload.universal_account_id:type_name -> uexecutor.v1.UniversalAccountId + 18, // 2: uexecutor.v1.MsgExecutePayload.universal_payload:type_name -> uexecutor.v1.UniversalPayload + 19, // 3: uexecutor.v1.MsgVoteInbound.inbound:type_name -> uexecutor.v1.Inbound + 20, // 4: uexecutor.v1.MsgVoteOutbound.observed_tx:type_name -> uexecutor.v1.OutboundObservation + 19, // 5: uexecutor.v1.MsgRevertStuckInbound.inbound:type_name -> uexecutor.v1.Inbound + 19, // 6: uexecutor.v1.MsgExecuteStuckInbound.inbound:type_name -> uexecutor.v1.Inbound + 20, // 7: uexecutor.v1.MsgExecuteStuckOutbound.observed_tx:type_name -> uexecutor.v1.OutboundObservation + 0, // 8: uexecutor.v1.Msg.UpdateParams:input_type -> uexecutor.v1.MsgUpdateParams + 2, // 9: uexecutor.v1.Msg.ExecutePayload:input_type -> uexecutor.v1.MsgExecutePayload + 4, // 10: uexecutor.v1.Msg.VoteInbound:input_type -> uexecutor.v1.MsgVoteInbound + 6, // 11: uexecutor.v1.Msg.VoteOutbound:input_type -> uexecutor.v1.MsgVoteOutbound + 8, // 12: uexecutor.v1.Msg.VoteChainMeta:input_type -> uexecutor.v1.MsgVoteChainMeta + 10, // 13: uexecutor.v1.Msg.RevertStuckInbound:input_type -> uexecutor.v1.MsgRevertStuckInbound + 12, // 14: uexecutor.v1.Msg.ExecuteStuckInbound:input_type -> uexecutor.v1.MsgExecuteStuckInbound + 14, // 15: uexecutor.v1.Msg.ExecuteStuckOutbound:input_type -> uexecutor.v1.MsgExecuteStuckOutbound + 1, // 16: uexecutor.v1.Msg.UpdateParams:output_type -> uexecutor.v1.MsgUpdateParamsResponse + 3, // 17: uexecutor.v1.Msg.ExecutePayload:output_type -> uexecutor.v1.MsgExecutePayloadResponse + 5, // 18: uexecutor.v1.Msg.VoteInbound:output_type -> uexecutor.v1.MsgVoteInboundResponse + 7, // 19: uexecutor.v1.Msg.VoteOutbound:output_type -> uexecutor.v1.MsgVoteOutboundResponse + 9, // 20: uexecutor.v1.Msg.VoteChainMeta:output_type -> uexecutor.v1.MsgVoteChainMetaResponse + 11, // 21: uexecutor.v1.Msg.RevertStuckInbound:output_type -> uexecutor.v1.MsgRevertStuckInboundResponse + 13, // 22: uexecutor.v1.Msg.ExecuteStuckInbound:output_type -> uexecutor.v1.MsgExecuteStuckInboundResponse + 15, // 23: uexecutor.v1.Msg.ExecuteStuckOutbound:output_type -> uexecutor.v1.MsgExecuteStuckOutboundResponse + 16, // [16:24] is the sub-list for method output_type + 8, // [8:16] is the sub-list for method input_type + 8, // [8:8] is the sub-list for extension type_name + 8, // [8:8] is the sub-list for extension extendee + 0, // [0:8] is the sub-list for field type_name } func init() { file_uexecutor_v1_tx_proto_init() } @@ -6517,6 +8733,54 @@ func file_uexecutor_v1_tx_proto_init() { return nil } } + file_uexecutor_v1_tx_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MsgExecuteStuckInbound); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_uexecutor_v1_tx_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MsgExecuteStuckInboundResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_uexecutor_v1_tx_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MsgExecuteStuckOutbound); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_uexecutor_v1_tx_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MsgExecuteStuckOutboundResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -6524,7 +8788,7 @@ func file_uexecutor_v1_tx_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_uexecutor_v1_tx_proto_rawDesc, NumEnums: 0, - NumMessages: 12, + NumMessages: 16, NumExtensions: 0, NumServices: 1, }, diff --git a/api/uexecutor/v1/tx_grpc.pb.go b/api/uexecutor/v1/tx_grpc.pb.go index a0cf9fbf1..cefd4ab8f 100644 --- a/api/uexecutor/v1/tx_grpc.pb.go +++ b/api/uexecutor/v1/tx_grpc.pb.go @@ -19,12 +19,14 @@ import ( const _ = grpc.SupportPackageIsVersion7 const ( - Msg_UpdateParams_FullMethodName = "/uexecutor.v1.Msg/UpdateParams" - Msg_ExecutePayload_FullMethodName = "/uexecutor.v1.Msg/ExecutePayload" - Msg_VoteInbound_FullMethodName = "/uexecutor.v1.Msg/VoteInbound" - Msg_VoteOutbound_FullMethodName = "/uexecutor.v1.Msg/VoteOutbound" - Msg_VoteChainMeta_FullMethodName = "/uexecutor.v1.Msg/VoteChainMeta" - Msg_RevertStuckInbound_FullMethodName = "/uexecutor.v1.Msg/RevertStuckInbound" + Msg_UpdateParams_FullMethodName = "/uexecutor.v1.Msg/UpdateParams" + Msg_ExecutePayload_FullMethodName = "/uexecutor.v1.Msg/ExecutePayload" + Msg_VoteInbound_FullMethodName = "/uexecutor.v1.Msg/VoteInbound" + Msg_VoteOutbound_FullMethodName = "/uexecutor.v1.Msg/VoteOutbound" + Msg_VoteChainMeta_FullMethodName = "/uexecutor.v1.Msg/VoteChainMeta" + Msg_RevertStuckInbound_FullMethodName = "/uexecutor.v1.Msg/RevertStuckInbound" + Msg_ExecuteStuckInbound_FullMethodName = "/uexecutor.v1.Msg/ExecuteStuckInbound" + Msg_ExecuteStuckOutbound_FullMethodName = "/uexecutor.v1.Msg/ExecuteStuckOutbound" ) // MsgClient is the client API for Msg service. @@ -47,6 +49,15 @@ type MsgClient interface { // ballot has expired without finalizing, refunding the user on the source // chain via the normal revert/outbound flow. Admin-only escape hatch. RevertStuckInbound(ctx context.Context, in *MsgRevertStuckInbound, opts ...grpc.CallOption) (*MsgRevertStuckInboundResponse, error) + // ExecuteStuckInbound finalizes an inbound ballot that is provably unable to + // finalize on its own yet already carries enough YES votes, then runs the + // normal post-finalization pipeline so the user receives funds on Push. + // Admin-only escape hatch, sibling of RevertStuckInbound. + ExecuteStuckInbound(ctx context.Context, in *MsgExecuteStuckInbound, opts ...grpc.CallOption) (*MsgExecuteStuckInboundResponse, error) + // ExecuteStuckOutbound settles an outbound whose ballot can no longer reach a + // terminal-and-settled state, running the same post-finalization pipeline a + // finalizing vote would have run. Admin-only escape hatch. + ExecuteStuckOutbound(ctx context.Context, in *MsgExecuteStuckOutbound, opts ...grpc.CallOption) (*MsgExecuteStuckOutboundResponse, error) } type msgClient struct { @@ -111,6 +122,24 @@ func (c *msgClient) RevertStuckInbound(ctx context.Context, in *MsgRevertStuckIn return out, nil } +func (c *msgClient) ExecuteStuckInbound(ctx context.Context, in *MsgExecuteStuckInbound, opts ...grpc.CallOption) (*MsgExecuteStuckInboundResponse, error) { + out := new(MsgExecuteStuckInboundResponse) + err := c.cc.Invoke(ctx, Msg_ExecuteStuckInbound_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) ExecuteStuckOutbound(ctx context.Context, in *MsgExecuteStuckOutbound, opts ...grpc.CallOption) (*MsgExecuteStuckOutboundResponse, error) { + out := new(MsgExecuteStuckOutboundResponse) + err := c.cc.Invoke(ctx, Msg_ExecuteStuckOutbound_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // MsgServer is the server API for Msg service. // All implementations must embed UnimplementedMsgServer // for forward compatibility @@ -131,6 +160,15 @@ type MsgServer interface { // ballot has expired without finalizing, refunding the user on the source // chain via the normal revert/outbound flow. Admin-only escape hatch. RevertStuckInbound(context.Context, *MsgRevertStuckInbound) (*MsgRevertStuckInboundResponse, error) + // ExecuteStuckInbound finalizes an inbound ballot that is provably unable to + // finalize on its own yet already carries enough YES votes, then runs the + // normal post-finalization pipeline so the user receives funds on Push. + // Admin-only escape hatch, sibling of RevertStuckInbound. + ExecuteStuckInbound(context.Context, *MsgExecuteStuckInbound) (*MsgExecuteStuckInboundResponse, error) + // ExecuteStuckOutbound settles an outbound whose ballot can no longer reach a + // terminal-and-settled state, running the same post-finalization pipeline a + // finalizing vote would have run. Admin-only escape hatch. + ExecuteStuckOutbound(context.Context, *MsgExecuteStuckOutbound) (*MsgExecuteStuckOutboundResponse, error) mustEmbedUnimplementedMsgServer() } @@ -156,6 +194,12 @@ func (UnimplementedMsgServer) VoteChainMeta(context.Context, *MsgVoteChainMeta) func (UnimplementedMsgServer) RevertStuckInbound(context.Context, *MsgRevertStuckInbound) (*MsgRevertStuckInboundResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method RevertStuckInbound not implemented") } +func (UnimplementedMsgServer) ExecuteStuckInbound(context.Context, *MsgExecuteStuckInbound) (*MsgExecuteStuckInboundResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ExecuteStuckInbound not implemented") +} +func (UnimplementedMsgServer) ExecuteStuckOutbound(context.Context, *MsgExecuteStuckOutbound) (*MsgExecuteStuckOutboundResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ExecuteStuckOutbound not implemented") +} func (UnimplementedMsgServer) mustEmbedUnimplementedMsgServer() {} // UnsafeMsgServer may be embedded to opt out of forward compatibility for this service. @@ -277,6 +321,42 @@ func _Msg_RevertStuckInbound_Handler(srv interface{}, ctx context.Context, dec f return interceptor(ctx, in, info, handler) } +func _Msg_ExecuteStuckInbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgExecuteStuckInbound) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).ExecuteStuckInbound(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Msg_ExecuteStuckInbound_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).ExecuteStuckInbound(ctx, req.(*MsgExecuteStuckInbound)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_ExecuteStuckOutbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgExecuteStuckOutbound) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).ExecuteStuckOutbound(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Msg_ExecuteStuckOutbound_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).ExecuteStuckOutbound(ctx, req.(*MsgExecuteStuckOutbound)) + } + return interceptor(ctx, in, info, handler) +} + // Msg_ServiceDesc is the grpc.ServiceDesc for Msg service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -308,6 +388,14 @@ var Msg_ServiceDesc = grpc.ServiceDesc{ MethodName: "RevertStuckInbound", Handler: _Msg_RevertStuckInbound_Handler, }, + { + MethodName: "ExecuteStuckInbound", + Handler: _Msg_ExecuteStuckInbound_Handler, + }, + { + MethodName: "ExecuteStuckOutbound", + Handler: _Msg_ExecuteStuckOutbound_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "uexecutor/v1/tx.proto", diff --git a/proto/uexecutor/v1/tx.proto b/proto/uexecutor/v1/tx.proto index bd3014797..0e4248a5e 100755 --- a/proto/uexecutor/v1/tx.proto +++ b/proto/uexecutor/v1/tx.proto @@ -35,6 +35,17 @@ service Msg { // ballot has expired without finalizing, refunding the user on the source // chain via the normal revert/outbound flow. Admin-only escape hatch. rpc RevertStuckInbound(MsgRevertStuckInbound) returns (MsgRevertStuckInboundResponse); + + // ExecuteStuckInbound finalizes an inbound ballot that is provably unable to + // finalize on its own yet already carries enough YES votes, then runs the + // normal post-finalization pipeline so the user receives funds on Push. + // Admin-only escape hatch, sibling of RevertStuckInbound. + rpc ExecuteStuckInbound(MsgExecuteStuckInbound) returns (MsgExecuteStuckInboundResponse); + + // ExecuteStuckOutbound settles an outbound whose ballot can no longer reach a + // terminal-and-settled state, running the same post-finalization pipeline a + // finalizing vote would have run. Admin-only escape hatch. + rpc ExecuteStuckOutbound(MsgExecuteStuckOutbound) returns (MsgExecuteStuckOutboundResponse); } // MsgUpdateParams is the Msg/UpdateParams request type. @@ -142,3 +153,51 @@ message MsgRevertStuckInboundResponse { string utx_id = 1; // ID of the UTX created to hold the revert string outbound_id = 2; // ID of the INBOUND_REVERT outbound created } + +// MsgExecuteStuckInbound is an admin escape hatch and the sibling of +// MsgRevertStuckInbound. For an inbound whose ballot is stored PENDING but can +// never finalize on its own, and whose YES votes already meet the recomputed +// threshold, this marks the ballot PASSED and runs the same post-finalization +// pipeline a finalizing vote would have run - so the user receives the bridged +// funds on Push instead of a source-chain refund. +message MsgExecuteStuckInbound { + option (amino.name) = "uexecutor/MsgExecuteStuckInbound"; + option (cosmos.msg.v1.signer) = "signer"; + + // signer must equal uvalidator Params.Admin + string signer = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + + // inbound is the original payload the stuck ballot was voting on. Admin + // supplies this from off-chain UV observation logs since the chain does not + // persist ballot payloads. + Inbound inbound = 2; +} + +message MsgExecuteStuckInboundResponse { + string utx_id = 1; // ID of the UTX created for the executed inbound +} + +// MsgExecuteStuckOutbound is an admin escape hatch for an outbound whose ballot +// can no longer reach a terminal-and-settled state — EXPIRED, or PENDING with +// every eligible voter already voted and the YES votes at the stored threshold. +// It runs the same settlement pipeline a finalizing vote would have run, so the +// outcome follows observed_tx.success: a success settles, a failure mints the +// bridged tokens back to the revert recipient and refunds the excess gas. +message MsgExecuteStuckOutbound { + option (amino.name) = "uexecutor/MsgExecuteStuckOutbound"; + option (cosmos.msg.v1.signer) = "signer"; + + // signer must equal uvalidator Params.Admin + string signer = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + string tx_id = 2; // txId of outbound tx + string utx_id = 3; // UniversalTx Id + // observed_tx is the destination-chain observation the stuck ballot was voting + // on. Admin supplies this from off-chain UV observation logs since the chain + // does not persist ballot payloads; it must match field-for-field or the + // derived ballot key finds no ballot. + OutboundObservation observed_tx = 4; +} + +message MsgExecuteStuckOutboundResponse { + string outbound_id = 1; // ID of the outbound that was settled +} diff --git a/test/integration/uexecutor/execute_stuck_inbound_test.go b/test/integration/uexecutor/execute_stuck_inbound_test.go new file mode 100644 index 000000000..e8c1f0f51 --- /dev/null +++ b/test/integration/uexecutor/execute_stuck_inbound_test.go @@ -0,0 +1,484 @@ +package integrationtest + +import ( + "fmt" + "math/big" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + + "github.com/pushchain/push-chain-node/app" + utils "github.com/pushchain/push-chain-node/test/utils" + uexecutorkeeper "github.com/pushchain/push-chain-node/x/uexecutor/keeper" + uexecutortypes "github.com/pushchain/push-chain-node/x/uexecutor/types" + uvalidatorkeeper "github.com/pushchain/push-chain-node/x/uvalidator/keeper" + uvalidatortypes "github.com/pushchain/push-chain-node/x/uvalidator/types" +) + +// TestExecuteStuckInbound_PendingUnreachable_ThresholdMet_Executes is the +// headline F-2026-18147 case for the execute hatch. +// +// RecomputeBallotQuorum preserves the votes of still-eligible voters and lowers +// the threshold, but returns PENDING without tallying what it just rebuilt. The +// shape reproduced here is the result: every remaining eligible voter has voted +// YES and the YES count already clears the recomputed threshold, so the ballot +// should have passed but AddVote rejects repeat votes and nothing can move it. +// +// The whole live validator set attested this deposit, so the correct resolution +// is to deliver the funds on Push - not to refund on the source chain, which is +// all RevertStuckInbound could do. +func TestExecuteStuckInbound_PendingUnreachable_ThresholdMet_Executes(t *testing.T) { + chainApp, ctx, inbound, admin := setupRevertStuckInbound(t) + seedPendingBallotWithVotes(t, chainApp, ctx, inbound, + uvalidatortypes.BallotStatus_BALLOT_STATUS_PENDING, + threeVoters(), + []uvalidatortypes.VoteResult{ + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + }, + 2, // YES (3) already clears the recomputed threshold + ) + + recipient := common.HexToAddress(inbound.Recipient) + require.Equal(t, 0, prc20BalanceOf(t, chainApp, ctx, recipient).Sign(), + "recipient must start with no bridged balance") + + ms := uexecutorkeeper.NewMsgServerImpl(chainApp.UexecutorKeeper) + resp, err := ms.ExecuteStuckInbound(sdk.WrapSDKContext(ctx), &uexecutortypes.MsgExecuteStuckInbound{ + Signer: admin, + Inbound: inbound, + }) + require.NoError(t, err, "an unreachable PENDING ballot at threshold must be executable") + require.Equal(t, uexecutortypes.GetInboundUniversalTxKey(*inbound), resp.UtxId) + + // --- UTX assertions --- + utx, _, err := chainApp.UexecutorKeeper.GetUniversalTx(ctx, resp.UtxId) + require.NoError(t, err) + require.NotNil(t, utx.InboundTx) + require.Equal(t, inbound.TxHash, utx.InboundTx.TxHash) + + require.Len(t, utx.PcTx, 1) + require.Equal(t, "SUCCESS", utx.PcTx[0].Status, + "the execute hatch must run the deposit, not record a failure") + + // The point of this hatch: execution, not refund. + require.Empty(t, utx.OutboundTx, "an executed inbound must not create a revert outbound") + + // --- The user actually got the funds --- + amount, ok := new(big.Int).SetString(inbound.Amount, 10) + require.True(t, ok) + require.Equal(t, 0, prc20BalanceOf(t, chainApp, ctx, recipient).Cmp(amount), + "recipient balance must equal the inbound amount") + + // --- Ballot is terminal, so the hatch cannot be re-entered --- + ballotKey, err := uexecutortypes.GetInboundBallotKey(*inbound) + require.NoError(t, err) + ballot, err := chainApp.UvalidatorKeeper.Ballots.Get(ctx, ballotKey) + require.NoError(t, err) + require.Equal(t, uvalidatortypes.BallotStatus_BALLOT_STATUS_PASSED, ballot.Status) + + // The pending audit-trail entry must be gone: the terminal hook clears it and + // the pipeline's RemovePendingInbound is a no-op on the absent key. + isPending, err := chainApp.UexecutorKeeper.IsPendingInbound(ctx, *inbound) + require.NoError(t, err) + require.False(t, isPending) +} + +// TestExecuteStuckInbound_DuplicateExecute_Rejected verifies a second call +// cannot mint twice. +// +// Two independent barriers stand in the way and the outer one wins here: the +// first call drove the ballot to PASSED, so IsUnreachablePending is already +// false. The UTX barrier underneath it is exercised by +// TestExecuteStuckInbound_AlreadyRevertedInbound_Refused, where the ballot stays +// PENDING-unreachable and only the UTX blocks the second call. +func TestExecuteStuckInbound_DuplicateExecute_Rejected(t *testing.T) { + chainApp, ctx, inbound, admin := setupRevertStuckInbound(t) + seedPendingBallotWithVotes(t, chainApp, ctx, inbound, + uvalidatortypes.BallotStatus_BALLOT_STATUS_PENDING, + threeVoters(), + []uvalidatortypes.VoteResult{ + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + }, + 2, + ) + + ms := uexecutorkeeper.NewMsgServerImpl(chainApp.UexecutorKeeper) + _, err := ms.ExecuteStuckInbound(sdk.WrapSDKContext(ctx), &uexecutortypes.MsgExecuteStuckInbound{ + Signer: admin, + Inbound: inbound, + }) + require.NoError(t, err) + + amount, ok := new(big.Int).SetString(inbound.Amount, 10) + require.True(t, ok) + recipient := common.HexToAddress(inbound.Recipient) + + _, err = ms.ExecuteStuckInbound(sdk.WrapSDKContext(ctx), &uexecutortypes.MsgExecuteStuckInbound{ + Signer: admin, + Inbound: inbound, + }) + require.Error(t, err, "a second execute must be refused") + require.Contains(t, err.Error(), "admin execute requires PENDING", + "the ballot is already PASSED, so the status gate refuses before the UTX gate is reached") + + require.Equal(t, 0, prc20BalanceOf(t, chainApp, ctx, recipient).Cmp(amount), + "the refused second execute must not have minted again") +} + +// TestExecuteStuckInbound_AlreadyRevertedInbound_Refused pins the same barrier +// against the sibling hatch: once RevertStuckInbound has created its UTX, the +// inbound cannot also be executed. Otherwise an operator could refund the user +// on the source chain and then mint them the same funds on Push. +func TestExecuteStuckInbound_AlreadyRevertedInbound_Refused(t *testing.T) { + chainApp, ctx, inbound, admin := setupRevertStuckInbound(t) + seedPendingBallotWithVotes(t, chainApp, ctx, inbound, + uvalidatortypes.BallotStatus_BALLOT_STATUS_PENDING, + threeVoters(), + []uvalidatortypes.VoteResult{ + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + }, + 2, + ) + + ms := uexecutorkeeper.NewMsgServerImpl(chainApp.UexecutorKeeper) + _, err := ms.RevertStuckInbound(sdk.WrapSDKContext(ctx), &uexecutortypes.MsgRevertStuckInbound{ + Signer: admin, + Inbound: inbound, + }) + require.NoError(t, err) + + _, err = ms.ExecuteStuckInbound(sdk.WrapSDKContext(ctx), &uexecutortypes.MsgExecuteStuckInbound{ + Signer: admin, + Inbound: inbound, + }) + require.Error(t, err, "an inbound already reverted must not also be executed") + require.Contains(t, err.Error(), "already exists") + + require.Equal(t, 0, prc20BalanceOf(t, chainApp, ctx, common.HexToAddress(inbound.Recipient)).Sign(), + "a refused execute must not mint") +} + +// TestExecuteStuckInbound_ExpiredBallot_Refused keeps the two hatches apart. An +// EXPIRED ballot never reached quorum, so there is no attestation to act on and +// the refund is the honest resolution — that is RevertStuckInbound's job. +func TestExecuteStuckInbound_ExpiredBallot_Refused(t *testing.T) { + chainApp, ctx, inbound, admin := setupRevertStuckInbound(t) + seedPendingBallotWithVotes(t, chainApp, ctx, inbound, + uvalidatortypes.BallotStatus_BALLOT_STATUS_EXPIRED, + threeVoters(), + []uvalidatortypes.VoteResult{ + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + }, + 2, + ) + + ms := uexecutorkeeper.NewMsgServerImpl(chainApp.UexecutorKeeper) + _, err := ms.ExecuteStuckInbound(sdk.WrapSDKContext(ctx), &uexecutortypes.MsgExecuteStuckInbound{ + Signer: admin, + Inbound: inbound, + }) + require.Error(t, err, "EXPIRED belongs to the revert hatch, not the execute hatch") + require.Contains(t, err.Error(), "admin execute requires PENDING") + require.Contains(t, err.Error(), "MsgRevertStuckInbound", + "the refusal must point the operator at the hatch that does apply") + + assertNoUtxOrMint(t, chainApp, ctx, inbound) +} + +// TestExecuteStuckInbound_PassedBallot_Refused guards the case where the ballot +// already finalized normally: IsUnreachablePending is false, so re-running the +// pipeline is refused even before the UTX check. +func TestExecuteStuckInbound_PassedBallot_Refused(t *testing.T) { + chainApp, ctx, inbound, admin := setupRevertStuckInbound(t) + seedPendingBallotWithVotes(t, chainApp, ctx, inbound, + uvalidatortypes.BallotStatus_BALLOT_STATUS_PASSED, + threeVoters(), + []uvalidatortypes.VoteResult{ + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + }, + 2, + ) + + ms := uexecutorkeeper.NewMsgServerImpl(chainApp.UexecutorKeeper) + _, err := ms.ExecuteStuckInbound(sdk.WrapSDKContext(ctx), &uexecutortypes.MsgExecuteStuckInbound{ + Signer: admin, + Inbound: inbound, + }) + require.Error(t, err) + require.Contains(t, err.Error(), "admin execute requires PENDING") + + assertNoUtxOrMint(t, chainApp, ctx, inbound) +} + +// TestExecuteStuckInbound_PendingWithUnvotedVoter_Refused is the guard against +// widening the hatch too far. +// +// The YES votes already clear the threshold, so this looks exactly like the +// headline case. It is not: one eligible voter still holds a NOT_YET_VOTED slot, +// so a single normal vote finalizes it through the real pipeline. Admin execute +// must not race that — the vote flow decides, not the admin. +func TestExecuteStuckInbound_PendingWithUnvotedVoter_Refused(t *testing.T) { + chainApp, ctx, inbound, admin := setupRevertStuckInbound(t) + seedPendingBallotWithVotes(t, chainApp, ctx, inbound, + uvalidatortypes.BallotStatus_BALLOT_STATUS_PENDING, + threeVoters(), + []uvalidatortypes.VoteResult{ + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_NOT_YET_VOTED, + }, + 2, // YES (2) already meets threshold — still refused, it can finalize normally + ) + + ms := uexecutorkeeper.NewMsgServerImpl(chainApp.UexecutorKeeper) + _, err := ms.ExecuteStuckInbound(sdk.WrapSDKContext(ctx), &uexecutortypes.MsgExecuteStuckInbound{ + Signer: admin, + Inbound: inbound, + }) + require.Error(t, err, "a PENDING ballot with an unvoted eligible voter can still finalize normally") + require.Contains(t, err.Error(), "admin execute requires PENDING") + + // The ballot must be left untouched so the remaining voter can still finalize it. + ballotKey, err := uexecutortypes.GetInboundBallotKey(*inbound) + require.NoError(t, err) + ballot, err := chainApp.UvalidatorKeeper.Ballots.Get(ctx, ballotKey) + require.NoError(t, err) + require.Equal(t, uvalidatortypes.BallotStatus_BALLOT_STATUS_PENDING, ballot.Status) + + assertNoUtxOrMint(t, chainApp, ctx, inbound) +} + +// TestExecuteStuckInbound_BelowThreshold_Refused covers the second stuck shape: +// every eligible voter has voted, so the ballot is unreachable, but the YES count +// never reached the threshold. There is no supermajority attestation to act on, +// so executing would deliver funds the validator set did not carry. That case +// belongs to the revert hatch, which accepts it. +// +// Unreachable today implies yes == len(EligibleVoters) because both inbound vote +// sites hardcode VOTE_RESULT_SUCCESS. This test seeds the FAILURE vote directly +// so the threshold check is pinned independently of that invariant. +func TestExecuteStuckInbound_BelowThreshold_Refused(t *testing.T) { + chainApp, ctx, inbound, admin := setupRevertStuckInbound(t) + seedPendingBallotWithVotes(t, chainApp, ctx, inbound, + uvalidatortypes.BallotStatus_BALLOT_STATUS_PENDING, + threeVoters(), + []uvalidatortypes.VoteResult{ + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_FAILURE, + }, + 3, // YES (2) < 3 → unreachable, but never carried + ) + + ms := uexecutorkeeper.NewMsgServerImpl(chainApp.UexecutorKeeper) + _, err := ms.ExecuteStuckInbound(sdk.WrapSDKContext(ctx), &uexecutortypes.MsgExecuteStuckInbound{ + Signer: admin, + Inbound: inbound, + }) + require.Error(t, err, "a ballot that never met its threshold must not be executed") + require.Contains(t, err.Error(), "against a voting threshold of 3") + require.Contains(t, err.Error(), "MsgRevertStuckInbound") + + assertNoUtxOrMint(t, chainApp, ctx, inbound) +} + +func TestExecuteStuckInbound_AdminAuth_RejectsNonAdmin(t *testing.T) { + chainApp, ctx, inbound, _ := setupRevertStuckInbound(t) + seedPendingBallotWithVotes(t, chainApp, ctx, inbound, + uvalidatortypes.BallotStatus_BALLOT_STATUS_PENDING, + threeVoters(), + []uvalidatortypes.VoteResult{ + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + }, + 2, + ) + + const notAdmin = "push1negskcfqu09j5zvpk7nhvacnwyy2mafffy7r6a" + ms := uexecutorkeeper.NewMsgServerImpl(chainApp.UexecutorKeeper) + _, err := ms.ExecuteStuckInbound(sdk.WrapSDKContext(ctx), &uexecutortypes.MsgExecuteStuckInbound{ + Signer: notAdmin, + Inbound: inbound, + }) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid admin") + + assertNoUtxOrMint(t, chainApp, ctx, inbound) +} + +func TestExecuteStuckInbound_BallotNotFound(t *testing.T) { + chainApp, ctx, inbound, admin := setupRevertStuckInbound(t) + // no ballot seeded + ms := uexecutorkeeper.NewMsgServerImpl(chainApp.UexecutorKeeper) + _, err := ms.ExecuteStuckInbound(sdk.WrapSDKContext(ctx), &uexecutortypes.MsgExecuteStuckInbound{ + Signer: admin, + Inbound: inbound, + }) + require.Error(t, err) + require.Contains(t, err.Error(), "ballot for inbound not found") +} + +func TestExecuteStuckInbound_NilInbound_Rejected(t *testing.T) { + chainApp, ctx, _, admin := setupRevertStuckInbound(t) + ms := uexecutorkeeper.NewMsgServerImpl(chainApp.UexecutorKeeper) + _, err := ms.ExecuteStuckInbound(sdk.WrapSDKContext(ctx), &uexecutortypes.MsgExecuteStuckInbound{ + Signer: admin, + Inbound: nil, + }) + require.Error(t, err) + require.Contains(t, err.Error(), "inbound is required") +} + +// TestExecuteStuckInbound_TamperedInbound_Refused pins the security property the +// ballot-key derivation buys: the admin can only execute the exact payload the +// validators voted on. A single changed field derives a different ballot key, +// which has no ballot at all. +func TestExecuteStuckInbound_TamperedInbound_Refused(t *testing.T) { + chainApp, ctx, inbound, admin := setupRevertStuckInbound(t) + seedPendingBallotWithVotes(t, chainApp, ctx, inbound, + uvalidatortypes.BallotStatus_BALLOT_STATUS_PENDING, + threeVoters(), + []uvalidatortypes.VoteResult{ + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + }, + 2, + ) + + tampered := *inbound + tampered.Amount = "999999999" + + ms := uexecutorkeeper.NewMsgServerImpl(chainApp.UexecutorKeeper) + _, err := ms.ExecuteStuckInbound(sdk.WrapSDKContext(ctx), &uexecutortypes.MsgExecuteStuckInbound{ + Signer: admin, + Inbound: &tampered, + }) + require.Error(t, err, "a payload the validators never voted on has no ballot") + require.Contains(t, err.Error(), "ballot for inbound not found") +} + +// TestExecuteStuckInbound_RecomputeThenExecute_E2E walks the whole F-2026-18147 +// story with real universal validators and real votes: +// +// 4 UVs, threshold 3 → 2 vote YES, ballot stays PENDING → the other 2 leave the +// set → MsgRecomputeBallotQuorum rebuilds it to the 2 remaining voters with +// threshold 2 and preserves their YES votes, but returns PENDING without +// tallying → nothing can ever move the ballot → MsgExecuteStuckInbound +// finalizes it PASSED and delivers the funds. +func TestExecuteStuckInbound_RecomputeThenExecute_E2E(t *testing.T) { + chainApp, ctx, universalVals, inbound, coreVals := setupInboundBridgeTest(t, 4) + + const admin = "push1fgaewhyd9fkwtqaj9c233letwcuey6dgly9gv9" + require.NoError(t, chainApp.UvalidatorKeeper.Params.Set(ctx, uvalidatortypes.Params{Admin: admin})) + + // Two of four vote YES. Threshold is (2*4)/3+1 = 3, so the ballot stays PENDING. + for i := 0; i < 2; i++ { + valAddr, err := sdk.ValAddressFromBech32(coreVals[i].OperatorAddress) + require.NoError(t, err) + require.NoError(t, utils.ExecVoteInbound(t, ctx, chainApp, universalVals[i], + sdk.AccAddress(valAddr).String(), inbound)) + } + + // Derive the ballot key the way the keeper does — off the canonical payload. + canonical := *inbound + canonical.Canonicalize() + ballotKey, err := uexecutortypes.GetInboundBallotKey(canonical) + require.NoError(t, err) + + ballot, err := chainApp.UvalidatorKeeper.Ballots.Get(ctx, ballotKey) + require.NoError(t, err) + require.Equal(t, uvalidatortypes.BallotStatus_BALLOT_STATUS_PENDING, ballot.Status) + require.Equal(t, int64(3), ballot.VotingThreshold) + + // The two silent validators leave the universal-validator set. + for i := 2; i < 4; i++ { + v := coreVals[i] + v.Status = stakingtypes.Unbonded + require.NoError(t, chainApp.StakingKeeper.SetValidator(ctx, v)) + } + + // Recompute: 2 eligible, threshold 2, both preserved votes YES. This is the + // bug — the recomputed ballot already satisfies its own threshold, yet it is + // returned PENDING and no vote is left to cast. + uvMs := uvalidatorkeeper.NewMsgServerImpl(chainApp.UvalidatorKeeper) + recomputeResp, err := uvMs.RecomputeBallotQuorum(sdk.WrapSDKContext(ctx), &uvalidatortypes.MsgRecomputeBallotQuorum{ + Signer: admin, + BallotId: ballotKey, + }) + require.NoError(t, err) + require.Equal(t, int64(2), recomputeResp.NewEligibleCount) + require.Equal(t, int64(2), recomputeResp.NewVotingThreshold) + require.Equal(t, uvalidatortypes.BallotStatus_BALLOT_STATUS_PENDING, recomputeResp.NewStatus) + + stuck, err := chainApp.UvalidatorKeeper.Ballots.Get(ctx, ballotKey) + require.NoError(t, err) + require.True(t, stuck.IsUnreachablePending(), "no eligible voter is left to cast a vote") + yes, _ := stuck.CountVotes() + require.Equal(t, 2, yes, "recompute preserved both YES votes") + + // A third vote cannot rescue it: the remaining voters have already voted, and + // the departed ones are no longer eligible. + valAddr, err := sdk.ValAddressFromBech32(coreVals[2].OperatorAddress) + require.NoError(t, err) + require.Error(t, + utils.ExecVoteInbound(t, ctx, chainApp, universalVals[2], sdk.AccAddress(valAddr).String(), inbound), + "the ballot is genuinely stuck, not merely waiting") + + recipient := common.HexToAddress(inbound.Recipient) + require.Equal(t, 0, prc20BalanceOf(t, chainApp, ctx, recipient).Sign()) + + // The escape hatch. + ms := uexecutorkeeper.NewMsgServerImpl(chainApp.UexecutorKeeper) + resp, err := ms.ExecuteStuckInbound(sdk.WrapSDKContext(ctx), &uexecutortypes.MsgExecuteStuckInbound{ + Signer: admin, + Inbound: inbound, + }) + require.NoError(t, err) + require.Equal(t, uexecutortypes.GetInboundUniversalTxKey(canonical), resp.UtxId) + + utx, _, err := chainApp.UexecutorKeeper.GetUniversalTx(ctx, resp.UtxId) + require.NoError(t, err) + require.Len(t, utx.PcTx, 1) + require.Equal(t, "SUCCESS", utx.PcTx[0].Status) + require.Empty(t, utx.OutboundTx, "the user is paid on Push, not refunded on the source chain") + + amount, ok := new(big.Int).SetString(inbound.Amount, 10) + require.True(t, ok) + require.Equal(t, 0, prc20BalanceOf(t, chainApp, ctx, recipient).Cmp(amount)) + + final, err := chainApp.UvalidatorKeeper.Ballots.Get(ctx, ballotKey) + require.NoError(t, err) + require.Equal(t, uvalidatortypes.BallotStatus_BALLOT_STATUS_PASSED, final.Status) + + isPending, err := chainApp.UexecutorKeeper.IsPendingInbound(ctx, canonical) + require.NoError(t, err) + require.False(t, isPending, "the pending audit-trail entry must be cleared") +} + +// assertNoUtxOrMint checks a refusal was total: no UniversalTx was written and +// nothing was minted to the recipient. +func assertNoUtxOrMint(t *testing.T, chainApp *app.ChainApp, ctx sdk.Context, inbound *uexecutortypes.Inbound) { + t.Helper() + utxKey := uexecutortypes.GetInboundUniversalTxKey(*inbound) + has, err := chainApp.UexecutorKeeper.HasUniversalTx(ctx, utxKey) + require.NoError(t, err) + require.False(t, has, fmt.Sprintf("a refused execute must not leave a UniversalTx behind (%s)", utxKey)) + + require.Equal(t, 0, prc20BalanceOf(t, chainApp, ctx, common.HexToAddress(inbound.Recipient)).Sign(), + "a refused execute must not mint") +} diff --git a/test/integration/uexecutor/execute_stuck_outbound_test.go b/test/integration/uexecutor/execute_stuck_outbound_test.go new file mode 100644 index 000000000..936673c3c --- /dev/null +++ b/test/integration/uexecutor/execute_stuck_outbound_test.go @@ -0,0 +1,466 @@ +package integrationtest + +import ( + "math/big" + "strings" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + + "github.com/pushchain/push-chain-node/app" + utils "github.com/pushchain/push-chain-node/test/utils" + chainutils "github.com/pushchain/push-chain-node/utils" + uexecutorkeeper "github.com/pushchain/push-chain-node/x/uexecutor/keeper" + uexecutortypes "github.com/pushchain/push-chain-node/x/uexecutor/types" + uvalidatortypes "github.com/pushchain/push-chain-node/x/uvalidator/types" +) + +const stuckOutboundAdmin = "push1fgaewhyd9fkwtqaj9c233letwcuey6dgly9gv9" + +// observedTxHash is deliberately mixed-case: the vote path lowercases an EVM tx +// hash before it hashes the observation into a ballot key, so a hatch that +// canonicalized later (or not at all) would derive a key no ballot sits under. +const observedTxHash = "0xAABBCCDDEEFF00112233445566778899AABBCCDDEEFF00112233445566778899" + +// setupStuckOutbound builds a chain app carrying one PENDING outbound (created +// by a real inbound-initiated withdraw) and sets the uvalidator admin. +func setupStuckOutbound(t *testing.T) ( + chainApp *app.ChainApp, + ctx sdk.Context, + utxId string, + outbound *uexecutortypes.OutboundTx, + admin string, +) { + t.Helper() + chainApp, ctx, _, utxId, outbound, _ = setupOutboundVotingTest(t, 4) + + admin = stuckOutboundAdmin + require.NoError(t, chainApp.UvalidatorKeeper.Params.Set(ctx, uvalidatortypes.Params{Admin: admin})) + + require.Equal(t, uexecutortypes.Status_PENDING, outbound.OutboundStatus) + return chainApp, ctx, utxId, outbound, admin +} + +// stuckObservation is the destination-chain observation the validators voted on. +func stuckObservation(success bool, errorMsg, gasFeeUsed string) uexecutortypes.OutboundObservation { + return uexecutortypes.OutboundObservation{ + Success: success, + ErrorMsg: errorMsg, + TxHash: observedTxHash, + BlockHeight: 42, + GasFeeUsed: gasFeeUsed, + } +} + +// outboundBallotKeyFor derives the ballot key the keeper will derive, i.e. over +// the canonicalized observation. +func outboundBallotKeyFor(t *testing.T, utxId string, outbound *uexecutortypes.OutboundTx, obs uexecutortypes.OutboundObservation) string { + t.Helper() + obs.TxHash = chainutils.LenientCanonicalizeTxHash(outbound.DestinationChain, obs.TxHash) + obs.GasFeeUsed = strings.TrimSpace(obs.GasFeeUsed) + obs.ErrorMsg = strings.TrimSpace(obs.ErrorMsg) + key, err := uexecutortypes.GetOutboundBallotKey(utxId, outbound.Id, obs) + require.NoError(t, err) + return key +} + +// seedOutboundBallot stores an outbound ballot under exactly that key, with a +// real eligible-voter list and per-voter vote slots — the F-2026-18147 shapes +// all turn on whether any eligible voter still holds a NOT_YET_VOTED slot. +func seedOutboundBallot( + t *testing.T, + chainApp *app.ChainApp, + ctx sdk.Context, + utxId string, + outbound *uexecutortypes.OutboundTx, + obs uexecutortypes.OutboundObservation, + status uvalidatortypes.BallotStatus, + voters []string, + votes []uvalidatortypes.VoteResult, + threshold int64, +) string { + t.Helper() + require.Len(t, votes, len(voters), "each eligible voter needs exactly one vote slot") + + ballotKey := outboundBallotKeyFor(t, utxId, outbound, obs) + require.NoError(t, chainApp.UvalidatorKeeper.Ballots.Set(ctx, ballotKey, uvalidatortypes.Ballot{ + Id: ballotKey, + BallotType: uvalidatortypes.BallotObservationType_BALLOT_OBSERVATION_TYPE_OUTBOUND_TX, + EligibleVoters: voters, + Votes: votes, + VotingThreshold: threshold, + Status: status, + BlockHeightCreated: 1, + BlockHeightExpiry: 100_000_000, + })) + return ballotKey +} + +func allVotedYes() []uvalidatortypes.VoteResult { + return []uvalidatortypes.VoteResult{ + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + } +} + +// revertRecipientOf mirrors handleFailedOutbound's choice of who gets the +// bridged tokens back. +func revertRecipientOf(outbound *uexecutortypes.OutboundTx) common.Address { + if outbound.RevertInstructions != nil && outbound.RevertInstructions.FundRecipient != "" { + return common.HexToAddress(outbound.RevertInstructions.FundRecipient) + } + return common.HexToAddress(outbound.Sender) +} + +func executeStuckOutbound( + t *testing.T, + chainApp *app.ChainApp, + ctx sdk.Context, + signer, utxId, outboundId string, + obs uexecutortypes.OutboundObservation, +) (*uexecutortypes.MsgExecuteStuckOutboundResponse, error) { + t.Helper() + ms := uexecutorkeeper.NewMsgServerImpl(chainApp.UexecutorKeeper) + return ms.ExecuteStuckOutbound(sdk.WrapSDKContext(ctx), &uexecutortypes.MsgExecuteStuckOutbound{ + Signer: signer, + TxId: outboundId, + UtxId: utxId, + ObservedTx: &obs, + }) +} + +func loadOutbound(t *testing.T, chainApp *app.ChainApp, ctx sdk.Context, utxId, outboundId string) *uexecutortypes.OutboundTx { + t.Helper() + utx, found, err := chainApp.UexecutorKeeper.GetUniversalTx(ctx, utxId) + require.NoError(t, err) + require.True(t, found) + for _, ob := range utx.OutboundTx { + if ob.Id == outboundId { + return ob + } + } + t.Fatalf("outbound %s not found in utx %s", outboundId, utxId) + return nil +} + +// TestExecuteStuckOutbound_ExpiredBallot_Success_Settles is the plain expiry +// case: quorum never formed, so the outbound sat PENDING forever even though the +// destination-chain tx landed. The hatch settles it against the observation. +func TestExecuteStuckOutbound_ExpiredBallot_Success_Settles(t *testing.T) { + chainApp, ctx, utxId, ob, admin := setupStuckOutbound(t) + + // gas_fee_used == GasFee → no excess, so nothing to refund. + obs := stuckObservation(true, "", ob.GasFee) + ballotKey := seedOutboundBallot(t, chainApp, ctx, utxId, ob, obs, + uvalidatortypes.BallotStatus_BALLOT_STATUS_EXPIRED, threeVoters(), allVotedYes(), 3) + + recipient := revertRecipientOf(ob) + before := prc20BalanceOf(t, chainApp, ctx, recipient) + + resp, err := executeStuckOutbound(t, chainApp, ctx, admin, utxId, ob.Id, obs) + require.NoError(t, err, "an EXPIRED outbound ballot must be settleable") + require.Equal(t, ob.Id, resp.OutboundId) + + settled := loadOutbound(t, chainApp, ctx, utxId, ob.Id) + require.Equal(t, uexecutortypes.Status_OBSERVED, settled.OutboundStatus) + require.NotNil(t, settled.ObservedTx) + require.True(t, settled.ObservedTx.Success) + require.Equal(t, strings.ToLower(observedTxHash), settled.ObservedTx.TxHash, + "the stored observation must be the canonical one that was hashed into the ballot key") + + // A successful outbound mints nothing back and refunds no gas. + require.Nil(t, settled.PcRevertExecution, "a successful settlement must not re-mint") + require.Nil(t, settled.PcRefundExecution, "no excess gas, so no refund") + require.Equal(t, 0, prc20BalanceOf(t, chainApp, ctx, recipient).Cmp(before), + "a successful settlement must not move the recipient's balance") + + // Pending index cleared, so the outbound leaves the signing queue. + has, err := chainApp.UexecutorKeeper.PendingOutbounds.Has(ctx, ob.Id) + require.NoError(t, err) + require.False(t, has) + + // EXPIRED is terminal already; MarkBallotFinalized only accepts + // PASSED/REJECTED, so the record is deliberately left alone. + ballot, err := chainApp.UvalidatorKeeper.Ballots.Get(ctx, ballotKey) + require.NoError(t, err) + require.Equal(t, uvalidatortypes.BallotStatus_BALLOT_STATUS_EXPIRED, ballot.Status) +} + +// TestExecuteStuckOutbound_ExpiredBallot_Failure_RevertsAndRefunds pins the +// other half of the same message: the outcome follows observed_tx.success, so a +// failed observation mints the bridged tokens back and refunds the excess gas — +// no separate revert message is needed. +func TestExecuteStuckOutbound_ExpiredBallot_Failure_RevertsAndRefunds(t *testing.T) { + chainApp, ctx, utxId, ob, admin := setupStuckOutbound(t) + + // gas_fee_used (50) < GasFee (111) → excess gas must be refunded too. + obs := stuckObservation(false, "execution reverted", "50") + seedOutboundBallot(t, chainApp, ctx, utxId, ob, obs, + uvalidatortypes.BallotStatus_BALLOT_STATUS_EXPIRED, threeVoters(), allVotedYes(), 3) + + recipient := revertRecipientOf(ob) + before := prc20BalanceOf(t, chainApp, ctx, recipient) + + _, err := executeStuckOutbound(t, chainApp, ctx, admin, utxId, ob.Id, obs) + require.NoError(t, err) + + settled := loadOutbound(t, chainApp, ctx, utxId, ob.Id) + require.Equal(t, uexecutortypes.Status_REVERTED, settled.OutboundStatus) + + require.NotNil(t, settled.PcRevertExecution, "a failed outbound must mint the bridged funds back") + require.Equal(t, "SUCCESS", settled.PcRevertExecution.Status) + + amount, ok := new(big.Int).SetString(ob.Amount, 10) + require.True(t, ok) + require.Equal(t, 0, prc20BalanceOf(t, chainApp, ctx, recipient).Cmp(new(big.Int).Add(before, amount)), + "the revert recipient must be credited the full outbound amount") + + require.NotNil(t, settled.PcRefundExecution, "excess gas must be refunded on failure too") + require.NotEmpty(t, settled.PcRefundExecution.Status) +} + +// TestExecuteStuckOutbound_PendingUnreachable_ThresholdMet_Settles is the +// F-2026-18147 shape on the outbound side: every eligible voter has voted YES +// and the YES count already clears the stored threshold, but the ballot was +// returned PENDING and AddVote rejects repeat votes, so nothing can move it. +func TestExecuteStuckOutbound_PendingUnreachable_ThresholdMet_Settles(t *testing.T) { + chainApp, ctx, utxId, ob, admin := setupStuckOutbound(t) + + obs := stuckObservation(true, "", ob.GasFee) + ballotKey := seedOutboundBallot(t, chainApp, ctx, utxId, ob, obs, + uvalidatortypes.BallotStatus_BALLOT_STATUS_PENDING, threeVoters(), allVotedYes(), + 2) // YES (3) already clears the recomputed threshold + + resp, err := executeStuckOutbound(t, chainApp, ctx, admin, utxId, ob.Id, obs) + require.NoError(t, err, "an unreachable PENDING ballot at threshold must be settleable") + require.Equal(t, ob.Id, resp.OutboundId) + + settled := loadOutbound(t, chainApp, ctx, utxId, ob.Id) + require.Equal(t, uexecutortypes.Status_OBSERVED, settled.OutboundStatus) + require.NotNil(t, settled.ObservedTx) + + // Unlike EXPIRED, this ballot was not terminal, so the hatch drives it there. + ballot, err := chainApp.UvalidatorKeeper.Ballots.Get(ctx, ballotKey) + require.NoError(t, err) + require.Equal(t, uvalidatortypes.BallotStatus_BALLOT_STATUS_PASSED, ballot.Status) +} + +// TestExecuteStuckOutbound_PendingUnreachable_BelowThreshold_Refused covers the +// other stuck shape: nothing can move the ballot, but the validators never +// carried it. Settling would act on an observation the set did not attest. +func TestExecuteStuckOutbound_PendingUnreachable_BelowThreshold_Refused(t *testing.T) { + chainApp, ctx, utxId, ob, admin := setupStuckOutbound(t) + + obs := stuckObservation(true, "", ob.GasFee) + seedOutboundBallot(t, chainApp, ctx, utxId, ob, obs, + uvalidatortypes.BallotStatus_BALLOT_STATUS_PENDING, threeVoters(), + []uvalidatortypes.VoteResult{ + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_FAILURE, + }, + 3) // YES (2) < 3 → unreachable, but never carried + + _, err := executeStuckOutbound(t, chainApp, ctx, admin, utxId, ob.Id, obs) + require.Error(t, err, "a ballot that never met its threshold must not be settled") + require.Contains(t, err.Error(), "against a voting threshold of 3") + + assertOutboundUntouched(t, chainApp, ctx, utxId, ob.Id) +} + +// TestExecuteStuckOutbound_PendingWithUnvotedVoter_Refused is the guard against +// widening the hatch: the YES votes already clear the threshold, but one +// eligible voter still holds a NOT_YET_VOTED slot, so a single normal vote +// finalizes it through the real pipeline. Admin settle must not race that. +func TestExecuteStuckOutbound_PendingWithUnvotedVoter_Refused(t *testing.T) { + chainApp, ctx, utxId, ob, admin := setupStuckOutbound(t) + + obs := stuckObservation(true, "", ob.GasFee) + ballotKey := seedOutboundBallot(t, chainApp, ctx, utxId, ob, obs, + uvalidatortypes.BallotStatus_BALLOT_STATUS_PENDING, threeVoters(), + []uvalidatortypes.VoteResult{ + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_SUCCESS, + uvalidatortypes.VoteResult_VOTE_RESULT_NOT_YET_VOTED, + }, + 2) + + _, err := executeStuckOutbound(t, chainApp, ctx, admin, utxId, ob.Id, obs) + require.Error(t, err, "a PENDING ballot with an unvoted eligible voter can still finalize normally") + require.Contains(t, err.Error(), "admin execute requires PENDING") + + // The ballot must be left votable so the remaining voter can finalize it. + ballot, err := chainApp.UvalidatorKeeper.Ballots.Get(ctx, ballotKey) + require.NoError(t, err) + require.Equal(t, uvalidatortypes.BallotStatus_BALLOT_STATUS_PENDING, ballot.Status) + + assertOutboundUntouched(t, chainApp, ctx, utxId, ob.Id) +} + +// TestExecuteStuckOutbound_AlreadySettled_Refused is the idempotency barrier. An +// EXPIRED ballot is left untouched by design, so the ballot gate lets a second +// call through and only the outbound's own status stops it — without which the +// admin could re-mint the same funds repeatedly. +func TestExecuteStuckOutbound_AlreadySettled_Refused(t *testing.T) { + chainApp, ctx, utxId, ob, admin := setupStuckOutbound(t) + + obs := stuckObservation(false, "execution reverted", ob.GasFee) + seedOutboundBallot(t, chainApp, ctx, utxId, ob, obs, + uvalidatortypes.BallotStatus_BALLOT_STATUS_EXPIRED, threeVoters(), allVotedYes(), 3) + + _, err := executeStuckOutbound(t, chainApp, ctx, admin, utxId, ob.Id, obs) + require.NoError(t, err) + + recipient := revertRecipientOf(ob) + afterFirst := prc20BalanceOf(t, chainApp, ctx, recipient) + + _, err = executeStuckOutbound(t, chainApp, ctx, admin, utxId, ob.Id, obs) + require.Error(t, err, "a second settle must be refused") + require.Contains(t, err.Error(), "already finalized") + + require.Equal(t, 0, prc20BalanceOf(t, chainApp, ctx, recipient).Cmp(afterFirst), + "the refused second settle must not have minted again") +} + +// TestExecuteStuckOutbound_VotedOutbound_Refused covers the normal-flow overlap: +// once the validators settled the outbound themselves, its ballot is PASSED and +// the hatch has nothing to do. +func TestExecuteStuckOutbound_VotedOutbound_Refused(t *testing.T) { + chainApp, ctx, vals, utxId, ob, coreVals := setupOutboundVotingTest(t, 4) + require.NoError(t, chainApp.UvalidatorKeeper.Params.Set(ctx, uvalidatortypes.Params{Admin: stuckOutboundAdmin})) + + for i := 0; i < 3; i++ { + valAddr, err := sdk.ValAddressFromBech32(coreVals[i].OperatorAddress) + require.NoError(t, err) + require.NoError(t, utils.ExecVoteOutbound( + t, ctx, chainApp, vals[i], sdk.AccAddress(valAddr).String(), utxId, ob, true, "", ob.GasFee)) + } + require.Equal(t, uexecutortypes.Status_OBSERVED, loadOutbound(t, chainApp, ctx, utxId, ob.Id).OutboundStatus) + + // The vote path's own observation, so the ballot key resolves to the PASSED ballot. + obs := uexecutortypes.OutboundObservation{ + Success: true, + TxHash: "0xobserved-" + ob.Id, + BlockHeight: 1, + GasFeeUsed: ob.GasFee, + } + _, err := executeStuckOutbound(t, chainApp, ctx, stuckOutboundAdmin, utxId, ob.Id, obs) + require.Error(t, err, "a ballot the validators finalized is not stuck") + require.Contains(t, err.Error(), "admin execute requires PENDING") +} + +// TestExecuteStuckOutbound_TamperedObservation_Refused pins the security +// property the ballot-key derivation buys: the admin can only settle against the +// exact observation the validators voted on. One changed field derives a +// different key, which has no ballot at all. +func TestExecuteStuckOutbound_TamperedObservation_Refused(t *testing.T) { + chainApp, ctx, utxId, ob, admin := setupStuckOutbound(t) + + obs := stuckObservation(true, "", ob.GasFee) + seedOutboundBallot(t, chainApp, ctx, utxId, ob, obs, + uvalidatortypes.BallotStatus_BALLOT_STATUS_EXPIRED, threeVoters(), allVotedYes(), 3) + + tampered := obs + tampered.BlockHeight = obs.BlockHeight + 1 + + _, err := executeStuckOutbound(t, chainApp, ctx, admin, utxId, ob.Id, tampered) + require.Error(t, err, "an observation the validators never voted on has no ballot") + require.Contains(t, err.Error(), "ballot for outbound not found") + + assertOutboundUntouched(t, chainApp, ctx, utxId, ob.Id) +} + +func TestExecuteStuckOutbound_BallotNotFound(t *testing.T) { + chainApp, ctx, utxId, ob, admin := setupStuckOutbound(t) + // no ballot seeded + _, err := executeStuckOutbound(t, chainApp, ctx, admin, utxId, ob.Id, stuckObservation(true, "", ob.GasFee)) + require.Error(t, err) + require.Contains(t, err.Error(), "ballot for outbound not found") +} + +func TestExecuteStuckOutbound_AdminAuth_RejectsNonAdmin(t *testing.T) { + chainApp, ctx, utxId, ob, _ := setupStuckOutbound(t) + + obs := stuckObservation(true, "", ob.GasFee) + seedOutboundBallot(t, chainApp, ctx, utxId, ob, obs, + uvalidatortypes.BallotStatus_BALLOT_STATUS_EXPIRED, threeVoters(), allVotedYes(), 3) + + const notAdmin = "push1negskcfqu09j5zvpk7nhvacnwyy2mafffy7r6a" + _, err := executeStuckOutbound(t, chainApp, ctx, notAdmin, utxId, ob.Id, obs) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid admin") + + assertOutboundUntouched(t, chainApp, ctx, utxId, ob.Id) +} + +func TestExecuteStuckOutbound_NilObservedTx_Rejected(t *testing.T) { + chainApp, ctx, utxId, ob, admin := setupStuckOutbound(t) + + ms := uexecutorkeeper.NewMsgServerImpl(chainApp.UexecutorKeeper) + _, err := ms.ExecuteStuckOutbound(sdk.WrapSDKContext(ctx), &uexecutortypes.MsgExecuteStuckOutbound{ + Signer: admin, + TxId: ob.Id, + UtxId: utxId, + ObservedTx: nil, + }) + require.Error(t, err) + require.Contains(t, err.Error(), "observed_tx is required") +} + +func TestExecuteStuckOutbound_UnknownOutbound_Rejected(t *testing.T) { + chainApp, ctx, utxId, ob, admin := setupStuckOutbound(t) + + const unknown = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + _, err := executeStuckOutbound(t, chainApp, ctx, admin, utxId, unknown, stuckObservation(true, "", ob.GasFee)) + require.Error(t, err) + require.Contains(t, err.Error(), "not found") + + _, err = executeStuckOutbound(t, chainApp, ctx, admin, unknown, ob.Id, stuckObservation(true, "", ob.GasFee)) + require.Error(t, err) + require.Contains(t, err.Error(), "UniversalTx not found") +} + +// TestExecuteStuckOutbound_PrefixedIds_Settles mirrors the vote path: UVs (and +// the operators reading their logs) carry 0x-prefixed IDs, and the handler has +// to strip them exactly once before the keeper lookup. +func TestExecuteStuckOutbound_PrefixedIds_Settles(t *testing.T) { + chainApp, ctx, utxId, ob, admin := setupStuckOutbound(t) + + obs := stuckObservation(true, "", ob.GasFee) + seedOutboundBallot(t, chainApp, ctx, utxId, ob, obs, + uvalidatortypes.BallotStatus_BALLOT_STATUS_EXPIRED, threeVoters(), allVotedYes(), 3) + + resp, err := executeStuckOutbound(t, chainApp, ctx, admin, "0x"+utxId, "0x"+ob.Id, obs) + require.NoError(t, err) + require.Equal(t, ob.Id, resp.OutboundId) + + require.Equal(t, uexecutortypes.Status_OBSERVED, loadOutbound(t, chainApp, ctx, utxId, ob.Id).OutboundStatus) +} + +// The keeper re-runs that validation, so the malformed value is refused even on +// a direct keeper call that never passed through ValidateBasic. +func TestExecuteStuckOutbound_MalformedGasFeeUsed_RefusedByKeeper(t *testing.T) { + chainApp, ctx, utxId, ob, admin := setupStuckOutbound(t) + + _, err := executeStuckOutbound(t, chainApp, ctx, admin, utxId, ob.Id, stuckObservation(true, "", "not-a-number")) + require.Error(t, err) + require.Contains(t, err.Error(), "observed_tx.gas_fee_used must be a valid uint256") + + assertOutboundUntouched(t, chainApp, ctx, utxId, ob.Id) +} + +// assertOutboundUntouched checks a refusal was total: the outbound is still +// PENDING and still queued for signing. +func assertOutboundUntouched(t *testing.T, chainApp *app.ChainApp, ctx sdk.Context, utxId, outboundId string) { + t.Helper() + ob := loadOutbound(t, chainApp, ctx, utxId, outboundId) + require.Equal(t, uexecutortypes.Status_PENDING, ob.OutboundStatus, "a refused settle must not move the outbound") + require.Nil(t, ob.ObservedTx) + + has, err := chainApp.UexecutorKeeper.PendingOutbounds.Has(ctx, outboundId) + require.NoError(t, err) + require.True(t, has, "a refused settle must leave the outbound queued") +} diff --git a/x/uexecutor/keeper/admin_execute.go b/x/uexecutor/keeper/admin_execute.go new file mode 100644 index 000000000..a341f9e0b --- /dev/null +++ b/x/uexecutor/keeper/admin_execute.go @@ -0,0 +1,103 @@ +package keeper + +import ( + "context" + "fmt" + + "cosmossdk.io/errors" + sdkErrors "github.com/cosmos/cosmos-sdk/types/errors" + + "github.com/pushchain/push-chain-node/x/uexecutor/types" + uvalidatortypes "github.com/pushchain/push-chain-node/x/uvalidator/types" +) + +// ExecuteStuckInbound finalizes a stuck inbound ballot as PASSED and runs the +// pipeline a finalizing vote would have, so the user receives the funds. +// +// Sibling of RevertStuckInbound, which is the wrong resolution when a ballot's +// preserved YES votes already clear the recomputed threshold — RecomputeBallotQuorum +// leaves those PENDING forever, and a refund was the only hatch (F-2026-18147). +// +// Requires PENDING-unreachable with YES >= VotingThreshold. EXPIRED belongs to +// RevertStuckInbound: no quorum ever formed, so there is nothing to act on. +// +// The ballot key is derived from the supplied inbound, so the admin cannot +// execute anything other than the payload the validators voted on. +func (k Keeper) ExecuteStuckInbound(ctx context.Context, inbound types.Inbound) (utxId string, err error) { + // Same canonical form as the vote path, so the admin-supplied payload + // derives the same ballot key / UTX key the votes did. + inbound.Canonicalize() + + if vErr := inbound.ValidateBasic(); vErr != nil { + return "", errors.Wrap(sdkErrors.ErrInvalidRequest, vErr.Error()) + } + + ballotKey, err := types.GetInboundBallotKey(inbound) + if err != nil { + return "", errors.Wrap(sdkErrors.ErrInvalidRequest, fmt.Sprintf("failed to derive ballot key: %s", err)) + } + + ballot, err := k.uvalidatorKeeper.GetBallot(ctx, ballotKey) + if err != nil { + return "", errors.Wrap(sdkErrors.ErrNotFound, fmt.Sprintf("ballot for inbound not found (key=%s): %s", ballotKey, err)) + } + + if gErr := requireCarriedUnreachablePending(ballotKey, ballot); gErr != nil { + return "", errors.Wrap(sdkErrors.ErrInvalidRequest, + fmt.Sprintf("%s. An EXPIRED ballot never reached quorum at all - use MsgRevertStuckInbound to refund this inbound on the source chain instead", + gErr)) + } + + universalTxKey := types.GetInboundUniversalTxKey(inbound) + if has, hErr := k.HasUniversalTx(ctx, universalTxKey); hErr != nil { + return "", fmt.Errorf("failed to check utx existence: %w", hErr) + } else if has { + return "", errors.Wrap(sdkErrors.ErrInvalidRequest, + fmt.Sprintf("universal tx %s already exists for this inbound", universalTxKey)) + } + + // Finalize before executing, mirroring the vote path's ordering: uvalidator + // marks the ballot PASSED (firing the PendingInbounds bookkeeping hook) and + // only then does the post-finalization pipeline run. The hook may clear the + // pending entry the pipeline would otherwise clear; that removal is a no-op + // on an absent key. + if fErr := k.uvalidatorKeeper.MarkBallotFinalized(ctx, ballotKey, uvalidatortypes.BallotStatus_BALLOT_STATUS_PASSED); fErr != nil { + return "", fmt.Errorf("failed to finalize ballot %s: %w", ballotKey, fErr) + } + + yes, _ := ballot.CountVotes() + k.Logger().Info("admin execute: stuck inbound ballot finalized", + "utx_id", universalTxKey, + "ballot_id", ballotKey, + "yes_votes", yes, + "voting_threshold", ballot.VotingThreshold, + "eligible_voters", len(ballot.EligibleVoters), + "source_chain", inbound.SourceChain, + "amount", inbound.Amount, + ) + + if execErr := k.finalizeInboundAndExecute(ctx, inbound, universalTxKey); execErr != nil { + return "", execErr + } + + return universalTxKey, nil +} + +// requireCarriedUnreachablePending accepts only the shape an admin hatch may +// finalize: PENDING, no vote left to cast, YES already at the threshold. +// Threshold is checked explicitly, not inferred — both vote sites hardcode +// SUCCESS today, but this must not depend on that. +func requireCarriedUnreachablePending(ballotKey string, ballot uvalidatortypes.Ballot) error { + if !ballot.IsUnreachablePending() { + return fmt.Errorf("ballot %s status is %s; admin execute requires PENDING with every eligible voter already voted (no further vote can be cast). "+ + "A pending ballot that still has an unvoted eligible voter has to be finalized by that voter through the normal vote flow", + ballotKey, ballot.Status.String()) + } + + if yes, _ := ballot.CountVotes(); int64(yes) < ballot.VotingThreshold { + return fmt.Errorf("ballot %s has %d YES vote(s) against a voting threshold of %d; admin execute may only finalize a ballot the validators actually carried", + ballotKey, yes, ballot.VotingThreshold) + } + + return nil +} diff --git a/x/uexecutor/keeper/admin_execute_outbound.go b/x/uexecutor/keeper/admin_execute_outbound.go new file mode 100644 index 000000000..a1edaf8cd --- /dev/null +++ b/x/uexecutor/keeper/admin_execute_outbound.go @@ -0,0 +1,119 @@ +package keeper + +import ( + "context" + "fmt" + "strings" + + "cosmossdk.io/errors" + sdkErrors "github.com/cosmos/cosmos-sdk/types/errors" + + "github.com/pushchain/push-chain-node/utils" + "github.com/pushchain/push-chain-node/x/uexecutor/types" + uvalidatortypes "github.com/pushchain/push-chain-node/x/uvalidator/types" +) + +// ExecuteStuckOutbound settles an outbound whose ballot can no longer finalize, +// running the same pipeline a finalizing vote would have. Accepts EXPIRED, and +// PENDING-unreachable with the threshold met (F-2026-18147). +// +// The outcome follows observed_tx.success — success settles, failure mints the +// tokens back and refunds gas — so one message covers both. The ballot key is +// derived from the supplied observation, so the admin can only settle against +// something validators actually voted on. +func (k Keeper) ExecuteStuckOutbound( + ctx context.Context, + utxId string, + outboundId string, + observedTx types.OutboundObservation, +) (string, error) { + // Located first because canonicalizing the tx hash needs DestinationChain. + utx, found, err := k.GetUniversalTx(ctx, utxId) + if err != nil { + return "", err + } + if !found { + return "", errors.Wrap(sdkErrors.ErrNotFound, fmt.Sprintf("UniversalTx not found: %s", utxId)) + } + if utx.OutboundTx == nil { + return "", errors.Wrap(sdkErrors.ErrNotFound, fmt.Sprintf("no outbound tx found in UniversalTx %s", utxId)) + } + + var outbound types.OutboundTx + found = false + for _, ob := range utx.OutboundTx { + if ob.Id == outboundId { + outbound = *ob + found = true + break + } + } + if !found { + return "", errors.Wrap(sdkErrors.ErrNotFound, fmt.Sprintf("outbound %s not found in UniversalTx %s", outboundId, utxId)) + } + + // Canonicalize before deriving the key — it is a digest over these fields. + observedTx.TxHash = utils.LenientCanonicalizeTxHash(outbound.DestinationChain, observedTx.TxHash) + observedTx.GasFeeUsed = strings.TrimSpace(observedTx.GasFeeUsed) + observedTx.ErrorMsg = strings.TrimSpace(observedTx.ErrorMsg) + + if vErr := observedTx.ValidateBasic(); vErr != nil { + return "", errors.Wrap(sdkErrors.ErrInvalidRequest, vErr.Error()) + } + + ballotKey, err := types.GetOutboundBallotKey(utxId, outboundId, observedTx) + if err != nil { + return "", errors.Wrap(sdkErrors.ErrInvalidRequest, fmt.Sprintf("failed to derive ballot key: %s", err)) + } + + ballot, err := k.uvalidatorKeeper.GetBallot(ctx, ballotKey) + if err != nil { + return "", errors.Wrap(sdkErrors.ErrNotFound, fmt.Sprintf("ballot for outbound not found (key=%s): %s", ballotKey, err)) + } + + // EXPIRED is already terminal; PENDING-unreachable is terminal in fact but + // not in the record, so only that one needs the ballot driven to PASSED. + finalizeBallot := false + if ballot.Status != uvalidatortypes.BallotStatus_BALLOT_STATUS_EXPIRED { + if gErr := requireCarriedUnreachablePending(ballotKey, ballot); gErr != nil { + return "", errors.Wrap(sdkErrors.ErrInvalidRequest, + fmt.Sprintf("%s. Admin execute of an outbound requires an EXPIRED ballot, or a PENDING one every eligible voter has already voted on whose YES votes meet the threshold", + gErr)) + } + finalizeBallot = true + } + + // Idempotency barrier. An EXPIRED ballot is deliberately left untouched + // below, so the outbound's own status is the only record of settlement. + if outbound.OutboundStatus != types.Status_PENDING { + return "", errors.Wrap(sdkErrors.ErrInvalidRequest, + fmt.Sprintf("outbound with key %s is already finalized (status %s)", outboundId, outbound.OutboundStatus.String())) + } + + // EXPIRED is left alone: MarkBallotFinalized takes only PASSED/REJECTED. + if finalizeBallot { + if fErr := k.uvalidatorKeeper.MarkBallotFinalized(ctx, ballotKey, uvalidatortypes.BallotStatus_BALLOT_STATUS_PASSED); fErr != nil { + return "", fmt.Errorf("failed to finalize ballot %s: %w", ballotKey, fErr) + } + } + + yes, _ := ballot.CountVotes() + k.Logger().Info("admin execute: settling stuck outbound", + "utx_id", utxId, + "outbound_id", outboundId, + "ballot_id", ballotKey, + "ballot_status", ballot.Status.String(), + "ballot_finalized", finalizeBallot, + "yes_votes", yes, + "voting_threshold", ballot.VotingThreshold, + "eligible_voters", len(ballot.EligibleVoters), + "dest_chain", outbound.DestinationChain, + "success", observedTx.Success, + ) + + if settleErr := k.finalizeOutboundAndSettle(ctx, utxId, outboundId, outbound, observedTx); settleErr != nil { + return "", settleErr + } + + return outboundId, nil +} diff --git a/x/uexecutor/keeper/admin_revert.go b/x/uexecutor/keeper/admin_revert.go index 1a141954d..e68df558f 100644 --- a/x/uexecutor/keeper/admin_revert.go +++ b/x/uexecutor/keeper/admin_revert.go @@ -53,7 +53,9 @@ import ( // chain instead of receiving bridged funds on Push. For a ballot whose YES votes // met the threshold that is the less generous of the two resolutions, and it is // the deliberate trade for a change that stays inside the module that owns -// inbound execution. +// inbound execution. MsgExecuteStuckInbound is the sibling hatch for that case +// and executes instead; which of the two to use stays the admin's call, so this +// one deliberately keeps accepting PENDING-unreachable. // // The ballot record itself is left untouched. The HasUniversalTx guard below is // the idempotency barrier, and mutating ballot status from x/uexecutor would diff --git a/x/uexecutor/keeper/msg_server.go b/x/uexecutor/keeper/msg_server.go index 97c292ad3..bfa00958d 100755 --- a/x/uexecutor/keeper/msg_server.go +++ b/x/uexecutor/keeper/msg_server.go @@ -233,3 +233,77 @@ func (ms msgServer) RevertStuckInbound(ctx context.Context, msg *types.MsgRevert OutboundId: outboundId, }, nil } + +// ExecuteStuckInbound is the admin escape hatch — see Keeper.ExecuteStuckInbound. +func (ms msgServer) ExecuteStuckInbound(ctx context.Context, msg *types.MsgExecuteStuckInbound) (*types.MsgExecuteStuckInboundResponse, error) { + ms.k.Logger().Info("msg: ExecuteStuckInbound", "signer", msg.Signer) + + admin, err := ms.k.uvalidatorKeeper.GetAdmin(ctx) + if err != nil { + return nil, errors.Wrap(err, "failed to read uvalidator admin") + } + if admin != msg.Signer { + return nil, errors.Wrapf(govtypes.ErrInvalidSigner, "invalid admin; expected %s, got %s", admin, msg.Signer) + } + + if msg.Inbound == nil { + return nil, errors.Wrap(sdkErrors.ErrInvalidRequest, "inbound is required") + } + + utxId, err := ms.k.ExecuteStuckInbound(ctx, *msg.Inbound) + if err != nil { + return nil, err + } + + sdkCtx := sdk.UnwrapSDKContext(ctx) + sdkCtx.EventManager().EmitEvent(sdk.NewEvent( + "inbound_executed_by_admin", + sdk.NewAttribute("admin", msg.Signer), + sdk.NewAttribute("utx_id", utxId), + sdk.NewAttribute("source_chain", msg.Inbound.SourceChain), + sdk.NewAttribute("amount", msg.Inbound.Amount), + )) + + return &types.MsgExecuteStuckInboundResponse{ + UtxId: utxId, + }, nil +} + +// ExecuteStuckOutbound is the admin escape hatch — see Keeper.ExecuteStuckOutbound. +func (ms msgServer) ExecuteStuckOutbound(ctx context.Context, msg *types.MsgExecuteStuckOutbound) (*types.MsgExecuteStuckOutboundResponse, error) { + ms.k.Logger().Info("msg: ExecuteStuckOutbound", "signer", msg.Signer) + + admin, err := ms.k.uvalidatorKeeper.GetAdmin(ctx) + if err != nil { + return nil, errors.Wrap(err, "failed to read uvalidator admin") + } + if admin != msg.Signer { + return nil, errors.Wrapf(govtypes.ErrInvalidSigner, "invalid admin; expected %s, got %s", admin, msg.Signer) + } + + if msg.ObservedTx == nil { + return nil, errors.Wrap(sdkErrors.ErrInvalidRequest, "observed_tx is required") + } + + // Normalize IDs: strip 0x prefix, as VoteOutbound does. + utxId := strings.TrimPrefix(msg.UtxId, "0x") + outboundId := strings.TrimPrefix(msg.TxId, "0x") + + settledId, err := ms.k.ExecuteStuckOutbound(ctx, utxId, outboundId, *msg.ObservedTx) + if err != nil { + return nil, err + } + + sdkCtx := sdk.UnwrapSDKContext(ctx) + sdkCtx.EventManager().EmitEvent(sdk.NewEvent( + "outbound_executed_by_admin", + sdk.NewAttribute("admin", msg.Signer), + sdk.NewAttribute("utx_id", utxId), + sdk.NewAttribute("outbound_id", settledId), + sdk.NewAttribute("success", fmt.Sprintf("%t", msg.ObservedTx.Success)), + )) + + return &types.MsgExecuteStuckOutboundResponse{ + OutboundId: settledId, + }, nil +} diff --git a/x/uexecutor/keeper/msg_vote_inbound.go b/x/uexecutor/keeper/msg_vote_inbound.go index 25897bb99..ea1a012c8 100644 --- a/x/uexecutor/keeper/msg_vote_inbound.go +++ b/x/uexecutor/keeper/msg_vote_inbound.go @@ -93,6 +93,19 @@ func (k Keeper) VoteInbound(ctx context.Context, universalValidator sdk.ValAddre } // --- Ballot finalized: always create UTX from here on --- + return k.finalizeInboundAndExecute(ctx, inbound, universalTxKey) +} + +// finalizeInboundAndExecute runs the post-finalization pipeline for an inbound +// whose ballot has reached PASSED: normalize, create the UniversalTx, drop the +// pending entry, then validate and execute. +// +// Shared by the normal vote path (VoteInbound) and the admin escape hatch +// (ExecuteStuckInbound) so a finalized inbound resolves identically whichever +// route finalized its ballot. +func (k Keeper) finalizeInboundAndExecute(ctx context.Context, inbound types.Inbound, universalTxKey string) error { + sdkCtx := sdk.UnwrapSDKContext(ctx) + k.Logger().Info("inbound ballot finalized, creating utx", "utx_key", universalTxKey, "source_chain", inbound.SourceChain) // Normalize inbound after finalization: strip irrelevant fields, decode raw_payload. diff --git a/x/uexecutor/keeper/msg_vote_outbound.go b/x/uexecutor/keeper/msg_vote_outbound.go index ae9a5b726..adb7a0429 100644 --- a/x/uexecutor/keeper/msg_vote_outbound.go +++ b/x/uexecutor/keeper/msg_vote_outbound.go @@ -107,6 +107,23 @@ func (k Keeper) VoteOutbound( return nil } + return k.finalizeOutboundAndSettle(ctx, utxId, outboundId, outbound, observedTx) +} + +// finalizeOutboundAndSettle runs the post-finalization pipeline for an outbound +// whose ballot has reached a terminal-and-settled state: record the observation, +// drop the pending entry, then settle (refund if the observation failed). +// +// Shared by the normal vote path (VoteOutbound) and the admin escape hatch +// (ExecuteStuckOutbound) so a settled outbound resolves identically whichever +// route finalized its ballot. +func (k Keeper) finalizeOutboundAndSettle( + ctx context.Context, + utxId string, + outboundId string, + outbound types.OutboundTx, + observedTx types.OutboundObservation, +) error { // Step 5: Update outbound state to OBSERVED outbound.OutboundStatus = types.Status_OBSERVED outbound.ObservedTx = &observedTx diff --git a/x/uexecutor/types/expected_keepers.go b/x/uexecutor/types/expected_keepers.go index c03a68e58..4130a8cfb 100644 --- a/x/uexecutor/types/expected_keepers.go +++ b/x/uexecutor/types/expected_keepers.go @@ -125,6 +125,10 @@ type UValidatorKeeper interface { GetEligibleVoters(ctx context.Context) ([]uvalidatortypes.UniversalValidator, error) GetBallot(ctx context.Context, id string) (uvalidatortypes.Ballot, error) GetAdmin(ctx context.Context) (string, error) + // MarkBallotFinalized drives a ballot to PASSED/REJECTED. Needed by the + // ExecuteStuckInbound escape hatch, which finalizes a ballot the vote flow + // can no longer finalize on its own. + MarkBallotFinalized(ctx context.Context, id string, status uvalidatortypes.BallotStatus) error } // ParamSubspace defines the expected Subspace interface for parameters. diff --git a/x/uexecutor/types/msg_execute_stuck_outbound.go b/x/uexecutor/types/msg_execute_stuck_outbound.go new file mode 100644 index 000000000..c981acf6e --- /dev/null +++ b/x/uexecutor/types/msg_execute_stuck_outbound.go @@ -0,0 +1,35 @@ +package types + +import ( + "strings" + + "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +var ( + _ sdk.Msg = &MsgExecuteStuckOutbound{} +) + +// ValidateBasic mirrors MsgVoteOutbound: the admin must supply exactly the +// observation the validators voted on. +func (msg *MsgExecuteStuckOutbound) ValidateBasic() error { + if _, err := sdk.AccAddressFromBech32(msg.Signer); err != nil { + return errors.Wrap(err, "invalid signer address") + } + + if strings.TrimSpace(msg.TxId) == "" { + return errors.Wrap(sdkerrors.ErrInvalidRequest, "tx_id cannot be empty") + } + + if strings.TrimSpace(msg.UtxId) == "" { + return errors.Wrap(sdkerrors.ErrInvalidRequest, "utx_id cannot be empty") + } + + if msg.ObservedTx == nil { + return errors.Wrap(sdkerrors.ErrInvalidRequest, "observed_tx cannot be nil") + } + + return msg.ObservedTx.ValidateBasic() +} diff --git a/x/uexecutor/types/msg_execute_stuck_outbound_test.go b/x/uexecutor/types/msg_execute_stuck_outbound_test.go new file mode 100644 index 000000000..ef74c95ed --- /dev/null +++ b/x/uexecutor/types/msg_execute_stuck_outbound_test.go @@ -0,0 +1,130 @@ +package types_test + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/pushchain/push-chain-node/x/uexecutor/types" +) + +const stuckOutboundSigner = "push1fgaewhyd9fkwtqaj9c233letwcuey6dgly9gv9" + +func newMsgExecuteStuckOutbound(obs *types.OutboundObservation) *types.MsgExecuteStuckOutbound { + return &types.MsgExecuteStuckOutbound{ + Signer: stuckOutboundSigner, + TxId: "outbound-1", + UtxId: "utx-1", + ObservedTx: obs, + } +} + +func successObservation(gasFeeUsed string) *types.OutboundObservation { + return &types.OutboundObservation{ + Success: true, + TxHash: "0x" + strings.Repeat("ab", 32), + BlockHeight: 42, + GasFeeUsed: gasFeeUsed, + } +} + +// gas_fee_used feeds both the outbound ballot key and the refund arithmetic, so +// MsgExecuteStuckOutbound has to admit exactly what MsgVoteOutbound admits — +// including the length cap and uint256 range check from F-2026-18798. +func TestMsgExecuteStuckOutbound_ValidateBasic_GasFeeUsed(t *testing.T) { + cases := []struct { + name string + gasFeeUsed string + expectErr string + }{ + {name: "valid", gasFeeUsed: "1000"}, + {name: "zero is valid", gasFeeUsed: "0"}, + {name: "empty", gasFeeUsed: "", expectErr: "observed_tx.gas_fee_used is required"}, + {name: "non-numeric", gasFeeUsed: "not-a-number", expectErr: "observed_tx.gas_fee_used must be a valid uint256"}, + {name: "negative", gasFeeUsed: "-1", expectErr: "observed_tx.gas_fee_used must be a valid uint256"}, + {name: "over uint256 range", gasFeeUsed: strings.Repeat("9", 78), expectErr: "value exceeds the uint256 range"}, + {name: "over length cap", gasFeeUsed: strings.Repeat("1", 81), expectErr: "exceeds the maximum of 80 characters"}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := newMsgExecuteStuckOutbound(successObservation(tc.gasFeeUsed)).ValidateBasic() + if tc.expectErr == "" { + require.NoError(t, err) + return + } + require.Error(t, err) + require.Contains(t, err.Error(), tc.expectErr) + }) + } +} + +func TestMsgExecuteStuckOutbound_ValidateBasic_RequiredFields(t *testing.T) { + require.NoError(t, newMsgExecuteStuckOutbound(successObservation("100")).ValidateBasic()) + + bad := newMsgExecuteStuckOutbound(successObservation("100")) + bad.Signer = "not-bech32" + require.ErrorContains(t, bad.ValidateBasic(), "invalid signer address") + + bad = newMsgExecuteStuckOutbound(successObservation("100")) + bad.TxId = " " + require.ErrorContains(t, bad.ValidateBasic(), "tx_id cannot be empty") + + bad = newMsgExecuteStuckOutbound(successObservation("100")) + bad.UtxId = "" + require.ErrorContains(t, bad.ValidateBasic(), "utx_id cannot be empty") + + require.ErrorContains(t, newMsgExecuteStuckOutbound(nil).ValidateBasic(), "observed_tx cannot be nil") + + // Success requires a tx hash and a block height. + bad = newMsgExecuteStuckOutbound(successObservation("100")) + bad.ObservedTx.TxHash = "" + require.ErrorContains(t, bad.ValidateBasic(), "observed_tx.tx_hash required when success=true") + + bad = newMsgExecuteStuckOutbound(successObservation("100")) + bad.ObservedTx.BlockHeight = 0 + require.ErrorContains(t, bad.ValidateBasic(), "observed_tx.block_height must be > 0 when success=true") + + // A failed observation may carry no tx hash — but if it does, it needs a height. + require.NoError(t, newMsgExecuteStuckOutbound(&types.OutboundObservation{ + Success: false, ErrorMsg: "reverted", GasFeeUsed: "100", + }).ValidateBasic()) + + require.ErrorContains(t, newMsgExecuteStuckOutbound(&types.OutboundObservation{ + Success: false, ErrorMsg: "reverted", GasFeeUsed: "100", TxHash: "0xdead", + }).ValidateBasic(), "observed_tx.block_height must be > 0 when tx_hash is provided") +} + +// The admin hatch and the validator vote path must admit the same observations: +// anything the vote path refuses can never have produced a ballot for the hatch +// to settle against. +func TestMsgExecuteStuckOutbound_MatchesVoteOutboundAdmission(t *testing.T) { + observations := []*types.OutboundObservation{ + successObservation("100"), + successObservation(""), + successObservation("not-a-number"), + successObservation(strings.Repeat("9", 78)), + {Success: false, ErrorMsg: "reverted", GasFeeUsed: "0"}, + {Success: false, GasFeeUsed: "1", TxHash: "0xdead"}, + {Success: true, GasFeeUsed: "1", BlockHeight: 0, TxHash: "0xdead"}, + } + + for i, obs := range observations { + voteMsg := &types.MsgVoteOutbound{ + Signer: stuckOutboundSigner, + TxId: "outbound-1", + UtxId: "utx-1", + ObservedTx: obs, + } + voteErr := voteMsg.ValidateBasic() + hatchErr := newMsgExecuteStuckOutbound(obs).ValidateBasic() + + if voteErr == nil { + require.NoError(t, hatchErr, "observation %d accepted by the vote path must be accepted by the hatch", i) + continue + } + require.Error(t, hatchErr, "observation %d refused by the vote path must be refused by the hatch", i) + require.Equal(t, voteErr.Error(), hatchErr.Error(), "observation %d must be refused for the same reason", i) + } +} diff --git a/x/uexecutor/types/msg_vote_outbound.go b/x/uexecutor/types/msg_vote_outbound.go index ae73d4327..6056722bd 100644 --- a/x/uexecutor/types/msg_vote_outbound.go +++ b/x/uexecutor/types/msg_vote_outbound.go @@ -66,38 +66,5 @@ func (msg *MsgVoteOutbound) ValidateBasic() error { } // Validate observed_tx content - obs := msg.ObservedTx - - // gas_fee_used is always required — the external chain consumes gas regardless - // of success or failure, and excess gas must be refundable in both cases. - if strings.TrimSpace(obs.GasFeeUsed) == "" { - return errors.Wrap(sdkerrors.ErrInvalidRequest, - "observed_tx.gas_fee_used is required") - } - // Length-capped, range-checked uint256 parse — see F-2026-18798. The value - // also feeds the outbound ballot key, so a malformed one must never be voted. - if _, err := ValidateUint256String(obs.GasFeeUsed, "observed_tx.gas_fee_used must be a valid uint256"); err != nil { - return err - } - - if obs.Success { - // Success additionally requires tx_hash and block_height. - if strings.TrimSpace(obs.TxHash) == "" { - return errors.Wrap(sdkerrors.ErrInvalidRequest, - "observed_tx.tx_hash required when success=true") - } - if obs.BlockHeight == 0 { - return errors.Wrap(sdkerrors.ErrInvalidRequest, - "observed_tx.block_height must be > 0 when success=true") - } - } else { - // Failure case: tx_hash MAY be empty. - // BUT if tx_hash is present, block_height must be > 0. - if strings.TrimSpace(obs.TxHash) != "" && obs.BlockHeight == 0 { - return errors.Wrap(sdkerrors.ErrInvalidRequest, - "observed_tx.block_height must be > 0 when tx_hash is provided") - } - } - - return nil + return msg.ObservedTx.ValidateBasic() } diff --git a/x/uexecutor/types/outbound_observation.go b/x/uexecutor/types/outbound_observation.go new file mode 100644 index 000000000..d987737f6 --- /dev/null +++ b/x/uexecutor/types/outbound_observation.go @@ -0,0 +1,45 @@ +package types + +import ( + "strings" + + "cosmossdk.io/errors" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +// ValidateBasic sanity-checks a destination-chain observation. Shared by +// MsgVoteOutbound and MsgExecuteStuckOutbound so the two cannot drift. +func (obs *OutboundObservation) ValidateBasic() error { + // gas_fee_used is always required — the external chain consumes gas regardless + // of success or failure, and excess gas must be refundable in both cases. + if strings.TrimSpace(obs.GasFeeUsed) == "" { + return errors.Wrap(sdkerrors.ErrInvalidRequest, + "observed_tx.gas_fee_used is required") + } + // Length-capped, range-checked uint256 parse — see F-2026-18798. The value + // also feeds the outbound ballot key, so a malformed one must never be voted. + if _, err := ValidateUint256String(obs.GasFeeUsed, "observed_tx.gas_fee_used must be a valid uint256"); err != nil { + return err + } + + if obs.Success { + // Success additionally requires tx_hash and block_height. + if strings.TrimSpace(obs.TxHash) == "" { + return errors.Wrap(sdkerrors.ErrInvalidRequest, + "observed_tx.tx_hash required when success=true") + } + if obs.BlockHeight == 0 { + return errors.Wrap(sdkerrors.ErrInvalidRequest, + "observed_tx.block_height must be > 0 when success=true") + } + } else { + // Failure case: tx_hash MAY be empty. + // BUT if tx_hash is present, block_height must be > 0. + if strings.TrimSpace(obs.TxHash) != "" && obs.BlockHeight == 0 { + return errors.Wrap(sdkerrors.ErrInvalidRequest, + "observed_tx.block_height must be > 0 when tx_hash is provided") + } + } + + return nil +} diff --git a/x/uexecutor/types/tx.pb.go b/x/uexecutor/types/tx.pb.go index afcb1b1c5..52d8dca10 100644 --- a/x/uexecutor/types/tx.pb.go +++ b/x/uexecutor/types/tx.pb.go @@ -655,6 +655,235 @@ func (m *MsgRevertStuckInboundResponse) GetOutboundId() string { return "" } +// MsgExecuteStuckInbound is an admin escape hatch and the sibling of +// MsgRevertStuckInbound. For an inbound whose ballot is stored PENDING but can +// never finalize on its own, and whose YES votes already meet the recomputed +// threshold, this marks the ballot PASSED and runs the same post-finalization +// pipeline a finalizing vote would have run - so the user receives the bridged +// funds on Push instead of a source-chain refund. +type MsgExecuteStuckInbound struct { + // signer must equal uvalidator Params.Admin + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + // inbound is the original payload the stuck ballot was voting on. Admin + // supplies this from off-chain UV observation logs since the chain does not + // persist ballot payloads. + Inbound *Inbound `protobuf:"bytes,2,opt,name=inbound,proto3" json:"inbound,omitempty"` +} + +func (m *MsgExecuteStuckInbound) Reset() { *m = MsgExecuteStuckInbound{} } +func (m *MsgExecuteStuckInbound) String() string { return proto.CompactTextString(m) } +func (*MsgExecuteStuckInbound) ProtoMessage() {} +func (*MsgExecuteStuckInbound) Descriptor() ([]byte, []int) { + return fileDescriptor_88d6216044506365, []int{12} +} +func (m *MsgExecuteStuckInbound) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgExecuteStuckInbound) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgExecuteStuckInbound.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgExecuteStuckInbound) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgExecuteStuckInbound.Merge(m, src) +} +func (m *MsgExecuteStuckInbound) XXX_Size() int { + return m.Size() +} +func (m *MsgExecuteStuckInbound) XXX_DiscardUnknown() { + xxx_messageInfo_MsgExecuteStuckInbound.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgExecuteStuckInbound proto.InternalMessageInfo + +func (m *MsgExecuteStuckInbound) GetSigner() string { + if m != nil { + return m.Signer + } + return "" +} + +func (m *MsgExecuteStuckInbound) GetInbound() *Inbound { + if m != nil { + return m.Inbound + } + return nil +} + +type MsgExecuteStuckInboundResponse struct { + UtxId string `protobuf:"bytes,1,opt,name=utx_id,json=utxId,proto3" json:"utx_id,omitempty"` +} + +func (m *MsgExecuteStuckInboundResponse) Reset() { *m = MsgExecuteStuckInboundResponse{} } +func (m *MsgExecuteStuckInboundResponse) String() string { return proto.CompactTextString(m) } +func (*MsgExecuteStuckInboundResponse) ProtoMessage() {} +func (*MsgExecuteStuckInboundResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_88d6216044506365, []int{13} +} +func (m *MsgExecuteStuckInboundResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgExecuteStuckInboundResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgExecuteStuckInboundResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgExecuteStuckInboundResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgExecuteStuckInboundResponse.Merge(m, src) +} +func (m *MsgExecuteStuckInboundResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgExecuteStuckInboundResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgExecuteStuckInboundResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgExecuteStuckInboundResponse proto.InternalMessageInfo + +func (m *MsgExecuteStuckInboundResponse) GetUtxId() string { + if m != nil { + return m.UtxId + } + return "" +} + +// MsgExecuteStuckOutbound is an admin escape hatch for an outbound whose ballot +// can no longer reach a terminal-and-settled state — EXPIRED, or PENDING with +// every eligible voter already voted and the YES votes at the stored threshold. +// It runs the same settlement pipeline a finalizing vote would have run, so the +// outcome follows observed_tx.success: a success settles, a failure mints the +// bridged tokens back to the revert recipient and refunds the excess gas. +type MsgExecuteStuckOutbound struct { + // signer must equal uvalidator Params.Admin + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + TxId string `protobuf:"bytes,2,opt,name=tx_id,json=txId,proto3" json:"tx_id,omitempty"` + UtxId string `protobuf:"bytes,3,opt,name=utx_id,json=utxId,proto3" json:"utx_id,omitempty"` + // observed_tx is the destination-chain observation the stuck ballot was voting + // on. Admin supplies this from off-chain UV observation logs since the chain + // does not persist ballot payloads; it must match field-for-field or the + // derived ballot key finds no ballot. + ObservedTx *OutboundObservation `protobuf:"bytes,4,opt,name=observed_tx,json=observedTx,proto3" json:"observed_tx,omitempty"` +} + +func (m *MsgExecuteStuckOutbound) Reset() { *m = MsgExecuteStuckOutbound{} } +func (m *MsgExecuteStuckOutbound) String() string { return proto.CompactTextString(m) } +func (*MsgExecuteStuckOutbound) ProtoMessage() {} +func (*MsgExecuteStuckOutbound) Descriptor() ([]byte, []int) { + return fileDescriptor_88d6216044506365, []int{14} +} +func (m *MsgExecuteStuckOutbound) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgExecuteStuckOutbound) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgExecuteStuckOutbound.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgExecuteStuckOutbound) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgExecuteStuckOutbound.Merge(m, src) +} +func (m *MsgExecuteStuckOutbound) XXX_Size() int { + return m.Size() +} +func (m *MsgExecuteStuckOutbound) XXX_DiscardUnknown() { + xxx_messageInfo_MsgExecuteStuckOutbound.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgExecuteStuckOutbound proto.InternalMessageInfo + +func (m *MsgExecuteStuckOutbound) GetSigner() string { + if m != nil { + return m.Signer + } + return "" +} + +func (m *MsgExecuteStuckOutbound) GetTxId() string { + if m != nil { + return m.TxId + } + return "" +} + +func (m *MsgExecuteStuckOutbound) GetUtxId() string { + if m != nil { + return m.UtxId + } + return "" +} + +func (m *MsgExecuteStuckOutbound) GetObservedTx() *OutboundObservation { + if m != nil { + return m.ObservedTx + } + return nil +} + +type MsgExecuteStuckOutboundResponse struct { + OutboundId string `protobuf:"bytes,1,opt,name=outbound_id,json=outboundId,proto3" json:"outbound_id,omitempty"` +} + +func (m *MsgExecuteStuckOutboundResponse) Reset() { *m = MsgExecuteStuckOutboundResponse{} } +func (m *MsgExecuteStuckOutboundResponse) String() string { return proto.CompactTextString(m) } +func (*MsgExecuteStuckOutboundResponse) ProtoMessage() {} +func (*MsgExecuteStuckOutboundResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_88d6216044506365, []int{15} +} +func (m *MsgExecuteStuckOutboundResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgExecuteStuckOutboundResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgExecuteStuckOutboundResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgExecuteStuckOutboundResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgExecuteStuckOutboundResponse.Merge(m, src) +} +func (m *MsgExecuteStuckOutboundResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgExecuteStuckOutboundResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgExecuteStuckOutboundResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgExecuteStuckOutboundResponse proto.InternalMessageInfo + +func (m *MsgExecuteStuckOutboundResponse) GetOutboundId() string { + if m != nil { + return m.OutboundId + } + return "" +} + func init() { proto.RegisterType((*MsgUpdateParams)(nil), "uexecutor.v1.MsgUpdateParams") proto.RegisterType((*MsgUpdateParamsResponse)(nil), "uexecutor.v1.MsgUpdateParamsResponse") @@ -668,65 +897,75 @@ func init() { proto.RegisterType((*MsgVoteChainMetaResponse)(nil), "uexecutor.v1.MsgVoteChainMetaResponse") proto.RegisterType((*MsgRevertStuckInbound)(nil), "uexecutor.v1.MsgRevertStuckInbound") proto.RegisterType((*MsgRevertStuckInboundResponse)(nil), "uexecutor.v1.MsgRevertStuckInboundResponse") + proto.RegisterType((*MsgExecuteStuckInbound)(nil), "uexecutor.v1.MsgExecuteStuckInbound") + proto.RegisterType((*MsgExecuteStuckInboundResponse)(nil), "uexecutor.v1.MsgExecuteStuckInboundResponse") + proto.RegisterType((*MsgExecuteStuckOutbound)(nil), "uexecutor.v1.MsgExecuteStuckOutbound") + proto.RegisterType((*MsgExecuteStuckOutboundResponse)(nil), "uexecutor.v1.MsgExecuteStuckOutboundResponse") } func init() { proto.RegisterFile("uexecutor/v1/tx.proto", fileDescriptor_88d6216044506365) } var fileDescriptor_88d6216044506365 = []byte{ - // 850 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x56, 0xcf, 0x6f, 0xe3, 0x44, - 0x14, 0x8e, 0xb7, 0x49, 0x50, 0x5f, 0xc2, 0x6e, 0x63, 0x12, 0xd6, 0xf1, 0x12, 0xa7, 0x0d, 0xbf, - 0x96, 0x94, 0xc6, 0x6c, 0x90, 0xf6, 0x90, 0xdb, 0x06, 0x90, 0x88, 0x50, 0xb4, 0xc5, 0xdb, 0x65, - 0xa5, 0xbd, 0x44, 0x13, 0x7b, 0xd6, 0xb1, 0xd8, 0x78, 0x2c, 0xcf, 0x38, 0x4a, 0x6f, 0x88, 0x23, - 0x27, 0x4e, 0xfc, 0x0d, 0x48, 0x7b, 0xe9, 0x81, 0x3f, 0x80, 0x63, 0x8f, 0x15, 0x12, 0x12, 0xa7, - 0x0a, 0xb5, 0x87, 0xfe, 0x1b, 0x28, 0xe3, 0x1f, 0xf1, 0xd8, 0xa1, 0x45, 0x3d, 0x70, 0xb1, 0xc6, - 0xdf, 0xf7, 0xde, 0xf3, 0xfb, 0xbe, 0x79, 0x33, 0x32, 0x34, 0x02, 0xbc, 0xc4, 0x66, 0xc0, 0x88, - 0xaf, 0x2f, 0x1e, 0xe9, 0x6c, 0xd9, 0xf3, 0x7c, 0xc2, 0x88, 0x5c, 0x4d, 0xe0, 0xde, 0xe2, 0x91, - 0x5a, 0x43, 0x73, 0xc7, 0x25, 0x3a, 0x7f, 0x86, 0x01, 0xea, 0x7d, 0x93, 0xd0, 0x39, 0xa1, 0xfa, - 0x9c, 0xda, 0xab, 0xc4, 0x39, 0xb5, 0x23, 0x42, 0x11, 0x0b, 0x1e, 0x7b, 0x98, 0x46, 0x4c, 0x4b, - 0x60, 0xcc, 0x19, 0x72, 0xdc, 0xc9, 0x1c, 0x33, 0x14, 0xd1, 0x75, 0x9b, 0xd8, 0x84, 0x2f, 0xf5, - 0xd5, 0x2a, 0x42, 0x9b, 0xe1, 0x77, 0x26, 0x21, 0x11, 0xbe, 0x84, 0x54, 0xe7, 0x8d, 0x04, 0xf7, - 0xc6, 0xd4, 0x7e, 0xee, 0x59, 0x88, 0xe1, 0x43, 0xe4, 0xa3, 0x39, 0x95, 0x1f, 0xc3, 0x36, 0x0a, - 0xd8, 0x8c, 0xf8, 0x0e, 0x3b, 0x56, 0xa4, 0x5d, 0xe9, 0xe1, 0xf6, 0x50, 0xf9, 0xe3, 0xb7, 0x83, - 0x7a, 0x94, 0xf8, 0xc4, 0xb2, 0x7c, 0x4c, 0xe9, 0x33, 0xe6, 0x3b, 0xae, 0x6d, 0xac, 0x43, 0xe5, - 0x3e, 0x94, 0x3d, 0x5e, 0x41, 0xb9, 0xb3, 0x2b, 0x3d, 0xac, 0xf4, 0xeb, 0xbd, 0xb4, 0x01, 0xbd, - 0xb0, 0xfa, 0xb0, 0x78, 0x7a, 0xde, 0x2e, 0x18, 0x51, 0xe4, 0xe0, 0xd3, 0x1f, 0xaf, 0x4e, 0xba, - 0xeb, 0x1a, 0x3f, 0x5d, 0x9d, 0x74, 0x9b, 0x6b, 0x89, 0x99, 0xce, 0x3a, 0x4d, 0xb8, 0x9f, 0x81, - 0x0c, 0x4c, 0x3d, 0xe2, 0x52, 0xdc, 0xf9, 0xfd, 0x0e, 0xd4, 0xc6, 0xd4, 0xfe, 0x8a, 0xa7, 0xe2, - 0x43, 0x74, 0xfc, 0x9a, 0x20, 0x4b, 0xfe, 0x0c, 0xca, 0xd4, 0xb1, 0x5d, 0xec, 0xdf, 0xa8, 0x23, - 0x8a, 0x93, 0x0d, 0xa8, 0x07, 0xae, 0xb3, 0xc0, 0x3e, 0x45, 0xaf, 0x27, 0xc8, 0x34, 0x49, 0xe0, - 0xb2, 0x89, 0x63, 0x45, 0x92, 0x76, 0x45, 0x49, 0xcf, 0xe3, 0xc8, 0x27, 0x61, 0xe0, 0xc8, 0x32, - 0xe4, 0x20, 0x87, 0xc9, 0xdf, 0x40, 0x6d, 0x5d, 0xd3, 0x0b, 0x5b, 0x53, 0xb6, 0x78, 0x41, 0xed, - 0x5f, 0x0a, 0x46, 0x02, 0x8c, 0x9d, 0x20, 0x83, 0xc8, 0xfb, 0x50, 0x5b, 0x60, 0xdf, 0x79, 0xe5, - 0x98, 0x88, 0x39, 0xc4, 0x9d, 0x58, 0x88, 0x21, 0xa5, 0xb8, 0x52, 0x67, 0xec, 0xa4, 0x89, 0x2f, - 0x11, 0x43, 0x83, 0xfd, 0x95, 0xbd, 0x91, 0xb4, 0x95, 0xb7, 0x0f, 0x04, 0x6f, 0x45, 0xb3, 0x3a, - 0x0f, 0xa0, 0x99, 0x03, 0x13, 0x7f, 0x7f, 0x91, 0xe0, 0xee, 0x98, 0xda, 0xdf, 0x11, 0x86, 0x47, - 0xee, 0x94, 0x04, 0xee, 0x6d, 0xcc, 0xd5, 0xe1, 0x2d, 0x27, 0x4c, 0x8e, 0xfc, 0x6c, 0x88, 0xf2, - 0xa3, 0xca, 0x46, 0x1c, 0x35, 0xd8, 0xcb, 0xf4, 0x5f, 0x0b, 0xb0, 0x2e, 0x76, 0xd1, 0x51, 0xe0, - 0x5d, 0x11, 0x49, 0x5a, 0x3e, 0x0f, 0x67, 0x7b, 0x45, 0x3d, 0x0d, 0xd8, 0x6d, 0x7b, 0x7e, 0x07, - 0x4a, 0x6c, 0x19, 0x4f, 0xc0, 0xb6, 0x51, 0x64, 0xcb, 0x91, 0x25, 0x37, 0xa0, 0x1c, 0x84, 0xe8, - 0x16, 0x47, 0x4b, 0x01, 0x87, 0x87, 0x50, 0x21, 0x53, 0x8a, 0xfd, 0x05, 0xb6, 0x26, 0x6c, 0xc9, - 0x77, 0xa5, 0xd2, 0xdf, 0x13, 0x35, 0xc6, 0xad, 0x3c, 0xe5, 0x81, 0x7c, 0xab, 0x0c, 0x88, 0xb3, - 0x8e, 0x96, 0x83, 0x4f, 0x32, 0x92, 0xc5, 0xe3, 0x90, 0x16, 0x13, 0x1d, 0x87, 0x34, 0x94, 0x68, - 0xff, 0x53, 0x82, 0x9d, 0x88, 0xfb, 0x62, 0x75, 0x49, 0x8c, 0x31, 0x43, 0xb7, 0x10, 0xdf, 0x85, - 0x5a, 0x22, 0x28, 0xbc, 0x6c, 0x12, 0x23, 0xee, 0xc5, 0x04, 0xaf, 0x3f, 0xb2, 0xe4, 0x3a, 0x94, - 0x3c, 0xdf, 0x31, 0x31, 0xb7, 0xa4, 0x68, 0x84, 0x2f, 0xf2, 0x1e, 0x54, 0xc3, 0xc4, 0x19, 0x76, - 0xec, 0x19, 0xe3, 0x9e, 0x14, 0x8d, 0x0a, 0xc7, 0xbe, 0xe6, 0xd0, 0xa0, 0x9b, 0x51, 0xac, 0xe6, - 0x14, 0x27, 0x12, 0x3a, 0x2a, 0x28, 0x59, 0x2c, 0xd1, 0xfc, 0x46, 0x82, 0xc6, 0x98, 0xda, 0x06, - 0x5e, 0x60, 0x9f, 0x3d, 0x63, 0x81, 0xf9, 0xfd, 0xff, 0x38, 0xa9, 0x7a, 0x46, 0x44, 0x5b, 0x10, - 0x91, 0xef, 0xa9, 0xf3, 0x02, 0x5a, 0x1b, 0x89, 0x58, 0x4e, 0x6a, 0xc6, 0xa4, 0xf4, 0x8c, 0xb5, - 0xa1, 0x42, 0xa2, 0xdd, 0x5e, 0x6f, 0x06, 0xc4, 0xd0, 0xc8, 0xea, 0xff, 0x5a, 0x84, 0xad, 0x31, - 0xb5, 0xe5, 0x23, 0xa8, 0x0a, 0xd7, 0x7a, 0x4b, 0x54, 0x90, 0xb9, 0x48, 0xd5, 0x0f, 0xaf, 0xa5, - 0x93, 0xae, 0x5e, 0xc2, 0xdd, 0xcc, 0x1d, 0xdb, 0xce, 0x25, 0x8a, 0x01, 0xea, 0xc7, 0x37, 0x04, - 0x24, 0xb5, 0xbf, 0x85, 0x4a, 0xfa, 0x7e, 0x79, 0x2f, 0x97, 0x97, 0x62, 0xd5, 0x0f, 0xae, 0x63, - 0x93, 0x92, 0x47, 0x50, 0x15, 0xce, 0x7f, 0x6b, 0x63, 0x56, 0x4c, 0x6f, 0x30, 0x61, 0xd3, 0xe9, - 0x92, 0x5f, 0xc0, 0xdb, 0xe2, 0xc9, 0xd2, 0x36, 0xe6, 0x25, 0xbc, 0xfa, 0xd1, 0xf5, 0x7c, 0x52, - 0xf8, 0x15, 0xc8, 0x1b, 0xc6, 0xf7, 0xfd, 0x5c, 0x76, 0x3e, 0x48, 0xdd, 0xff, 0x0f, 0x41, 0xf1, - 0x77, 0xd4, 0xd2, 0x0f, 0x57, 0x27, 0x5d, 0x69, 0x78, 0x78, 0x7a, 0xa1, 0x49, 0x67, 0x17, 0x9a, - 0xf4, 0xf7, 0x85, 0x26, 0xfd, 0x7c, 0xa9, 0x15, 0xce, 0x2e, 0xb5, 0xc2, 0x5f, 0x97, 0x5a, 0xe1, - 0xe5, 0x63, 0xdb, 0x61, 0xb3, 0x60, 0xda, 0x33, 0xc9, 0x5c, 0xf7, 0x02, 0x3a, 0xe3, 0xe7, 0x95, - 0xaf, 0x0e, 0xf8, 0xf2, 0xc0, 0x25, 0x16, 0xd6, 0x97, 0xfa, 0x7a, 0xca, 0xf9, 0x5f, 0xca, 0xb4, - 0xcc, 0x7f, 0x2b, 0x3e, 0xff, 0x27, 0x00, 0x00, 0xff, 0xff, 0x31, 0xef, 0x9f, 0x75, 0x13, 0x09, + // 946 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x56, 0xcd, 0x6e, 0xdb, 0x46, + 0x10, 0x36, 0x63, 0x59, 0x85, 0x47, 0x6e, 0x62, 0x31, 0x72, 0x23, 0x33, 0x35, 0x65, 0xb3, 0x49, + 0x9b, 0xca, 0xb1, 0x18, 0xbb, 0x40, 0x0a, 0xe8, 0x16, 0xb5, 0x05, 0x2a, 0x14, 0x42, 0x5c, 0xc6, + 0x69, 0x80, 0x5c, 0x84, 0x35, 0xb9, 0xa1, 0x88, 0x5a, 0x5c, 0x81, 0xbb, 0x14, 0xe4, 0x5b, 0xdb, + 0x63, 0x4f, 0x3d, 0xf5, 0x25, 0x82, 0x02, 0x3e, 0xf4, 0x01, 0x7a, 0xcc, 0x31, 0x28, 0x50, 0xa0, + 0xa7, 0xa0, 0xb0, 0x0f, 0xbe, 0xf5, 0x19, 0x0a, 0x2d, 0xc9, 0x15, 0x97, 0xa4, 0x7f, 0xe0, 0x83, + 0x81, 0x5e, 0x84, 0xd5, 0x7c, 0x33, 0xb3, 0xf3, 0x7d, 0x3b, 0xb3, 0x4b, 0x58, 0x09, 0xf1, 0x04, + 0xdb, 0x21, 0x23, 0x81, 0x39, 0xde, 0x36, 0xd9, 0xa4, 0x35, 0x0a, 0x08, 0x23, 0xea, 0x92, 0x30, + 0xb7, 0xc6, 0xdb, 0x5a, 0x15, 0x0d, 0x3d, 0x9f, 0x98, 0xfc, 0x37, 0x72, 0xd0, 0xee, 0xd8, 0x84, + 0x0e, 0x09, 0x35, 0x87, 0xd4, 0x9d, 0x06, 0x0e, 0xa9, 0x1b, 0x03, 0x75, 0x39, 0xe1, 0xe1, 0x08, + 0xd3, 0x18, 0x59, 0x93, 0x10, 0x7b, 0x80, 0x3c, 0xbf, 0x3f, 0xc4, 0x0c, 0xc5, 0x70, 0xcd, 0x25, + 0x2e, 0xe1, 0x4b, 0x73, 0xba, 0x8a, 0xad, 0xab, 0xd1, 0x3e, 0xfd, 0x08, 0x88, 0xfe, 0x44, 0x90, + 0xf1, 0x5a, 0x81, 0x5b, 0x3d, 0xea, 0x3e, 0x1f, 0x39, 0x88, 0xe1, 0x5d, 0x14, 0xa0, 0x21, 0x55, + 0x1f, 0xc3, 0x22, 0x0a, 0xd9, 0x80, 0x04, 0x1e, 0x3b, 0xac, 0x2b, 0xeb, 0xca, 0x83, 0xc5, 0x4e, + 0xfd, 0xcf, 0xdf, 0xb7, 0x6a, 0x71, 0xe0, 0x13, 0xc7, 0x09, 0x30, 0xa5, 0xcf, 0x58, 0xe0, 0xf9, + 0xae, 0x35, 0x73, 0x55, 0x77, 0xa0, 0x3c, 0xe2, 0x19, 0xea, 0x37, 0xd6, 0x95, 0x07, 0x95, 0x9d, + 0x5a, 0x2b, 0x2d, 0x40, 0x2b, 0xca, 0xde, 0x29, 0xbd, 0x79, 0xd7, 0x98, 0xb3, 0x62, 0xcf, 0xf6, + 0xc3, 0x9f, 0x4e, 0x8f, 0x9a, 0xb3, 0x1c, 0x3f, 0x9f, 0x1e, 0x35, 0x57, 0x67, 0x14, 0x33, 0x95, + 0x19, 0xab, 0x70, 0x27, 0x63, 0xb2, 0x30, 0x1d, 0x11, 0x9f, 0x62, 0xe3, 0x8f, 0x1b, 0x50, 0xed, + 0x51, 0xf7, 0x2b, 0x1e, 0x8a, 0x77, 0xd1, 0xe1, 0x01, 0x41, 0x8e, 0xfa, 0x08, 0xca, 0xd4, 0x73, + 0x7d, 0x1c, 0x5c, 0xc8, 0x23, 0xf6, 0x53, 0x2d, 0xa8, 0x85, 0xbe, 0x37, 0xc6, 0x01, 0x45, 0x07, + 0x7d, 0x64, 0xdb, 0x24, 0xf4, 0x59, 0xdf, 0x73, 0x62, 0x4a, 0xeb, 0x32, 0xa5, 0xe7, 0x89, 0xe7, + 0x93, 0xc8, 0xb1, 0xeb, 0x58, 0x6a, 0x98, 0xb3, 0xa9, 0xdf, 0x40, 0x75, 0x96, 0x73, 0x14, 0x95, + 0x56, 0x9f, 0xe7, 0x09, 0xf5, 0x33, 0x12, 0xc6, 0x04, 0xac, 0xe5, 0x30, 0x63, 0x51, 0x37, 0xa1, + 0x3a, 0xc6, 0x81, 0xf7, 0xca, 0xb3, 0x11, 0xf3, 0x88, 0xdf, 0x77, 0x10, 0x43, 0xf5, 0xd2, 0x94, + 0x9d, 0xb5, 0x9c, 0x06, 0xbe, 0x44, 0x0c, 0xb5, 0x37, 0xa7, 0xf2, 0xc6, 0xd4, 0xa6, 0xda, 0xde, + 0x95, 0xb4, 0x95, 0xc5, 0x32, 0xee, 0xc2, 0x6a, 0xce, 0x28, 0xf4, 0xfd, 0x55, 0x81, 0x9b, 0x3d, + 0xea, 0x7e, 0x47, 0x18, 0xee, 0xfa, 0xfb, 0x24, 0xf4, 0xaf, 0x22, 0xae, 0x09, 0xef, 0x79, 0x51, + 0x70, 0xac, 0xe7, 0x8a, 0x4c, 0x3f, 0xce, 0x6c, 0x25, 0x5e, 0xed, 0x8d, 0x4c, 0xfd, 0xd5, 0x10, + 0x9b, 0x72, 0x15, 0x46, 0x1d, 0x3e, 0x90, 0x2d, 0xa2, 0xe4, 0x77, 0x51, 0x6f, 0x4f, 0xa1, 0xa7, + 0x21, 0xbb, 0x6a, 0xcd, 0xb7, 0x61, 0x81, 0x4d, 0x92, 0x0e, 0x58, 0xb4, 0x4a, 0x6c, 0xd2, 0x75, + 0xd4, 0x15, 0x28, 0x87, 0x91, 0x75, 0x9e, 0x5b, 0x17, 0x42, 0x6e, 0xee, 0x40, 0x85, 0xec, 0x53, + 0x1c, 0x8c, 0xb1, 0xd3, 0x67, 0x13, 0x7e, 0x2a, 0x95, 0x9d, 0x0d, 0x99, 0x63, 0x52, 0xca, 0x53, + 0xee, 0xc8, 0x8f, 0xca, 0x82, 0x24, 0x6a, 0x6f, 0xd2, 0xfe, 0x34, 0x43, 0x59, 0x1e, 0x87, 0x34, + 0x99, 0x78, 0x1c, 0xd2, 0x26, 0xc1, 0xfd, 0x2f, 0x05, 0x96, 0x63, 0xec, 0x8b, 0xe9, 0x25, 0xd1, + 0xc3, 0x0c, 0x5d, 0x81, 0x7c, 0x13, 0xaa, 0x82, 0x50, 0x74, 0xd9, 0x08, 0x21, 0x6e, 0x25, 0x00, + 0xcf, 0xdf, 0x75, 0xd4, 0x1a, 0x2c, 0x8c, 0x02, 0xcf, 0xc6, 0x5c, 0x92, 0x92, 0x15, 0xfd, 0x51, + 0x37, 0x60, 0x29, 0x0a, 0x1c, 0x60, 0xcf, 0x1d, 0x30, 0xae, 0x49, 0xc9, 0xaa, 0x70, 0xdb, 0xd7, + 0xdc, 0xd4, 0x6e, 0x66, 0x18, 0x6b, 0x39, 0xc6, 0x82, 0x82, 0xa1, 0x41, 0x3d, 0x6b, 0x13, 0x9c, + 0x5f, 0x2b, 0xb0, 0xd2, 0xa3, 0xae, 0x85, 0xc7, 0x38, 0x60, 0xcf, 0x58, 0x68, 0x7f, 0x7f, 0x8d, + 0x9d, 0x6a, 0x66, 0x48, 0x34, 0x24, 0x12, 0xf9, 0x9a, 0x8c, 0x17, 0xb0, 0x56, 0x08, 0x24, 0x74, + 0x52, 0x3d, 0xa6, 0xa4, 0x7b, 0xac, 0x01, 0x15, 0x12, 0x9f, 0xf6, 0xec, 0x30, 0x20, 0x31, 0x75, + 0x1d, 0xe3, 0x37, 0x85, 0x4f, 0x44, 0x3c, 0xc7, 0xd7, 0xad, 0xc3, 0xa3, 0x8c, 0x0e, 0xeb, 0x45, + 0x37, 0x8e, 0x24, 0xc4, 0xe7, 0xa0, 0x17, 0x23, 0x17, 0x28, 0x61, 0xfc, 0xab, 0xf0, 0xfe, 0x4f, + 0x47, 0xfe, 0x8f, 0xe6, 0x7c, 0x3b, 0x23, 0xd4, 0xc6, 0x99, 0x42, 0x89, 0x79, 0xef, 0x40, 0xe3, + 0x0c, 0x48, 0x48, 0x95, 0xe9, 0x0e, 0x25, 0xdb, 0x1d, 0x3b, 0x3f, 0x96, 0x61, 0xbe, 0x47, 0x5d, + 0x75, 0x0f, 0x96, 0xa4, 0x47, 0x7f, 0x4d, 0xae, 0x3e, 0xf3, 0xcc, 0x6a, 0xf7, 0xcf, 0x85, 0xc5, + 0xf6, 0x2f, 0xe1, 0x66, 0xe6, 0x05, 0x6e, 0xe4, 0x02, 0x65, 0x07, 0xed, 0x93, 0x0b, 0x1c, 0x44, + 0xee, 0x6f, 0xa1, 0x92, 0x7e, 0x7d, 0x3e, 0xcc, 0xc5, 0xa5, 0x50, 0xed, 0xde, 0x79, 0xa8, 0x48, + 0xb9, 0x07, 0x4b, 0xd2, 0xeb, 0xb0, 0x56, 0x18, 0x95, 0xc0, 0x05, 0x22, 0x14, 0xdd, 0xbd, 0xea, + 0x0b, 0x78, 0x5f, 0xbe, 0x77, 0xf5, 0xc2, 0x38, 0x81, 0x6b, 0x1f, 0x9f, 0x8f, 0x8b, 0xc4, 0xaf, + 0x40, 0x2d, 0xb8, 0xdc, 0x3e, 0xca, 0x45, 0xe7, 0x9d, 0xb4, 0xcd, 0x4b, 0x38, 0x89, 0x7d, 0x3c, + 0xb8, 0x5d, 0x74, 0x7b, 0xdc, 0x3b, 0xeb, 0xa4, 0xa4, 0x9d, 0x1e, 0x5e, 0xc6, 0x4b, 0x6c, 0x75, + 0x00, 0xb5, 0xc2, 0xf9, 0xbd, 0x7f, 0x6e, 0x16, 0x71, 0x22, 0x5b, 0x97, 0x72, 0x4b, 0x76, 0xd3, + 0x16, 0x7e, 0x38, 0x3d, 0x6a, 0x2a, 0x9d, 0xdd, 0x37, 0xc7, 0xba, 0xf2, 0xf6, 0x58, 0x57, 0xfe, + 0x39, 0xd6, 0x95, 0x5f, 0x4e, 0xf4, 0xb9, 0xb7, 0x27, 0xfa, 0xdc, 0xdf, 0x27, 0xfa, 0xdc, 0xcb, + 0xc7, 0xae, 0xc7, 0x06, 0xe1, 0x7e, 0xcb, 0x26, 0x43, 0x73, 0x14, 0xd2, 0x01, 0x7f, 0xa6, 0xf8, + 0x6a, 0x8b, 0x2f, 0xb7, 0x7c, 0xe2, 0x60, 0x73, 0x62, 0xce, 0x66, 0x95, 0x7f, 0x9c, 0xef, 0x97, + 0xf9, 0xd7, 0xf4, 0x67, 0xff, 0x05, 0x00, 0x00, 0xff, 0xff, 0x40, 0x4b, 0x06, 0x6e, 0x0a, 0x0c, 0x00, 0x00, } @@ -758,6 +997,15 @@ type MsgClient interface { // ballot has expired without finalizing, refunding the user on the source // chain via the normal revert/outbound flow. Admin-only escape hatch. RevertStuckInbound(ctx context.Context, in *MsgRevertStuckInbound, opts ...grpc.CallOption) (*MsgRevertStuckInboundResponse, error) + // ExecuteStuckInbound finalizes an inbound ballot that is provably unable to + // finalize on its own yet already carries enough YES votes, then runs the + // normal post-finalization pipeline so the user receives funds on Push. + // Admin-only escape hatch, sibling of RevertStuckInbound. + ExecuteStuckInbound(ctx context.Context, in *MsgExecuteStuckInbound, opts ...grpc.CallOption) (*MsgExecuteStuckInboundResponse, error) + // ExecuteStuckOutbound settles an outbound whose ballot can no longer reach a + // terminal-and-settled state, running the same post-finalization pipeline a + // finalizing vote would have run. Admin-only escape hatch. + ExecuteStuckOutbound(ctx context.Context, in *MsgExecuteStuckOutbound, opts ...grpc.CallOption) (*MsgExecuteStuckOutboundResponse, error) } type msgClient struct { @@ -822,6 +1070,24 @@ func (c *msgClient) RevertStuckInbound(ctx context.Context, in *MsgRevertStuckIn return out, nil } +func (c *msgClient) ExecuteStuckInbound(ctx context.Context, in *MsgExecuteStuckInbound, opts ...grpc.CallOption) (*MsgExecuteStuckInboundResponse, error) { + out := new(MsgExecuteStuckInboundResponse) + err := c.cc.Invoke(ctx, "/uexecutor.v1.Msg/ExecuteStuckInbound", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) ExecuteStuckOutbound(ctx context.Context, in *MsgExecuteStuckOutbound, opts ...grpc.CallOption) (*MsgExecuteStuckOutboundResponse, error) { + out := new(MsgExecuteStuckOutboundResponse) + err := c.cc.Invoke(ctx, "/uexecutor.v1.Msg/ExecuteStuckOutbound", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // MsgServer is the server API for Msg service. type MsgServer interface { // UpdateParams defines a governance operation for updating the parameters. @@ -840,6 +1106,15 @@ type MsgServer interface { // ballot has expired without finalizing, refunding the user on the source // chain via the normal revert/outbound flow. Admin-only escape hatch. RevertStuckInbound(context.Context, *MsgRevertStuckInbound) (*MsgRevertStuckInboundResponse, error) + // ExecuteStuckInbound finalizes an inbound ballot that is provably unable to + // finalize on its own yet already carries enough YES votes, then runs the + // normal post-finalization pipeline so the user receives funds on Push. + // Admin-only escape hatch, sibling of RevertStuckInbound. + ExecuteStuckInbound(context.Context, *MsgExecuteStuckInbound) (*MsgExecuteStuckInboundResponse, error) + // ExecuteStuckOutbound settles an outbound whose ballot can no longer reach a + // terminal-and-settled state, running the same post-finalization pipeline a + // finalizing vote would have run. Admin-only escape hatch. + ExecuteStuckOutbound(context.Context, *MsgExecuteStuckOutbound) (*MsgExecuteStuckOutboundResponse, error) } // UnimplementedMsgServer can be embedded to have forward compatible implementations. @@ -864,6 +1139,12 @@ func (*UnimplementedMsgServer) VoteChainMeta(ctx context.Context, req *MsgVoteCh func (*UnimplementedMsgServer) RevertStuckInbound(ctx context.Context, req *MsgRevertStuckInbound) (*MsgRevertStuckInboundResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method RevertStuckInbound not implemented") } +func (*UnimplementedMsgServer) ExecuteStuckInbound(ctx context.Context, req *MsgExecuteStuckInbound) (*MsgExecuteStuckInboundResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ExecuteStuckInbound not implemented") +} +func (*UnimplementedMsgServer) ExecuteStuckOutbound(ctx context.Context, req *MsgExecuteStuckOutbound) (*MsgExecuteStuckOutboundResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ExecuteStuckOutbound not implemented") +} func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) @@ -977,6 +1258,42 @@ func _Msg_RevertStuckInbound_Handler(srv interface{}, ctx context.Context, dec f return interceptor(ctx, in, info, handler) } +func _Msg_ExecuteStuckInbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgExecuteStuckInbound) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).ExecuteStuckInbound(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/uexecutor.v1.Msg/ExecuteStuckInbound", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).ExecuteStuckInbound(ctx, req.(*MsgExecuteStuckInbound)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_ExecuteStuckOutbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgExecuteStuckOutbound) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).ExecuteStuckOutbound(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/uexecutor.v1.Msg/ExecuteStuckOutbound", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).ExecuteStuckOutbound(ctx, req.(*MsgExecuteStuckOutbound)) + } + return interceptor(ctx, in, info, handler) +} + var _Msg_serviceDesc = grpc.ServiceDesc{ ServiceName: "uexecutor.v1.Msg", HandlerType: (*MsgServer)(nil), @@ -1005,6 +1322,14 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "RevertStuckInbound", Handler: _Msg_RevertStuckInbound_Handler, }, + { + MethodName: "ExecuteStuckInbound", + Handler: _Msg_ExecuteStuckInbound_Handler, + }, + { + MethodName: "ExecuteStuckOutbound", + Handler: _Msg_ExecuteStuckOutbound_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "uexecutor/v1/tx.proto", @@ -1450,49 +1775,207 @@ func (m *MsgRevertStuckInboundResponse) MarshalToSizedBuffer(dAtA []byte) (int, return len(dAtA) - i, nil } -func encodeVarintTx(dAtA []byte, offset int, v uint64) int { - offset -= sovTx(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ +func (m *MsgExecuteStuckInbound) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err } - dAtA[offset] = uint8(v) - return base + return dAtA[:n], nil } -func (m *MsgUpdateParams) Size() (n int) { - if m == nil { - return 0 - } + +func (m *MsgExecuteStuckInbound) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgExecuteStuckInbound) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i var l int _ = l - l = len(m.Authority) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) + if m.Inbound != nil { + { + size, err := m.Inbound.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 } - l = m.Params.Size() - n += 1 + l + sovTx(uint64(l)) - return n + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil } -func (m *MsgUpdateParamsResponse) Size() (n int) { - if m == nil { - return 0 +func (m *MsgExecuteStuckInboundResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err } - var l int - _ = l - return n + return dAtA[:n], nil } -func (m *MsgExecutePayload) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Signer) - if l > 0 { +func (m *MsgExecuteStuckInboundResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgExecuteStuckInboundResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.UtxId) > 0 { + i -= len(m.UtxId) + copy(dAtA[i:], m.UtxId) + i = encodeVarintTx(dAtA, i, uint64(len(m.UtxId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgExecuteStuckOutbound) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgExecuteStuckOutbound) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgExecuteStuckOutbound) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.ObservedTx != nil { + { + size, err := m.ObservedTx.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + if len(m.UtxId) > 0 { + i -= len(m.UtxId) + copy(dAtA[i:], m.UtxId) + i = encodeVarintTx(dAtA, i, uint64(len(m.UtxId))) + i-- + dAtA[i] = 0x1a + } + if len(m.TxId) > 0 { + i -= len(m.TxId) + copy(dAtA[i:], m.TxId) + i = encodeVarintTx(dAtA, i, uint64(len(m.TxId))) + i-- + dAtA[i] = 0x12 + } + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgExecuteStuckOutboundResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgExecuteStuckOutboundResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgExecuteStuckOutboundResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.OutboundId) > 0 { + i -= len(m.OutboundId) + copy(dAtA[i:], m.OutboundId) + i = encodeVarintTx(dAtA, i, uint64(len(m.OutboundId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintTx(dAtA []byte, offset int, v uint64) int { + offset -= sovTx(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MsgUpdateParams) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Authority) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.Params.Size() + n += 1 + l + sovTx(uint64(l)) + return n +} + +func (m *MsgUpdateParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgExecutePayload) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { n += 1 + l + sovTx(uint64(l)) } if m.UniversalAccountId != nil { @@ -1645,6 +2128,74 @@ func (m *MsgRevertStuckInboundResponse) Size() (n int) { return n } +func (m *MsgExecuteStuckInbound) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.Inbound != nil { + l = m.Inbound.Size() + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgExecuteStuckInboundResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.UtxId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgExecuteStuckOutbound) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.TxId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.UtxId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.ObservedTx != nil { + l = m.ObservedTx.Size() + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgExecuteStuckOutboundResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.OutboundId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + func sovTx(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -2886,6 +3437,470 @@ func (m *MsgRevertStuckInboundResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *MsgExecuteStuckInbound) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgExecuteStuckInbound: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgExecuteStuckInbound: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Inbound", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Inbound == nil { + m.Inbound = &Inbound{} + } + if err := m.Inbound.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgExecuteStuckInboundResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgExecuteStuckInboundResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgExecuteStuckInboundResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UtxId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.UtxId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgExecuteStuckOutbound) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgExecuteStuckOutbound: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgExecuteStuckOutbound: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TxId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.TxId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UtxId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.UtxId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ObservedTx", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ObservedTx == nil { + m.ObservedTx = &OutboundObservation{} + } + if err := m.ObservedTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgExecuteStuckOutboundResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgExecuteStuckOutboundResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgExecuteStuckOutboundResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field OutboundId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.OutboundId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipTx(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 From 5f0341e0428a0bb1b78dd7263cfd06c1d07425cd Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Thu, 3 Sep 2026 12:28:50 +0530 Subject: [PATCH 56/60] fix(uexecutor): cap the outbound gateway payload at admission (F-2026-18146) (#364) * fix(uexecutor): cap the outbound gateway payload at admission BuildOutboundsFromReceipt copied an attacker-controlled payload into state unbounded. Rejecting there reverts the whole EVM tx, so the gateway burn rolls back with it. * fix(pushcore): halve the pending outbound page instead of failing the poll * fix(pushcore): budget the pending outbound walk in rows, not pages * refactor(pushcore): state the pending outbound cap as a row count * test(pushcore): pin that the capped pending set is read once and in order * refactor(pushcore): drop the request cap; the row budget already bounds the walk * test(pushcore): cover the budget clamp and make the page floor fail rather than hang --------- Co-authored-by: aman035 --- .../uexecutor/outbound_payload_cap_test.go | 81 ++++++ universalClient/pushcore/pushCore.go | 45 ++- universalClient/pushcore/pushCore_test.go | 257 +++++++++++++++++- x/uexecutor/keeper/create_outbound.go | 5 + x/uexecutor/types/constants.go | 7 + .../types/outbound_payload_size_test.go | 56 ++++ x/uexecutor/types/outbound_tx.go | 9 + x/uexecutor/types/universal_payload.go | 10 + 8 files changed, 457 insertions(+), 13 deletions(-) create mode 100644 test/integration/uexecutor/outbound_payload_cap_test.go create mode 100644 x/uexecutor/types/outbound_payload_size_test.go diff --git a/test/integration/uexecutor/outbound_payload_cap_test.go b/test/integration/uexecutor/outbound_payload_cap_test.go new file mode 100644 index 000000000..207df7aa8 --- /dev/null +++ b/test/integration/uexecutor/outbound_payload_cap_test.go @@ -0,0 +1,81 @@ +package integrationtest + +import ( + "math/big" + "strings" + "testing" + + evmtypes "github.com/cosmos/evm/x/vm/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + + utils "github.com/pushchain/push-chain-node/test/utils" + uexecutortypes "github.com/pushchain/push-chain-node/x/uexecutor/types" +) + +// gatewayOutboundLog builds a UniversalTxOutbound log carrying payloadBytes, +// matching what DecodeUniversalTxOutboundFromLog expects. +func gatewayOutboundLog(t *testing.T, prc20 common.Address, payloadBytes []byte) *evmtypes.Log { + t.Helper() + + strT, _ := abi.NewType("string", "", nil) + bytesT, _ := abi.NewType("bytes", "", nil) + u256T, _ := abi.NewType("uint256", "", nil) + addrT, _ := abi.NewType("address", "", nil) + u8T, _ := abi.NewType("uint8", "", nil) + + args := abi.Arguments{ + {Type: strT}, {Type: bytesT}, {Type: u256T}, {Type: addrT}, {Type: u256T}, + {Type: u256T}, {Type: bytesT}, {Type: u256T}, {Type: addrT}, {Type: u8T}, {Type: u256T}, + } + target := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") + data, err := args.Pack( + "eip155:11155111", target.Bytes(), big.NewInt(1000000), + common.Address{}, big.NewInt(500000), big.NewInt(21000), + payloadBytes, big.NewInt(0), target, uint8(1), big.NewInt(1), + ) + require.NoError(t, err) + + return &evmtypes.Log{ + Address: strings.ToLower(utils.GetDefaultAddresses().UniversalGatewayPCAddr.Hex()), + Topics: []string{ + uexecutortypes.UniversalTxOutboundEventSig, + common.HexToHash("0x01").Hex(), + common.HexToHash("0x02").Hex(), + common.BytesToHash(prc20.Bytes()).Hex(), + }, + Data: data, + Index: 0, + } +} + +// F-2026-18146: the gateway payload is attacker-controlled and lands in state, +// so BuildOutboundsFromReceipt must reject an oversized one at admission. +func TestBuildOutboundsFromReceipt_RejectsOversizedPayload(t *testing.T) { + prc20 := utils.GetDefaultAddresses().PRC20USDCAddr + chainApp, ctx, _, _, _ := setupMulticallOutboundTest(t, 4) + + t.Run("a payload within the cap is accepted", func(t *testing.T) { + receipt := &evmtypes.MsgEthereumTxResponse{ + Hash: "0xabc", + Logs: []*evmtypes.Log{gatewayOutboundLog(t, prc20, []byte{0xde, 0xad, 0xbe, 0xef})}, + } + outbounds, err := chainApp.UexecutorKeeper.BuildOutboundsFromReceipt(ctx, "utx-ok", receipt) + require.NoError(t, err) + require.Len(t, outbounds, 1) + }) + + t.Run("an oversized payload is rejected", func(t *testing.T) { + // Hex-encoded, so half the cap in bytes is exactly at it; one more byte is over. + oversized := make([]byte, uexecutortypes.MaxOutboundPayloadBytes/2) + receipt := &evmtypes.MsgEthereumTxResponse{ + Hash: "0xabc", + Logs: []*evmtypes.Log{gatewayOutboundLog(t, prc20, oversized)}, + } + outbounds, err := chainApp.UexecutorKeeper.BuildOutboundsFromReceipt(ctx, "utx-big", receipt) + require.Error(t, err) + require.Contains(t, err.Error(), "payload too large") + require.Empty(t, outbounds, "no outbound may be built from an oversized payload") + }) +} diff --git a/universalClient/pushcore/pushCore.go b/universalClient/pushcore/pushCore.go index ecf58eff3..6a501618e 100644 --- a/universalClient/pushcore/pushCore.go +++ b/universalClient/pushcore/pushCore.go @@ -22,8 +22,10 @@ import ( uvalidatortypes "github.com/pushchain/push-chain-node/x/uvalidator/types" "github.com/rs/zerolog" "google.golang.org/grpc" + "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/status" ) // Client is a fan-out client that connects to multiple Push Chain gRPC endpoints. @@ -401,7 +403,11 @@ const ( // gRPC's 4 MiB default. Asking for the whole set in one request would fail // the call outright once the set grew, taking the poll down entirely. pendingOutboundPageSize = 1000 - pendingOutboundMaxPages = 5 + + // pendingOutboundMaxRows caps one poll. The remainder is read on the next + // tick. Counted in rows so a page that had to shrink costs extra requests + // rather than fewer rows. + pendingOutboundMaxRows = 5000 chainConfigPageSize = 200 chainConfigMaxPages = 20 @@ -439,35 +445,58 @@ func (c *Client) GetAllPendingOutbounds(ctx context.Context) ([]*uexecutortypes. outbounds []*uexecutortypes.OutboundTx ) - for page := 0; page < pendingOutboundMaxPages; page++ { - offset := uint64(page) * pendingOutboundPageSize + // Halving on ResourceExhausted is what keeps one large row from blinding the + // whole poll. It terminates because core caps an outbound payload + // (MaxOutboundPayloadBytes) well below maxPushCoreRecvMsgSize, so a page of + // one always fits and no row is ever unfetchable. + var offset uint64 + limit := uint64(pendingOutboundPageSize) + + // The walk is bounded without counting requests: a served page adds at least + // one row or is short and ends the walk, so there are at most + // pendingOutboundMaxRows of them, and halving bottoms out at a page of one. + for uint64(len(entries)) < pendingOutboundMaxRows { + pageLimit := limit + if remaining := pendingOutboundMaxRows - uint64(len(entries)); pageLimit > remaining { + pageLimit = remaining + } resp, err := retryWithRoundRobin( len(c.uexecutorClients), &c.rr, func(idx int) (*uexecutortypes.QueryAllPendingOutboundsResponse, error) { return c.uexecutorClients[idx].AllPendingOutbounds(ctx, &uexecutortypes.QueryAllPendingOutboundsRequest{ - Pagination: &query.PageRequest{Offset: offset, Limit: pendingOutboundPageSize}, + Pagination: &query.PageRequest{Offset: offset, Limit: pageLimit}, }) }, "GetAllPendingOutbounds", c.logger, ) if err != nil { - return nil, nil, err + if status.Code(err) != codes.ResourceExhausted || limit == 1 { + return nil, nil, err + } + limit /= 2 + c.logger.Warn(). + Uint64("offset", offset). + Uint64("was", pageLimit). + Uint64("now", limit). + Msg("pending outbound page exceeded the receive limit; halving the page") + continue } entries = append(entries, resp.Entries...) outbounds = append(outbounds, resp.Outbounds...) - if len(resp.Entries) < pendingOutboundPageSize { + if uint64(len(resp.Entries)) < pageLimit { return entries, outbounds, nil } + offset += uint64(len(resp.Entries)) } c.logger.Warn(). - Int("max_pages", pendingOutboundMaxPages). + Int("max_rows", pendingOutboundMaxRows). Int("fetched", len(entries)). - Msg("pending outbound page cap reached; the remainder is read on the next poll") + Msg("pending outbound row budget reached; the remainder is read on the next poll") return entries, outbounds, nil } diff --git a/universalClient/pushcore/pushCore_test.go b/universalClient/pushcore/pushCore_test.go index e5930d08c..a5ad7ea68 100644 --- a/universalClient/pushcore/pushCore_test.go +++ b/universalClient/pushcore/pushCore_test.go @@ -7,6 +7,7 @@ import ( "fmt" "math/big" "testing" + "time" cmtservice "github.com/cosmos/cosmos-sdk/client/grpc/cmtservice" sdktypes "github.com/cosmos/cosmos-sdk/types" @@ -22,6 +23,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) func TestNew(t *testing.T) { @@ -1243,16 +1246,16 @@ func TestClient_GetAllPendingOutbounds_WalksOldestFirst(t *testing.T) { }) // The cap bounds one poll; the rest is read on the next tick. - t.Run("stops at the page cap and says so", func(t *testing.T) { + t.Run("stops at the row budget and says so", func(t *testing.T) { var logBuf bytes.Buffer - m := &mockUExecutorQueryClient{pendingTotal: pendingOutboundPageSize * (pendingOutboundMaxPages + 2)} + m := &mockUExecutorQueryClient{pendingTotal: pendingOutboundMaxRows + 2*pendingOutboundPageSize} client := &Client{logger: zerolog.New(&logBuf), uexecutorClients: []uexecutortypes.QueryClient{m}} entries, _, err := client.GetAllPendingOutbounds(ctx) require.NoError(t, err) - assert.Len(t, m.pendingReqs, pendingOutboundMaxPages) - assert.Len(t, entries, pendingOutboundPageSize*pendingOutboundMaxPages) - assert.Contains(t, logBuf.String(), "page cap reached") + assert.Len(t, m.pendingReqs, pendingOutboundMaxRows/pendingOutboundPageSize) + assert.Len(t, entries, pendingOutboundMaxRows) + assert.Contains(t, logBuf.String(), "row budget reached") }) t.Run("quiet when the set fits", func(t *testing.T) { @@ -1265,3 +1268,247 @@ func TestClient_GetAllPendingOutbounds_WalksOldestFirst(t *testing.T) { assert.NotContains(t, logBuf.String(), "page cap reached") }) } + +// oversizedPageClient serves rows by offset like the mock above, but rejects any +// page larger than maxServable with ResourceExhausted, the way grpc-go does when +// a response exceeds the receive limit. +type oversizedPageClient struct { + uexecutortypes.QueryClient + total int + maxServable uint64 + reqs []*query.PageRequest +} + +func (m *oversizedPageClient) AllPendingOutbounds(ctx context.Context, req *uexecutortypes.QueryAllPendingOutboundsRequest, opts ...grpc.CallOption) (*uexecutortypes.QueryAllPendingOutboundsResponse, error) { + m.reqs = append(m.reqs, req.Pagination) + if req.Pagination.GetLimit() > m.maxServable { + return nil, status.Error(codes.ResourceExhausted, + "grpc: received message larger than max") + } + offset := int(req.Pagination.GetOffset()) + end := offset + int(req.Pagination.GetLimit()) + if end > m.total { + end = m.total + } + resp := &uexecutortypes.QueryAllPendingOutboundsResponse{ + Pagination: &query.PageResponse{Total: uint64(m.total)}, + } + for i := offset; i < end; i++ { + id := fmt.Sprintf("ob-%d", i) + resp.Entries = append(resp.Entries, &uexecutortypes.PendingOutboundEntry{OutboundId: id}) + resp.Outbounds = append(resp.Outbounds, &uexecutortypes.OutboundTx{Id: id}) + } + return resp, nil +} + +// A page that will not fit must not blind the poll. The client halves until the +// response fits, which terminates because core caps an outbound payload well +// below the receive limit, so a page of one always fits. +func TestGetAllPendingOutbounds_HalvesOnResourceExhausted(t *testing.T) { + ctx := context.Background() + + t.Run("degrades and still returns every row oldest first", func(t *testing.T) { + m := &oversizedPageClient{total: 300, maxServable: 250} + client := &Client{logger: zerolog.Nop(), uexecutorClients: []uexecutortypes.QueryClient{m}} + + entries, outbounds, err := client.GetAllPendingOutbounds(ctx) + require.NoError(t, err, "an oversized page must not fail the whole poll") + require.Len(t, entries, 300) + require.Len(t, outbounds, 300) + + assert.Equal(t, "ob-0", entries[0].OutboundId, "oldest first") + assert.Equal(t, "ob-299", entries[299].OutboundId) + + // 1000 rejected, then 500 rejected, then 250 serves. + require.GreaterOrEqual(t, len(m.reqs), 3) + assert.Equal(t, uint64(1000), m.reqs[0].Limit) + assert.Equal(t, uint64(500), m.reqs[1].Limit) + assert.Equal(t, uint64(250), m.reqs[2].Limit) + }) + + t.Run("offsets follow the rows actually returned", func(t *testing.T) { + m := &oversizedPageClient{total: 300, maxServable: 250} + client := &Client{logger: zerolog.Nop(), uexecutorClients: []uexecutortypes.QueryClient{m}} + + _, _, err := client.GetAllPendingOutbounds(ctx) + require.NoError(t, err) + + // The second served page must start where the first ended, not at a + // multiple of the original page size. + var served []*query.PageRequest + for _, r := range m.reqs { + if r.Limit <= m.maxServable { + served = append(served, r) + } + } + require.GreaterOrEqual(t, len(served), 2) + assert.Equal(t, uint64(0), served[0].Offset) + assert.Equal(t, uint64(250), served[1].Offset, "no row may be skipped or read twice") + }) + + t.Run("a page of one that still fails is a real error", func(t *testing.T) { + m := &oversizedPageClient{total: 10, maxServable: 0} + client := &Client{logger: zerolog.Nop(), uexecutorClients: []uexecutortypes.QueryClient{m}} + + // Bounded, because the failure mode here is a walk that never ends: if + // the floor guard let the page reach zero, every request would return no + // rows and the offset would stop moving. That must fail, not hang. + done := make(chan error, 1) + go func() { + _, _, err := client.GetAllPendingOutbounds(ctx) + done <- err + }() + + select { + case err := <-done: + require.Error(t, err, "nothing left to halve, so the caller must see it") + assert.Equal(t, codes.ResourceExhausted, status.Code(err)) + case <-time.After(5 * time.Second): + t.Fatal("the walk did not terminate; the page size floor is gone") + } + }) + + t.Run("an unrelated error is not retried smaller", func(t *testing.T) { + m := &mockUExecutorQueryClient{err: status.Error(codes.Unavailable, "endpoint down")} + client := &Client{logger: zerolog.Nop(), uexecutorClients: []uexecutortypes.QueryClient{m}} + + _, _, err := client.GetAllPendingOutbounds(ctx) + require.Error(t, err) + assert.Len(t, m.pendingReqs, 1, "halving is only for a response that did not fit") + }) +} + +// A page that had to shrink must cost extra requests, not rows. Bounding the +// walk by iterations instead would quietly cut a degraded poll from 5000 rows to +// five times whatever the page shrank to. +func TestGetAllPendingOutbounds_RowBudgetSurvivesDegradedPages(t *testing.T) { + ctx := context.Background() + + full := &oversizedPageClient{total: 20000, maxServable: pendingOutboundPageSize} + fullClient := &Client{logger: zerolog.Nop(), uexecutorClients: []uexecutortypes.QueryClient{full}} + fullEntries, _, err := fullClient.GetAllPendingOutbounds(ctx) + require.NoError(t, err) + + degraded := &oversizedPageClient{total: 20000, maxServable: 250} + degradedClient := &Client{logger: zerolog.Nop(), uexecutorClients: []uexecutortypes.QueryClient{degraded}} + degradedEntries, _, err := degradedClient.GetAllPendingOutbounds(ctx) + require.NoError(t, err) + + assert.Len(t, fullEntries, pendingOutboundMaxRows) + assert.Len(t, degradedEntries, pendingOutboundMaxRows, + "a shrunken page must not shrink the poll") + assert.Greater(t, len(degraded.reqs), len(full.reqs), + "the cost of degrading is requests, not rows") + + // Same rows, same order, whatever the page size was. + require.Equal(t, fullEntries[0].OutboundId, degradedEntries[0].OutboundId) + require.Equal(t, fullEntries[len(fullEntries)-1].OutboundId, degradedEntries[len(degradedEntries)-1].OutboundId) +} + +// Every payload sitting at the cap is the worst page the client can meet: 8 MiB +// over a 128 KiB row is 64 rows, so the page settles at 62 and the poll costs +// more than 80 requests. It must still read the full budget rather than stop at +// some request count. +func TestGetAllPendingOutbounds_WorstCaseRowSizeStillReadsTheBudget(t *testing.T) { + m := &oversizedPageClient{total: 20000, maxServable: 64} + client := &Client{logger: zerolog.Nop(), uexecutorClients: []uexecutortypes.QueryClient{m}} + + entries, _, err := client.GetAllPendingOutbounds(context.Background()) + require.NoError(t, err) + require.Len(t, entries, pendingOutboundMaxRows, "a small page must not shrink the poll") + assert.Greater(t, len(m.reqs), 64, "this case genuinely needs more than 64 requests") + + for i, e := range entries { + require.Equal(t, fmt.Sprintf("ob-%d", i), e.OutboundId, "row %d out of order", i) + } +} + +// The cap is only useful if the rows under it are the right ones. Asserting the +// count alone would pass on a walk that skipped a page and re-read another, so +// this checks the whole sequence, and that entries and outbounds stay aligned. +func TestGetAllPendingOutbounds_ReadsTheCappedSetExactlyOnce(t *testing.T) { + ctx := context.Background() + + assertContiguous := func(t *testing.T, entries []*uexecutortypes.PendingOutboundEntry, outbounds []*uexecutortypes.OutboundTx) { + t.Helper() + require.Len(t, entries, pendingOutboundMaxRows) + require.Len(t, outbounds, pendingOutboundMaxRows, "an outbound per entry") + + seen := make(map[string]int, len(entries)) + for i, e := range entries { + assert.Equal(t, fmt.Sprintf("ob-%d", i), e.OutboundId, "row %d out of order", i) + assert.Equal(t, e.OutboundId, outbounds[i].Id, "entry and outbound diverged at %d", i) + seen[e.OutboundId]++ + } + require.Len(t, seen, pendingOutboundMaxRows, "a row was skipped or read twice") + for id, n := range seen { + require.Equal(t, 1, n, "%s appeared %d times", id, n) + } + } + + t.Run("full pages", func(t *testing.T) { + m := &oversizedPageClient{total: 20000, maxServable: pendingOutboundPageSize} + client := &Client{logger: zerolog.Nop(), uexecutorClients: []uexecutortypes.QueryClient{m}} + + entries, outbounds, err := client.GetAllPendingOutbounds(ctx) + require.NoError(t, err) + assertContiguous(t, entries, outbounds) + }) + + t.Run("pages that had to shrink", func(t *testing.T) { + m := &oversizedPageClient{total: 20000, maxServable: 250} + client := &Client{logger: zerolog.Nop(), uexecutorClients: []uexecutortypes.QueryClient{m}} + + entries, outbounds, err := client.GetAllPendingOutbounds(ctx) + require.NoError(t, err) + assertContiguous(t, entries, outbounds) + }) + + t.Run("exactly the cap available stops without a second empty request", func(t *testing.T) { + m := &oversizedPageClient{total: pendingOutboundMaxRows, maxServable: pendingOutboundPageSize} + client := &Client{logger: zerolog.Nop(), uexecutorClients: []uexecutortypes.QueryClient{m}} + + entries, outbounds, err := client.GetAllPendingOutbounds(ctx) + require.NoError(t, err) + assertContiguous(t, entries, outbounds) + assert.Len(t, m.reqs, pendingOutboundMaxRows/pendingOutboundPageSize, + "hitting the cap exactly must not cost an extra round trip") + }) + + t.Run("under the cap returns everything and stops early", func(t *testing.T) { + m := &oversizedPageClient{total: 2500, maxServable: pendingOutboundPageSize} + client := &Client{logger: zerolog.Nop(), uexecutorClients: []uexecutortypes.QueryClient{m}} + + entries, _, err := client.GetAllPendingOutbounds(ctx) + require.NoError(t, err) + require.Len(t, entries, 2500) + assert.Equal(t, "ob-0", entries[0].OutboundId) + assert.Equal(t, "ob-2499", entries[2499].OutboundId) + }) +} + +// The last page is trimmed to what is left of the budget. It matters whenever +// the page the walk settled on does not divide the budget: at 62 rows the walk +// reaches 4960 and the final request must ask for 40, not another 62. +func TestGetAllPendingOutbounds_LastPageIsTrimmedToTheBudget(t *testing.T) { + m := &oversizedPageClient{total: 20000, maxServable: 64} + client := &Client{logger: zerolog.Nop(), uexecutorClients: []uexecutortypes.QueryClient{m}} + + entries, outbounds, err := client.GetAllPendingOutbounds(context.Background()) + require.NoError(t, err) + + require.Len(t, entries, pendingOutboundMaxRows, "the budget is a ceiling, not a rounding") + require.Len(t, outbounds, pendingOutboundMaxRows) + + last := m.reqs[len(m.reqs)-1] + assert.Equal(t, uint64(40), last.Limit, "the final page asks only for what is left") + assert.Equal(t, uint64(pendingOutboundMaxRows-40), last.Offset) + + // No request may reach past the budget. + for i, r := range m.reqs { + if r.Limit <= m.maxServable { + assert.LessOrEqual(t, r.Offset+r.Limit, uint64(pendingOutboundMaxRows), + "request %d would read past the budget", i) + } + } +} diff --git a/x/uexecutor/keeper/create_outbound.go b/x/uexecutor/keeper/create_outbound.go index 3ff5f1db3..60c322c0d 100644 --- a/x/uexecutor/keeper/create_outbound.go +++ b/x/uexecutor/keeper/create_outbound.go @@ -69,6 +69,11 @@ func (k Keeper) BuildOutboundsFromReceipt( return nil, fmt.Errorf("no token config for PRC20 %s on chain %s: %w", event.Token, event.ChainId, err) } + // The gateway payload is attacker-controlled and lands in state (F-2026-18146). + if err := types.ValidateOutboundPayloadBlobSize("payload", event.Payload); err != nil { + return nil, err + } + outbound := &types.OutboundTx{ DestinationChain: event.ChainId, Recipient: event.Target, diff --git a/x/uexecutor/types/constants.go b/x/uexecutor/types/constants.go index 2d7e58384..00ce987ba 100644 --- a/x/uexecutor/types/constants.go +++ b/x/uexecutor/types/constants.go @@ -69,3 +69,10 @@ var RescueFundsOnSourceChainEventSig = crypto.Keccak256Hash([]byte( // UniversalPayload struct because that struct is variable length; a flat number // is auditable and stable. const MaxUniversalPayloadBytes = 128 * 1024 + +// MaxOutboundPayloadBytes caps the hex-encoded payload an outbound carries to a +// destination chain, so ~64 KiB of calldata. Bounded by what the destination can +// accept: geth's txpool rejects transactions over 128 KB (txMaxSize), so a larger +// payload yields a tx no EVM node accepts — unsendable once TSS has signed it. +// Same value as MaxUniversalPayloadBytes, different reason; do not collapse them. +const MaxOutboundPayloadBytes = 128 * 1024 diff --git a/x/uexecutor/types/outbound_payload_size_test.go b/x/uexecutor/types/outbound_payload_size_test.go new file mode 100644 index 000000000..f77f48826 --- /dev/null +++ b/x/uexecutor/types/outbound_payload_size_test.go @@ -0,0 +1,56 @@ +package types_test + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/pushchain/push-chain-node/x/uexecutor/types" +) + +// F-2026-18146: an outbound payload comes from an attacker-controlled gateway +// event and lands in state, so it is capped at admission. +func TestValidateOutboundPayloadBlobSize(t *testing.T) { + max := types.MaxOutboundPayloadBytes + require.Equal(t, 128*1024, max) + + for _, tc := range []struct { + name string + blob string + wantOK bool + }{ + {"empty", "", true}, + {"small", "0xdeadbeef", true}, + {"at the cap", strings.Repeat("a", max), true}, + {"one over", strings.Repeat("a", max+1), false}, + {"the published ~2 MiB payload", strings.Repeat("a", 2*1024*1024), false}, + } { + t.Run(tc.name, func(t *testing.T) { + err := types.ValidateOutboundPayloadBlobSize("payload", tc.blob) + if tc.wantOK { + require.NoError(t, err) + return + } + require.Error(t, err) + require.Contains(t, err.Error(), "payload too large") + require.Contains(t, err.Error(), "131072") + }) + } +} + +func TestOutboundTx_ValidateSize(t *testing.T) { + require.NoError(t, (*types.OutboundTx)(nil).ValidateSize()) + require.NoError(t, (&types.OutboundTx{Payload: "0xdeadbeef"}).ValidateSize()) + + err := (&types.OutboundTx{Payload: strings.Repeat("a", types.MaxOutboundPayloadBytes+1)}).ValidateSize() + require.Error(t, err) + require.Contains(t, err.Error(), "payload too large") +} + +// The outbound cap is derived from geth's txpool limit, not from the inbound +// cap. Equal today, but they must stay independently changeable. +func TestOutboundCapIsIndependentOfInboundCap(t *testing.T) { + require.Equal(t, 128*1024, types.MaxOutboundPayloadBytes) + require.Equal(t, 128*1024, types.MaxUniversalPayloadBytes) +} diff --git a/x/uexecutor/types/outbound_tx.go b/x/uexecutor/types/outbound_tx.go index ae54c0703..0cc10e5f1 100644 --- a/x/uexecutor/types/outbound_tx.go +++ b/x/uexecutor/types/outbound_tx.go @@ -20,6 +20,15 @@ func (p OutboundTx) String() string { } // ValidateBasic does the sanity check on the OutboundTx fields. +// ValidateSize caps the payload. Split out so the keeper can apply it to an +// event-sourced outbound before the row is built. +func (p *OutboundTx) ValidateSize() error { + if p == nil { + return nil + } + return ValidateOutboundPayloadBlobSize("payload", p.Payload) +} + func (p OutboundTx) ValidateBasic() error { // Validate destination_chain (must follow CAIP-2 format) chain := strings.TrimSpace(p.DestinationChain) diff --git a/x/uexecutor/types/universal_payload.go b/x/uexecutor/types/universal_payload.go index 57a868e32..4f1b2467a 100644 --- a/x/uexecutor/types/universal_payload.go +++ b/x/uexecutor/types/universal_payload.go @@ -34,6 +34,16 @@ func (p *UniversalPayload) ValidateSize() error { return nil } +// ValidateOutboundPayloadBlobSize enforces MaxOutboundPayloadBytes on an +// outbound's payload. +func ValidateOutboundPayloadBlobSize(field, blob string) error { + if len(blob) > MaxOutboundPayloadBytes { + return errors.Wrapf(sdkerrors.ErrInvalidRequest, + "%s too large: %d bytes exceeds the %d byte limit", field, len(blob), MaxOutboundPayloadBytes) + } + return nil +} + // ValidatePayloadBlobSize enforces MaxUniversalPayloadBytes on a hex blob that // carries a universal payload (or its verification data) before it is decoded. func ValidatePayloadBlobSize(field, blob string) error { From 6063049a5deeb5ab883629b641c21353220974fc Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Fri, 11 Sep 2026 10:10:07 +0530 Subject: [PATCH 57/60] chore: pin evm to the audit-fixes merge --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e69843719..c471853c5 100755 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ replace ( cosmossdk.io/x/upgrade => cosmossdk.io/x/upgrade v0.2.0 github.com/CosmWasm/wasmd => github.com/CosmWasm/wasmd v0.55.0 // Keep v0.55.0 github.com/cosmos/cosmos-sdk => github.com/cosmos/cosmos-sdk v0.53.7 // Use stable v0.53.7 - github.com/cosmos/evm => github.com/pushchain/evm v1.0.0-rc2.0.20260907111253-e9816bddce44 + github.com/cosmos/evm => github.com/pushchain/evm v1.0.0-rc2.0.20260911043035-bfa1fef525c4 github.com/ethereum/go-ethereum => github.com/cosmos/go-ethereum v0.0.0-20250806193535-2fc7571efa91 github.com/spf13/viper => github.com/spf13/viper v1.17.0 github.com/strangelove-ventures/tokenfactory => github.com/strangelove-ventures/tokenfactory v0.50.7-wasmvm2 diff --git a/go.sum b/go.sum index f0f091a8c..247000b97 100755 --- a/go.sum +++ b/go.sum @@ -1769,8 +1769,8 @@ github.com/prysmaticlabs/gohashtree v0.0.4-beta.0.20240624100937-73632381301b h1 github.com/prysmaticlabs/gohashtree v0.0.4-beta.0.20240624100937-73632381301b/go.mod h1:HRuvtXLZ4WkaB1MItToVH2e8ZwKwZPY5/Rcby+CvvLY= github.com/prysmaticlabs/prysm/v5 v5.3.0 h1:7Lr8ndapBTZg00YE+MgujN6+yvJR6Bdfn28ZDSJ00II= github.com/prysmaticlabs/prysm/v5 v5.3.0/go.mod h1:r1KhlduqDMIGZ1GhR5pjZ2Ko8Q89noTDYTRoPKwf1+c= -github.com/pushchain/evm v1.0.0-rc2.0.20260907111253-e9816bddce44 h1:AwjH9/uMSblFHrclRp7zctH4+0VdZqChMoeXh3v6jHk= -github.com/pushchain/evm v1.0.0-rc2.0.20260907111253-e9816bddce44/go.mod h1:QuenX5DgRhWeYdIg0J/p65cyS/ntpgnzpZOIajZ/SHk= +github.com/pushchain/evm v1.0.0-rc2.0.20260911043035-bfa1fef525c4 h1:2Ra4vGhuK46tekmcd3XyXMKAn634tVu6gqGsEmOiC88= +github.com/pushchain/evm v1.0.0-rc2.0.20260911043035-bfa1fef525c4/go.mod h1:QuenX5DgRhWeYdIg0J/p65cyS/ntpgnzpZOIajZ/SHk= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= github.com/quic-go/qtls-go1-20 v0.3.4 h1:MfFAPULvst4yoMgY9QmtpYmfij/em7O8UUi+bNVm7Cg= From 209e3191aebc49a0c0eff66dfc7368c5a2de2b46 Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Fri, 11 Sep 2026 11:01:29 +0530 Subject: [PATCH 58/60] chore: bump evm pin to the merged audit-fixes develop --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c471853c5..d0eecb4d3 100755 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ replace ( cosmossdk.io/x/upgrade => cosmossdk.io/x/upgrade v0.2.0 github.com/CosmWasm/wasmd => github.com/CosmWasm/wasmd v0.55.0 // Keep v0.55.0 github.com/cosmos/cosmos-sdk => github.com/cosmos/cosmos-sdk v0.53.7 // Use stable v0.53.7 - github.com/cosmos/evm => github.com/pushchain/evm v1.0.0-rc2.0.20260911043035-bfa1fef525c4 + github.com/cosmos/evm => github.com/pushchain/evm v1.0.0-rc2.0.20260911052808-93debf9a8fe6 github.com/ethereum/go-ethereum => github.com/cosmos/go-ethereum v0.0.0-20250806193535-2fc7571efa91 github.com/spf13/viper => github.com/spf13/viper v1.17.0 github.com/strangelove-ventures/tokenfactory => github.com/strangelove-ventures/tokenfactory v0.50.7-wasmvm2 diff --git a/go.sum b/go.sum index 247000b97..c884f04c3 100755 --- a/go.sum +++ b/go.sum @@ -1769,8 +1769,8 @@ github.com/prysmaticlabs/gohashtree v0.0.4-beta.0.20240624100937-73632381301b h1 github.com/prysmaticlabs/gohashtree v0.0.4-beta.0.20240624100937-73632381301b/go.mod h1:HRuvtXLZ4WkaB1MItToVH2e8ZwKwZPY5/Rcby+CvvLY= github.com/prysmaticlabs/prysm/v5 v5.3.0 h1:7Lr8ndapBTZg00YE+MgujN6+yvJR6Bdfn28ZDSJ00II= github.com/prysmaticlabs/prysm/v5 v5.3.0/go.mod h1:r1KhlduqDMIGZ1GhR5pjZ2Ko8Q89noTDYTRoPKwf1+c= -github.com/pushchain/evm v1.0.0-rc2.0.20260911043035-bfa1fef525c4 h1:2Ra4vGhuK46tekmcd3XyXMKAn634tVu6gqGsEmOiC88= -github.com/pushchain/evm v1.0.0-rc2.0.20260911043035-bfa1fef525c4/go.mod h1:QuenX5DgRhWeYdIg0J/p65cyS/ntpgnzpZOIajZ/SHk= +github.com/pushchain/evm v1.0.0-rc2.0.20260911052808-93debf9a8fe6 h1:nsKZBgR7H5stpZmeex23MlsQrtPDrlN/CzEgeCcJZOs= +github.com/pushchain/evm v1.0.0-rc2.0.20260911052808-93debf9a8fe6/go.mod h1:QuenX5DgRhWeYdIg0J/p65cyS/ntpgnzpZOIajZ/SHk= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= github.com/quic-go/qtls-go1-20 v0.3.4 h1:MfFAPULvst4yoMgY9QmtpYmfij/em7O8UUi+bNVm7Cg= From a39fa1a536e09b78dd0f52cbba7c04206331926b Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Fri, 11 Sep 2026 11:43:15 +0530 Subject: [PATCH 59/60] fix: activate both verifier addresses in genesis, drop dead utxhashverifier --- .../scripts/setup-genesis-auto.sh | 2 +- local-native/scripts/setup-genesis-auto.sh | 2 +- precompiles/usigverifier/genesis_scripts_test.go | 15 ++++++--------- scripts/test_node.sh | 2 +- testnet/core/setup/setup_genesis_validator.sh | 2 +- 5 files changed, 10 insertions(+), 13 deletions(-) diff --git a/local-multi-validator/scripts/setup-genesis-auto.sh b/local-multi-validator/scripts/setup-genesis-auto.sh index 727155ff3..20eebc97e 100755 --- a/local-multi-validator/scripts/setup-genesis-auto.sh +++ b/local-multi-validator/scripts/setup-genesis-auto.sh @@ -231,7 +231,7 @@ update_genesis '.app_state["gov"]["params"]["expedited_voting_period"]="150s"' # EVM update_genesis `printf '.app_state["evm"]["params"]["evm_denom"]="%s"' $DENOM` -update_genesis '.app_state["evm"]["params"]["active_static_precompiles"]=["0x0000000000000000000000000000000000000100","0x0000000000000000000000000000000000000400","0x0000000000000000000000000000000000000800","0x0000000000000000000000000000000000000801","0x0000000000000000000000000000000000000802","0x0000000000000000000000000000000000000803","0x0000000000000000000000000000000000000804","0x0000000000000000000000000000000000000805","0xEC00000000000000000000000000000000000001"]' +update_genesis '.app_state["evm"]["params"]["active_static_precompiles"]=["0x00000000000000000000000000000000000000ca","0x0000000000000000000000000000000000000100","0x0000000000000000000000000000000000000400","0x0000000000000000000000000000000000000800","0x0000000000000000000000000000000000000801","0x0000000000000000000000000000000000000802","0x0000000000000000000000000000000000000803","0x0000000000000000000000000000000000000804","0x0000000000000000000000000000000000000805","0xEC00000000000000000000000000000000000001"]' # EVM Chain config update_genesis `printf '.app_state["evm"]["params"]["chain_config"]["chain_id"]=%s' $EVM_CHAIN_ID` diff --git a/local-native/scripts/setup-genesis-auto.sh b/local-native/scripts/setup-genesis-auto.sh index 7560bea8d..ff12f9c52 100755 --- a/local-native/scripts/setup-genesis-auto.sh +++ b/local-native/scripts/setup-genesis-auto.sh @@ -113,7 +113,7 @@ update_genesis '.app_state["gov"]["params"]["max_deposit_period"]="300s"' update_genesis '.app_state["gov"]["params"]["voting_period"]="300s"' update_genesis '.app_state["gov"]["params"]["expedited_voting_period"]="60s"' update_genesis ".app_state[\"evm\"][\"params\"][\"evm_denom\"]=\"$DENOM\"" -update_genesis '.app_state["evm"]["params"]["active_static_precompiles"]=["0x0000000000000000000000000000000000000100","0x0000000000000000000000000000000000000400","0x0000000000000000000000000000000000000800","0x0000000000000000000000000000000000000801","0x0000000000000000000000000000000000000802","0x0000000000000000000000000000000000000803","0x0000000000000000000000000000000000000804","0x0000000000000000000000000000000000000805","0xEC00000000000000000000000000000000000001"]' +update_genesis '.app_state["evm"]["params"]["active_static_precompiles"]=["0x00000000000000000000000000000000000000ca","0x0000000000000000000000000000000000000100","0x0000000000000000000000000000000000000400","0x0000000000000000000000000000000000000800","0x0000000000000000000000000000000000000801","0x0000000000000000000000000000000000000802","0x0000000000000000000000000000000000000803","0x0000000000000000000000000000000000000804","0x0000000000000000000000000000000000000805","0xEC00000000000000000000000000000000000001"]' update_genesis ".app_state[\"staking\"][\"params\"][\"bond_denom\"]=\"$DENOM\"" update_genesis ".app_state[\"mint\"][\"params\"][\"mint_denom\"]=\"$DENOM\"" update_genesis '.consensus["params"]["abci"]["vote_extensions_enable_height"]="2"' diff --git a/precompiles/usigverifier/genesis_scripts_test.go b/precompiles/usigverifier/genesis_scripts_test.go index 83468826d..00f73b88e 100644 --- a/precompiles/usigverifier/genesis_scripts_test.go +++ b/precompiles/usigverifier/genesis_scripts_test.go @@ -11,13 +11,7 @@ import ( usigverifierprecompile "github.com/pushchain/push-chain-node/precompiles/usigverifier" ) -// legacyUSigVerifierAddress is where the Ed25519 signature verifier precompile -// used to live. Nothing is registered at it any more, so a genesis that still -// declares it active routes calls to an unimplemented address, which panics -// (recovered by baseapp, so the tx just fails). -const legacyUSigVerifierAddress = "0x00000000000000000000000000000000000000ca" - -// legacyUtxHashVerifierAddress is the other stale entry: the utxhashverifier +// legacyUtxHashVerifierAddress is the stale entry: the utxhashverifier // precompile has no implementation anywhere in the tree, and the // remove-utxverifier upgrade strips it from live chains. Leaving it in genesis // would re-introduce on every fresh chain exactly the address that upgrade @@ -51,13 +45,16 @@ func TestGenesisScriptsActivateCurrentVerifier(t *testing.T) { } require.NotEmpty(t, line, "no active_static_precompiles assignment found") - require.NotContains(t, strings.ToLower(line), strings.ToLower(legacyUSigVerifierAddress), - "genesis must not declare the legacy verifier address, nothing is registered at it") require.NotContains(t, strings.ToLower(line), strings.ToLower(legacyUtxHashVerifierAddress), "genesis must not declare the utxhashverifier address, nothing is registered at it") + // The node registers the verifier at both addresses (app.go), so genesis + // must activate both: 0x…ca stays live for contracts that hardcoded it. require.Contains(t, strings.ToLower(line), strings.ToLower(usigverifierprecompile.USigVerifierPrecompileAddress), "genesis must activate the verifier address the node registers") + require.Contains(t, strings.ToLower(line), + strings.ToLower(usigverifierprecompile.USigVerifierPrecompileAddressV2), + "genesis must activate the v2 verifier address the node registers") }) } } diff --git a/scripts/test_node.sh b/scripts/test_node.sh index 928637120..e073e98df 100755 --- a/scripts/test_node.sh +++ b/scripts/test_node.sh @@ -116,7 +116,7 @@ from_scratch () { # (LoadEvmCoinInfo); without metadata for the base denom the node panics on start # with "denom metadata could not be found". update_test_genesis '.app_state["bank"]["denom_metadata"]=[{"description":"Native token of Push Chain","denom_units":[{"denom":"upc","exponent":0,"aliases":[]},{"denom":"pushchain","exponent":18,"aliases":[]}],"base":"upc","display":"pushchain","name":"Push Chain","symbol":"PC"}]' - update_test_genesis '.app_state["evm"]["params"]["active_static_precompiles"]=["0x00000000000000000000000000000000000000CB","0x00000000000000000000000000000000000000ca","0x0000000000000000000000000000000000000100","0x0000000000000000000000000000000000000400","0x0000000000000000000000000000000000000800","0x0000000000000000000000000000000000000801","0x0000000000000000000000000000000000000802","0x0000000000000000000000000000000000000803","0x0000000000000000000000000000000000000804","0x0000000000000000000000000000000000000805","0xEC00000000000000000000000000000000000001"]' + update_test_genesis '.app_state["evm"]["params"]["active_static_precompiles"]=["0x00000000000000000000000000000000000000ca","0x0000000000000000000000000000000000000100","0x0000000000000000000000000000000000000400","0x0000000000000000000000000000000000000800","0x0000000000000000000000000000000000000801","0x0000000000000000000000000000000000000802","0x0000000000000000000000000000000000000803","0x0000000000000000000000000000000000000804","0x0000000000000000000000000000000000000805","0xEC00000000000000000000000000000000000001"]' update_test_genesis '.app_state["erc20"]["native_precompiles"]=["0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"]' # https://eips.ethereum.org/EIPS/eip-7528 update_test_genesis `printf '.app_state["erc20"]["token_pairs"]=[{contract_owner:1,erc20_address:"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",denom:"%s",enabled:true}]' $DENOM` update_test_genesis '.app_state["feemarket"]["params"]["no_base_fee"]=false' diff --git a/testnet/core/setup/setup_genesis_validator.sh b/testnet/core/setup/setup_genesis_validator.sh index 8a3001a0a..2c14d166d 100755 --- a/testnet/core/setup/setup_genesis_validator.sh +++ b/testnet/core/setup/setup_genesis_validator.sh @@ -120,7 +120,7 @@ echo "🛠️ Updating genesis parameters..." # EVM update_test_genesis `printf '.app_state["evm"]["params"]["evm_denom"]="%s"' $DENOM` # This seems duplicated since chain config already has this - update_test_genesis '.app_state["evm"]["params"]["active_static_precompiles"]=["0x0000000000000000000000000000000000000100","0x0000000000000000000000000000000000000400","0x0000000000000000000000000000000000000800","0x0000000000000000000000000000000000000801","0x0000000000000000000000000000000000000802","0x0000000000000000000000000000000000000803","0x0000000000000000000000000000000000000804","0x0000000000000000000000000000000000000805","0xEC00000000000000000000000000000000000001"]' + update_test_genesis '.app_state["evm"]["params"]["active_static_precompiles"]=["0x00000000000000000000000000000000000000ca","0x0000000000000000000000000000000000000100","0x0000000000000000000000000000000000000400","0x0000000000000000000000000000000000000800","0x0000000000000000000000000000000000000801","0x0000000000000000000000000000000000000802","0x0000000000000000000000000000000000000803","0x0000000000000000000000000000000000000804","0x0000000000000000000000000000000000000805","0xEC00000000000000000000000000000000000001"]' update_test_genesis '.app_state["evm"]["params"]["chain_config"]["homestead_block"]="0"' update_test_genesis '.app_state["evm"]["params"]["chain_config"]["dao_fork_block"]="0"' update_test_genesis '.app_state["evm"]["params"]["chain_config"]["dao_fork_support"]=true' From e217f35e76cac03e25440d1ae729e26db963d97f Mon Sep 17 00:00:00 2001 From: Nilesh Gupta Date: Fri, 11 Sep 2026 12:07:06 +0530 Subject: [PATCH 60/60] chore: add audit-fixes upgrade handler Seeds max_gasless_tx_gas so the ante's zero-fallback is not the live value. --- app/upgrades.go | 2 + app/upgrades/audit-fixes/upgrade.go | 83 +++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 app/upgrades/audit-fixes/upgrade.go diff --git a/app/upgrades.go b/app/upgrades.go index 3c489f141..4bc60636f 100755 --- a/app/upgrades.go +++ b/app/upgrades.go @@ -8,6 +8,7 @@ import ( "github.com/pushchain/push-chain-node/app/upgrades" aiauditfixes "github.com/pushchain/push-chain-node/app/upgrades/ai-audit-fixes" aiauditfixes2 "github.com/pushchain/push-chain-node/app/upgrades/ai-audit-fixes-2" + auditfixes "github.com/pushchain/push-chain-node/app/upgrades/audit-fixes" ceagasandpayload "github.com/pushchain/push-chain-node/app/upgrades/cea-gas-and-payload" ceapayloadverificationfix "github.com/pushchain/push-chain-node/app/upgrades/cea-payload-verification-fix" chainmeta "github.com/pushchain/push-chain-node/app/upgrades/chain-meta" @@ -102,6 +103,7 @@ var Upgrades = []upgrades.Upgrade{ // address still unclaimed in the A/B/C ranges (41 of 47 on donut, incl. 0xC2) readstate.NewUpgrade(), evmv063.NewUpgrade(), + auditfixes.NewUpgrade(), } // RegisterUpgradeHandlers registers the chain upgrade handlers diff --git a/app/upgrades/audit-fixes/upgrade.go b/app/upgrades/audit-fixes/upgrade.go new file mode 100644 index 000000000..9238909ef --- /dev/null +++ b/app/upgrades/audit-fixes/upgrade.go @@ -0,0 +1,83 @@ +package auditfixes + +import ( + "context" + "fmt" + + storetypes "cosmossdk.io/store/types" + upgradetypes "cosmossdk.io/x/upgrade/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + + "github.com/pushchain/push-chain-node/app/upgrades" + uexecutortypes "github.com/pushchain/push-chain-node/x/uexecutor/types" +) + +const UpgradeName = "audit-fixes" + +// Hacken audit remediation plus the evm-side fixes. No store or module version +// changes; the handler seeds the one new param so the ante's zero-fallback is +// never the operative value. +func NewUpgrade() upgrades.Upgrade { + return upgrades.Upgrade{ + UpgradeName: UpgradeName, + CreateUpgradeHandler: CreateUpgradeHandler, + StoreUpgrades: storetypes.StoreUpgrades{ + Added: []string{}, + Deleted: []string{}, + }, + } +} + +func CreateUpgradeHandler( + mm upgrades.ModuleManager, + configurator module.Configurator, + ak *upgrades.AppKeepers, +) upgradetypes.UpgradeHandler { + return func(ctx context.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { + sdkCtx := sdk.UnwrapSDKContext(ctx) + logger := sdkCtx.Logger().With("upgrade", UpgradeName) + logger.Info("starting audit-fixes upgrade") + + if err := setMaxGaslessTxGas(ctx, ak, logger); err != nil { + return nil, err + } + + versionMap, err := mm.RunMigrations(ctx, configurator, fromVM) + if err != nil { + return nil, fmt.Errorf("run migrations: %w", err) + } + + logger.Info("audit-fixes upgrade complete") + return versionMap, nil + } +} + +// setMaxGaslessTxGas writes the cap a gasless tx may declare. Params stored before +// this release decode the new field as 0, which the ante reads as "unset" and +// substitutes the default for; setting it here makes the live value explicit and +// governance-adjustable instead. +func setMaxGaslessTxGas(ctx context.Context, ak *upgrades.AppKeepers, logger interface{ Info(string, ...any) }) error { + if ak == nil || ak.UExecutorKeeper == nil { + return fmt.Errorf("uexecutor keeper unavailable") + } + + params, err := ak.UExecutorKeeper.GetParams(ctx) + if err != nil { + return fmt.Errorf("read uexecutor params: %w", err) + } + + if params.MaxGaslessTxGas != 0 { + logger.Info("max gasless tx gas already set, leaving it alone", + "value", params.MaxGaslessTxGas) + return nil + } + + params.MaxGaslessTxGas = uexecutortypes.DefaultMaxGaslessTxGas + if err := ak.UExecutorKeeper.UpdateParams(ctx, params); err != nil { + return fmt.Errorf("set max gasless tx gas: %w", err) + } + + logger.Info("max gasless tx gas set", "value", uexecutortypes.DefaultMaxGaslessTxGas) + return nil +}