From d3ce868454aba522c4966ed1c7cea204ce69959a Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 28 Nov 2023 14:28:33 +0100 Subject: [PATCH 001/216] Allows unshielding assets from previous epochs --- shared/src/ledger/native_vp/masp.rs | 49 +++++++++++------------------ 1 file changed, 18 insertions(+), 31 deletions(-) diff --git a/shared/src/ledger/native_vp/masp.rs b/shared/src/ledger/native_vp/masp.rs index 54048f6759..4022b3ef99 100644 --- a/shared/src/ledger/native_vp/masp.rs +++ b/shared/src/ledger/native_vp/masp.rs @@ -58,23 +58,6 @@ fn asset_type_from_epoched_address( AssetType::new(token_bytes.as_ref()).expect("unable to create asset type") } -/// Checks if the asset type matches the expected asset type, Adds a -/// debug log if the values do not match. -fn valid_asset_type( - asset_type: &AssetType, - asset_type_to_test: &AssetType, -) -> bool { - let res = - asset_type.get_identifier() == asset_type_to_test.get_identifier(); - if !res { - tracing::debug!( - "The asset type must be derived from the token address and \ - current epoch" - ); - } - res -} - /// Checks if the reported transparent amount and the unshielded /// values agree, if not adds to the debug log fn valid_transfer_amount( @@ -196,20 +179,23 @@ where None => continue, }; - let expected_asset_type: AssetType = - asset_type_from_epoched_address( - epoch, - &transfer.token, - denom, - ); - // Satisfies 2. and 3. - if !valid_asset_type(&expected_asset_type, &out.asset_type) { - // we don't know which masp denoms are necessary - // apriori. This is encoded via - // the asset types. - continue; - } + let conversion_state = self.ctx.storage.get_conversion_state(); + let asset_epoch = + match conversion_state.assets.get(&out.asset_type) { + Some(((address, _), asset_epoch, _, _)) + if address == &transfer.token => + { + asset_epoch + } + _ => { + // we don't know which masp denoms are necessary + // apriori. This is encoded via + // the asset types. + continue; + } + }; + if !valid_transfer_amount( out.value, denom.denominate(&transfer.amount.amount), @@ -218,7 +204,7 @@ where } let (_transp_asset, transp_amt) = convert_amount( - epoch, + *asset_epoch, &transfer.token, transfer.amount.amount, denom, @@ -285,6 +271,7 @@ where } _ => {} } + // Verify the proofs and charge the gas for the expensive execution self.ctx .charge_gas(MASP_VERIFY_SHIELDED_TX_GAS) From fdf727a30772bcb97cc28b75c35bcdaa11e37e3c Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 28 Nov 2023 14:29:20 +0100 Subject: [PATCH 002/216] Tests masp cross-epoch unshield --- tests/src/integration/masp.rs | 90 +++++++++++++++++++++++++++++++++-- 1 file changed, 87 insertions(+), 3 deletions(-) diff --git a/tests/src/integration/masp.rs b/tests/src/integration/masp.rs index c5b51a6eac..6f644d219f 100644 --- a/tests/src/integration/masp.rs +++ b/tests/src/integration/masp.rs @@ -1163,9 +1163,6 @@ fn wrapper_fee_unshielding() -> Result<()> { let validator_one_rpc = "127.0.0.1:26567"; // Download the shielded pool parameters before starting node let _ = FsShieldedUtils::new(PathBuf::new()); - // Lengthen epoch to ensure that a transaction can be constructed and - // submitted within the same block. Necessary to ensure that conversion is - // not invalidated. let (mut node, _services) = setup::setup()?; _ = node.next_epoch(); @@ -1248,3 +1245,90 @@ fn wrapper_fee_unshielding() -> Result<()> { assert!(tx_run); Ok(()) } + +// Test that a masp unshield transaction can be succesfully executed even across +// an epoch boundary. +#[test] +fn cross_epoch_tx() -> Result<()> { + // This address doesn't matter for tests. But an argument is required. + let validator_one_rpc = "127.0.0.1:26567"; + // Download the shielded pool parameters before starting node + let _ = FsShieldedUtils::new(PathBuf::new()); + let (mut node, _services) = setup::setup()?; + _ = node.next_epoch(); + + // 1. Shield some tokens + run( + &node, + Bin::Client, + vec![ + "transfer", + "--source", + ALBERT, + "--target", + AA_PAYMENT_ADDRESS, + "--token", + NAM, + "--amount", + "1000", + "--ledger-address", + validator_one_rpc, + ], + )?; + node.assert_success(); + + // 2. Generate the tx in the current epoch + let tempdir = tempfile::tempdir().unwrap(); + run( + &node, + Bin::Client, + vec![ + "transfer", + "--source", + A_SPENDING_KEY, + "--target", + BERTHA, + "--token", + NAM, + "--amount", + "100", + "--gas-payer", + ALBERT_KEY, + "--output-folder-path", + tempdir.path().to_str().unwrap(), + "--dump-tx", + "--ledger-address", + validator_one_rpc, + ], + )?; + node.assert_success(); + + // Look for the only file in the temp dir + let tx_path = tempdir + .path() + .read_dir() + .unwrap() + .next() + .unwrap() + .unwrap() + .path(); + + // 3. Submit the unshielding in the following epoch + _ = node.next_epoch(); + run( + &node, + Bin::Client, + vec![ + "tx", + "--owner", + ALBERT_KEY, + "--tx-path", + tx_path.to_str().unwrap(), + "--ledger-address", + validator_one_rpc, + ], + )?; + node.assert_success(); + + Ok(()) +} From b81de9f24f8ebac3f176a6bc1338372cc4057bac Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 28 Nov 2023 15:39:35 +0100 Subject: [PATCH 003/216] Refactors `convert_amount` --- shared/src/ledger/native_vp/masp.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/shared/src/ledger/native_vp/masp.rs b/shared/src/ledger/native_vp/masp.rs index 4022b3ef99..dcdba8f901 100644 --- a/shared/src/ledger/native_vp/masp.rs +++ b/shared/src/ledger/native_vp/masp.rs @@ -45,7 +45,7 @@ where pub ctx: Ctx<'a, DB, H, CA>, } -/// Generates the current asset type given the current epoch and an +/// Generates the current asset type given the provided epoch and an /// unique token address fn asset_type_from_epoched_address( epoch: Epoch, @@ -82,13 +82,12 @@ fn convert_amount( token: &Address, val: token::Amount, denom: token::MaspDenom, -) -> (AssetType, I128Sum) { +) -> I128Sum { let asset_type = asset_type_from_epoched_address(epoch, token, denom); // Combine the value and unit into one amount - let amount = - I128Sum::from_nonnegative(asset_type, denom.denominate(&val) as i128) - .expect("invalid value or asset type for amount"); - (asset_type, amount) + + I128Sum::from_nonnegative(asset_type, denom.denominate(&val) as i128) + .expect("invalid value or asset type for amount") } impl<'a, DB, H, CA> NativeVp for MaspVp<'a, DB, H, CA> @@ -117,7 +116,7 @@ where // where the shielded value has an incorrect timestamp // are automatically rejected for denom in token::MaspDenom::iter() { - let (_transp_asset, transp_amt) = convert_amount( + let transp_amt = convert_amount( epoch, &transfer.token, transfer.amount.into(), @@ -203,7 +202,7 @@ where return Ok(false); } - let (_transp_asset, transp_amt) = convert_amount( + let transp_amt = convert_amount( *asset_epoch, &transfer.token, transfer.amount.amount, From 0629006cfd2497b3907c8673626cec3cc2d06752 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 28 Nov 2023 16:18:27 +0100 Subject: [PATCH 004/216] Updates masp fixtures for tests --- ...3F779253B53A9A0BD62FD3DF8F6FCF4AF1E145.bin | Bin 17018 -> 17018 bytes ...BF9BC175A70082068C8785BDDBF49DCCA4BE66.bin | Bin 7448 -> 7448 bytes ...C89879834677F926130D56EB5E4067728EB5CF.bin | Bin 10382 -> 10382 bytes ...BE50202AF004DFD895A721EDA284D96B253ACC.bin | Bin 7448 -> 7448 bytes ...3CEA99A9BC66B2BAB03D4ABA1AE57B12596061.bin | Bin 7448 -> 7448 bytes ...F136D4522BAFC99E0E81FD54A28A38E8419CFC.bin | Bin 20518 -> 20518 bytes ...B0464F2130123149D5691B85C35AABC49FA2BD.bin | Bin 7448 -> 7448 bytes ...C010E6D95775165E4FC619A11DCFDB59E21D30.bin | Bin 0 -> 6669 bytes ...6D2CF14C7F9E90A8588BA75DA0D2860F36E2CB.bin | Bin 9649 -> 9649 bytes ...396732D2EBF77728D2772EB251123DF2CEF6A1.bin | Bin 9941 -> 9941 bytes ...20D5BB3FDEB1199EF712C059C80C4DBF6F3ED8.bin | Bin 9941 -> 9941 bytes ...5CF776D5DE7C9153D67B7FDF22ED6F35558738.bin | Bin 24494 -> 24494 bytes ...DBA1FDB2515DF33D43611F2860B8D3C178B474.bin | Bin 7448 -> 7448 bytes ...0F74AF27826AB075ECA82AE64F488CC7D7B99D.bin | Bin 0 -> 7448 bytes ...6C8B75587507CA52981F48FD1FC9C5BE0BEE43.bin | Bin 15257 -> 15257 bytes 15 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 test_fixtures/masp_proofs/A312CDD49C05B7C768F5DAF708C010E6D95775165E4FC619A11DCFDB59E21D30.bin create mode 100644 test_fixtures/masp_proofs/E7F3B43D776427F6570F4EF1600F74AF27826AB075ECA82AE64F488CC7D7B99D.bin diff --git a/test_fixtures/masp_proofs/51EF94B13138B91DB3081F95193F779253B53A9A0BD62FD3DF8F6FCF4AF1E145.bin b/test_fixtures/masp_proofs/51EF94B13138B91DB3081F95193F779253B53A9A0BD62FD3DF8F6FCF4AF1E145.bin index 8b21aac70d3fd50b05a84cd005d0e440b1cbefc4..b142e6796d50a608ea20b778d7b3facb2c9cff06 100644 GIT binary patch delta 3073 zcmV+c4F2=_gaP`50k9h;Abth`c&vfJ^b=7?(j}Ky*AU{EP9ZU*U6v`>e`^cnV3Rv0 zKp>UgHQL}kTxqlPc_c<9Lq-$_%p{HDR=pW45J4s))nt>KCP86?=~Ix^Hr5$lf%*oq zEEgRxQ>4|*XvSz|9ks~&EB_c&Hc{0Yi#=acEZZAYnOjxO_X2uGwz(iAnjsUnd^!{c z0GJJYJY$#!{}C+2`@RC7Gp1Qig}2b@ewzk%hABm_vppxI0)KiHmoFr;MA8R9`5#%g z#nstT&XpXS-&f%@T++YLYnvxxae0?gD+6I!3OH)aom5`rSY=xz;9rI{$U;R3Fds=< zFK_%0@E5Z_^MDv%&e|CCYIB`To^#Sv#3BzN3>}U&tz0Ox`LRG$=$Ufb*@FINBmFpQ zF+mDl>&dR_*u{>Q)L97BdP$*B&e=UPJbUhSZ;U^ug zM{kRd?D@3rcB%@U)X)>s?77pjU#%~4oKR;On}&; zObN*d6Ss%c4i+fuRshW+(qbrWhL9e!W}P4dKnp!f4!o~xC@`~hEtLX)f>=ooYr#lqVqnu&G#OLPDZqCu*CMWCvLF2%dZ=q{Z}nn) zVMv}!#hYp*;=)ZTbBa>&HHuh)%C9PRaBT2T43Hh6c%x7LO=T@JHw01yS|GC+O#O*) z)A>djzs@F)^UO<7N0x@Oh%l7`e`j@A?hxHn4xLi++<9r&`8RV9YY1ma()#Rj{|UPr zQ7Dp!sehK%MR=Z9^{2#Lv>;7(Z|@A9=T>&CEi4!%?GtN`TZ zUw|zN!8Pfa@K~1}d`$)tcwmK3le4fkl>$-0I=((IiKdbr^UKGsgO;F@YbK_+qG%5& z@VC(6v?|=h!$wz|_+{d*$v8XT4FJyr_Yn}hiL5!|#dAw|#qnvocd>;g!#Hi6hVGxK zoaCASkr5!1wmBewwldL__vMNx17D(EUJPcE`PG%((=C}~6lCJ_wKQSX#J-E(5mJM@ z3S9Q8`&;lai50pd@d4mFsp1der?1X3DlfHZWuDU1wek;x!K!*uYpJzw%Dbv{9bB4N zfWecR7#igqi@>XfMsD8BY!i~clbG|b@-(ER3Vm+_{Mna({-2gjLn-245$NjTWo}9+ zR;1j$*>m%_4>-AGL=;?aSJJ6f=>oJLgMKR~@Mn13K?+B3Ulz#bNnBRHFr>4=(v00; zAQ|Q3I~hoUvNf7)!LACbJ+OwG+&fkf0>WaDoVswA$wtlx4$shVf{}9S`;3bP=}OKz zNh%Ai%b!<&38~n*O(PEO=@3v#BE;?eaA?u>fE^lxyrQB#_AXR(jmHpIb4(M9Nf5o` z8l%-kd{`6p-Mh7rp!oS9Or%e>6Fvl9|1q@d!=dX`w1mKR>@|q)jdGJTlM(;|J9f-= zQhcHsXA|{K6SF{*7U+qMMEQZhd}o3Io+D@3sIpdnrc8A({EoQ0AuQYSTvP)Lp3OZZ zuX)WPMm_z}O@R1E>TE5Qa8)voiS|GV0CZwJu<6!k=ATxwW~^|mARR{ezRdzF`>DVW z3)5mP&ARiCIpdD0kJ&87!%=?EkFPABaHAj-5_1D_{K$Nj9k=F2#e2>pOH>pdh*CjCq z7LcK*mrQd;LAFQMx6A#Y*s6K#VG!yb;b=F1$A0VR|H4tqU1UXksDsiTIMutiqqF%A zT4p0?M9_N$@~D#y2SuFDN`KKlk&>w(F5%Q15CbzuZMiozmjqm;g(JV8;Ded|+9+J1 zuJt3Mz)e=Kj6LlWEiMrX3zy4VJ{Z)@{`(2+IMrxFMHt9AIc#vyMdDvjsSzGZBB#wN4~p+vx)guv}qd8y-D(~$XOW8QdRPMa!!c$L`1 zF+xj}!pb5W(+j;Qksq(5<>*Tqp)Qo+gCqoBsKpA;Cawxe>rpT<%+ zv36pS%ocMZmaIld3iQ|OrV-Hv^t@rCU-A#w{6jJ?d%hUK+_ipAh{)$(3%hc8CUp+j zSvqIu{QqTo{p>W)qBo?4>J+no>YyS!?DFYxz{}_5WZ173jEFWvUZ}Od8k*<^GH=_@ zQff_@VNmw`#R=KPTn!=Yy4HvtlWVe!LGP9t9V2Z#QCPXNRqKFru75Z_XALmzN#f;HzqBp5>(-@_aAbcUfx#GI7UB;6)wiupxtn^bGdYG%oE!7H8O&@PD90 z^`ymt;obyYn@ai-*6VU!MY^WA_KNa|etY z4LzUQs?LP1|LS(b?#RMq|50I;K6GD>XPLp-eyULrNKVe?k*2`p00oO?LDI${G<(~4BWd}qA(!~n zA#Ac3qxp9)+`HibcoWErV=n!rSyzsRlGwyOvts9x3Kt=?NH66mQ@38P4P{emgYKWY zG9`b_s^AzF7ZuWf#3}?D!oKuX){!#jri~oyXooA|)fDKE9V50S{x|4_k+uvu_~v`R zvAB16vFj!t+rc7mC3E^H3hQ#3)PM(A!jtd9V`G6@0HJ0;v18AVe|pgvK}TtXExg_U z$$o`vuW;r*ALV1Qfs1P?AQ1Kma@N z)*x6WqcB|m%Yb$HVd{mQwkMbli5b-JgxoX5p}o2UG~h9P^C}i>q^x=AOE4~cBCtA zA|(+ZGtrrUAiQMGjfmRqn=&Q5-s6_Y0w$z`CJAwI(2uXA4<8v-(=zL>?GUub3x%D5 z?x60)pw;&%CKz4sIOT7R?txyb)#^k5eswXEi@X0&wa0rOU_|#%!cBnLtxb&Hi*))< z!9+ck94ViN;$LM0fdbHRR4SH8N@~vSRU;&ijOzFO;!txEE%ie@=y)^WBoHm9dOmW~ P|90p7ZFidklNLf`uU7d} delta 3073 zcmV+c4F2=_gaP`50k9h;AnI@ZJz^f#>llV+W){QA+|b+gurll52aS8!3tK>yhLbxc zKp?YGZysmI5h<$bE88eF^nM%s$^x(NiD;JU2Wb#)Vuq8OCP86s=jK{$!7%egrol9|pJW4G;B?cVaUJ zAC@h0pD%2cx_tBEejMj|jR!pafeJ?LZda4%7QoR^vppxI0)GbAynkk?zrIn5xx1U8f(XFTyY<1o$9Rm?wz?euCnvE=?{BmFpQ zF+mDl>&d`r6724f;Xm}3HK1pZS{Vgy_-CLeXR>Oh$h49Bz9s7NBNt} z@<8PokL1){$hLA?Q}tkMmsPJ#TS@##j@C5exPS)bT6fjo+#4l26x_we3{1g~>YN{`dLd!tdjzs@F)^UO<7N0x@Oh%l7`ep_{6IR3t=ZMoQXbA5%xoW4h>smM5DIfmp zWp;(;ex#gRC*{;^<7=0@9ipPAoj1E*cVgfot5R)c&MT|8{A{~0$Ulz2%iP3t4vG`o zSSi_fgoo*v@K~1}d`$)tcwmK3le4fkl>$+y{db{?&BcOik9HJIrMqt!_3dWe$1#ZP z(%xjplfcFGp8lkTtLOEUB}W2NC3O{|m(oqIOG6x-Dq6Cp;u$)?NHvr*}(oX}%czLi662w?sLn71bHzxkH; z4TLW43{RYKfFx9zizOEvf)tAj~Idk!BURv5A>g z+%gajiSxa(Q6!Wj17JDzC)?i>)5r!^pK92H2I>+p^$R!>#H@~+w`LNS)|QY6n@b4s zkSthK^nM=pti&m8SC_}kfEX4?Az%rN6mQMFV$aYr?NmSje=S0^CYRx&7}%Fl)i7ay zmVX1pj~bAqK`yb1xPJVSd>+CCX5^KB)Qr>vL~!%Bl4GXC2GiMoFGoVOKYIy%vPu!r znZ?J(vYJ`GMgrqKIk#Hvv1vqGHY0%?rre3#)dWvCW~T~Nz<=@ zRa_ZU@!Jc`xr*I?#!h&oH#+@Vg_iq8jN<6q!V{l~cDIB-|9D9=tl3XG_}`Z*;mqEv zN2w^TI9%aE>@V}2WJy6avwx7F%Jkx>R*z(5g|O-}HeB9IngeL0ZL5rLYtl-rUCSj- zxSQqK)8D-QNGWOqg!NR4TsE*o zdM6Z}1T(K%eG{ghl11QHQ38#4-UkXSms(}(BH8PK?mj{#cFE70`=@m6a;G`U5gN8* zUcB=AHK%>t@I@)FF?7LH(|<#9)G+pujm&X9?R-DrXr4(^(w_~&wM}?`&62c%J(7yW zFkiSsEM)zOxmC_lLJj9}sE(ejuK`F=x>u5J!JUfw6}y(#(ik396OX&2-v0umViCD1 z*XKX2R8H=C5R{F-B>Y)Zspv#sOGIz0xDqBZem4?N1}-;kE_JMsS6~gWugJ3reoSEIx=z{2z?xB zPseRdMRAsTQG-4P&qVsS9n68T&Zdk8NF_}@2nvW;Q%FvxH{_>(xEUF7lqtvNWi2>C zJ3E$TbGXN4^hYgF>NvKzqf^X4DXR;1$y2$wPlB%t4xJSOs2(9<*;0#zs!Wp6GEb2l zcFz|L_>!~$e>;l^?0#~pZX!HTNAtY_swb2zi4JRR+w-><3LG7jrlR-aeK~8wku{Fp z3v@yFgPe+jAv!OA8hf%cvKvNWn+n%Im$9Z_N7m2elai|T@mwKO`DpMr(E!&Yr7*~q z4#@zwF5$KF3PoFqH=K%n4FQKFORvSyjW`Hmx%7DJ{Q|Kf&0~A?E8z7PlbaKUSu6|Q zYJ?5p8*V=TL6z@&mIHcVGy`2^0LeTzpT>JH^xMRDhk}}a9!07pQ?}j=>Pi;kmAk

IvZy^mIO?x?F@kHN>Pv7Nl(|>V&efoOZuVs4QX) zZ(sV3YEZd<4bMt-X`2hOBxBm7b18{)8{C*G(U#Ch@cFjK=RH1y8yTn*WWP48iTdnS zyOv26obcd7Jm`2cTg*bR6I{nAF26gIX=k#5{~aIF1IfdPp`Y*ZNucO9?eL$m%d=My^a3D(fC>u2?(0% znm$|x&X1N)dNY}u)pE0-J{zL5YZLSWAXB+lfjRG7-XR5e`N5pq*J(dm9zGbdB*Om! zwlpzY6SIF7B?Eu09$tG1k#=eUh4*L~B#T@Q>%2#(bcUs3eC8msvaTb?)a&Oh&B|IT z(-)4t-b5&^;|A~(Y}_Domt*E(e|qAH-${~b9zRZZ4t<3hX8@Jz>){5dVk zoP#P7!4}NdQ8D7<>PknYWdU}ZH#{YqP8|GZ|Gh-lIV*o|sQQd|b@cCHq#mgIVKk&Wf-;2F=-lmpO7A~TRzhGm-H z9ZeGY4%Qt>X;)1kDTT;vx}F75u69ypbz)l}y3GxAxfH8?DBE_~&~TefmNa(Bx62q# zvAW=*xQ>5=hoQWTAbxxrhY!U!nm>brUfT>i z>*Y@ zZzy21BOXIp_xdZoI0Z*~9BN}Qy;yjbrOwXvs!pPsJ1KcH%lVu+!Q$1J(v%$?{}fP4^zI*$Nmcu+$2+yCIXD=F4>3%I zmF)(M>0+vZ@P)hhsA(jPJO41fM1BR-#Ngf)gtME z>4@0AVwk|Sq~B@6!`(H6&KCb~g57md#-4vAiWD}-6uCq8acBej%%so%TbKv=MX-gR z>SNvEgBO!O2Tj6f7g$i;`QF399m;7^sOwDlf{0IwS7$?1{jmBVw)7`)($eXMB#Ko) zMKVyG(f-PVs~q~Rjfo=m^rR?niyjc4ghm|%s(hWsdy^L5Y>~GNPCrIdVOBP>RMbXT z1rQAV{O|_#6Cu?pVgs7m`^!5-Cx?-I1KT3IB^eYkLf?qEl>k^4p!EU|>^IWh>wYGo Y+;h-sLf8McF-nE-YVNnx43p#>G{1E8SO5S3 delta 1002 zcmVNPkU z2-)a$D6@YSB?Eth)89^1So>pF^2pBqgUjqnvLu^YUL{Pre-h-xn!Z7?g+pT#%vc-A zUz^6jpi*m?v)(c}`Lzihu%uU}{!$7KC7|gSWuF)aK^;{Sx!=blyVA(3#ahYjjX2$T zh}IkzGcsFlvzu#HCnxndZ!-;5^qNd1qDy`($3SO|eQbXPBH<_Jr|wFA2Q?kG)I9O< zj)NE5PyRT+-Uy5TDm}ys452lA3Q|L;p8-A@rE<;>%ozJ8&PejW!_kf}0Rem|{g-Lsp z|7T`9Gm?a4RZeQ!Xc|*LLYzMYFdIF5+8g141tSt;KAjv;KVC( z)8T5c}C2kinNM1U_7N7CA$B<5t=wA z)CSuY3*V$S18)veyakOEZGi)gL7z1h?1YstYTlS5emCcT0@%pLLM=He(%stXhmZ(4 z0pVL?a~fxmOuvsoM)^^U8hPI%r$v%WLTi7BUHXIbrbO~uhD$U(X4 zhsf{qRH*76m&Z<*%F~9NRMW<#wqlLv+|p3YtvlM&eWravj9pYc-r+J@13mqLTVO_O zLf9weDfrerW(h4~fa&sIc@#r01azGF)*-ACGbZoAQw`5 YCRN$cNHgWa`!LU8nQY__1e4?(G>7!;O8@`> diff --git a/test_fixtures/masp_proofs/6DD5A788D36258E9D5FEF454DBC89879834677F926130D56EB5E4067728EB5CF.bin b/test_fixtures/masp_proofs/6DD5A788D36258E9D5FEF454DBC89879834677F926130D56EB5E4067728EB5CF.bin index 94f997688ba6215afe7dba75785ff44eb9cd9d65..d6777132a3525103b65eb8b4b116daa2a475fa63 100644 GIT binary patch delta 1781 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 Ys&7%Tua*n$zS2UtTw5CJ1C!(&G&rT}e*gdg delta 1002 zcmVwgD`TA3m5A&fV<}mCf zJbQMuPYuYh(n{0Cx;vYjUODc%8+%mgtiE~QG26J(AND5(tYNxC4sm`c;tZwdemyL) z`4O=TaDsLwajbtUanGc`>9fWQ&;5_VURD0*=BtmCJwbn1D9IuE?Q;#qX}P@waWK?W zt3K5Uh8LLh&GjfYx*~S>g z9-t!*=Ehn(^CjT8SqA4L%O9v`2qWS>qZ@s$@|Ehan5@vSy0&+LaCsdcYl{O?=yG4? z)pxU%lal}dE_KD^Gg5J;G=IniJ_xC?Fp;?35ck7|i( zgHu4#v|A7K9;Ac0FlunaZHflI7^35JovxI%2L*ykj=WvMpnN|tRsE+GYi}lw_2mz2 zG|$k1&^WJQhxGX?oFfIpa^y=MkX(Luv#SW@!WQbpILR`Upv=*tiBp`;m!zn{pB^r+ zU~7N+h#`z`nIAGlH|LnAr<7;ALUCHCVH{xQd8U1J^t!@Y!$XZ4c0b|x+M&5Y2sqUw z6AxohieK+*br5HgAvHx>Yyz*f*FYxP=W({c_SzHh;rfqj*=+@=FF0O}Aea$c20dd{ zNzp2!60IOmbMs^{?n>*2J}VKCeNActLl%F&^JYeLu!>Om*o{GFFtypUpid|P#!+Ne zqF*XH`NXO(TQ*lJ0~zQ^7R>GFEt$U0flUc<62`t)zVi#dTqlLd2c~O4~%kbDON)|IU2FrON?zUgXdIkm8Lda|{ YsSi{VHUvmtKNi8DPhU`<3zOs=G=6i~LjV8( diff --git a/test_fixtures/masp_proofs/8032CA7B951C625E43F48AEBD53CEA99A9BC66B2BAB03D4ABA1AE57B12596061.bin b/test_fixtures/masp_proofs/8032CA7B951C625E43F48AEBD53CEA99A9BC66B2BAB03D4ABA1AE57B12596061.bin index b983254f6b6b56c0574db0d1880140d8aa00309c..cce1836fd8c7b690adbc526badc0accadbe85162 100644 GIT binary patch delta 1002 zcmVf#X%_&^$sqSBQ&0ptDyH^a3C!piC%4uEXW;(pXYv zyf6Jw>7%^MK!~2^qk&f#PUgL{YZLSWAZKv(5(NfRvYbzR`{4a@ixm-f>fhwcNiXAk zKft#Fma~5rB?Eti(vr>NZQ#c-Ybw_qSewMB zg-k5%wJycfgJ@Q_t3aek@XFzLAVRpYy)52jX~UHJ2VB{oX*uAcLMEV3rhA=ygd^^m z4h)fo$MXA2x`kyVlkX^n>diziVU7CbOr6fpS<<_qn{@y#?1D-3MD7QGlM}ggZ4nC> z8%Z5}eZqfa&0$7ZR=!tG##`1im}Ym@5we|cf|pw&wd8|+xI>G#*8{M}cjS^j^E?`o z|LziEH)D$vR!bWPK25>>RkV$Xl1fAZ@(&hiIo^t+LO;x{(1fGS)iLYK-R9sDDS7rz z%^X^#ENvA>v2E{m9G+c~Iz%z4td@8rG29deM6`cgKB%d-ij&M~QNu~dkb8j}So~TE zokzSZBrd_BKkHhmKQ=cVrO?$el5>!-T-o@=dXKeR>soR`W~w-De7pbqt+ZqLItPx# zC@E8-6sL@LHrPEhcI#Jksl2FsBh+;H@rDj9hcxHn>GrngMihXMBO8VLl>AnVb5YAH`Z{qJeI^m*Qz12unkRYEkuQ`pf4(v(k-GLXs zSFhS?8k-sz^3hGqvkdb_BunD1d%D8&Qv$@xC8xzIP+#9~Wb(I5jBp#d*lF30uWp>? z_w04{SK1^*+eUROcsVVAN@<^;GymK*y`_IKBEgNLrp}dNr8uweRY)(>%yTYVS|tpP zBk~%4>ZTB^b$Sf+1rNSb*o2zClmONR$ePLeXX_YbNtk__6_GgvhK!Dw#w?62cx7ez zC$j;|!~55rj^_A1GNf-|P*J5$-wK>1`tM> zi3M-kS>O7HeiC>eX7!s^Ca8K)iNZQAqhp*P_GEF?BkU?>t9mg{mmaQYB30u5xvH6? YF5gGFx8o1KqS9TObP-JW43p#>G?_c@egFUf delta 1002 zcmV<7cYm zG_w5sJf!zKTk zb9me9UB?M<>k0_Yfa}|gE4}eAGR8~uk$g5(VNYhqsRnziTY@kRvct)5p5i`^lutt- z6naRqbW9C~iMw&FBC;dA6Ya%O+&y3*NY~&c+B1J;!4<7O-baBFf-uxqbXp1{T+H0! z;iQiS<9HEP3h7V;qc;l2v2?H1lxyTtH;$!2yxNg32bHLL1Z)Qs_H_iS2|2u?#u%(b z$ydsKS-@jVdnId45*`#5W-?-06c1YZ2Hy(D>BP*#8*%OHQWM6G`35^)CHATDdK-4k zr^$bthB6-zZU_f3Pz5vI%tkTH(E4eDN0(vlPaOFHb+K!(tq2)g=gFF8SGWW})T3Z! zx3$I#U&jFTXqYJ$T81vT2-Nz7$4l?wTy!uE=VV@<9VE6e&w`dB0*d{Zk9OkLBy)Om zBjNhmC2?esI5w8`%PH!m;Qft6<8_Gk4rYJfe(t}{nze<;1Gc#EK-7R@kviD;O8<@JA(mut?&+b-QSw}hK()8rE(i@R`{RR) z%J!Gq=_S%PlBnPx# diff --git a/test_fixtures/masp_proofs/8A79BF5E0292339E70287DB626F136D4522BAFC99E0E81FD54A28A38E8419CFC.bin b/test_fixtures/masp_proofs/8A79BF5E0292339E70287DB626F136D4522BAFC99E0E81FD54A28A38E8419CFC.bin index 5aedea90bf4018785a6b0a53a436dfbe106fafe2..c74348c48c549d9cb35d9f7b00f273e92b6b05b7 100644 GIT binary patch delta 3659 zcmV-R4z%&6paG_!0kHQlAeaey4KKIcTXd}b1?3A5)&rtZ2Fy>$@(+LIldUMEMUxjX zKp^WpOV&}x8t8Ptdk*X^$U}CF+d;?>T^buaox#XuPkep z`#1UCdyd`u+~Dnr>@Dp~^HIx_*D*jKG1oHcMZ@A| zKjZBVlQ%L!VE~+utNlc9Nh8k)HlGJ6r;TTwiG6kHo`9<#N#FU~#VGe z5;Sovh1b~BKHdi=TEU%YYV=Mi)&@WGdX{#NDl%jZCz93J*wVY6ukey_F-r2rVTh~j zF`Kj5GNb~3z`stGrg~U+J$}^ai_kQ7c7(-BD_Os@PuY<|WT2up4|m`){FY!@I*}p; zDPb0tMfR$^>~&yKP&iug^U7GB8aMpdCzB=F7Gd!=Z!yJ+-gFRo-6l_8U8Cx1>J4_Z z&~b_lIY5cf=SoqIId$nv;2kzj45fq!Vw>E*T^75u{56#Vf9EH~dDV_?LP>XMfJh&X z2W5CPvqN%0QfA&OgxrGV8+{;q^;+e&Jxfi?(&!2%ZgYx4&~iQ%tZc#WfePuIeqiXR z$?j#iD0D(g-CBziTM*GHN_IW#z7|Je3m@04oZ%I?hd4Y{ZeYKgv7N;o#4R|(ySfr*VP3)b$Fb;4YMRXl>&dji-!&Z7cfz1 zAR=p2y{b4*<9=|W3MeGPF@+i0RkZP3KmWW_k&qZ!$B7mOpZnCb4K%0<4SLW1sL|+1 ztX|rghc9WfnB)GZMhzZ7Qm0UjK*>z@-{BJ=Tc6&nM;L4ruk zk>3??^&OznW9-Ftj9RleKa~Q1p72QVTv4ONMZix^3RxVbhWEBSK1*CD$4PuiziO zn}v8G+vy?$-XY?}aq#$jyYY{$%ZLMV5!<6)RR6mv?0 zyMbLxhhxelb`E*d1p|#mtXF43^|amp(xg;30M$>KtAk55i{2IqnX;8hSjg^oT4K}a z5kY!FQ?wC11uIW3#kJ1gkyPB2W3m?M%#$%iL4S~>udvOWlVhXgp|xaDFjyM0Yvv+e z$W``VwWpo^6tpcI#EQiv!7W#5^EV@=IvcC;_+Wc&uu&qPnh|g|u4yj}`#05xY3#_= zDr%qhY7c1fDCX1i|F!foy~35@Cu9yCCF7<>@huShM^?G=&ztQb5EzI|@B9A#m20n}E z(I{MVZM_!N}qE)w#G5VJh5F+6QhJB7NO8n5m1T7 zUbmt;quI3GW?z+ZBw&#w+$pgC^6c1fhL_QvOY+6wFiE^Xg2A5%%ps@dKKO(}Y=0t! z&y|`GuGuNaIyb$eA=oBvT}h1cRK*D?-3&u*K+sT;fyStR#nBEfL#uSmpTuN}6s(q9 z9k?BPQ)#u-E`j4GhVy_1A%w5Bid#?xKvZNc3w%R3h2%W>yJo1R9b#dID+6Z!`T&+r zvpiG~S92IOK?DurpboBao^Y^ZqJMOT&{i+@OgDRo2l`X?@Px|!4G*xQvv;kily;Fi9$5mq*36g10op|A zwDmsE1oD}3*-vsj`HyQ5^YM>-HfAf-JY)c_h4nDtJs;!!kC*+3Ps|5~G*d z(5eX3Yti1hO*ngwyFns=CcXG&1xdXXZbmz@^^$lF)6i-R50i84kY?Giz`r1C;L-m` z3!7R_#v-yv1|PO?ew&IKWVQeQ5!xI6q?l_kC8dBaMn$Y!z3x@fTYs1*m1{Cs4%x`# zKD$C~H_0H7QcaZ=V7RA{QH*FFj2{NB#%zy&1;$N^-ZU0TxMv*-8d|FqE{J+jkn4iG z8fOv>39Psb2nkTF?J)T zyD4$iZ6rX%NrvX9g@0`odac8)@dCGy!7&;t!PBGT2|2?^vztb_bHq58o_m9aUKgxS?*i zu!B8fvzBCcDprKRs_7ZV2I;pLg?vvj+S5?q>DPJy!z<(;e}5fVvhGh;OaQuqd4+qFZ8+_tm%NcnU?SHvY6V0hxZsaQG!>G7uqmZgJZM!eDLuQDK<)F9$Rb!M*TRr5t+&rL= z4N#u?cm-?%KAh1^mfG~4>*R^BvQ+Y2NC>Li3z>8mS6%C@ouI>0pd-4-Ec%~Pv5HL; zZwO;^#I+!CJlhTF6Iv9|U} zdU?}fh<}%5;xfp(YH7z?Vzvctm|1;cbr>J&DM6v&WdE;WR)ph~Fxh2dzgL37(zYue zHyt@oVO9n7Wy~v&f-);>Ze1c^P0~NHK#t+zCU>i@u#3*@eemm3d9}CGZPPu%sr)** zLF{j^WNs0wQRtg4$IYY=(k2y2FiFI5qA;eg?tgr#hL&x@q$1$*xmSFsVd5s}fF2B% zw-NL}NCD6I#>|(N#RL)1CJF-1FY00p4m%{V<8J6o#01Rznj>+&t@U1@iq2ZQt(lPS z=~ILhC)`kmZDLB=K^-j29I@M#PW^^|GC8vftm0EaTMmv9B}Lxc5Kg4>^hs1ldae6o zn}1Lu<)xB^duMpTd1F!JlE=Z)oFbD~Y}tMl_YAa#)EM5CjmeS(LkDziMy$NfX~Z?K z)}raiBb>_C{}KX=q}_u9O1J5rbn$u1legO?mW|w&VgX;i@j(q#yjUF{GcKaTbn3i= zR;Z!Soerek3nF`NU_#kOrCINfS;O+Kx_@KxfgVD%9Dx>y``@fzo(ar?O=fTokce$kT|)9u5Qi(88R7Wh781VKfIT$Euqhh>26La{yV|gqMr%+`0)=X6yQ(wB z$bkwF0rH)iO;1zA9*f_t#P5(RSP|VGa%LRrwZp$d;grN7eV#rA-~bAe=gcC9@_(s+ z|C}{Mv?yHr@;5L@I$QU#n)1PulV`q}N;wm$B{GSNAf}qf2sCZZ5$EB~#{>_t>?y8m zeQ5{}xJZP>lbh8_t@gOsrarAa69sC?V<0%P8e7HhHKk^C1`VGX_s7ecDH4!sk596k~csfRb77QJ7LrtD}PGN0O_n# zf=aPTHJJ%)4cz)8i$%Vom|4+BQ>jvj3rKVStnGDM_JTEbT!--1z6rYNCC2KeyY6wlehpJ_G`!SqZDuV93W|s%vQ%pxc;*{kUw^08 zz;F|VTy04ipvvD;Y?!oPZBqn<$7v=#3ncl`9#!inv_57MphG8}*g;PaYw)e_(@trB dQ*kOt?Jcjn@^Vyz;6vbc8>J{zJ`IzyPjrJnCiVaT delta 3659 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 Y47Is>l^kAk;^AVTKp0j54wK{@G;^loMgRZ+ delta 1002 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 literal 0 HcmV?d00001 diff --git a/test_fixtures/masp_proofs/A8A9963AC2983B576BAE4DE9BA6D2CF14C7F9E90A8588BA75DA0D2860F36E2CB.bin b/test_fixtures/masp_proofs/A8A9963AC2983B576BAE4DE9BA6D2CF14C7F9E90A8588BA75DA0D2860F36E2CB.bin index e475b08f8b72978c2a6cf90280bec31fc71997bb..9e942f81f5005e2591a70ab551c8bb02e5eeeaa0 100644 GIT binary patch delta 1676 zcmV;726OqbOR-C^niwDwD{z;k!%G7}rxu{vp-cCeIW1PIi*{%MKry3Xhqq^wx)?wp zjN1DC`K}yMWRRs&&j4iUgd0-flJA8W420{+cw=CIV;< zyLeBUnbqm>LV&`6sssI+$#5S9fQWivTt%%**yppn8KeS#1=?LExZa%t_wjA9??>|^ zwi;0<_sEHnZO-$u+U$-FSpOS?)2jgd`7q+C69A@An9l0Dx+1e&G_9o&PopO{L zBU5$`9Pp*H-yBv0e~Tw;kA=j!vkeKA%OFEpJi_)8bjG)ROJp?&t=8WAp=eJlG92p| zoYg#CsH4o!OpbWR@T3g|uo>m?Ob3QZ6^`^7`CEV7dO$i+M~^s#kI$S^`+9ONT4#@?rqdVuf94pZOeAwf?^IU}DAErPE1obA257w5|2t!O zyZN*Na0=d87BSVObaeKKBLg%c0E@$N@2Js@|3yQ+OpTnF;`~U9Y&rgIABx~Q5ny0+ zmpqa{^#IT5KJ##>O^5ecvIY5)=n_rk$dB@~N|o!gf8J12X*5H2;GDeN^SPNJ5J}nN zyp12Yfm7v+AD4WbW32>6kkr#<4ljs{ZVw^?h`{`W;-kyKkG{WR5T`W_135PcXZUbs z!JT%An=K{K4Ry%>Zx1A}l}Wvs(n*+1aZ7BQbo_C06w|*4YMZZ>-w{gmG^jQ1GrRI# z5OwS{e<`PY3#afRhs-DlNwE09&M^D&5euPG_3?jsu32d~V_ff5C5RN82zVLEt-6Qc zo$&aKO|z(d#Vcq88cbLX_YL(bfB1YkljaFH$GD1+Jgk&xFwz&89G!PQzKvGg09V+g z^b8w8;0GPLHz^BaD0cR?n0(n3zkzE2y@zj-4Sl5{23FxWjpE&%-vh&+Wn1S)gA`6DD-9JQ(Cik-r@-dvib3Ik4qNn-2Vd37t z9{tV=_X;%5yk)zHQX7d_W0hHr@o@2HgOJ%E9sfsiUDO&_ecM!JxV+%XJ&wtew zAP`lbLz3^iP$d(tfKHDrJO_>oglh&yu^I8MGO3Ys9-xJty$lO#^}Hp8A@hFk)gSu8 zeguXEx-|OIq_= WH9pHfeK26zWTA9Wz>x%#btODS<3T|H delta 1676 zcmV;726OqbOR-C^niwFZ*1S_7!#ydu5L3T7cC*=oiO(920-T$W0ufKM1 z!<3unjh;VaESm(iD;jA-ug-p6r57$72=4WfC=ETd#7l8~hO}XW!yG?VM7Phy7XWl6 zgUHeI;^Y6V5IO~-1r6T4tx!Q$N=Z1D!xT%gO@!9*xZOerNz%dl&8YJ>_9o&PopO{L zBU5$`9Pp*H-yBv0e~pF zVHGshjsP7OBdK%F-`X1uagn=^HkCu3c-(%6TVkG4xe7zv#;md2{{$F8jZ~Vl12`t1 z5meIj<`OPT$y!mUsXWdYBq%-(!sa2?qA;YOu59BtoDW?`f1Nrz?@Fpu2TN2xt}!uC zV=b3k(bp?=$^<0aCZeZP!9359VJbdu_kUc8>qm5Z*1|PX1U{4=lC=Kui-&}WGp2yb z{Rb2dI6`)HK)09ev)(SBHhJuI*0ntZLUUVlj(O|i-pb~RpEu)|N}9DsYCgDnm8(mP zF+Xq@XlTcNe^{~dzQoRSfObJGR4Ij349kEB(^9x`9sar%Lxc^cKtmu4rX&^;oS7Rt z9g({dF)s7tO$9(qnPgvQChBv>X7<-bVX2wDx-owXIgZeNQ>!SiNtePr!eZ2>bU6!T zpDsdu=LZ#%8N$3TQ+<)O{fa>|+@9Y>_*gE2Nx>fje`V6f`McNDa9qm(HrRWQUrilx z9GcttRjo;y-b)Kgu?}Iir}}EDW~aIonnz%m4AZG{(iAZKxdj@Zp6>bzK$`0$ zB?WZ^w`-35W$w`L%a1i?9m!rsJm?F4dgC|j<|P5iH+m3wZM$^016am1#RY(hd|TlV zcFKjZf1@f(0;W&!FG0}#xe1I?Bd?@S!;}9Ki>)tFt)T)qTO*L{Ai-rV@Niz+2 z{l=LQuBf1L6P9a0PTf~cX61!Duoj}i-AiXe=)(WNSBb5NO>iI;nn6$HUNE38U3JG zc~{;NFbb6ChOn@^AvbgBvndE|V`CrCkaf}GVS-%x8&5R%-LsJ2@oKkyC4HCSaK-XQ z-q8(g18lX(tnY|6e>gUDfU=CRmp#Tf-qx4QL00SlSX7QJWc}94 zQ)&u+f4qg~8Jlj$0hlX_K?@0599J4(W^bL4vCth(hT4q3*wJ<}IbsV3L&|YY)nVDh zpSJ1b8gvu-a*jZ0LW0LSE*xCB6f{nT74V<%0)4-T(A*O1tp~^Y7QJH)0%&7df7!(n zc12LTnemG7tcLQnpe&n>#7mJ!>6x4#nuqL#Z4Dm|*p89K@0kgOc-O7X=Wg`H1x+&J ztx8iI&I=vIT~;IU@mCKB7lU{7QLMt1x2QX!lYDMV$5QE)2sirv#hna#o&&=G{=BTY zC$7q0QPzX}B%f6J;gLS}QKLo%sx&>u<*@O;$sKZbfC#k}kmV6aG@ z9u4gEbL>W&$SDH?Feeei|AY&|xuTgpNm0ibMV2LTDh4P>vXXm@Kq*{o%ARAE)WX%j zU(LSk3Ne@%5(;(kOp~xL4s(^_)O2(zfCWGDH-~COl%$H2tNEJ<2%JCrO6^?MmAhI6 zsZigFl&{n_66^HDfylP{-|J(6uxd_cWi`9LhzGJZI<(fB|isL Wr?5^RF%04@YmL{|`(_A}btOEm11Yiq diff --git a/test_fixtures/masp_proofs/AEA19C9B07742FF5F6D759B171396732D2EBF77728D2772EB251123DF2CEF6A1.bin b/test_fixtures/masp_proofs/AEA19C9B07742FF5F6D759B171396732D2EBF77728D2772EB251123DF2CEF6A1.bin index 4b7967cf8517fcab5932ceb352630a2cdb92311b..6cba86af9a258beb76ffd299c1665469b94a2947 100644 GIT binary patch delta 1742 zcmV;<1~K{7P1Q}XJ{cfT904UY-GL)`I*p3dq~zk%sBjQ`MeOG%-d6dGz^3|WUa5Ch^^RkO4mqykX95fMJtz6ct!!cpSwl7f;gBr3}q zM&VeJ87YyS2$R_$K!2H+S#3P}QuyUEz>``G>}nXgnWHs-D5{ienvo%nYc#D;8+2lJ ziQd%{;?B`~MgfwM{fFL&g|&V^|HnY>4e`o3_N?I-UbTY2{RJ(tX+S-P4|1Yi8Q`Ec z3Jj(QJLCc4#qU@XV4$H8hX0ck*}d*9+!~>iyIMRBn#1&^ zm@M40A1skCj6Zei(4S5Ap(dxGmC&aP>%u)?8z!`^Io=9Ru=$bTVI-+19_Yk@c4DX& z@e94c8w??99e*QZE7ESN-1>;|X2$|e$o0H%*I(#e>(^wpZ)EZddo6aM5`1`&md^34 z(13mYv-EQ@Tz*|=iz#^w(aWH3neUIW34%L~^wCFHopRt%S`T^ruKkK6d?+Z(ak6Ha znK!Is5AcCME-5^FeSq1twZ$WgZIi#vg_$K6+J+~IF@OKx=zy4hcjkX2%hy(^27uE7 zm^fNG{+?9Km+V20=!SWN1O@j_xj4a~pL~qVooHEFRF}x9X_dKwrAq?C|4)~pe}%=X z1h`T!&C#voAMbJ)R$^gSZ-00F|B{aEEQkY@I% z?murR(D+i2V`_VgbqQn9J#+d~aDS_(f}L&s1b7b~uEvVY$k? z4zEGJ(*&T9n&L`UajMT8ev1KDoo1Mbn>ulK8SW0}!&1EH*%I)UgTqR0H; z{D0r88rIniw;RZK6t-ru5zzu{Czf8 zTPlKcuP?;ma4b`Abnv#dup8U3BUIkxNQ`~T%K!FNNFZ$`L4-x34h?3#%hg<%LYxK} z7XGdhc!%X8Cu7y=wc@c|JXMZf!wjRBnsG1KO2_!tL@+z!^(FTV=_L&v` zcTxux-Pl7<{vt+Z9HA)exh-b@RmP@o$!jXlMbb+>TB<<21<}p=on*Gz@TpB$N9;9F z1sY6*PvOtZ`jx>dktywtprFi(!PK@GfWf9YvFeTAQsw^+9$3^XkfPJ=(2_=-n}41& z1YpkMXPTjBZRRkdt#0I--Q0T%)0i|dg1Q-fxqltmu^6VDYY-^Y7-feHuWIx}gMDt#BL8QEET6n;)Pl zt0RRy@Zi=)Nq}mZ1h9**c;jBHe(>OxS{~Yq9ht_!X^)-GP5!;=kE8YdowYm7p!Y;Z z8mY7Zg##&Nf|Ml_I6l5M2AG;*ISoP;UpRlu=oCt^ukTqXd709r#SN;`kbh(Up4TL@ z-giLnsPU1S2VYUIWrI1J3RVt6{8u#$kdsrAG)oQlyT<{ z=5lV3WS@;)a)WrIh$-0_t(Masi(~DY$vv~sR*ecp20sA*0v~S3HEKLlOvSTp8DGx1 kYz^y37kci?=S+UVL-4MA(+m3Taa<9Pb%8M?3X_>8JXj%XhyVZp delta 1742 zcmV;<1~K{7P1Q}XJ{cf%N*$at@l2q=E9@Y0iuXpHP7Ztb<()P3yc|R+b~GN7UKv0j zOSi<4b>41zOnOvn=ihviBPmS*VeTxBU6?W*K{-uTlfD^1Ai;rR$YG~6i-njY*_MEe>250gV0~!Mci)?( zsUrfpLDdhiGdzt#Y8{<;!{g&F;5#F>H9RH)bsc5L|2ZAApc?c7AfQ0QuFg!yf6G+& z1zMc*xtxk&HUpk^E)Wd+$T(GaPP4QfqykVmc#@zW${}4}@crVrLj~YIaKLE6Bsh94 zn6q!j5$d!#trN+(-@4>!;s2Tj4b#^YpLd>%vcxOa2?3;7z1@IJMPfrz3mR6v>*!DD zM&6T>87YyS2$R_$K!36vI?uCnGCKbeap;N+_1-5&l?ixq8d1|1=pJ~9D_p`@fC(>^ z3;b&Hzfq0LrVP0XzbYjR4ubi4NxC>o=)NY=3tOdmsaq1buGbwJ2u^G0L|;g^fHfF@ z@=2@jn!F5lS(z?$x5aF$f=(lU7%r~gjvd^{%KoNLz)x|541at20xG%$iR?s$zo9?y z@n#&PT^l*w3+ikpE20x~;?8gqRi2gDE(-uUM9vyp4)5|2_>8`F#ZmA>p}*oIFnei< zJJZ~7`){^YbfS!Gt&s<4xtZc8@BlV$&0-V4M#YZC3f3RVPXIEa$yK`;-&L`$c|ALD z3s6fS)WkUjjephtNZJ*wWFzs66Nnm>W~2zL8LP&G=PM(YZ%A1L#4JE-4~wQiZMxT1 zz&2ule;(uxTKX3Q98f|FptYeF?Z@| z+Z3xj>`gj!z7$b{#mxA5 zaKLd*BtWT!xREjo?Hx}X=%icJ;r3>#g3fvZ#1BF|uf);jd~7e1W?B@p5p zc<3G5n}8P-H-?pbc_1g4Uj-g8*+E}sWy2G-IvD;qTFgqLlydF@+CIho>{5-pQ9;`E zu;*bJt0}^ctiyIHDUlcB~Go zVK-qo%IOZ(Z|vyTYT)oU>8*%6q3ez()ug>0$TvI!)K^VW#e55~wrMC2 zyA}7Yo1gV97sde~Q$(;>c^6blZ}#PHKV>QY{<7%Vr3L>VO7pIUJkn&bZ5zq{w(__k zRe$e#;JEGwiWcYQb`tPk&-YJ3)o7yz2Z$m16K z2-7?Jr&&9>L7!vF#sCS>cP+B6b$gWF%_2y}RwO27zF7YShE!hWT|KhkZz-i3XhyJr z7ONwnsd0Ip(98wQYk642s{ux87@KzocYnk{_o#p3ePei2q0;deouZwmK^W3hZnFAk z?!`1GZK?Q{@iUW6W&n!2LKyMtdmiA``UgX-xx7*EFy`Iv&}zdR8A5p?0+LE2>zmcX zS`xZ6J)g#T8-IQp`RFgHvr>+|eg|UGGM)$r$od%j<9*iQ8Gf5xhp4g(K~q9KVSm1X ziCm*-PW35>^~ProzTBJG8ZMCn)>+?>v!#Mf>`?EoomkR)2Vwd$WkL2xpD6n-;OwE736_Fa(#CfePu2iP!Wv zSJ8|g#~eAF_G1@uNYOTj2oQ7ppzNf#R;i7kL0a0j_;vu_VA}@)44clV^W`Edy$h*u z?W+!<)3`5pj_og;mY|btG*>5VepK!ef{}L8^vhIkRVdn{d~SpYX-5VE=zkQxwctFA zEfZ3$CRgEqTHEg|2yMJrn#8Wuj+c0!Zu!R1$emK04(4n}hf*XV1p)`8=|I(blz!!z z%M6;IOBvlh;*`+nk1(yrBavs>3%xSL*Wd<6d;ax#WPSg7hpfHtHaFf?1bMbEd%S>= k-}xw!coO^JeCbKqpK`8pY70qryiLd^26qcn0+X30JZRBMo&W#< diff --git a/test_fixtures/masp_proofs/B947FEFC6EC94A041657FF3A9420D5BB3FDEB1199EF712C059C80C4DBF6F3ED8.bin b/test_fixtures/masp_proofs/B947FEFC6EC94A041657FF3A9420D5BB3FDEB1199EF712C059C80C4DBF6F3ED8.bin index 9627a1ce5cf7859d22fd169bd2f71968cdb6ed2a..017ffcd74e17c6107720b129068e877d5ba10f70 100644 GIT binary patch delta 1742 zcmV;<1~K{7P1Q}XJ{cg^I>|h=Vy*jxO1gUKv0j z*Ha}5%Md0r{-b1X>D6^u32y%~xnFiHZ(o(XtD7|(lfD^1AYh%zvy&a?fOOMIH-zGg z@z#>y5xATVMu7hdB>U&0W(_ z7I&-!tMMw0?i+*!LAxVX25Yv=inFvGqykVW7c3)T$(P{~M^|p4b`KWJv))A94$scD zzt*^!Re)rC-N_cBTY@d}^tmwhNPhwPC<=Gnjb`2c%F`af2R#l5IclSMcY}Hztq6Tu zT2Wq-87YyS2$R_$K!1;a*}(SlF>T{EH|RN=D~6EH#U`|lH_yW6b(11x+6k#uT|p1q z393^EFElE|TO6ol+{B;us6FkTP)|)PIrP*Zun6dxz%cbIVKxZ;b{WU2L|q4;6yjU( znt#A=S~m(`Ko$I943FKGT?D;(Cy?Fh2&_Z?ShPaa^#J4lm4CF(Czv)q0bu&8zYUV)lzbuVT$u{DIy)BX}k)&?ml=^b9#>~J?O`!d^3Kyvywq=u74B&{Q`E-G zfvFh0-0_FVgzc#Q*Ak4W-y|UVzuY3XUO*uZhm9@$PLG#3*_dv0mhv2T1t6<$1%i-M ziu@Vn`t7vu(yKt{0@MM{XGm_Bsi|yK?}F{8*q=QJ{eP-V;*fxczV?j@i<_$ikO}6w zJCex=3KNji#NZ!SFsAwyi?ou@-*yc(ArF%Z$M@|Qo`5Y}+1tgt_~^{_Wddsd2)t#W zmnr%jG5#t1jpVgo+L@B1hnFG>Bz`AOp)QqqdL& zAoXdZs((YCHJ^v(V+u|HIm3}c)If5+&eM##Wr#?&*ZD@C{U!M=-PMXhVI9}L+$|E3xh>>o7At*g(Fv7qkB!w z^iREqPI+wsoIcxXH!LHZMrl?BZ>IrvxrP6G{+j3+SS6$e@-;tXu;TlJ?g#zF2c1znJhiF0HK=L3Jm~JIe*g9mS|jPMFM&sTsMB z4kgQpK*C~l;H&6#4PdLxXE=3h3xe65bbooiJeLwKKepR|hgl<$bmn<#dOUaHC4lf5 zc)FO6`t)I}_Eo-|D{Xi!&Y{11;bsK`C^z4}i&O?5O^H@xFpW)S_5SIieXAC*_(rjQ zQQc(in{|pGmS}ifBu%A~|DV-+eFpQ`hpXQ-UykiZ;V+>sTJ#~&c($j<6#E1WE1$q;ZGbK$qHG9s zm$=Y$G4ufAQlwiOz4*mg;kKK(JEeb7xINC0pd z^W)gaXCB}59UdJKn31au|F>RVdF&^+BOs=bNT(a7H5kNUALEJ}G*Iv}+Dj6~bqS+a zehq@~Gb`9cwGsGoEY(Gu@*n4$-dKwZu`=g?T}EfJ<~W@UG|_C8+0HH`-G9cqt6^Si|SovF3zf zh3G+x8gkC&mq6#KDu7;R@FtFFu!LgA>u-9r1I!gE_ly-HK7w{^Gp#jkAh9RDiuZ^r k+Vbs0LW7axs@P+hX~~zEAq#j=HCyZMwymr40h5^~JdGe$K>z>% delta 1742 zcmV;<1~K{7P1Q}XJ{cg#hRQM-EwIWUQl7@Yr-$EMHT=X+icd&iQ~3c{OHN*sUKv0j zhwz-loneJc+so9N7rP#9)@_kyN0)p2-rLhL(`^mXlfD^1AZmR^-8EGxgdTo~$d4hD z3-a>cDp4e!*?Q{jz6AWya;rm2i+wj6nVoBn_K)9PUY5jltW24qR@6ZNYrMrIZED1}-YspYJ|DPPjN6 zjs&FcCFD}_%#E;h?shPEJtXSvnA4UHQHLjHdPjj=Y3yAV3sL>DwNs|Rl;Vf@vlWoN zwFl=v(`*I}&(%`(2DuJ-z^N)lk17~j4=*c^`?~4+Invy5!GF6~%a=Gc?xI?55TT`X z^L#Fn@t`RqYPl_Qj2j8pFtwNJN4+Y=IlE21r)!ZwWlyFxz~nkw!~(u0d9qFFP?6Y< z5?xo;Z;*~4NuactTd-EoXIe=IEpql&xW>nJQj%s0)4uDw$Lkcll8(@5lXW4ayQo_> za`Nq)b-JODJAc-bM78d2OAcZb-|pyU--~LpAjCz{zn#}32LAX9Rf=sd5p&aKU6(Q( zac?K9e$sRtIHNO=S!|{xEFF1e*pwmQ8{GPy>~7Cw7Xc<5ZiN!%wn<_Hm;grPFDA{r z9gbs*Q<998D5sW}e-mm+pK7AJrd@PAmWRHorZK)T{aIrxmkuB4IA zX=)FQ2t^lce+AS&4Q#Hk-oymmzf+VCZ5E#Q7RJso1Fhds>QDU^ePqFGnCoo{0$6JM ziMOPtbcG~-?mJO3dvo2JJSrro6FZ~)v~yQ{pcsUFeN}*xeL#`P04SK{=tk+3uE>2v zlm$6EaewOSjmki)fs#(vaI_V1Oy~H*D4~vR5W_YkVqkwy<|P$x2hi>7SFK?(4#gug z%FFsnGG{yt2=9D*JMMuy9ZQBcIGTmD))?tXCr!n|J81XQSC1jtic#%_8OCm=B^d?a z7rzQhg1YFpl}$ZiE28xPk6L2QT3vMi?w)i*>3{oj@1Y5i^BFm9BciL(+n4MJX;dhs zo6Z4!=OL5exn9EFkBAR(#CJzxe){)ot-?B7w*`Dk0DlZ#HGQIXzHRBn@nr#dm!oe? zJpKjXVUu!mr_&m)VL*}gXM@5HH$m2LxC2>hA-;EVLfy^7|4mVvhyz#jbdb(;3ywd! zz<)K^cZ}Qvk1}UH3ivL@WA`bO5Vi)ye?CMeZQcp}Rcdcf;h>kzQA@trV>!$%ll0MPk%i1?r%>y{~u2f8nsMtJJ0+5vC_ zfM!w)3zp8cq$foXHn)2#BQPb?&42J5Ta%mQR=%5uubjt^w?J7?*Dz!ziIy&})v~gbj?S&5!1(@S`fiC5w0f$oLHhnl3@(#xOww zx+Ron&Vl(UgmB$7hK7F#1WZj;m80twDSQ{&JTr=W>uI201O6Xq2bikR`G4;K=YNl2 zUNf7`XT28z&(E^w7jSv{I3FoJuc|e>DuWO<>(#zXqCzx{dzCz6k@*gBHA{t;mDgWI z;1|n`{zX!E@3Og+kjyby)lxp!XLdWCdq`+tb2wFSmnhaNm5x|+c9sbp0zy4+?j)A5 kz69NbT`MS~f0^mM{wdKM;tP(C9Gtrvzf08w43n8AJk}Re_y7O^ diff --git a/test_fixtures/masp_proofs/C788F9057C615CCE7B260BB8BF5CF776D5DE7C9153D67B7FDF22ED6F35558738.bin b/test_fixtures/masp_proofs/C788F9057C615CCE7B260BB8BF5CF776D5DE7C9153D67B7FDF22ED6F35558738.bin index d3c3a5c24181bdbe0be7e43634ce4e5cd0ac396a..a22c9f21997d0e122b4ae00413dfa3825211e82b 100644 GIT binary patch delta 4170 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 U4*j3bVjHbNHzMT;1d}pfeAH0`p8x;= delta 4170 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 Uq$nFTV-TH5piX((29q*hd^jc<<^TWy diff --git a/test_fixtures/masp_proofs/CBF4087F2AB578C83A736E4598DBA1FDB2515DF33D43611F2860B8D3C178B474.bin b/test_fixtures/masp_proofs/CBF4087F2AB578C83A736E4598DBA1FDB2515DF33D43611F2860B8D3C178B474.bin index 06393963b3a99b320f64849410effb8d8cad18ce..d446d16439f472945b8b22768ae5f1098ee10989 100644 GIT binary patch delta 1002 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 literal 0 HcmV?d00001 diff --git a/test_fixtures/masp_proofs/EDE8DC791D02098C5C199CAF2F6C8B75587507CA52981F48FD1FC9C5BE0BEE43.bin b/test_fixtures/masp_proofs/EDE8DC791D02098C5C199CAF2F6C8B75587507CA52981F48FD1FC9C5BE0BEE43.bin index 90da4e0f00c39b4945c4607f9b3178de728e604e..c26a5a7cc6da1ab867b61b4abb04be28f0c3cfea 100644 GIT binary patch delta 2748 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 Cy+j%S delta 2749 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 Date: Tue, 28 Nov 2023 16:23:18 +0100 Subject: [PATCH 005/216] Changelog #2222 --- .../unreleased/improvements/2222-masp-cross-epoch-proof-fix.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/2222-masp-cross-epoch-proof-fix.md diff --git a/.changelog/unreleased/improvements/2222-masp-cross-epoch-proof-fix.md b/.changelog/unreleased/improvements/2222-masp-cross-epoch-proof-fix.md new file mode 100644 index 0000000000..bd8f0ac7b3 --- /dev/null +++ b/.changelog/unreleased/improvements/2222-masp-cross-epoch-proof-fix.md @@ -0,0 +1,2 @@ +- Allowed the unshielding of previous epochs assets from the masp. + ([\#2222](https://github.com/anoma/namada/pull/2222)) \ No newline at end of file From d8b51149f65fe460eaff1d7377e25e9591c67d5c Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 30 Nov 2023 11:24:59 +0100 Subject: [PATCH 006/216] Updates masp tx to reveal nullifiers --- core/src/types/token.rs | 5 ++++- shared/src/ledger/native_vp/ibc/context.rs | 15 +++++++++++++++ shared/src/vm/host_env.rs | 14 ++++++++++++++ tx_prelude/src/token.rs | 13 +++++++++++++ 4 files changed, 46 insertions(+), 1 deletion(-) diff --git a/core/src/types/token.rs b/core/src/types/token.rs index 5c319b7da6..b8ff751f93 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -900,6 +900,8 @@ pub const HEAD_TX_KEY: &str = "head-tx"; pub const TX_KEY_PREFIX: &str = "tx-"; /// Key segment prefix for pinned shielded transactions pub const PIN_KEY_PREFIX: &str = "pin-"; +/// Key segment prefix for the nullifiers +pub const MASP_NULLIFIERS_KEY_PREFIX: &str = "nullifiers"; /// Last calculated inflation value handed out pub const MASP_LAST_INFLATION_KEY: &str = "last_inflation"; /// The last locked ratio @@ -1124,7 +1126,8 @@ pub fn is_masp_key(key: &Key) -> bool { if *addr == MASP && (key == HEAD_TX_KEY || key.starts_with(TX_KEY_PREFIX) - || key.starts_with(PIN_KEY_PREFIX))) + || key.starts_with(PIN_KEY_PREFIX) + || key.starts_with(MASP_NULLIFIERS_KEY_PREFIX))) } /// Obtain the storage key for the last locked ratio of a token diff --git a/shared/src/ledger/native_vp/ibc/context.rs b/shared/src/ledger/native_vp/ibc/context.rs index ffe2faa2f5..ad613b15eb 100644 --- a/shared/src/ledger/native_vp/ibc/context.rs +++ b/shared/src/ledger/native_vp/ibc/context.rs @@ -5,6 +5,8 @@ use std::collections::{BTreeSet, HashMap, HashSet}; use borsh_ext::BorshSerializeExt; use masp_primitives::transaction::Transaction; use namada_core::ledger::ibc::{IbcCommonContext, IbcStorageContext}; +use namada_core::types::hash::Hash; +use namada_core::types::token::MASP_NULLIFIERS_KEY_PREFIX; use crate::ledger::ibc::storage::is_ibc_key; use crate::ledger::native_vp::CtxPreStorageRead; @@ -237,6 +239,19 @@ where ); self.write(¤t_tx_key, record.serialize_to_vec())?; self.write(&head_tx_key, (current_tx_idx + 1).serialize_to_vec())?; + for description in shielded + .masp_tx + .sapling_bundle() + .map_or(&vec![], |description| &description.shielded_spends) + { + // Reveal the nullifier to prevent double spending + let nullifier_key = Key::from(masp_addr.to_db_key()) + .push(&MASP_NULLIFIERS_KEY_PREFIX.to_owned()) + .expect("Cannot obtain a storage key") + .push(&Hash(description.nullifier.0)) + .expect("Cannot obtain a storage key"); + self.write(&nullifier_key, ())?; + } // If storage key has been supplied, then pin this transaction to it if let Some(key) = &shielded.transfer.key { let pin_key = Key::from(masp_addr.to_db_key()) diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index 2a527d2ff5..19d15b3d3e 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -13,6 +13,7 @@ use namada_core::ledger::gas::{ use namada_core::types::address::{ESTABLISHED_ADDRESS_BYTES_LEN, MASP}; use namada_core::types::internal::KeyVal; use namada_core::types::storage::TX_INDEX_LENGTH; +use namada_core::types::token::MASP_NULLIFIERS_KEY_PREFIX; use namada_core::types::transaction::TxSentinel; use namada_core::types::validity_predicate::VpSentinel; use thiserror::Error; @@ -2541,6 +2542,19 @@ where ); self.write(¤t_tx_key, record)?; self.write(&head_tx_key, current_tx_idx + 1)?; + for description in shielded + .masp_tx + .sapling_bundle() + .map_or(&vec![], |description| &description.shielded_spends) + { + // Reveal the nullifier to prevent double spending + let nullifier_key = Key::from(masp_addr.to_db_key()) + .push(&MASP_NULLIFIERS_KEY_PREFIX.to_owned()) + .expect("Cannot obtain a storage key") + .push(&Hash(description.nullifier.0)) + .expect("Cannot obtain a storage key"); + self.write(&nullifier_key, ())?; + } // If storage key has been supplied, then pin this transaction to it if let Some(key) = &shielded.transfer.key { let pin_key = Key::from(masp_addr.to_db_key()) diff --git a/tx_prelude/src/token.rs b/tx_prelude/src/token.rs index 009cccb36d..5ee657b6df 100644 --- a/tx_prelude/src/token.rs +++ b/tx_prelude/src/token.rs @@ -1,5 +1,6 @@ use masp_primitives::transaction::Transaction; use namada_core::types::address::{Address, MASP}; +use namada_core::types::hash::Hash; use namada_core::types::storage::KeySeg; use namada_core::types::token; pub use namada_core::types::token::*; @@ -60,6 +61,18 @@ pub fn handle_masp_tx( ); ctx.write(¤t_tx_key, record)?; ctx.write(&head_tx_key, current_tx_idx + 1)?; + for description in shielded + .sapling_bundle() + .map_or(&vec![], |description| &description.shielded_spends) + { + // Reveal the nullifier to prevent double spending + let nullifier_key = storage::Key::from(masp_addr.to_db_key()) + .push(&MASP_NULLIFIERS_KEY_PREFIX.to_owned()) + .expect("Cannot obtain a storage key") + .push(&Hash(description.nullifier.0)) + .expect("Cannot obtain a storage key"); + ctx.write(&nullifier_key, ())?; + } // If storage key has been supplied, then pin this transaction to it if let Some(key) = &transfer.key { let pin_key = storage::Key::from(masp_addr.to_db_key()) From 9f855dcdfd0062b47f609d2ba1c8ba9e09892460 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 30 Nov 2023 16:30:24 +0100 Subject: [PATCH 007/216] Masp VP checks uniqueness of nullifiers --- core/src/types/token.rs | 9 +++++ shared/src/ledger/native_vp/masp.rs | 52 ++++++++++++++++++++++++++--- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/core/src/types/token.rs b/core/src/types/token.rs index b8ff751f93..3cb820f4a3 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -1130,6 +1130,15 @@ pub fn is_masp_key(key: &Key) -> bool { || key.starts_with(MASP_NULLIFIERS_KEY_PREFIX))) } +/// Check if the given storage key is a masp nullifier key +pub fn is_masp_nullifier_key(key: &Key) -> bool { + matches!(&key.segments[..], + [DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + .. + ] if *addr == MASP && prefix == MASP_NULLIFIERS_KEY_PREFIX) +} + /// Obtain the storage key for the last locked ratio of a token pub fn masp_last_locked_ratio_key(token_address: &Address) -> Key { key_of_token( diff --git a/shared/src/ledger/native_vp/masp.rs b/shared/src/ledger/native_vp/masp.rs index 54048f6759..a660e1fcdc 100644 --- a/shared/src/ledger/native_vp/masp.rs +++ b/shared/src/ledger/native_vp/masp.rs @@ -1,7 +1,7 @@ //! MASP native VP use std::cmp::Ordering; -use std::collections::BTreeSet; +use std::collections::{BTreeSet, HashSet}; use borsh_ext::BorshSerializeExt; use masp_primitives::asset_type::AssetType; @@ -11,10 +11,12 @@ use namada_core::ledger::storage; use namada_core::ledger::storage_api::OptionExt; use namada_core::ledger::vp_env::VpEnv; use namada_core::proto::Tx; -use namada_core::types::address::Address; use namada_core::types::address::InternalAddress::Masp; -use namada_core::types::storage::{Epoch, Key}; -use namada_core::types::token; +use namada_core::types::address::{Address, MASP}; +use namada_core::types::storage::{Epoch, Key, KeySeg}; +use namada_core::types::token::{ + self, is_masp_nullifier_key, MASP_NULLIFIERS_KEY_PREFIX, +}; use namada_sdk::masp::verify_shielded_tx; use ripemd::Digest as RipemdDigest; use sha2::Digest as Sha2Digest; @@ -119,7 +121,7 @@ where fn validate_tx( &self, tx_data: &Tx, - _keys_changed: &BTreeSet, + keys_changed: &BTreeSet, _verifiers: &BTreeSet

, ) -> Result { let epoch = self.ctx.get_block_epoch()?; @@ -151,6 +153,9 @@ where // 2. the transparent transaction value pool's amount must equal // the containing wrapper transaction's fee // amount Satisfies 1. + // 3. The nullifiers provided by the transaction have not been + // revealed previously (even in the same tx) and no unneeded + // nullifier is being revealed by the tx if let Some(transp_bundle) = shielded_tx.transparent_bundle() { if !transp_bundle.vin.is_empty() { tracing::debug!( @@ -161,6 +166,43 @@ where return Ok(false); } } + + let mut revealed_nullifiers = HashSet::new(); + for description in shielded_tx + .sapling_bundle() + .map_or(&vec![], |description| &description.shielded_spends) + { + let nullifier_key = Key::from(MASP.to_db_key()) + .push(&MASP_NULLIFIERS_KEY_PREFIX.to_owned()) + .expect("Cannot obtain a storage key") + .push(&namada_core::types::hash::Hash( + description.nullifier.0, + )) + .expect("Cannot obtain a storage key"); + if self.ctx.has_key_pre(&nullifier_key)? + || revealed_nullifiers.contains(&nullifier_key) + { + tracing::debug!( + "MASP double spending attempt, the nullifier {:#?} \ + has already been revealed previously", + description.nullifier.0 + ); + return Ok(false); + } + revealed_nullifiers.insert(nullifier_key); + } + + for nullifier_key in + keys_changed.iter().filter(|key| is_masp_nullifier_key(key)) + { + if !revealed_nullifiers.contains(nullifier_key) { + tracing::debug!( + "An unexpected MASP nullifier key {nullifier_key} has \ + been revealed by the transaction" + ); + return Ok(false); + } + } } if transfer.target != Address::Internal(Masp) { From 5cc290c39a3715e71c5b86a3abd28c1cc28ff02a Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 30 Nov 2023 16:31:30 +0100 Subject: [PATCH 008/216] Removes wrong comment --- core/src/ledger/storage/write_log.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/ledger/storage/write_log.rs b/core/src/ledger/storage/write_log.rs index 0cb7b91a2e..a53ac7db36 100644 --- a/core/src/ledger/storage/write_log.rs +++ b/core/src/ledger/storage/write_log.rs @@ -182,7 +182,6 @@ impl WriteLog { &self, key: &storage::Key, ) -> (Option<&StorageModification>, u64) { - // try to read from tx write log first match self.block_write_log.get(key) { Some(v) => { let gas = match v { From 9ed6d5250ba38b268a8c4e95a820d2cf159c1552 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 30 Nov 2023 19:25:19 +0100 Subject: [PATCH 009/216] Changelog #2240 --- .changelog/unreleased/bug-fixes/2240-nullifier-uniqueness.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/2240-nullifier-uniqueness.md diff --git a/.changelog/unreleased/bug-fixes/2240-nullifier-uniqueness.md b/.changelog/unreleased/bug-fixes/2240-nullifier-uniqueness.md new file mode 100644 index 0000000000..dc80af96ac --- /dev/null +++ b/.changelog/unreleased/bug-fixes/2240-nullifier-uniqueness.md @@ -0,0 +1,2 @@ +- Prevents double-spending in masp by adding a nullifier set. + ([\#2240](https://github.com/anoma/namada/pull/2240)) \ No newline at end of file From f1156c1d8966098499276fd61db323c9f943c692 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 1 Dec 2023 12:05:20 +0100 Subject: [PATCH 010/216] Fixes masp vp nullifier validation --- shared/src/ledger/native_vp/masp.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/shared/src/ledger/native_vp/masp.rs b/shared/src/ledger/native_vp/masp.rs index a660e1fcdc..d2b9a9b56b 100644 --- a/shared/src/ledger/native_vp/masp.rs +++ b/shared/src/ledger/native_vp/masp.rs @@ -189,6 +189,16 @@ where ); return Ok(false); } + + // Check that the nullifier is indeed committed (no temp write + // and no delete) and carries no associated data (the latter not + // strictly necessary for validation, but we don't expect any + // value for this key anyway) + match self.ctx.read_bytes_post(&nullifier_key)? { + Some(value) if value.is_empty() => (), + _ => return Ok(false), + } + revealed_nullifiers.insert(nullifier_key); } From 144536fea507e19dfea612428f2fd96ff4917f91 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 1 Dec 2023 12:36:58 +0100 Subject: [PATCH 011/216] Reduce code duplication to reveal masp nullifiers --- core/src/ledger/masp_utils.rs | 30 ++++++++++++++++++++++ core/src/ledger/mod.rs | 1 + shared/src/ledger/native_vp/ibc/context.rs | 17 ++---------- shared/src/vm/host_env.rs | 16 ++---------- tx_prelude/src/token.rs | 15 ++--------- 5 files changed, 37 insertions(+), 42 deletions(-) create mode 100644 core/src/ledger/masp_utils.rs diff --git a/core/src/ledger/masp_utils.rs b/core/src/ledger/masp_utils.rs new file mode 100644 index 0000000000..43100a315a --- /dev/null +++ b/core/src/ledger/masp_utils.rs @@ -0,0 +1,30 @@ +//! MASP utilities + +use masp_primitives::transaction::Transaction; + +use super::storage_api::StorageWrite; +use crate::ledger::storage_api::Result; +use crate::types::address::MASP; +use crate::types::hash::Hash; +use crate::types::storage::{Key, KeySeg}; +use crate::types::token::MASP_NULLIFIERS_KEY_PREFIX; + +/// Writes the nullifiers of the provided masp transaction to storage +pub fn reveal_nullifiers( + ctx: &mut impl StorageWrite, + transaction: &Transaction, +) -> Result<()> { + for description in transaction + .sapling_bundle() + .map_or(&vec![], |description| &description.shielded_spends) + { + let nullifier_key = Key::from(MASP.to_db_key()) + .push(&MASP_NULLIFIERS_KEY_PREFIX.to_owned()) + .expect("Cannot obtain a storage key") + .push(&Hash(description.nullifier.0)) + .expect("Cannot obtain a storage key"); + ctx.write(&nullifier_key, ())?; + } + + Ok(()) +} diff --git a/core/src/ledger/mod.rs b/core/src/ledger/mod.rs index debeaba7db..96cf4d2331 100644 --- a/core/src/ledger/mod.rs +++ b/core/src/ledger/mod.rs @@ -6,6 +6,7 @@ pub mod governance; pub mod ibc; pub mod inflation; pub mod masp_conversions; +pub mod masp_utils; pub mod parameters; pub mod pgf; pub mod replay_protection; diff --git a/shared/src/ledger/native_vp/ibc/context.rs b/shared/src/ledger/native_vp/ibc/context.rs index ad613b15eb..6bb6f90e1c 100644 --- a/shared/src/ledger/native_vp/ibc/context.rs +++ b/shared/src/ledger/native_vp/ibc/context.rs @@ -5,8 +5,7 @@ use std::collections::{BTreeSet, HashMap, HashSet}; use borsh_ext::BorshSerializeExt; use masp_primitives::transaction::Transaction; use namada_core::ledger::ibc::{IbcCommonContext, IbcStorageContext}; -use namada_core::types::hash::Hash; -use namada_core::types::token::MASP_NULLIFIERS_KEY_PREFIX; +use namada_core::ledger::masp_utils; use crate::ledger::ibc::storage::is_ibc_key; use crate::ledger::native_vp::CtxPreStorageRead; @@ -239,19 +238,7 @@ where ); self.write(¤t_tx_key, record.serialize_to_vec())?; self.write(&head_tx_key, (current_tx_idx + 1).serialize_to_vec())?; - for description in shielded - .masp_tx - .sapling_bundle() - .map_or(&vec![], |description| &description.shielded_spends) - { - // Reveal the nullifier to prevent double spending - let nullifier_key = Key::from(masp_addr.to_db_key()) - .push(&MASP_NULLIFIERS_KEY_PREFIX.to_owned()) - .expect("Cannot obtain a storage key") - .push(&Hash(description.nullifier.0)) - .expect("Cannot obtain a storage key"); - self.write(&nullifier_key, ())?; - } + masp_utils::reveal_nullifiers(self, &shielded.masp_tx)?; // If storage key has been supplied, then pin this transaction to it if let Some(key) = &shielded.transfer.key { let pin_key = Key::from(masp_addr.to_db_key()) diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index 19d15b3d3e..1bef64fdb2 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -10,10 +10,10 @@ use masp_primitives::transaction::Transaction; use namada_core::ledger::gas::{ GasMetering, TxGasMeter, MEMORY_ACCESS_GAS_PER_BYTE, }; +use namada_core::ledger::masp_utils; use namada_core::types::address::{ESTABLISHED_ADDRESS_BYTES_LEN, MASP}; use namada_core::types::internal::KeyVal; use namada_core::types::storage::TX_INDEX_LENGTH; -use namada_core::types::token::MASP_NULLIFIERS_KEY_PREFIX; use namada_core::types::transaction::TxSentinel; use namada_core::types::validity_predicate::VpSentinel; use thiserror::Error; @@ -2542,19 +2542,7 @@ where ); self.write(¤t_tx_key, record)?; self.write(&head_tx_key, current_tx_idx + 1)?; - for description in shielded - .masp_tx - .sapling_bundle() - .map_or(&vec![], |description| &description.shielded_spends) - { - // Reveal the nullifier to prevent double spending - let nullifier_key = Key::from(masp_addr.to_db_key()) - .push(&MASP_NULLIFIERS_KEY_PREFIX.to_owned()) - .expect("Cannot obtain a storage key") - .push(&Hash(description.nullifier.0)) - .expect("Cannot obtain a storage key"); - self.write(&nullifier_key, ())?; - } + masp_utils::reveal_nullifiers(self, &shielded.masp_tx)?; // If storage key has been supplied, then pin this transaction to it if let Some(key) = &shielded.transfer.key { let pin_key = Key::from(masp_addr.to_db_key()) diff --git a/tx_prelude/src/token.rs b/tx_prelude/src/token.rs index 5ee657b6df..ac4a4ba1da 100644 --- a/tx_prelude/src/token.rs +++ b/tx_prelude/src/token.rs @@ -1,6 +1,6 @@ use masp_primitives::transaction::Transaction; +use namada_core::ledger::masp_utils; use namada_core::types::address::{Address, MASP}; -use namada_core::types::hash::Hash; use namada_core::types::storage::KeySeg; use namada_core::types::token; pub use namada_core::types::token::*; @@ -61,18 +61,7 @@ pub fn handle_masp_tx( ); ctx.write(¤t_tx_key, record)?; ctx.write(&head_tx_key, current_tx_idx + 1)?; - for description in shielded - .sapling_bundle() - .map_or(&vec![], |description| &description.shielded_spends) - { - // Reveal the nullifier to prevent double spending - let nullifier_key = storage::Key::from(masp_addr.to_db_key()) - .push(&MASP_NULLIFIERS_KEY_PREFIX.to_owned()) - .expect("Cannot obtain a storage key") - .push(&Hash(description.nullifier.0)) - .expect("Cannot obtain a storage key"); - ctx.write(&nullifier_key, ())?; - } + masp_utils::reveal_nullifiers(ctx, shielded)?; // If storage key has been supplied, then pin this transaction to it if let Some(key) = &transfer.key { let pin_key = storage::Key::from(masp_addr.to_db_key()) From 39e00c5c16fdb68f88626c6ff175ff6948cf2dda Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 4 Dec 2023 11:51:35 +0100 Subject: [PATCH 012/216] Refactors `handle_masp_tx` --- core/src/ledger/masp_utils.rs | 53 ++++++++++++++++++++-- shared/src/ledger/native_vp/ibc/context.rs | 41 ++--------------- shared/src/vm/host_env.rs | 37 ++------------- tx_prelude/src/ibc.rs | 5 +- tx_prelude/src/token.rs | 45 +----------------- wasm/wasm_source/src/tx_transfer.rs | 2 +- 6 files changed, 61 insertions(+), 122 deletions(-) diff --git a/core/src/ledger/masp_utils.rs b/core/src/ledger/masp_utils.rs index 43100a315a..4ddc2bc49a 100644 --- a/core/src/ledger/masp_utils.rs +++ b/core/src/ledger/masp_utils.rs @@ -2,15 +2,18 @@ use masp_primitives::transaction::Transaction; -use super::storage_api::StorageWrite; +use super::storage_api::{StorageRead, StorageWrite}; use crate::ledger::storage_api::Result; use crate::types::address::MASP; use crate::types::hash::Hash; -use crate::types::storage::{Key, KeySeg}; -use crate::types::token::MASP_NULLIFIERS_KEY_PREFIX; +use crate::types::storage::{BlockHeight, Epoch, Key, KeySeg, TxIndex}; +use crate::types::token::{ + Transfer, HEAD_TX_KEY, MASP_NULLIFIERS_KEY_PREFIX, PIN_KEY_PREFIX, + TX_KEY_PREFIX, +}; -/// Writes the nullifiers of the provided masp transaction to storage -pub fn reveal_nullifiers( +// Writes the nullifiers of the provided masp transaction to storage +fn reveal_nullifiers( ctx: &mut impl StorageWrite, transaction: &Transaction, ) -> Result<()> { @@ -28,3 +31,43 @@ pub fn reveal_nullifiers( Ok(()) } + +/// Handle a MASP transaction. +pub fn handle_masp_tx( + ctx: &mut (impl StorageRead + StorageWrite), + transfer: &Transfer, + shielded: &Transaction, +) -> Result<()> { + let masp_addr = MASP; + let head_tx_key = Key::from(masp_addr.to_db_key()) + .push(&HEAD_TX_KEY.to_owned()) + .expect("Cannot obtain a storage key"); + let current_tx_idx: u64 = + ctx.read(&head_tx_key).unwrap_or(None).unwrap_or(0); + let current_tx_key = Key::from(masp_addr.to_db_key()) + .push(&(TX_KEY_PREFIX.to_owned() + ¤t_tx_idx.to_string())) + .expect("Cannot obtain a storage key"); + // Save the Transfer object and its location within the blockchain + // so that clients do not have to separately look these + // up + let record: (Epoch, BlockHeight, TxIndex, Transfer, Transaction) = ( + ctx.get_block_epoch()?, + ctx.get_block_height()?, + ctx.get_tx_index()?, + transfer.clone(), + shielded.clone(), + ); + ctx.write(¤t_tx_key, record)?; + ctx.write(&head_tx_key, current_tx_idx + 1)?; + reveal_nullifiers(ctx, shielded)?; + + // If storage key has been supplied, then pin this transaction to it + if let Some(key) = &transfer.key { + let pin_key = Key::from(masp_addr.to_db_key()) + .push(&(PIN_KEY_PREFIX.to_owned() + key)) + .expect("Cannot obtain a storage key"); + ctx.write(&pin_key, current_tx_idx)?; + } + + Ok(()) +} diff --git a/shared/src/ledger/native_vp/ibc/context.rs b/shared/src/ledger/native_vp/ibc/context.rs index 6bb6f90e1c..1c806afe35 100644 --- a/shared/src/ledger/native_vp/ibc/context.rs +++ b/shared/src/ledger/native_vp/ibc/context.rs @@ -3,7 +3,6 @@ use std::collections::{BTreeSet, HashMap, HashSet}; use borsh_ext::BorshSerializeExt; -use masp_primitives::transaction::Transaction; use namada_core::ledger::ibc::{IbcCommonContext, IbcStorageContext}; use namada_core::ledger::masp_utils; @@ -12,15 +11,12 @@ use crate::ledger::native_vp::CtxPreStorageRead; use crate::ledger::storage::write_log::StorageModification; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; -use crate::types::address::{Address, InternalAddress, MASP}; +use crate::types::address::{Address, InternalAddress}; use crate::types::ibc::{IbcEvent, IbcShieldedTransfer}; use crate::types::storage::{ - BlockHash, BlockHeight, Epoch, Header, Key, KeySeg, TxIndex, -}; -use crate::types::token::{ - self, Amount, DenominatedAmount, Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, - TX_KEY_PREFIX, + BlockHash, BlockHeight, Epoch, Header, Key, TxIndex, }; +use crate::types::token::{self, Amount, DenominatedAmount}; use crate::vm::WasmCacheAccess; /// Result of a storage API call. @@ -217,36 +213,7 @@ where } fn handle_masp_tx(&mut self, shielded: &IbcShieldedTransfer) -> Result<()> { - let masp_addr = MASP; - let head_tx_key = Key::from(masp_addr.to_db_key()) - .push(&HEAD_TX_KEY.to_owned()) - .expect("Cannot obtain a storage key"); - let current_tx_idx: u64 = - self.ctx.read(&head_tx_key).unwrap_or(None).unwrap_or(0); - let current_tx_key = Key::from(masp_addr.to_db_key()) - .push(&(TX_KEY_PREFIX.to_owned() + ¤t_tx_idx.to_string())) - .expect("Cannot obtain a storage key"); - // Save the Transfer object and its location within the blockchain - // so that clients do not have to separately look these - // up - let record: (Epoch, BlockHeight, TxIndex, Transfer, Transaction) = ( - self.ctx.get_block_epoch()?, - self.ctx.get_block_height()?, - self.ctx.get_tx_index()?, - shielded.transfer.clone(), - shielded.masp_tx.clone(), - ); - self.write(¤t_tx_key, record.serialize_to_vec())?; - self.write(&head_tx_key, (current_tx_idx + 1).serialize_to_vec())?; - masp_utils::reveal_nullifiers(self, &shielded.masp_tx)?; - // If storage key has been supplied, then pin this transaction to it - if let Some(key) = &shielded.transfer.key { - let pin_key = Key::from(masp_addr.to_db_key()) - .push(&(PIN_KEY_PREFIX.to_owned() + key)) - .expect("Cannot obtain a storage key"); - self.write(&pin_key, current_tx_idx.serialize_to_vec())?; - } - Ok(()) + masp_utils::handle_masp_tx(self, &shielded.transfer, &shielded.masp_tx) } fn mint_token( diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index 1bef64fdb2..0e3fc21929 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -6,12 +6,11 @@ use std::num::TryFromIntError; use borsh::BorshDeserialize; use borsh_ext::BorshSerializeExt; -use masp_primitives::transaction::Transaction; use namada_core::ledger::gas::{ GasMetering, TxGasMeter, MEMORY_ACCESS_GAS_PER_BYTE, }; use namada_core::ledger::masp_utils; -use namada_core::types::address::{ESTABLISHED_ADDRESS_BYTES_LEN, MASP}; +use namada_core::types::address::ESTABLISHED_ADDRESS_BYTES_LEN; use namada_core::types::internal::KeyVal; use namada_core::types::storage::TX_INDEX_LENGTH; use namada_core::types::transaction::TxSentinel; @@ -33,10 +32,9 @@ use crate::types::address::{self, Address}; use crate::types::hash::Hash; use crate::types::ibc::{IbcEvent, IbcShieldedTransfer}; use crate::types::internal::HostEnvResult; -use crate::types::storage::{BlockHeight, Epoch, Key, KeySeg, TxIndex}; +use crate::types::storage::{BlockHeight, Epoch, Key, TxIndex}; use crate::types::token::{ is_any_minted_balance_key, is_any_minter_key, is_any_token_balance_key, - Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, TX_KEY_PREFIX, }; use crate::vm::memory::VmMemory; use crate::vm::prefix_iter::{PrefixIteratorId, PrefixIterators}; @@ -2521,36 +2519,7 @@ where &mut self, shielded: &IbcShieldedTransfer, ) -> Result<(), storage_api::Error> { - let masp_addr = MASP; - let head_tx_key = Key::from(masp_addr.to_db_key()) - .push(&HEAD_TX_KEY.to_owned()) - .expect("Cannot obtain a storage key"); - let current_tx_idx = - self.read::(&head_tx_key).unwrap_or(None).unwrap_or(0); - let current_tx_key = Key::from(masp_addr.to_db_key()) - .push(&(TX_KEY_PREFIX.to_owned() + ¤t_tx_idx.to_string())) - .expect("Cannot obtain a storage key"); - // Save the Transfer object and its location within the blockchain - // so that clients do not have to separately look these - // up - let record: (Epoch, BlockHeight, TxIndex, Transfer, Transaction) = ( - self.get_block_epoch()?, - self.get_block_height()?, - self.get_tx_index()?, - shielded.transfer.clone(), - shielded.masp_tx.clone(), - ); - self.write(¤t_tx_key, record)?; - self.write(&head_tx_key, current_tx_idx + 1)?; - masp_utils::reveal_nullifiers(self, &shielded.masp_tx)?; - // If storage key has been supplied, then pin this transaction to it - if let Some(key) = &shielded.transfer.key { - let pin_key = Key::from(masp_addr.to_db_key()) - .push(&(PIN_KEY_PREFIX.to_owned() + key)) - .expect("Cannot obtain a storage key"); - self.write(&pin_key, current_tx_idx)?; - } - Ok(()) + masp_utils::handle_masp_tx(self, &shielded.transfer, &shielded.masp_tx) } fn mint_token( diff --git a/tx_prelude/src/ibc.rs b/tx_prelude/src/ibc.rs index dfd0430e5b..3e9382b59a 100644 --- a/tx_prelude/src/ibc.rs +++ b/tx_prelude/src/ibc.rs @@ -6,12 +6,13 @@ use std::rc::Rc; pub use namada_core::ledger::ibc::{ IbcActions, IbcCommonContext, IbcStorageContext, ProofSpec, TransferModule, }; +use namada_core::ledger::masp_utils; use namada_core::ledger::tx_env::TxEnv; use namada_core::types::address::{Address, InternalAddress}; pub use namada_core::types::ibc::{IbcEvent, IbcShieldedTransfer}; use namada_core::types::token::DenominatedAmount; -use crate::token::{burn, handle_masp_tx, mint, transfer}; +use crate::token::{burn, mint, transfer}; use crate::{Ctx, Error}; /// IBC actions to handle an IBC message @@ -52,7 +53,7 @@ impl IbcStorageContext for Ctx { &mut self, shielded: &IbcShieldedTransfer, ) -> Result<(), Error> { - handle_masp_tx(self, &shielded.transfer, &shielded.masp_tx) + masp_utils::handle_masp_tx(self, &shielded.transfer, &shielded.masp_tx) } fn mint_token( diff --git a/tx_prelude/src/token.rs b/tx_prelude/src/token.rs index ac4a4ba1da..70a874bdd8 100644 --- a/tx_prelude/src/token.rs +++ b/tx_prelude/src/token.rs @@ -1,7 +1,5 @@ -use masp_primitives::transaction::Transaction; -use namada_core::ledger::masp_utils; -use namada_core::types::address::{Address, MASP}; -use namada_core::types::storage::KeySeg; +pub use namada_core::ledger::masp_utils; +use namada_core::types::address::Address; use namada_core::types::token; pub use namada_core::types::token::*; @@ -33,45 +31,6 @@ pub fn transfer( Ok(()) } -/// Handle a MASP transaction. -pub fn handle_masp_tx( - ctx: &mut Ctx, - transfer: &Transfer, - shielded: &Transaction, -) -> TxResult { - let masp_addr = MASP; - ctx.insert_verifier(&masp_addr)?; - let head_tx_key = storage::Key::from(masp_addr.to_db_key()) - .push(&HEAD_TX_KEY.to_owned()) - .expect("Cannot obtain a storage key"); - let current_tx_idx: u64 = - ctx.read(&head_tx_key).unwrap_or(None).unwrap_or(0); - let current_tx_key = storage::Key::from(masp_addr.to_db_key()) - .push(&(TX_KEY_PREFIX.to_owned() + ¤t_tx_idx.to_string())) - .expect("Cannot obtain a storage key"); - // Save the Transfer object and its location within the blockchain - // so that clients do not have to separately look these - // up - let record: (Epoch, BlockHeight, TxIndex, Transfer, Transaction) = ( - ctx.get_block_epoch()?, - ctx.get_block_height()?, - ctx.get_tx_index()?, - transfer.clone(), - shielded.clone(), - ); - ctx.write(¤t_tx_key, record)?; - ctx.write(&head_tx_key, current_tx_idx + 1)?; - masp_utils::reveal_nullifiers(ctx, shielded)?; - // If storage key has been supplied, then pin this transaction to it - if let Some(key) = &transfer.key { - let pin_key = storage::Key::from(masp_addr.to_db_key()) - .push(&(PIN_KEY_PREFIX.to_owned() + key)) - .expect("Cannot obtain a storage key"); - ctx.write(&pin_key, current_tx_idx)?; - } - Ok(()) -} - /// Mint that can be used in a transaction. pub fn mint( ctx: &mut Ctx, diff --git a/wasm/wasm_source/src/tx_transfer.rs b/wasm/wasm_source/src/tx_transfer.rs index e92750e016..e473ef1d9e 100644 --- a/wasm/wasm_source/src/tx_transfer.rs +++ b/wasm/wasm_source/src/tx_transfer.rs @@ -38,7 +38,7 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { }) .transpose()?; if let Some(shielded) = shielded { - token::handle_masp_tx(ctx, &transfer, &shielded)?; + token::masp_utils::handle_masp_tx(ctx, &transfer, &shielded)?; } Ok(()) } From 4fb5142c7326717c7662c7c4e99e5d994081ba4b Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 1 Dec 2023 19:07:29 +0100 Subject: [PATCH 013/216] Updates masp tx with note commitment tree and anchor --- Cargo.lock | 1 + apps/src/lib/node/ledger/shell/init_chain.rs | 28 +++++++++++ core/Cargo.toml | 1 + core/src/ledger/masp_utils.rs | 52 +++++++++++++++++++- core/src/types/token.rs | 17 ++++++- wasm/Cargo.lock | 1 + wasm_for_tests/wasm_source/Cargo.lock | 1 + 7 files changed, 99 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a760f39701..c87d9e7c49 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3930,6 +3930,7 @@ dependencies = [ "itertools 0.10.5", "k256", "masp_primitives", + "masp_proofs", "namada_macros", "num-integer", "num-rational 0.4.1", diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index aad1fcc362..12a18469b2 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -2,6 +2,9 @@ use std::collections::HashMap; use std::hash::Hash; +use masp_primitives::merkle_tree::CommitmentTree; +use masp_primitives::sapling::Node; +use masp_proofs::bls12_381; use namada::ledger::parameters::Parameters; use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, DB}; @@ -9,10 +12,14 @@ use namada::ledger::storage_api::token::{credit_tokens, write_denom}; use namada::ledger::storage_api::StorageWrite; use namada::ledger::{ibc, pos}; use namada::proof_of_stake::BecomeValidator; +use namada::types::address::MASP; use namada::types::hash::Hash as CodeHash; use namada::types::key::*; use namada::types::storage::KeySeg; use namada::types::time::{DateTimeUtc, TimeZone, Utc}; +use namada::types::token::{ + MASP_NOTE_COMMITMENT_ANCHOR_PREFIX, MASP_NOTE_COMMITMENT_TREE, +}; use namada::vm::validate_untrusted_wasm; use namada_sdk::eth_bridge::EthBridgeStatus; use namada_sdk::proof_of_stake::types::ValidatorMetaData; @@ -172,6 +179,27 @@ where ibc::init_genesis_storage(&mut self.wl_storage); + // Init masp commitment tree and anchor + let empty_commitment_tree: CommitmentTree = + CommitmentTree::empty(); + let anchor = empty_commitment_tree.root(); + let note_commitment_tree_key = Key::from(MASP.to_db_key()) + .push(&MASP_NOTE_COMMITMENT_TREE.to_owned()) + .expect("Cannot obtain a storage key"); + self.wl_storage + .write(¬e_commitment_tree_key, empty_commitment_tree) + .unwrap(); + let commitment_tree_anchor_key = Key::from(MASP.to_db_key()) + .push(&MASP_NOTE_COMMITMENT_ANCHOR_PREFIX.to_owned()) + .expect("Cannot obtain a storage key") + .push(&namada::core::types::hash::Hash( + bls12_381::Scalar::from(anchor).to_bytes(), + )) + .expect("Cannot obtain a storage key"); + self.wl_storage + .write(&commitment_tree_anchor_key, ()) + .unwrap(); + // Set the initial validator set response.validators = self .get_abci_validator_updates(true, |pk, power| { diff --git a/core/Cargo.toml b/core/Cargo.toml index 53ee824077..e2e2351087 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -51,6 +51,7 @@ index-set.workspace = true itertools.workspace = true k256.workspace = true masp_primitives.workspace = true +masp_proofs.workspace = true num256.workspace = true num_enum = "0.7.0" num-integer = "0.1.45" diff --git a/core/src/ledger/masp_utils.rs b/core/src/ledger/masp_utils.rs index 4ddc2bc49a..9eaeb2faa7 100644 --- a/core/src/ledger/masp_utils.rs +++ b/core/src/ledger/masp_utils.rs @@ -1,9 +1,12 @@ //! MASP utilities +use masp_primitives::merkle_tree::CommitmentTree; +use masp_primitives::sapling::Node; use masp_primitives::transaction::Transaction; +use masp_proofs::bls12_381; use super::storage_api::{StorageRead, StorageWrite}; -use crate::ledger::storage_api::Result; +use crate::ledger::storage_api::{Error, Result}; use crate::types::address::MASP; use crate::types::hash::Hash; use crate::types::storage::{BlockHeight, Epoch, Key, KeySeg, TxIndex}; @@ -11,6 +14,10 @@ use crate::types::token::{ Transfer, HEAD_TX_KEY, MASP_NULLIFIERS_KEY_PREFIX, PIN_KEY_PREFIX, TX_KEY_PREFIX, }; +use crate::types::token::{ + MASP_NOTE_COMMITMENT_ANCHOR_PREFIX, MASP_NOTE_COMMITMENT_TREE, + MASP_NULLIFIERS_KEY_PREFIX, +}; // Writes the nullifiers of the provided masp transaction to storage fn reveal_nullifiers( @@ -32,6 +39,48 @@ fn reveal_nullifiers( Ok(()) } +// Appends the note commitments of the provided transaction to the merkle tree +// and updates the anchor +fn update_note_commitment_tree( + ctx: &mut (impl StorageRead + StorageWrite), + transaction: &Transaction, +) -> Result<()> { + if let Some(bundle) = transaction.sapling_bundle() { + if !bundle.shielded_outputs.is_empty() { + let tree_key = Key::from(MASP.to_db_key()) + .push(&MASP_NOTE_COMMITMENT_TREE.to_owned()) + .expect("Cannot obtain a storage key"); + let mut spend_tree = ctx + .read::>(&tree_key)? + .ok_or(Error::SimpleMessage( + "Missing note commitment tree in storage", + ))?; + + for description in &bundle.shielded_outputs { + // Add cmu to the merkle tree + spend_tree + .append(Node::from_scalar(description.cmu)) + .map_err(|_| { + Error::SimpleMessage("Note commitment tree is full") + })?; + } + + // Write the tree back to storage and update the anchor + let updated_anchor = spend_tree.root(); + ctx.write(&tree_key, spend_tree)?; + let anchor_key = Key::from(MASP.to_db_key()) + .push(&MASP_NOTE_COMMITMENT_ANCHOR_PREFIX.to_owned()) + .expect("Cannot obtain a storage key") + .push(&Hash(bls12_381::Scalar::from(updated_anchor).to_bytes())) + .expect("Cannot obtain a storage key"); + + ctx.write(&anchor_key, ())?; + } + } + + Ok(()) +} + /// Handle a MASP transaction. pub fn handle_masp_tx( ctx: &mut (impl StorageRead + StorageWrite), @@ -59,6 +108,7 @@ pub fn handle_masp_tx( ); ctx.write(¤t_tx_key, record)?; ctx.write(&head_tx_key, current_tx_idx + 1)?; + update_note_commitment_tree(ctx, shielded)?; reveal_nullifiers(ctx, shielded)?; // If storage key has been supplied, then pin this transaction to it diff --git a/core/src/types/token.rs b/core/src/types/token.rs index 3cb820f4a3..1df1ea37da 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -902,6 +902,10 @@ pub const TX_KEY_PREFIX: &str = "tx-"; pub const PIN_KEY_PREFIX: &str = "pin-"; /// Key segment prefix for the nullifiers pub const MASP_NULLIFIERS_KEY_PREFIX: &str = "nullifiers"; +/// Key segment prefix for the note commitment merkle tree +pub const MASP_NOTE_COMMITMENT_TREE: &str = "spend_tree"; +/// Key segment prefix for the note commitment anchor +pub const MASP_NOTE_COMMITMENT_ANCHOR_PREFIX: &str = "spend_anchor"; /// Last calculated inflation value handed out pub const MASP_LAST_INFLATION_KEY: &str = "last_inflation"; /// The last locked ratio @@ -1127,7 +1131,9 @@ pub fn is_masp_key(key: &Key) -> bool { && (key == HEAD_TX_KEY || key.starts_with(TX_KEY_PREFIX) || key.starts_with(PIN_KEY_PREFIX) - || key.starts_with(MASP_NULLIFIERS_KEY_PREFIX))) + || key.starts_with(MASP_NULLIFIERS_KEY_PREFIX) + || key == MASP_NOTE_COMMITMENT_TREE + || key.starts_with(MASP_NOTE_COMMITMENT_ANCHOR_PREFIX))) } /// Check if the given storage key is a masp nullifier key @@ -1139,6 +1145,15 @@ pub fn is_masp_nullifier_key(key: &Key) -> bool { ] if *addr == MASP && prefix == MASP_NULLIFIERS_KEY_PREFIX) } +/// Check if the given storage key is a masp anchor key +pub fn is_masp_anchor_key(key: &Key) -> bool { + matches!(&key.segments[..], + [DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + .. + ] if *addr == MASP && prefix == MASP_NOTE_COMMITMENT_ANCHOR_PREFIX) +} + /// Obtain the storage key for the last locked ratio of a token pub fn masp_last_locked_ratio_key(token_address: &Address) -> Key { key_of_token( diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 66a3bb5817..7c6464415f 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -3020,6 +3020,7 @@ dependencies = [ "itertools 0.10.5", "k256", "masp_primitives", + "masp_proofs", "namada_macros", "num-integer", "num-rational 0.4.1", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 133935e7f0..5f66a044d6 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -3020,6 +3020,7 @@ dependencies = [ "itertools 0.10.5", "k256", "masp_primitives", + "masp_proofs", "namada_macros", "num-integer", "num-rational 0.4.1", From cf1ad179c7374a1d91c1a0d96d08bde15c3dbf6e Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Sat, 2 Dec 2023 22:27:08 +0100 Subject: [PATCH 014/216] Updates masp vp to validate note commitment tree and anchor --- shared/src/ledger/native_vp/masp.rs | 139 +++++++++++++++++++++++++++- 1 file changed, 137 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/native_vp/masp.rs b/shared/src/ledger/native_vp/masp.rs index d2b9a9b56b..607843adf8 100644 --- a/shared/src/ledger/native_vp/masp.rs +++ b/shared/src/ledger/native_vp/masp.rs @@ -5,7 +5,11 @@ use std::collections::{BTreeSet, HashSet}; use borsh_ext::BorshSerializeExt; use masp_primitives::asset_type::AssetType; +use masp_primitives::merkle_tree::CommitmentTree; +use masp_primitives::sapling::Node; use masp_primitives::transaction::components::I128Sum; +use masp_primitives::transaction::Transaction; +use masp_proofs::bls12_381; use namada_core::ledger::gas::MASP_VERIFY_SHIELDED_TX_GAS; use namada_core::ledger::storage; use namada_core::ledger::storage_api::OptionExt; @@ -15,7 +19,9 @@ use namada_core::types::address::InternalAddress::Masp; use namada_core::types::address::{Address, MASP}; use namada_core::types::storage::{Epoch, Key, KeySeg}; use namada_core::types::token::{ - self, is_masp_nullifier_key, MASP_NULLIFIERS_KEY_PREFIX, + self, is_masp_anchor_key, is_masp_nullifier_key, + MASP_NOTE_COMMITMENT_ANCHOR_PREFIX, MASP_NOTE_COMMITMENT_TREE, + MASP_NULLIFIERS_KEY_PREFIX, }; use namada_sdk::masp::verify_shielded_tx; use ripemd::Digest as RipemdDigest; @@ -110,6 +116,121 @@ fn convert_amount( (asset_type, amount) } +impl<'a, DB, H, CA> MaspVp<'a, DB, H, CA> +where + DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, + H: 'static + storage::StorageHasher, + CA: 'static + WasmCacheAccess, +{ + // Check that a transaction carrying output descriptions correctly updates + // the tree and anchor in storage + fn validate_note_commitment_update( + &self, + keys_changed: &BTreeSet, + transaction: &Transaction, + ) -> Result { + // Check that the merkle tree in storage has been correctly updated with + // the output descriptions cmu + let tree_key = Key::from(MASP.to_db_key()) + .push(&MASP_NOTE_COMMITMENT_TREE.to_owned()) + .expect("Cannot obtain a storage key"); + let mut previous_tree: CommitmentTree = + self.ctx.read_pre(&tree_key)?.ok_or(Error::NativeVpError( + native_vp::Error::SimpleMessage("Cannot read storage"), + ))?; + let post_tree: CommitmentTree = + self.ctx.read_post(&tree_key)?.ok_or(Error::NativeVpError( + native_vp::Error::SimpleMessage("Cannot read storage"), + ))?; + + // Based on the output descriptions of the transaction, update the + // previous tree in storage + for description in transaction + .sapling_bundle() + .map_or(&vec![], |bundle| &bundle.shielded_outputs) + { + previous_tree + .append(Node::from_scalar(description.cmu)) + .map_err(|()| { + Error::NativeVpError(native_vp::Error::SimpleMessage( + "Failed to update the commitment tree", + )) + })?; + } + // Check that the updated previous tree matches the actual post tree. + // This verifies that all and only the necessary notes have been + // appended to the tree + if previous_tree != post_tree { + tracing::debug!("The note commitment tree was incorrectly updated"); + return Ok(false); + } + + // Check that only one anchor was published + if keys_changed + .iter() + .filter(|key| is_masp_anchor_key(key)) + .count() + != 1 + { + tracing::debug!( + "More than one MASP anchor keys havebeen revealed by the \ + transaction" + ); + return Ok(false); + } + + // Check that the new anchor has been written to storage + let updated_anchor_key = Key::from(MASP.to_db_key()) + .push(&MASP_NOTE_COMMITMENT_ANCHOR_PREFIX.to_owned()) + .expect("Cannot obtain a storage key") + .push(&namada_core::types::hash::Hash( + bls12_381::Scalar::from(previous_tree.root()).to_bytes(), + )) + .expect("Cannot obtain a storage key"); + + match self.ctx.read_bytes_post(&updated_anchor_key)? { + Some(value) if value.is_empty() => (), + _ => { + tracing::debug!( + "The commitment tree anchor was not updated correctly" + ); + return Ok(false); + } + } + + Ok(true) + } + + // Check that the spend descriptions anchors of a transaction are valid + fn validate_spend_descriptions_anchor( + &self, + transaction: &Transaction, + ) -> Result { + for description in transaction + .sapling_bundle() + .map_or(&vec![], |bundle| &bundle.shielded_spends) + { + let anchor_key = Key::from(MASP.to_db_key()) + .push(&MASP_NOTE_COMMITMENT_ANCHOR_PREFIX.to_owned()) + .expect("Cannot obtain a storage key") + .push(&namada_core::types::hash::Hash( + description.anchor.to_bytes(), + )) + .expect("Cannot obtain a storage key"); + + // Check if the provided anchor was published before + if !self.ctx.has_key_pre(&anchor_key)? { + tracing::debug!( + "Spend description refers to an invalid anchor" + ); + return Ok(false); + } + } + + Ok(true) + } +} + impl<'a, DB, H, CA> NativeVp for MaspVp<'a, DB, H, CA> where DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, @@ -153,7 +274,8 @@ where // 2. the transparent transaction value pool's amount must equal // the containing wrapper transaction's fee // amount Satisfies 1. - // 3. The nullifiers provided by the transaction have not been + // 3. The spend descriptions' anchors are valid + // 4. The nullifiers provided by the transaction have not been // revealed previously (even in the same tx) and no unneeded // nullifier is being revealed by the tx if let Some(transp_bundle) = shielded_tx.transparent_bundle() { @@ -167,6 +289,10 @@ where } } + if !self.validate_spend_descriptions_anchor(&shielded_tx)? { + return Ok(false); + } + let mut revealed_nullifiers = HashSet::new(); for description in shielded_tx .sapling_bundle() @@ -304,6 +430,8 @@ where // Handle shielded output // The following boundary conditions must be satisfied // 1. Zero transparent output + // 2. The transaction must correctly update the note commitment tree + // and the anchor in storage with the new output descriptions // Satisfies 1. if let Some(transp_bundle) = shielded_tx.transparent_bundle() { @@ -316,6 +444,13 @@ where return Ok(false); } } + + // Satisfies 2 + if !self + .validate_note_commitment_update(keys_changed, &shielded_tx)? + { + return Ok(false); + } } match transparent_tx_pool.partial_cmp(&I128Sum::zero()) { From 61a46de2e3461fbee48e176734a43e59c628fc2c Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Sat, 2 Dec 2023 22:46:56 +0100 Subject: [PATCH 015/216] Refactors masp nullifiers check in a separate function --- shared/src/ledger/native_vp/masp.rs | 113 +++++++++++++++------------- 1 file changed, 60 insertions(+), 53 deletions(-) diff --git a/shared/src/ledger/native_vp/masp.rs b/shared/src/ledger/native_vp/masp.rs index 607843adf8..3e705f9606 100644 --- a/shared/src/ledger/native_vp/masp.rs +++ b/shared/src/ledger/native_vp/masp.rs @@ -122,9 +122,63 @@ where H: 'static + storage::StorageHasher, CA: 'static + WasmCacheAccess, { + // Check that the transaction correctly revealed the nullifiers + fn valid_nullifiers_reveal( + &self, + keys_changed: &BTreeSet, + transaction: &Transaction, + ) -> Result { + let mut revealed_nullifiers = HashSet::new(); + for description in transaction + .sapling_bundle() + .map_or(&vec![], |description| &description.shielded_spends) + { + let nullifier_key = Key::from(MASP.to_db_key()) + .push(&MASP_NULLIFIERS_KEY_PREFIX.to_owned()) + .expect("Cannot obtain a storage key") + .push(&namada_core::types::hash::Hash(description.nullifier.0)) + .expect("Cannot obtain a storage key"); + if self.ctx.has_key_pre(&nullifier_key)? + || revealed_nullifiers.contains(&nullifier_key) + { + tracing::debug!( + "MASP double spending attempt, the nullifier {:#?} has \ + already been revealed previously", + description.nullifier.0 + ); + return Ok(false); + } + + // Check that the nullifier is indeed committed (no temp write + // and no delete) and carries no associated data (the latter not + // strictly necessary for validation, but we don't expect any + // value for this key anyway) + match self.ctx.read_bytes_post(&nullifier_key)? { + Some(value) if value.is_empty() => (), + _ => return Ok(false), + } + + revealed_nullifiers.insert(nullifier_key); + } + + for nullifier_key in + keys_changed.iter().filter(|key| is_masp_nullifier_key(key)) + { + if !revealed_nullifiers.contains(nullifier_key) { + tracing::debug!( + "An unexpected MASP nullifier key {nullifier_key} has \ + been revealed by the transaction" + ); + return Ok(false); + } + } + + Ok(true) + } + // Check that a transaction carrying output descriptions correctly updates // the tree and anchor in storage - fn validate_note_commitment_update( + fn valid_note_commitment_update( &self, keys_changed: &BTreeSet, transaction: &Transaction, @@ -202,7 +256,7 @@ where } // Check that the spend descriptions anchors of a transaction are valid - fn validate_spend_descriptions_anchor( + fn valid_spend_descriptions_anchor( &self, transaction: &Transaction, ) -> Result { @@ -289,55 +343,10 @@ where } } - if !self.validate_spend_descriptions_anchor(&shielded_tx)? { - return Ok(false); - } - - let mut revealed_nullifiers = HashSet::new(); - for description in shielded_tx - .sapling_bundle() - .map_or(&vec![], |description| &description.shielded_spends) - { - let nullifier_key = Key::from(MASP.to_db_key()) - .push(&MASP_NULLIFIERS_KEY_PREFIX.to_owned()) - .expect("Cannot obtain a storage key") - .push(&namada_core::types::hash::Hash( - description.nullifier.0, - )) - .expect("Cannot obtain a storage key"); - if self.ctx.has_key_pre(&nullifier_key)? - || revealed_nullifiers.contains(&nullifier_key) - { - tracing::debug!( - "MASP double spending attempt, the nullifier {:#?} \ - has already been revealed previously", - description.nullifier.0 - ); - return Ok(false); - } - - // Check that the nullifier is indeed committed (no temp write - // and no delete) and carries no associated data (the latter not - // strictly necessary for validation, but we don't expect any - // value for this key anyway) - match self.ctx.read_bytes_post(&nullifier_key)? { - Some(value) if value.is_empty() => (), - _ => return Ok(false), - } - - revealed_nullifiers.insert(nullifier_key); - } - - for nullifier_key in - keys_changed.iter().filter(|key| is_masp_nullifier_key(key)) + if !(self.valid_spend_descriptions_anchor(&shielded_tx)? + && self.valid_nullifiers_reveal(keys_changed, &shielded_tx)?) { - if !revealed_nullifiers.contains(nullifier_key) { - tracing::debug!( - "An unexpected MASP nullifier key {nullifier_key} has \ - been revealed by the transaction" - ); - return Ok(false); - } + return Ok(false); } } @@ -446,9 +455,7 @@ where } // Satisfies 2 - if !self - .validate_note_commitment_update(keys_changed, &shielded_tx)? - { + if !self.valid_note_commitment_update(keys_changed, &shielded_tx)? { return Ok(false); } } From 2183aaa999e8fe8e8409a4001f644b18d6c5ad9a Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Sat, 2 Dec 2023 23:38:37 +0100 Subject: [PATCH 016/216] Updates commitment tree anchor only once per block --- Cargo.lock | 1 - .../lib/node/ledger/shell/finalize_block.rs | 25 +++++++++++++++++ core/Cargo.toml | 1 - core/src/ledger/masp_utils.rs | 23 ++++----------- shared/src/ledger/native_vp/masp.rs | 28 +++---------------- wasm/Cargo.lock | 1 - wasm_for_tests/wasm_source/Cargo.lock | 1 - 7 files changed, 35 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c87d9e7c49..a760f39701 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3930,7 +3930,6 @@ dependencies = [ "itertools 0.10.5", "k256", "masp_primitives", - "masp_proofs", "namada_macros", "num-integer", "num-rational 0.4.1", diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 257b766a03..6c3c67d3ac 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -1,9 +1,13 @@ //! Implementation of the `FinalizeBlock` ABCI++ method for the Shell use data_encoding::HEXUPPER; +use masp_primitives::merkle_tree::CommitmentTree; +use masp_primitives::sapling::Node; +use masp_proofs::bls12_381; use namada::core::ledger::inflation; use namada::core::ledger::masp_conversions::update_allowed_conversions; use namada::core::ledger::pgf::ADDRESS as pgf_address; +use namada::core::types::storage::KeySeg; use namada::ledger::events::EventType; use namada::ledger::gas::{GasMetering, TxGasMeter}; use namada::ledger::parameters::storage as params_storage; @@ -17,9 +21,13 @@ use namada::proof_of_stake::{ find_validator_by_raw_hash, read_last_block_proposer_address, read_pos_params, read_total_stake, write_last_block_proposer_address, }; +use namada::types::address::MASP; use namada::types::dec::Dec; use namada::types::key::tm_raw_hash_to_string; use namada::types::storage::{BlockHash, BlockResults, Epoch, Header}; +use namada::types::token::{ + MASP_NOTE_COMMITMENT_ANCHOR_PREFIX, MASP_NOTE_COMMITMENT_TREE, +}; use namada::types::transaction::protocol::{ ethereum_tx_data_variants, ProtocolTxType, }; @@ -557,6 +565,23 @@ where tracing::info!("{}", stats); tracing::info!("{}", stats.format_tx_executed()); + // Update the MASP commitment tree anchor + let tree_key = Key::from(MASP.to_db_key()) + .push(&MASP_NOTE_COMMITMENT_TREE.to_owned()) + .expect("Cannot obtain a storage key"); + let updated_tree: CommitmentTree = self + .wl_storage + .read(&tree_key)? + .expect("Missing note commitment tree in storage"); + let anchor_key = Key::from(MASP.to_db_key()) + .push(&MASP_NOTE_COMMITMENT_ANCHOR_PREFIX.to_owned()) + .expect("Cannot obtain a storage key") + .push(&namada::core::types::hash::Hash( + bls12_381::Scalar::from(updated_tree.root()).to_bytes(), + )) + .expect("Cannot obtain a storage key"); + self.wl_storage.write(&anchor_key, ())?; + if update_for_tendermint { self.update_epoch(&mut response); // send the latest oracle configs. These may have changed due to diff --git a/core/Cargo.toml b/core/Cargo.toml index e2e2351087..53ee824077 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -51,7 +51,6 @@ index-set.workspace = true itertools.workspace = true k256.workspace = true masp_primitives.workspace = true -masp_proofs.workspace = true num256.workspace = true num_enum = "0.7.0" num-integer = "0.1.45" diff --git a/core/src/ledger/masp_utils.rs b/core/src/ledger/masp_utils.rs index 9eaeb2faa7..eb9918a707 100644 --- a/core/src/ledger/masp_utils.rs +++ b/core/src/ledger/masp_utils.rs @@ -3,7 +3,6 @@ use masp_primitives::merkle_tree::CommitmentTree; use masp_primitives::sapling::Node; use masp_primitives::transaction::Transaction; -use masp_proofs::bls12_381; use super::storage_api::{StorageRead, StorageWrite}; use crate::ledger::storage_api::{Error, Result}; @@ -15,8 +14,7 @@ use crate::types::token::{ TX_KEY_PREFIX, }; use crate::types::token::{ - MASP_NOTE_COMMITMENT_ANCHOR_PREFIX, MASP_NOTE_COMMITMENT_TREE, - MASP_NULLIFIERS_KEY_PREFIX, + MASP_NOTE_COMMITMENT_TREE, MASP_NULLIFIERS_KEY_PREFIX, }; // Writes the nullifiers of the provided masp transaction to storage @@ -50,31 +48,22 @@ fn update_note_commitment_tree( let tree_key = Key::from(MASP.to_db_key()) .push(&MASP_NOTE_COMMITMENT_TREE.to_owned()) .expect("Cannot obtain a storage key"); - let mut spend_tree = ctx + let mut commitment_tree = ctx .read::>(&tree_key)? .ok_or(Error::SimpleMessage( - "Missing note commitment tree in storage", - ))?; + "Missing note commitment tree in storage", + ))?; for description in &bundle.shielded_outputs { // Add cmu to the merkle tree - spend_tree + commitment_tree .append(Node::from_scalar(description.cmu)) .map_err(|_| { Error::SimpleMessage("Note commitment tree is full") })?; } - // Write the tree back to storage and update the anchor - let updated_anchor = spend_tree.root(); - ctx.write(&tree_key, spend_tree)?; - let anchor_key = Key::from(MASP.to_db_key()) - .push(&MASP_NOTE_COMMITMENT_ANCHOR_PREFIX.to_owned()) - .expect("Cannot obtain a storage key") - .push(&Hash(bls12_381::Scalar::from(updated_anchor).to_bytes())) - .expect("Cannot obtain a storage key"); - - ctx.write(&anchor_key, ())?; + ctx.write(&tree_key, commitment_tree)?; } } diff --git a/shared/src/ledger/native_vp/masp.rs b/shared/src/ledger/native_vp/masp.rs index 3e705f9606..e4b8f3f53b 100644 --- a/shared/src/ledger/native_vp/masp.rs +++ b/shared/src/ledger/native_vp/masp.rs @@ -9,7 +9,6 @@ use masp_primitives::merkle_tree::CommitmentTree; use masp_primitives::sapling::Node; use masp_primitives::transaction::components::I128Sum; use masp_primitives::transaction::Transaction; -use masp_proofs::bls12_381; use namada_core::ledger::gas::MASP_VERIFY_SHIELDED_TX_GAS; use namada_core::ledger::storage; use namada_core::ledger::storage_api::OptionExt; @@ -219,39 +218,20 @@ where return Ok(false); } - // Check that only one anchor was published + // Check that no anchor was published (this is to be done by the + // protocol) if keys_changed .iter() .filter(|key| is_masp_anchor_key(key)) .count() - != 1 + != 0 { tracing::debug!( - "More than one MASP anchor keys havebeen revealed by the \ - transaction" + "The transaction revealed one or more MASP anchor keys" ); return Ok(false); } - // Check that the new anchor has been written to storage - let updated_anchor_key = Key::from(MASP.to_db_key()) - .push(&MASP_NOTE_COMMITMENT_ANCHOR_PREFIX.to_owned()) - .expect("Cannot obtain a storage key") - .push(&namada_core::types::hash::Hash( - bls12_381::Scalar::from(previous_tree.root()).to_bytes(), - )) - .expect("Cannot obtain a storage key"); - - match self.ctx.read_bytes_post(&updated_anchor_key)? { - Some(value) if value.is_empty() => (), - _ => { - tracing::debug!( - "The commitment tree anchor was not updated correctly" - ); - return Ok(false); - } - } - Ok(true) } diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 7c6464415f..66a3bb5817 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -3020,7 +3020,6 @@ dependencies = [ "itertools 0.10.5", "k256", "masp_primitives", - "masp_proofs", "namada_macros", "num-integer", "num-rational 0.4.1", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 5f66a044d6..133935e7f0 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -3020,7 +3020,6 @@ dependencies = [ "itertools 0.10.5", "k256", "masp_primitives", - "masp_proofs", "namada_macros", "num-integer", "num-rational 0.4.1", From f7da77b2d4f62d2bc09f148d6199a6766e0ad8ab Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Sun, 3 Dec 2023 18:57:53 +0100 Subject: [PATCH 017/216] Updates the merkle tree anchor only if the tree changed --- .../lib/node/ledger/shell/finalize_block.rs | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 6c3c67d3ac..077feef2ad 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -14,9 +14,10 @@ use namada::ledger::parameters::storage as params_storage; use namada::ledger::pos::{namada_proof_of_stake, staking_token_address}; use namada::ledger::protocol; use namada::ledger::storage::wl_storage::WriteLogAndStorage; +use namada::ledger::storage::write_log::StorageModification; use namada::ledger::storage::EPOCH_SWITCH_BLOCKS_DELAY; use namada::ledger::storage_api::token::credit_tokens; -use namada::ledger::storage_api::{pgf, StorageRead, StorageWrite}; +use namada::ledger::storage_api::{pgf, ResultExt, StorageRead, StorageWrite}; use namada::proof_of_stake::{ find_validator_by_raw_hash, read_last_block_proposer_address, read_pos_params, read_total_stake, write_last_block_proposer_address, @@ -565,22 +566,24 @@ where tracing::info!("{}", stats); tracing::info!("{}", stats.format_tx_executed()); - // Update the MASP commitment tree anchor + // Update the MASP commitment tree anchor if the tree was updated let tree_key = Key::from(MASP.to_db_key()) .push(&MASP_NOTE_COMMITMENT_TREE.to_owned()) .expect("Cannot obtain a storage key"); - let updated_tree: CommitmentTree = self - .wl_storage - .read(&tree_key)? - .expect("Missing note commitment tree in storage"); - let anchor_key = Key::from(MASP.to_db_key()) - .push(&MASP_NOTE_COMMITMENT_ANCHOR_PREFIX.to_owned()) - .expect("Cannot obtain a storage key") - .push(&namada::core::types::hash::Hash( - bls12_381::Scalar::from(updated_tree.root()).to_bytes(), - )) - .expect("Cannot obtain a storage key"); - self.wl_storage.write(&anchor_key, ())?; + if let Some(StorageModification::Write { value }) = + self.wl_storage.write_log.read(&tree_key).0 + { + let updated_tree = CommitmentTree::::try_from_slice(value) + .into_storage_result()?; + let anchor_key = Key::from(MASP.to_db_key()) + .push(&MASP_NOTE_COMMITMENT_ANCHOR_PREFIX.to_owned()) + .expect("Cannot obtain a storage key") + .push(&namada::core::types::hash::Hash( + bls12_381::Scalar::from(updated_tree.root()).to_bytes(), + )) + .expect("Cannot obtain a storage key"); + self.wl_storage.write(&anchor_key, ())?; + } if update_for_tendermint { self.update_epoch(&mut response); From fd07f6b8dd5ba27feeb703816c012eb98a3d1115 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Thu, 30 Nov 2023 21:25:09 +0200 Subject: [PATCH 018/216] Now denominate the fee amount in wrapper headers. --- apps/src/lib/bench_utils.rs | 5 +- .../lib/node/ledger/shell/finalize_block.rs | 38 +++++--- apps/src/lib/node/ledger/shell/mod.rs | 90 +++++++++++++----- .../lib/node/ledger/shell/prepare_proposal.rs | 59 ++++++++---- .../lib/node/ledger/shell/process_proposal.rs | 85 ++++++++++++----- benches/process_wrapper.rs | 3 +- core/src/types/token.rs | 15 +++ core/src/types/transaction/mod.rs | 12 ++- core/src/types/transaction/wrapper.rs | 17 ++-- sdk/src/signing.rs | 37 ++++--- shared/src/ledger/protocol/mod.rs | 12 ++- shared/src/vm/host_env.rs | 2 +- wasm/checksums.json | 50 +++++----- wasm_for_tests/tx_fail.wasm | Bin 387383 -> 370069 bytes wasm_for_tests/tx_memory_limit.wasm | Bin 438268 -> 421361 bytes wasm_for_tests/tx_mint_tokens.wasm | Bin 594890 -> 594890 bytes wasm_for_tests/tx_no_op.wasm | Bin 387544 -> 370096 bytes wasm_for_tests/tx_proposal_code.wasm | Bin 440466 -> 418426 bytes wasm_for_tests/tx_read_storage_key.wasm | Bin 442958 -> 427447 bytes wasm_for_tests/tx_write.wasm | Bin 449600 -> 431956 bytes wasm_for_tests/tx_write_storage_key.wasm | Bin 465107 -> 465107 bytes wasm_for_tests/vp_always_false.wasm | Bin 409829 -> 391355 bytes wasm_for_tests/vp_always_true.wasm | Bin 409795 -> 391497 bytes wasm_for_tests/vp_eval.wasm | Bin 483964 -> 467705 bytes wasm_for_tests/vp_memory_limit.wasm | Bin 462210 -> 445498 bytes wasm_for_tests/vp_read_storage_key.wasm | Bin 469245 -> 451219 bytes 26 files changed, 276 insertions(+), 149 deletions(-) diff --git a/apps/src/lib/bench_utils.rs b/apps/src/lib/bench_utils.rs index 551bf49abd..5810b767f3 100644 --- a/apps/src/lib/bench_utils.rs +++ b/apps/src/lib/bench_utils.rs @@ -824,10 +824,7 @@ impl BenchShieldedCtx { source: TransferSource, target: TransferTarget, ) -> Tx { - let denominated_amount = DenominatedAmount { - amount, - denom: 0.into(), - }; + let denominated_amount = DenominatedAmount::native(amount); let async_runtime = tokio::runtime::Runtime::new().unwrap(); let spending_key = self .wallet diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 257b766a03..30a3838010 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -984,7 +984,9 @@ mod test_finalize_block { use namada::types::key::tm_consensus_key_raw_hash; use namada::types::storage::Epoch; use namada::types::time::{DateTimeUtc, DurationSecs}; - use namada::types::token::{Amount, NATIVE_MAX_DECIMAL_PLACES}; + use namada::types::token::{ + Amount, DenominatedAmount, NATIVE_MAX_DECIMAL_PLACES, + }; use namada::types::transaction::governance::{ InitProposalData, VoteProposalData, }; @@ -1022,7 +1024,7 @@ mod test_finalize_block { let mut wrapper_tx = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: 1.into(), + amount_per_gas_unit: DenominatedAmount::native(1.into()), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -1063,7 +1065,7 @@ mod test_finalize_block { let mut outer_tx = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: 1.into(), + amount_per_gas_unit: DenominatedAmount::native(1.into()), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -1178,7 +1180,9 @@ mod test_finalize_block { let mut outer_tx = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: Default::default(), + amount_per_gas_unit: DenominatedAmount::native( + Default::default(), + ), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -1232,7 +1236,7 @@ mod test_finalize_block { // not valid tx bytes let wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: 0.into(), + amount_per_gas_unit: DenominatedAmount::native(0.into()), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -2955,7 +2959,7 @@ mod test_finalize_block { let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: 1.into(), + amount_per_gas_unit: DenominatedAmount::native(1.into()), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -2972,7 +2976,7 @@ mod test_finalize_block { let mut new_wrapper = wrapper.clone(); new_wrapper.update_header(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: 1.into(), + amount_per_gas_unit: DenominatedAmount::native(1.into()), token: shell.wl_storage.storage.native_token.clone(), }, keypair_2.ref_to(), @@ -3084,7 +3088,9 @@ mod test_finalize_block { let mut unsigned_wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: Amount::zero(), + amount_per_gas_unit: DenominatedAmount::native( + Amount::zero(), + ), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -3260,7 +3266,7 @@ mod test_finalize_block { let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: 0.into(), + amount_per_gas_unit: DenominatedAmount::native(0.into()), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -3339,7 +3345,7 @@ mod test_finalize_block { let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: 100.into(), + amount_per_gas_unit: DenominatedAmount::native(100.into()), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -3422,7 +3428,7 @@ mod test_finalize_block { let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: 1.into(), + amount_per_gas_unit: DenominatedAmount::native(1.into()), token: shell.wl_storage.storage.native_token.clone(), }, crate::wallet::defaults::albert_keypair().ref_to(), @@ -3444,6 +3450,12 @@ mod test_finalize_block { ))); let fee_amount = wrapper.header().wrapper().unwrap().get_tx_fee().unwrap(); + let fee_amount = fee_amount + .apply_precision( + &wrapper.header().wrapper().unwrap().fee.token, + &shell.wl_storage, + ) + .unwrap(); let signer_balance = storage_api::token::read_balance( &shell.wl_storage, @@ -3481,7 +3493,7 @@ mod test_finalize_block { .unwrap(); assert_eq!( new_proposer_balance, - proposer_balance.checked_add(fee_amount).unwrap() + proposer_balance.checked_add(fee_amount.amount).unwrap() ); let new_signer_balance = storage_api::token::read_balance( @@ -3492,7 +3504,7 @@ mod test_finalize_block { .unwrap(); assert_eq!( new_signer_balance, - signer_balance.checked_sub(fee_amount).unwrap() + signer_balance.checked_sub(fee_amount.amount).unwrap() ) } diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 861fc4e727..03c7130d6c 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -1400,15 +1400,35 @@ where } }; - if wrapper.fee.amount_per_gas_unit < minimum_gas_price { - // The fees do not match the minimum required - return Err(Error::TxApply(protocol::Error::FeeError(format!( - "Fee amount {:?} do not match the minimum required amount \ - {:?} for token {}", - wrapper.fee.amount_per_gas_unit, - minimum_gas_price, - wrapper.fee.token - )))); + match wrapper + .fee + .amount_per_gas_unit + .apply_precision(&wrapper.fee.token, &self.wl_storage) + { + Ok(amount_per_gas_unit) + if amount_per_gas_unit.amount < minimum_gas_price => + { + // The fees do not match the minimum required + return Err(Error::TxApply(protocol::Error::FeeError( + format!( + "Fee amount {:?} do not match the minimum required \ + amount {:?} for token {}", + wrapper.fee.amount_per_gas_unit, + minimum_gas_price, + wrapper.fee.token + ), + ))); + } + Ok(_) => {} + Err(err) => { + return Err(Error::TxApply(protocol::Error::FeeError( + format!( + "The precision of the fee amount {:?} is higher than \ + the denomination for token {}: {}", + wrapper.fee.amount_per_gas_unit, wrapper.fee.token, err, + ), + ))); + } } if let Some(transaction) = masp_transaction { @@ -1580,6 +1600,7 @@ mod test_utils { use crate::facade::tendermint_proto::v0_37::abci::{ RequestPrepareProposal, RequestProcessProposal, }; + use crate::node::ledger::shell::token::DenominatedAmount; use crate::node::ledger::shims::abcipp_shim_types; use crate::node::ledger::shims::abcipp_shim_types::shim::request::{ FinalizeBlock, ProcessedTx, @@ -2086,7 +2107,7 @@ mod test_utils { let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: Default::default(), + amount_per_gas_unit: DenominatedAmount::native(0.into()), token: native_token, }, keypair.ref_to(), @@ -2262,6 +2283,7 @@ mod test_utils { #[cfg(test)] mod shell_tests { use namada::core::ledger::replay_protection; + use namada::ledger::storage_api::token::read_denom; use namada::proto::{ Code, Data, Section, SignableEthMessage, Signature, Signed, Tx, }; @@ -2276,6 +2298,7 @@ mod shell_tests { use super::*; use crate::node::ledger::shell::test_utils; + use crate::node::ledger::shell::token::DenominatedAmount; use crate::wallet; const GAS_LIMIT_MULTIPLIER: u64 = 100_000; @@ -2505,8 +2528,10 @@ mod shell_tests { let mut unsigned_wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: token::Amount::from_uint(100, 0) - .expect("This can't fail"), + amount_per_gas_unit: DenominatedAmount::native( + token::Amount::from_uint(100, 0) + .expect("This can't fail"), + ), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -2542,8 +2567,10 @@ mod shell_tests { let mut invalid_wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: token::Amount::from_uint(100, 0) - .expect("This can't fail"), + amount_per_gas_unit: DenominatedAmount::native( + token::Amount::from_uint(100, 0) + .expect("This can't fail"), + ), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -2565,7 +2592,8 @@ mod shell_tests { // we mount a malleability attack to try and remove the fee let mut new_wrapper = invalid_wrapper.header().wrapper().expect("Test failed"); - new_wrapper.fee.amount_per_gas_unit = Default::default(); + new_wrapper.fee.amount_per_gas_unit = + DenominatedAmount::native(0.into()); invalid_wrapper.update_header(TxType::Wrapper(Box::new(new_wrapper))); let mut result = shell.mempool_validate( @@ -2611,8 +2639,10 @@ mod shell_tests { let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: token::Amount::from_uint(100, 0) - .expect("This can't fail"), + amount_per_gas_unit: DenominatedAmount::native( + token::Amount::from_uint(100, 0) + .expect("This can't fail"), + ), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -2773,7 +2803,7 @@ mod shell_tests { let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: 100.into(), + amount_per_gas_unit: DenominatedAmount::native(100.into()), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -2806,7 +2836,7 @@ mod shell_tests { let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: 100.into(), + amount_per_gas_unit: DenominatedAmount::native(100.into()), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -2835,11 +2865,17 @@ mod shell_tests { #[test] fn test_fee_non_whitelisted_token() { let (shell, _recv, _, _) = test_utils::setup(); + let apfel_denom = read_denom(&shell.wl_storage, &address::apfel()) + .expect("unable to read denomination from storage") + .expect("unable to find denomination of apfels"); let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: 100.into(), + amount_per_gas_unit: DenominatedAmount { + amount: 100.into(), + denom: apfel_denom, + }, token: address::apfel(), }, crate::wallet::defaults::albert_keypair().ref_to(), @@ -2874,7 +2910,7 @@ mod shell_tests { let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: 0.into(), + amount_per_gas_unit: DenominatedAmount::native(0.into()), token: shell.wl_storage.storage.native_token.clone(), }, crate::wallet::defaults::albert_keypair().ref_to(), @@ -2908,7 +2944,9 @@ mod shell_tests { let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: 1_000_000_000.into(), + amount_per_gas_unit: DenominatedAmount::native( + 1_000_000_000.into(), + ), token: shell.wl_storage.storage.native_token.clone(), }, crate::wallet::defaults::albert_keypair().ref_to(), @@ -2942,7 +2980,9 @@ mod shell_tests { let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: token::Amount::max(), + amount_per_gas_unit: DenominatedAmount::native( + token::Amount::max(), + ), token: shell.wl_storage.storage.native_token.clone(), }, crate::wallet::defaults::albert_keypair().ref_to(), @@ -2987,7 +3027,9 @@ mod shell_tests { let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: 100.into(), + amount_per_gas_unit: DenominatedAmount::native( + 100.into(), + ), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index baf77012d8..1543e61e45 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -379,6 +379,7 @@ mod test_prepare_proposal { use namada::core::ledger::storage_api::collections::lazy_map::{ NestedSubKey, SubKey, }; + use namada::core::ledger::storage_api::token::read_denom; use namada::ledger::gas::Gas; use namada::ledger::pos::PosQueries; use namada::ledger::replay_protection; @@ -393,7 +394,7 @@ mod test_prepare_proposal { use namada::types::key::RefTo; use namada::types::storage::BlockHeight; use namada::types::token; - use namada::types::token::Amount; + use namada::types::token::{Amount, DenominatedAmount}; use namada::types::transaction::protocol::EthereumTxData; use namada::types::transaction::{Fee, TxType, WrapperTx}; use namada::types::vote_extensions::ethereum_events; @@ -449,7 +450,9 @@ mod test_prepare_proposal { let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: Default::default(), + amount_per_gas_unit: DenominatedAmount::native( + Default::default(), + ), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -747,7 +750,9 @@ mod test_prepare_proposal { let mut tx = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: 1.into(), + amount_per_gas_unit: DenominatedAmount::native( + 1.into(), + ), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -816,7 +821,7 @@ mod test_prepare_proposal { let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: 0.into(), + amount_per_gas_unit: DenominatedAmount::native(0.into()), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -863,7 +868,7 @@ mod test_prepare_proposal { let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: 1.into(), + amount_per_gas_unit: DenominatedAmount::native(1.into()), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -898,7 +903,9 @@ mod test_prepare_proposal { let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: Amount::zero(), + amount_per_gas_unit: DenominatedAmount::native( + Amount::zero(), + ), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -946,7 +953,7 @@ mod test_prepare_proposal { let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: 1.into(), + amount_per_gas_unit: DenominatedAmount::native(1.into()), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -968,7 +975,7 @@ mod test_prepare_proposal { new_wrapper.update_header(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: 1.into(), + amount_per_gas_unit: DenominatedAmount::native(1.into()), token: shell.wl_storage.storage.native_token.clone(), }, keypair_2.ref_to(), @@ -998,7 +1005,7 @@ mod test_prepare_proposal { let mut wrapper_tx = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: 1.into(), + amount_per_gas_unit: DenominatedAmount::native(1.into()), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -1047,7 +1054,7 @@ mod test_prepare_proposal { let wrapper = WrapperTx::new( Fee { - amount_per_gas_unit: 100.into(), + amount_per_gas_unit: DenominatedAmount::native(100.into()), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -1086,7 +1093,7 @@ mod test_prepare_proposal { let wrapper = WrapperTx::new( Fee { - amount_per_gas_unit: 100.into(), + amount_per_gas_unit: DenominatedAmount::native(100.into()), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -1133,9 +1140,16 @@ mod test_prepare_proposal { }); } + let btc_denom = read_denom(&shell.wl_storage, &address::btc()) + .expect("unable to read denomination from storage") + .expect("unable to find denomination of btcs"); + let wrapper = WrapperTx::new( Fee { - amount_per_gas_unit: 100.into(), + amount_per_gas_unit: DenominatedAmount { + amount: 100.into(), + denom: btc_denom, + }, token: address::btc(), }, crate::wallet::defaults::albert_keypair().ref_to(), @@ -1174,9 +1188,16 @@ mod test_prepare_proposal { fn test_fee_non_whitelisted_token() { let (shell, _recv, _, _) = test_utils::setup(); + let apfel_denom = read_denom(&shell.wl_storage, &address::apfel()) + .expect("unable to read denomination from storage") + .expect("unable to find denomination of apfels"); + let wrapper = WrapperTx::new( Fee { - amount_per_gas_unit: 100.into(), + amount_per_gas_unit: DenominatedAmount { + amount: 100.into(), + denom: apfel_denom, + }, token: address::apfel(), }, crate::wallet::defaults::albert_keypair().ref_to(), @@ -1227,7 +1248,7 @@ mod test_prepare_proposal { let wrapper = WrapperTx::new( Fee { - amount_per_gas_unit: 10.into(), + amount_per_gas_unit: DenominatedAmount::native(10.into()), token: shell.wl_storage.storage.native_token.clone(), }, crate::wallet::defaults::albert_keypair().ref_to(), @@ -1267,7 +1288,7 @@ mod test_prepare_proposal { let wrapper = WrapperTx::new( Fee { - amount_per_gas_unit: 0.into(), + amount_per_gas_unit: DenominatedAmount::native(0.into()), token: shell.wl_storage.storage.native_token.clone(), }, crate::wallet::defaults::albert_keypair().ref_to(), @@ -1306,7 +1327,9 @@ mod test_prepare_proposal { let wrapper = WrapperTx::new( Fee { - amount_per_gas_unit: 1_000_000_000.into(), + amount_per_gas_unit: DenominatedAmount::native( + 1_000_000_000.into(), + ), token: shell.wl_storage.storage.native_token.clone(), }, crate::wallet::defaults::albert_keypair().ref_to(), @@ -1345,7 +1368,9 @@ mod test_prepare_proposal { let wrapper = WrapperTx::new( Fee { - amount_per_gas_unit: token::Amount::max(), + amount_per_gas_unit: DenominatedAmount::native( + token::Amount::max(), + ), token: shell.wl_storage.storage.native_token.clone(), }, crate::wallet::defaults::albert_keypair().ref_to(), diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 9f19cf2d66..357bba2a7b 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -691,6 +691,7 @@ where #[cfg(test)] mod test_process_proposal { use namada::ledger::replay_protection; + use namada::ledger::storage_api::token::read_denom; use namada::ledger::storage_api::StorageWrite; use namada::proto::{ Code, Data, Section, SignableEthMessage, Signature, Signed, @@ -700,7 +701,7 @@ mod test_process_proposal { use namada::types::storage::Epoch; use namada::types::time::DateTimeUtc; use namada::types::token; - use namada::types::token::Amount; + use namada::types::token::{Amount, DenominatedAmount}; use namada::types::transaction::protocol::EthereumTxData; use namada::types::transaction::{Fee, WrapperTx}; use namada::types::vote_extensions::{bridge_pool_roots, ethereum_events}; @@ -935,7 +936,9 @@ mod test_process_proposal { let mut outer_tx = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: Default::default(), + amount_per_gas_unit: DenominatedAmount::native( + Default::default(), + ), token: shell.wl_storage.storage.native_token.clone(), }, public_key, @@ -985,8 +988,9 @@ mod test_process_proposal { let mut outer_tx = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: Amount::from_uint(100, 0) - .expect("Test failed"), + amount_per_gas_unit: DenominatedAmount::native( + Amount::from_uint(100, 0).expect("Test failed"), + ), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -1005,7 +1009,8 @@ mod test_process_proposal { let mut new_tx = outer_tx.clone(); if let TxType::Wrapper(wrapper) = &mut new_tx.header.tx_type { // we mount a malleability attack to try and remove the fee - wrapper.fee.amount_per_gas_unit = Default::default(); + wrapper.fee.amount_per_gas_unit = + DenominatedAmount::native(Default::default()); } else { panic!("Test failed") }; @@ -1057,8 +1062,9 @@ mod test_process_proposal { let mut outer_tx = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: Amount::from_uint(1, 0) - .expect("Test failed"), + amount_per_gas_unit: DenominatedAmount::native( + Amount::from_uint(1, 0).expect("Test failed"), + ), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -1124,7 +1130,9 @@ mod test_process_proposal { let mut outer_tx = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: Amount::native_whole(1_000_100), + amount_per_gas_unit: DenominatedAmount::native( + Amount::native_whole(1_000_100), + ), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -1180,7 +1188,9 @@ mod test_process_proposal { let mut outer_tx = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: Amount::native_whole(i as u64), + amount_per_gas_unit: DenominatedAmount::native( + Amount::native_whole(i as u64), + ), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -1239,7 +1249,9 @@ mod test_process_proposal { let mut tx = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: Default::default(), + amount_per_gas_unit: DenominatedAmount::native( + Default::default(), + ), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -1292,7 +1304,9 @@ mod test_process_proposal { // not valid tx bytes let wrapper = WrapperTx { fee: Fee { - amount_per_gas_unit: Default::default(), + amount_per_gas_unit: DenominatedAmount::native( + Default::default(), + ), token: shell.wl_storage.storage.native_token.clone(), }, pk: keypair.ref_to(), @@ -1396,7 +1410,9 @@ mod test_process_proposal { let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: Amount::zero(), + amount_per_gas_unit: DenominatedAmount::native( + Amount::zero(), + ), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -1471,7 +1487,7 @@ mod test_process_proposal { let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: 1.into(), + amount_per_gas_unit: DenominatedAmount::native(1.into()), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -1523,7 +1539,9 @@ mod test_process_proposal { let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: Amount::zero(), + amount_per_gas_unit: DenominatedAmount::native( + Amount::zero(), + ), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -1587,7 +1605,7 @@ mod test_process_proposal { let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: 1.into(), + amount_per_gas_unit: DenominatedAmount::native(1.into()), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -1607,7 +1625,7 @@ mod test_process_proposal { new_wrapper.update_header(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: 1.into(), + amount_per_gas_unit: DenominatedAmount::native(1.into()), token: shell.wl_storage.storage.native_token.clone(), }, keypair_2.ref_to(), @@ -1641,7 +1659,9 @@ mod test_process_proposal { let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: Amount::zero(), + amount_per_gas_unit: DenominatedAmount::native( + Amount::zero(), + ), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -1702,7 +1722,7 @@ mod test_process_proposal { let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: 1.into(), + amount_per_gas_unit: DenominatedAmount::native(1.into()), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -1749,7 +1769,7 @@ mod test_process_proposal { let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: 100.into(), + amount_per_gas_unit: DenominatedAmount::native(100.into()), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -1791,7 +1811,7 @@ mod test_process_proposal { let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: 100.into(), + amount_per_gas_unit: DenominatedAmount::native(100.into()), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -1829,10 +1849,17 @@ mod test_process_proposal { fn test_fee_non_whitelisted_token() { let (shell, _recv, _, _) = test_utils::setup(); + let apfel_denom = read_denom(&shell.wl_storage, &address::apfel()) + .expect("unable to read denomination from storage") + .expect("unable to find denomination of apfels"); + let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: 100.into(), + amount_per_gas_unit: DenominatedAmount { + amount: 100.into(), + denom: apfel_denom, + }, token: address::apfel(), }, crate::wallet::defaults::albert_keypair().ref_to(), @@ -1875,7 +1902,7 @@ mod test_process_proposal { let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: 0.into(), + amount_per_gas_unit: DenominatedAmount::native(0.into()), token: shell.wl_storage.storage.native_token.clone(), }, crate::wallet::defaults::albert_keypair().ref_to(), @@ -1918,7 +1945,9 @@ mod test_process_proposal { let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: 1_000_000_000.into(), + amount_per_gas_unit: DenominatedAmount::native( + 1_000_000_000.into(), + ), token: shell.wl_storage.storage.native_token.clone(), }, crate::wallet::defaults::albert_keypair().ref_to(), @@ -1961,7 +1990,9 @@ mod test_process_proposal { let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: token::Amount::max(), + amount_per_gas_unit: DenominatedAmount::native( + token::Amount::max(), + ), token: shell.wl_storage.storage.native_token.clone(), }, crate::wallet::defaults::albert_keypair().ref_to(), @@ -2007,7 +2038,7 @@ mod test_process_proposal { let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: 0.into(), + amount_per_gas_unit: DenominatedAmount::native(0.into()), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), @@ -2074,7 +2105,9 @@ mod test_process_proposal { let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: 100.into(), + amount_per_gas_unit: DenominatedAmount::native( + 100.into(), + ), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), diff --git a/benches/process_wrapper.rs b/benches/process_wrapper.rs index d8187e11ad..6f3c812ffb 100644 --- a/benches/process_wrapper.rs +++ b/benches/process_wrapper.rs @@ -6,6 +6,7 @@ use namada::proto::Signature; use namada::types::key::RefTo; use namada::types::storage::BlockHeight; use namada::types::time::DateTimeUtc; +use namada::types::token::DenominatedAmount; use namada::types::transaction::{Fee, WrapperTx}; use namada_apps::bench_utils::{BenchShell, TX_TRANSFER_WASM}; use namada_apps::node::ledger::shell::process_proposal::ValidationMeta; @@ -37,7 +38,7 @@ fn process_tx(c: &mut Criterion) { WrapperTx::new( Fee { token: address::nam(), - amount_per_gas_unit: 1.into(), + amount_per_gas_unit: DenominatedAmount::native(1.into()), }, defaults::albert_keypair().ref_to(), 0.into(), diff --git a/core/src/types/token.rs b/core/src/types/token.rs index 5c319b7da6..f9559a851f 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -401,6 +401,21 @@ impl DenominatedAmount { }) .ok_or(AmountParseError::PrecisionOverflow) } + + /// Attempt to apply the precision of the given token to this amount. + pub fn apply_precision( + self, + token: &Address, + storage: &impl StorageRead, + ) -> storage_api::Result { + let denom = read_denom(storage, token)?.ok_or_else(|| { + storage_api::Error::SimpleMessage( + "No denomination found in storage for the given token", + ) + })?; + self.increase_precision(denom) + .map_err(storage_api::Error::new) + } } impl Display for DenominatedAmount { diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index 1ab89ba61f..1b9b353a07 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -193,7 +193,7 @@ mod test_process_tx { use crate::types::address::nam; use crate::types::key::*; use crate::types::storage::Epoch; - use crate::types::token::Amount; + use crate::types::token::{Amount, DenominatedAmount}; fn gen_keypair() -> common::SecretKey { use rand::prelude::ThreadRng; @@ -278,8 +278,9 @@ mod test_process_tx { // the signed tx let mut tx = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: Amount::from_uint(10, 0) - .expect("Test failed"), + amount_per_gas_unit: DenominatedAmount::native( + Amount::from_uint(10, 0).expect("Test failed"), + ), token: nam(), }, keypair.ref_to(), @@ -306,8 +307,9 @@ mod test_process_tx { // the signed tx let mut tx = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: Amount::from_uint(10, 0) - .expect("Test failed"), + amount_per_gas_unit: DenominatedAmount::native( + Amount::from_uint(10, 0).expect("Test failed"), + ), token: nam(), }, keypair.ref_to(), diff --git a/core/src/types/transaction/wrapper.rs b/core/src/types/transaction/wrapper.rs index d66e61dcb3..ee0a17e607 100644 --- a/core/src/types/transaction/wrapper.rs +++ b/core/src/types/transaction/wrapper.rs @@ -65,7 +65,7 @@ pub mod wrapper_tx { )] pub struct Fee { /// amount of fee per gas unit - pub amount_per_gas_unit: Amount, + pub amount_per_gas_unit: DenominatedAmount, /// address of the token /// TODO: This should support multi-tokens pub token: Address, @@ -301,10 +301,7 @@ pub mod wrapper_tx { source: MASP, target: self.fee_payer(), token: self.fee.token.clone(), - amount: DenominatedAmount { - amount: self.get_tx_fee()?, - denom: 0.into(), - }, + amount: self.get_tx_fee()?, key: None, shielded: Some(masp_hash), }; @@ -317,11 +314,13 @@ pub mod wrapper_tx { /// Get the [`Amount`] of fees to be paid by the given wrapper. Returns /// an error if the amount overflows - pub fn get_tx_fee(&self) -> Result { - self.fee - .amount_per_gas_unit + pub fn get_tx_fee(&self) -> Result { + let mut amount_per_gas_unit = self.fee.amount_per_gas_unit; + amount_per_gas_unit.amount = amount_per_gas_unit + .amount .checked_mul(self.gas_limit.into()) - .ok_or(WrapperTxErr::OverflowingFee) + .ok_or(WrapperTxErr::OverflowingFee)?; + Ok(amount_per_gas_unit) } } diff --git a/sdk/src/signing.rs b/sdk/src/signing.rs index 6784be92ba..5c3c8da9bf 100644 --- a/sdk/src/signing.rs +++ b/sdk/src/signing.rs @@ -485,6 +485,9 @@ pub async fn wrap_tx<'a, N: Namada<'a>>( } } }; + let validated_minimum_fee = context + .denominate_amount(&args.fee_token, minimum_fee) + .await; let fee_amount = match args.fee_amount { Some(amount) => { let validated_fee_amount = @@ -492,26 +495,23 @@ pub async fn wrap_tx<'a, N: Namada<'a>>( .await .expect("Expected to be able to validate fee"); - let amount = - Amount::from_uint(validated_fee_amount.amount, 0).unwrap(); - - if amount >= minimum_fee { - amount + if validated_fee_amount >= validated_minimum_fee { + validated_fee_amount } else if !args.force { // Update the fee amount if it's not enough display_line!( context.io(), "The provided gas price {} is less than the minimum \ amount required {}, changing it to match the minimum", - amount.to_string_native(), - minimum_fee.to_string_native() + validated_fee_amount.to_string(), + validated_minimum_fee.to_string() ); - minimum_fee + validated_minimum_fee } else { - amount + validated_fee_amount } } - None => minimum_fee, + None => validated_minimum_fee, }; let mut updated_balance = match tx_source_balance { @@ -533,7 +533,7 @@ pub async fn wrap_tx<'a, N: Namada<'a>>( } }; - let total_fee = fee_amount * u64::from(args.gas_limit); + let total_fee = fee_amount.amount * u64::from(args.gas_limit); let (unshield, unshielding_epoch) = match total_fee .checked_sub(updated_balance) @@ -1737,28 +1737,23 @@ pub async fn to_ledger_vector<'a>( } if let Some(wrapper) = tx.header.wrapper() { - let gas_token = wrapper.fee.token.clone(); - let gas_limit = context - .format_amount(&gas_token, Amount::from(wrapper.gas_limit)) - .await; - let fee_amount_per_gas_unit = context - .format_amount(&gas_token, wrapper.fee.amount_per_gas_unit) - .await; + let fee_amount_per_gas_unit = + to_ledger_decimal(&wrapper.fee.amount_per_gas_unit.to_string()); tv.output_expert.extend(vec![ format!("Timestamp : {}", tx.header.timestamp.0), format!("Pubkey : {}", wrapper.pk), format!("Epoch : {}", wrapper.epoch), - format!("Gas limit : {}", gas_limit), + format!("Gas limit : {}", u64::from(wrapper.gas_limit)), ]); if let Some(token) = tokens.get(&wrapper.fee.token) { tv.output_expert.push(format!( "Fees/gas unit : {} {}", token.to_uppercase(), - to_ledger_decimal(&fee_amount_per_gas_unit), + fee_amount_per_gas_unit, )); } else { tv.output_expert.extend(vec![ - format!("Fee token : {}", gas_token), + format!("Fee token : {}", wrapper.fee.token), format!("Fees/gas unit : {}", fee_amount_per_gas_unit), ]); } diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index 3dd3a8710d..98ab30a6af 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -405,13 +405,16 @@ where match wrapper.get_tx_fee() { Ok(fees) => { - if balance.checked_sub(fees).is_some() { + let fees = fees + .apply_precision(&wrapper.fee.token, wl_storage) + .map_err(|e| Error::FeeError(e.to_string()))?; + if balance.checked_sub(fees.amount).is_some() { token_transfer( wl_storage, &wrapper.fee.token, &wrapper.fee_payer(), block_proposer, - fees, + fees.amount, ) .map_err(|e| Error::FeeError(e.to_string())) } else { @@ -530,7 +533,10 @@ where .get_tx_fee() .map_err(|e| Error::FeeError(e.to_string()))?; - if balance.checked_sub(fees).is_some() { + let fees = fees + .apply_precision(&wrapper.fee.token, wl_storage) + .map_err(|e| Error::FeeError(e.to_string()))?; + if balance.checked_sub(fees.amount).is_some() { Ok(()) } else { Err(Error::FeeError( diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index 2a527d2ff5..4fdc779dc5 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -2497,7 +2497,7 @@ where amount: namada_core::types::token::DenominatedAmount, ) -> Result<(), storage_api::Error> { use namada_core::types::token; - + if amount.amount != token::Amount::default() && src != dest { let src_key = token::balance_key(token, src); let dest_key = token::balance_key(token, dest); diff --git a/wasm/checksums.json b/wasm/checksums.json index 8c94ff7a0d..abda2d7f51 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,27 +1,27 @@ { - "tx_bond.wasm": "tx_bond.a660b13a79bd5c50c75a055ab4fed8eddb1cc2ae6d93e5505fbeb876b307fc49.wasm", - "tx_bridge_pool.wasm": "tx_bridge_pool.9564137202b291bd8930410700a5b0e08a1949bb4f79da5ee3e3dc9654266f4d.wasm", - "tx_change_consensus_key.wasm": "tx_change_consensus_key.4d2de1a762b4575925222fdeacadada429896a93fe3586f41657f247480161d9.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.457abeb00c021822e54674b77ae97535620555707efd2029e52b0d4335a32b55.wasm", - "tx_change_validator_metadata.wasm": "tx_change_validator_metadata.b56e2fbaefe19800d8a6d989883ab0ceac439bdeac96fb1299f6aa2ce424e521.wasm", - "tx_claim_rewards.wasm": "tx_claim_rewards.c4fc5d2ace6b0ee785f12f90a6cc600e3a0a8f19f7237ecf9374a32ba27eb99e.wasm", - "tx_deactivate_validator.wasm": "tx_deactivate_validator.c7cb406926241ffaa5df56e43c8cc43854bdcb0ea565144f63b6a3e2eb697a96.wasm", - "tx_ibc.wasm": "tx_ibc.14568fef2ec08752b87ec97cb2c87b76adec72e6eeea771274948d7c7ddb92a8.wasm", - "tx_init_account.wasm": "tx_init_account.0017279b252083d2d337d943bf84251d3f8223756ecfd9648e7ce66305c45796.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.cb9d7a33dd9a37675dd1d9cb761f3f87a48728eae38f377a623c0b8f9a97b612.wasm", - "tx_init_validator.wasm": "tx_init_validator.53fefa66ce6351d6b3e7de7a2c993bd4435fbd478b66caecf5c588b5efdcf669.wasm", - "tx_reactivate_validator.wasm": "tx_reactivate_validator.6a40f8daebdd6b9f6bcc9ab88686b40bb7a7882443cd07443c0795a3fae27cb5.wasm", - "tx_redelegate.wasm": "tx_redelegate.ded70e2609371fc9d81de875cf44b0ec60757fb46f61f23a26418cfa9f437b0f.wasm", - "tx_resign_steward.wasm": "tx_resign_steward.a881ed70174b53a15402cbbe6e64200f72431ec73604f7b173fd77ea80b5aedb.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.fbe760707aa0559c898cee47c91a5c7d0bc4b2f545cb47cc459ddcb8f97c455e.wasm", - "tx_transfer.wasm": "tx_transfer.aef75ec63198e4e6f95307a55e53fd0440448caf46617047f7a7557a7a2f1f4e.wasm", - "tx_unbond.wasm": "tx_unbond.1fd257addac3d91d77e1ae334b9394085335075581a6500ce2364cd403d028e9.wasm", - "tx_unjail_validator.wasm": "tx_unjail_validator.ba0e378bb48d5ba577dd898e618478c917e80ee11e46f79b00bb72fea4f91c28.wasm", - "tx_update_account.wasm": "tx_update_account.aa4ef57619b8b7c7c3849fa71873448808f7342fcc8d612e4b5e688a708a174a.wasm", - "tx_update_steward_commission.wasm": "tx_update_steward_commission.e7bf45b9039a5b4ef399fe926a4b56e035e9a7eedc4fdd59fc89210b4a8bbf74.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.4b39fd9c6ccc8a9537a295d154a8b9e60b8d135fe995276a01d714bc8097be6a.wasm", - "tx_withdraw.wasm": "tx_withdraw.2f6bf8c3cafbd5d987849db1583bd63e10bbe06193f619859caa80ab10f2e2a9.wasm", - "vp_implicit.wasm": "vp_implicit.262b8660014cd140bcbbef2d187863160678cb0034903257815dc08d947b03fa.wasm", - "vp_user.wasm": "vp_user.d66a3a289c9c9e6353a2741335775eab1487e2e7ee4b8baa69d32e73083363d3.wasm", - "vp_validator.wasm": "vp_validator.d89911d9c27b2e0926499623cc88ee35c4396c8ef0563f897386ff431c8148e7.wasm" + "tx_bond.wasm": "tx_bond.24a1a93ab606bfb3ea280fcb6938eed4735ce98da25fbcfbac034bf466ceea3c.wasm", + "tx_bridge_pool.wasm": "tx_bridge_pool.986638b4c38d6ea6c9cd87a985ace2ebcf2f5e1bd5ec19478a8a134f9543d8f9.wasm", + "tx_change_consensus_key.wasm": "tx_change_consensus_key.6c723f526276e2f3444c85a226daba7ec4fee55514a1817710159fc1206f3178.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.7bafc20d7aaa7acfcdecd09f059b151cf685c309dba8ae1ebf16b21759d0f111.wasm", + "tx_change_validator_metadata.wasm": "tx_change_validator_metadata.06775afbe8c702d52802d0db5c41c0391cf11bacee90214c4ce0f095379f2f0b.wasm", + "tx_claim_rewards.wasm": "tx_claim_rewards.352f86e06f979ff2b36dcad550ff57e3689fb975b6fd4573c5cacd26f2797987.wasm", + "tx_deactivate_validator.wasm": "tx_deactivate_validator.31b43c64a8e1a900002aa35e13a0c3d541ac329a585f6ca3df70870a760c7323.wasm", + "tx_ibc.wasm": "tx_ibc.0aa4d16c8f3a5c4b21dfc3092443b05705225f04bc3a1df8be8177ac73183fef.wasm", + "tx_init_account.wasm": "tx_init_account.eab0b875c424453c26c6420b972657434b23c6024e378ec436573f36c20f3d6b.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.0e130b5904f51dea4d02fc911fb9bf95fb7af42084498dfa436771f41bad0f55.wasm", + "tx_init_validator.wasm": "tx_init_validator.99d21a71edf5f0ca52b775ff2542d5e1b4f28c13ddb8ad875379e0bee9ac7174.wasm", + "tx_reactivate_validator.wasm": "tx_reactivate_validator.b4c855c7b6ca1116cfb7ebb791df963820c1b180c1a95eab7d3c6faec7e734fe.wasm", + "tx_redelegate.wasm": "tx_redelegate.489a6f8618e4d82bc76eb8984b1af0f8aeaf445f8c5b2449d8de71a7c5afbac4.wasm", + "tx_resign_steward.wasm": "tx_resign_steward.b809936ec9fae98eee1c079fc83d2fcc1e085eb5a110057a086261f9731bd3bc.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.da41d83cf22cd03cab83e3f5c0a6a0b054f63a0ed7a0a61ea8be8c5b2c61c2b4.wasm", + "tx_transfer.wasm": "tx_transfer.44cb4d4741d2e581747c9dd1c5c024bd182be7be2b600b275026c4b6af42fc8c.wasm", + "tx_unbond.wasm": "tx_unbond.180f5dd3b64105fc92b2f5949f77b097516ee50772a2d42dc80826cec2ea5e49.wasm", + "tx_unjail_validator.wasm": "tx_unjail_validator.2cea8c20197392eb3b09b19a8e7278ec70c16d85b11e4c20052ed5bf60951004.wasm", + "tx_update_account.wasm": "tx_update_account.54a2eac05115223a7a769830121def96d1feb4959eee37db4586fb717bd3c146.wasm", + "tx_update_steward_commission.wasm": "tx_update_steward_commission.4f5fd4ac948736be8ba4a6c84112a6bee2a3ad3369028a566e573f32059b0f22.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.357ae21d13a6e2a845c6276fca6f42620bef23311ae6abd9ce25c0256cb7b4dd.wasm", + "tx_withdraw.wasm": "tx_withdraw.e7b3b93d8470858b02cd449e330ac6de84929c53e11dff1c4f7030d74a204bfe.wasm", + "vp_implicit.wasm": "vp_implicit.e843bdfe9b211a138cca599c8d3bc6426b38587d10cf81da113c8b9c65544b45.wasm", + "vp_user.wasm": "vp_user.a6c222ecc1c237144ac01a9145369c0fd7176a053985addffc005f8fa00e67fa.wasm", + "vp_validator.wasm": "vp_validator.755de40fa771cd675ec219794c849e8bd4e3b3ec244e34b82925b88ca25b5ab8.wasm" } \ No newline at end of file diff --git a/wasm_for_tests/tx_fail.wasm b/wasm_for_tests/tx_fail.wasm index f6406827b4d0415e3378f6e2d576e8eb29d2a2d0..0b377e201357b11d5db69a68a685b7b7463a7796 100755 GIT binary patch delta 115194 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®5{&ILeGHA?U=h>YlPR$!YDg-(*82rI`Og62Iz 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 XuGd}m2+P*5ox7CP&0QLJr0V|w4mJkC delta 132718 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 OAm^3-cZ+e0`Tqd%y$xsp delta 152228 zcmd4434C2wb?<+Md!;K~UFph_B6;5DIJ`4@iEkpj_-`eM# zd*wL5zx4gzOFq`#XP_5vi z7L$*|o-UdWi&qss3cI7(jnVqEPQLS=pq>Tk^t(vA&#QF(`b&QO71#e(6!g5{mtXnH zYk%tnH~eO>diLh%%)!yIuZRB-{@S&_?kXGqK79FwubusP?yYBi| z-SFzy{A-vU4!;}zOZbWKqECi*hkp`&D*WT{;js7ZpBlV1`t05(-xO_}eR2GfX!=95 zpNn6iQH6tG24IFPJ#|r-wfJ2XFf3{P5nb&x|)5I##QN z;qKY5_dF1uH~X>P&+_-C`XxiVqa`kIk&7ocMYS}j-`r^I9sJvP@!mK6OEi$VAj+a` z#s4lGk1DM=jh|B4=Awz4r}^*Xp4#Sk3k^i+mdWNK7p;whR_%^fm_8C-msT!sgne<{ zg)UAjQ>`uyF;p`F%~U2^J)5Gy#p6NH>fPR8?$dLD>vg@BdFs+u7N=ozs8!$I=yLUx zn$4S|1emyZQ^W{dWm9yqf7;^57&BHEyXxf)DyJF$!W;M#T(PuOp<({*T-xe>s)p~n zTWX~*th<;tyInlh>T}U}w1wQ2s*!HVCTXm%IiTW;6IN$D+Ll!6v{Ro<8ZHuWTwfZg z!3N{&OT(#_2D!x1s$+r`>RA`Adq>jEhuw{ENnMq<27#-j;bh&0q3YG0jo5l>)m?Qm z>5jHB0au-3=v{o-#nAa+2YqRVB;BrOeP9iRu1j6#@a>^l18rXf)cF7Ku6u&!FzCe> zuF}++^Mz}#NX7N!i&0@6G{Nd*tLn1eoed%7+IUN=HlgKiKC=^(x2%f=be(&-K?6if!lM{A>t4e7G!=@Tn&Y4scaxu-V(ci;@ugA@q) zcGW|b9@2ou849IM5{iJQYS&2MR9YMs+_7SY&YR;gy6nGtyLx2c1~|D$srk<`5BhvQVdf>%()iCuGQ3l6z>I~-AK zqxSQ5)f!J~jYO^O>u7DwPi(FMJBre6Q>}3C^c&;hY)ei>kPJ{T(%sBFVgX2eS1|H) z2EA&@jHtqrsNmE#@A0VCiUpK$AQ*ePV)gEvaJNpxyEjLbdK%U}gpLN2X9ffHpq|d} z8o4J(H#49ulj+ROb3yv>&7jMY+574*+BwvUw!@YU9vvJaI)y_-jI@(OMCqvv)_5jS#gDr7hjcH`n;^GWj1*wP6{vNR}xJ!DJT$KJ>ZL+!I(3yjc zC|o&v{?PM!R=O3g=@vCt9@;nb{qRin#rNsayc5a7d4|Z^3}4q=<`VwQet6{JMa$Cc zRcWZAXg$MF@!>O84gc`_n({tPiqs}ooBj3~Yr7MV*|RH0R}T%f!0#ULdssrl@O$>z zqvsEQ>ybx&+{4Esvxi29$_74PG@vir1ahcrY;m~xw{Hb?M07wjykxCNXbwiZtZY90@ z`;qpsTjr|v$q|$NFamoz!BYrc`R@01>AN`ZYK|v#k+f<9KF$+g<*n@LR?RT&wReYk z;*EWmMd`e~jMHIzIa8Z#nRKxZ5AuW<%Rij%ZGXtzc#;%4-@XI!G_+!-JguO^hXQ*E z=vzxbJP#Dss?~B!?8EM+Yw4TWdzVeEp^%9;>3M}4cS}fxnwsnqx5&zuxHZi)XRlfQ z=i!;N$%;Qed)lww64z>;nGCyDssgvd#(s=33{3=lIeb0XIFKXg2vu7_K@rx$2GaSc!okOgbZa z^%yw&_BAgIR~-8On(h#q>vLdlbLDK;+JA%&zq9tu;mSidogIZd>^kQtf8YAJ-gAy^3C7nb#u!rd3|WtC9hH&-@Ej` z^YDqwUK}#I73n!)KV9Td9e%>c!k%`8-`M_J)o}jM zcb@e0ux{*Jrb6?)*{h!XobwOg0!%B2hUVhRwshZa=6C_mqqs`C5#dw+ zT=&Rc_qAx`Z97J0-*?re*)4+UbU5j{fqZph=%!ZhM9{R-ZBwp7VV4S77hgC}RV`9i zQE(!dL>mb7y|1xx(mpGjnWuspFDp3z0c|&L$<+PPyv=IS1Vrlip7G>u?x#SVl)W~6 zo7M(RSdYa+x+kw)evaI=ArzmljrWADeiyHe5B<-lejxHd&{yHw*tIlR_)QI+YUrk7 z1R5i6$67S1x6M{vaA_9r#q)XJ6r}Witkux_{^I>n|Gp;h>b+|X=zX8wdmu1Iy(?$I z6dA9k?cpjIXlP}ZS_+M(>|;NDsC#QvfEDOfP_{Ww4RumB%35?ypf}fxUJmBHl#QOq zdnp^$f~(uI(Sv!nWuqtZUdl#62~DL@bfWduRPr{T-p0VP(P`RHFJ+^1c`s$7)3KjR z*(&&|UIx_5p=UmGRor_P{F@nu&9i2AJ!>VL?s=li1-TkVtxp&3i()fqI9*tC0@h0`SeDCvL z9QV$)8jG`4LAs|l)%wLV1NV#RL2Tg1QF63@sufX!oWC?0CJ8Q;j-Cq8ktqEbGH`8t zFiLKUwiW+N6FU*L{wPc%S-fb*R{MA~iJ!r%^LwlW94Je&87n&0Q6#`43_D6rvRYAk zq@zSQfhg@L5iUmQ52ML(Q=GlTJ{);*&0wR4$!pl@SFGkO@g!#9)+}I_M*qn4xZ~pu z!LInC@Bu#1OlRWF@e%UtgXuVV3z_j~cbxpTWp0a;-&DcAIQbnbxIIoj7)Cdl$HDv# z(E=C^x2kQo3>K4D{0>S-n(NjifU@__%yqZIiATa)TERr+X^qf@X3QheCu%c&z34FG zQL`nNOoE<(Y_CjhvelBmMmlYV0hZQebf)`k(txdmyr?-!Ghka59k3$cUzUg%qm(r& zOAcEJ3t5(kmTa-hk{K&uMLO5wc(@s+=fQc5{51LW#~HM2OQOZFwK!=dM>{{>z&czM zzSe(xgl}IdTNI5p?RS~W(tdmesMpc8X$^mYnNyMU%>%$xHq!f*VqfkTjNbu{?^WLq z#*@juOu0F7@34$ipESNc`E^Ra+&!86u1b%Rxs()5D6vt0j?6Dn@cG(g@_VYWhs^ED zJj|PSD)X(L$>d$i%#(Sy)gki)3J&0p+OLAcjmhL~*MtX?T6|LJGb@!F#O}=JP&fPEc~IGKZtd0jJye*6k_l2s%t(uwc9g6o{x8^@%rUF43tG*s z!T;!rY?#Gg*X*x#2VqxKt;7SeA3R~g_`b@_HU#%{ixvF$?zDNs^>R$D59Yd)$18oH zyR|mwe^d;=(tT0*F01_I?&J*hf3!QWaU??@oChxLh@lyxpR<{ZH{OKwRXYDu1R+*PPnXHgiYq&M^T$=o@3unYiPHk z+95Kqs_t%nD zO83{2QPM2Bz2^Ox`)bLWiJp9tZslgAyIW~w)Td<#E(%ZdLLV6fm3XgwydfA>DMO-c zs#PnbPh#{HuMHN1(h%7hpgze+vQ`@igAf2o1N@KIXF*@`dbr5iAWp7#v=}B=t8V>N zSM5v>gE-ibw18iDPI0i&I$!c}qJpatk#NRD;Av<$VSgl9%fGGs+s41whrGL<%++k1 zXlX^ACwec+!p~^@@z&soz{o5M@44V;4qMg)W`!_`oE5)sJ zqBn*L+U)5Z&x^DK zBo$!@n+>wb4mL`ktBGW zJG-O4HMmb>Vm3#V2A76^ZYdOXN%$9iupKHdbs4ZZ(g$M!*3GnLge7F@VkyK|RG4dZ zT`U`N>r}!x6$Qa_zC$?&4etQB`AW!jFPuMIzbLd3)lK8d7}c-Sup8VE+Aa{9&stDG z8$gVDbMQHh`nm#%J}uZ@r>ddRNqnSF?3izYKkMEcyagiF_jBEwV-YL{L2t2ff|ips zu`S$GtA1v{bYpFjWL?`E%hQT$0{2!wTY$Kpx>%a7E0>Z(xp>Ob)*!UNmdPMnjni&t z8sCt_eBKZLN4u3LweI?%?7QHOtnQno6Kx^jlJJZN?|fCL zb0uS71$H`7$hy@cNlr#U%dopr7S{=(xM2u2z^a*wJ1-ZHr1Hhf>v6s+~e6g%V zy8ooy#P64UAl*+oLA`xKMWooC9yjcg+#p5K0LCf6$a%2p!g#Z@|Z$2oXKtNI_TlKUtr4=wTh@QICYlZLcX{~^SV5-5y z;+fX6iARE4CU)JtduGr6x#{qx^=9B>ECoX-~2o1*X+6qG4()z`y0!wFWiz z)-zns`L6>Rve&s__OhwPSrALmX{p>Z+%qP^TW^_|E?UoSxta|zH|Wl0f~~VBvA{sN z;J>gkEXIv4xFvyx7=I1zDUDy_1}EtWbed{4;5Ari3nxUpfyAo*=d*CCKMJ!55_=(A z?%^c<=3P-k`;Q9>Wc9IR^~)Pk{%IX)+TTU&NgM`@=cj+&S%cPx9HSdyFfPPk#&0H> z9oOr1K-Di$v9dwX35J%Ta?N9k!&HI+01_C<3WoL=UqH;_@vO-0J=6>Si-8DCp?*BM z&fpU~^(yw5SGX16NoR*vms&s(1%cf>2DSZquoWw;huu%N!JP*(t8w9nU-s5=*T46! zsoAq2X48bF?Aiq zA_OY}>4XR3{l*8`ZRsu)Uo4hMmtAV40dv$hUhn0hkR>7_4><`#0BDc<6+9t|hr2^1 z!$guWg;8)faib+0gBE6bl4s<=d6oG7GhkYvQFI~SCT|ejvgo!RnOEb{o}T2F)$P8X z*00=={6C&D`HSTEn*v`mQ12%G*>cWxn_hIGEIP)CIPXqwP>QAU%u_#CdH#W!fG^5F z$S%phV9IT6?r$_S)dxAfN_M^zm%aorvAbDO0L3%6qwL+_o|zZ8|Hr+m`L(bQKonn4 zBRWkD{7132h~ebPt21|lX7TFe{GtK5muyb6HV7v53;7UJKks3dvQp|RRe7O^P+(+) zsvyN=ZsU=9OpRrs(VwwKZCzgB9v1B|41x*H^oU#L)KDhVd=|E|pqXBp*=P$bjmk!- zumBq&x^u?}UDlPxS1Mc~THu{vF&?eot}I+~r&aE|I+XjiZXaz+=MbboWG+Cvj{!C^ zbA<*Mr(HLsnH*Ou&^A$6q%cd89Yzm#D>g+RQ3^y4HelZ!*zsi+Myggqx<2?}Hv%i# z(IDh1Jyl=AAe|+x%1UoP0spdJ~u9P&C1w3IM3%4{3A`#*w0~!DH**i)B=SK z&D*?JUbA9$+gapXVKa-G`K+9nzUdaTh|JW=&aW4QC(np64_JX9)~Z671|@m{RcfMo zGccKJn5$#b&ZP9Ul{2I5-FOVJ6mc5$7JAxt5KMZS0tj98`sB4{(?l4x;eH0KT+|Arm!-{r=}-{%awf?e4WD*|aIaLdvE?^lc-DEV)K@3nb?t zXme>v{it4lC-GBqws;b9+bt|g##-`5D6TE4|7RFUPI=8hetZ_SMxsXi+wLUvE`rh;@XtX9ZfH|lrGz9 z2Dxibq`E%n#a^UZgj=DC=r*eCfX)$|9_buN#)eaWu}|6GGUy04UCd+oM8MZtNE0*3 zAeAwh2?vr}OCvd_#EJEWOPNQHAA$FbxE${Ct-)dX6RrzCj4qcle#+Jccl9R_9rI{< zcQl!DLMDl-FgBZ%)RiDeUSlmM%TApVdL(g99pa7QFXP{9e7~*p?!b1<-Dno7yY%AV zy&BfwMdAK_+-HzVF){O{6~y@KYQPgI!g+J>K7I6_Qd2T#+IvE|39`_81@<#1fF+%) z%M~^e3RefIbU4>=+U8NohuH*tv~vyj<(a4W*0B9tb%Aef%e63~Y%ra@@BND&%el7Y z+Mn!PQ>&+OuKn!3wX8v8P=|Bv?s_sL1e&YUbR9x*LsDtIMkuIy=UNwVK`YD^z*y0V z4w^2_m4Sed59vF~=C zHsFN{#@Cmzn>DfPY!FMXep$RiP^)w)SgLs(>=)9VcSr7Rqixx7_@F%x7|BX^8&v1r zZ2?}Dw!3ZV?p8pH3WR>;ExXZu*^}DgJ#XS1h38Eg-PzVED(IG%C#!Qwq~2oCYA2TD z>NI=}-L?|jUl_s?nGUleoWF-f*fNz|pky*xo;H(Vn(yOZ7fgsa_ZG7MT6_^nf8O+J z*9Y18gBAVK3;I)s>qcIKD2>1^-kBfGEyqPdBMeLyMcl6D1~YhHjhV9+IuL^OO4x!N zJ)@I}+?Hy{Xq~25*T-n;DrPr$N=w!dpJIE_SgTJ)WS?7&u}622_892(H5uNu3pf+9 za|L6N)d0`}mT%f`vpNI{M1ahTc;3dbi~i}J#0RdSQ9Rb?ey9j#3i zjuH2n7hcL^bFziE$@us=E~~+#TC!)v2<05^=w}g+9X+fP=-M3+J=)ncA$*-p3(g%a ziw(&EIl^S}&K9{Hh5fh6O&6tW+@}0}qvS(afq5B%$~*Z*W1tI*Cv>TqAFtJ90p+(F z1)xWMNc5jf4PfpcPzpTeuqb_Qbf>LGcD>Z3YT9d$JFjDPct^uR@xwow3(|GzLnO^} zX=*ezrD-*Iu$qH5YJ~dz_62C~chSP9EN#w*a=CT6;T<%NIU<+I5HeV6PkcFs&JC>^ z(bGvaJSU8NGh-Pt6D9zxR&8hY`UJNMEw>#10K?{ z&t$q{O_Nk6-D)AOn$uAumS{>ZtFHekR-DB|{d2 z?GV{)V*Pa+K~wi-!^iFXo{{KzcJ&M2$Bo4IZrz9K))o$)qW_ z*4Cw+ZC%>g)}@_onU*R0%gO$IK^;Q&!;9DxL7$NWx;S3MR6!J3E%=~z$geyI%I}a^ zhiq#bfK#iBN!1id3O&PYjD^Y6m0_)Ids#FFGMK4t)yHtmOfF*s9H~@cK8240&0~6K zw`5?g`i|1-Q=z*^MtF*8G1dHZC{^M3cM%th)7}4)i;F5ljB}9`nTHckh#=G&&MBHO zNNLUeAmdWmgft%QT7A(eCjc4 z%MGI!U1Yg4SY2P>B;W7C$puJS*JBNV*{X+zK(H^!eZeJ5f^mpPv#&%#&GrazJ_rX(4-(4St2O ztVJquT9@fsB@E$UD7_?t3ED|5WD3&Q_>LxujidDj-ZCu{kly@JPl7?mX$$V$l^!GJ z795f`fS&{@9XmkCIyqZ{u2g}$PZIic=?Id?(600dSu7+1@VhJu`aECbs}$oO+TTx$ z7&=HO8?hhna)u@+0cH)OX16p#+zlo&angCuJ&9Txf@%q?I9Nf3cn(S`4(d7TN3@en zBooj5(!X{{``D2zFBx!cdt(vn=szX3s&tKo#u_prLQaVdK-1V1%|JPXIH%eJ$qa*AZWVlVC9hrgxz;r@R(u_uQN*QjaS^`^~-pr9L$t&}55qEI94iXNJ zFpA#GWZiQB!l_}+bA;FMv=h+MO1;PZ0Hjl8caJegy9&c7uVM`117`JSYmWG zziv|R`;$Q?5^ZzK)0jBL_df6Sacl3juaAfHXE>9-$sS~l`c_X^`HzSMmwxbDxk%t(`6MEl)y7kaj9ZT| z_wg0%b9VIw97tu=IXu2k6usk4H>jObc2oB7e5%t|pU-upL|p9Ttj%3npF+OGu5{_j z_x@>ufji>taNJ31^7TF4cyzrlA@rVIcs@`z&#pO7?O}muH_Sb|w5yP=ws$uZ1$4T1 z;TGoICC$C->s{v#UT)QikBuX4lMh+N2fGuHvF+X^j;PbU`=s2xtI(B$eUk(mn9~b1 zdYTC~yk7AE_WT5_WL+90oB5aUPfFDwVPNx1ITx=uu;PE}ViLQKYb$yuZkE5}jt2!{ zR>;w%zv4no*aa6Wr1-4Extg~*QQJ&p3W(}={c))gqrv94P95y&_D)=j9%9E?4#!@R zavurEU*n1lyxqxq(uW5olX23!mrN!t(*27k6+OR+>@CBSIBt5=U1lWormq{DOfFEs zP7WSz&}pQVRulAhsAwXIjtR(*#RQOkOD3XZBAt8vJwd|Bq=dj%YI9*l#7!4cWoq_0 zcU9z{6T7A_sRm4Zwedj8xO(Zyt7Q)DvPbnaAZj|P&Gqr1N_eplc%O27a+CpZ?t;H3M<<({`2eX7Ho~x<7fb(wY9`B}(`7C%>w6u95tj(j$%JrAm)Bl9wsn*PmRa zbbo(xxzYpu$#az+>`yLJdbl4Qigd0&*`V}DKcc>u73n=MIM#n->msiAptb&6P==O( zSIgW*WDhF4!LkRGU256=%3kCyqryI)eJX#14<>vp0vQ>2XePE^=?;oE6IEex5{_&How}N7~URd(&g+?VHluLo;b9SkJ<6 zP!{Qtv#iW^V8mG1R{{M?*0M(4I<71UP&d~|lP-`{2HzTK+C{-pEAZPjLSwhaUJ+kl z_{v{!7pyfBtQW(REr~46G^TxvTg}dLSY)rv4Q5q0o0IK6IH@#6ieMtz{j0Kh)ToMU zIGh#}I$4t93ddDOaBc(9-k3~x{a`N8VBn>SLr#DA?;Xz%>mjqZslTuSZYW3Oh*zc1 zoaIvDDbO7>IH(GU zOv0xxUtbltz8qk-Y`9eF>D`f`4-F5Yt)~zAN8pc6)4{d#Wk60XyCd1mP<4>DE+^kPc4y&kS|w5 z7erx8>#B!F8B!0jun-e*fr54T-vk@3m)+vH5rHP-TXwsKDPg-|kxRUJw}FO{)V=8p z4&h-^0DygzivY?-XWzDW{Bq3!p@AEIh?Gf}b!I5j_dupBo2CpAF7TCQ-{-o7%51pr zoR_fCB7Fiuk1mItPwVo7YK7_x)rq<-k#yHGE{f_n5d3#*gE+ZdYJHgR>1gk0p~8dg zHen&9r`W&7q1c;#4ApXxwrc%QTrVnMFY30~goK-Ge!mcGq#RjowpIZ!@q%C2FdNXwVcThDFH$?r@ErhSOIhL!YV^Kd20Ra?^rriIwbk4!^lWeDhp3XUE0mp} zj#kr~{+VqbUr>_W9M4r8GdW7=`;(($@~qt1eq&Iv%nX7W4DalYom})Z>6Y_nkWO%N zSZVLt;HzhB4t@X{xrvK|e^*+Y-TAw%3?)koxQvyO%KFM%v^xl1!B(ok+uSD}Ee}!@ zQv?|n!}6N51kZ)h-3@657ZOt@%^+XSe=L?0r2rMa(OM(hsiU*ZdOHG^vKdmen;zx4 zQ96n={P-ki66U&;Lq$R57>`g{L^%(|0`u*7(sHH*FN~$TwfV_Wo>AP&=8y2qc?&)F znbP$s3YxAm`|96Ymn|~vG#(nLW$>J^BGp2$$)-eueKJSz~O01*S;znE2E z=oVw9gh;||w8NgkS^ikb>W*V2I8#=mr=KO=9&1m>N}$CBTi=VMyJKB0O5gC{?}R(? zBPT?sxeBeW_UB4gyNc>)?|Zd7Yr(;i)zb7iShCuhUCnapV99E_)6%TYpcB-Fv)m%d zm$Ss(>|hBEPkM8s^I*wp%+1vh4+l#$J$a@`CDrI%81!6>FLxRCr`$Wh;#iklJBn{0 z@W8`J`mk+TTBVHEyyK_JCV@Vg;}gEt=15=p$rc1c`LJ!uZPsY3>AkjOphSTT)%1Sw zL(U-HvhM!8Ow}d0!~|F_fSV&tkMk;&)s$7!dGbq@uj-_=TjQGssob#7f=|$(RX9wx zz}u>B(<#xenjYY(K)PzWpC`>G_OtoC9yf${$MUUK(=YJY6y~9f@OVORQx23Ds7Koz zvRvioaTnQMyp;tOR?}Os7%vwisHQiQg>|-kRb+y?aTkSp_a;4!2TSGIt){=Dr*!3H z$&O(SC)&8>~Abw6B>X#NTc?`LmxGwDH2UJQ_)0PMd( zsyhQ7PkNN{C#sz1b4iJuc@TKGe<%EY_P53KR)q6-QIpKxkZ z29|A6vHT?FW{AKi5WY2>{wTx|;6Fzt{E?3F1R;72>$+1nLFz>Bur(B$f+Rre0@J{1 zKNXkvVlxN8J3s~#OHcmmT#yq}427g8{xvIYjhF>Vk5eI+@hHaSL+VYBVKp?IE|xjK z&W7|8d$Kd<+<&#R!6ud*9DY>Qa#ZF8q3-lFUXUsoSacKZuCHRzImFIzhHxz(hBlp^ z@DtJPPM>*W7 z)kBhiZUmPUVtQDjrYAj#0$oV*VacK2$VJSsL{yIj%@mO?!xCYf=;ITG+xUi|9i}Mf1IWf)A3W_-BtPB@U6locHz2)QNib_f6*Q0RS8@eJFiICna_8 zyO{1_;ulYuE}Qi|=Pq84 zT{0@>aSOA)0WNUaOo259*0GqzQDYyYD+cIt-jTCZZ#F%R7p2*x6LY1nAp^o@HxUdA z+wFxKcvgh23`#@F4)JL;0qj`;5E${PS@g7)7B*yrL|!V~liAcot-pcPsvPk1?j28h zv3pvZelmF|wHb_LUhj7fWz+c8V7}{RHx*I4fEnkK7!3Q5o&l{5-qE)?_!mKo(An=Q zWqarZY0h{~R3@8y3&z8REuf0fjJs0CsdF&H8i-x1@XYkMaqtZ76Bv1ioaB$Ivwq7& zd(vS9*&SRTc*L-HcMk-NWU@8h4SzIxE`miK!iQziW!L75r}I&HHdE9YPA2Pk58@xq>&@lq!CqslSP-lhIQK5rGS>-cXPc>|WS(ep zL1@cV1vJd&XW02B9buPBYyV}h+kJHdEOkq7OqQ}C8Xhcl_}0y!{8jJ=6Vn2)6p92C$LrmYg^9=w90rvKhB_IIEs{=WAiw zylDCtP%+Ea`6BQy33qve>>faFV&B`1YCpI|D^Q$9=wLUROnj`w?vo zt)})5Fz@sH5%Nd!{892p@#I1ax2X#WHc`IZ$=ub1u?#IvWPqGTj@DbxReGe}dWkh! z&sx8#3{lJB?%lg5f~PNSJ)64WgpAl%UUTP-tzUD$dLta>#~=T^ZpJGx38i3_qLvf-LL9fNbf#wcGqaoOuzSGkvHq^~>p`p-}A`u^<~Fi`hw z>L1ZiZI#^R{Qt6N=JhXkFTF9N$Bqv|_Z&Vr-tOQkN2~b&NJ<%&$(D))e*<@&|0srw%=ySy)(Di!?RR@Cs4C#>-%{L&< zS?-Cjj54yxl%v+!6tcHkxN@GxBWhbAOMBcCjnyMujC!%>JrZ8f+=1)j@#z?+;|dG> zY@TV#ygs1y(|DvQoyQb&>T!>6Hzil3>7GsvG0uZP3@mCqja!11vBx<*vXP<^u@iZw z+9hIBL?<83i|{WqL5JUn6b1d5rPL* zrrRudp4W2@tNm7TyrblRm7FL_2B~OULvTy%Prj}QO*S)5W_3KjB}I6Hzl8~sb`d#~ zBUTNAjG-kOw;Se zRYDyi7+`I37$nB(0`1!!@Y8y6;XoO;t%T&o`rryTsgszHN^r^{8m?9Vi8!5gF0RDR z8)~Yo)WTCVcmhz335%Kiex-CJo^nMEb^c?=XmmQ_XS}0E@$q#e9lB=t3 zvMy(ZTdkGO=pGI_CY_UP6ro^c(g*4>`grji* ziPgRuFV_{9VTFa6#qN~VIu*c-j|*0E+E3A0<+aL$!PI0K^ZaO!7 zC)K3pag0egVps}(qP*jHM#UUPl^jmboD#%`z5f#aE30uF_7#^7%wa{N(Fa&G)tWR1 zCT(5e9heYk?wrHbiGN`Lf-ob&oS1UadoLz;K$sWPuExWQxeCvuB9}r8F3fZNewZC19q8b6$NLZn#|ZizNRQ)GS>%ui$A(YwU^?%?bRIOa z9Z;!!@RIBFU~>s>&0x(b&TP3@k>lW{9euC!Zc=FDCIs)yDW^U%RHGU zD&CXXk|%RO^wx9(uB9-h0Zu2#nZ{HIPFh0xIb4&QNZD@1KpRN!BCctK^aNLco<+*G zG!DiAyT!CYx`*c>(rJjU&didgk85)4Sqbl;?B&+vn}rtkBSoixkKnIU;U@9r^`DgF=fM^Ev<$M!$twLy2O-`6|u~$Kv-UZMzuO_w4tGRh=@TWb7E}OZO zDColfH<124&I#^hZ3|!Kou(IPEart6=scY~4W#78-)r(Hk#C*z>jH)#=}aRx!&$R- z_GDjPdR7M_3G?-c1@`d2sq9v%T~zJZDF ze&dsW_r5!RbjR;gK)C$C#Aok+-?6V8{ij2pq~HJr>GT|KQfxnCMCt3lg2+T%K7nVD zD6Kc2z!b^Ds_Y0T4B)uolnIRxXZR35? z<@NS28)?vdqL62O@Wlq+A;T)->OiV}dHy*0J$Zhf{7jw~;$?>PFj5*8(#us8ZBuJA z{hr@Lxa`&IS6uKNiB%mTcwgh-qe26)L81Osn|rjH^O!M=)<5XSD)Kk6BEJZ!GCrtj zY-(l%3ewrhk&e!=7p=3RDM69iMQ24TC<*m+*V!O-kn{?pjklhVcwmV9m3Vl!w%2S5 zpdGkmcN>4kXLYx6_GzEj<;EgAPhxUI8u_62T06{O2|?->Vg#v<2mjimElR6(qQ^6? zX_F+S#&@nlMQG%B9||^8JUGCyLp;zTQ(aMemhGH!I*aE&;JJ(y@skbG@7YhNd4VEH zRB5f}mriu5!HXq%E48tYw{nB0r~*5Pn3>e)ip~+soNi0BSU;AM+Yb^8Ss@q1j?y`( z4%y?G-+Az#;`&#(bFu+Avnx`-!w)m>Wk44%Vv7$*du9D#W%udg9K!`x(^sQ!+mPZ{ z?g;afw=*WR&TY6jh)Am1Xzpd`{;T{O89D<*X7sRQM>^Njz5$h^mjmfpvRI2N0tQge z;e&+{XcFppgEM>ON$3v`hEme{cvyNby&Wko} zUz>P{bx^;2g+3l)9sNn!B2_rV**fq+m`xcDLA$aES_|*lJR53Q=WQ4!)|a%H7Mt5u zx4N$GT}!lT?orv9Oc|&Z@bcD4Zm8(Uivh5JT`wa*R9qAk}`)gib!ZdxW zo@ZTVTV1{GvOD}a4sHZR5x}3#vz+5LL`Osdw_ouWu6q5QuGgQXK&0F1vaDbTVI^#2 z$LRiwQ}8sXWANw@3G@dNPqkK?lFvtZl!BCeueYM)BmBo?_2ySHNUzt$d6+-6zQ`;K z&R^1xw=A^pZBDbnADn#&kPyWWiKKnTV-@{L6 zc@N4jr^(gwsLf`LT(5n^qUPZi^8WdInrrU5Tyu&tY0$8Y3yXsExQVyrD?h`sHRHxvxCJ`aZZrSiWoPZ=RUHdCr%p?`ip>d);gAMB;oX zOgiUDDDAr#>+aR%06VX2OsVw? zF6E4q4lb7$zXAGZ++l}{!4ePre0{l{hDcS4Sb%cLC7x{&$`V~5Zws+t8H55lLksBV zIh12J$df-xe!ffP)w`bO#1TzzA$ zZr}oS{~HHFoCDmj|8XlPV6VG%qusOrF%{fs*X)0!+r3JVb8iTDbHC0rj9>6G2fXI$ zeSU1_V7Ci(_5O`qxsNE{%&>z)ncROH6V8(@PChDp{aEPEPd*;9?8)KKU6XtQ$9?k0 zp}Q-@E*(og8M;Fux9**n+#R|nB{#Y?$)AL-FZopHMw34c-Kt~`YEJIK!H{&j4~H-> zZdJ_>obUS)Y)@{qNH?Kz3I6#LgZeRvdfnS4BbX@5XLo&j*>l~gKkiafbP23P(Iwn@ z1WGFKg5%=wKF-^)TRsc}BD%!tRPj^l4M%M3jp~a<X2vrtB%m-*cdHDXN{tWRDc? zpG^K=q)i%gC=EIv-Sk^uE<5Gd z_`Ic%E@iA(^#cCG?e|?;oF8^RbAsR&j4tBMq%Ig4`m3yR*t5n=Ow`GdIa<_m+7K{$ zi}hjsIBD(Ir}H;;nFE+~=fv2pKs4r<#}V<{;8K6}ynZ6(;@}Hb1f5+He7+BkKL#oT zde9IhU8_UGg9dlu;$Ry~vm8wu@Ik#A!MCp~Q#r>`EYlzyQ%k%pgJMA8kLSjH>5iH%3aPryBjm}dWqsVVIQ+A zc|UuX$)`DH>MwJ!C1&*yu|xVcPKNsU@d_>k&1Hj?a9COHL^)(~i2C$oE$;fXvnF}yQ2y;Zyq(Xl+kmc^wnTmWe43f#-nG#&xnZ^*)%4;d)uq(0jlQpSYE-|4+t4lte_f#`G&lvrbGeyD_iSDBJK-4(vHu^+Sw{s; zw86eN-D`##fI!Ry#-nk0;~H#%C;i^?7&XhINzjotP_Arj#P;YHS#ihA=|Zt<^d;M) zmVV~HJskP{6S0m#dKmS#$0(aCR1yx3Vxx509-{&wwg-m_U~jt4QnA>h^rsP$%J$%L z8Bq%XG^~RtfObz~du)X9kDBd~X=dyA-685~jtR;7fTP7b6dWiBn9i=KlPX&-jLlz* zf!at%GR`SRed3MTxVlyYXz&_dS1$Imhp20qKL3tGT|3h(_U93_i3o2nCII47Ni-mA>ZAc< zBhY{ac~IZe0ElBWKziD{_{4KWmJ97~(Lf{3Y!i{0t@D>?XpZQ`tZPmKeS(0|fUaEc zf@~O@r-8nb1}Mg6@awUJbuegvk{hi$mmd{0&?g$8aif7gXdrWoHv~oleWC&3knopN z5u$;-2crR&Js*@_7lX=cKm(kK5e@W-2I!hfYET=S5R=jyh0~MishOD|ErvX$r zqXB-5-Dm)WO&X#BemQZB&&Gsyxg6-AfwA?$vkN&eHXi*F2sx&H`}hDk(Buhjv`hp= ziI^BAW2~Jxb*8L-!{$sYB&t@@!S^%vz+GJ(L{JyTJ?2ln?Bh(1h~T%gyMMdwg%T04YJyN7L_j4G zK_BSUNd&f<5CP{(Aub)xlpyHKiGWl@z*QxhFG@fz2!su%A%YAl=rc*6nLW-Ef#%3z zM-~?HlOR|u2sE{k*)Sq#Xl$McjQ5xz;J~AZz!qc)3t`XzC$~Um`|t=f3L;o6BB1eK zL=b@gk_2NLg6BvOh&5^;zofy#&qM@y4>=JOgVO6_P_Z%dun=E|%TjsqqO5Nga6)d{&nR? z8oJR#X|Zeei(eYbP+lsK6!%5qxV2BQ#=ujwiZrq&b)iuNi3UjgAB79fHDGkfhI{n6 zhSIF~X~!C#F9D|j{4Rg4ftlDe^klzES4=h^ryXGM^%<*XEaekd#vrmA*JSQTMZ|RI`wfqnuo>pU`E(Mq-$V3U>JkLLJ@R-j#*~@^aitJ zaZWeOxQ+u3(jCT|2lE``3G6+SC$P}*w21D-+LV*qbDib(Np~2h2&TwiH`)B0sp*ar ziqql3GY-r`1~ON9zEB%?u|i5YC5ZekqPSuhCPnOL?@q_Z4Kss*x+!S3*eR%a25=oaujD}?qhAlGYy@;{o zO)+HxQ8&j|aKZt0pETgq#f;JSZOR=JMoZv^#w}vji%{uZ!_GKRbg{n>OfQpueSoyk z0S8(iBUo3?-7%eO&{aw0xduFz3QCU7WZ<0c25xp&Y}fk0`OPF9XJCfo#L0Fs3C7$C zc3*8r8MstW`Zaa41dGtxc`}0nbkr^PZ4Gb~lsmfyHVTNj3)r9pB--2AB_Ayw{%NJQ z-4ef$#$@q$XoN1%kp|{>1tOQ^$@pnVuC6DU@SKBDU1~~h2t&3$61XKJ^Q&C56Afd! zfO5?Ix2FLHLVmKfxWuBx!lD9yq#%|j9i@Sm+dwan{#l-4kdj-vT8;JFrh-irQOvns zp$X%dOvX%jjUJJ9PUThh`AT1RO*`S zZ>DyoqqwJ7XoYmJ;H8D3`{a@)PTGTg`*N2;npzvntvDi;X>TFhp|APE-InpV6$%d!aHMSwzcn1E#TC$ zqA^p0OeKii)Ah-(_$>po17o*4biX1O)ozZ2O*)G%A`7D0F<-JEaQ-#yYpy;K3c@&~ zEbKdCA5GU>b&U9gM%=$}#6xWajvM0piuDx0cyw2QI2@)cf@U!C>xRR(p{+|fK*~kIf>_GmNVfsMv;{D5eDA; z3#}?Bk6ZK3INea`pu34z&Db9y`unR7)ifc77!jOF+3t%SjQW)xkto>aVgV2L?DtI zkLFF+ zJAhYWe6PU)Rp(c}N)HtLVuAI|7Nt&m!v1S`1??gLIiE}(OVt>p)O3$iOb*jNFgcLe zXqAh!M3@|i6tlZYy~$y|CtQk!&7li$jTtQXfyp(Hiv*2OrnI{?J#d-nn%#||AwbFQ zPVs|sN_UB@9l0_&*PVvCcmlmt+ks>Gy2nBU$%?Ej01srQmCvAoY{BvwEHG2hBaZ1a zvN_B5;DGGZ@);Bu6Pxk%M?BnN#T!u*woP$0qSTX6-j=D{-rz{N$xMa@>S1Ye2>-OcnpTv) zmqkMRz-n0UZ*nR2eHXQl0Y>RtZ0A?HZ4TVXEz(Xsg#dZ?6tMVGXWIoVkd)c7 zpo}F1W*ZpjMWOv5LPQ*)c7@q*>HJL?0u+J9-;}?miG;F}W94N&$T2H9+EFrZB}Y0+ zj#4bYAJ>iJDM~g?cPciJObuHts7X6{jYg zP2b0JD#wX>cb?i$v~hk;J4qn4ez=K6en?L^pNONsmd6Jg+ES>xjq(-qOaPyD82IT9 zLNxH-C5c1^J200(|DMUS6?H|@(ss}mwzK24Z~|z4JX*#!CrKZh)g*x~P51xnvBu>< zBt&9O(bLPpF`mxkCYYG@cdZpujOXRVBT@Re7A|F68m)9H?RpIn2CzSV97O3LX$``{ z$J{Hrtfhy(aX*)HuOT;Gy%v3jA9&EiM<_OreXsNKr?)wj5a>qy6rb%11-BY0@iMwc zfQ*mwS|Hf5YE!bsbMVTZNSHeFnjW<;L?T<&D>L3%Pdrv2^!(;-^6i#WJ^ zyWkSu%~$Z?wVVa5@>fqwoJ+YOg1>pamP8Fkyc*@Y2X_{6nf_SRSq#kwbr#PV1MIli z{w#6Uj*IQpg~p)UU#%<5Nrn3WA>YCk(jNu{q2d_`4G2QTGY}dOgoIwxRnLXFf4EOs{oUM7hNO1PA&Q>EY5}b=?8hMf6oIkr4}^Ju^fJ^go%|*gks}BZ15WgVvcegKPVUV94_h$VJI+jZf)}HqF@a2#enP_fNvEOn zL8Uy`pz~q3Nq3ypY;_EOFJ$3_(5)N`GIyb-OT%w&+IG#(iQ&S75r=Qky>;n$zbll`NJrbcEP8uwC8>x;`Xn_2th--NCdxaYl z_VsyW>*HnaQf?#LC@q#a2yEoIU4U%|c0C%|hzU1rTb*OuYV@=YY@^t)jgSb#wqMAx z?HAlyd9@3XMX_PqW?`E|Rx;IEl4DqbV2eA^3&|z)T0WK0Oo?A6x`tcHY4K~b$FGll z`+LEajnzn*W3?Q=^zdEUIC~bUsx-Zj?%o4W*ZRwbPccyir-hsmQw1mve5W*oDVRvr%m>rlaAqOOWHXARts{-rO$%#4m5KrBY`x47nG3#9|>+nJc|#H<%7?a1H{4dakDsRkLLY;`Jx?>VI7&Ta znxFj$p`c{1nP8NnOO^#lFjF_nf^ij~k(LGPRe;i(s|}{(9+ZwN4o$O%l$sX3UV8n9 zc~o2`EH5*o&ujlgY`T{-x_lD*!+aK)2K&uj(VpHbaK&&qLi+y8Ejz93#f9PU-CjLy z2(bwigEJo8X09oI%G!c##-lek2osPloM~^xZ@$mI1p ztYW?(vM<|lHr(xUr>n|!wmbJ(X#++__ZY{U`5jzVliVJy=yWy9SDKQ2<-6PMUF=^W z8!S%f{pITedJXj(j?~=~==?-4J~xkh?>4Y0oHcOoHn4TZz`o1g5^xGo-^JTZD+l09 z#K9bYP(P1}MtV#sD*XdWxvcIsL3I`Pj41sy9*s9E^L{cj%G^d~w=&z=pi+vLt#+VWyNN67K?q|go8lI?{n8hmVAJ}$sPK05&tImcRl}RRPYAr2qAef|6ansU*+Gg@$aSldl~<(;@{=`doKS1IP!GwRI6e}1UCh=I5+v` z5U>u9pB>HUUT=P)fvB96dSUZs_)$faI`Ph#yQcr^8~^FP3k1u`>|OtBD6Gux`r**U z6=LfrC%*W)-~IN#f8n;*f6;_vMFMN$JOBLf$=}aUAMp)mPbMe+=){*E{?Pxr`{Nh; zX0H3*(9$oJ`YO)F&PpBOx2h%%yzO7#{>c6Rw)-CiGYa(nua)kPE(*S? z3<2%OyGi#D;aX*IzxnNFW)<#V!l}9g46CGX{EOQ^+F40cr1*F@_oOx7NfW-Zc04M? z77|+6(c)uPW>w|HD!cBim@Q~FrY1;q+L~017-Q#f_uJ^Si^NY_6?!Z1qgXdIiq#mf zxFe2&@pi$C6-dGMyu2c7i7KTWsOb7F3Q9Glq6_CJD4BdE58BpPxx_kb_2CfIHD{~z z%VQOS!?`K)cz1gAjrRoUIXMuI{S}_thrdQ->=Jxbfl zZPH&25u{YvU*ljS>AqU>YU`zzJVWVBjreQQ>00t6r6>96Sf%_R!!o7EyU{6z(}(Tw zcx#j+F2mYIOP>9R?-89K39zscrr^V|bjXLkQ) zj--$ZEYnv$&X(`x+={oLF#=bI7c|z(ZV<*0dqn9rE*QoT@(6;W11TDs{bXE)y*>uA z=3x1GaCK3RrDqBKPCpvCw|^GW`T0j98>YcH;G^0v%;^C4)=wscF(EXB@BzrhDB%vv za6$G(Du5IwltK#ED}@xq4viMxU>S&kpDGw+dm9l3OCN;JmD5G)LAqf9KYzo4Kj`4+ zZKVI|@BiSR{_jHiF4$uYHg2BxMG>Z{AwJC8Knz-TApY@gcqZ5kJPh@*X->y=3)J7! zWz{7lTxC4Ct!q>8Eir5IZzcat<+%m#c&rQPSjV{Rvx7c7{ou

ktq$j6f9jOIXA7 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 z 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

Uund`?F8K8)jl;=vUAOl54shJCS_NaV1sk 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 delta 165188 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 bi{OpofV}JS-a%RH&F*-<7=rI2Z0Y|6dC_FF literal 409829 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 H!j}F&g>YJM literal 409795 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 Nb)Fc4_Yk)9e*u1>Cvk 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 w_j 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

w*;y&Ht>FZLkxhp5L6{#(TSFUHpb^Ezm?r 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%0DJ0GH-1Uj1TC}@)0Xe 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 diff --git a/wasm_for_tests/vp_memory_limit.wasm b/wasm_for_tests/vp_memory_limit.wasm index 5c91e6113c678741e0ce50aabdeb9c3af532e830..cdebb21ec9613e09265ce3bb0497431cac83bf4d 100755 GIT binary patch delta 161333 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%0D;l?`D9KYguPTcV%>(u(t7sR`C{xQk; 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�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 D8U7o@ diff --git a/wasm_for_tests/vp_read_storage_key.wasm b/wasm_for_tests/vp_read_storage_key.wasm index 7665fa7e39d74c8ff4470db4e9852f229146fcdc..14be17069c2881760ba97e16d589d018d1a31da4 100755 GIT binary patch delta 160946 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 +@ws5Nt8kPtG?YjZ`)3hmWN{lxF?FSp~@-)K#AK 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 z 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 Czo4uD delta 178697 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≀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 Date: Mon, 4 Dec 2023 18:18:09 +0200 Subject: [PATCH 019/216] Removed to_ledger_vectors dependency on Namada client. --- sdk/src/signing.rs | 23 +++++------------------ shared/src/vm/host_env.rs | 2 +- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/sdk/src/signing.rs b/sdk/src/signing.rs index 5c3c8da9bf..a462c00a00 100644 --- a/sdk/src/signing.rs +++ b/sdk/src/signing.rs @@ -735,7 +735,6 @@ fn make_ledger_amount_addr( /// Adds a Ledger output line describing a given transaction amount and asset /// type async fn make_ledger_amount_asset<'a>( - context: &impl Namada<'a>, tokens: &HashMap, output: &mut Vec, amount: u64, @@ -745,22 +744,17 @@ async fn make_ledger_amount_asset<'a>( ) { if let Some((token, _, _epoch)) = assets.get(token) { // If the AssetType can be decoded, then at least display Addressees - let formatted_amt = context.format_amount(token, amount.into()).await; if let Some(token) = tokens.get(token) { output.push(format!( "{}Amount : {} {}", prefix, token.to_uppercase(), - to_ledger_decimal(&formatted_amt), + amount, )); } else { output.extend(vec![ format!("{}Token : {}", prefix, token), - format!( - "{}Amount : {}", - prefix, - to_ledger_decimal(&formatted_amt) - ), + format!("{}Amount : {}", prefix, amount,), ]); } } else { @@ -830,7 +824,6 @@ fn format_outputs(output: &mut Vec) { /// Adds a Ledger output for the sender and destination for transparent and MASP /// transactions pub async fn make_ledger_masp_endpoints<'a>( - context: &impl Namada<'a>, tokens: &HashMap, output: &mut Vec, transfer: &Transfer, @@ -853,7 +846,6 @@ pub async fn make_ledger_masp_endpoints<'a>( let vk = ExtendedViewingKey::from(*sapling_input.key()); output.push(format!("Sender : {}", vk)); make_ledger_amount_asset( - context, tokens, output, sapling_input.value(), @@ -880,7 +872,6 @@ pub async fn make_ledger_masp_endpoints<'a>( let pa = PaymentAddress::from(sapling_output.address()); output.push(format!("Destination : {}", pa)); make_ledger_amount_asset( - context, tokens, output, sapling_output.value(), @@ -917,7 +908,7 @@ pub async fn generate_test_vector<'a>( // Contract the large data blobs in the transaction tx.wallet_filter(); // Convert the transaction to Ledger format - let decoding = to_ledger_vector(context, &tx).await?; + let decoding = to_ledger_vector(*context.wallet().await, &tx).await?; let output = serde_json::to_string(&decoding) .map_err(|e| Error::from(EncodingError::Serde(e.to_string())))?; // Record the transaction at the identified path @@ -1015,13 +1006,11 @@ impl<'a> Display for LedgerProposalType<'a> { /// Converts the given transaction to the form that is displayed on the Ledger /// device pub async fn to_ledger_vector<'a>( - context: &impl Namada<'a>, + wallet: &Wallet, tx: &Tx, ) -> Result { // To facilitate lookups of human-readable token names - let tokens: HashMap = context - .wallet() - .await + let tokens: HashMap = wallet .get_addresses() .into_iter() .map(|(alias, addr)| (addr, alias)) @@ -1397,7 +1386,6 @@ pub async fn to_ledger_vector<'a>( tv.output.push("Type : Transfer".to_string()); make_ledger_masp_endpoints( - context, &tokens, &mut tv.output, &transfer, @@ -1406,7 +1394,6 @@ pub async fn to_ledger_vector<'a>( ) .await; make_ledger_masp_endpoints( - context, &tokens, &mut tv.output_expert, &transfer, diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index 4fdc779dc5..2a527d2ff5 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -2497,7 +2497,7 @@ where amount: namada_core::types::token::DenominatedAmount, ) -> Result<(), storage_api::Error> { use namada_core::types::token; - + if amount.amount != token::Amount::default() && src != dest { let src_key = token::balance_key(token, src); let dest_key = token::balance_key(token, dest); From 439fec115b06ebc1824679a8d9fb82638b6d9fb1 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Tue, 5 Dec 2023 14:57:47 +0200 Subject: [PATCH 020/216] Protocol now ensures that a DenominatedAmounts denom is correct. Added checked arithmetic operations to DenominatedAmount to catch mistakes. --- apps/src/lib/cli.rs | 22 +++-- apps/src/lib/config/genesis.rs | 24 ++--- apps/src/lib/config/genesis/chain.rs | 2 +- apps/src/lib/config/genesis/templates.rs | 6 +- apps/src/lib/config/genesis/transactions.rs | 59 ++++++++--- .../lib/node/ledger/shell/finalize_block.rs | 6 +- apps/src/lib/node/ledger/shell/init_chain.rs | 8 +- apps/src/lib/node/ledger/shell/mod.rs | 12 +-- .../lib/node/ledger/shell/prepare_proposal.rs | 16 +-- .../lib/node/ledger/shell/process_proposal.rs | 8 +- core/src/ledger/ibc/context/token_transfer.rs | 2 +- core/src/types/token.rs | 93 ++++++++++++++---- core/src/types/transaction/wrapper.rs | 10 +- ethereum_bridge/src/parameters.rs | 11 ++- sdk/src/eth_bridge/bridge_pool.rs | 14 +-- sdk/src/lib.rs | 13 +-- sdk/src/masp.rs | 12 +-- sdk/src/queries/router.rs | 32 +++--- sdk/src/rpc.rs | 6 +- sdk/src/signing.rs | 10 +- sdk/src/tx.rs | 10 +- shared/src/ledger/native_vp/ibc/context.rs | 15 +-- shared/src/ledger/native_vp/masp.rs | 9 +- shared/src/ledger/protocol/mod.rs | 10 +- shared/src/vm/host_env.rs | 17 ++-- tests/src/e2e/eth_bridge_tests.rs | 41 +++----- tests/src/e2e/ledger_tests.rs | 10 +- tests/src/e2e/setup.rs | 8 +- tx_prelude/src/ibc.rs | 4 +- tx_prelude/src/token.rs | 7 +- wasm/checksums.json | 50 +++++----- wasm/wasm_source/src/vp_implicit.rs | 24 ++--- wasm/wasm_source/src/vp_testnet_faucet.rs | 18 ++-- wasm/wasm_source/src/vp_user.rs | 24 ++--- wasm/wasm_source/src/vp_validator.rs | 24 ++--- wasm_for_tests/tx_memory_limit.wasm | Bin 421361 -> 421407 bytes wasm_for_tests/tx_proposal_code.wasm | Bin 418426 -> 418454 bytes wasm_for_tests/tx_read_storage_key.wasm | Bin 427447 -> 427487 bytes wasm_for_tests/tx_write.wasm | Bin 431956 -> 431996 bytes wasm_for_tests/vp_always_false.wasm | Bin 391355 -> 391352 bytes wasm_for_tests/vp_always_true.wasm | Bin 391497 -> 391494 bytes wasm_for_tests/vp_eval.wasm | Bin 467705 -> 467745 bytes wasm_for_tests/vp_memory_limit.wasm | Bin 445498 -> 445538 bytes wasm_for_tests/vp_read_storage_key.wasm | Bin 451219 -> 451259 bytes 44 files changed, 362 insertions(+), 275 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 3bb7f5c7ad..9a22224379 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2872,9 +2872,11 @@ pub mod args { pub const BRIDGE_POOL_GAS_AMOUNT: ArgDefault = arg_default( "pool-gas-amount", - DefaultFn(|| token::DenominatedAmount { - amount: token::Amount::zero(), - denom: NATIVE_MAX_DECIMAL_PLACES.into(), + DefaultFn(|| { + token::DenominatedAmount::new( + token::Amount::zero(), + NATIVE_MAX_DECIMAL_PLACES.into(), + ) }), ); pub const BRIDGE_POOL_GAS_PAYER: ArgOpt = @@ -2944,9 +2946,11 @@ pub mod args { pub const FEE_PAYER: Arg = arg("fee-payer"); pub const FEE_AMOUNT: ArgDefault = arg_default( "fee-amount", - DefaultFn(|| token::DenominatedAmount { - amount: token::Amount::default(), - denom: NATIVE_MAX_DECIMAL_PLACES.into(), + DefaultFn(|| { + token::DenominatedAmount::new( + token::Amount::default(), + NATIVE_MAX_DECIMAL_PLACES.into(), + ) }), ); pub const GENESIS_PATH: Arg = arg("genesis-path"); @@ -4261,7 +4265,7 @@ pub mod args { println!("Could not parse bond amount: {:?}", e); safe_exit(1); }) - .amount; + .amount(); let source = SOURCE_OPT.parse(matches); let tx_code_path = PathBuf::from(TX_BOND_WASM); Self { @@ -4311,7 +4315,7 @@ pub mod args { println!("Could not parse bond amount: {:?}", e); safe_exit(1); }) - .amount; + .amount(); let source = SOURCE_OPT.parse(matches); let tx_code_path = PathBuf::from(TX_UNBOND_WASM); Self { @@ -4437,7 +4441,7 @@ pub mod args { println!("Could not parse bond amount: {:?}", e); safe_exit(1); }) - .amount; + .amount(); let tx_code_path = PathBuf::from(TX_REDELEGATE_WASM); Self { tx, diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index b1fac85e81..3d146dd61c 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -411,10 +411,10 @@ pub fn make_dev_genesis( StringEncoded { raw: account_keypair.ref_to(), }, - token::DenominatedAmount { - amount: token::Amount::native_whole(200_000), - denom: NATIVE_MAX_DECIMAL_PLACES.into(), - }, + token::DenominatedAmount::new( + token::Amount::native_whole(200_000), + NATIVE_MAX_DECIMAL_PLACES.into(), + ), ); } // transfer funds from implicit key to validator @@ -425,10 +425,10 @@ pub fn make_dev_genesis( raw: account_keypair.ref_to(), }, target: alias.clone(), - amount: token::DenominatedAmount { - amount: token::Amount::native_whole(200_000), - denom: NATIVE_MAX_DECIMAL_PLACES.into(), - }, + amount: token::DenominatedAmount::new( + token::Amount::native_whole(200_000), + NATIVE_MAX_DECIMAL_PLACES.into(), + ), }) } // self bond @@ -436,10 +436,10 @@ pub fn make_dev_genesis( bonds.push(transactions::BondTx { source: transactions::AliasOrPk::Alias(alias.clone()), validator: alias, - amount: token::DenominatedAmount { - amount: token::Amount::native_whole(100_000), - denom: NATIVE_MAX_DECIMAL_PLACES.into(), - }, + amount: token::DenominatedAmount::new( + token::Amount::native_whole(100_000), + NATIVE_MAX_DECIMAL_PLACES.into(), + ), }) } } diff --git a/apps/src/lib/config/genesis/chain.rs b/apps/src/lib/config/genesis/chain.rs index 502e8c27f5..dbf7ba3b49 100644 --- a/apps/src/lib/config/genesis/chain.rs +++ b/apps/src/lib/config/genesis/chain.rs @@ -313,7 +313,7 @@ impl Finalized { .map(|(token, amt)| { ( self.tokens.token.get(token).cloned().unwrap().address, - amt.amount, + amt.amount(), ) }) .collect(), diff --git a/apps/src/lib/config/genesis/templates.rs b/apps/src/lib/config/genesis/templates.rs index 50afeb9ac8..0668130c29 100644 --- a/apps/src/lib/config/genesis/templates.rs +++ b/apps/src/lib/config/genesis/templates.rs @@ -491,7 +491,7 @@ pub struct EthBridgeParams { impl TokenBalances { pub fn get(&self, pk: common::PublicKey) -> Option { let pk = StringEncoded { raw: pk }; - self.0.get(&pk).map(|amt| amt.amount) + self.0.get(&pk).map(|amt| amt.amount()) } } #[derive( @@ -906,7 +906,7 @@ pub fn validate_balances( let sum = next.0.values().try_fold( token::Amount::default(), |acc, amount| { - let res = acc.checked_add(amount.amount); + let res = acc.checked_add(amount.amount()); if res.as_ref().is_none() { is_valid = false; eprintln!( @@ -997,7 +997,7 @@ mod tests { .0 .get(&StringEncoded { raw: pk }) .unwrap() - .amount + .amount() ); } } diff --git a/apps/src/lib/config/genesis/transactions.rs b/apps/src/lib/config/genesis/transactions.rs index d457ef87f5..54b0cf8d80 100644 --- a/apps/src/lib/config/genesis/transactions.rs +++ b/apps/src/lib/config/genesis/transactions.rs @@ -167,7 +167,7 @@ pub fn init_validator( validator_wallet, )]); - let transfer = if transfer_from_source_amount.amount.is_zero() { + let transfer = if transfer_from_source_amount.amount().is_zero() { None } else { let unsigned_transfer_tx = TransferTx { @@ -181,7 +181,7 @@ pub fn init_validator( Some(vec![transfer_tx]) }; - let bond = if self_bond_amount.amount.is_zero() { + let bond = if self_bond_amount.amount().is_zero() { None } else { let unsigned_bond_tx = BondTx { @@ -513,7 +513,7 @@ impl Transactions { BTreeMap::new(); for tx in txs { let entry = stakes.entry(&tx.validator).or_default(); - *entry += tx.amount.amount; + *entry += tx.amount.amount(); } stakes.into_iter().any(|(_validator, stake)| { @@ -1129,8 +1129,19 @@ fn validate_bond( balances.pks.0.remove(source); } } + } else if let Some(new_balance) = + balance.checked_sub(*amount) + { + *balance = new_balance; } else { - balance.amount -= amount.amount; + eprintln!( + "Invalid bond tx. Amount {} should have the \ + denomination {:?}. Got {:?}.", + amount, + balance.denom(), + amount.denom(), + ); + is_valid = false; } } } @@ -1370,7 +1381,7 @@ pub fn validate_transfer( match balances.get_mut(token) { Some(balances) => match balances.pks.0.get_mut(source) { Some(balance) => { - if balance.amount < amount.amount { + if *balance < *amount { eprintln!( "Invalid transfer tx. Source {source} doesn't have \ enough balance of token \"{token}\" to transfer {}. \ @@ -1380,21 +1391,47 @@ pub fn validate_transfer( is_valid = false; } else { // Deduct the amount from source - if amount.amount == balance.amount { + if amount == balance { balances.pks.0.remove(source); + } else if let Some(new_balance) = + balance.checked_sub(*amount) + { + *balance = new_balance; } else { - balance.amount -= amount.amount; + eprintln!( + "Invalid bond tx. Amount {} should have the \ + denomination {:?}. Got {:?}.", + amount, + balance.denom(), + amount.denom(), + ); + is_valid = false; } // Add the amount to target let target_balance = balances .aliases .entry(target.clone()) - .or_insert_with(|| DenominatedAmount { - amount: token::Amount::zero(), - denom: amount.denom, + .or_insert_with(|| { + DenominatedAmount::new( + token::Amount::zero(), + amount.denom(), + ) }); - target_balance.amount += amount.amount; + if let Some(new_balance) = + target_balance.checked_add(*amount) + { + *target_balance = new_balance; + } else { + eprintln!( + "Invalid bond tx. Amount {} should have the \ + denomination {:?}. Got {:?}.", + amount, + target_balance.denom(), + amount.denom(), + ); + is_valid = false; + } } } None => { diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 30a3838010..ae0928d360 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -3451,7 +3451,7 @@ mod test_finalize_block { let fee_amount = wrapper.header().wrapper().unwrap().get_tx_fee().unwrap(); let fee_amount = fee_amount - .apply_precision( + .to_amount( &wrapper.header().wrapper().unwrap().fee.token, &shell.wl_storage, ) @@ -3493,7 +3493,7 @@ mod test_finalize_block { .unwrap(); assert_eq!( new_proposer_balance, - proposer_balance.checked_add(fee_amount.amount).unwrap() + proposer_balance.checked_add(fee_amount).unwrap() ); let new_signer_balance = storage_api::token::read_balance( @@ -3504,7 +3504,7 @@ mod test_finalize_block { .unwrap(); assert_eq!( new_signer_balance, - signer_balance.checked_sub(fee_amount.amount).unwrap() + signer_balance.checked_sub(fee_amount).unwrap() ) } diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index aad1fcc362..4e8c832bd2 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -326,10 +326,10 @@ where &mut self.wl_storage, token_address, &owner, - balance.amount, + balance.amount(), ) .expect("Couldn't credit initial balance"); - total_token_balance += balance.amount; + total_token_balance += balance.amount(); } // Write the total amount of tokens for the ratio self.wl_storage @@ -530,7 +530,7 @@ where token, &source, &target, - amount.amount, + amount.amount(), ) { tracing::warn!( "Genesis token transfer tx failed with: {err}. \ @@ -592,7 +592,7 @@ where &mut self.wl_storage, Some(&source), validator, - amount.amount, + amount.amount(), current_epoch, Some(0), ) { diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 03c7130d6c..92d536170c 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -1403,10 +1403,10 @@ where match wrapper .fee .amount_per_gas_unit - .apply_precision(&wrapper.fee.token, &self.wl_storage) + .to_amount(&wrapper.fee.token, &self.wl_storage) { Ok(amount_per_gas_unit) - if amount_per_gas_unit.amount < minimum_gas_price => + if amount_per_gas_unit < minimum_gas_price => { // The fees do not match the minimum required return Err(Error::TxApply(protocol::Error::FeeError( @@ -2872,10 +2872,10 @@ mod shell_tests { let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: DenominatedAmount { - amount: 100.into(), - denom: apfel_denom, - }, + amount_per_gas_unit: DenominatedAmount::new( + 100.into(), + apfel_denom, + ), token: address::apfel(), }, crate::wallet::defaults::albert_keypair().ref_to(), diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 1543e61e45..210464af8b 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -1146,10 +1146,10 @@ mod test_prepare_proposal { let wrapper = WrapperTx::new( Fee { - amount_per_gas_unit: DenominatedAmount { - amount: 100.into(), - denom: btc_denom, - }, + amount_per_gas_unit: DenominatedAmount::new( + 100.into(), + btc_denom, + ), token: address::btc(), }, crate::wallet::defaults::albert_keypair().ref_to(), @@ -1194,10 +1194,10 @@ mod test_prepare_proposal { let wrapper = WrapperTx::new( Fee { - amount_per_gas_unit: DenominatedAmount { - amount: 100.into(), - denom: apfel_denom, - }, + amount_per_gas_unit: DenominatedAmount::new( + 100.into(), + apfel_denom, + ), token: address::apfel(), }, crate::wallet::defaults::albert_keypair().ref_to(), diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 357bba2a7b..928369566e 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -1856,10 +1856,10 @@ mod test_process_proposal { let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: DenominatedAmount { - amount: 100.into(), - denom: apfel_denom, - }, + amount_per_gas_unit: DenominatedAmount::new( + 100.into(), + apfel_denom, + ), token: address::apfel(), }, crate::wallet::defaults::albert_keypair().ref_to(), diff --git a/core/src/ledger/ibc/context/token_transfer.rs b/core/src/ledger/ibc/context/token_transfer.rs index 4c41b8c956..ee0fb9f70f 100644 --- a/core/src/ledger/ibc/context/token_transfer.rs +++ b/core/src/ledger/ibc/context/token_transfer.rs @@ -64,7 +64,7 @@ where .into(), ) })?; - let amount = token::DenominatedAmount { amount, denom }; + let amount = token::DenominatedAmount::new(amount, denom); Ok((token, amount)) } diff --git a/core/src/types/token.rs b/core/src/types/token.rs index f9559a851f..6364bc8b03 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -169,7 +169,7 @@ impl Amount { .map(|result| Self { raw: result }) } - /// Checked division. Returns `None` on overflow. + /// Checked multiplication. Returns `None` on overflow. pub fn checked_mul(&self, amount: Amount) -> Option { self.raw .checked_mul(amount.raw) @@ -181,9 +181,7 @@ impl Amount { string: impl AsRef, denom: impl Into, ) -> Result { - DenominatedAmount::from_str(string.as_ref())? - .increase_precision(denom.into().into()) - .map(Into::into) + DenominatedAmount::from_str(string.as_ref())?.scale(denom) } /// Attempt to convert an unsigned integer to an `Amount` with the @@ -320,12 +318,17 @@ impl From for u8 { )] pub struct DenominatedAmount { /// The mantissa - pub amount: Amount, + amount: Amount, /// The number of decimal places in base ten. - pub denom: Denomination, + denom: Denomination, } impl DenominatedAmount { + /// Make a new denominated amount + pub fn new(amount: Amount, denom: Denomination) -> Self { + Self { amount, denom } + } + /// Return a denominated native token amount. pub const fn native(amount: Amount) -> Self { Self { @@ -402,12 +405,14 @@ impl DenominatedAmount { .ok_or(AmountParseError::PrecisionOverflow) } - /// Attempt to apply the precision of the given token to this amount. - pub fn apply_precision( + /// Convert this denominated amount into a plain amount by increasing its + /// precision to the given token's denomination and then taking the + /// significand. + pub fn to_amount( self, token: &Address, storage: &impl StorageRead, - ) -> storage_api::Result { + ) -> storage_api::Result { let denom = read_denom(storage, token)?.ok_or_else(|| { storage_api::Error::SimpleMessage( "No denomination found in storage for the given token", @@ -415,6 +420,64 @@ impl DenominatedAmount { })?; self.increase_precision(denom) .map_err(storage_api::Error::new) + .map(|x| x.amount) + } + + /// Multiply this number by 10^denom and return the computed integer if + /// possible. Otherwise error out. + pub fn scale( + self, + denom: impl Into, + ) -> Result { + self.increase_precision(Denomination(denom.into())) + .map(|x| x.amount) + } + + /// Checked multiplication. Returns `None` on overflow. + pub fn checked_mul(&self, rhs: DenominatedAmount) -> Option { + let amount = self.amount.checked_mul(rhs.amount)?; + let denom = self.denom.0.checked_add(rhs.denom.0)?.into(); + Some(Self { amount, denom }) + } + + /// Checked subtraction. Returns `None` on overflow. + pub fn checked_sub(&self, mut rhs: DenominatedAmount) -> Option { + let mut lhs = *self; + if lhs.denom < rhs.denom { + lhs = lhs.increase_precision(rhs.denom).ok()?; + } else { + rhs = rhs.increase_precision(lhs.denom).ok()?; + } + let amount = lhs.amount.checked_sub(rhs.amount)?; + Some(Self { + amount, + denom: lhs.denom, + }) + } + + /// Checked addition. Returns `None` on overflow. + pub fn checked_add(&self, mut rhs: DenominatedAmount) -> Option { + let mut lhs = *self; + if lhs.denom < rhs.denom { + lhs = lhs.increase_precision(rhs.denom).ok()?; + } else { + rhs = rhs.increase_precision(lhs.denom).ok()?; + } + let amount = lhs.amount.checked_add(rhs.amount)?; + Some(Self { + amount, + denom: lhs.denom, + }) + } + + /// Returns the significand of this number + pub fn amount(&self) -> Amount { + self.amount + } + + /// Returns the denomination of this number + pub fn denom(&self) -> Denomination { + self.denom } } @@ -569,15 +632,9 @@ impl<'de> serde::Deserialize<'de> for DenominatedAmount { } } -impl<'a> From<&'a DenominatedAmount> for &'a Amount { - fn from(denom: &'a DenominatedAmount) -> Self { - &denom.amount - } -} - -impl From for Amount { - fn from(denom: DenominatedAmount) -> Self { - denom.amount +impl From for DenominatedAmount { + fn from(amount: Amount) -> Self { + DenominatedAmount::new(amount, 0.into()) } } diff --git a/core/src/types/transaction/wrapper.rs b/core/src/types/transaction/wrapper.rs index ee0a17e607..fbac749ef6 100644 --- a/core/src/types/transaction/wrapper.rs +++ b/core/src/types/transaction/wrapper.rs @@ -315,12 +315,10 @@ pub mod wrapper_tx { /// Get the [`Amount`] of fees to be paid by the given wrapper. Returns /// an error if the amount overflows pub fn get_tx_fee(&self) -> Result { - let mut amount_per_gas_unit = self.fee.amount_per_gas_unit; - amount_per_gas_unit.amount = amount_per_gas_unit - .amount - .checked_mul(self.gas_limit.into()) - .ok_or(WrapperTxErr::OverflowingFee)?; - Ok(amount_per_gas_unit) + self.fee + .amount_per_gas_unit + .checked_mul(Amount::from(self.gas_limit).into()) + .ok_or(WrapperTxErr::OverflowingFee) } } diff --git a/ethereum_bridge/src/parameters.rs b/ethereum_bridge/src/parameters.rs index 62d1f9a7a1..34db6246b7 100644 --- a/ethereum_bridge/src/parameters.rs +++ b/ethereum_bridge/src/parameters.rs @@ -208,11 +208,12 @@ impl EthereumBridgeParams { .unwrap(); for Erc20WhitelistEntry { token_address: addr, - token_cap: DenominatedAmount { amount: cap, denom }, + token_cap, } in erc20_whitelist { - if addr == native_erc20 - && denom != &NATIVE_MAX_DECIMAL_PLACES.into() + let cap = token_cap.amount(); + let denom = token_cap.denom(); + if addr == native_erc20 && denom != NATIVE_MAX_DECIMAL_PLACES.into() { panic!( "Error writing Ethereum bridge config: The native token \ @@ -232,14 +233,14 @@ impl EthereumBridgeParams { suffix: whitelist::KeyType::Cap, } .into(); - wl_storage.write_bytes(&key, encode(cap)).unwrap(); + wl_storage.write_bytes(&key, encode(&cap)).unwrap(); let key = whitelist::Key { asset: *addr, suffix: whitelist::KeyType::Denomination, } .into(); - wl_storage.write_bytes(&key, encode(denom)).unwrap(); + wl_storage.write_bytes(&key, encode(&denom)).unwrap(); } // Initialize the storage for the Ethereum Bridge VP. vp::init_storage(wl_storage); diff --git a/sdk/src/eth_bridge/bridge_pool.rs b/sdk/src/eth_bridge/bridge_pool.rs index e682521987..611a62a61b 100644 --- a/sdk/src/eth_bridge/bridge_pool.rs +++ b/sdk/src/eth_bridge/bridge_pool.rs @@ -19,7 +19,7 @@ use namada_core::types::eth_bridge_pool::{ use namada_core::types::ethereum_events::EthAddress; use namada_core::types::keccak::KeccakHash; use namada_core::types::storage::Epoch; -use namada_core::types::token::{balance_key, Amount, DenominatedAmount}; +use namada_core::types::token::{balance_key, Amount}; use namada_core::types::voting_power::FractionalVotingPower; use owo_colors::OwoColorize; use serde::Serialize; @@ -144,12 +144,8 @@ async fn validate_bridge_pool_tx( }); // validate amounts - let ( - tok_denominated @ DenominatedAmount { amount, .. }, - fee_denominated @ DenominatedAmount { - amount: fee_amount, .. - }, - ) = futures::try_join!(validate_token_amount, validate_fee_amount)?; + let (tok_denominated, fee_denominated) = + futures::try_join!(validate_token_amount, validate_fee_amount)?; // build pending Bridge pool transfer let fee_payer = fee_payer.unwrap_or_else(|| sender.clone()); @@ -158,7 +154,7 @@ async fn validate_bridge_pool_tx( asset, recipient, sender, - amount, + amount: tok_denominated.amount(), kind: if nut { TransferToEthereumKind::Nut } else { @@ -167,7 +163,7 @@ async fn validate_bridge_pool_tx( }, gas_fee: GasFee { token: fee_token, - amount: fee_amount, + amount: fee_denominated.amount(), payer: fee_payer, }, }; diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index f5c0ae37fa..3aa38ab391 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -38,7 +38,6 @@ use namada_core::types::ethereum_events::EthAddress; use namada_core::types::key::*; use namada_core::types::masp::{TransferSource, TransferTarget}; use namada_core::types::token; -use namada_core::types::token::NATIVE_MAX_DECIMAL_PLACES; use namada_core::types::transaction::GasLimit; use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; @@ -50,7 +49,7 @@ use crate::rpc::{ denominate_amount, format_denominated_amount, query_native_token, }; use crate::signing::SigningTxData; -use crate::token::DenominatedAmount; +use crate::token::{DenominatedAmount, NATIVE_MAX_DECIMAL_PLACES}; use crate::tx::{ ProcessTxResponse, TX_BOND_WASM, TX_BRIDGE_POOL_WASM, TX_CHANGE_COMMISSION_WASM, TX_CHANGE_CONSENSUS_KEY_WASM, @@ -448,10 +447,12 @@ pub trait Namada<'a>: Sized { recipient, asset, amount, - fee_amount: InputAmount::Unvalidated(token::DenominatedAmount { - amount: token::Amount::default(), - denom: NATIVE_MAX_DECIMAL_PLACES.into(), - }), + fee_amount: InputAmount::Unvalidated( + token::DenominatedAmount::new( + token::Amount::default(), + NATIVE_MAX_DECIMAL_PLACES.into(), + ), + ), fee_payer: None, fee_token: self.native_token(), nut: false, diff --git a/sdk/src/masp.rs b/sdk/src/masp.rs index 68459688e0..c2782c5706 100644 --- a/sdk/src/masp.rs +++ b/sdk/src/masp.rs @@ -911,7 +911,7 @@ impl ShieldedContext { tx.source.clone(), MaspChange { asset: token_addr, - change: -tx.amount.amount.change(), + change: -tx.amount.amount().change(), }, ); self.last_txidx += 1; @@ -1565,7 +1565,7 @@ impl ShieldedContext { // Convert transaction amount into MASP types let (asset_types, masp_amount) = - convert_amount(epoch, token, amount.amount)?; + convert_amount(epoch, token, amount.amount())?; // If there are shielded inputs if let Some(sk) = spending_key { @@ -1620,7 +1620,7 @@ impl ShieldedContext { builder .add_transparent_input(TxOut { asset_type: *asset_type, - value: denom.denominate(&amount), + value: denom.denominate(&amount.amount()), address: script, }) .map_err(builder::Error::TransparentBuild)?; @@ -1638,7 +1638,7 @@ impl ShieldedContext { ovk_opt, pa.into(), *asset_type, - denom.denominate(&amount), + denom.denominate(&amount.amount()), memo.clone(), ) .map_err(builder::Error::SaplingBuild)?; @@ -1659,7 +1659,7 @@ impl ShieldedContext { )); for (denom, asset_type) in MaspDenom::iter().zip(asset_types.iter()) { - let vout = denom.denominate(&amount); + let vout = denom.denominate(&amount.amount()); if vout != 0 { builder .add_transparent_output( @@ -1931,7 +1931,7 @@ impl ShieldedContext { transfer.source.clone(), MaspChange { asset: transfer.token.clone(), - change: -transfer.amount.amount.change(), + change: -transfer.amount.amount().change(), }, )]); diff --git a/sdk/src/queries/router.rs b/sdk/src/queries/router.rs index b632097738..c83d2bb449 100644 --- a/sdk/src/queries/router.rs +++ b/sdk/src/queries/router.rs @@ -1047,25 +1047,25 @@ mod test { let result = TEST_RPC.b1(&client).await.unwrap(); assert_eq!(result, "b1"); - let balance = token::DenominatedAmount { - amount: token::Amount::native_whole(123_000_000), - denom: NATIVE_MAX_DECIMAL_PLACES.into(), - }; + let balance = token::DenominatedAmount::new( + token::Amount::native_whole(123_000_000), + NATIVE_MAX_DECIMAL_PLACES.into(), + ); let result = TEST_RPC.b2i(&client, &balance).await.unwrap(); assert_eq!(result, format!("b2i/{balance}")); - let a1 = token::DenominatedAmount { - amount: token::Amount::native_whole(345), - denom: NATIVE_MAX_DECIMAL_PLACES.into(), - }; - let a2 = token::DenominatedAmount { - amount: token::Amount::native_whole(123_000), - denom: NATIVE_MAX_DECIMAL_PLACES.into(), - }; - let a3 = token::DenominatedAmount { - amount: token::Amount::native_whole(1_000_999), - denom: NATIVE_MAX_DECIMAL_PLACES.into(), - }; + let a1 = token::DenominatedAmount::new( + token::Amount::native_whole(345), + NATIVE_MAX_DECIMAL_PLACES.into(), + ); + let a2 = token::DenominatedAmount::new( + token::Amount::native_whole(123_000), + NATIVE_MAX_DECIMAL_PLACES.into(), + ); + let a3 = token::DenominatedAmount::new( + token::Amount::native_whole(1_000_999), + NATIVE_MAX_DECIMAL_PLACES.into(), + ); let result = TEST_RPC.b3(&client, &a1, &a2, &a3).await.unwrap(); assert_eq!(result, format!("b3/{a1}/{a2}/{a3}")); diff --git a/sdk/src/rpc.rs b/sdk/src/rpc.rs index 0d0d69a79b..c8ec42b26c 100644 --- a/sdk/src/rpc.rs +++ b/sdk/src/rpc.rs @@ -1004,7 +1004,7 @@ pub async fn validate_amount<'a, N: Namada<'a>>( "No denomination found for token: {token}, but --force \ was passed. Defaulting to the provided denomination." ); - Ok(input_amount.denom) + Ok(input_amount.denom()) } else { display_line!( context.io(), @@ -1017,7 +1017,7 @@ pub async fn validate_amount<'a, N: Namada<'a>>( } } }?; - if denom < input_amount.denom && !force { + if denom < input_amount.denom() && !force { display_line!( context.io(), "The input amount contained a higher precision than allowed by \ @@ -1123,7 +1123,7 @@ pub async fn denominate_amount( ); 0.into() }); - DenominatedAmount { amount, denom } + DenominatedAmount::new(amount, denom) } /// Look up the denomination of a token in order to format it diff --git a/sdk/src/signing.rs b/sdk/src/signing.rs index a462c00a00..f6f2bb28e0 100644 --- a/sdk/src/signing.rs +++ b/sdk/src/signing.rs @@ -533,7 +533,7 @@ pub async fn wrap_tx<'a, N: Namada<'a>>( } }; - let total_fee = fee_amount.amount * u64::from(args.gas_limit); + let total_fee = fee_amount.amount() * u64::from(args.gas_limit); let (unshield, unshielding_epoch) = match total_fee .checked_sub(updated_balance) @@ -544,15 +544,15 @@ pub async fn wrap_tx<'a, N: Namada<'a>>( let target = namada_core::types::masp::TransferTarget::Address( fee_payer_address.clone(), ); - let fee_amount = DenominatedAmount { + let fee_amount = DenominatedAmount::new( // NOTE: must unshield the total fee amount, not the // diff, because the ledger evaluates the transaction in // reverse (wrapper first, inner second) and cannot know // ahead of time if the inner will modify the balance of // the gas payer - amount: total_fee, - denom: 0.into(), - }; + total_fee, + 0.into(), + ); match ShieldedContext::::gen_shielded_transfer( context, diff --git a/sdk/src/tx.rs b/sdk/src/tx.rs index 9378615890..8dcd7e066b 100644 --- a/sdk/src/tx.rs +++ b/sdk/src/tx.rs @@ -1951,7 +1951,7 @@ pub async fn build_ibc_transfer<'a>( validate_amount(context, args.amount, &args.token, args.tx.force) .await .expect("expected to validate amount"); - if validated_amount.canonical().denom.0 != 0 { + if validated_amount.canonical().denom().0 != 0 { return Err(Error::Other(format!( "The amount for the IBC transfer should be an integer: {}", validated_amount @@ -1964,7 +1964,7 @@ pub async fn build_ibc_transfer<'a>( let post_balance = check_balance_too_low_err( &args.token, &source, - validated_amount.amount, + validated_amount.amount(), balance_key, args.tx.force, context, @@ -2212,7 +2212,7 @@ pub async fn build_transfer<'a, N: Namada<'a>>( let post_balance = check_balance_too_low_err( &args.token, &source, - validated_amount.amount, + validated_amount.amount(), balance_key, args.tx.force, context, @@ -2234,7 +2234,7 @@ pub async fn build_transfer<'a, N: Namada<'a>>( // TODO Refactor me, we shouldn't rely on any specific token here. (token::Amount::zero(), args.native_token.clone()) } else { - (validated_amount.amount, args.token.clone()) + (validated_amount.amount(), args.token.clone()) }; // Determine whether to pin this transaction to a storage key let key = match &args.target { @@ -2258,7 +2258,7 @@ pub async fn build_transfer<'a, N: Namada<'a>>( Err(Build(builder::Error::InsufficientFunds(_))) => { Err(TxError::NegativeBalanceAfterTransfer( Box::new(source.clone()), - validated_amount.amount.to_string_native(), + validated_amount.amount().to_string_native(), Box::new(token.clone()), )) } diff --git a/shared/src/ledger/native_vp/ibc/context.rs b/shared/src/ledger/native_vp/ibc/context.rs index ffe2faa2f5..86732dbaf3 100644 --- a/shared/src/ledger/native_vp/ibc/context.rs +++ b/shared/src/ledger/native_vp/ibc/context.rs @@ -202,14 +202,15 @@ where token: &Address, amount: DenominatedAmount, ) -> Result<()> { + let amount = amount.to_amount(token, self)?; let src_key = token::balance_key(token, src); let dest_key = token::balance_key(token, dest); let src_bal: Option = self.ctx.read(&src_key)?; let mut src_bal = src_bal.expect("The source has no balance"); - src_bal.spend(&amount.amount); + src_bal.spend(&amount); let mut dest_bal: Amount = self.ctx.read(&dest_key)?.unwrap_or_default(); - dest_bal.receive(&amount.amount); + dest_bal.receive(&amount); self.write(&src_key, src_bal.serialize_to_vec())?; self.write(&dest_key, dest_bal.serialize_to_vec()) @@ -253,15 +254,16 @@ where token: &Address, amount: DenominatedAmount, ) -> Result<()> { + let amount = amount.to_amount(token, self)?; let target_key = token::balance_key(token, target); let mut target_bal: Amount = self.ctx.read(&target_key)?.unwrap_or_default(); - target_bal.receive(&amount.amount); + target_bal.receive(&amount); let minted_key = token::minted_balance_key(token); let mut minted_bal: Amount = self.ctx.read(&minted_key)?.unwrap_or_default(); - minted_bal.receive(&amount.amount); + minted_bal.receive(&amount); self.write(&target_key, target_bal.serialize_to_vec())?; self.write(&minted_key, minted_bal.serialize_to_vec())?; @@ -279,15 +281,16 @@ where token: &Address, amount: DenominatedAmount, ) -> Result<()> { + let amount = amount.to_amount(token, self)?; let target_key = token::balance_key(token, target); let mut target_bal: Amount = self.ctx.read(&target_key)?.unwrap_or_default(); - target_bal.spend(&amount.amount); + target_bal.spend(&amount); let minted_key = token::minted_balance_key(token); let mut minted_bal: Amount = self.ctx.read(&minted_key)?.unwrap_or_default(); - minted_bal.spend(&amount.amount); + minted_bal.spend(&amount); self.write(&target_key, target_bal.serialize_to_vec())?; self.write(&minted_key, minted_bal.serialize_to_vec()) diff --git a/shared/src/ledger/native_vp/masp.rs b/shared/src/ledger/native_vp/masp.rs index 54048f6759..2abba82c22 100644 --- a/shared/src/ledger/native_vp/masp.rs +++ b/shared/src/ledger/native_vp/masp.rs @@ -124,6 +124,9 @@ where ) -> Result { let epoch = self.ctx.get_block_epoch()?; let (transfer, shielded_tx) = self.ctx.get_shielded_action(tx_data)?; + let transfer_amount = transfer + .amount + .to_amount(&transfer.token, &self.ctx.pre())?; let mut transparent_tx_pool = I128Sum::zero(); // The Sapling value balance adds to the transparent tx pool transparent_tx_pool += shielded_tx.sapling_value_balance(); @@ -137,7 +140,7 @@ where let (_transp_asset, transp_amt) = convert_amount( epoch, &transfer.token, - transfer.amount.into(), + transfer_amount, denom, ); @@ -212,7 +215,7 @@ where } if !valid_transfer_amount( out.value, - denom.denominate(&transfer.amount.amount), + denom.denominate(&transfer_amount), ) { return Ok(false); } @@ -220,7 +223,7 @@ where let (_transp_asset, transp_amt) = convert_amount( epoch, &transfer.token, - transfer.amount.amount, + transfer_amount, denom, ); diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index 98ab30a6af..89813becf7 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -406,15 +406,15 @@ where match wrapper.get_tx_fee() { Ok(fees) => { let fees = fees - .apply_precision(&wrapper.fee.token, wl_storage) + .to_amount(&wrapper.fee.token, wl_storage) .map_err(|e| Error::FeeError(e.to_string()))?; - if balance.checked_sub(fees.amount).is_some() { + if balance.checked_sub(fees).is_some() { token_transfer( wl_storage, &wrapper.fee.token, &wrapper.fee_payer(), block_proposer, - fees.amount, + fees, ) .map_err(|e| Error::FeeError(e.to_string())) } else { @@ -534,9 +534,9 @@ where .map_err(|e| Error::FeeError(e.to_string()))?; let fees = fees - .apply_precision(&wrapper.fee.token, wl_storage) + .to_amount(&wrapper.fee.token, wl_storage) .map_err(|e| Error::FeeError(e.to_string()))?; - if balance.checked_sub(fees.amount).is_some() { + if balance.checked_sub(fees).is_some() { Ok(()) } else { Err(Error::FeeError( diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index 2a527d2ff5..38a68ee6c6 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -2498,7 +2498,8 @@ where ) -> Result<(), storage_api::Error> { use namada_core::types::token; - if amount.amount != token::Amount::default() && src != dest { + let amount = amount.to_amount(token, self)?; + if amount != token::Amount::default() && src != dest { let src_key = token::balance_key(token, src); let dest_key = token::balance_key(token, dest); let src_bal = self.read::(&src_key)?; @@ -2506,10 +2507,10 @@ where self.log_string(format!("src {} has no balance", src_key)); unreachable!() }); - src_bal.spend(&amount.amount); + src_bal.spend(&amount); let mut dest_bal = self.read::(&dest_key)?.unwrap_or_default(); - dest_bal.receive(&amount.amount); + dest_bal.receive(&amount); self.write(&src_key, src_bal)?; self.write(&dest_key, dest_bal)?; } @@ -2559,15 +2560,16 @@ where ) -> Result<(), storage_api::Error> { use namada_core::types::token; + let amount = amount.to_amount(token, self)?; let target_key = token::balance_key(token, target); let mut target_bal = self.read::(&target_key)?.unwrap_or_default(); - target_bal.receive(&amount.amount); + target_bal.receive(&amount); let minted_key = token::minted_balance_key(token); let mut minted_bal = self.read::(&minted_key)?.unwrap_or_default(); - minted_bal.receive(&amount.amount); + minted_bal.receive(&amount); self.write(&target_key, target_bal)?; self.write(&minted_key, minted_bal)?; @@ -2587,16 +2589,17 @@ where ) -> Result<(), storage_api::Error> { use namada_core::types::token; + let amount = amount.to_amount(token, self)?; let target_key = token::balance_key(token, target); let mut target_bal = self.read::(&target_key)?.unwrap_or_default(); - target_bal.spend(&amount.amount); + target_bal.spend(&amount); // burn the minted amount let minted_key = token::minted_balance_key(token); let mut minted_bal = self.read::(&minted_key)?.unwrap_or_default(); - minted_bal.spend(&amount.amount); + minted_bal.spend(&amount); self.write(&target_key, target_bal)?; self.write(&minted_key, minted_bal) diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index 9f6c14fb1f..2c0491b4ff 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -137,11 +137,8 @@ async fn test_roundtrip_eth_transfer() -> Result<()> { assert_eq!(dai_supply, Some(transfer_amount)); // let's transfer them back to Ethereum - let amount = token::DenominatedAmount { - amount: transfer_amount, - denom: 0u8.into(), - } - .to_string(); + let amount = + token::DenominatedAmount::new(transfer_amount, 0u8.into()).to_string(); let dai_addr = DAI_ERC20_ETH_ADDRESS.to_string(); let tx_args = vec![ "add-erc20-transfer", @@ -762,10 +759,7 @@ async fn test_wdai_transfer_implicit_unauthorized() -> Result<()> { &albert_addr.to_string(), &bertha_addr.to_string(), &bertha_addr.to_string(), - &token::DenominatedAmount { - amount: token::Amount::from(10_000), - denom: 0u8.into(), - }, + &token::DenominatedAmount::new(token::Amount::from(10_000), 0u8.into()), )?; cmd.exp_string("Transaction is valid.")?; cmd.exp_string("Transaction is invalid.")?; @@ -832,10 +826,7 @@ async fn test_wdai_transfer_established_unauthorized() -> Result<()> { &albert_established_addr.to_string(), &bertha_addr.to_string(), &bertha_addr.to_string(), - &token::DenominatedAmount { - amount: token::Amount::from(10_000), - denom: 0u8.into(), - }, + &token::DenominatedAmount::new(token::Amount::from(10_000), 0u8.into()), )?; cmd.exp_string("Transaction is valid.")?; cmd.exp_string("Transaction is invalid.")?; @@ -882,10 +873,8 @@ async fn test_wdai_transfer_implicit_to_implicit() -> Result<()> { // attempt a transfer from Albert to Bertha that should succeed, as it's // signed with Albert's key let bertha_addr = find_address(&test, BERTHA)?; - let second_transfer_amount = &token::DenominatedAmount { - amount: token::Amount::from(10_000), - denom: 0u8.into(), - }; + let second_transfer_amount = + &token::DenominatedAmount::new(token::Amount::from(10_000), 0u8.into()); let mut cmd = attempt_wrapped_erc20_transfer( &test, &Who::Validator(0), @@ -960,10 +949,8 @@ async fn test_wdai_transfer_implicit_to_established() -> Result<()> { // attempt a transfer from Albert to Bertha that should succeed, as it's // signed with Albert's key - let second_transfer_amount = &token::DenominatedAmount { - amount: token::Amount::from(10_000), - denom: 0u8.into(), - }; + let second_transfer_amount = + &token::DenominatedAmount::new(token::Amount::from(10_000), 0u8.into()); let mut cmd = attempt_wrapped_erc20_transfer( &test, &Who::Validator(0), @@ -1041,10 +1028,8 @@ async fn test_wdai_transfer_established_to_implicit() -> Result<()> { // attempt a transfer from Albert to Bertha that should succeed, as it's // signed with Albert's key - let second_transfer_amount = &token::DenominatedAmount { - amount: token::Amount::from(10_000), - denom: 0u8.into(), - }; + let second_transfer_amount = + &token::DenominatedAmount::new(token::Amount::from(10_000), 0u8.into()); let mut cmd = attempt_wrapped_erc20_transfer( &test, &Who::Validator(0), @@ -1131,10 +1116,8 @@ async fn test_wdai_transfer_established_to_established() -> Result<()> { // attempt a transfer from Albert to Bertha that should succeed, as it's // signed with Albert's key - let second_transfer_amount = &token::DenominatedAmount { - amount: token::Amount::from(10_000), - denom: 0u8.into(), - }; + let second_transfer_amount = + &token::DenominatedAmount::new(token::Amount::from(10_000), 0u8.into()); let mut cmd = attempt_wrapped_erc20_transfer( &test, &Who::Validator(0), diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 9f584e2b24..fd638c4d88 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -435,10 +435,10 @@ fn ledger_txs_and_queries() -> Result<()> { source: find_address(&test, BERTHA).unwrap(), target: find_address(&test, ALBERT).unwrap(), token: find_address(&test, NAM).unwrap(), - amount: token::DenominatedAmount { - amount: token::Amount::native_whole(10), - denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), - }, + amount: token::DenominatedAmount::new( + token::Amount::native_whole(10), + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ), key: None, shielded: None, } @@ -1488,7 +1488,7 @@ fn pos_init_validator() -> Result<()> { NATIVE_MAX_DECIMAL_PLACES.into(), ) .unwrap() - .amount + .amount() }, ) }) diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 35f5216905..1f98ccf444 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -170,14 +170,14 @@ where .0 .insert( StringEncoded::new(sk.ref_to()), - token::DenominatedAmount { - amount: token::Amount::from_uint( + token::DenominatedAmount::new( + token::Amount::from_uint( 3000000, NATIVE_MAX_DECIMAL_PLACES, ) .unwrap(), - denom: NATIVE_MAX_DECIMAL_PLACES.into(), - }, + NATIVE_MAX_DECIMAL_PLACES.into(), + ), ); // invoke `init-genesis-validator` signed by balance key to generate // validator pre-genesis wallet signed genesis txs diff --git a/tx_prelude/src/ibc.rs b/tx_prelude/src/ibc.rs index dfd0430e5b..1415557a23 100644 --- a/tx_prelude/src/ibc.rs +++ b/tx_prelude/src/ibc.rs @@ -66,7 +66,7 @@ impl IbcStorageContext for Ctx { &Address::Internal(InternalAddress::Ibc), target, token, - amount.amount, + amount.to_amount(token, self)?, ) } @@ -76,7 +76,7 @@ impl IbcStorageContext for Ctx { token: &Address, amount: DenominatedAmount, ) -> Result<(), Error> { - burn(self, target, token, amount.amount) + burn(self, target, token, amount.to_amount(token, self)?) } fn log_string(&self, message: String) { diff --git a/tx_prelude/src/token.rs b/tx_prelude/src/token.rs index 009cccb36d..6f0d40f46a 100644 --- a/tx_prelude/src/token.rs +++ b/tx_prelude/src/token.rs @@ -15,7 +15,8 @@ pub fn transfer( token: &Address, amount: DenominatedAmount, ) -> TxResult { - if amount.amount != Amount::default() && src != dest { + let amount = amount.to_amount(token, ctx)?; + if amount != Amount::default() && src != dest { let src_key = token::balance_key(token, src); let dest_key = token::balance_key(token, dest); let src_bal: Option = ctx.read(&src_key)?; @@ -23,9 +24,9 @@ pub fn transfer( log_string(format!("src {} has no balance", src_key)); unreachable!() }); - src_bal.spend(&amount.amount); + src_bal.spend(&amount); let mut dest_bal: Amount = ctx.read(&dest_key)?.unwrap_or_default(); - dest_bal.receive(&amount.amount); + dest_bal.receive(&amount); ctx.write(&src_key, src_bal)?; ctx.write(&dest_key, dest_bal)?; } diff --git a/wasm/checksums.json b/wasm/checksums.json index abda2d7f51..7f022f8902 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,27 +1,27 @@ { - "tx_bond.wasm": "tx_bond.24a1a93ab606bfb3ea280fcb6938eed4735ce98da25fbcfbac034bf466ceea3c.wasm", - "tx_bridge_pool.wasm": "tx_bridge_pool.986638b4c38d6ea6c9cd87a985ace2ebcf2f5e1bd5ec19478a8a134f9543d8f9.wasm", - "tx_change_consensus_key.wasm": "tx_change_consensus_key.6c723f526276e2f3444c85a226daba7ec4fee55514a1817710159fc1206f3178.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.7bafc20d7aaa7acfcdecd09f059b151cf685c309dba8ae1ebf16b21759d0f111.wasm", - "tx_change_validator_metadata.wasm": "tx_change_validator_metadata.06775afbe8c702d52802d0db5c41c0391cf11bacee90214c4ce0f095379f2f0b.wasm", - "tx_claim_rewards.wasm": "tx_claim_rewards.352f86e06f979ff2b36dcad550ff57e3689fb975b6fd4573c5cacd26f2797987.wasm", - "tx_deactivate_validator.wasm": "tx_deactivate_validator.31b43c64a8e1a900002aa35e13a0c3d541ac329a585f6ca3df70870a760c7323.wasm", - "tx_ibc.wasm": "tx_ibc.0aa4d16c8f3a5c4b21dfc3092443b05705225f04bc3a1df8be8177ac73183fef.wasm", - "tx_init_account.wasm": "tx_init_account.eab0b875c424453c26c6420b972657434b23c6024e378ec436573f36c20f3d6b.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.0e130b5904f51dea4d02fc911fb9bf95fb7af42084498dfa436771f41bad0f55.wasm", - "tx_init_validator.wasm": "tx_init_validator.99d21a71edf5f0ca52b775ff2542d5e1b4f28c13ddb8ad875379e0bee9ac7174.wasm", - "tx_reactivate_validator.wasm": "tx_reactivate_validator.b4c855c7b6ca1116cfb7ebb791df963820c1b180c1a95eab7d3c6faec7e734fe.wasm", - "tx_redelegate.wasm": "tx_redelegate.489a6f8618e4d82bc76eb8984b1af0f8aeaf445f8c5b2449d8de71a7c5afbac4.wasm", - "tx_resign_steward.wasm": "tx_resign_steward.b809936ec9fae98eee1c079fc83d2fcc1e085eb5a110057a086261f9731bd3bc.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.da41d83cf22cd03cab83e3f5c0a6a0b054f63a0ed7a0a61ea8be8c5b2c61c2b4.wasm", - "tx_transfer.wasm": "tx_transfer.44cb4d4741d2e581747c9dd1c5c024bd182be7be2b600b275026c4b6af42fc8c.wasm", - "tx_unbond.wasm": "tx_unbond.180f5dd3b64105fc92b2f5949f77b097516ee50772a2d42dc80826cec2ea5e49.wasm", - "tx_unjail_validator.wasm": "tx_unjail_validator.2cea8c20197392eb3b09b19a8e7278ec70c16d85b11e4c20052ed5bf60951004.wasm", - "tx_update_account.wasm": "tx_update_account.54a2eac05115223a7a769830121def96d1feb4959eee37db4586fb717bd3c146.wasm", - "tx_update_steward_commission.wasm": "tx_update_steward_commission.4f5fd4ac948736be8ba4a6c84112a6bee2a3ad3369028a566e573f32059b0f22.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.357ae21d13a6e2a845c6276fca6f42620bef23311ae6abd9ce25c0256cb7b4dd.wasm", - "tx_withdraw.wasm": "tx_withdraw.e7b3b93d8470858b02cd449e330ac6de84929c53e11dff1c4f7030d74a204bfe.wasm", - "vp_implicit.wasm": "vp_implicit.e843bdfe9b211a138cca599c8d3bc6426b38587d10cf81da113c8b9c65544b45.wasm", - "vp_user.wasm": "vp_user.a6c222ecc1c237144ac01a9145369c0fd7176a053985addffc005f8fa00e67fa.wasm", - "vp_validator.wasm": "vp_validator.755de40fa771cd675ec219794c849e8bd4e3b3ec244e34b82925b88ca25b5ab8.wasm" + "tx_bond.wasm": "tx_bond.73c4dcb072809e261d32720f561b3ff2d2ae8f3a9934b43b449c00d1cc87bdae.wasm", + "tx_bridge_pool.wasm": "tx_bridge_pool.60bba2d50db40fd766382c30730b36798b33a8755e709903fce46855e551c117.wasm", + "tx_change_consensus_key.wasm": "tx_change_consensus_key.5aaab8f9b38d76e883087c8d7b44ae35e7e366c0fd69571c97cba81ca330a314.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.f46d30072abedb9215e0a6960e27c43e38d9024c8ce2278dd430c93f5efb69bf.wasm", + "tx_change_validator_metadata.wasm": "tx_change_validator_metadata.44acdf1b6f2a4c6baa683c296ba7ab4d210a394b3716ff44981b0c931ef0046c.wasm", + "tx_claim_rewards.wasm": "tx_claim_rewards.a9388649fef0d061b10a614095f88b260831f3342c134bf2b3a9e32528754eb5.wasm", + "tx_deactivate_validator.wasm": "tx_deactivate_validator.b60b92858676dc6a8bea275054e0c9248650e4124b796b267843b9dd2d53ed20.wasm", + "tx_ibc.wasm": "tx_ibc.0e7068406299fae90c58ed71fbbff4c56c7eedda3bfce15eb8624f87e40e5339.wasm", + "tx_init_account.wasm": "tx_init_account.57d1f2f429c452860242c3683f979b948032c2f646e76659c9f0fda9f5938222.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.924a79b87b7bf853a97ae45f76f9e13b6fb7e94a7116d6d0aeaeda46846cc48d.wasm", + "tx_init_validator.wasm": "tx_init_validator.78fabdb251f3e54617c0a1819677f602a37b0e7c9bb7ebbb3f811e8acdff1f7d.wasm", + "tx_reactivate_validator.wasm": "tx_reactivate_validator.a1b502803bb5ac5d5b1d0d46baee9cbebddad354977aec876c11f79728377c6b.wasm", + "tx_redelegate.wasm": "tx_redelegate.fe7388cc7d385b15b402bf38c7e73fef6a8a1baf58de1bab893eedf947fda89d.wasm", + "tx_resign_steward.wasm": "tx_resign_steward.edce1bf663399964ced3f56f3b753f4c8ded12850359e0754b6a28a90d3d8e69.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.1c34064d4df5c98f75b527429141ec1b9b3ce648862fcc88bf4d27c20684234e.wasm", + "tx_transfer.wasm": "tx_transfer.2f079333e8fd3cc9c03fed8694d44b7e3674b7ce8dc44e5a98ddc1a375969fb4.wasm", + "tx_unbond.wasm": "tx_unbond.5099496c79f0b06581f8d56772f7aaee041175d88d7e338c7599324786a88ecb.wasm", + "tx_unjail_validator.wasm": "tx_unjail_validator.52beb890e48494dd338e283f8acaa5594b223689422cedd92953a05d9d9d7c31.wasm", + "tx_update_account.wasm": "tx_update_account.709e50e66e854e55ebce338ed9b000ac99b1a5f2ff01abd151139ae444b1ba53.wasm", + "tx_update_steward_commission.wasm": "tx_update_steward_commission.199041932781aed4328ca769b3f7c3531c0a9052494c37a8eab8362cc1131753.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.92cf747e8308b20d9b0ab43a646f180849467bf33a172904284533fbbca4a2cf.wasm", + "tx_withdraw.wasm": "tx_withdraw.fbaf3e244e5aa3e6afb52ca9adc49a534790334f4bfafd451c1edb6609e8e891.wasm", + "vp_implicit.wasm": "vp_implicit.e3812f7e6f4966e369adbe66488dffcf6d1cb1afa390edaadf91c7cb44af52aa.wasm", + "vp_user.wasm": "vp_user.33496832f27dcb133427db0fdaee6a0395c88baa9cdf9db14907e107853836a1.wasm", + "vp_validator.wasm": "vp_validator.9e4c36bec5f96570462a336bd805b994c2a48d70bb0e720fd9a936fd3e8f362c.wasm" } \ No newline at end of file diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index 95d1401c54..8f46ceb9b1 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -349,10 +349,10 @@ mod tests { // able to transfer from it tx_env.credit_tokens(&source, &token, amount); - let amount = token::DenominatedAmount { + let amount = token::DenominatedAmount::new( amount, - denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), - }; + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction @@ -584,10 +584,10 @@ mod tests { ) .unwrap(); - let amount = token::DenominatedAmount { + let amount = token::DenominatedAmount::new( amount, - denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), - }; + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -645,10 +645,10 @@ mod tests { ) .unwrap(); - let amount = token::DenominatedAmount { + let amount = token::DenominatedAmount::new( amount, - denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), - }; + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction @@ -716,10 +716,10 @@ mod tests { ) .unwrap(); - let amount = token::DenominatedAmount { + let amount = token::DenominatedAmount::new( amount, - denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), - }; + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { diff --git a/wasm/wasm_source/src/vp_testnet_faucet.rs b/wasm/wasm_source/src/vp_testnet_faucet.rs index d8144d441c..b5c8b3456e 100644 --- a/wasm/wasm_source/src/vp_testnet_faucet.rs +++ b/wasm/wasm_source/src/vp_testnet_faucet.rs @@ -163,10 +163,10 @@ mod tests { // able to transfer from it tx_env.credit_tokens(&source, &token, amount); - let amount = token::DenominatedAmount { + let amount = token::DenominatedAmount::new( amount, - denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), - }; + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -325,10 +325,10 @@ mod tests { // be able to transfer from it tx_env.credit_tokens(&vp_owner, &token, amount); tx_env.commit_genesis(); - let amount = token::DenominatedAmount { + let amount = token::DenominatedAmount::new( amount, - denom: token::NATIVE_MAX_DECIMAL_PLACES.into() - }; + token::NATIVE_MAX_DECIMAL_PLACES.into() + ); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -380,10 +380,10 @@ mod tests { let solution = challenge.solve(); let solution_bytes = solution.serialize_to_vec(); - let amount = token::DenominatedAmount { + let amount = token::DenominatedAmount::new( amount, - denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), - }; + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index 28bfcbdc9f..33928f5583 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -244,10 +244,10 @@ mod tests { ) .unwrap(); - let amount = token::DenominatedAmount { + let amount = token::DenominatedAmount::new( amount, - denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), - }; + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction @@ -299,10 +299,10 @@ mod tests { // be able to transfer from it tx_env.credit_tokens(&vp_owner, &token, amount); - let amount = token::DenominatedAmount { + let amount = token::DenominatedAmount::new( amount, - denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), - }; + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction @@ -357,10 +357,10 @@ mod tests { ) .unwrap(); - let amount = token::DenominatedAmount { + let amount = token::DenominatedAmount::new( amount, - denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), - }; + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -592,10 +592,10 @@ mod tests { // be able to transfer from it tx_env.credit_tokens(&source, &token, amount); - let amount = token::DenominatedAmount { + let amount = token::DenominatedAmount::new( amount, - denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), - }; + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { diff --git a/wasm/wasm_source/src/vp_validator.rs b/wasm/wasm_source/src/vp_validator.rs index 2c104c13d8..e199600f60 100644 --- a/wasm/wasm_source/src/vp_validator.rs +++ b/wasm/wasm_source/src/vp_validator.rs @@ -321,10 +321,10 @@ mod tests { ) .unwrap(); - let amount = token::DenominatedAmount { + let amount = token::DenominatedAmount::new( amount, - denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), - }; + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction @@ -368,10 +368,10 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it tx_env.credit_tokens(&vp_owner, &token, amount); - let amount = token::DenominatedAmount { + let amount = token::DenominatedAmount::new( amount, - denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), - }; + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ); // write the denomination of NAM into storage storage_api::token::write_denom( &mut tx_env.wl_storage, @@ -434,10 +434,10 @@ mod tests { ) .unwrap(); - let amount = token::DenominatedAmount { + let amount = token::DenominatedAmount::new( amount, - denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), - }; + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -683,10 +683,10 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it tx_env.credit_tokens(&source, &token, amount); - let amount = token::DenominatedAmount { + let amount = token::DenominatedAmount::new( amount, - denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), - }; + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { diff --git a/wasm_for_tests/tx_memory_limit.wasm b/wasm_for_tests/tx_memory_limit.wasm index 624f796b52ab40097f01f3f220d3604992f2652e..8f5d647c9e7eeb2cfa7327c7bdbec76ada01092d 100755 GIT binary patch delta 6449 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 ajIS%`1palo)At`+YX|uBXsvBNn)?g0wOK6y delta 1812 zcmcIlO^6&t6t1f7s_x$EUOKojSr@gs<6*}T!yX)$u!nibBpb3x7EdbV5_0MhW!5k! zVGgdus|aZY4|?$6NlAn`7y_dPB-x1K$@qgHCLtP=EG|ec>-V}lA^39%NYneaUe~Ml zec!A1=H$8Q+n-N=^ox??S1+s4WhLDwTUIZQMS4}*N#TT(w0iNxwzG1y?X0@`n7X+( zl0U1bBk#ut;_?mc&6G!fQ^yxbWP$3`4=E>i37LnK^k`G|bL}*#!!sm%lS&C`>E_y4 zWQHa)O>q{Kv{ODfbRmC_qcAYq_f#xJGGS>V({XGmg0Yrt7Q~UI5G$AfCbXns0!scFp$XBtB2p3Nij3P>3A2Y2_c;Nw424F5gH-w&T8?V=ve;}ijH z-IoZ9oDs8H)jO|Oryd>xBj*scdF zko?q#9}K_qUUnh2O%L*bWPb+k>+gJeO4VLfIc}d`-H^4zuN*&IdHq@w7gmnb=f3`` z2}%1iz`{sy7p1lLLdc>9TEw|I^)UJ$a{o8)NBb*TbT7EAP;nvCbw5Kh)8*opbGOvv z&((TFcl2PStVcwR9_(N}B5L$t2kQ}0qX#=`J#hDXXvSKW^5a|TfHWu9)f3W`yVq6g z7-k9@HZEqB$W7PpprEWC8dEGL0gOyduDc29j!TV(yMhg;uirkW<{Vzz-q7?6*YJ?#s(0g ziw|ZYRxxVqLi88Cd44SDNqYAJ2I@0azcnnvyDR_}X@@2cy9s6#rV56KXshi29=6q# zi|f;?n+#ql96qN*#W2S9GaZa65-v#Jui9V=Ai!`tcLQ(~4thhFhZv~~iah8cbb{nQ zgvJQvd4Ta%AdCq!FAJ7YH(_dZE?iwsnV=~!+XW97OpacTqxYi_4QH(QY)tY3vz3z~ zAC9#+Gq(_=s=*XfoK-14G@3iJ8?%W~brS)Z4PQB~xdcR0L*Ttty;xpT(uR5@#Y9Ul z$V|Er`#^Q{(uR8eOSv{W|C#sf5&70=N9MmBW&%H9V`0D9IVXhhoA(gxVZ6(DPh4vG Mc=hN~%bc123kYUXumAu6 diff --git a/wasm_for_tests/tx_read_storage_key.wasm b/wasm_for_tests/tx_read_storage_key.wasm index e85b8aa6e5a2596bdf417be63ea7f067b44cabc0..53b10af81ed9091199174088e807a3d2780cdc20 100755 GIT binary patch delta 4062 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 diff --git a/wasm_for_tests/tx_write.wasm b/wasm_for_tests/tx_write.wasm index 3a4dfe82b5eebfbdddf1e155afa59a087143edaa..23c39cc39fc2e6f4ac331b7d0e0461ef1c6e690a 100755 GIT binary patch delta 4138 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 ZI}o1|Li_~32l0Dy{JUB5gC^4 KM`T#P-v$85tt9CH delta 117 zcmdn-QGEAD@rEsoJtbUA9v)y&WO3wEWS-7w%q+2eT?wNr8{>}cU#b{W85wu9N7XO_ zG1K;_8fL`;#`D{2-Y`dMF`n6eO^oF%t0IHr4xoPSx1t6N3=Aw#>;j}+f!Ga*+YidH LY(FT&`u#QlbRs0= diff --git a/wasm_for_tests/vp_always_true.wasm b/wasm_for_tests/vp_always_true.wasm index 821283ddcafed3335303aaef42be111f1d723e0b..03321c33c5e5cc4784b5b6735b69b65be9bd3dc6 100755 GIT binary patch delta 114 zcmX^4N&MI+@rEsoQ%kt`6qz0OJUqal$TEFl38UEdttE`^Y>Z2`|E*$7Wn^5^o?OES z#7x_hYnUYq7>{kQeZw57#rSahbupH+ta1hn3=Aw#>;j}+f!K|~@dD65?q==wqcSYp KkIJyN+yMYDB_(12 delta 117 zcmX^1N&Ms|@rEsoQ%ks(JUqal$l}PS$UJ>v38UEdttE`^Y>Yd$|E*$7Wn|pZo?OES z#7x_hYnUYq7*B4meZw57#dv1>bupH+tcnbdJAnGRXKNcUFfgz{u?vuP1!6ZKZa*Z$ Lvi*<@Ys(z~*IFfA diff --git a/wasm_for_tests/vp_eval.wasm b/wasm_for_tests/vp_eval.wasm index 44e575b73c5f5bc6aa67a62975ac537cb6855587..ee95a921b35ee91e865fbb924c5b3f9e9ba8878e 100755 GIT binary patch delta 4028 zcmd^CU2Ggz6~5=r?9Qxb*Pb-0+88&vGY-aE8Z`vEF*c#T4QUHXWROMu{xakry0a`d16 zFe4(@e6!nKQ$O%iDrTLhwyZY#&-DK6zFwgx9rMRu&?n8i-Ll~34y&9QIzf+;XO5qs zU8_Cig!^T;lFCtTr6f1zJ5Aq7>ZSYZpE^nRQgu!M9hJj!SY_N{CG~=+;5m<{(2G>* zmm+q;!=&T}F&4Ypy|0j04t6>^`?aXU!I6GB8fWc3Je1Qf{n+2F*X`g<%GWAfWx>d| zTcd1{z_`~}S!{QeZ^CIPlQTP~srTm5D63pZiJXAnu5v1{>gj1R1MSLvTdttowgN5F z&Nsuev^L9{M!uPtp)Wc)T>80biq-?U{#D;Q|HQzf&x0Pg3jIpMyC(c|JmAIoQL;{5jT?3SFLbo!sXx@2+ zzEfaNW*Wj-^q|Y)%xk}*5AX7!(eBtUh#)`~{NCywb);%ZotU(cm{eG5Qce|MQbA8R zdl1GBJwuhqX`19TZ~Ve(+H$AW-fe21D(dQn?oA-o{OoDEeMgc5i4Ip19lDn4(4o4Z zLzkZS_e6OW>TAo~IwhNKH6Nd)+o^0GnWgt_U+zr5>A8kNac2`sRDyq3rBEYilL%T# znY(AHmEaWwgi>KIuh7+$o_#rT)9kNrZ;0*0mUadKg)Z|Hie5GLSQ8zXv5bffQ7k zlH8lT#vs8a93m0B;SrmlDvvWRzHnfmUl?Z*6zxVX>Vdh#9GIg!vX%qN=9hES>y)Fs zdG-yujeN83DBWTnJxVv3FCV4OTqoog9TDZsJ+IPM@WOXrC3O?Tit&v>5y=TDhDscBJ~vPjFJPhELGio=%rmdi zE!7&hB8N!M3lh*QzXribhEs_lf{yKxz5te|rl~gE6B#(Y(fxn=# z^xVNySfPe^%NVPIEHB9^EU;Yo05@VEiFgGk!bk+RHze8V>6%jksX+O%8Tk!u8EG+! z<^AGv)^7q0no5AcvU4J|LodiBLLd20g(70Xwt`3-Syp-NjE@drClCic1+5^lq53*) za!_~X$FI`|tDfx=8F*o$*)0wc`jZ}D8rbY`3??nGu?0L~f&K>IV7qGkW7#hD0NvVx zxETh3!WP6Wv$F@7w)!wZF&*KAddDXt$5@6X<%&xYJq&^^Y1O`vAS%)0Y*a}QgXS1zoF zhsH1~;5CjOg4e@lzx131zK@GWzwNq*08ZB8~m*7!fB2n?tgtdFH;#;(d@)Jg_zt*mZl z@7q+m14g2v^XxplLw<+P5xWP*#z<=zYsJ6?X6;2<^M7abxwk1^%f!h82-C665$kBo z7+m-@v+5Fc$2A!vA;)cMp={wv_8*6ror+N8%#KSmq?czK{OUqPb6>}gpD90CN-CIVf6;MqWwz#P5VX=U%wJavEmUz-RbPKE|Q zhPwB;!+Ed)oKbXc!0aE*Q*U);`CN$XM%?it5^sZO-eK$YhVD2VJ52EWQzI#J{Oqi( z7mB(9G%Bgq$#SqutPdOwEqbeH5&``&)A1eJ1+HCRP_F0euh6i1% z2frhCC!MH=ACeCdo!I}_CW$|%{@5n>_`H1gV7(Y*CW~~g{_~Gyj%Cf>p-i8z7x7Cx SIM%cO&`_p+Xej&OU;YUzw;!$m delta 3943 zcmdT{U2I%O6`nKq?(V(5yY`JjaFe>}-0Kk8{YjlPSqFPl?HDI52vAk=BY^~=QUR@l z6eKG3iCu)-Bx6L4$Prgidq@6z$R+`=K(x=it>=$}Lnm_%9K5I6dq`=J_Q5m!CBt1%=`SnS9 zc%`SDaF1N0q;iy7%FCf_&J4Ut{mxe9nAx+my84}0=}szd#hfRLD&-cH)Wr$>c_k(E z2UHr6B6h+fq~zVA7`xiNKL@E;=jikgqAmv;2Ba6K?K=D^hhO~(eft-9k@B?)S6DFe z?b0Y6Bru=J6Rz_7QtYUd?lb3SX(XK*bVQUf-#A76tGp~Mt>(mO8c81c?*(A3Zdr>w&@MGoTaZPN>$S-(Fu4~Ka)YJhN)=#Tpew0e zP{GslPAN(;%X}>$iGq{WaS%}l-z$~pz^B6|_a+Yq(%Ry0OEm=owsfkkDH7G;^ zg$&gJWn0iK49%I~3_YG@`Ayj@dBkOD=J#jl-f#NQXCn4DRP?TIIZJ$zed~cv6{Or%^OO(x~X>yXlQ-)HM-;T)(flx z?GMLr%hC_;)1p~-)xzop^?sEQi$ATVbX|_1u=oEuWy#UX6i#y=F$v8xM1XO zXmJ1{Zk2JX51pR5XWcbXVHOe8F6L5vJZ&9n4yd3ox#r#Q<`v4_04_z+~CPKR*v)v|+-m)b(g zJ93%MQc^61K3>X>T)MLmICw`jo$^g4@5}8SUZdaC;h-NNq0ge2; z^Bk>KsNAh|k91?(5eL--Wgt=a=jUjXM4~)@j_xdbwm={Sa}YLmet>XHp^hT_s=3^2|T$wEoMu;;;#YC}pK6v`YPVqIaWva0;=<#r`pqs#A|S zI7=JMj<;z%l^K?k@+SvAy>eLUAubXe33_j%)4E%rEmfH#F*h|1{4a?Pj-8_7n1J=~ zyiH#Txt^eIj0388d08~Y3$*Ii;)KpB7q)}hBKiV6#!)`-c))!B0&VeHLVk6DzHQJd zMdg|-jWrx#x;=cQJv`nXB9N|e#6VsB+`iLdP&Rh%2Qal5cn-kPMF1ah6kGW@#Ul1M zCVQON^BH#!la?afv*n6EAri{Mq&#`C!1se&%x0Jf1W-lB@UwC2F$h3* zXWo%`8FQF1k6xzHIq@O{%tw3CKQ7ZfWo~!y5hH8|VN(YJ9|#f%=PHZfSRlwC zO#~Mtc5>7w7lhB3W0aL>$l1v=e4%V)BZ=SCXfriOz43owIqOZY(0RJifmqhY##Y}u^l5ZA`qtoxV&cF!+kC;d_`=2 z2rb|LxbB(1JuPqf1Z1~r9oR(=qbt??JLGebPW@)4r?oKS_+Sox*Jqv`O|6$g?3o@-Ri{VO-}=kH0Y@bS-~a#s diff --git a/wasm_for_tests/vp_memory_limit.wasm b/wasm_for_tests/vp_memory_limit.wasm index cdebb21ec9613e09265ce3bb0497431cac83bf4d..31c584ff3b37984bf1f04a2821238e7227981ce9 100755 GIT binary patch delta 4188 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 YmD^kqV*^3`u3qnYB=GVh!LR@EA056y-v9sr delta 6626 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 Date: Tue, 5 Dec 2023 19:01:01 +0200 Subject: [PATCH 021/216] Dont introduce denominations in the bridge pool transaction WASM. --- tx_prelude/src/token.rs | 25 +++++++++++++++++++++++ wasm/checksums.json | 16 +++++++-------- wasm/wasm_source/src/tx_bridge_pool.rs | 12 +++++------ wasm_for_tests/tx_mint_tokens.wasm | Bin 594890 -> 594890 bytes wasm_for_tests/tx_write_storage_key.wasm | Bin 465107 -> 465107 bytes 5 files changed, 39 insertions(+), 14 deletions(-) diff --git a/tx_prelude/src/token.rs b/tx_prelude/src/token.rs index 6f0d40f46a..c0cf260c18 100644 --- a/tx_prelude/src/token.rs +++ b/tx_prelude/src/token.rs @@ -33,6 +33,31 @@ pub fn transfer( Ok(()) } +/// An undenominated token transfer that can be used in a transaction. +pub fn undenominated_transfer( + ctx: &mut Ctx, + src: &Address, + dest: &Address, + token: &Address, + amount: Amount, +) -> TxResult { + if amount != Amount::default() && src != dest { + let src_key = token::balance_key(token, src); + let dest_key = token::balance_key(token, dest); + let src_bal: Option = ctx.read(&src_key)?; + let mut src_bal = src_bal.unwrap_or_else(|| { + log_string(format!("src {} has no balance", src_key)); + unreachable!() + }); + src_bal.spend(&amount); + let mut dest_bal: Amount = ctx.read(&dest_key)?.unwrap_or_default(); + dest_bal.receive(&amount); + ctx.write(&src_key, src_bal)?; + ctx.write(&dest_key, dest_bal)?; + } + Ok(()) +} + /// Handle a MASP transaction. pub fn handle_masp_tx( ctx: &mut Ctx, diff --git a/wasm/checksums.json b/wasm/checksums.json index 7f022f8902..2228db2ef1 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,26 +1,26 @@ { - "tx_bond.wasm": "tx_bond.73c4dcb072809e261d32720f561b3ff2d2ae8f3a9934b43b449c00d1cc87bdae.wasm", - "tx_bridge_pool.wasm": "tx_bridge_pool.60bba2d50db40fd766382c30730b36798b33a8755e709903fce46855e551c117.wasm", + "tx_bond.wasm": "tx_bond.b67442fe5db80a6e0410501f1ebfd040b4543b529c38167739d7d70de260872b.wasm", + "tx_bridge_pool.wasm": "tx_bridge_pool.2e2bf7cc6b3ea9c297bf83826990113a13a07ffde6096757f3ab668e56a7809b.wasm", "tx_change_consensus_key.wasm": "tx_change_consensus_key.5aaab8f9b38d76e883087c8d7b44ae35e7e366c0fd69571c97cba81ca330a314.wasm", "tx_change_validator_commission.wasm": "tx_change_validator_commission.f46d30072abedb9215e0a6960e27c43e38d9024c8ce2278dd430c93f5efb69bf.wasm", "tx_change_validator_metadata.wasm": "tx_change_validator_metadata.44acdf1b6f2a4c6baa683c296ba7ab4d210a394b3716ff44981b0c931ef0046c.wasm", - "tx_claim_rewards.wasm": "tx_claim_rewards.a9388649fef0d061b10a614095f88b260831f3342c134bf2b3a9e32528754eb5.wasm", + "tx_claim_rewards.wasm": "tx_claim_rewards.86422f251824999bfde58d2732865845f94723e7008d295e472175f4b0f48a9b.wasm", "tx_deactivate_validator.wasm": "tx_deactivate_validator.b60b92858676dc6a8bea275054e0c9248650e4124b796b267843b9dd2d53ed20.wasm", "tx_ibc.wasm": "tx_ibc.0e7068406299fae90c58ed71fbbff4c56c7eedda3bfce15eb8624f87e40e5339.wasm", "tx_init_account.wasm": "tx_init_account.57d1f2f429c452860242c3683f979b948032c2f646e76659c9f0fda9f5938222.wasm", "tx_init_proposal.wasm": "tx_init_proposal.924a79b87b7bf853a97ae45f76f9e13b6fb7e94a7116d6d0aeaeda46846cc48d.wasm", - "tx_init_validator.wasm": "tx_init_validator.78fabdb251f3e54617c0a1819677f602a37b0e7c9bb7ebbb3f811e8acdff1f7d.wasm", + "tx_init_validator.wasm": "tx_init_validator.d4a4e5792274cd87eb2e521639e9654a65062c82faee00efbe986434a023b373.wasm", "tx_reactivate_validator.wasm": "tx_reactivate_validator.a1b502803bb5ac5d5b1d0d46baee9cbebddad354977aec876c11f79728377c6b.wasm", - "tx_redelegate.wasm": "tx_redelegate.fe7388cc7d385b15b402bf38c7e73fef6a8a1baf58de1bab893eedf947fda89d.wasm", + "tx_redelegate.wasm": "tx_redelegate.8b55cc81357a7c023190ca00a4f6837ea5f9e4c5761e4d5fe0e77fbda8c47833.wasm", "tx_resign_steward.wasm": "tx_resign_steward.edce1bf663399964ced3f56f3b753f4c8ded12850359e0754b6a28a90d3d8e69.wasm", "tx_reveal_pk.wasm": "tx_reveal_pk.1c34064d4df5c98f75b527429141ec1b9b3ce648862fcc88bf4d27c20684234e.wasm", - "tx_transfer.wasm": "tx_transfer.2f079333e8fd3cc9c03fed8694d44b7e3674b7ce8dc44e5a98ddc1a375969fb4.wasm", - "tx_unbond.wasm": "tx_unbond.5099496c79f0b06581f8d56772f7aaee041175d88d7e338c7599324786a88ecb.wasm", + "tx_transfer.wasm": "tx_transfer.5fb1fe649946bf84e4e5843399cdc67af74f1434847a83f51804958ebc8cdd13.wasm", + "tx_unbond.wasm": "tx_unbond.93ac88b39aff33274c13c377542f6534352759ee8b37e5f8c3ac05e73c7c563b.wasm", "tx_unjail_validator.wasm": "tx_unjail_validator.52beb890e48494dd338e283f8acaa5594b223689422cedd92953a05d9d9d7c31.wasm", "tx_update_account.wasm": "tx_update_account.709e50e66e854e55ebce338ed9b000ac99b1a5f2ff01abd151139ae444b1ba53.wasm", "tx_update_steward_commission.wasm": "tx_update_steward_commission.199041932781aed4328ca769b3f7c3531c0a9052494c37a8eab8362cc1131753.wasm", "tx_vote_proposal.wasm": "tx_vote_proposal.92cf747e8308b20d9b0ab43a646f180849467bf33a172904284533fbbca4a2cf.wasm", - "tx_withdraw.wasm": "tx_withdraw.fbaf3e244e5aa3e6afb52ca9adc49a534790334f4bfafd451c1edb6609e8e891.wasm", + "tx_withdraw.wasm": "tx_withdraw.f2bd17c776b8f47b51c38ab247e1b65c5546b6466f7df1a3009c8467e61e5c28.wasm", "vp_implicit.wasm": "vp_implicit.e3812f7e6f4966e369adbe66488dffcf6d1cb1afa390edaadf91c7cb44af52aa.wasm", "vp_user.wasm": "vp_user.33496832f27dcb133427db0fdaee6a0395c88baa9cdf9db14907e107853836a1.wasm", "vp_validator.wasm": "vp_validator.9e4c36bec5f96570462a336bd805b994c2a48d70bb0e720fd9a936fd3e8f362c.wasm" diff --git a/wasm/wasm_source/src/tx_bridge_pool.rs b/wasm/wasm_source/src/tx_bridge_pool.rs index 321005d23e..84de025fd4 100644 --- a/wasm/wasm_source/src/tx_bridge_pool.rs +++ b/wasm/wasm_source/src/tx_bridge_pool.rs @@ -21,12 +21,12 @@ fn apply_tx(ctx: &mut Ctx, signed: Tx) -> TxResult { amount, ref payer, } = transfer.gas_fee; - token::transfer( + token::undenominated_transfer( ctx, payer, &bridge_pool::BRIDGE_POOL_ADDRESS, fee_token_addr, - amount.native_denominated(), + amount, )?; log_string("Token transfer succeeded."); let TransferToEthereum { @@ -38,22 +38,22 @@ fn apply_tx(ctx: &mut Ctx, signed: Tx) -> TxResult { // if minting wNam, escrow the correct amount if asset == native_erc20_address(ctx)? { let nam_addr = ctx.get_native_token()?; - token::transfer( + token::undenominated_transfer( ctx, sender, ð_bridge::ADDRESS, &nam_addr, - amount.native_denominated(), + amount, )?; } else { // Otherwise we escrow ERC20 tokens. let token = transfer.token_address(); - token::transfer( + token::undenominated_transfer( ctx, sender, &bridge_pool::BRIDGE_POOL_ADDRESS, &token, - amount.native_denominated(), + amount, )?; } log_string("Escrow succeeded"); diff --git a/wasm_for_tests/tx_mint_tokens.wasm b/wasm_for_tests/tx_mint_tokens.wasm index 761e1a14e90d92dd9527555320a3569913e76ea7..e79d206dfd61c87bbf7707ac7a33db09682c92f7 100755 GIT binary patch delta 95 zcmX@LT;m=Fk@0>WNl9~V*+AkAZ7t#5T6Z**|(>eairV@0H6OD2LJ#7 delta 95 zcmX@LT; o7V)x3DX>jWNS||V*+AkAZ7t#Rv-q6v2RZ^<4Cy+0H5F(2LJ#7 diff --git a/wasm_for_tests/tx_write_storage_key.wasm b/wasm_for_tests/tx_write_storage_key.wasm index 2f6e3946b4e85269bfeb88312d54a0803d537cdb..902d297af7161db3146da0ea1cf3c95588a681ee 100755 GIT binary patch delta 59 zcmcb-QRebSnT8g|EldjCoVki@3M`IK81koUb~9BnvQ1yy&7{T1+J3H^35c12m<5Pg Lx1Z}~W9j;b~9BnvQA&z&7{T1)_$&=35c12m<5Pg Lx1Z}~W9 Date: Wed, 6 Dec 2023 08:32:17 +0200 Subject: [PATCH 022/216] Fixed the redaction of MASP amounts and ensured that correct token type is attached. Integration tests now work. --- sdk/src/tx.rs | 19 ++++++++++--------- wasm_for_tests/tx_mint_tokens.wasm | Bin 594890 -> 594890 bytes wasm_for_tests/tx_write_storage_key.wasm | Bin 465107 -> 465107 bytes 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/sdk/src/tx.rs b/sdk/src/tx.rs index 8dcd7e066b..811bcdc47e 100644 --- a/sdk/src/tx.rs +++ b/sdk/src/tx.rs @@ -2230,12 +2230,13 @@ pub async fn build_transfer<'a, N: Namada<'a>>( // signer. Also, if the transaction is shielded, redact the amount and token // types by setting the transparent value to 0 and token type to a constant. // This has no side-effect because transaction is to self. - let (_amount, token) = if source == masp_addr && target == masp_addr { - // TODO Refactor me, we shouldn't rely on any specific token here. - (token::Amount::zero(), args.native_token.clone()) - } else { - (validated_amount.amount(), args.token.clone()) - }; + let (transparent_amount, transparent_token) = + if source == masp_addr && target == masp_addr { + // TODO Refactor me, we shouldn't rely on any specific token here. + (token::Amount::zero().into(), args.native_token.clone()) + } else { + (validated_amount, args.token.clone()) + }; // Determine whether to pin this transaction to a storage key let key = match &args.target { TransferTarget::PaymentAddress(pa) if pa.is_pinned() => Some(pa.hash()), @@ -2259,7 +2260,7 @@ pub async fn build_transfer<'a, N: Namada<'a>>( Err(TxError::NegativeBalanceAfterTransfer( Box::new(source.clone()), validated_amount.amount().to_string_native(), - Box::new(token.clone()), + Box::new(args.token.clone()), )) } Err(err) => Err(TxError::MaspError(err.to_string())), @@ -2283,8 +2284,8 @@ pub async fn build_transfer<'a, N: Namada<'a>>( let transfer = token::Transfer { source: source.clone(), target: target.clone(), - token: token.clone(), - amount: validated_amount, + token: transparent_token.clone(), + amount: transparent_amount, key: key.clone(), // Link the Transfer to the MASP Transaction by hash code shielded: None, diff --git a/wasm_for_tests/tx_mint_tokens.wasm b/wasm_for_tests/tx_mint_tokens.wasm index e79d206dfd61c87bbf7707ac7a33db09682c92f7..761e1a14e90d92dd9527555320a3569913e76ea7 100755 GIT binary patch delta 95 zcmX@LT; o7V)x3DX>jWNS||V*+AkAZ7t#Rv-q6v2RZ^<4Cy+0H5F(2LJ#7 delta 95 zcmX@LT;m=Fk@0>WNl9~V*+AkAZ7t#5T6Z**|(>eairV@0H6OD2LJ#7 diff --git a/wasm_for_tests/tx_write_storage_key.wasm b/wasm_for_tests/tx_write_storage_key.wasm index 902d297af7161db3146da0ea1cf3c95588a681ee..2f6e3946b4e85269bfeb88312d54a0803d537cdb 100755 GIT binary patch delta 59 zcmcb-QRebSnT8g|EldjCocW4u3M`IK7;>j;b~9BnvQA&z&7{T1)_$&=35c12m<5Pg Lx1Z}~W9 Date: Wed, 6 Dec 2023 11:57:58 +0100 Subject: [PATCH 023/216] Fixes commitment tree validation in masp vp. Adds a workaround to update tree from the tx --- .../lib/node/ledger/shell/finalize_block.rs | 4 +- apps/src/lib/node/ledger/shell/init_chain.rs | 4 +- core/src/ledger/masp_utils.rs | 27 ++++++------ core/src/types/token.rs | 6 +-- shared/src/ledger/native_vp/ibc/context.rs | 7 ++- shared/src/ledger/native_vp/masp.rs | 17 ++++---- shared/src/vm/host_env.rs | 43 ++++++++++++++++++- shared/src/vm/wasm/host_env.rs | 1 + tx_prelude/src/ibc.rs | 7 ++- tx_prelude/src/lib.rs | 18 ++++++++ vm_env/src/lib.rs | 5 +++ wasm/wasm_source/src/tx_transfer.rs | 1 + 12 files changed, 108 insertions(+), 32 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 077feef2ad..ca3972b560 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -27,7 +27,7 @@ use namada::types::dec::Dec; use namada::types::key::tm_raw_hash_to_string; use namada::types::storage::{BlockHash, BlockResults, Epoch, Header}; use namada::types::token::{ - MASP_NOTE_COMMITMENT_ANCHOR_PREFIX, MASP_NOTE_COMMITMENT_TREE, + MASP_NOTE_COMMITMENT_ANCHOR_PREFIX, MASP_NOTE_COMMITMENT_TREE_KEY, }; use namada::types::transaction::protocol::{ ethereum_tx_data_variants, ProtocolTxType, @@ -568,7 +568,7 @@ where // Update the MASP commitment tree anchor if the tree was updated let tree_key = Key::from(MASP.to_db_key()) - .push(&MASP_NOTE_COMMITMENT_TREE.to_owned()) + .push(&MASP_NOTE_COMMITMENT_TREE_KEY.to_owned()) .expect("Cannot obtain a storage key"); if let Some(StorageModification::Write { value }) = self.wl_storage.write_log.read(&tree_key).0 diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 12a18469b2..18d7acd749 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -18,7 +18,7 @@ use namada::types::key::*; use namada::types::storage::KeySeg; use namada::types::time::{DateTimeUtc, TimeZone, Utc}; use namada::types::token::{ - MASP_NOTE_COMMITMENT_ANCHOR_PREFIX, MASP_NOTE_COMMITMENT_TREE, + MASP_NOTE_COMMITMENT_ANCHOR_PREFIX, MASP_NOTE_COMMITMENT_TREE_KEY, }; use namada::vm::validate_untrusted_wasm; use namada_sdk::eth_bridge::EthBridgeStatus; @@ -184,7 +184,7 @@ where CommitmentTree::empty(); let anchor = empty_commitment_tree.root(); let note_commitment_tree_key = Key::from(MASP.to_db_key()) - .push(&MASP_NOTE_COMMITMENT_TREE.to_owned()) + .push(&MASP_NOTE_COMMITMENT_TREE_KEY.to_owned()) .expect("Cannot obtain a storage key"); self.wl_storage .write(¬e_commitment_tree_key, empty_commitment_tree) diff --git a/core/src/ledger/masp_utils.rs b/core/src/ledger/masp_utils.rs index eb9918a707..d15834f7a1 100644 --- a/core/src/ledger/masp_utils.rs +++ b/core/src/ledger/masp_utils.rs @@ -10,11 +10,8 @@ use crate::types::address::MASP; use crate::types::hash::Hash; use crate::types::storage::{BlockHeight, Epoch, Key, KeySeg, TxIndex}; use crate::types::token::{ - Transfer, HEAD_TX_KEY, MASP_NULLIFIERS_KEY_PREFIX, PIN_KEY_PREFIX, - TX_KEY_PREFIX, -}; -use crate::types::token::{ - MASP_NOTE_COMMITMENT_TREE, MASP_NULLIFIERS_KEY_PREFIX, + Transfer, HEAD_TX_KEY, MASP_NOTE_COMMITMENT_TREE_KEY, + MASP_NULLIFIERS_KEY_PREFIX, PIN_KEY_PREFIX, TX_KEY_PREFIX, }; // Writes the nullifiers of the provided masp transaction to storage @@ -37,20 +34,21 @@ fn reveal_nullifiers( Ok(()) } -// Appends the note commitments of the provided transaction to the merkle tree -// and updates the anchor -fn update_note_commitment_tree( +/// Appends the note commitments of the provided transaction to the merkle tree +/// and updates the anchor +/// NOTE: this function is public as a temporary workaround because of an issue +/// when running this function in WASM +pub fn update_note_commitment_tree( ctx: &mut (impl StorageRead + StorageWrite), transaction: &Transaction, ) -> Result<()> { if let Some(bundle) = transaction.sapling_bundle() { if !bundle.shielded_outputs.is_empty() { let tree_key = Key::from(MASP.to_db_key()) - .push(&MASP_NOTE_COMMITMENT_TREE.to_owned()) + .push(&MASP_NOTE_COMMITMENT_TREE_KEY.to_owned()) .expect("Cannot obtain a storage key"); - let mut commitment_tree = ctx - .read::>(&tree_key)? - .ok_or(Error::SimpleMessage( + let mut commitment_tree: CommitmentTree = + ctx.read(&tree_key)?.ok_or(Error::SimpleMessage( "Missing note commitment tree in storage", ))?; @@ -97,7 +95,10 @@ pub fn handle_masp_tx( ); ctx.write(¤t_tx_key, record)?; ctx.write(&head_tx_key, current_tx_idx + 1)?; - update_note_commitment_tree(ctx, shielded)?; + // TODO: temporarily disabled because of the node aggregation issue in WASM. + // Using the host env tx_update_masp_note_commitment_tree or directly the + // update_note_commitment_tree function as a workaround instead + // update_note_commitment_tree(ctx, shielded)?; reveal_nullifiers(ctx, shielded)?; // If storage key has been supplied, then pin this transaction to it diff --git a/core/src/types/token.rs b/core/src/types/token.rs index 1df1ea37da..b5737120d8 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -903,9 +903,9 @@ pub const PIN_KEY_PREFIX: &str = "pin-"; /// Key segment prefix for the nullifiers pub const MASP_NULLIFIERS_KEY_PREFIX: &str = "nullifiers"; /// Key segment prefix for the note commitment merkle tree -pub const MASP_NOTE_COMMITMENT_TREE: &str = "spend_tree"; +pub const MASP_NOTE_COMMITMENT_TREE_KEY: &str = "commitment_tree"; /// Key segment prefix for the note commitment anchor -pub const MASP_NOTE_COMMITMENT_ANCHOR_PREFIX: &str = "spend_anchor"; +pub const MASP_NOTE_COMMITMENT_ANCHOR_PREFIX: &str = "note_commitment_anchor"; /// Last calculated inflation value handed out pub const MASP_LAST_INFLATION_KEY: &str = "last_inflation"; /// The last locked ratio @@ -1132,7 +1132,7 @@ pub fn is_masp_key(key: &Key) -> bool { || key.starts_with(TX_KEY_PREFIX) || key.starts_with(PIN_KEY_PREFIX) || key.starts_with(MASP_NULLIFIERS_KEY_PREFIX) - || key == MASP_NOTE_COMMITMENT_TREE + || key == MASP_NOTE_COMMITMENT_TREE_KEY || key.starts_with(MASP_NOTE_COMMITMENT_ANCHOR_PREFIX))) } diff --git a/shared/src/ledger/native_vp/ibc/context.rs b/shared/src/ledger/native_vp/ibc/context.rs index 1c806afe35..c8d162c000 100644 --- a/shared/src/ledger/native_vp/ibc/context.rs +++ b/shared/src/ledger/native_vp/ibc/context.rs @@ -213,7 +213,12 @@ where } fn handle_masp_tx(&mut self, shielded: &IbcShieldedTransfer) -> Result<()> { - masp_utils::handle_masp_tx(self, &shielded.transfer, &shielded.masp_tx) + masp_utils::handle_masp_tx( + self, + &shielded.transfer, + &shielded.masp_tx, + )?; + masp_utils::update_note_commitment_tree(self, &shielded.masp_tx) } fn mint_token( diff --git a/shared/src/ledger/native_vp/masp.rs b/shared/src/ledger/native_vp/masp.rs index e4b8f3f53b..1ae3b6bdae 100644 --- a/shared/src/ledger/native_vp/masp.rs +++ b/shared/src/ledger/native_vp/masp.rs @@ -19,7 +19,7 @@ use namada_core::types::address::{Address, MASP}; use namada_core::types::storage::{Epoch, Key, KeySeg}; use namada_core::types::token::{ self, is_masp_anchor_key, is_masp_nullifier_key, - MASP_NOTE_COMMITMENT_ANCHOR_PREFIX, MASP_NOTE_COMMITMENT_TREE, + MASP_NOTE_COMMITMENT_ANCHOR_PREFIX, MASP_NOTE_COMMITMENT_TREE_KEY, MASP_NULLIFIERS_KEY_PREFIX, }; use namada_sdk::masp::verify_shielded_tx; @@ -185,7 +185,7 @@ where // Check that the merkle tree in storage has been correctly updated with // the output descriptions cmu let tree_key = Key::from(MASP.to_db_key()) - .push(&MASP_NOTE_COMMITMENT_TREE.to_owned()) + .push(&MASP_NOTE_COMMITMENT_TREE_KEY.to_owned()) .expect("Cannot obtain a storage key"); let mut previous_tree: CommitmentTree = self.ctx.read_pre(&tree_key)?.ok_or(Error::NativeVpError( @@ -330,6 +330,12 @@ where } } + // The transaction must correctly update the note commitment tree + // and the anchor in storage with the new output descriptions + if !self.valid_note_commitment_update(keys_changed, &shielded_tx)? { + return Ok(false); + } + if transfer.target != Address::Internal(Masp) { // Handle transparent output // The following boundary conditions must be satisfied @@ -419,8 +425,6 @@ where // Handle shielded output // The following boundary conditions must be satisfied // 1. Zero transparent output - // 2. The transaction must correctly update the note commitment tree - // and the anchor in storage with the new output descriptions // Satisfies 1. if let Some(transp_bundle) = shielded_tx.transparent_bundle() { @@ -433,11 +437,6 @@ where return Ok(false); } } - - // Satisfies 2 - if !self.valid_note_commitment_update(keys_changed, &shielded_tx)? { - return Ok(false); - } } match transparent_tx_pool.partial_cmp(&I128Sum::zero()) { diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index 0e3fc21929..f95b8a24dd 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -6,6 +6,7 @@ use std::num::TryFromIntError; use borsh::BorshDeserialize; use borsh_ext::BorshSerializeExt; +use masp_primitives::transaction::Transaction; use namada_core::ledger::gas::{ GasMetering, TxGasMeter, MEMORY_ACCESS_GAS_PER_BYTE, }; @@ -2164,6 +2165,41 @@ where } } +/// Appends the new note commitments to the tree in storage +pub fn tx_update_masp_note_commitment_tree( + env: &TxVmEnv, + transaction_ptr: u64, + transaction_len: u64, +) -> TxResult +where + MEM: VmMemory, + DB: storage::DB + for<'iter> storage::DBIter<'iter>, + H: StorageHasher, + CA: WasmCacheAccess, +{ + let _sentinel = unsafe { env.ctx.sentinel.get() }; + let _gas_meter = unsafe { env.ctx.gas_meter.get() }; + let (serialized_transaction, gas) = env + .memory + .read_bytes(transaction_ptr, transaction_len as _) + .map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?; + + tx_charge_gas(env, gas)?; + let transaction = Transaction::try_from_slice(&serialized_transaction) + .map_err(TxRuntimeError::EncodingError)?; + + let mut ctx = env.ctx.clone(); + match masp_utils::update_note_commitment_tree(&mut ctx, &transaction) { + Ok(()) => Ok(HostEnvResult::Success.to_i64()), + Err(_) => { + // NOTE: sentinel for gas errors is already set by the + // update_note_commitment_tree function which in turn calls other + // host functions + Ok(HostEnvResult::Fail.to_i64()) + } + } +} + /// Evaluate a validity predicate with the given input data. pub fn vp_eval( env: &VpVmEnv<'static, MEM, DB, H, EVAL, CA>, @@ -2519,7 +2555,12 @@ where &mut self, shielded: &IbcShieldedTransfer, ) -> Result<(), storage_api::Error> { - masp_utils::handle_masp_tx(self, &shielded.transfer, &shielded.masp_tx) + masp_utils::handle_masp_tx( + self, + &shielded.transfer, + &shielded.masp_tx, + )?; + masp_utils::update_note_commitment_tree(self, &shielded.masp_tx) } fn mint_token( diff --git a/shared/src/vm/wasm/host_env.rs b/shared/src/vm/wasm/host_env.rs index 8c897f1a29..304a27a7f2 100644 --- a/shared/src/vm/wasm/host_env.rs +++ b/shared/src/vm/wasm/host_env.rs @@ -88,6 +88,7 @@ where "namada_tx_ibc_execute" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_ibc_execute), "namada_tx_set_commitment_sentinel" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_set_commitment_sentinel), "namada_tx_verify_tx_section_signature" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_verify_tx_section_signature), + "namada_tx_update_masp_note_commitment_tree" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_update_masp_note_commitment_tree) }, } } diff --git a/tx_prelude/src/ibc.rs b/tx_prelude/src/ibc.rs index 3e9382b59a..38e907a4fe 100644 --- a/tx_prelude/src/ibc.rs +++ b/tx_prelude/src/ibc.rs @@ -53,7 +53,12 @@ impl IbcStorageContext for Ctx { &mut self, shielded: &IbcShieldedTransfer, ) -> Result<(), Error> { - masp_utils::handle_masp_tx(self, &shielded.transfer, &shielded.masp_tx) + masp_utils::handle_masp_tx( + self, + &shielded.transfer, + &shielded.masp_tx, + )?; + masp_utils::update_note_commitment_tree(self, &shielded.masp_tx) } fn mint_token( diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index 29e9881488..fbb6e03a78 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -19,6 +19,7 @@ use std::marker::PhantomData; pub use borsh::{BorshDeserialize, BorshSerialize}; pub use borsh_ext; use borsh_ext::BorshSerializeExt; +use masp_primitives::transaction::Transaction; pub use namada_core::ledger::governance::storage as gov_storage; pub use namada_core::ledger::parameters::storage as parameters_storage; pub use namada_core::ledger::storage::types::encode; @@ -404,3 +405,20 @@ pub fn verify_signatures_of_pks( Ok(HostEnvResult::is_success(valid)) } + +/// Update the masp note commitment tree in storage with the new notes +pub fn update_masp_note_commitment_tree( + transaction: &Transaction, +) -> EnvResult { + // Serialize transaction + let transaction = transaction.serialize_to_vec(); + + let valid = unsafe { + namada_tx_update_masp_note_commitment_tree( + transaction.as_ptr() as _, + transaction.len() as _, + ) + }; + + Ok(HostEnvResult::is_success(valid)) +} diff --git a/vm_env/src/lib.rs b/vm_env/src/lib.rs index 3b2c5c4dcc..f46ad44b94 100644 --- a/vm_env/src/lib.rs +++ b/vm_env/src/lib.rs @@ -133,6 +133,11 @@ pub mod tx { max_signatures_len: u64, ) -> i64; + /// Update the masp note commitment tree with the new notes + pub fn namada_tx_update_masp_note_commitment_tree( + transaction_ptr: u64, + transaction_len: u64, + ) -> i64; } } diff --git a/wasm/wasm_source/src/tx_transfer.rs b/wasm/wasm_source/src/tx_transfer.rs index e473ef1d9e..e521be4f75 100644 --- a/wasm/wasm_source/src/tx_transfer.rs +++ b/wasm/wasm_source/src/tx_transfer.rs @@ -39,6 +39,7 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { .transpose()?; if let Some(shielded) = shielded { token::masp_utils::handle_masp_tx(ctx, &transfer, &shielded)?; + update_masp_note_commitment_tree(&shielded)?; } Ok(()) } From 8841aacf624303cc11f5612531a435363c0100ca Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 6 Dec 2023 15:43:38 +0100 Subject: [PATCH 024/216] Fixes masp vp benchmark --- Cargo.lock | 2 ++ benches/Cargo.toml | 2 ++ benches/native_vps.rs | 32 +++++++++++++++++++++++++++++++- 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index a760f39701..0ed05112b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3896,6 +3896,8 @@ dependencies = [ "borsh", "borsh-ext", "criterion", + "masp_primitives", + "masp_proofs", "namada", "namada_apps", "rand 0.8.5", diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 57de47dba4..b9d15a22f4 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -42,6 +42,8 @@ path = "host_env.rs" [dev-dependencies] namada = { path = "../shared", features = ["testing"] } namada_apps = { path = "../apps", features = ["benches"] } +masp_primitives.workspace = true +masp_proofs.workspace = true borsh.workspace = true borsh-ext.workspace = true criterion = { version = "0.5", features = ["html_reports"] } diff --git a/benches/native_vps.rs b/benches/native_vps.rs index fdaed5d1f9..def2e3c135 100644 --- a/benches/native_vps.rs +++ b/benches/native_vps.rs @@ -4,6 +4,8 @@ use std::rc::Rc; use std::str::FromStr; use criterion::{criterion_group, criterion_main, Criterion}; +use masp_primitives::sapling::Node; +use masp_proofs::bls12_381; use namada::core::ledger::governance::storage::proposal::ProposalType; use namada::core::ledger::governance::storage::vote::{ StorageProposalVote, VoteType, @@ -40,14 +42,18 @@ use namada::ledger::native_vp::{Ctx, NativeVp}; use namada::ledger::pgf::PgfVp; use namada::ledger::pos::PosVP; use namada::namada_sdk::masp::verify_shielded_tx; +use namada::namada_sdk::masp_primitives::merkle_tree::CommitmentTree; use namada::namada_sdk::masp_primitives::transaction::Transaction; use namada::proof_of_stake; use namada::proof_of_stake::KeySeg; use namada::proto::{Code, Section, Tx}; -use namada::types::address::InternalAddress; +use namada::types::address::{InternalAddress, MASP}; use namada::types::eth_bridge_pool::{GasFee, PendingTransfer}; use namada::types::masp::{TransferSource, TransferTarget}; use namada::types::storage::{Epoch, TxIndex}; +use namada::types::token::{ + MASP_NOTE_COMMITMENT_ANCHOR_PREFIX, MASP_NOTE_COMMITMENT_TREE_KEY, +}; use namada::types::transaction::governance::{ InitProposalData, VoteProposalData, }; @@ -502,6 +508,30 @@ fn setup_storage_for_masp_verification( ); shielded_ctx.shell.execute_tx(&shield_tx); shielded_ctx.shell.wl_storage.commit_tx(); + + // Update the anchor in storage + let tree_key = namada::core::types::storage::Key::from(MASP.to_db_key()) + .push(&MASP_NOTE_COMMITMENT_TREE_KEY.to_owned()) + .expect("Cannot obtain a storage key"); + let updated_tree: CommitmentTree = shielded_ctx + .shell + .wl_storage + .read(&tree_key) + .unwrap() + .unwrap(); + let anchor_key = namada::core::types::storage::Key::from(MASP.to_db_key()) + .push(&MASP_NOTE_COMMITMENT_ANCHOR_PREFIX.to_owned()) + .expect("Cannot obtain a storage key") + .push(&namada::core::types::hash::Hash( + bls12_381::Scalar::from(updated_tree.root()).to_bytes(), + )) + .expect("Cannot obtain a storage key"); + shielded_ctx + .shell + .wl_storage + .write(&anchor_key, ()) + .unwrap(); + shielded_ctx.shell.commit(); let signed_tx = match bench_name { From c09875fc14edb00f77995bee1aae6b27f25be880 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 6 Dec 2023 15:48:58 +0100 Subject: [PATCH 025/216] Updates comment --- shared/src/ledger/native_vp/masp.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/ledger/native_vp/masp.rs b/shared/src/ledger/native_vp/masp.rs index 1ae3b6bdae..d506da9c8f 100644 --- a/shared/src/ledger/native_vp/masp.rs +++ b/shared/src/ledger/native_vp/masp.rs @@ -331,7 +331,7 @@ where } // The transaction must correctly update the note commitment tree - // and the anchor in storage with the new output descriptions + // in storage with the new output descriptions if !self.valid_note_commitment_update(keys_changed, &shielded_tx)? { return Ok(false); } From 6276330eb964716011811540e734a979a1e428a3 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 6 Dec 2023 12:20:44 +0100 Subject: [PATCH 026/216] Changelog #2244 --- .../unreleased/bug-fixes/2244-spend-description-validation.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/2244-spend-description-validation.md diff --git a/.changelog/unreleased/bug-fixes/2244-spend-description-validation.md b/.changelog/unreleased/bug-fixes/2244-spend-description-validation.md new file mode 100644 index 0000000000..c531894144 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/2244-spend-description-validation.md @@ -0,0 +1,2 @@ +- Updates masp tx to store the notes and the native vp to validate them and the + anchors. ([\#2244](https://github.com/anoma/namada/pull/2244)) \ No newline at end of file From fdf769359820a2eb90ee4eeccb2d32f448619ace Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 6 Dec 2023 18:02:03 +0100 Subject: [PATCH 027/216] `update_allowed_conversions` to publish the updated convert anchor --- core/src/ledger/masp_conversions.rs | 15 ++++++++++++++- core/src/types/token.rs | 5 ++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/core/src/ledger/masp_conversions.rs b/core/src/ledger/masp_conversions.rs index 371e8a5ec9..c3d32ae180 100644 --- a/core/src/ledger/masp_conversions.rs +++ b/core/src/ledger/masp_conversions.rs @@ -16,7 +16,7 @@ use crate::ledger::storage_api::token::read_denom; use crate::ledger::storage_api::{StorageRead, StorageWrite}; use crate::types::address::{Address, MASP}; use crate::types::dec::Dec; -use crate::types::storage::Epoch; +use crate::types::storage::{Epoch, Key, KeySeg}; use crate::types::token; use crate::types::token::MaspDenom; use crate::types::uint::Uint; @@ -211,6 +211,7 @@ where use rayon::prelude::ParallelSlice; use crate::types::address; + use crate::types::token::MASP_CONVERT_ANCHOR_PREFIX; // The derived conversions will be placed in MASP address space let masp_addr = MASP; @@ -454,6 +455,18 @@ where // obtained wl_storage.storage.conversion_state.tree = FrozenCommitmentTree::merge(&tree_parts); + // Publish the new anchor in storage + let anchor_key = Key::from(MASP.to_db_key()) + .push(&MASP_CONVERT_ANCHOR_PREFIX.to_owned()) + .expect("Cannot obtain a storage key") + .push(&crate::types::hash::Hash( + masp_primitives::bls12_381::Scalar::from( + wl_storage.storage.conversion_state.tree.root(), + ) + .to_bytes(), + )) + .expect("Cannot obtain a storage key"); + wl_storage.write(&anchor_key, ())?; // Add purely decoding entries to the assets map. These will be // overwritten before the creation of the next commitment tree diff --git a/core/src/types/token.rs b/core/src/types/token.rs index b5737120d8..086f6b23ac 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -906,6 +906,8 @@ pub const MASP_NULLIFIERS_KEY_PREFIX: &str = "nullifiers"; pub const MASP_NOTE_COMMITMENT_TREE_KEY: &str = "commitment_tree"; /// Key segment prefix for the note commitment anchor pub const MASP_NOTE_COMMITMENT_ANCHOR_PREFIX: &str = "note_commitment_anchor"; +/// Key segment prefix for the convert anchor +pub const MASP_CONVERT_ANCHOR_PREFIX: &str = "convert_anchor"; /// Last calculated inflation value handed out pub const MASP_LAST_INFLATION_KEY: &str = "last_inflation"; /// The last locked ratio @@ -1133,7 +1135,8 @@ pub fn is_masp_key(key: &Key) -> bool { || key.starts_with(PIN_KEY_PREFIX) || key.starts_with(MASP_NULLIFIERS_KEY_PREFIX) || key == MASP_NOTE_COMMITMENT_TREE_KEY - || key.starts_with(MASP_NOTE_COMMITMENT_ANCHOR_PREFIX))) + || key.starts_with(MASP_NOTE_COMMITMENT_ANCHOR_PREFIX) + || key.starts_with(MASP_CONVERT_ANCHOR_PREFIX))) } /// Check if the given storage key is a masp nullifier key From 3ef207a4808947e80b7d0262b1e061a2ca21426e Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 6 Dec 2023 19:08:45 +0100 Subject: [PATCH 028/216] Masp VP verifies the anchors of convert descriptions --- shared/src/ledger/native_vp/masp.rs | 37 ++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/shared/src/ledger/native_vp/masp.rs b/shared/src/ledger/native_vp/masp.rs index d506da9c8f..4694642ab0 100644 --- a/shared/src/ledger/native_vp/masp.rs +++ b/shared/src/ledger/native_vp/masp.rs @@ -19,8 +19,8 @@ use namada_core::types::address::{Address, MASP}; use namada_core::types::storage::{Epoch, Key, KeySeg}; use namada_core::types::token::{ self, is_masp_anchor_key, is_masp_nullifier_key, - MASP_NOTE_COMMITMENT_ANCHOR_PREFIX, MASP_NOTE_COMMITMENT_TREE_KEY, - MASP_NULLIFIERS_KEY_PREFIX, + MASP_CONVERT_ANCHOR_PREFIX, MASP_NOTE_COMMITMENT_ANCHOR_PREFIX, + MASP_NOTE_COMMITMENT_TREE_KEY, MASP_NULLIFIERS_KEY_PREFIX, }; use namada_sdk::masp::verify_shielded_tx; use ripemd::Digest as RipemdDigest; @@ -263,6 +263,35 @@ where Ok(true) } + + // Check that the convert descriptions anchors of a transaction are valid + fn valid_convert_descriptions_anchor( + &self, + transaction: &Transaction, + ) -> Result { + for description in transaction + .sapling_bundle() + .map_or(&vec![], |bundle| &bundle.shielded_converts) + { + let anchor_key = Key::from(MASP.to_db_key()) + .push(&MASP_CONVERT_ANCHOR_PREFIX.to_owned()) + .expect("Cannot obtain a storage key") + .push(&namada_core::types::hash::Hash( + description.anchor.to_bytes(), + )) + .expect("Cannot obtain a storage key"); + + // Check if the provided anchor was published before + if !self.ctx.has_key_pre(&anchor_key)? { + tracing::debug!( + "Convert description refers to an invalid anchor" + ); + return Ok(false); + } + } + + Ok(true) + } } impl<'a, DB, H, CA> NativeVp for MaspVp<'a, DB, H, CA> @@ -309,7 +338,8 @@ where // the containing wrapper transaction's fee // amount Satisfies 1. // 3. The spend descriptions' anchors are valid - // 4. The nullifiers provided by the transaction have not been + // 4. The convert descriptions's anchors are valid + // 5. The nullifiers provided by the transaction have not been // revealed previously (even in the same tx) and no unneeded // nullifier is being revealed by the tx if let Some(transp_bundle) = shielded_tx.transparent_bundle() { @@ -324,6 +354,7 @@ where } if !(self.valid_spend_descriptions_anchor(&shielded_tx)? + && self.valid_convert_descriptions_anchor(&shielded_tx)? && self.valid_nullifiers_reveal(keys_changed, &shielded_tx)?) { return Ok(false); From 01caceb2a7ea45f459d9476f73a7588d3aba8173 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 7 Dec 2023 12:57:11 +0100 Subject: [PATCH 029/216] Improves masp vp keys verification --- core/src/types/token.rs | 36 +++++++--- shared/src/ledger/native_vp/masp.rs | 107 +++++++++++++++++++++++----- 2 files changed, 113 insertions(+), 30 deletions(-) diff --git a/core/src/types/token.rs b/core/src/types/token.rs index 086f6b23ac..8081e5ab7d 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -1127,6 +1127,13 @@ pub fn is_denom_key(token_addr: &Address, key: &Key) -> bool { /// Check if the given storage key is a masp key pub fn is_masp_key(key: &Key) -> bool { + matches!(&key.segments[..], + [DbKeySeg::AddressSeg(addr), ..] if *addr == MASP + ) +} + +/// Check if the given storage key is allowed to be touched by a masp transfer +pub fn is_masp_allowed_key(key: &Key) -> bool { matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(key)] if *addr == MASP @@ -1134,27 +1141,34 @@ pub fn is_masp_key(key: &Key) -> bool { || key.starts_with(TX_KEY_PREFIX) || key.starts_with(PIN_KEY_PREFIX) || key.starts_with(MASP_NULLIFIERS_KEY_PREFIX) - || key == MASP_NOTE_COMMITMENT_TREE_KEY - || key.starts_with(MASP_NOTE_COMMITMENT_ANCHOR_PREFIX) - || key.starts_with(MASP_CONVERT_ANCHOR_PREFIX))) + || key == MASP_NOTE_COMMITMENT_TREE_KEY)) } -/// Check if the given storage key is a masp nullifier key -pub fn is_masp_nullifier_key(key: &Key) -> bool { +/// Check if the given storage key is a masp tx prefix key +pub fn is_masp_tx_prefix_key(key: &Key) -> bool { matches!(&key.segments[..], - [DbKeySeg::AddressSeg(addr), + [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix), .. - ] if *addr == MASP && prefix == MASP_NULLIFIERS_KEY_PREFIX) + ] if *addr == MASP && prefix.starts_with(TX_KEY_PREFIX)) } -/// Check if the given storage key is a masp anchor key -pub fn is_masp_anchor_key(key: &Key) -> bool { +/// Check if the given storage key is a masp tx pin key +pub fn is_masp_tx_pin_key(key: &Key) -> bool { matches!(&key.segments[..], - [DbKeySeg::AddressSeg(addr), + [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix), .. - ] if *addr == MASP && prefix == MASP_NOTE_COMMITMENT_ANCHOR_PREFIX) + ] if *addr == MASP && prefix.starts_with(PIN_KEY_PREFIX)) +} + +/// Check if the given storage key is a masp nullifier key +pub fn is_masp_nullifier_key(key: &Key) -> bool { + matches!(&key.segments[..], + [DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + .. + ] if *addr == MASP && prefix == MASP_NULLIFIERS_KEY_PREFIX) } /// Obtain the storage key for the last locked ratio of a token diff --git a/shared/src/ledger/native_vp/masp.rs b/shared/src/ledger/native_vp/masp.rs index 4694642ab0..3b7c96c04c 100644 --- a/shared/src/ledger/native_vp/masp.rs +++ b/shared/src/ledger/native_vp/masp.rs @@ -16,11 +16,13 @@ use namada_core::ledger::vp_env::VpEnv; use namada_core::proto::Tx; use namada_core::types::address::InternalAddress::Masp; use namada_core::types::address::{Address, MASP}; -use namada_core::types::storage::{Epoch, Key, KeySeg}; +use namada_core::types::storage::{BlockHeight, Epoch, Key, KeySeg, TxIndex}; use namada_core::types::token::{ - self, is_masp_anchor_key, is_masp_nullifier_key, + self, is_masp_allowed_key, is_masp_key, is_masp_nullifier_key, + is_masp_tx_pin_key, is_masp_tx_prefix_key, Transfer, HEAD_TX_KEY, MASP_CONVERT_ANCHOR_PREFIX, MASP_NOTE_COMMITMENT_ANCHOR_PREFIX, - MASP_NOTE_COMMITMENT_TREE_KEY, MASP_NULLIFIERS_KEY_PREFIX, + MASP_NOTE_COMMITMENT_TREE_KEY, MASP_NULLIFIERS_KEY_PREFIX, PIN_KEY_PREFIX, + TX_KEY_PREFIX, }; use namada_sdk::masp::verify_shielded_tx; use ripemd::Digest as RipemdDigest; @@ -179,7 +181,6 @@ where // the tree and anchor in storage fn valid_note_commitment_update( &self, - keys_changed: &BTreeSet, transaction: &Transaction, ) -> Result { // Check that the merkle tree in storage has been correctly updated with @@ -218,20 +219,6 @@ where return Ok(false); } - // Check that no anchor was published (this is to be done by the - // protocol) - if keys_changed - .iter() - .filter(|key| is_masp_anchor_key(key)) - .count() - != 0 - { - tracing::debug!( - "The transaction revealed one or more MASP anchor keys" - ); - return Ok(false); - } - Ok(true) } @@ -292,6 +279,84 @@ where Ok(true) } + + /// Check the correctness of the general storage changes that pertain to all + /// types of masp transfers + fn valid_state( + &self, + keys_changed: &BTreeSet, + transfer: &Transfer, + transaction: &Transaction, + ) -> Result { + // Check that the transaction didn't write unallowed masp keys, nor + // multiple variations of the same key prefixes + let mut found_tx_key = false; + let mut found_pin_key = false; + for key in keys_changed.iter().filter(|key| is_masp_key(key)) { + if !is_masp_allowed_key(key) { + return Ok(false); + } else if is_masp_tx_prefix_key(key) { + if found_tx_key { + return Ok(false); + } else { + found_tx_key = true; + } + } else if is_masp_tx_pin_key(key) { + if found_pin_key { + return Ok(false); + } else { + found_pin_key = true; + } + } + } + + // Validate head tx + let head_tx_key = Key::from(MASP.to_db_key()) + .push(&HEAD_TX_KEY.to_owned()) + .expect("Cannot obtain a storage key"); + let pre_head: u64 = self.ctx.read_pre(&head_tx_key)?.unwrap_or(0); + let post_head: u64 = self.ctx.read_post(&head_tx_key)?.unwrap_or(0); + + if post_head != pre_head + 1 { + return Ok(false); + } + + // Validate tx key + let current_tx_key = Key::from(MASP.to_db_key()) + .push(&(TX_KEY_PREFIX.to_owned() + &pre_head.to_string())) + .expect("Cannot obtain a storage key"); + match self + .ctx + .read_post::<(Epoch, BlockHeight, TxIndex, Transfer, Transaction)>( + ¤t_tx_key, + )? { + Some(( + epoch, + height, + tx_index, + storage_transfer, + storage_transaction, + )) if (epoch == self.ctx.get_block_epoch()? + && height == self.ctx.get_block_height()? + && tx_index == self.ctx.get_tx_index()? + && &storage_transfer == transfer + && &storage_transaction == transaction) => {} + _ => return Ok(false), + } + + // Validate pin key + if let Some(key) = &transfer.key { + let pin_key = Key::from(MASP.to_db_key()) + .push(&(PIN_KEY_PREFIX.to_owned() + key)) + .expect("Cannot obtain a storage key"); + match self.ctx.read_post::(&pin_key)? { + Some(tx_idx) if tx_idx == pre_head => (), + _ => return Ok(false), + } + } + + Ok(true) + } } impl<'a, DB, H, CA> NativeVp for MaspVp<'a, DB, H, CA> @@ -314,6 +379,10 @@ where // The Sapling value balance adds to the transparent tx pool transparent_tx_pool += shielded_tx.sapling_value_balance(); + if !self.valid_state(keys_changed, &transfer, &shielded_tx)? { + return Ok(false); + } + if transfer.source != Address::Internal(Masp) { // Handle transparent input // Note that the asset type is timestamped so shields @@ -363,7 +432,7 @@ where // The transaction must correctly update the note commitment tree // in storage with the new output descriptions - if !self.valid_note_commitment_update(keys_changed, &shielded_tx)? { + if !self.valid_note_commitment_update(&shielded_tx)? { return Ok(false); } From 751b0dfa5c5e8b9063eed8d055fb0d03b18d0cf9 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 7 Dec 2023 13:38:48 +0100 Subject: [PATCH 030/216] Removes redundant masp dependency from bench crate --- Cargo.lock | 1 - benches/Cargo.toml | 1 - benches/native_vps.rs | 2 +- core/src/ledger/masp_conversions.rs | 6 +++++- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0ed05112b6..3f90455135 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3897,7 +3897,6 @@ dependencies = [ "borsh-ext", "criterion", "masp_primitives", - "masp_proofs", "namada", "namada_apps", "rand 0.8.5", diff --git a/benches/Cargo.toml b/benches/Cargo.toml index b9d15a22f4..b467253e2d 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -43,7 +43,6 @@ path = "host_env.rs" namada = { path = "../shared", features = ["testing"] } namada_apps = { path = "../apps", features = ["benches"] } masp_primitives.workspace = true -masp_proofs.workspace = true borsh.workspace = true borsh-ext.workspace = true criterion = { version = "0.5", features = ["html_reports"] } diff --git a/benches/native_vps.rs b/benches/native_vps.rs index def2e3c135..29c7e16ff1 100644 --- a/benches/native_vps.rs +++ b/benches/native_vps.rs @@ -4,8 +4,8 @@ use std::rc::Rc; use std::str::FromStr; use criterion::{criterion_group, criterion_main, Criterion}; +use masp_primitives::bls12_381; use masp_primitives::sapling::Node; -use masp_proofs::bls12_381; use namada::core::ledger::governance::storage::proposal::ProposalType; use namada::core::ledger::governance::storage::vote::{ StorageProposalVote, VoteType, diff --git a/core/src/ledger/masp_conversions.rs b/core/src/ledger/masp_conversions.rs index c3d32ae180..1ca4a2c6ef 100644 --- a/core/src/ledger/masp_conversions.rs +++ b/core/src/ledger/masp_conversions.rs @@ -5,6 +5,8 @@ use std::collections::BTreeMap; use borsh::{BorshDeserialize, BorshSerialize}; use borsh_ext::BorshSerializeExt; use masp_primitives::asset_type::AssetType; +#[cfg(feature = "wasm-runtime")] +use masp_primitives::bls12_381; use masp_primitives::convert::AllowedConversion; use masp_primitives::merkle_tree::FrozenCommitmentTree; use masp_primitives::sapling::Node; @@ -16,7 +18,9 @@ use crate::ledger::storage_api::token::read_denom; use crate::ledger::storage_api::{StorageRead, StorageWrite}; use crate::types::address::{Address, MASP}; use crate::types::dec::Dec; -use crate::types::storage::{Epoch, Key, KeySeg}; +use crate::types::storage::Epoch; +#[cfg(feature = "wasm-runtime")] +use crate::types::storage::{Key, KeySeg}; use crate::types::token; use crate::types::token::MaspDenom; use crate::types::uint::Uint; From 463bfc8cdd2f1c64936261c6ffcf83841c67de91 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 7 Dec 2023 13:40:53 +0100 Subject: [PATCH 031/216] Fixes conversion anchor handling --- apps/src/lib/node/ledger/shell/init_chain.rs | 17 +++++++- core/src/ledger/masp_conversions.rs | 19 +++++---- core/src/types/token.rs | 2 +- shared/src/ledger/native_vp/masp.rs | 45 +++++++++++--------- 4 files changed, 53 insertions(+), 30 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 18d7acd749..710974dd88 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -18,7 +18,8 @@ use namada::types::key::*; use namada::types::storage::KeySeg; use namada::types::time::{DateTimeUtc, TimeZone, Utc}; use namada::types::token::{ - MASP_NOTE_COMMITMENT_ANCHOR_PREFIX, MASP_NOTE_COMMITMENT_TREE_KEY, + MASP_CONVERT_ANCHOR_KEY, MASP_NOTE_COMMITMENT_ANCHOR_PREFIX, + MASP_NOTE_COMMITMENT_TREE_KEY, }; use namada::vm::validate_untrusted_wasm; use namada_sdk::eth_bridge::EthBridgeStatus; @@ -200,6 +201,20 @@ where .write(&commitment_tree_anchor_key, ()) .unwrap(); + // Init masp convert anchor + let convert_anchor_key = Key::from(MASP.to_db_key()) + .push(&MASP_CONVERT_ANCHOR_KEY.to_owned()) + .expect("Cannot obtain a storage key"); + self.wl_storage.write( + &convert_anchor_key, + namada::core::types::hash::Hash( + bls12_381::Scalar::from( + self.wl_storage.storage.conversion_state.tree.root(), + ) + .to_bytes(), + ), + )?; + // Set the initial validator set response.validators = self .get_abci_validator_updates(true, |pk, power| { diff --git a/core/src/ledger/masp_conversions.rs b/core/src/ledger/masp_conversions.rs index 1ca4a2c6ef..6983324f2b 100644 --- a/core/src/ledger/masp_conversions.rs +++ b/core/src/ledger/masp_conversions.rs @@ -215,7 +215,7 @@ where use rayon::prelude::ParallelSlice; use crate::types::address; - use crate::types::token::MASP_CONVERT_ANCHOR_PREFIX; + use crate::types::token::MASP_CONVERT_ANCHOR_KEY; // The derived conversions will be placed in MASP address space let masp_addr = MASP; @@ -459,18 +459,19 @@ where // obtained wl_storage.storage.conversion_state.tree = FrozenCommitmentTree::merge(&tree_parts); - // Publish the new anchor in storage + // Update the anchor in storage let anchor_key = Key::from(MASP.to_db_key()) - .push(&MASP_CONVERT_ANCHOR_PREFIX.to_owned()) - .expect("Cannot obtain a storage key") - .push(&crate::types::hash::Hash( - masp_primitives::bls12_381::Scalar::from( + .push(&MASP_CONVERT_ANCHOR_KEY.to_owned()) + .expect("Cannot obtain a storage key"); + wl_storage.write( + &anchor_key, + crate::types::hash::Hash( + bls12_381::Scalar::from( wl_storage.storage.conversion_state.tree.root(), ) .to_bytes(), - )) - .expect("Cannot obtain a storage key"); - wl_storage.write(&anchor_key, ())?; + ), + )?; // Add purely decoding entries to the assets map. These will be // overwritten before the creation of the next commitment tree diff --git a/core/src/types/token.rs b/core/src/types/token.rs index 8081e5ab7d..e5d6c3706b 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -907,7 +907,7 @@ pub const MASP_NOTE_COMMITMENT_TREE_KEY: &str = "commitment_tree"; /// Key segment prefix for the note commitment anchor pub const MASP_NOTE_COMMITMENT_ANCHOR_PREFIX: &str = "note_commitment_anchor"; /// Key segment prefix for the convert anchor -pub const MASP_CONVERT_ANCHOR_PREFIX: &str = "convert_anchor"; +pub const MASP_CONVERT_ANCHOR_KEY: &str = "convert_anchor"; /// Last calculated inflation value handed out pub const MASP_LAST_INFLATION_KEY: &str = "last_inflation"; /// The last locked ratio diff --git a/shared/src/ledger/native_vp/masp.rs b/shared/src/ledger/native_vp/masp.rs index 3b7c96c04c..7bae49dde2 100644 --- a/shared/src/ledger/native_vp/masp.rs +++ b/shared/src/ledger/native_vp/masp.rs @@ -20,7 +20,7 @@ use namada_core::types::storage::{BlockHeight, Epoch, Key, KeySeg, TxIndex}; use namada_core::types::token::{ self, is_masp_allowed_key, is_masp_key, is_masp_nullifier_key, is_masp_tx_pin_key, is_masp_tx_prefix_key, Transfer, HEAD_TX_KEY, - MASP_CONVERT_ANCHOR_PREFIX, MASP_NOTE_COMMITMENT_ANCHOR_PREFIX, + MASP_CONVERT_ANCHOR_KEY, MASP_NOTE_COMMITMENT_ANCHOR_PREFIX, MASP_NOTE_COMMITMENT_TREE_KEY, MASP_NULLIFIERS_KEY_PREFIX, PIN_KEY_PREFIX, TX_KEY_PREFIX, }; @@ -256,24 +256,31 @@ where &self, transaction: &Transaction, ) -> Result { - for description in transaction - .sapling_bundle() - .map_or(&vec![], |bundle| &bundle.shielded_converts) - { - let anchor_key = Key::from(MASP.to_db_key()) - .push(&MASP_CONVERT_ANCHOR_PREFIX.to_owned()) - .expect("Cannot obtain a storage key") - .push(&namada_core::types::hash::Hash( - description.anchor.to_bytes(), - )) - .expect("Cannot obtain a storage key"); - - // Check if the provided anchor was published before - if !self.ctx.has_key_pre(&anchor_key)? { - tracing::debug!( - "Convert description refers to an invalid anchor" - ); - return Ok(false); + if let Some(bundle) = transaction.sapling_bundle() { + if !bundle.shielded_converts.is_empty() { + let anchor_key = Key::from(MASP.to_db_key()) + .push(&MASP_CONVERT_ANCHOR_KEY.to_owned()) + .expect("Cannot obtain a storage key"); + let expected_anchor = self + .ctx + .read_pre::(&anchor_key)? + .ok_or(Error::NativeVpError( + native_vp::Error::SimpleMessage("Cannot read storage"), + ))?; + + for description in &bundle.shielded_converts { + // Check if the provided anchor matches the current + // conversion tree's one + if namada_core::types::hash::Hash( + description.anchor.to_bytes(), + ) != expected_anchor + { + tracing::debug!( + "Convert description refers to an invalid anchor" + ); + return Ok(false); + } + } } } From 8b28d2798d088e794d4ea6491bc9e1223cae8f7f Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Thu, 7 Dec 2023 10:02:32 +0200 Subject: [PATCH 032/216] Fixed the formatting of update account in the test vectors. --- sdk/src/signing.rs | 94 +++++++++--------- ...3F779253B53A9A0BD62FD3DF8F6FCF4AF1E145.bin | Bin 17018 -> 17018 bytes ...BF9BC175A70082068C8785BDDBF49DCCA4BE66.bin | Bin 7448 -> 7448 bytes ...C89879834677F926130D56EB5E4067728EB5CF.bin | Bin 10382 -> 10382 bytes ...BE50202AF004DFD895A721EDA284D96B253ACC.bin | Bin 7448 -> 7448 bytes ...3CEA99A9BC66B2BAB03D4ABA1AE57B12596061.bin | Bin 7448 -> 7448 bytes ...F136D4522BAFC99E0E81FD54A28A38E8419CFC.bin | Bin 20518 -> 20518 bytes ...B0464F2130123149D5691B85C35AABC49FA2BD.bin | Bin 7448 -> 7448 bytes ...6D2CF14C7F9E90A8588BA75DA0D2860F36E2CB.bin | Bin 9649 -> 9649 bytes ...396732D2EBF77728D2772EB251123DF2CEF6A1.bin | Bin 9941 -> 9941 bytes ...20D5BB3FDEB1199EF712C059C80C4DBF6F3ED8.bin | Bin 9941 -> 9941 bytes ...5CF776D5DE7C9153D67B7FDF22ED6F35558738.bin | Bin 24494 -> 24494 bytes ...DBA1FDB2515DF33D43611F2860B8D3C178B474.bin | Bin 7448 -> 7448 bytes ...6C8B75587507CA52981F48FD1FC9C5BE0BEE43.bin | Bin 15257 -> 15257 bytes 14 files changed, 48 insertions(+), 46 deletions(-) diff --git a/sdk/src/signing.rs b/sdk/src/signing.rs index f6f2bb28e0..59c32c09dd 100644 --- a/sdk/src/signing.rs +++ b/sdk/src/signing.rs @@ -695,14 +695,14 @@ fn other_err(string: String) -> Result { } /// Represents the transaction data that is displayed on a Ledger device -#[derive(Default, Serialize, Deserialize)] +#[derive(Default, Serialize, Deserialize, Debug, Clone)] pub struct LedgerVector { - blob: String, - index: u64, - name: String, - output: Vec, - output_expert: Vec, - valid: bool, + pub blob: String, + pub index: u64, + pub name: String, + pub output: Vec, + pub output_expert: Vec, + pub valid: bool, } /// Adds a Ledger output line describing a given transaction amount and address @@ -1291,9 +1291,25 @@ pub async fn to_ledger_vector<'a>( Error::from(EncodingError::Conversion(err.to_string())) })?; - tv.name = "Update_VP_0".to_string(); + tv.name = "Update_Account_0".to_string(); + tv.output.extend(vec![ + format!("Type : Update Account"), + format!("Address : {}", update_account.addr), + ]); + tv.output.extend( + update_account + .public_keys + .iter() + .map(|k| format!("Public key : {}", k)), + ); + if update_account.threshold.is_some() { + tv.output.extend(vec![format!( + "Threshold : {}", + update_account.threshold.unwrap() + )]) + } - match &update_account.vp_code_hash { + let vp_code_data = match &update_account.vp_code_hash { Some(hash) => { let extra = tx .get_section(hash) @@ -1308,45 +1324,31 @@ pub async fn to_ledger_vector<'a>( } else { HEXLOWER.encode(&extra.code.hash().0) }; - tv.output.extend(vec![ - format!("Type : Update VP"), - format!("Address : {}", update_account.addr), - ]); - tv.output.extend( - update_account - .public_keys - .iter() - .map(|k| format!("Public key : {}", k)), - ); - if update_account.threshold.is_some() { - tv.output.extend(vec![format!( - "Threshold : {}", - update_account.threshold.unwrap() - )]) - } - tv.output.extend(vec![format!("VP type : {}", vp_code)]); - - tv.output_expert - .extend(vec![format!("Address : {}", update_account.addr)]); - tv.output_expert.extend( - update_account - .public_keys - .iter() - .map(|k| format!("Public key : {}", k)), - ); - if update_account.threshold.is_some() { - tv.output_expert.extend(vec![format!( - "Threshold : {}", - update_account.threshold.unwrap() - )]) - } - tv.output_expert.extend(vec![format!( - "VP type : {}", - HEXLOWER.encode(&extra.code.hash().0) - )]); + Some((vp_code, extra.code.hash())) } - None => (), + None => None, }; + if let Some((vp_code, _)) = &vp_code_data { + tv.output.extend(vec![format!("VP type : {}", vp_code)]); + } + tv.output_expert + .extend(vec![format!("Address : {}", update_account.addr)]); + tv.output_expert.extend( + update_account + .public_keys + .iter() + .map(|k| format!("Public key : {}", k)), + ); + if let Some(threshold) = update_account.threshold { + tv.output_expert + .extend(vec![format!("Threshold : {}", threshold,)]) + } + if let Some((_, extra_code_hash)) = vp_code_data { + tv.output_expert.extend(vec![format!( + "VP type : {}", + HEXLOWER.encode(&extra_code_hash.0) + )]); + } } else if code_sec.tag == Some(TX_TRANSFER_WASM.to_string()) { let transfer = Transfer::try_from_slice( &tx.data() diff --git a/test_fixtures/masp_proofs/51EF94B13138B91DB3081F95193F779253B53A9A0BD62FD3DF8F6FCF4AF1E145.bin b/test_fixtures/masp_proofs/51EF94B13138B91DB3081F95193F779253B53A9A0BD62FD3DF8F6FCF4AF1E145.bin index 8b21aac70d3fd50b05a84cd005d0e440b1cbefc4..9f29ac6dd3f842170a4028092beba668f8a6412b 100644 GIT binary patch delta 3073 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 PehllV+W){QA+|b+gurll52aS8!3tK>yhLbxc zKp?YGZysmI5h<$bE88eF^nM%s$^x(NiD;JU2Wb#)Vuq8OCP86s=jK{$!7%egrol9|pJW4G;B?cVaUJ zAC@h0pD%2cx_tBEejMj|jR!pafeJ?LZda4%7QoR^vppxI0)GbAynkk?zrIn5xx1U8f(XFTyY<1o$9Rm?wz?euCnvE=?{BmFpQ zF+mDl>&d`r6724f;Xm}3HK1pZS{Vgy_-CLeXR>Oh$h49Bz9s7NBNt} z@<8PokL1){$hLA?Q}tkMmsPJ#TS@##j@C5exPS)bT6fjo+#4l26x_we3{1g~>YN{`dLd!tdjzs@F)^UO<7N0x@Oh%l7`ep_{6IR3t=ZMoQXbA5%xoW4h>smM5DIfmp zWp;(;ex#gRC*{;^<7=0@9ipPAoj1E*cVgfot5R)c&MT|8{A{~0$Ulz2%iP3t4vG`o zSSi_fgoo*v@K~1}d`$)tcwmK3le4fkl>$+y{db{?&BcOik9HJIrMqt!_3dWe$1#ZP z(%xjplfcFGp8lkTtLOEUB}W2NC3O{|m(oqIOG6x-Dq6Cp;u$)?NHvr*}(oX}%czLi662w?sLn71bHzxkH; z4TLW43{RYKfFx9zizOEvf)tAj~Idk!BURv5A>g z+%gajiSxa(Q6!Wj17JDzC)?i>)5r!^pK92H2I>+p^$R!>#H@~+w`LNS)|QY6n@b4s zkSthK^nM=pti&m8SC_}kfEX4?Az%rN6mQMFV$aYr?NmSje=S0^CYRx&7}%Fl)i7ay zmVX1pj~bAqK`yb1xPJVSd>+CCX5^KB)Qr>vL~!%Bl4GXC2GiMoFGoVOKYIy%vPu!r znZ?J(vYJ`GMgrqKIk#Hvv1vqGHY0%?rre3#)dWvCW~T~Nz<=@ zRa_ZU@!Jc`xr*I?#!h&oH#+@Vg_iq8jN<6q!V{l~cDIB-|9D9=tl3XG_}`Z*;mqEv zN2w^TI9%aE>@V}2WJy6avwx7F%Jkx>R*z(5g|O-}HeB9IngeL0ZL5rLYtl-rUCSj- zxSQqK)8D-QNGWOqg!NR4TsE*o zdM6Z}1T(K%eG{ghl11QHQ38#4-UkXSms(}(BH8PK?mj{#cFE70`=@m6a;G`U5gN8* zUcB=AHK%>t@I@)FF?7LH(|<#9)G+pujm&X9?R-DrXr4(^(w_~&wM}?`&62c%J(7yW zFkiSsEM)zOxmC_lLJj9}sE(ejuK`F=x>u5J!JUfw6}y(#(ik396OX&2-v0umViCD1 z*XKX2R8H=C5R{F-B>Y)Zspv#sOGIz0xDqBZem4?N1}-;kE_JMsS6~gWugJ3reoSEIx=z{2z?xB zPseRdMRAsTQG-4P&qVsS9n68T&Zdk8NF_}@2nvW;Q%FvxH{_>(xEUF7lqtvNWi2>C zJ3E$TbGXN4^hYgF>NvKzqf^X4DXR;1$y2$wPlB%t4xJSOs2(9<*;0#zs!Wp6GEb2l zcFz|L_>!~$e>;l^?0#~pZX!HTNAtY_swb2zi4JRR+w-><3LG7jrlR-aeK~8wku{Fp z3v@yFgPe+jAv!OA8hf%cvKvNWn+n%Im$9Z_N7m2elai|T@mwKO`DpMr(E!&Yr7*~q z4#@zwF5$KF3PoFqH=K%n4FQKFORvSyjW`Hmx%7DJ{Q|Kf&0~A?E8z7PlbaKUSu6|Q zYJ?5p8*V=TL6z@&mIHcVGy`2^0LeTzpT>JH^xMRDhk}}a9!07pQ?}j=>Pi;kmAk

IvZy^mIO?x?F@kHN>Pv7Nl(|>V&efoOZuVs4QX) zZ(sV3YEZd<4bMt-X`2hOBxBm7b18{)8{C*G(U#Ch@cFjK=RH1y8yTn*WWP48iTdnS zyOv26obcd7Jm`2cTg*bR6I{nAF26gIX=k#5{~aInjlnd92Ci)30kbA+uKy^a3Dxy%B_nOqZebLXU?y zvv0ujer{c`^maIj_Z^;?$zE==YZLSWAX2VwrFzoC@I0Qt84#XOh0zWb659E{l^Zpz zi`b$$w6lK}B?Eu9&KncX4W{c8Jr^wgn(!XE{#I|%OEt^{i9gO&^|NgQWg`ExnVO{5 zNKmbld;;&M;Vbr*e1Gb!ZqKmuVy;S0TM ziFG6wL9)c^eW+J^Wkie&+(@PZ!@{9(S&7c_$ow8(ll*_L08ernWYH1|3MsD--{RKR zs_NsHRxe&ev5)quV`Cej^|FRSeJr?xIGpz&u#;!avF|_~$+6q=052*`Shxm?x%1mT z(Jw&QdvWvs=GyK#E^qqN@-~&Ko{OW0zLL>_czZ43SJ5FoI~gqC6;cY;rdq}^=3eHW zgbIxmTdRLb%~1aWAWt>GJRG-X6jX0c#O{LHk_o%n)r%c<+Z2_L*e!bt*cq+G4lmaz3rV8i=7GEGS{UqSC_33WgzmgNq1;XwBdg}CcI|c0G z#>8I}`--z7WKB1L;<3|%{ox)lBk zrt9)x66!l1!nmU?(QML0*l|iA>G2la*MJc9NrQr>Nj^2i7l_*jnQ7v)K&&L&OE>mJ z@(Jx;ZeY5t>%*~Z`zNp4T$eB1jc_~jH3I^}d`PR~P?Qu}f8>G=ZLSa0!z(%b{mFw3 z<+YreNDlA zpQZb=kV^LTbf|S6n8EI+?o0ypf{!N2@vnc}Tsx&K`S}H8X*o}{vSQiZu=a29lFV2D z|L_({$1DEF7$0^=250;Zj@s8P=CQE2YwQ!B)?Z1>phIwX*oYFK5Ce&q))Eq{; z?p0TvNPkU z2-)a$D6@YSB?Eth)89^1So>pF^2pBqgUjqnvLu^YUL{Pre-h-xn!Z7?g+pT#%vc-A zUz^6jpi*m?v)(c}`Lzihu%uU}{!$7KC7|gSWuF)aK^;{Sx!=blyVA(3#ahYjjX2$T zh}IkzGcsFlvzu#HCnxndZ!-;5^qNd1qDy`($3SO|eQbXPBH<_Jr|wFA2Q?kG)I9O< zj)NE5PyRT+-Uy5TDm}ys452lA3Q|L;p8-A@rE<;>%ozJ8&PejW!_kf}0Rem|{g-Lsp z|7T`9Gm?a4RZeQ!Xc|*LLYzMYFdIF5+8g141tSt;KAjv;KVC( z)8T5c}C2kinNM1U_7N7CA$B<5t=wA z)CSuY3*V$S18)veyakOEZGi)gL7z1h?1YstYTlS5emCcT0@%pLLM=He(%stXhmZ(4 z0pVL?a~fxmOuvsoM)^^U8hPI%r$v%WLTi7BUHXIbrbO~uhD$U(X4 zhsf{qRH*76m&Z<*%F~9NRMW<#wqlLv+|p3YtvlM&eWravj9pYc-r+J@13mqLTVO_O zLf9weDfrerW(h4~fa&sIc@#r01azGF)*-ACGbZoAQw`5 YCRN$cNHgWa`!LU8nQY__1e4?(G>7!;O8@`> diff --git a/test_fixtures/masp_proofs/6DD5A788D36258E9D5FEF454DBC89879834677F926130D56EB5E4067728EB5CF.bin b/test_fixtures/masp_proofs/6DD5A788D36258E9D5FEF454DBC89879834677F926130D56EB5E4067728EB5CF.bin index 94f997688ba6215afe7dba75785ff44eb9cd9d65..a6c29f7bcfb2833dcac7b6b01d331f3e046de60b 100644 GIT binary patch delta 1781 zcmVlwq795dOO7lkXcrVFTb*N74Md19?bZ_j>yb zoI1nlhYty*v9v7E4{Bt1FOWE>=Od767!Gsy(Md)W`X<;ZH+|`uCmog;uLqT7zXDdw ztA#9mN((tS%jlH5OxoZh42P%`6Yq##IvmT>9_+J`9Hat&cy9G}ZGDJ$--fqqQqy6F z(Fbl!K5JJK|FSe}BNl@*7O*!MIf?w%lMP^0ipUu~OtzAtL<*Cc(EeSpwW98$mXt&FbrJIQ6AqBFeLM*s3M zQ5fmj3P}U!P=z+8-$JRPpx>`~T|u{W_FI3cHI@o8l4`g1YbO1pW|J`+DUqEBlgc7M ze~rt2q0)Flke^m=4N=;xeC7*paqgqYxpnJ>#5^j0;aK>mH~U8KU5H za9Rr?d8*ggMlOy!H%EechLj8fkx z(x|HBPS9yYvb?(lgf2x4hwD-mRo>@&37q??*Qm&=US2uByP?nEo zf1)l4_yn`>2Pu@Qz;Ke|Fz0#AlWqf0>WP zTAxgQ?gj!z%0|^OOLWBJImomQuG3M|qlp?Qqj)C+CByYp4G3QZ;cm0*EI&vgL<0XQ znaK-6&G{ohGI;SL*b<~2$XkOolpDM8+WbQlrv8fKLr5u2`yU+}$c;Up6f3wob0)?0~ z3Q<9bxE6hO!P=x_n&-<0m_mIyN5m3faY0V%MygHA$s61-Yq_Wt_}~TY431C?wWRnN zHD2s^I?`vY+uG895Fm)J<%`BA2)uy_832V6mW~CL{SjoxXzc=Wt`RbkjYU@S20zx? zB-bl(oiKaA^g%wp^sz57eN^z)9V+k zOcm~U%i;y0&CT2k&e3&*D$#s1NBL~86v-J=#oe(6WU&H$TiZn7fBhLTQY71_)<)i> zSlf$d^_bx1){aZCuuE=bE|?k$ZGqm6!B9aLEFUk*@`w2Ibf3Vo(R|lH4YI)=1jcla z>V2sQ-ag}tl&GtG+U0m6ou-8=P^mZz$17rbUGb&g_mwg|rYN#dshnTgn5-8ghf4)1<*6T%G8YVwti|ko_&>f;oAm5|Z>uWWz~Zos5a{pq z*l%DB2~lQobsD{^8Bx6B2i42#^h>{_Kgz=UPe~21ZQ``Se=`1+N{z;R(tvOEnnsc| z{t>7it5W^es*Vo``5<lRP+-@-PLt2YXsrL}SbVN& zaPhO*$C##OVBOFYn*_ftbca5CJks(wiR1vVmN>hpVC{{ X5UK-{UMMJ&UMLg@1^@s600000QAc*_ delta 1781 zcmV01dlgq($?`4`1(Mk1fPhW+et8(iWAIj~hTB zmtG_VU0DnA5)4bW0^Z6A?c(Kc&ReSVZTePy`4R=nlkXcrVXc+}MF&oZh;neDWQ9_y z3kc(aR$u|fzW#X0u`eE;zXThNkRj1%zy0js$*l(n+Qn$i9#pe>57X81G51*lZ~{FA zBksn&pncVLX5A}g2xPa%%E#6|;Xz)aa-72{-x0Hs9Hat&J;0iDgJqrQ!CD&=QexKe zxWzc=dZ`J%asP<9bUH27@O!q7A+@8M&SyZ@AFMveT((r=$E4p+|m34vjYI5Yh`>Z}}T@Qn%lMP|Qus%-msacOueQ%w^=PA!&M;$kep5xeaCx(_g*EmoXk|8i>I) zQZ@DdM0v+Tl6!|jl98jYFcwuNgZMh4bXoipGWwh;1zHl43X?G#DUqEBlgc7M zf2wNW>Q)0lr&offB)k>ZN+Z<`AyrH;z-XHN&wz|j-54<+^(CKdaQP+HMm3mp=dn={ zv;xqy=erIfOmx}8dZu4k^*Z!AS0wuGA}*_j1L!D40Xg!~RhFSKPJve<>Jc8A(nfA-C+i& z!%b_eF1{s%=m3pzdv#8OC-k=u5N z1yI~!UTgplQPaMC77Rb-?zG-qe+$`38;Ho4rk;&bU5>2M;p#F}`dPDU6V2d1X70!s z*t6r;Wv+2)yjJ#)!tTG!)FDm_bI~D9-bR$zPjJcg`Z9wB3l}KmVm#@hI*C!VwNx&T zZ_6xl8q^^_gB!bP0kdGd z-;#M!!}G@8r;y63i#lI^C0Znv0BkD`54fXn%(oH@8P+@a8j6bJZm_s1N%|7;>rY?}t_-DKOzu-$$68U;r;7eP0oSg0(_w4S z-f}a4SKw{~*$Pok@^7nQD7@OrP|8k#f=7e@?_S@;K;HC7fUh+ zcfujwQ{Xo10-Zp3P(=@FZlpb6v$&TyX7*=?C_5WFN26`cDN7LU6e+>T?V*gMhY(M0n}5Lk;u- zAa9xoy?SF!3wN6B1qEDUUz!MDJ$07GU+|{qZZH%_h_hD^^a3CaKB5t3c|t~^B25ZB z%a^8y7@_@OyPeRxfT`&7`$pFO#KY2-`b2flNw$?S~WRFJ_gfyLL8h(UkP_^CD=z;Y`cnU zV4r(NNZ|L~la6b<~>b!MKc;_mx4YR~diosFI;WjYoS}i!dFw1e*+3TgD~c znhr#9*q*%!p;a8)G2`0F;6}_jF$K?3gF3lt0nw~H7U~hJHwHesYRVPp+2pmnNQ>sI z{z@(Tdr}-HBE>sLsJ+6U#yMrHIOY%P|4j(tm~s~KV2BURjD@g23;O}AwypRa)H9pV zKi_|7j1KF(9w`*tFU~SIt)gKiW;)WW67{%Q4LYw>fM7J46+7i$4$Mtac^pMMN6c-D zD5Xvh^?e1nhXtzmj<=Zen=*Nz+5hQEzAOU?XUcF;&w(pV)U0OiGPVaHjIM+2yoiNF zlv_coJZfO-H@BQkt7nn9nc*X2fHCuP_LF}T#_(E%{_xmf;;aJ}YP-;f3?tD$cK5&{(+C#bYZt5G6z7ykV}HqG+SP)@L{of! zbt;z`&QN>)vCFzPC^x7A`=y}+{M^=eIUKv0MwEZZJr9IwuhX;a@ljE6`*@?6kncvq zuXKr#@n%%#WKLXID&hRrk$a~h+OAoqOPCgg?9FA@_O=n22sVW8oq-UPg&E}sPj9n) YoRAW1^uXI&j?01?AOSIh4U^;?G~+erL;wH) delta 1002 zcmVwgD`TA3m5A&fV<}mCf zJbQMuPYuYh(n{0Cx;vYjUODc%8+%mgtiE~QG26J(AND5(tYNxC4sm`c;tZwdemyL) z`4O=TaDsLwajbtUanGc`>9fWQ&;5_VURD0*=BtmCJwbn1D9IuE?Q;#qX}P@waWK?W zt3K5Uh8LLh&GjfYx*~S>g z9-t!*=Ehn(^CjT8SqA4L%O9v`2qWS>qZ@s$@|Ehan5@vSy0&+LaCsdcYl{O?=yG4? z)pxU%lal}dE_KD^Gg5J;G=IniJ_xC?Fp;?35ck7|i( zgHu4#v|A7K9;Ac0FlunaZHflI7^35JovxI%2L*ykj=WvMpnN|tRsE+GYi}lw_2mz2 zG|$k1&^WJQhxGX?oFfIpa^y=MkX(Luv#SW@!WQbpILR`Upv=*tiBp`;m!zn{pB^r+ zU~7N+h#`z`nIAGlH|LnAr<7;ALUCHCVH{xQd8U1J^t!@Y!$XZ4c0b|x+M&5Y2sqUw z6AxohieK+*br5HgAvHx>Yyz*f*FYxP=W({c_SzHh;rfqj*=+@=FF0O}Aea$c20dd{ zNzp2!60IOmbMs^{?n>*2J}VKCeNActLl%F&^JYeLu!>Om*o{GFFtypUpid|P#!+Ne zqF*XH`NXO(TQ*lJ0~zQ^7R>GFEt$U0flUc<62`t)zVi#dTqlLd2c~O4~%kbDON)|IU2FrON?zUgXdIkm8Lda|{ YsSi{VHUvmtKNi8DPhU`<3zOs=G=6i~LjV8( diff --git a/test_fixtures/masp_proofs/8032CA7B951C625E43F48AEBD53CEA99A9BC66B2BAB03D4ABA1AE57B12596061.bin b/test_fixtures/masp_proofs/8032CA7B951C625E43F48AEBD53CEA99A9BC66B2BAB03D4ABA1AE57B12596061.bin index b983254f6b6b56c0574db0d1880140d8aa00309c..35ad1144f5ae9ceb2d39109c0fb49d526813f161 100644 GIT binary patch delta 1002 zcmVAj9{i(W}YX`wErt$l&62>rvo}Vhtx`6a^(@r9bj3= zth`i=moFUD3WO6d1V`&Eo{G5Ep>~P-=LD}rFDvx8h9D8}R_0x{Fmc#PV%={vsJbTN z+)>IYZMkO-HE~k~B7@6hH#ivtu=V7J4)7rJ=q_uCF9Bh*EPl&s%Qb}Urps1-@Qg|VOiXF#jm5k=m13-&+r z*PER`E0yN_Ae{dd_v=qDOJBJKLYD**t$1iPf$-Q+}_kM(^v*;IPe{B>$m~ zfB7J`*ar!~l|2OjIZNhCR*YO^GpL|uL_hONm9&JvM?r`W)~s(1PVVDrR7Wb@@WP~F zRM6;CCANEe@=q8$aV#0t--;U*^BT{;gdh}MQ>EN|g(#j^K!imLOxR{tDsEQrxw zpNijGNf^aqfePCIBPAUz&$X!17fdKlmL+6P8v6}t)IFfs|4vokF83zDYO-bML2gE= zHh1+9AK=%1M$+=g%ZltM#xFn-$<7cYm zG_w5sJf!zKTk zb9me9UB?M<>k0_Yfa}|gE4}eAGR8~uk$g5(VNYhqsRnziTY@kRvct)5p5i`^lutt- z6naRqbW9C~iMw&FBC;dA6Ya%O+&y3*NY~&c+B1J;!4<7O-baBFf-uxqbXp1{T+H0! z;iQiS<9HEP3h7V;qc;l2v2?H1lxyTtH;$!2yxNg32bHLL1Z)Qs_H_iS2|2u?#u%(b z$ydsKS-@jVdnId45*`#5W-?-06c1YZ2Hy(D>BP*#8*%OHQWM6G`35^)CHATDdK-4k zr^$bthB6-zZU_f3Pz5vI%tkTH(E4eDN0(vlPaOFHb+K!(tq2)g=gFF8SGWW})T3Z! zx3$I#U&jFTXqYJ$T81vT2-Nz7$4l?wTy!uE=VV@<9VE6e&w`dB0*d{Zk9OkLBy)Om zBjNhmC2?esI5w8`%PH!m;Qft6<8_Gk4rYJfe(t}{nze<;1Gc#EK-7R@kviD;O8<@JA(mut?&+b-QSw}hK()8rE(i@R`{RR) z%J!Gq=_S%PlBnPx# diff --git a/test_fixtures/masp_proofs/8A79BF5E0292339E70287DB626F136D4522BAFC99E0E81FD54A28A38E8419CFC.bin b/test_fixtures/masp_proofs/8A79BF5E0292339E70287DB626F136D4522BAFC99E0E81FD54A28A38E8419CFC.bin index 5aedea90bf4018785a6b0a53a436dfbe106fafe2..18a31aefb3c2922c8aa5aecf25a7dc87737c9fcc 100644 GIT binary patch delta 3658 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 delta 3658 zcmV-Q4z=;7paG_!0kHQlASg?_e_56p^Z?JYLIPREZW#tHkOX?=z70nD(-fScCzBU3 zKpQxI7eurtvVj=GMo0 z$594;0dr#fj6|G}veD{D7__L9*D*jKzUcPT1VJ0<316v*3)mW+CHFrvb8$&4l3UYU zBgvOfe z?w9F@oqlkVFh*Rg{>!EEf$JNUss^h}2PKeweMi{8rgW&QC2uRkhAM@j2w{`MrtNHl zhFY`PGNb~3cEAW_NPC*|QS*+>Z7$;Xo-ESF`+^19`CIG$IHi+S(3Y4nbZ;YE*T^75u{56#Vf1a+JOex>LXtXq4mK%x~ z7zc-0nR!bdMHZ?ijnZ0A;>3T-uHZ_g-)z`M+~seQgl#RPfERW__`mA(U!^TBkmh4c z_%Wj$&G{-J(y+0Lh^VC$#oy11kyhs{cas5eM(n!vptDW)-*dZ_*tfmU?&u)Rt;GWK zSD7gyIPSd0vk*Cz0)LJ^o{07Wq@Zm9Jg(fquBgdqNCrs6%bDGu#jPcAFcu19Jim5y z2liRsjn+8L9WGy_G?av*5ar-WB@bt-5(ZKH0`Y&b_=QciOHmit! z{)c!;JZ-d@)a(p~2;Gd#eH!La*t)6h3??^&OznW9-Ftj9RleKa~Q10M<%&H3v=?6KMqvZ#~~*r!<`CPL@PV@&&}4SbG*? z#Ms(yTMX)QNL(3O0sb{GPuE0jK7~E>ST-KK#Xjv(%#50V+2XFaeu27VXbQg>*WsX@bLN_R2i=41;ed?)u-?RR6mIAal81t0jr)I7 zeSdHrS1Z27l%sNR8iy-<0(Y#I2tc-2Qwsv@d_HVK+LOngdbPe}ti1zid3qKI6#mNF zz@h&y0HZy%6@({vC!MSZTw@4uN=RJVG>N;G&mLs}~(`1C?7QiId6>E}02|X0v`hQJ!u8mu{4{R9c0N^itVA^wZe;|_Vv+^nt=ISQ zYK~v9ksJ_RIJQ+kID$jJD(yZvHkc-hM$-tN);E-vg#Dgy|Dgr!usvn22olS~eI5z^ z&9ljZ?<&4W6gvON zJ+(gNL9fWHy``AZ*qf;G%4JlCbNuO-_+wA8cS{t9YR07g4}bDkh8gfIQM1r)Amxin zv{d}Ejpb96XGywB`bfRGrYW31>MJAi2QHcRLW`dj&s)f{yD7{M9b}l2s+dnT3O(2| zPqH!cD>6-k;Ft&;gC@%DQh$0O(eh?7OhADi(mfva1XzQ+b;laLytlkI{K~9+Spq>5 zDNs}^%{`GX>mW7VGr5iI8;9;0xYJthuB%Ju%b>5)!D;|BLLefOzhm&xKU`siXpK@M zHzrqQG_^}ieWB_}kGz_Bfb+~ohi=vw>9CHb!`o-)<{9@tL%vT+>3?lUE5@G%JW~~w z`#^lP%IN@KuXQ(s4EIK<9okg15o%}Z6Sh3{70$Dy^!yrjLwKZ`Qg?aQoKR7`4QjKxL0WGCC4}%AX-hTtb zlCR}O$QtCrU7JF7hJUUi(!}4V0fg^UaY=`;f-xyhKnkGEgKjUqqk}>p` zy}CwK68d~mTy5d?)rZ#SCxlktSkaM_3?jTsa6JFX*km;#CMKisMTYf|6L;n34ij`^fs5@trfVF^}FOR z{g=fU<*&nSA#$eRHstC;uM2{j29s&`Wvy&sh)LoN;vfPJMMwZjBQRwcR3CkXX5PZW ztS_-wf5eG2@awhXA}RP&zfa1X>-*R$MFAtCX&A_j303F}Hk+$qJfU!MFFn#;3HuIa z`Q^Ch&*t`hpMRU7&H&h+IJ!OJds^`*&R&4R6#qi0*g3Tbu+9(l;i%A5@JHfs-(|2Od$Rerf1QzBjw#N zGklv3OB}=7LXMb}K>s~8t4)JUSl`%r!ARNMG|7868Gn5qdkRLk7)YCujo2uY|0pay zb~g%D8d81ycrykRyp~=LkP*hgI zjo>WzE~XvV9!1#rUXii#M)tEZ3=t(@1(V6xa+$aGZnbR@|wnH8k7#>G0=V@+9Y%nN>W<3} z(jJG?v|g!0-DUU>;tE!Dc38L8od$r%VFVyd0cs8qnI^?F_#4kZ^q#i29>~Cx`09Gl zrGN2V4_Z7Cv>1F*fg>M`bdSFuFcqoG9b3D-e(7kO7tn;UU|B|D2U2KFMe#l68+I%HNLN2T$pG8;Bk z6#`xr^YdT>nS8WqN5si|EQO7=kr*w*2Y*;QEIe%+nR%~7Kd9q)bW5b~C#EHpyq=$7 z;i2V1r}KRbmY@sqN|3Gf5i)d{Z5mH~C>uWgLfQLMCE)!y=e(&Kl29k75YcU>{^sHn zuljgpMl~_?Z!;aSdzYmT(kwHU~j+i=@dTE z+}#a}DL~0e34^H%Wy;PPUH%U8U*eALE6MJh_N&$mxId2FOa9v5=yYRONsre7SmR(7 zTveZ=>_5u^rLLd(b6@6&kj-OUo+N#<^kZm$xcw9dh!cZ++CXPKP?F7Tbbp)!^1j(n z4^gqc(Z)jHmL||wqZ&WutBi6vkD@5 z&$FRfhsBt-$GcQsnXbq^RG+$5q#X@u1E3@tB9x%XH$IcB?~>=eiOD^a3FBBu9cnxPLWw1q9K7 zl*23;LIOgyQB`nSSzqU%$k%|gYZLSWAl2>@MlrrF)Dfg1&obD_k#x=tKIOZS7+4A= ze!~4S+OvNaB?Et_^$r;b{8pCWIf+4-6?Qafr#<`gLCC(g&*6Tr53+Elbt=*9^t<@} z*BS7(YKi8nI@8Ni4xq$f7&(09QaQE&P4EM$Uqt+@i;wG*dZzgqQpXHqn!`2xlR|)&i%vuTdK7DH3HYC-j;bn^1UU>3?l9x43 zU|9Y4Q*Y{T3U5Rsj!6}h<=K#%JM-i$n}3djEZRaXV{Kg1yJm$sIWmK{vjC1v-(ACn zGo#;6n-OFiWb55MCM6660qUEZVKHsfD}zi!MmC5riJlK6lJ9b@a64~BD4 ztF;_3XI`vaWQxJ`8#MF}f`Y|yNyrTVhNBKTS#y8d%4voH_IYbjl!I>O@f_j!kO?@L zQi|<|dh}i!#|bV>QS76T4F*lOA0T+mH-`lBK}@xa!@DIZAWBDWo# zmJi-N6)qT(TEMiab@w#9_s^P=nntX${L>=mr~ay^r({Wpx8&4XSrk5n2%+xLiA6k( z6l8Z)Yo3e-?sC*s6vsG*M0xNnYea!Iq2zz2ky;oQ#LxH=RbFrK2?0Vr7RM!>uVK=` zfKm?k+`kyVa+;H%4CP|4rJS$$9snI!4Wi%P!Z3y-gx`FGYyntHY)7R+3d(a{A4e68D&PF< Y>d-RA0e@i%7->CKeRh$|43p#>G&a`s2><{9 delta 1002 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 WRz_!d@=tgaKNk<_IVS^?btOFD060JZ delta 1676 zcmV;726OqbOR-C^niwFZ*1S_7!#ydu5L3T7cC*=oiO(920-T$W0ufKM1 z!<3unjh;VaESm(iD;jA-ug-p6r57$72=4WfC=ETd#7l8~hO}XW!yG?VM7Phy7XWl6 zgUHeI;^Y6V5IO~-1r6T4tx!Q$N=Z1D!xT%gO@!9*xZOerNz%dl&8YJ>_9o&PopO{L zBU5$`9Pp*H-yBv0e~pF zVHGshjsP7OBdK%F-`X1uagn=^HkCu3c-(%6TVkG4xe7zv#;md2{{$F8jZ~Vl12`t1 z5meIj<`OPT$y!mUsXWdYBq%-(!sa2?qA;YOu59BtoDW?`f1Nrz?@Fpu2TN2xt}!uC zV=b3k(bp?=$^<0aCZeZP!9359VJbdu_kUc8>qm5Z*1|PX1U{4=lC=Kui-&}WGp2yb z{Rb2dI6`)HK)09ev)(SBHhJuI*0ntZLUUVlj(O|i-pb~RpEu)|N}9DsYCgDnm8(mP zF+Xq@XlTcNe^{~dzQoRSfObJGR4Ij349kEB(^9x`9sar%Lxc^cKtmu4rX&^;oS7Rt z9g({dF)s7tO$9(qnPgvQChBv>X7<-bVX2wDx-owXIgZeNQ>!SiNtePr!eZ2>bU6!T zpDsdu=LZ#%8N$3TQ+<)O{fa>|+@9Y>_*gE2Nx>fje`V6f`McNDa9qm(HrRWQUrilx z9GcttRjo;y-b)Kgu?}Iir}}EDW~aIonnz%m4AZG{(iAZKxdj@Zp6>bzK$`0$ zB?WZ^w`-35W$w`L%a1i?9m!rsJm?F4dgC|j<|P5iH+m3wZM$^016am1#RY(hd|TlV zcFKjZf1@f(0;W&!FG0}#xe1I?Bd?@S!;}9Ki>)tFt)T)qTO*L{Ai-rV@Niz+2 z{l=LQuBf1L6P9a0PTf~cX61!Duoj}i-AiXe=)(WNSBb5NO>iI;nn6$HUNE38U3JG zc~{;NFbb6ChOn@^AvbgBvndE|V`CrCkaf}GVS-%x8&5R%-LsJ2@oKkyC4HCSaK-XQ z-q8(g18lX(tnY|6e>gUDfU=CRmp#Tf-qx4QL00SlSX7QJWc}94 zQ)&u+f4qg~8Jlj$0hlX_K?@0599J4(W^bL4vCth(hT4q3*wJ<}IbsV3L&|YY)nVDh zpSJ1b8gvu-a*jZ0LW0LSE*xCB6f{nT74V<%0)4-T(A*O1tp~^Y7QJH)0%&7df7!(n zc12LTnemG7tcLQnpe&n>#7mJ!>6x4#nuqL#Z4Dm|*p89K@0kgOc-O7X=Wg`H1x+&J ztx8iI&I=vIT~;IU@mCKB7lU{7QLMt1x2QX!lYDMV$5QE)2sirv#hna#o&&=G{=BTY zC$7q0QPzX}B%f6J;gLS}QKLo%sx&>u<*@O;$sKZbfC#k}kmV6aG@ z9u4gEbL>W&$SDH?Feeei|AY&|xuTgpNm0ibMV2LTDh4P>vXXm@Kq*{o%ARAE)WX%j zU(LSk3Ne@%5(;(kOp~xL4s(^_)O2(zfCWGDH-~COl%$H2tNEJ<2%JCrO6^?MmAhI6 zsZigFl&{n_66^HDfylP{-|J(6uxd_cWi`9LhzGJZI<(fB|isL Wr?5^RF%04@YmL{|`(_A}btOEm11Yiq diff --git a/test_fixtures/masp_proofs/AEA19C9B07742FF5F6D759B171396732D2EBF77728D2772EB251123DF2CEF6A1.bin b/test_fixtures/masp_proofs/AEA19C9B07742FF5F6D759B171396732D2EBF77728D2772EB251123DF2CEF6A1.bin index 4b7967cf8517fcab5932ceb352630a2cdb92311b..ff5c5f48a282a9dc89a17004a076032d3d477e26 100644 GIT binary patch delta 1742 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 diff --git a/test_fixtures/masp_proofs/B947FEFC6EC94A041657FF3A9420D5BB3FDEB1199EF712C059C80C4DBF6F3ED8.bin b/test_fixtures/masp_proofs/B947FEFC6EC94A041657FF3A9420D5BB3FDEB1199EF712C059C80C4DBF6F3ED8.bin index 9627a1ce5cf7859d22fd169bd2f71968cdb6ed2a..448a4a51c06c489d898387a0340f2bde96bfe576 100644 GIT binary patch delta 1742 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 diff --git a/test_fixtures/masp_proofs/C788F9057C615CCE7B260BB8BF5CF776D5DE7C9153D67B7FDF22ED6F35558738.bin b/test_fixtures/masp_proofs/C788F9057C615CCE7B260BB8BF5CF776D5DE7C9153D67B7FDF22ED6F35558738.bin index d3c3a5c24181bdbe0be7e43634ce4e5cd0ac396a..016db1e629afcd5261265cee85810dda15e412ef 100644 GIT binary patch delta 4169 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 TsprOMTNK9f%xpBKlQCa?Tg(U? delta 4169 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 Tq$nFTV-TH5piX((lQCa?AEp`K diff --git a/test_fixtures/masp_proofs/CBF4087F2AB578C83A736E4598DBA1FDB2515DF33D43611F2860B8D3C178B474.bin b/test_fixtures/masp_proofs/CBF4087F2AB578C83A736E4598DBA1FDB2515DF33D43611F2860B8D3C178B474.bin index 06393963b3a99b320f64849410effb8d8cad18ce..9fe1c6b56665c65beb54fa863ed39da3d8618876 100644 GIT binary patch delta 1002 zcmV~e`=pUWH~VE)syLk;u- zAVZ8_q^CP9-=uod1)Q zo7=0;p#$&jXsQ@}Svp$xA>OvLYZLSWAWL$iMtB?Et<)+nb2TI`v~;zr(U4G!^m2&|ca8TwT|F?87+m-;pN`H8Sj_;7@! zNqYU&cYGR>dr#JCZuXP+cf|Q;y@fIc;9877({v0Dd)HzY{Il~*>*N~7EYo)p4yssQ z+#>=Fu1pruJYKwjylU(Qd2i{@sMQY5C%32lDhZk)JJEm3-dL?i|FcC^=wp|XX67C7 zmUs3l0245<3h&eju4e?yZe>*eR>e6xVV8=zAD^*vEwF!-_&+|1*4+T-GKr5!rEEW= zztPhpzVe%%OnJLC1(4Qjwzx24Fgct9_P)>%Ql?d6g8SZ|nMWm(#M+5lkU|M|;`q#`A%c}7^ z@ywnz#vbqvV3Hj!WFiGap`fom1C*&RUHuwZ$Pze#)nI^J*{Pikx76i+rGTG+8itiz z-+A}{JY0&J>-iN?Ifd(IosBrsojrJKYF zpUr=ohnQ(~_rvSf&o&HUuHu(*r+D6kifFpu**6crDZ_*lSqxn)kIt8$`qRgk)vM_v zkY7uIJO#^j0ULDLGEP<8kRbJry35qs$6sy>=__k$ycq@Bmeby zd?ov{GuZ>K+F6o~7hDj`s~Dc8bRir0JyX5hzuPe9U(#M^<9)!V*yFk|@W-W2D@>n9 zgYTNxL;UbAmFY{cez=kHNcDKcXS68^KP39I|jK86;?&BA+}pGpd<>1S#UxL6doKp6utj0tnuw#2HO1 YKFSRgO>}!O>Qx0!W|Z<}3X|j?A~qNlDN1MTg5 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 DI!{Z~ delta 2749 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 Date: Thu, 7 Dec 2023 16:28:14 +0100 Subject: [PATCH 033/216] Fixes masp key validation --- core/src/ledger/masp_utils.rs | 6 +++--- core/src/types/token.rs | 24 ++++++++++++++++-------- shared/src/ledger/native_vp/masp.rs | 4 ++-- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/core/src/ledger/masp_utils.rs b/core/src/ledger/masp_utils.rs index d15834f7a1..f37b725531 100644 --- a/core/src/ledger/masp_utils.rs +++ b/core/src/ledger/masp_utils.rs @@ -10,8 +10,8 @@ use crate::types::address::MASP; use crate::types::hash::Hash; use crate::types::storage::{BlockHeight, Epoch, Key, KeySeg, TxIndex}; use crate::types::token::{ - Transfer, HEAD_TX_KEY, MASP_NOTE_COMMITMENT_TREE_KEY, - MASP_NULLIFIERS_KEY_PREFIX, PIN_KEY_PREFIX, TX_KEY_PREFIX, + Transfer, HEAD_TX_KEY, MASP_NOTE_COMMITMENT_TREE_KEY, MASP_NULLIFIERS_KEY, + PIN_KEY_PREFIX, TX_KEY_PREFIX, }; // Writes the nullifiers of the provided masp transaction to storage @@ -24,7 +24,7 @@ fn reveal_nullifiers( .map_or(&vec![], |description| &description.shielded_spends) { let nullifier_key = Key::from(MASP.to_db_key()) - .push(&MASP_NULLIFIERS_KEY_PREFIX.to_owned()) + .push(&MASP_NULLIFIERS_KEY.to_owned()) .expect("Cannot obtain a storage key") .push(&Hash(description.nullifier.0)) .expect("Cannot obtain a storage key"); diff --git a/core/src/types/token.rs b/core/src/types/token.rs index e5d6c3706b..4477967c8a 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -901,7 +901,7 @@ pub const TX_KEY_PREFIX: &str = "tx-"; /// Key segment prefix for pinned shielded transactions pub const PIN_KEY_PREFIX: &str = "pin-"; /// Key segment prefix for the nullifiers -pub const MASP_NULLIFIERS_KEY_PREFIX: &str = "nullifiers"; +pub const MASP_NULLIFIERS_KEY: &str = "nullifiers"; /// Key segment prefix for the note commitment merkle tree pub const MASP_NOTE_COMMITMENT_TREE_KEY: &str = "commitment_tree"; /// Key segment prefix for the note commitment anchor @@ -1134,14 +1134,24 @@ pub fn is_masp_key(key: &Key) -> bool { /// Check if the given storage key is allowed to be touched by a masp transfer pub fn is_masp_allowed_key(key: &Key) -> bool { - matches!(&key.segments[..], + match &key.segments[..] { [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(key)] if *addr == MASP && (key == HEAD_TX_KEY || key.starts_with(TX_KEY_PREFIX) || key.starts_with(PIN_KEY_PREFIX) - || key.starts_with(MASP_NULLIFIERS_KEY_PREFIX) - || key == MASP_NOTE_COMMITMENT_TREE_KEY)) + || key == MASP_NOTE_COMMITMENT_TREE_KEY) => + { + true + } + + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(key), + DbKeySeg::StringSeg(_nullifier), + ] if *addr == MASP && key == MASP_NULLIFIERS_KEY => true, + _ => false, + } } /// Check if the given storage key is a masp tx prefix key @@ -1149,7 +1159,6 @@ pub fn is_masp_tx_prefix_key(key: &Key) -> bool { matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix), - .. ] if *addr == MASP && prefix.starts_with(TX_KEY_PREFIX)) } @@ -1158,7 +1167,6 @@ pub fn is_masp_tx_pin_key(key: &Key) -> bool { matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix), - .. ] if *addr == MASP && prefix.starts_with(PIN_KEY_PREFIX)) } @@ -1167,8 +1175,8 @@ pub fn is_masp_nullifier_key(key: &Key) -> bool { matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix), - .. - ] if *addr == MASP && prefix == MASP_NULLIFIERS_KEY_PREFIX) + DbKeySeg::StringSeg(_nullifier) + ] if *addr == MASP && prefix == MASP_NULLIFIERS_KEY) } /// Obtain the storage key for the last locked ratio of a token diff --git a/shared/src/ledger/native_vp/masp.rs b/shared/src/ledger/native_vp/masp.rs index 7bae49dde2..b7efededcc 100644 --- a/shared/src/ledger/native_vp/masp.rs +++ b/shared/src/ledger/native_vp/masp.rs @@ -21,7 +21,7 @@ use namada_core::types::token::{ self, is_masp_allowed_key, is_masp_key, is_masp_nullifier_key, is_masp_tx_pin_key, is_masp_tx_prefix_key, Transfer, HEAD_TX_KEY, MASP_CONVERT_ANCHOR_KEY, MASP_NOTE_COMMITMENT_ANCHOR_PREFIX, - MASP_NOTE_COMMITMENT_TREE_KEY, MASP_NULLIFIERS_KEY_PREFIX, PIN_KEY_PREFIX, + MASP_NOTE_COMMITMENT_TREE_KEY, MASP_NULLIFIERS_KEY, PIN_KEY_PREFIX, TX_KEY_PREFIX, }; use namada_sdk::masp::verify_shielded_tx; @@ -135,7 +135,7 @@ where .map_or(&vec![], |description| &description.shielded_spends) { let nullifier_key = Key::from(MASP.to_db_key()) - .push(&MASP_NULLIFIERS_KEY_PREFIX.to_owned()) + .push(&MASP_NULLIFIERS_KEY.to_owned()) .expect("Cannot obtain a storage key") .push(&namada_core::types::hash::Hash(description.nullifier.0)) .expect("Cannot obtain a storage key"); From 1971cf80d1fe9567e6a6abd5c35d41847abd38c4 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 7 Dec 2023 16:29:59 +0100 Subject: [PATCH 034/216] Changelog #2248 --- .../unreleased/bug-fixes/2248-convert-description-validation.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/2248-convert-description-validation.md diff --git a/.changelog/unreleased/bug-fixes/2248-convert-description-validation.md b/.changelog/unreleased/bug-fixes/2248-convert-description-validation.md new file mode 100644 index 0000000000..2f7b72ceb2 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/2248-convert-description-validation.md @@ -0,0 +1,2 @@ +- Updates the masp vp to validate the convert description's anchor. + ([\#2248](https://github.com/anoma/namada/pull/2248)) \ No newline at end of file From 7e923dbf1e2edb50059ca1ef0ae291084acd9a20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 23 Nov 2023 15:21:42 +0100 Subject: [PATCH 035/216] wasm/vp_user: fix a typo --- wasm/wasm_source/src/vp_user.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index 390bc389fb..c0f8c149ce 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -21,7 +21,7 @@ enum KeyType<'a> { PoS, Vp(&'a Address), Masp, - PgfStward(&'a Address), + PgfSteward(&'a Address), GovernanceVote(&'a Address), Unknown, } @@ -40,7 +40,7 @@ impl<'a> From<&'a storage::Key> for KeyType<'a> { Self::Unknown } } else if let Some(address) = pgf_storage::keys::is_stewards_key(key) { - Self::PgfStward(address) + Self::PgfSteward(address) } else if let Some(address) = key.is_validity_predicate() { Self::Vp(address) } else if token::is_masp_key(key) { @@ -213,7 +213,7 @@ fn validate_tx( true } } - KeyType::PgfStward(address) => { + KeyType::PgfSteward(address) => { if address == &addr { *valid_sig } else { From b092f2c80cd38994c13858db47918cf0e6d6d64c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 23 Nov 2023 15:24:23 +0100 Subject: [PATCH 036/216] wasm/vp_implicit: port Masp and PgfSteward key handling from vp_user --- wasm/wasm_source/src/vp_implicit.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index 95d1401c54..89fa421fee 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -22,6 +22,8 @@ enum KeyType<'a> { owner: &'a Address, }, PoS, + Masp, + PgfSteward(&'a Address), GovernanceVote(&'a Address), Unknown, } @@ -34,6 +36,8 @@ impl<'a> From<&'a storage::Key> for KeyType<'a> { Self::Token { owner } } else if proof_of_stake::storage::is_pos_key(key) { Self::PoS + } else if let Some(address) = pgf_storage::keys::is_stewards_key(key) { + Self::PgfSteward(address) } else if gov_storage::keys::is_vote_key(key) { let voter_address = gov_storage::keys::get_voter_address(key); if let Some(address) = voter_address { @@ -41,6 +45,8 @@ impl<'a> From<&'a storage::Key> for KeyType<'a> { } else { Self::Unknown } + } else if token::is_masp_key(key) { + Self::Masp } else { Self::Unknown } @@ -156,6 +162,13 @@ fn validate_tx( ); valid } + KeyType::PgfSteward(address) => { + if address == &addr { + *valid_sig + } else { + true + } + } KeyType::GovernanceVote(voter) => { if voter == &addr { *valid_sig @@ -163,6 +176,7 @@ fn validate_tx( true } } + KeyType::Masp => true, KeyType::Unknown => { if key.segments.get(0) == Some(&addr.to_db_key()) { // Unknown changes to this address space require a valid From 63f70d28a22e0599c64e272e0e90d56109d421e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 23 Nov 2023 15:38:37 +0100 Subject: [PATCH 037/216] wasm/vp_user: require valid sig for unknown changes --- wasm/wasm_source/src/vp_user.rs | 59 ++++++++++++--------------------- 1 file changed, 21 insertions(+), 38 deletions(-) diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index c0f8c149ce..644428efec 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -11,7 +11,6 @@ //! //! Any other storage key changes are allowed only with a valid signature. -use namada_vp_prelude::storage::KeySeg; use namada_vp_prelude::*; use once_cell::unsync::Lazy; use proof_of_stake::types::ValidatorState; @@ -115,15 +114,15 @@ fn validate_tx( proof_of_stake::storage::is_unbond_key(key) .map(|(bond_id, _, _)| bond_id) }); - let valid_bond_or_unbond_change = match bond_id { + let is_valid_bond_or_unbond_change = match bond_id { Some(bond_id) => { // Bonds and unbonds changes for this address // must be signed bond_id.source != addr || *valid_sig } None => { - // Any other PoS changes are allowed without signature - true + // Unknown changes are not allowed + false } }; // Commission rate changes must be signed by the validator @@ -131,26 +130,26 @@ fn validate_tx( proof_of_stake::storage::is_validator_commission_rate_key( key, ); - let valid_commission_rate_change = match comm { + let is_valid_commission_rate_change = match comm { Some((validator, _epoch)) => { *validator == addr && *valid_sig } - None => true, + None => false, }; // Metadata changes must be signed by the validator whose // metadata is manipulated let metadata = proof_of_stake::storage::is_validator_metadata_key(key); - let valid_metadata_change = match metadata { + let is_valid_metadata_change = match metadata { Some(address) => *address == addr && *valid_sig, - None => true, + None => false, }; // Changes due to unjailing, deactivating, and reactivating are // marked by changes in validator state let state_change = proof_of_stake::storage::is_validator_state_key(key); - let valid_state_change = match state_change { + let is_valid_state_change = match state_change { Some((address, epoch)) => { let params_pre = proof_of_stake::read_pos_params(&ctx.pre())?; @@ -188,38 +187,28 @@ fn validate_tx( { *address == addr && *valid_sig } else { - true + // Unknown state changes are not allowed + false } } (None, Some(_post)) => { // Becoming a validator must be authorized *valid_sig } - _ => true, + _ => false, } } - None => true, + None => false, }; - valid_bond_or_unbond_change - && valid_commission_rate_change - && valid_state_change - && valid_metadata_change - } - KeyType::GovernanceVote(voter) => { - if voter == &addr { - *valid_sig - } else { - true - } - } - KeyType::PgfSteward(address) => { - if address == &addr { - *valid_sig - } else { - true - } + is_valid_bond_or_unbond_change + || is_valid_commission_rate_change + || is_valid_state_change + || is_valid_metadata_change + || *valid_sig } + KeyType::PgfSteward(address) => address != &addr || *valid_sig, + KeyType::GovernanceVote(voter) => voter != &addr || *valid_sig, KeyType::Vp(owner) => { let has_post: bool = ctx.has_key_post(key)?; if owner == &addr { @@ -237,14 +226,8 @@ fn validate_tx( } KeyType::Masp => true, KeyType::Unknown => { - if key.segments.get(0) == Some(&addr.to_db_key()) { - // Unknown changes to this address space require a valid - // signature - *valid_sig - } else { - // Unknown changes anywhere else are permitted - true - } + // Unknown changes require a valid signature + *valid_sig } }; if !is_valid { From 14fde59e574efe14897e9048501e8abdc517df00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 23 Nov 2023 15:42:10 +0100 Subject: [PATCH 038/216] wasm/vp_implicit: require valid sig for unknown changes --- wasm/wasm_source/src/vp_implicit.rs | 41 +++++++---------------------- 1 file changed, 9 insertions(+), 32 deletions(-) diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index 89fa421fee..eee17fe63e 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -11,7 +11,6 @@ //! //! Any other storage key changes are allowed only with a valid signature. -use namada_vp_prelude::storage::KeySeg; use namada_vp_prelude::*; use once_cell::unsync::Lazy; @@ -144,48 +143,26 @@ fn validate_tx( proof_of_stake::storage::is_unbond_key(key) .map(|(bond_id, _, _)| bond_id) }); - let valid = match bond_id { + let is_valid_bond_or_unbond_change = match bond_id { Some(bond_id) => { // Bonds and unbonds changes for this address // must be signed bond_id.source != addr || *valid_sig } None => { - // Any other PoS changes are allowed without signature - true + // Unknown changes are not allowed + false } }; - debug_log!( - "PoS key {} {}", - key, - if valid { "accepted" } else { "rejected" } - ); - valid - } - KeyType::PgfSteward(address) => { - if address == &addr { - *valid_sig - } else { - true - } - } - KeyType::GovernanceVote(voter) => { - if voter == &addr { - *valid_sig - } else { - true - } + + is_valid_bond_or_unbond_change || *valid_sig } + KeyType::PgfSteward(address) => address != &addr || *valid_sig, + KeyType::GovernanceVote(voter) => voter != &addr || *valid_sig, KeyType::Masp => true, KeyType::Unknown => { - if key.segments.get(0) == Some(&addr.to_db_key()) { - // Unknown changes to this address space require a valid - // signature - *valid_sig - } else { - // Unknown changes anywhere else are permitted - true - } + // Unknown changes require a valid signature + *valid_sig } }; if !is_valid { From 50adb726022714b35faffc23560b3b274e39fcf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 23 Nov 2023 15:46:53 +0100 Subject: [PATCH 039/216] changelog: add #2213 --- .changelog/unreleased/improvements/2213-vp-less-permissive.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/unreleased/improvements/2213-vp-less-permissive.md diff --git a/.changelog/unreleased/improvements/2213-vp-less-permissive.md b/.changelog/unreleased/improvements/2213-vp-less-permissive.md new file mode 100644 index 0000000000..18ffa15100 --- /dev/null +++ b/.changelog/unreleased/improvements/2213-vp-less-permissive.md @@ -0,0 +1,3 @@ +- The default implicit and established user account VPs now + require valid signature(s) for unknown storage changes. + ([\#2213](https://github.com/anoma/namada/pull/2213)) \ No newline at end of file From 63e14a47f546e43f65994a00d050554699773807 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 24 Nov 2023 20:25:32 +0100 Subject: [PATCH 040/216] wasm/vp_user+vp_implicit: impl PoS key checks --- proof_of_stake/src/storage.rs | 387 ++++++++++++++++++++-------- wasm/wasm_source/src/vp_implicit.rs | 136 ++++++++-- wasm/wasm_source/src/vp_user.rs | 276 ++++++++++++-------- 3 files changed, 574 insertions(+), 225 deletions(-) diff --git a/proof_of_stake/src/storage.rs b/proof_of_stake/src/storage.rs index b76760650b..2991526760 100644 --- a/proof_of_stake/src/storage.rs +++ b/proof_of_stake/src/storage.rs @@ -5,7 +5,7 @@ use namada_core::types::address::Address; use namada_core::types::storage::{DbKeySeg, Epoch, Key, KeySeg}; use super::ADDRESS; -use crate::epoched::LAZY_MAP_SUB_KEY; +use crate::epoched; use crate::types::BondId; const PARAMS_STORAGE_KEY: &str = "params"; @@ -121,19 +121,23 @@ pub fn validator_consensus_key_key(validator: &Address) -> Key { /// Is storage key for validator's consensus key? pub fn is_validator_consensus_key_key(key: &Key) -> Option<&Address> { - match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(prefix), - DbKeySeg::AddressSeg(validator), - DbKeySeg::StringSeg(key), - ] if addr == &ADDRESS - && prefix == VALIDATOR_STORAGE_PREFIX - && key == VALIDATOR_CONSENSUS_KEY_STORAGE_KEY => - { - Some(validator) + if key.segments.len() >= 4 { + match &key.segments[..4] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(validator), + DbKeySeg::StringSeg(key), + ] if addr == &ADDRESS + && prefix == VALIDATOR_STORAGE_PREFIX + && key == VALIDATOR_CONSENSUS_KEY_STORAGE_KEY => + { + Some(validator) + } + _ => None, } - _ => None, + } else { + None } } @@ -146,19 +150,23 @@ pub fn validator_eth_cold_key_key(validator: &Address) -> Key { /// Is storage key for validator's eth cold key? pub fn is_validator_eth_cold_key_key(key: &Key) -> Option<&Address> { - match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(prefix), - DbKeySeg::AddressSeg(validator), - DbKeySeg::StringSeg(key), - ] if addr == &ADDRESS - && prefix == VALIDATOR_STORAGE_PREFIX - && key == VALIDATOR_ETH_COLD_KEY_STORAGE_KEY => - { - Some(validator) + if key.segments.len() >= 4 { + match &key.segments[..4] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(validator), + DbKeySeg::StringSeg(key), + ] if addr == &ADDRESS + && prefix == VALIDATOR_STORAGE_PREFIX + && key == VALIDATOR_ETH_COLD_KEY_STORAGE_KEY => + { + Some(validator) + } + _ => None, } - _ => None, + } else { + None } } @@ -171,19 +179,23 @@ pub fn validator_eth_hot_key_key(validator: &Address) -> Key { /// Is storage key for validator's eth hot key? pub fn is_validator_eth_hot_key_key(key: &Key) -> Option<&Address> { - match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(prefix), - DbKeySeg::AddressSeg(validator), - DbKeySeg::StringSeg(key), - ] if addr == &ADDRESS - && prefix == VALIDATOR_STORAGE_PREFIX - && key == VALIDATOR_ETH_HOT_KEY_STORAGE_KEY => - { - Some(validator) + if key.segments.len() >= 4 { + match &key.segments[..4] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(validator), + DbKeySeg::StringSeg(key), + ] if addr == &ADDRESS + && prefix == VALIDATOR_STORAGE_PREFIX + && key == VALIDATOR_ETH_HOT_KEY_STORAGE_KEY => + { + Some(validator) + } + _ => None, } - _ => None, + } else { + None } } @@ -195,29 +207,24 @@ pub fn validator_commission_rate_key(validator: &Address) -> Key { } /// Is storage key for validator's commission rate? -pub fn is_validator_commission_rate_key( - key: &Key, -) -> Option<(&Address, Epoch)> { - match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(prefix), - DbKeySeg::AddressSeg(validator), - DbKeySeg::StringSeg(key), - DbKeySeg::StringSeg(lazy_map), - DbKeySeg::StringSeg(data), - DbKeySeg::StringSeg(epoch), - ] if addr == &ADDRESS - && prefix == VALIDATOR_STORAGE_PREFIX - && key == VALIDATOR_COMMISSION_RATE_STORAGE_KEY - && lazy_map == LAZY_MAP_SUB_KEY - && data == lazy_map::DATA_SUBKEY => - { - let epoch = Epoch::parse(epoch.clone()) - .expect("Should be able to parse the epoch"); - Some((validator, epoch)) +pub fn is_validator_commission_rate_key(key: &Key) -> Option<&Address> { + if key.segments.len() >= 4 { + match &key.segments[..4] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(validator), + DbKeySeg::StringSeg(key), + ] if addr == &ADDRESS + && prefix == VALIDATOR_STORAGE_PREFIX + && key == VALIDATOR_COMMISSION_RATE_STORAGE_KEY => + { + Some(validator) + } + _ => None, } - _ => None, + } else { + None } } @@ -313,6 +320,22 @@ pub fn rewards_counter_key(source: &Address, validator: &Address) -> Key { .expect("Cannot obtain a storage key") } +/// Is the storage key for rewards counter? +pub fn is_rewards_counter_key(key: &Key) -> Option { + match &key.segments[..] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(key), + DbKeySeg::AddressSeg(source), + DbKeySeg::AddressSeg(validator), + ] if addr == &ADDRESS && key == REWARDS_COUNTER_KEY => Some(BondId { + source: source.clone(), + validator: validator.clone(), + }), + _ => None, + } +} + /// Storage key for a validator's incoming redelegations, where the prefixed /// validator is the destination validator. pub fn validator_incoming_redelegations_key(validator: &Address) -> Key { @@ -345,6 +368,33 @@ pub fn validator_total_redelegated_unbonded_key(validator: &Address) -> Key { .expect("Cannot obtain a storage key") } +/// Is the storage key's prefix matching one of validator's: +/// +/// - incoming or outgoing redelegations +/// - total redelegated bonded or unbond amounts +pub fn is_validator_redelegations_key(key: &Key) -> bool { + if key.segments.len() >= 4 { + match &key.segments[..4] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(val_prefix), + DbKeySeg::AddressSeg(_validator), + DbKeySeg::StringSeg(prefix), + ] => { + addr == &ADDRESS + && val_prefix == VALIDATOR_STORAGE_PREFIX + && (prefix == VALIDATOR_INCOMING_REDELEGATIONS_KEY + || prefix == VALIDATOR_OUTGOING_REDELEGATIONS_KEY + || prefix == VALIDATOR_TOTAL_REDELEGATED_BONDED_KEY + || prefix == VALIDATOR_TOTAL_REDELEGATED_UNBONDED_KEY) + } + _ => false, + } + } else { + false + } +} + /// Storage key prefix for all delegators' redelegated bonds. pub fn delegator_redelegated_bonds_prefix() -> Key { Key::from(ADDRESS.to_db_key()) @@ -373,6 +423,29 @@ pub fn delegator_redelegated_unbonds_key(delegator: &Address) -> Key { .expect("Cannot obtain a storage key") } +/// Is the storage key's prefix matching delegator's total redelegated bonded or +/// unbond amounts? If so, returns the delegator's address. +pub fn is_delegator_redelegations_key(key: &Key) -> Option<&Address> { + if key.segments.len() >= 3 { + match &key.segments[..3] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(delegator), + ] if addr == &ADDRESS + && (prefix == DELEGATOR_REDELEGATED_BONDS_KEY + || prefix == DELEGATOR_REDELEGATED_UNBONDS_KEY) => + { + Some(delegator) + } + + _ => None, + } + } else { + None + } +} + /// Storage key for validator's last known rewards product epoch. pub fn validator_last_known_product_epoch_key(validator: &Address) -> Key { validator_prefix(validator) @@ -421,7 +494,7 @@ pub fn is_validator_state_key(key: &Key) -> Option<(&Address, Epoch)> { ] if addr == &ADDRESS && prefix == VALIDATOR_STORAGE_PREFIX && key == VALIDATOR_STATE_STORAGE_KEY - && lazy_map == LAZY_MAP_SUB_KEY + && lazy_map == epoched::LAZY_MAP_SUB_KEY && data == lazy_map::DATA_SUBKEY => { let epoch = Epoch::parse(epoch.clone()) @@ -432,6 +505,30 @@ pub fn is_validator_state_key(key: &Key) -> Option<(&Address, Epoch)> { } } +/// Is storage key for a validator state's last update or oldest epoch? +pub fn is_validator_state_epoched_meta_key(key: &Key) -> bool { + if key.segments.len() >= 5 { + match &key.segments[..5] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(_validator), + DbKeySeg::StringSeg(key), + DbKeySeg::StringSeg(data), + ] => { + addr == &ADDRESS + && prefix == VALIDATOR_STORAGE_PREFIX + && key == VALIDATOR_STATE_STORAGE_KEY + && (data == epoched::LAST_UPDATE_SUB_KEY + || data == epoched::OLDEST_EPOCH_SUB_KEY) + } + _ => false, + } + } else { + false + } +} + /// Storage key for validator's deltas. pub fn validator_deltas_key(validator: &Address) -> Key { validator_prefix(validator) @@ -440,35 +537,48 @@ pub fn validator_deltas_key(validator: &Address) -> Key { } /// Is storage key for validator's total deltas? -pub fn is_validator_deltas_key(key: &Key) -> Option<&Address> { - match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(prefix), - DbKeySeg::AddressSeg(validator), - DbKeySeg::StringSeg(key), - DbKeySeg::StringSeg(lazy_map), - DbKeySeg::StringSeg(data), - DbKeySeg::StringSeg(_epoch), - ] if addr == &ADDRESS - && prefix == VALIDATOR_STORAGE_PREFIX - && key == VALIDATOR_DELTAS_STORAGE_KEY - && lazy_map == LAZY_MAP_SUB_KEY - && data == lazy_map::DATA_SUBKEY => - { - Some(validator) +pub fn is_validator_deltas_key(key: &Key) -> bool { + if key.segments.len() >= 4 { + match &key.segments[..4] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(_validator), + DbKeySeg::StringSeg(key), + ] => { + addr == &ADDRESS + && prefix == VALIDATOR_STORAGE_PREFIX + && key == VALIDATOR_DELTAS_STORAGE_KEY + } + _ => false, } - _ => None, + } else { + false } } -/// Storage prefix for all active validators (consensus, below-capacity, jailed) +/// Storage prefix for all active validators (consensus, below-capacity, +/// below-threshold, inactive, jailed) pub fn validator_addresses_key() -> Key { Key::from(ADDRESS.to_db_key()) .push(&VALIDATOR_ADDRESSES_KEY.to_owned()) .expect("Cannot obtain a storage key") } +/// Is the storage key a prefix for all active validators? +pub fn is_validator_addresses_key(key: &Key) -> bool { + if key.segments.len() >= 2 { + match &key.segments[..2] { + [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix)] => { + addr == &ADDRESS && prefix == VALIDATOR_ADDRESSES_KEY + } + _ => false, + } + } else { + false + } +} + /// Storage prefix for slashes. pub fn slashes_prefix() -> Key { Key::from(ADDRESS.to_db_key()) @@ -494,7 +604,7 @@ pub fn validator_slashes_key(validator: &Address) -> Key { /// Is storage key for a validator's slashes pub fn is_validator_slashes_key(key: &Key) -> Option

{ if key.segments.len() >= 5 { - match &key.segments[..] { + match &key.segments[..5] { [ DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix), @@ -557,7 +667,7 @@ pub fn is_bond_key(key: &Key) -> Option<(BondId, Epoch)> { DbKeySeg::StringSeg(epoch_str), ] if addr == &ADDRESS && prefix == BOND_STORAGE_KEY - && lazy_map == crate::epoched::LAZY_MAP_SUB_KEY + && lazy_map == epoched::LAZY_MAP_SUB_KEY && data == lazy_map::DATA_SUBKEY => { let start = Epoch::parse(epoch_str.clone()).ok()?; @@ -576,13 +686,61 @@ pub fn is_bond_key(key: &Key) -> Option<(BondId, Epoch)> { } } +/// Is storage key for a bond last update or oldest epoch? Returns the bond ID +/// if so. +pub fn is_bond_epoched_meta_key(key: &Key) -> Option { + if key.segments.len() >= 5 { + match &key.segments[..5] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(source), + DbKeySeg::AddressSeg(validator), + DbKeySeg::StringSeg(data), + ] if addr == &ADDRESS + && prefix == BOND_STORAGE_KEY + && (data == epoched::LAST_UPDATE_SUB_KEY + || data == epoched::OLDEST_EPOCH_SUB_KEY) => + { + Some(BondId { + source: source.clone(), + validator: validator.clone(), + }) + } + _ => None, + } + } else { + None + } +} + /// Storage key for the total bonds for a given validator. pub fn validator_total_bonded_key(validator: &Address) -> Key { - Key::from(ADDRESS.to_db_key()) + validator_prefix(validator) .push(&VALIDATOR_TOTAL_BONDED_STORAGE_KEY.to_owned()) .expect("Cannot obtain a storage key") - .push(&validator.to_db_key()) - .expect("Cannot obtain a storage key") +} + +/// Is the storage key for the total bonds or unbonds for a validator? +pub fn is_validator_total_bond_or_unbond_key(key: &Key) -> bool { + if key.segments.len() >= 4 { + match &key.segments[..4] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(val_prefix), + DbKeySeg::AddressSeg(_validator), + DbKeySeg::StringSeg(prefix), + ] => { + addr == &ADDRESS + && val_prefix == VALIDATOR_STORAGE_PREFIX + && (prefix == VALIDATOR_TOTAL_BONDED_STORAGE_KEY + || prefix == VALIDATOR_TOTAL_UNBONDED_STORAGE_KEY) + } + _ => false, + } + } else { + false + } } /// Storage key prefix for all unbonds. @@ -673,12 +831,12 @@ pub fn below_capacity_validator_set_key() -> Key { /// Is storage key for the consensus validator set? pub fn is_consensus_validator_set_key(key: &Key) -> bool { - matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(key), DbKeySeg::StringSeg(set_type), DbKeySeg::StringSeg(lazy_map), DbKeySeg::StringSeg(data), DbKeySeg::StringSeg(_epoch), DbKeySeg::StringSeg(_), DbKeySeg::StringSeg(_amount), DbKeySeg::StringSeg(_), DbKeySeg::StringSeg(_position)] if addr == &ADDRESS && key == VALIDATOR_SETS_STORAGE_PREFIX && set_type == CONSENSUS_VALIDATOR_SET_STORAGE_KEY && lazy_map == LAZY_MAP_SUB_KEY && data == lazy_map::DATA_SUBKEY) + matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(key), DbKeySeg::StringSeg(set_type), DbKeySeg::StringSeg(lazy_map), DbKeySeg::StringSeg(data), DbKeySeg::StringSeg(_epoch), DbKeySeg::StringSeg(_), DbKeySeg::StringSeg(_amount), DbKeySeg::StringSeg(_), DbKeySeg::StringSeg(_position)] if addr == &ADDRESS && key == VALIDATOR_SETS_STORAGE_PREFIX && set_type == CONSENSUS_VALIDATOR_SET_STORAGE_KEY && lazy_map == epoched::LAZY_MAP_SUB_KEY && data == lazy_map::DATA_SUBKEY) } /// Is storage key for the below-capacity validator set? pub fn is_below_capacity_validator_set_key(key: &Key) -> bool { - matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(key), DbKeySeg::StringSeg(set_type), DbKeySeg::StringSeg(lazy_map), DbKeySeg::StringSeg(data), DbKeySeg::StringSeg(_epoch), DbKeySeg::StringSeg(_), DbKeySeg::StringSeg(_amount), DbKeySeg::StringSeg(_), DbKeySeg::StringSeg(_position)] if addr == &ADDRESS && key == VALIDATOR_SETS_STORAGE_PREFIX && set_type == BELOW_CAPACITY_VALIDATOR_SET_STORAGE_KEY && lazy_map == LAZY_MAP_SUB_KEY && data == lazy_map::DATA_SUBKEY) + matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(key), DbKeySeg::StringSeg(set_type), DbKeySeg::StringSeg(lazy_map), DbKeySeg::StringSeg(data), DbKeySeg::StringSeg(_epoch), DbKeySeg::StringSeg(_), DbKeySeg::StringSeg(_amount), DbKeySeg::StringSeg(_), DbKeySeg::StringSeg(_position)] if addr == &ADDRESS && key == VALIDATOR_SETS_STORAGE_PREFIX && set_type == BELOW_CAPACITY_VALIDATOR_SET_STORAGE_KEY && lazy_map == epoched::LAZY_MAP_SUB_KEY && data == lazy_map::DATA_SUBKEY) } /// Storage key for total consensus stake @@ -704,22 +862,16 @@ pub fn total_deltas_key() -> Key { } /// Is storage key for total deltas of all validators? -pub fn is_total_deltas_key(key: &Key) -> Option<&String> { - match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(key), - DbKeySeg::StringSeg(lazy_map), - DbKeySeg::StringSeg(data), - DbKeySeg::StringSeg(epoch), - ] if addr == &ADDRESS - && key == TOTAL_DELTAS_STORAGE_KEY - && lazy_map == LAZY_MAP_SUB_KEY - && data == lazy_map::DATA_SUBKEY => - { - Some(epoch) +pub fn is_total_deltas_key(key: &Key) -> bool { + if key.segments.len() >= 2 { + match &key.segments[..2] { + [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix)] => { + addr == &ADDRESS && prefix == TOTAL_DELTAS_STORAGE_KEY + } + _ => false, } - _ => None, + } else { + false } } @@ -772,6 +924,25 @@ pub fn last_pos_reward_claim_epoch_key( .expect("Cannot obtain a storage key") } +/// Is the storage key for epoch at which an account last claimed PoS +/// inflationary rewards? Return the bond ID if so. +pub fn is_last_pos_reward_claim_epoch_key(key: &Key) -> Option { + match &key.segments[..] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(key), + DbKeySeg::AddressSeg(source), + DbKeySeg::AddressSeg(validator), + ] if addr == &ADDRESS && key == LAST_REWARD_CLAIM_EPOCH => { + Some(BondId { + source: source.clone(), + validator: validator.clone(), + }) + } + _ => None, + } +} + /// Get validator address from bond key pub fn get_validator_address_from_bond(key: &Key) -> Option
{ match key.get_at(3) { @@ -790,6 +961,20 @@ pub fn validator_set_positions_key() -> Key { .expect("Cannot obtain a storage key") } +/// Is the storage key for validator set positions? +pub fn is_validator_set_positions_key(key: &Key) -> bool { + if key.segments.len() >= 2 { + match &key.segments[..2] { + [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix)] => { + addr == &ADDRESS && prefix == VALIDATOR_SET_POSITIONS_KEY + } + _ => false, + } + } else { + false + } +} + /// Storage key for consensus keys set. pub fn consensus_keys_key() -> Key { Key::from(ADDRESS.to_db_key()) diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index eee17fe63e..955969fb46 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -11,6 +11,8 @@ //! //! Any other storage key changes are allowed only with a valid signature. +use core::ops::Deref; + use namada_vp_prelude::*; use once_cell::unsync::Lazy; @@ -135,28 +137,7 @@ fn validate_tx( true } } - KeyType::PoS => { - // Allow the account to be used in PoS - let bond_id = proof_of_stake::storage::is_bond_key(key) - .map(|(bond_id, _)| bond_id) - .or_else(|| { - proof_of_stake::storage::is_unbond_key(key) - .map(|(bond_id, _, _)| bond_id) - }); - let is_valid_bond_or_unbond_change = match bond_id { - Some(bond_id) => { - // Bonds and unbonds changes for this address - // must be signed - bond_id.source != addr || *valid_sig - } - None => { - // Unknown changes are not allowed - false - } - }; - - is_valid_bond_or_unbond_change || *valid_sig - } + KeyType::PoS => validate_pos_changes(ctx, &addr, key, &valid_sig)?, KeyType::PgfSteward(address) => address != &addr || *valid_sig, KeyType::GovernanceVote(voter) => voter != &addr || *valid_sig, KeyType::Masp => true, @@ -174,6 +155,117 @@ fn validate_tx( accept() } +fn validate_pos_changes( + ctx: &Ctx, + owner: &Address, + key: &storage::Key, + valid_sig: &impl Deref, +) -> VpResult { + use proof_of_stake::storage; + + // Bond or unbond + let is_valid_bond_or_unbond_change = || { + let bond_id = storage::is_bond_key(key) + .map(|(bond_id, _)| bond_id) + .or_else(|| storage::is_bond_epoched_meta_key(key)) + .or_else(|| { + storage::is_unbond_key(key).map(|(bond_id, _, _)| bond_id) + }); + if let Some(bond_id) = bond_id { + // Bonds and unbonds changes for this address must be signed + return &bond_id.source != owner || **valid_sig; + }; + // Unknown changes are not allowed + false + }; + + // Changes in validator state + let is_valid_state_change = || { + let state_change = storage::is_validator_state_key(key); + let is_valid_state = match state_change { + Some((address, epoch)) => { + let params_pre = proof_of_stake::read_pos_params(&ctx.pre())?; + let state_pre = proof_of_stake::validator_state_handle(address) + .get(&ctx.pre(), epoch, ¶ms_pre)?; + + let params_post = proof_of_stake::read_pos_params(&ctx.post())?; + let state_post = proof_of_stake::validator_state_handle( + address, + ) + .get(&ctx.post(), epoch, ¶ms_post)?; + + match (state_pre, state_post) { + (Some(pre), Some(post)) => { + use proof_of_stake::types::ValidatorState::*; + + // Bonding and unbonding may affect validator sets + if matches!( + pre, + Consensus | BelowCapacity | BelowThreshold + ) && matches!( + post, + Consensus | BelowCapacity | BelowThreshold + ) { + true + } else { + // Unknown state changes are not allowed + false + } + } + (Some(_pre), None) => { + // Clearing of old epoched data + true + } + _ => false, + } + } + None => false, + }; + + VpResult::Ok( + is_valid_state + || storage::is_validator_state_epoched_meta_key(key) + || storage::is_consensus_validator_set_key(key) + || storage::is_below_capacity_validator_set_key(key), + ) + }; + + let is_valid_reward_claim = || { + if let Some(bond_id) = storage::is_last_pos_reward_claim_epoch_key(key) + { + // Claims for this address must be signed + return &bond_id.source != owner || **valid_sig; + } + false + }; + + let is_valid_redelegation = || { + if storage::is_validator_redelegations_key(key) { + return true; + } + if let Some(delegator) = storage::is_delegator_redelegations_key(key) { + // Redelegations for this address must be signed + return delegator != owner || **valid_sig; + } + if let Some(bond_id) = storage::is_rewards_counter_key(key) { + // Redelegations auto-claim rewards + return &bond_id.source != owner || **valid_sig; + } + false + }; + + Ok(is_valid_bond_or_unbond_change() + || storage::is_total_deltas_key(key) + || storage::is_validator_deltas_key(key) + || storage::is_validator_total_bond_or_unbond_key(key) + || storage::is_validator_set_positions_key(key) + || storage::is_total_consensus_stake_key(key) + || is_valid_state_change()? + || is_valid_reward_claim() + || is_valid_redelegation() + || **valid_sig) +} + #[cfg(test)] mod tests { // Use this as `#[test]` annotation to enable logging diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index 644428efec..a9fd3f5e2d 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -11,9 +11,10 @@ //! //! Any other storage key changes are allowed only with a valid signature. +use core::ops::Deref; + use namada_vp_prelude::*; use once_cell::unsync::Lazy; -use proof_of_stake::types::ValidatorState; enum KeyType<'a> { Token { owner: &'a Address }, @@ -106,107 +107,7 @@ fn validate_tx( true } } - KeyType::PoS => { - // Bond or unbond - let bond_id = proof_of_stake::storage::is_bond_key(key) - .map(|(bond_id, _)| bond_id) - .or_else(|| { - proof_of_stake::storage::is_unbond_key(key) - .map(|(bond_id, _, _)| bond_id) - }); - let is_valid_bond_or_unbond_change = match bond_id { - Some(bond_id) => { - // Bonds and unbonds changes for this address - // must be signed - bond_id.source != addr || *valid_sig - } - None => { - // Unknown changes are not allowed - false - } - }; - // Commission rate changes must be signed by the validator - let comm = - proof_of_stake::storage::is_validator_commission_rate_key( - key, - ); - let is_valid_commission_rate_change = match comm { - Some((validator, _epoch)) => { - *validator == addr && *valid_sig - } - None => false, - }; - // Metadata changes must be signed by the validator whose - // metadata is manipulated - let metadata = - proof_of_stake::storage::is_validator_metadata_key(key); - let is_valid_metadata_change = match metadata { - Some(address) => *address == addr && *valid_sig, - None => false, - }; - - // Changes due to unjailing, deactivating, and reactivating are - // marked by changes in validator state - let state_change = - proof_of_stake::storage::is_validator_state_key(key); - let is_valid_state_change = match state_change { - Some((address, epoch)) => { - let params_pre = - proof_of_stake::read_pos_params(&ctx.pre())?; - let state_pre = - proof_of_stake::validator_state_handle(address) - .get(&ctx.pre(), epoch, ¶ms_pre)?; - - let params_post = - proof_of_stake::read_pos_params(&ctx.post())?; - let state_post = - proof_of_stake::validator_state_handle(address) - .get(&ctx.post(), epoch, ¶ms_post)?; - - match (state_pre, state_post) { - (Some(pre), Some(post)) => { - if - // Deactivation case - (matches!( - pre, - ValidatorState::Consensus - | ValidatorState::BelowCapacity - | ValidatorState::BelowThreshold - ) && post == ValidatorState::Inactive) - // Reactivation case - || pre == ValidatorState::Inactive - && post != ValidatorState::Inactive - // Unjail case - || pre == ValidatorState::Jailed - && matches!( - post, - ValidatorState::Consensus - | ValidatorState::BelowCapacity - | ValidatorState::BelowThreshold - ) - { - *address == addr && *valid_sig - } else { - // Unknown state changes are not allowed - false - } - } - (None, Some(_post)) => { - // Becoming a validator must be authorized - *valid_sig - } - _ => false, - } - } - None => false, - }; - - is_valid_bond_or_unbond_change - || is_valid_commission_rate_change - || is_valid_state_change - || is_valid_metadata_change - || *valid_sig - } + KeyType::PoS => validate_pos_changes(ctx, &addr, key, &valid_sig)?, KeyType::PgfSteward(address) => address != &addr || *valid_sig, KeyType::GovernanceVote(voter) => voter != &addr || *valid_sig, KeyType::Vp(owner) => { @@ -239,6 +140,177 @@ fn validate_tx( accept() } +fn validate_pos_changes( + ctx: &Ctx, + owner: &Address, + key: &storage::Key, + valid_sig: &impl Deref, +) -> VpResult { + use proof_of_stake::storage; + + // Bond or unbond + let is_valid_bond_or_unbond_change = || { + let bond_id = storage::is_bond_key(key) + .map(|(bond_id, _)| bond_id) + .or_else(|| storage::is_bond_epoched_meta_key(key)) + .or_else(|| { + storage::is_unbond_key(key).map(|(bond_id, _, _)| bond_id) + }); + if let Some(bond_id) = bond_id { + // Bonds and unbonds changes for this address must be signed + return &bond_id.source != owner || **valid_sig; + }; + // Unknown changes are not allowed + false + }; + + // Commission rate changes must be signed by the validator + let is_valid_commission_rate_change = || { + if let Some(validator) = storage::is_validator_commission_rate_key(key) + { + return validator == owner && **valid_sig; + } + false + }; + + // Metadata changes must be signed by the validator whose + // metadata is manipulated + let is_valid_metadata_change = || { + let metadata = storage::is_validator_metadata_key(key); + match metadata { + Some(address) => address == owner && **valid_sig, + None => false, + } + }; + + // Changes in validator state + let is_valid_state_change = || { + let state_change = storage::is_validator_state_key(key); + let is_valid_state = match state_change { + Some((address, epoch)) => { + let params_pre = proof_of_stake::read_pos_params(&ctx.pre())?; + let state_pre = proof_of_stake::validator_state_handle(address) + .get(&ctx.pre(), epoch, ¶ms_pre)?; + + let params_post = proof_of_stake::read_pos_params(&ctx.post())?; + let state_post = proof_of_stake::validator_state_handle( + address, + ) + .get(&ctx.post(), epoch, ¶ms_post)?; + + match (state_pre, state_post) { + (Some(pre), Some(post)) => { + use proof_of_stake::types::ValidatorState::*; + + if ( + // Deactivation case + matches!( + pre, + Consensus | BelowCapacity | BelowThreshold + ) && post == Inactive) + // Reactivation case + || pre == Inactive && post != Inactive + // Unjail case + || pre == Jailed + && matches!( + post, + Consensus + | BelowCapacity + | BelowThreshold + ) + { + address == owner && **valid_sig + } else if + // Bonding and unbonding may affect validator sets + matches!( + pre, + Consensus | BelowCapacity | BelowThreshold + ) && matches!( + post, + Consensus | BelowCapacity | BelowThreshold + ) { + true + } else { + // Unknown state changes are not allowed + false + } + } + (None, Some(_post)) => { + // Becoming a validator must be authorized + **valid_sig + } + (Some(_pre), None) => { + // Clearing of old epoched data + true + } + _ => false, + } + } + None => false, + }; + + VpResult::Ok( + is_valid_state + || storage::is_validator_state_epoched_meta_key(key) + || storage::is_consensus_validator_set_key(key) + || storage::is_below_capacity_validator_set_key(key), + ) + }; + + let is_valid_reward_claim = || { + if let Some(bond_id) = storage::is_last_pos_reward_claim_epoch_key(key) + { + // Claims for this address must be signed + return &bond_id.source != owner || **valid_sig; + } + false + }; + + let is_valid_redelegation = || { + if storage::is_validator_redelegations_key(key) { + return true; + } + if let Some(delegator) = storage::is_delegator_redelegations_key(key) { + // Redelegations for this address must be signed + return delegator != owner || **valid_sig; + } + if let Some(bond_id) = storage::is_rewards_counter_key(key) { + // Redelegations auto-claim rewards + return &bond_id.source != owner || **valid_sig; + } + false + }; + + let is_valid_become_validator = || { + if storage::is_validator_addresses_key(key) + || storage::is_consensus_keys_key(key) + || storage::is_validator_eth_cold_key_key(key).is_some() + || storage::is_validator_eth_hot_key_key(key).is_some() + || storage::is_validator_max_commission_rate_change_key(key) + .is_some() + || storage::is_validator_address_raw_hash_key(key).is_some() + { + // A signature is required to become validator + return **valid_sig; + } + false + }; + + Ok(is_valid_bond_or_unbond_change() + || storage::is_total_deltas_key(key) + || storage::is_validator_deltas_key(key) + || storage::is_validator_total_bond_or_unbond_key(key) + || storage::is_validator_set_positions_key(key) + || storage::is_total_consensus_stake_key(key) + || is_valid_state_change()? + || is_valid_reward_claim() + || is_valid_redelegation() + || is_valid_commission_rate_change() + || is_valid_metadata_change() + || is_valid_become_validator() + || **valid_sig) +} + #[cfg(test)] mod tests { use address::testing::arb_non_internal_address; From c09699a45f55de17f892e1482946870fc127e494 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 24 Nov 2023 20:25:57 +0100 Subject: [PATCH 041/216] wasm/vp_user+vp_implicit: always print rejected keys --- wasm/wasm_source/src/vp_implicit.rs | 2 +- wasm/wasm_source/src/vp_user.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index 955969fb46..94cd6ef024 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -147,7 +147,7 @@ fn validate_tx( } }; if !is_valid { - debug_log!("key {} modification failed vp", key); + log_string(format!("key {} modification failed vp_implicit", key)); return reject(); } } diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index a9fd3f5e2d..7e8153cc5e 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -132,7 +132,7 @@ fn validate_tx( } }; if !is_valid { - debug_log!("key {} modification failed vp", key); + log_string(format!("key {} modification failed vp_user", key)); return reject(); } } From 648fde2a994e26b357ba430a0b1bf44e5cb2eb8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 27 Nov 2023 10:11:46 +0100 Subject: [PATCH 042/216] benches: fix foreign key write tx sigs --- benches/vps.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/benches/vps.rs b/benches/vps.rs index f5a035e542..b25fce372b 100644 --- a/benches/vps.rs +++ b/benches/vps.rs @@ -182,8 +182,7 @@ fn vp_implicit(c: &mut Criterion) { .try_to_sk() .unwrap(); - let foreign_key_write = - generate_foreign_key_tx(&defaults::albert_keypair()); + let foreign_key_write = generate_foreign_key_tx(&implicit_account); let shell = BenchShell::default(); let transfer = shell.generate_tx( @@ -327,7 +326,7 @@ fn vp_validator(c: &mut Criterion) { let mut group = c.benchmark_group("vp_validator"); let foreign_key_write = - generate_foreign_key_tx(&defaults::albert_keypair()); + generate_foreign_key_tx(&defaults::validator_keypair()); let transfer = shell.generate_tx( TX_TRANSFER_WASM, From 06d5895aac4f42aebd80f2790d6771cb609f5605 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 27 Nov 2023 16:44:15 +0100 Subject: [PATCH 043/216] wasm/vp_user+vp_implicit: update for IBC actions --- core/src/types/address.rs | 2 ++ core/src/types/ibc.rs | 1 + wasm/wasm_source/src/vp_implicit.rs | 19 +++++++++++++++---- wasm/wasm_source/src/vp_user.rs | 19 +++++++++++++++---- 4 files changed, 33 insertions(+), 8 deletions(-) diff --git a/core/src/types/address.rs b/core/src/types/address.rs index b348451996..1d3cd165d2 100644 --- a/core/src/types/address.rs +++ b/core/src/types/address.rs @@ -62,6 +62,8 @@ pub const POS_SLASH_POOL: Address = pub const GOV: Address = Address::Internal(InternalAddress::Governance); /// Internal MASP address pub const MASP: Address = Address::Internal(InternalAddress::Masp); +/// Internal Multitoken address +pub const MULTITOKEN: Address = Address::Internal(InternalAddress::Multitoken); /// Error from decoding address from string pub type DecodeError = string_encoding::DecodeError; diff --git a/core/src/types/ibc.rs b/core/src/types/ibc.rs index 2f04e11709..25e5e3cf23 100644 --- a/core/src/types/ibc.rs +++ b/core/src/types/ibc.rs @@ -15,6 +15,7 @@ use crate::ibc::apps::transfer::types::{Memo, PrefixedDenom, TracePath}; use crate::ibc::core::handler::types::events::{ Error as IbcEventError, IbcEvent as RawIbcEvent, }; +pub use crate::ledger::ibc::storage::is_ibc_key; use crate::tendermint::abci::Event as AbciEvent; use crate::types::masp::PaymentAddress; diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index 94cd6ef024..84d3d7699d 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -19,13 +19,16 @@ use once_cell::unsync::Lazy; enum KeyType<'a> { /// Public key - written once revealed Pk(&'a Address), - Token { + TokenBalance { owner: &'a Address, }, + TokenMinted, + TokenMinter(&'a Address), PoS, Masp, PgfSteward(&'a Address), GovernanceVote(&'a Address), + Ibc, Unknown, } @@ -34,7 +37,11 @@ impl<'a> From<&'a storage::Key> for KeyType<'a> { if let Some(address) = key::is_pks_key(key) { Self::Pk(address) } else if let Some([_, owner]) = token::is_any_token_balance_key(key) { - Self::Token { owner } + Self::TokenBalance { owner } + } else if token::is_any_minted_balance_key(key).is_some() { + Self::TokenMinted + } else if let Some(minter) = token::is_any_minter_key(key) { + Self::TokenMinter(minter) } else if proof_of_stake::storage::is_pos_key(key) { Self::PoS } else if let Some(address) = pgf_storage::keys::is_stewards_key(key) { @@ -48,6 +55,8 @@ impl<'a> From<&'a storage::Key> for KeyType<'a> { } } else if token::is_masp_key(key) { Self::Masp + } else if ibc::is_ibc_key(key) { + Self::Ibc } else { Self::Unknown } @@ -105,7 +114,7 @@ fn validate_tx( } true } - KeyType::Token { owner, .. } => { + KeyType::TokenBalance { owner, .. } => { if owner == &addr { let pre: token::Amount = ctx.read_pre(key)?.unwrap_or_default(); @@ -137,10 +146,12 @@ fn validate_tx( true } } + KeyType::TokenMinted => verifiers.contains(&address::MULTITOKEN), + KeyType::TokenMinter(minter) => minter != &addr || *valid_sig, KeyType::PoS => validate_pos_changes(ctx, &addr, key, &valid_sig)?, KeyType::PgfSteward(address) => address != &addr || *valid_sig, KeyType::GovernanceVote(voter) => voter != &addr || *valid_sig, - KeyType::Masp => true, + KeyType::Masp | KeyType::Ibc => true, KeyType::Unknown => { // Unknown changes require a valid signature *valid_sig diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index 7e8153cc5e..a590db87e0 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -17,19 +17,26 @@ use namada_vp_prelude::*; use once_cell::unsync::Lazy; enum KeyType<'a> { - Token { owner: &'a Address }, + TokenBalance { owner: &'a Address }, + TokenMinted, + TokenMinter(&'a Address), PoS, Vp(&'a Address), Masp, PgfSteward(&'a Address), GovernanceVote(&'a Address), + Ibc, Unknown, } impl<'a> From<&'a storage::Key> for KeyType<'a> { fn from(key: &'a storage::Key) -> KeyType<'a> { if let Some([_, owner]) = token::is_any_token_balance_key(key) { - Self::Token { owner } + Self::TokenBalance { owner } + } else if token::is_any_minted_balance_key(key).is_some() { + Self::TokenMinted + } else if let Some(minter) = token::is_any_minter_key(key) { + Self::TokenMinter(minter) } else if proof_of_stake::storage::is_pos_key(key) { Self::PoS } else if gov_storage::keys::is_vote_key(key) { @@ -45,6 +52,8 @@ impl<'a> From<&'a storage::Key> for KeyType<'a> { Self::Vp(address) } else if token::is_masp_key(key) { Self::Masp + } else if ibc::is_ibc_key(key) { + Self::Ibc } else { Self::Unknown } @@ -77,7 +86,7 @@ fn validate_tx( for key in keys_changed.iter() { let key_type: KeyType = key.into(); let is_valid = match key_type { - KeyType::Token { owner, .. } => { + KeyType::TokenBalance { owner, .. } => { if owner == &addr { let pre: token::Amount = ctx.read_pre(key)?.unwrap_or_default(); @@ -107,6 +116,8 @@ fn validate_tx( true } } + KeyType::TokenMinted => verifiers.contains(&address::MULTITOKEN), + KeyType::TokenMinter(minter) => minter != &addr || *valid_sig, KeyType::PoS => validate_pos_changes(ctx, &addr, key, &valid_sig)?, KeyType::PgfSteward(address) => address != &addr || *valid_sig, KeyType::GovernanceVote(voter) => voter != &addr || *valid_sig, @@ -125,7 +136,7 @@ fn validate_tx( is_vp_whitelisted(ctx, &vp_hash)? } } - KeyType::Masp => true, + KeyType::Masp | KeyType::Ibc => true, KeyType::Unknown => { // Unknown changes require a valid signature *valid_sig From 763ea166f1a9a19d825627afcacf89efadc559f6 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 6 Dec 2023 18:19:17 -0500 Subject: [PATCH 044/216] additions from comments --- wasm/wasm_source/src/vp_user.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index a590db87e0..d355e336e2 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -248,7 +248,7 @@ fn validate_pos_changes( } (None, Some(_post)) => { // Becoming a validator must be authorized - **valid_sig + address == owner && **valid_sig } (Some(_pre), None) => { // Clearing of old epoched data @@ -274,6 +274,10 @@ fn validate_pos_changes( // Claims for this address must be signed return &bond_id.source != owner || **valid_sig; } + if let Some(bond_id) = storage::is_rewards_counter_key(key) { + // Claims for this address must be signed + return &bond_id.source != owner || **valid_sig; + } false }; From ef1894c82b5d77ac7452045e0392e44b1cb950a6 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Fri, 8 Dec 2023 18:56:48 +0200 Subject: [PATCH 045/216] Added a unit test for denominated arithmetic. --- core/src/types/token.rs | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/core/src/types/token.rs b/core/src/types/token.rs index 6364bc8b03..2730f71e18 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -324,8 +324,8 @@ pub struct DenominatedAmount { } impl DenominatedAmount { - /// Make a new denominated amount - pub fn new(amount: Amount, denom: Denomination) -> Self { + /// Make a new denominated amount representing amount*10^(-denom) + pub const fn new(amount: Amount, denom: Denomination) -> Self { Self { amount, denom } } @@ -418,9 +418,8 @@ impl DenominatedAmount { "No denomination found in storage for the given token", ) })?; - self.increase_precision(denom) + self.scale(denom) .map_err(storage_api::Error::new) - .map(|x| x.amount) } /// Multiply this number by 10^denom and return the computed integer if @@ -471,12 +470,12 @@ impl DenominatedAmount { } /// Returns the significand of this number - pub fn amount(&self) -> Amount { + pub const fn amount(&self) -> Amount { self.amount } /// Returns the denomination of this number - pub fn denom(&self) -> Denomination { + pub const fn denom(&self) -> Denomination { self.denom } } @@ -1481,6 +1480,23 @@ mod tests { assert_eq!(three.mul_ceil(dec), two); } + #[test] + fn test_denominateed_arithmetic() { + let a = DenominatedAmount::new(10.into(), 3.into()); + let b = DenominatedAmount::new(10.into(), 2.into()); + let c = DenominatedAmount::new(110.into(), 3.into()); + let d = DenominatedAmount::new(90.into(), 3.into()); + let e = DenominatedAmount::new(100.into(), 5.into()); + let f = DenominatedAmount::new(100.into(), 3.into()); + let g = DenominatedAmount::new(0.into(), 3.into()); + assert_eq!(a.checked_add(b).unwrap(), c); + assert_eq!(b.checked_sub(a).unwrap(), d); + assert_eq!(a.checked_mul(b).unwrap(), e); + assert!(a.checked_sub(b).is_none()); + assert_eq!(c.checked_sub(a).unwrap(), f); + assert_eq!(c.checked_sub(c).unwrap(), g); + } + #[test] fn test_denominated_amt_ord() { let denom_1 = DenominatedAmount { From 41acfb9d3b14cf879138a37b21cca9ad705a503a Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Sat, 9 Dec 2023 10:40:06 +0200 Subject: [PATCH 046/216] Add test vector printers for bridge pool transfers, resign steward, update steward commission, and redelegate. --- core/src/types/eth_bridge_pool.rs | 9 +++ core/src/types/token.rs | 9 ++- sdk/src/signing.rs | 120 ++++++++++++++++++++++++++++-- 3 files changed, 130 insertions(+), 8 deletions(-) diff --git a/core/src/types/eth_bridge_pool.rs b/core/src/types/eth_bridge_pool.rs index 8e533ea262..43b71b204c 100644 --- a/core/src/types/eth_bridge_pool.rs +++ b/core/src/types/eth_bridge_pool.rs @@ -55,6 +55,15 @@ pub enum TransferToEthereumKind { Nut, } +impl std::fmt::Display for TransferToEthereumKind { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::Erc20 => write!(f, "ERC20"), + Self::Nut => write!(f, "NUT"), + } + } +} + /// Additional data appended to a [`TransferToEthereumEvent`] to /// construct a [`PendingTransfer`]. #[derive( diff --git a/core/src/types/token.rs b/core/src/types/token.rs index 2730f71e18..09705d5707 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -271,6 +271,12 @@ impl Amount { } } +impl Display for Amount { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.raw) + } +} + /// Given a number represented as `M*B^D`, then /// `M` is the matissa, `B` is the base and `D` /// is the denomination, represented by this stuct. @@ -418,8 +424,7 @@ impl DenominatedAmount { "No denomination found in storage for the given token", ) })?; - self.scale(denom) - .map_err(storage_api::Error::new) + self.scale(denom).map_err(storage_api::Error::new) } /// Multiply this number by 10^denom and return the computed integer if diff --git a/sdk/src/signing.rs b/sdk/src/signing.rs index 59c32c09dd..acbe1ed255 100644 --- a/sdk/src/signing.rs +++ b/sdk/src/signing.rs @@ -27,6 +27,7 @@ use namada_core::types::transaction::account::{InitAccount, UpdateAccount}; use namada_core::types::transaction::governance::{ InitProposalData, VoteProposalData, }; +use namada_core::types::transaction::pgf::UpdateStewardCommission; use namada_core::types::transaction::pos::InitValidator; use namada_core::types::transaction::{pos, Fee}; use prost::Message; @@ -40,6 +41,7 @@ use crate::core::ledger::governance::storage::proposal::ProposalType; use crate::core::ledger::governance::storage::vote::{ StorageProposalVote, VoteType, }; +use crate::core::types::eth_bridge_pool::PendingTransfer; use crate::error::{EncodingError, Error, TxError}; use crate::ibc::applications::transfer::msgs::transfer::MsgTransfer; use crate::ibc_proto::google::protobuf::Any; @@ -48,13 +50,14 @@ use crate::masp::make_asset_type; use crate::proto::{MaspBuilder, Section, Tx}; use crate::rpc::validate_amount; use crate::tx::{ - TX_BOND_WASM, TX_CHANGE_COMMISSION_WASM, TX_CHANGE_CONSENSUS_KEY_WASM, - TX_CHANGE_METADATA_WASM, TX_CLAIM_REWARDS_WASM, - TX_DEACTIVATE_VALIDATOR_WASM, TX_IBC_WASM, TX_INIT_ACCOUNT_WASM, - TX_INIT_PROPOSAL, TX_INIT_VALIDATOR_WASM, TX_REACTIVATE_VALIDATOR_WASM, + TX_BOND_WASM, TX_BRIDGE_POOL_WASM, TX_CHANGE_COMMISSION_WASM, + TX_CHANGE_CONSENSUS_KEY_WASM, TX_CHANGE_METADATA_WASM, + TX_CLAIM_REWARDS_WASM, TX_DEACTIVATE_VALIDATOR_WASM, TX_IBC_WASM, + TX_INIT_ACCOUNT_WASM, TX_INIT_PROPOSAL, TX_INIT_VALIDATOR_WASM, + TX_REACTIVATE_VALIDATOR_WASM, TX_REDELEGATE_WASM, TX_RESIGN_STEWARD, TX_REVEAL_PK, TX_TRANSFER_WASM, TX_UNBOND_WASM, TX_UNJAIL_VALIDATOR_WASM, - TX_UPDATE_ACCOUNT_WASM, TX_VOTE_PROPOSAL, TX_WITHDRAW_WASM, VP_USER_WASM, - VP_VALIDATOR_WASM, + TX_UPDATE_ACCOUNT_WASM, TX_UPDATE_STEWARD_COMMISSION, TX_VOTE_PROPOSAL, + TX_WITHDRAW_WASM, VP_USER_WASM, VP_VALIDATOR_WASM, }; pub use crate::wallet::store::AddressVpType; use crate::wallet::{Wallet, WalletIo}; @@ -1720,6 +1723,111 @@ pub async fn to_ledger_vector<'a>( ]); tv.output_expert.push(format!("Validator : {}", address)); + } else if code_sec.tag == Some(TX_REDELEGATE_WASM.to_string()) { + let redelegation = pos::Redelegation::try_from_slice( + &tx.data() + .ok_or_else(|| Error::Other("Invalid Data".to_string()))?, + ) + .map_err(|err| { + Error::from(EncodingError::Conversion(err.to_string())) + })?; + + tv.name = "Redelegate_0".to_string(); + + tv.output.extend(vec![ + format!("Type : Redelegate"), + format!("Source Validator : {}", redelegation.src_validator), + format!("Destination Validator : {}", redelegation.dest_validator), + format!("Owner : {}", redelegation.owner), + format!( + "Amount : {}", + to_ledger_decimal(&redelegation.amount.to_string_native()) + ), + ]); + + tv.output_expert.extend(vec![ + format!("Source Validator : {}", redelegation.src_validator), + format!("Destination Validator : {}", redelegation.dest_validator), + format!("Owner : {}", redelegation.owner), + format!( + "Amount : {}", + to_ledger_decimal(&redelegation.amount.to_string_native()) + ), + ]); + } else if code_sec.tag == Some(TX_UPDATE_STEWARD_COMMISSION.to_string()) { + let update = UpdateStewardCommission::try_from_slice( + &tx.data() + .ok_or_else(|| Error::Other("Invalid Data".to_string()))?, + ) + .map_err(|err| { + Error::from(EncodingError::Conversion(err.to_string())) + })?; + + tv.name = "Update_Steward_Commission_0".to_string(); + tv.output.extend(vec![ + format!("Type : Update Steward Commission"), + format!("Steward : {}", update.steward), + ]); + for (address, dec) in &update.commission { + tv.output.push(format!("Commission : {} {}", address, dec)); + } + + tv.output_expert + .push(format!("Steward : {}", update.steward)); + for (address, dec) in &update.commission { + tv.output_expert + .push(format!("Commission : {} {}", address, dec)); + } + } else if code_sec.tag == Some(TX_RESIGN_STEWARD.to_string()) { + let address = Address::try_from_slice( + &tx.data() + .ok_or_else(|| Error::Other("Invalid Data".to_string()))?, + ) + .map_err(|err| { + Error::from(EncodingError::Conversion(err.to_string())) + })?; + + tv.name = "Resign_Steward_0".to_string(); + + tv.output.extend(vec![ + format!("Type : Resign Steward"), + format!("Steward : {}", address), + ]); + + tv.output_expert.push(format!("Steward : {}", address)); + } else if code_sec.tag == Some(TX_BRIDGE_POOL_WASM.to_string()) { + let transfer = PendingTransfer::try_from_slice( + &tx.data() + .ok_or_else(|| Error::Other("Invalid Data".to_string()))?, + ) + .map_err(|err| { + Error::from(EncodingError::Conversion(err.to_string())) + })?; + + tv.name = "Bridge_Pool_Transfer_0".to_string(); + + tv.output.extend(vec![ + format!("Type : Bridge Pool Transfer"), + format!("Transfer Kind : {}", transfer.transfer.kind), + format!("Transfer Sender : {}", transfer.transfer.sender), + format!("Transfer Recipient : {}", transfer.transfer.recipient), + format!("Transfer Asset : {}", transfer.transfer.asset), + format!("Transfer Amount : {}", transfer.transfer.amount), + format!("Gas Payer : {}", transfer.gas_fee.payer), + format!("Gas Token : {}", transfer.gas_fee.token), + format!("Gas Amount : {}", transfer.gas_fee.amount), + ]); + + tv.output_expert.extend(vec![ + format!("Transfer Kind : {}", transfer.transfer.kind), + format!("Transfer Sender : {}", transfer.transfer.sender), + format!("Transfer Recipient : {}", transfer.transfer.recipient), + format!("Transfer Asset : {}", transfer.transfer.asset), + format!("Transfer Amount : {}", transfer.transfer.amount), + format!("Gas Payer : {}", transfer.gas_fee.payer), + format!("Gas Token : {}", transfer.gas_fee.token), + format!("Gas Amount : {}", transfer.gas_fee.amount), + ]); } else { tv.name = "Custom_0".to_string(); tv.output.push("Type : Custom".to_string()); From ff273461bb4de9302754013701c7ee932b62340d Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Sat, 9 Dec 2023 13:55:34 +0200 Subject: [PATCH 047/216] Added changelog entry. --- .../unreleased/improvements/2245-denominated-fee-amount.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/2245-denominated-fee-amount.md diff --git a/.changelog/unreleased/improvements/2245-denominated-fee-amount.md b/.changelog/unreleased/improvements/2245-denominated-fee-amount.md new file mode 100644 index 0000000000..718bef820f --- /dev/null +++ b/.changelog/unreleased/improvements/2245-denominated-fee-amount.md @@ -0,0 +1,2 @@ +- Fee amounts in transaction wrappers are now denominated to facilitate hardware + wallet support. ([\#2245](https://github.com/anoma/namada/pull/2245)) \ No newline at end of file From 2e367edaf0c0308bfd97be650e3a5eec8b9c56ce Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Sat, 9 Dec 2023 22:43:05 +0200 Subject: [PATCH 048/216] Added proptest strategies for generating transactions. --- Cargo.lock | 1 + .../src/ledger/governance/storage/proposal.rs | 60 ++ core/src/ledger/governance/storage/vote.rs | 30 + core/src/ledger/ibc/mod.rs | 176 +++++ core/src/types/dec.rs | 21 + core/src/types/eth_bridge_pool.rs | 71 +++ core/src/types/ethereum_events.rs | 9 + core/src/types/hash.rs | 14 +- core/src/types/key/mod.rs | 17 + core/src/types/storage.rs | 14 +- core/src/types/token.rs | 46 +- core/src/types/transaction/account.rs | 48 ++ core/src/types/transaction/governance.rs | 53 ++ core/src/types/transaction/pgf.rs | 23 + core/src/types/transaction/pos.rs | 135 ++++ sdk/Cargo.toml | 3 + sdk/src/lib.rs | 601 ++++++++++++++++++ wasm/Cargo.lock | 1 + wasm_for_tests/wasm_source/Cargo.lock | 1 + 19 files changed, 1318 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d5aa62a40a..91ee00c9df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4451,6 +4451,7 @@ dependencies = [ "owo-colors", "parse_duration", "paste", + "proptest", "prost 0.12.3", "rand 0.8.5", "rand_core 0.6.4", diff --git a/core/src/ledger/governance/storage/proposal.rs b/core/src/ledger/governance/storage/proposal.rs index c4a59389ef..04e30b52c8 100644 --- a/core/src/ledger/governance/storage/proposal.rs +++ b/core/src/ledger/governance/storage/proposal.rs @@ -311,3 +311,63 @@ impl Display for StorageProposal { ) } } + +#[cfg(any(test, feature = "testing"))] +/// Tests and strategies for governance proposals +pub mod tests { + use proptest::prelude::Strategy; + use proptest::{collection, option, prop_compose}; + + use super::*; + use crate::types::address::testing::arb_address; + use crate::types::hash::tests::arb_hash; + use crate::types::token::testing::arb_amount; + + /// Generate an arbitrary add or removal of what's generated by the supplied + /// strategy + pub fn arb_add_remove( + strategy: X, + ) -> impl Strategy::Value>> { + (0..2, strategy).prop_map(|(discriminant, val)| match discriminant { + 0 => AddRemove::Add(val), + 1 => AddRemove::Remove(val), + _ => unreachable!(), + }) + } + + prop_compose! { + /// Generate an arbitrary PGF target + pub fn arb_pgf_target()( + target in arb_address(), + amount in arb_amount(), + ) -> PGFTarget { + PGFTarget { + target, + amount, + } + } + } + + /// Generate an arbitrary PGF action + pub fn arb_pgf_action() -> impl Strategy { + arb_add_remove(arb_pgf_target()) + .prop_map(PGFAction::Continuous) + .boxed() + .prop_union(arb_pgf_target().prop_map(PGFAction::Retro).boxed()) + } + + /// Generate an arbitrary proposal type + pub fn arb_proposal_type() -> impl Strategy { + option::of(arb_hash()) + .prop_map(ProposalType::Default) + .boxed() + .prop_union( + collection::hash_set(arb_add_remove(arb_address()), 0..10) + .prop_map(ProposalType::PGFSteward) + .boxed(), + ) + .or(collection::vec(arb_pgf_action(), 0..10) + .prop_map(ProposalType::PGFPayment) + .boxed()) + } +} diff --git a/core/src/ledger/governance/storage/vote.rs b/core/src/ledger/governance/storage/vote.rs index 780d4fdf2e..ed6b5e0318 100644 --- a/core/src/ledger/governance/storage/vote.rs +++ b/core/src/ledger/governance/storage/vote.rs @@ -144,3 +144,33 @@ impl PartialEq for ProposalType { } } } + +#[cfg(any(test, feature = "testing"))] +/// Tests and strategies for governance votes +pub mod tests { + use proptest::prelude::{Just, Strategy}; + use proptest::prop_compose; + + use super::*; + + prop_compose! { + /// Geerate an arbitrary vote type + pub fn arb_vote_type()(discriminant in 0..3) -> VoteType { + match discriminant { + 0 => VoteType::Default, + 1 => VoteType::PGFSteward, + 2 => VoteType::PGFPayment, + _ => unreachable!(), + } + } + } + + /// Generate an arbitrary proposal vote + pub fn arb_proposal_vote() -> impl Strategy { + arb_vote_type() + .prop_map(StorageProposalVote::Yay) + .boxed() + .prop_union(Just(StorageProposalVote::Nay).boxed()) + .or(Just(StorageProposalVote::Abstain).boxed()) + } +} diff --git a/core/src/ledger/ibc/mod.rs b/core/src/ledger/ibc/mod.rs index 1b01017d3f..dfe61b7935 100644 --- a/core/src/ledger/ibc/mod.rs +++ b/core/src/ledger/ibc/mod.rs @@ -296,3 +296,179 @@ pub fn received_ibc_token( } Ok(storage::ibc_token(ibc_denom.to_string())) } + +#[cfg(any(test, feature = "testing"))] +/// Tests ans strategies for IBC +pub mod tests { + use std::str::FromStr; + + use ibc::apps::transfer::types::packet::PacketData; + use ibc::apps::transfer::types::{ + Amount, BaseDenom, Memo, PrefixedCoin, PrefixedDenom, TracePath, + TracePrefix, + }; + use ibc::core::channel::types::timeout::TimeoutHeight; + use ibc::core::client::types::Height; + use ibc::core::host::types::identifiers::{ChannelId, PortId}; + use ibc::core::primitives::Signer; + use ibc::primitives::proto::Any; + use ibc::primitives::{Msg, Timestamp}; + use proptest::prelude::{Just, Strategy}; + use proptest::{collection, prop_compose}; + + use crate::ibc::apps::transfer::types::msgs::transfer::MsgTransfer; + + prop_compose! { + /// Generate an arbitrary port ID + pub fn arb_ibc_port_id()(id in "[a-zA-Z0-9_+.\\-\\[\\]#<>]{2,128}") -> PortId { + PortId::new(id).expect("generated invalid port ID") + } + } + + prop_compose! { + /// Generate an arbitrary channel ID + pub fn arb_ibc_channel_id()(id: u64) -> ChannelId { + ChannelId::new(id) + } + } + + prop_compose! { + /// Generate an arbitrary IBC height + pub fn arb_ibc_height()( + revision_number: u64, + revision_height in 1u64.., + ) -> Height { + Height::new(revision_number, revision_height) + .expect("generated invalid IBC height") + } + } + + /// Generate arbitrary timeout data + pub fn arb_ibc_timeout_data() -> impl Strategy { + arb_ibc_height() + .prop_map(TimeoutHeight::At) + .boxed() + .prop_union(Just(TimeoutHeight::Never).boxed()) + } + + prop_compose! { + /// Generate an arbitrary IBC timestamp + pub fn arb_ibc_timestamp()(nanoseconds: u64) -> Timestamp { + Timestamp::from_nanoseconds(nanoseconds).expect("generated invalid IBC timestamp") + } + } + + prop_compose! { + /// Generate an arbitrary IBC memo + pub fn arb_ibc_memo()(memo in "[a-zA-Z0-9_]*") -> Memo { + memo.into() + } + } + + prop_compose! { + /// Generate an arbitrary IBC memo + pub fn arb_ibc_signer()(signer in "[a-zA-Z0-9_]*") -> Signer { + signer.into() + } + } + + prop_compose! { + /// Generate an arbitrary IBC trace prefix + pub fn arb_ibc_trace_prefix()( + port_id in arb_ibc_port_id(), + channel_id in arb_ibc_channel_id(), + ) -> TracePrefix { + TracePrefix::new(port_id, channel_id) + } + } + + prop_compose! { + /// Generate an arbitrary IBC trace path + pub fn arb_ibc_trace_path()(path in collection::vec(arb_ibc_trace_prefix(), 0..10)) -> TracePath { + TracePath::from(path) + } + } + + prop_compose! { + /// Generate an arbitrary IBC base denomination + pub fn arb_ibc_base_denom()(base_denom in "[a-zA-Z0-9_]+") -> BaseDenom { + BaseDenom::from_str(&base_denom).expect("generated invalid IBC base denomination") + } + } + + prop_compose! { + /// Generate an arbitrary IBC prefixed denomination + pub fn arb_ibc_prefixed_denom()( + trace_path in arb_ibc_trace_path(), + base_denom in arb_ibc_base_denom(), + ) -> PrefixedDenom { + PrefixedDenom { + trace_path, + base_denom, + } + } + } + + prop_compose! { + /// Generate an arbitrary IBC amount + pub fn arb_ibc_amount()(value: [u64; 4]) -> Amount { + value.into() + } + } + + prop_compose! { + /// Generate an arbitrary prefixed coin + pub fn arb_ibc_prefixed_coin()( + denom in arb_ibc_prefixed_denom(), + amount in arb_ibc_amount(), + ) -> PrefixedCoin { + PrefixedCoin { + denom, + amount, + } + } + } + + prop_compose! { + /// Generate arbitrary packet data + pub fn arb_ibc_packet_data()( + token in arb_ibc_prefixed_coin(), + sender in arb_ibc_signer(), + receiver in arb_ibc_signer(), + memo in arb_ibc_memo(), + ) -> PacketData { + PacketData { + token, + sender, + receiver, + memo, + } + } + } + + prop_compose! { + /// Generate an arbitrary IBC transfer message + pub fn arb_ibc_msg_transfer()( + port_id_on_a in arb_ibc_port_id(), + chan_id_on_a in arb_ibc_channel_id(), + packet_data in arb_ibc_packet_data(), + timeout_height_on_b in arb_ibc_timeout_data(), + timeout_timestamp_on_b in arb_ibc_timestamp(), + ) -> MsgTransfer { + MsgTransfer { + port_id_on_a, + chan_id_on_a, + packet_data, + timeout_height_on_b, + timeout_timestamp_on_b, + } + } + } + + prop_compose! { + /// Generate an arbitrary IBC any object + pub fn arb_ibc_any()(msg_transfer in arb_ibc_msg_transfer()) -> Any { + msg_transfer.to_any() + } + } +} diff --git a/core/src/types/dec.rs b/core/src/types/dec.rs index 3a77b991e8..bfb635b4a3 100644 --- a/core/src/types/dec.rs +++ b/core/src/types/dec.rs @@ -491,6 +491,27 @@ pub mod testing { |(mantissa, scale)| Dec::new(mantissa.into(), scale).unwrap(), ) } + + prop_compose! { + /// Generate an arbitrary uint + pub fn arb_uint()(value: [u64; 4]) -> Uint { + Uint(value) + } + } + + prop_compose! { + /// Generate an arbitrary signed 256-bit integer + pub fn arb_i256()(value in arb_uint()) -> I256 { + I256(value) + } + } + + prop_compose! { + /// Generate an arbitrary decimal wih the native denomination + pub fn arb_dec()(value in arb_i256()) -> Dec { + Dec(value) + } + } } #[cfg(test)] diff --git a/core/src/types/eth_bridge_pool.rs b/core/src/types/eth_bridge_pool.rs index 43b71b204c..d454437249 100644 --- a/core/src/types/eth_bridge_pool.rs +++ b/core/src/types/eth_bridge_pool.rs @@ -302,6 +302,77 @@ pub struct GasFee { pub token: Address, } +#[cfg(any(test, feature = "testing"))] +/// Tests and strategies for the Ethereum bridge pool +pub mod tests { + use proptest::prop_compose; + + use super::*; + use crate::types::address::testing::arb_address; + use crate::types::ethereum_events::testing::arb_eth_address; + use crate::types::token::testing::arb_amount; + + prop_compose! { + /// Generate an arbitrary pending transfer + pub fn arb_pending_transfer()( + transfer in arb_transfer_to_ethereum(), + gas_fee in arb_gas_fee(), + ) -> PendingTransfer { + PendingTransfer { + transfer, + gas_fee, + } + } + } + + prop_compose! { + /// Generate an arbitrary Ethereum gas fee + pub fn arb_gas_fee()( + amount in arb_amount(), + payer in arb_address(), + token in arb_address(), + ) -> GasFee { + GasFee { + amount, + payer, + token, + } + } + } + + prop_compose! { + /// Generate the kind of a transfer to ethereum + pub fn arb_transfer_to_ethereum_kind()( + discriminant in 0..2, + ) -> TransferToEthereumKind { + match discriminant { + 0 => TransferToEthereumKind::Erc20, + 1 => TransferToEthereumKind::Nut, + _ => unreachable!(), + } + } + } + + prop_compose! { + /// Generate an arbitrary transfer to Ethereum + pub fn arb_transfer_to_ethereum()( + kind in arb_transfer_to_ethereum_kind(), + asset in arb_eth_address(), + recipient in arb_eth_address(), + sender in arb_address(), + amount in arb_amount(), + ) -> TransferToEthereum { + TransferToEthereum { + kind, + asset, + recipient, + sender, + amount, + } + } + } +} + #[cfg(test)] mod test_eth_bridge_pool_types { use super::*; diff --git a/core/src/types/ethereum_events.rs b/core/src/types/ethereum_events.rs index 2dc3601e5e..8569b118e4 100644 --- a/core/src/types/ethereum_events.rs +++ b/core/src/types/ethereum_events.rs @@ -447,6 +447,8 @@ pub mod tests { /// Test helpers #[cfg(any(test, feature = "testing"))] pub mod testing { + use proptest::prop_compose; + use super::*; use crate::types::token::{self, Amount}; @@ -498,4 +500,11 @@ pub mod testing { }], } } + + prop_compose! { + // Generate an arbitrary Ethereum address + pub fn arb_eth_address()(bytes: [u8; 20]) -> EthAddress { + EthAddress(bytes) + } + } } diff --git a/core/src/types/hash.rs b/core/src/types/hash.rs index af2d94d8ed..17d4049a15 100644 --- a/core/src/types/hash.rs +++ b/core/src/types/hash.rs @@ -146,14 +146,17 @@ impl From for crate::tendermint::Hash { } } -#[cfg(test)] -mod tests { +#[cfg(any(test, feature = "testing"))] +/// Tests and strategies for hashes +pub mod tests { use proptest::prelude::*; + #[cfg(test)] use proptest::string::{string_regex, RegexGeneratorStrategy}; use super::*; /// Returns a proptest strategy that yields hex encoded hashes. + #[cfg(test)] fn hex_encoded_hash_strat() -> RegexGeneratorStrategy { string_regex(r"[a-fA-F0-9]{64}").unwrap() } @@ -164,4 +167,11 @@ mod tests { let _: Hash = hex_hash.try_into().unwrap(); } } + + prop_compose! { + /// Generate an arbitrary hash + pub fn arb_hash()(bytes: [u8; 32]) -> Hash { + Hash(bytes) + } + } } diff --git a/core/src/types/key/mod.rs b/core/src/types/key/mod.rs index 9d22ee3af1..37ad34e43f 100644 --- a/core/src/types/key/mod.rs +++ b/core/src/types/key/mod.rs @@ -532,6 +532,23 @@ pub mod testing { use super::SigScheme; use crate::types::key::*; + /// Generate an arbitrary public key + pub fn arb_pk() + -> impl Strategy::PublicKey> { + arb_keypair::().prop_map(|x| x.ref_to()) + } + + /// Generate an arbitrary common key + pub fn arb_common_pk() -> impl Strategy { + let ed25519 = arb_pk::() + .prop_map(common::PublicKey::Ed25519) + .sboxed(); + let secp256k1 = arb_pk::() + .prop_map(common::PublicKey::Secp256k1) + .sboxed(); + ed25519.prop_union(secp256k1) + } + /// A keypair for tests pub fn keypair_1() -> ::SecretKey { // generated from `cargo test gen_keypair -- --nocapture` diff --git a/core/src/types/storage.rs b/core/src/types/storage.rs index e16318568e..4b2e331f5c 100644 --- a/core/src/types/storage.rs +++ b/core/src/types/storage.rs @@ -1433,13 +1433,22 @@ impl GetEventNonce for InnerEthEventsQueue { } } -#[cfg(test)] -mod tests { +#[cfg(any(test, feature = "testing"))] +/// Tests and strategies for storage +pub mod tests { use proptest::prelude::*; use super::*; + #[cfg(test)] use crate::types::address::testing::arb_address; + prop_compose! { + /// Generate an arbitrary epoch + pub fn arb_epoch()(epoch: u64) -> Epoch { + Epoch(epoch) + } + } + proptest! { /// Tests that any key that doesn't contain reserved prefixes is valid. /// This test excludes key segments starting with `#` or `?` @@ -1828,6 +1837,7 @@ mod tests { } } + #[cfg(test)] fn test_address_in_storage_key_order_aux(addr1: Address, addr2: Address) { println!("addr1 {addr1}"); println!("addr2 {addr2}"); diff --git a/core/src/types/token.rs b/core/src/types/token.rs index e04faf3559..ae2f160c8f 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -1303,9 +1303,51 @@ pub enum TransferError { NoToken, } -#[cfg(test)] -mod tests { +#[cfg(any(test, feature = "testing"))] +/// Tests and strategies for tokens +pub mod tests { + use proptest::{option, prop_compose}; + use super::*; + use crate::types::address::testing::arb_address; + use crate::types::token::testing::arb_amount; + + prop_compose! { + /// Generate an arbitrary denomination + pub fn arb_denomination()(denom in 0u8..) -> Denomination { + Denomination(denom) + } + } + + prop_compose! { + /// Generate a denominated amount + pub fn arb_denominated_amount()( + amount in arb_amount(), + denom in arb_denomination(), + ) -> DenominatedAmount { + DenominatedAmount::new(amount, denom) + } + } + + prop_compose! { + /// Generate a transfer + pub fn arb_transfer()( + source in arb_address(), + target in arb_address(), + token in arb_address(), + amount in arb_denominated_amount(), + key in option::of("[a-zA-Z0-9_]*"), + ) -> Transfer { + Transfer { + source, + target, + token, + amount, + key, + shielded: None, + } + } + } #[test] fn test_token_display() { diff --git a/core/src/types/transaction/account.rs b/core/src/types/transaction/account.rs index f2eaafe7ef..2e08b57235 100644 --- a/core/src/types/transaction/account.rs +++ b/core/src/types/transaction/account.rs @@ -50,3 +50,51 @@ pub struct UpdateAccount { /// The account signature threshold pub threshold: Option, } + +#[cfg(any(test, feature = "testing"))] +/// Tests and strategies for accounts +pub mod tests { + use proptest::prelude::Just; + use proptest::{collection, option, prop_compose}; + + use super::*; + use crate::types::address::testing::arb_address; + use crate::types::hash::tests::arb_hash; + use crate::types::key::testing::arb_common_pk; + + prop_compose! { + /// Generate an account initialization + pub fn arb_init_account()( + public_keys in collection::vec(arb_common_pk(), 0..10), + )( + threshold in 0..=public_keys.len() as u8, + public_keys in Just(public_keys), + vp_code_hash in arb_hash(), + ) -> InitAccount { + InitAccount { + public_keys, + vp_code_hash, + threshold, + } + } + } + + prop_compose! { + /// Generate an arbitrary account update + pub fn arb_update_account()( + public_keys in collection::vec(arb_common_pk(), 0..10), + )( + addr in arb_address(), + vp_code_hash in option::of(arb_hash()), + threshold in option::of(0..=public_keys.len() as u8), + public_keys in Just(public_keys), + ) -> UpdateAccount { + UpdateAccount { + addr, + vp_code_hash, + public_keys, + threshold, + } + } + } +} diff --git a/core/src/types/transaction/governance.rs b/core/src/types/transaction/governance.rs index 4d32801d52..99b291c814 100644 --- a/core/src/types/transaction/governance.rs +++ b/core/src/types/transaction/governance.rs @@ -164,3 +164,56 @@ impl TryFrom for InitProposalData { }) } } + +#[cfg(any(test, feature = "testing"))] +/// Tests and strategies for governance +pub mod tests { + use proptest::{collection, prop_compose}; + + use super::*; + use crate::ledger::governance::storage::proposal::tests::arb_proposal_type; + use crate::ledger::governance::storage::vote::tests::arb_proposal_vote; + use crate::types::address::testing::arb_address; + use crate::types::hash::tests::arb_hash; + use crate::types::storage::tests::arb_epoch; + + prop_compose! { + /// Generate a proposal initialization + pub fn arb_init_proposal()( + id: Option, + content in arb_hash(), + author in arb_address(), + r#type in arb_proposal_type(), + voting_start_epoch in arb_epoch(), + voting_end_epoch in arb_epoch(), + grace_epoch in arb_epoch(), + ) -> InitProposalData { + InitProposalData { + id, + content, + author, + r#type, + voting_start_epoch, + voting_end_epoch, + grace_epoch, + } + } + } + + prop_compose! { + /// Generate an arbitrary vote proposal + pub fn arb_vote_proposal()( + id: u64, + vote in arb_proposal_vote(), + voter in arb_address(), + delegations in collection::vec(arb_address(), 0..10), + ) -> VoteProposalData { + VoteProposalData { + id, + vote, + voter, + delegations, + } + } + } +} diff --git a/core/src/types/transaction/pgf.rs b/core/src/types/transaction/pgf.rs index 5935d04ef9..6a8c0c5fb4 100644 --- a/core/src/types/transaction/pgf.rs +++ b/core/src/types/transaction/pgf.rs @@ -30,3 +30,26 @@ pub struct UpdateStewardCommission { /// The new commission distribution pub commission: HashMap, } + +#[cfg(any(test, feature = "testing"))] +/// Tests and strategies for PGF +pub mod tests { + use proptest::{collection, prop_compose}; + + use crate::types::address::testing::arb_address; + use crate::types::dec::testing::arb_dec; + use crate::types::transaction::pgf::UpdateStewardCommission; + + prop_compose! { + /// Generate an arbitraary steward commission update + pub fn arb_update_steward_commission()( + steward in arb_address(), + commission in collection::hash_map(arb_address(), arb_dec(), 0..10), + ) -> UpdateStewardCommission { + UpdateStewardCommission { + steward, + commission, + } + } + } +} diff --git a/core/src/types/transaction/pos.rs b/core/src/types/transaction/pos.rs index f44be68d85..e353be333e 100644 --- a/core/src/types/transaction/pos.rs +++ b/core/src/types/transaction/pos.rs @@ -206,3 +206,138 @@ pub struct ConsensusKeyChange { /// The new consensus key pub consensus_key: common::PublicKey, } + +#[cfg(any(test, feature = "testing"))] +/// Tests and strategies for proof-of-stake +pub mod tests { + use proptest::{option, prop_compose}; + + use super::*; + use crate::types::address::testing::arb_address; + use crate::types::dec::testing::arb_dec; + use crate::types::key::testing::{arb_common_pk, arb_pk}; + use crate::types::token::testing::arb_amount; + + prop_compose! { + /// Generate a bond + pub fn arb_bond()( + validator in arb_address(), + amount in arb_amount(), + source in option::of(arb_address()), + ) -> Bond { + Bond { + validator, + amount, + source, + } + } + } + + prop_compose! { + /// Generate an arbitrary withdraw + pub fn arb_withdraw()( + validator in arb_address(), + source in option::of(arb_address()), + ) -> Withdraw { + Withdraw { + validator, + source, + } + } + } + + prop_compose! { + /// Generate an arbitrary commission change + pub fn arb_commission_change()( + validator in arb_address(), + new_rate in arb_dec(), + ) -> CommissionChange { + CommissionChange { + validator, + new_rate, + } + } + } + + prop_compose! { + /// Generate an arbitrary metadata change + pub fn arb_metadata_change()( + validator in arb_address(), + email in option::of("[a-zA-Z0-9_]*"), + description in option::of("[a-zA-Z0-9_]*"), + website in option::of("[a-zA-Z0-9_]*"), + discord_handle in option::of("[a-zA-Z0-9_]*"), + commission_rate in option::of(arb_dec()), + ) -> MetaDataChange { + MetaDataChange { + validator, + email, + description, + website, + discord_handle, + commission_rate, + } + } + } + + prop_compose! { + /// Generate an arbitrary consensus key change + pub fn arb_consensus_key_change()( + validator in arb_address(), + consensus_key in arb_common_pk(), + ) -> ConsensusKeyChange { + ConsensusKeyChange { + validator, + consensus_key, + } + } + } + + prop_compose! { + /// Generate a validator initialization + pub fn arb_become_validator()( + address in arb_address(), + consensus_key in arb_common_pk(), + eth_cold_key in arb_pk::(), + eth_hot_key in arb_pk::(), + protocol_key in arb_common_pk(), + commission_rate in arb_dec(), + max_commission_rate_change in arb_dec(), + email in "[a-zA-Z0-9_]*", + description in option::of("[a-zA-Z0-9_]*"), + website in option::of("[a-zA-Z0-9_]*"), + discord_handle in option::of("[a-zA-Z0-9_]*"), + ) -> BecomeValidator { + BecomeValidator { + address, + consensus_key, + eth_cold_key, + eth_hot_key, + protocol_key, + commission_rate, + max_commission_rate_change, + email, + description, + website, + discord_handle, + } + } + } + + prop_compose! { + /// Generate an arbitrary redelegation + pub fn arb_redelegation()( + src_validator in arb_address(), + dest_validator in arb_address(), + owner in arb_address(), + amount in arb_amount(), + ) -> Redelegation { + Redelegation { + src_validator, + dest_validator, + owner, + amount, + } + } + } +} diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index e0f19b33a0..d38af4d07e 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -49,6 +49,7 @@ testing = [ "namada_ethereum_bridge/testing", "namada_proof_of_stake/testing", "async-client", + "proptest", ] # Download MASP params if they're not present @@ -77,6 +78,7 @@ orion.workspace = true owo-colors = "3.5.0" parse_duration = "2.1.1" paste.workspace = true +proptest = {workspace = true, optional = true} prost.workspace = true rand.workspace = true rand_core.workspace = true @@ -107,4 +109,5 @@ namada_core = {path = "../core", default-features = false, features = ["rand", " namada_ethereum_bridge = {path = "../ethereum_bridge", default-features = false, features = ["testing"]} namada_proof_of_stake = {path = "../proof_of_stake", default-features = false, features = ["testing"]} namada_test_utils = {path = "../test_utils"} +proptest.workspace = true tempfile.workspace = true diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 029353313c..c19976bf0c 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -755,3 +755,604 @@ where } } } + +#[cfg(any(test, feature = "testing"))] +/// Tests and strategies for transactions +pub mod testing { + use ibc::primitives::proto::Any; + use namada_core::ledger::governance::storage::proposal::ProposalType; + use namada_core::ledger::ibc::tests::arb_ibc_any; + use namada_core::types::address::testing::arb_address; + use namada_core::types::eth_bridge_pool::PendingTransfer; + use namada_core::types::hash::tests::arb_hash; + use namada_core::types::storage::tests::arb_epoch; + use namada_core::types::token::tests::{ + arb_denominated_amount, arb_transfer, + }; + use namada_core::types::token::Transfer; + use namada_core::types::transaction::account::{ + InitAccount, UpdateAccount, + }; + use namada_core::types::transaction::governance::{ + InitProposalData, VoteProposalData, + }; + use namada_core::types::transaction::pgf::UpdateStewardCommission; + use namada_core::types::transaction::pos::{ + BecomeValidator, Bond, CommissionChange, ConsensusKeyChange, + MetaDataChange, Redelegation, Unbond, Withdraw, + }; + use proptest::prelude::{Just, Strategy}; + use proptest::{option, prop_compose}; + use prost::Message; + + use super::*; + use crate::core::types::chain::ChainId; + use crate::core::types::eth_bridge_pool::tests::arb_pending_transfer; + use crate::core::types::key::testing::arb_common_pk; + use crate::core::types::time::{DateTime, DateTimeUtc, Utc}; + use crate::core::types::transaction::account::tests::{ + arb_init_account, arb_update_account, + }; + use crate::core::types::transaction::governance::tests::{ + arb_init_proposal, arb_vote_proposal, + }; + use crate::core::types::transaction::pgf::tests::arb_update_steward_commission; + use crate::core::types::transaction::pos::tests::{ + arb_become_validator, arb_bond, arb_commission_change, + arb_consensus_key_change, arb_metadata_change, arb_redelegation, + arb_withdraw, + }; + use crate::core::types::transaction::{ + DecryptedTx, Fee, TxType, WrapperTx, + }; + use crate::proto::{Code, Commitment, Header, Section}; + + #[derive(Debug)] + #[allow(clippy::large_enum_variant)] + // To facilitate propagating debugging information + pub enum TxData { + CommissionChange(CommissionChange), + ConsensusKeyChange(ConsensusKeyChange), + MetaDataChange(MetaDataChange), + ClaimRewards(Withdraw), + DeactivateValidator(Address), + InitAccount(InitAccount), + InitProposal(InitProposalData), + InitValidator(BecomeValidator), + ReactivateValidator(Address), + RevealPk(common::PublicKey), + Unbond(Unbond), + UnjailValidator(Address), + UpdateAccount(UpdateAccount), + VoteProposal(VoteProposalData), + Withdraw(Withdraw), + Transfer(Transfer), + Bond(Bond), + Redelegation(Redelegation), + UpdateStewardCommission(UpdateStewardCommission), + ResignSteward(Address), + PendingTransfer(PendingTransfer), + IbcAny(Any), + Custom(Box), + } + + prop_compose! { + // Generate an arbitrary commitment + pub fn arb_commitment()( + hash in arb_hash(), + ) -> Commitment { + Commitment::Hash(hash) + } + } + + prop_compose! { + // Generate an arbitrary code section + pub fn arb_code()( + salt: [u8; 8], + code in arb_commitment(), + tag in option::of("[a-zA-Z0-9_]*"), + ) -> Code { + Code { + salt, + code, + tag, + } + } + } + + prop_compose! { + // Generate a chain ID + pub fn arb_chain_id()(id in "[a-zA-Z0-9_]*") -> ChainId { + ChainId(id) + } + } + + prop_compose! { + // Generate a date and time + pub fn arb_date_time_utc()( + secs in DateTime::::MIN_UTC.timestamp()..=DateTime::::MAX_UTC.timestamp(), + nsecs in ..1000000000u32, + ) -> DateTimeUtc { + DateTimeUtc(DateTime::::from_timestamp(secs, nsecs).unwrap()) + } + } + + prop_compose! { + // Generate an arbitrary fee + pub fn arb_fee()( + amount_per_gas_unit in arb_denominated_amount(), + token in arb_address(), + ) -> Fee { + Fee { + amount_per_gas_unit, + token, + } + } + } + + prop_compose! { + // Generate an arbitrary gas limit + pub fn arb_gas_limit()(multiplier: u64) -> GasLimit { + multiplier.into() + } + } + + prop_compose! { + // Generate an arbitrary wrapper transaction + pub fn arb_wrapper_tx()( + fee in arb_fee(), + epoch in arb_epoch(), + pk in arb_common_pk(), + gas_limit in arb_gas_limit(), + unshield_section_hash in option::of(arb_hash()), + ) -> WrapperTx { + WrapperTx { + fee, + epoch, + pk, + gas_limit, + unshield_section_hash, + } + } + } + + prop_compose! { + // Generate an arbitrary decrypted transaction + pub fn arb_decrypted_tx()(discriminant in 0..2) -> DecryptedTx { + match discriminant { + 0 => DecryptedTx::Decrypted, + 1 => DecryptedTx::Undecryptable, + _ => unreachable!(), + } + } + } + + // Generate an arbitrary transaction type + pub fn arb_tx_type() -> impl Strategy { + let raw_tx = Just(TxType::Raw).boxed(); + let decrypted_tx = + arb_decrypted_tx().prop_map(TxType::Decrypted).boxed(); + let wrapper_tx = arb_wrapper_tx() + .prop_map(|x| TxType::Wrapper(Box::new(x))) + .boxed(); + raw_tx.prop_union(decrypted_tx).or(wrapper_tx) + } + + prop_compose! { + // Generate an arbitrary header + pub fn arb_header()( + chain_id in arb_chain_id(), + expiration in option::of(arb_date_time_utc()), + timestamp in arb_date_time_utc(), + code_hash in arb_hash(), + data_hash in arb_hash(), + tx_type in arb_tx_type(), + ) -> Header { + Header { + chain_id, + expiration, + timestamp, + data_hash, + code_hash, + tx_type, + } + } + } + + prop_compose! { + // Generate an arbitrary transfer transaction + pub fn arb_transfer_tx()( + mut header in arb_header(), + wrapper in arb_wrapper_tx(), + transfer in arb_transfer(), + code_hash in arb_hash(), + ) -> (Tx, TxData) { + header.tx_type = TxType::Wrapper(Box::new(wrapper)); + let mut tx = Tx { header, sections: vec![] }; + tx.add_data(transfer.clone()); + tx.add_code_from_hash(code_hash, Some(TX_TRANSFER_WASM.to_owned())); + (tx, TxData::Transfer(transfer)) + } + } + + prop_compose! { + // Generate an arbitrary bond transaction + pub fn arb_bond_tx()( + mut header in arb_header(), + wrapper in arb_wrapper_tx(), + bond in arb_bond(), + code_hash in arb_hash(), + ) -> (Tx, TxData) { + header.tx_type = TxType::Wrapper(Box::new(wrapper)); + let mut tx = Tx { header, sections: vec![] }; + tx.add_data(bond.clone()); + tx.add_code_from_hash(code_hash, Some(TX_BOND_WASM.to_owned())); + (tx, TxData::Bond(bond)) + } + } + + prop_compose! { + // Generate an arbitrary bond transaction + pub fn arb_unbond_tx()( + mut header in arb_header(), + wrapper in arb_wrapper_tx(), + unbond in arb_bond(), + code_hash in arb_hash(), + ) -> (Tx, TxData) { + header.tx_type = TxType::Wrapper(Box::new(wrapper)); + let mut tx = Tx { header, sections: vec![] }; + tx.add_data(unbond.clone()); + tx.add_code_from_hash(code_hash, Some(TX_UNBOND_WASM.to_owned())); + (tx, TxData::Unbond(unbond)) + } + } + + prop_compose! { + // Generate an arbitrary account initialization transaction + pub fn arb_init_account_tx()( + mut header in arb_header(), + wrapper in arb_wrapper_tx(), + mut init_account in arb_init_account(), + extra_data in arb_code(), + code_hash in arb_hash(), + ) -> (Tx, TxData) { + header.tx_type = TxType::Wrapper(Box::new(wrapper)); + let mut tx = Tx { header, sections: vec![] }; + let vp_code_hash = tx.add_section(Section::ExtraData(extra_data)).get_hash(); + init_account.vp_code_hash = vp_code_hash; + tx.add_data(init_account.clone()); + tx.add_code_from_hash(code_hash, Some(TX_INIT_ACCOUNT_WASM.to_owned())); + (tx, TxData::InitAccount(init_account)) + } + } + + prop_compose! { + // Generate an arbitrary account initialization transaction + pub fn arb_become_validator_tx()( + mut header in arb_header(), + wrapper in arb_wrapper_tx(), + become_validator in arb_become_validator(), + code_hash in arb_hash(), + ) -> (Tx, TxData) { + header.tx_type = TxType::Wrapper(Box::new(wrapper)); + let mut tx = Tx { header, sections: vec![] }; + tx.add_data(become_validator.clone()); + tx.add_code_from_hash(code_hash, Some(TX_BECOME_VALIDATOR_WASM.to_owned())); + (tx, TxData::InitValidator(become_validator)) + } + } + + prop_compose! { + // Generate an arbitrary proposal initialization transaction + pub fn arb_init_proposal_tx()( + mut header in arb_header(), + wrapper in arb_wrapper_tx(), + mut init_proposal in arb_init_proposal(), + content_extra_data in arb_code(), + type_extra_data in arb_code(), + code_hash in arb_hash(), + ) -> (Tx, TxData) { + header.tx_type = TxType::Wrapper(Box::new(wrapper)); + let mut tx = Tx { header, sections: vec![] }; + let content_hash = tx.add_section(Section::ExtraData(content_extra_data)).get_hash(); + init_proposal.content = content_hash; + if let ProposalType::Default(Some(hash)) = &mut init_proposal.r#type { + let type_hash = tx.add_section(Section::ExtraData(type_extra_data)).get_hash(); + *hash = type_hash; + } + tx.add_data(init_proposal.clone()); + tx.add_code_from_hash(code_hash, Some(TX_INIT_PROPOSAL.to_owned())); + (tx, TxData::InitProposal(init_proposal)) + } + } + + prop_compose! { + // Generate an arbitrary vote proposal transaction + pub fn arb_vote_proposal_tx()( + mut header in arb_header(), + wrapper in arb_wrapper_tx(), + vote_proposal in arb_vote_proposal(), + code_hash in arb_hash(), + ) -> (Tx, TxData) { + header.tx_type = TxType::Wrapper(Box::new(wrapper)); + let mut tx = Tx { header, sections: vec![] }; + tx.add_data(vote_proposal.clone()); + tx.add_code_from_hash(code_hash, Some(TX_VOTE_PROPOSAL.to_owned())); + (tx, TxData::VoteProposal(vote_proposal)) + } + } + + prop_compose! { + // Generate an arbitrary reveal public key transaction + pub fn arb_reveal_pk_tx()( + mut header in arb_header(), + wrapper in arb_wrapper_tx(), + pk in arb_common_pk(), + code_hash in arb_hash(), + ) -> (Tx, TxData) { + header.tx_type = TxType::Wrapper(Box::new(wrapper)); + let mut tx = Tx { header, sections: vec![] }; + tx.add_data(pk.clone()); + tx.add_code_from_hash(code_hash, Some(TX_REVEAL_PK.to_owned())); + (tx, TxData::RevealPk(pk)) + } + } + + prop_compose! { + // Generate an arbitrary account initialization transaction + pub fn arb_update_account_tx()( + mut header in arb_header(), + wrapper in arb_wrapper_tx(), + mut update_account in arb_update_account(), + extra_data in arb_code(), + code_hash in arb_hash(), + ) -> (Tx, TxData) { + header.tx_type = TxType::Wrapper(Box::new(wrapper)); + let mut tx = Tx { header, sections: vec![] }; + if let Some(vp_code_hash) = &mut update_account.vp_code_hash { + let new_code_hash = tx.add_section(Section::ExtraData(extra_data)).get_hash(); + *vp_code_hash = new_code_hash; + } + tx.add_data(update_account.clone()); + tx.add_code_from_hash(code_hash, Some(TX_UPDATE_ACCOUNT_WASM.to_owned())); + (tx, TxData::UpdateAccount(update_account)) + } + } + + prop_compose! { + // Generate an arbitrary reveal public key transaction + pub fn arb_withdraw_tx()( + mut header in arb_header(), + wrapper in arb_wrapper_tx(), + withdraw in arb_withdraw(), + code_hash in arb_hash(), + ) -> (Tx, TxData) { + header.tx_type = TxType::Wrapper(Box::new(wrapper)); + let mut tx = Tx { header, sections: vec![] }; + tx.add_data(withdraw.clone()); + tx.add_code_from_hash(code_hash, Some(TX_WITHDRAW_WASM.to_owned())); + (tx, TxData::Withdraw(withdraw)) + } + } + + prop_compose! { + // Generate an arbitrary claim rewards transaction + pub fn arb_claim_rewards_tx()( + mut header in arb_header(), + wrapper in arb_wrapper_tx(), + claim_rewards in arb_withdraw(), + code_hash in arb_hash(), + ) -> (Tx, TxData) { + header.tx_type = TxType::Wrapper(Box::new(wrapper)); + let mut tx = Tx { header, sections: vec![] }; + tx.add_data(claim_rewards.clone()); + tx.add_code_from_hash(code_hash, Some(TX_CLAIM_REWARDS_WASM.to_owned())); + (tx, TxData::ClaimRewards(claim_rewards)) + } + } + + prop_compose! { + // Generate an arbitrary commission change transaction + pub fn arb_commission_change_tx()( + mut header in arb_header(), + wrapper in arb_wrapper_tx(), + commission_change in arb_commission_change(), + code_hash in arb_hash(), + ) -> (Tx, TxData) { + header.tx_type = TxType::Wrapper(Box::new(wrapper)); + let mut tx = Tx { header, sections: vec![] }; + tx.add_data(commission_change.clone()); + tx.add_code_from_hash(code_hash, Some(TX_CHANGE_COMMISSION_WASM.to_owned())); + (tx, TxData::CommissionChange(commission_change)) + } + } + + prop_compose! { + // Generate an arbitrary commission change transaction + pub fn arb_metadata_change_tx()( + mut header in arb_header(), + wrapper in arb_wrapper_tx(), + metadata_change in arb_metadata_change(), + code_hash in arb_hash(), + ) -> (Tx, TxData) { + header.tx_type = TxType::Wrapper(Box::new(wrapper)); + let mut tx = Tx { header, sections: vec![] }; + tx.add_data(metadata_change.clone()); + tx.add_code_from_hash(code_hash, Some(TX_CHANGE_METADATA_WASM.to_owned())); + (tx, TxData::MetaDataChange(metadata_change)) + } + } + + prop_compose! { + // Generate an arbitrary unjail validator transaction + pub fn arb_unjail_validator_tx()( + mut header in arb_header(), + wrapper in arb_wrapper_tx(), + address in arb_address(), + code_hash in arb_hash(), + ) -> (Tx, TxData) { + header.tx_type = TxType::Wrapper(Box::new(wrapper)); + let mut tx = Tx { header, sections: vec![] }; + tx.add_data(address.clone()); + tx.add_code_from_hash(code_hash, Some(TX_UNJAIL_VALIDATOR_WASM.to_owned())); + (tx, TxData::UnjailValidator(address)) + } + } + + prop_compose! { + // Generate an arbitrary deactivate validator transaction + pub fn arb_deactivate_validator_tx()( + mut header in arb_header(), + wrapper in arb_wrapper_tx(), + address in arb_address(), + code_hash in arb_hash(), + ) -> (Tx, TxData) { + header.tx_type = TxType::Wrapper(Box::new(wrapper)); + let mut tx = Tx { header, sections: vec![] }; + tx.add_data(address.clone()); + tx.add_code_from_hash(code_hash, Some(TX_DEACTIVATE_VALIDATOR_WASM.to_owned())); + (tx, TxData::DeactivateValidator(address)) + } + } + + prop_compose! { + // Generate an arbitrary reactivate validator transaction + pub fn arb_reactivate_validator_tx()( + mut header in arb_header(), + wrapper in arb_wrapper_tx(), + address in arb_address(), + code_hash in arb_hash(), + ) -> (Tx, TxData) { + header.tx_type = TxType::Wrapper(Box::new(wrapper)); + let mut tx = Tx { header, sections: vec![] }; + tx.add_data(address.clone()); + tx.add_code_from_hash(code_hash, Some(TX_REACTIVATE_VALIDATOR_WASM.to_owned())); + (tx, TxData::ReactivateValidator(address)) + } + } + + prop_compose! { + // Generate an arbitrary consensus key change transaction + pub fn arb_consensus_key_change_tx()( + mut header in arb_header(), + wrapper in arb_wrapper_tx(), + consensus_key_change in arb_consensus_key_change(), + code_hash in arb_hash(), + ) -> (Tx, TxData) { + header.tx_type = TxType::Wrapper(Box::new(wrapper)); + let mut tx = Tx { header, sections: vec![] }; + tx.add_data(consensus_key_change.clone()); + tx.add_code_from_hash(code_hash, Some(TX_CHANGE_CONSENSUS_KEY_WASM.to_owned())); + (tx, TxData::ConsensusKeyChange(consensus_key_change)) + } + } + + prop_compose! { + // Generate an arbitrary redelegation transaction + pub fn arb_redelegation_tx()( + mut header in arb_header(), + wrapper in arb_wrapper_tx(), + redelegation in arb_redelegation(), + code_hash in arb_hash(), + ) -> (Tx, TxData) { + header.tx_type = TxType::Wrapper(Box::new(wrapper)); + let mut tx = Tx { header, sections: vec![] }; + tx.add_data(redelegation.clone()); + tx.add_code_from_hash(code_hash, Some(TX_REDELEGATE_WASM.to_owned())); + (tx, TxData::Redelegation(redelegation)) + } + } + + prop_compose! { + // Generate an arbitrary redelegation transaction + pub fn arb_update_steward_commission_tx()( + mut header in arb_header(), + wrapper in arb_wrapper_tx(), + update_steward_commission in arb_update_steward_commission(), + code_hash in arb_hash(), + ) -> (Tx, TxData) { + header.tx_type = TxType::Wrapper(Box::new(wrapper)); + let mut tx = Tx { header, sections: vec![] }; + tx.add_data(update_steward_commission.clone()); + tx.add_code_from_hash(code_hash, Some(TX_UPDATE_STEWARD_COMMISSION.to_owned())); + (tx, TxData::UpdateStewardCommission(update_steward_commission)) + } + } + + prop_compose! { + // Generate an arbitrary redelegation transaction + pub fn arb_resign_steward_tx()( + mut header in arb_header(), + wrapper in arb_wrapper_tx(), + steward in arb_address(), + code_hash in arb_hash(), + ) -> (Tx, TxData) { + header.tx_type = TxType::Wrapper(Box::new(wrapper)); + let mut tx = Tx { header, sections: vec![] }; + tx.add_data(steward.clone()); + tx.add_code_from_hash(code_hash, Some(TX_RESIGN_STEWARD.to_owned())); + (tx, TxData::ResignSteward(steward)) + } + } + + prop_compose! { + // Generate an arbitrary pending transfer transaction + pub fn arb_pending_transfer_tx()( + mut header in arb_header(), + wrapper in arb_wrapper_tx(), + pending_transfer in arb_pending_transfer(), + code_hash in arb_hash(), + ) -> (Tx, TxData) { + header.tx_type = TxType::Wrapper(Box::new(wrapper)); + let mut tx = Tx { header, sections: vec![] }; + tx.add_data(pending_transfer.clone()); + tx.add_code_from_hash(code_hash, Some(TX_BRIDGE_POOL_WASM.to_owned())); + (tx, TxData::PendingTransfer(pending_transfer)) + } + } + + prop_compose! { + // Generate an arbitrary IBC any transaction + pub fn arb_ibc_any_tx()( + mut header in arb_header(), + wrapper in arb_wrapper_tx(), + ibc_any in arb_ibc_any(), + code_hash in arb_hash(), + ) -> (Tx, TxData) { + header.tx_type = TxType::Wrapper(Box::new(wrapper)); + let mut tx = Tx { header, sections: vec![] }; + let mut tx_data = vec![]; + ibc_any.encode(&mut tx_data).expect("unable to encode IBC data"); + tx.add_serialized_data(tx_data); + tx.add_code_from_hash(code_hash, Some(TX_IBC_WASM.to_owned())); + (tx, TxData::IbcAny(ibc_any)) + } + } + + // Generate an arbitrary tx + pub fn arb_tx() -> impl Strategy { + arb_transfer_tx() + .boxed() + .prop_union(arb_bond_tx().boxed()) + .or(arb_unbond_tx().boxed()) + .or(arb_init_account_tx().boxed()) + .or(arb_become_validator_tx().boxed()) + .or(arb_init_proposal_tx().boxed()) + .or(arb_vote_proposal_tx().boxed()) + .or(arb_reveal_pk_tx().boxed()) + .or(arb_update_account_tx().boxed()) + .or(arb_withdraw_tx().boxed()) + .or(arb_claim_rewards_tx().boxed()) + .or(arb_commission_change_tx().boxed()) + .or(arb_metadata_change_tx().boxed()) + .or(arb_unjail_validator_tx().boxed()) + .or(arb_deactivate_validator_tx().boxed()) + .or(arb_reactivate_validator_tx().boxed()) + .or(arb_consensus_key_change_tx().boxed()) + .or(arb_redelegation_tx().boxed()) + .or(arb_update_steward_commission_tx().boxed()) + .or(arb_resign_steward_tx().boxed()) + .or(arb_pending_transfer_tx().boxed()) + .or(arb_ibc_any_tx().boxed()) + } +} diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 47f9f746fd..76e4ea724b 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -3486,6 +3486,7 @@ dependencies = [ "owo-colors", "parse_duration", "paste", + "proptest", "prost 0.12.3", "rand 0.8.5", "rand_core 0.6.4", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 4016982ed0..6cd5a53a02 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -3486,6 +3486,7 @@ dependencies = [ "owo-colors", "parse_duration", "paste", + "proptest", "prost 0.12.3", "rand 0.8.5", "rand_core 0.6.4", From 4ca6922cbca13586d12819f139ce9caf1cc4dfb1 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Sat, 9 Dec 2023 23:20:45 +0200 Subject: [PATCH 049/216] Added a binary to automatically generate test vectors. --- Cargo.lock | 11 +++++++++++ Cargo.toml | 1 + examples/Cargo.toml | 24 ++++++++++++++++++++++++ examples/generate_txs.rs | 35 +++++++++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+) create mode 100644 examples/Cargo.toml create mode 100644 examples/generate_txs.rs diff --git a/Cargo.lock b/Cargo.lock index 91ee00c9df..0115e3c398 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4391,6 +4391,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "namada_examples" +version = "0.28.0" +dependencies = [ + "masp_proofs", + "namada_sdk", + "proptest", + "serde_json", + "tokio", +] + [[package]] name = "namada_macros" version = "0.28.0" diff --git a/Cargo.toml b/Cargo.toml index e440a56b30..6799668f31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ members = [ "vp_prelude", "encoding_spec", "sdk", + "examples", ] # wasm packages have to be built separately diff --git a/examples/Cargo.toml b/examples/Cargo.toml new file mode 100644 index 0000000000..fba6785cec --- /dev/null +++ b/examples/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "namada_examples" +description = "Namada examples" +resolver = "2" +authors.workspace = true +edition.workspace = true +documentation.workspace = true +homepage.workspace = true +keywords.workspace = true +license.workspace = true +readme.workspace = true +repository.workspace = true +version.workspace = true + +[[example]] +name = "generate-txs" +path = "generate_txs.rs" + +[dev-dependencies] +masp_proofs = { workspace = true, default-features = false, features = ["local-prover", "download-params"] } +namada_sdk = { path = "../sdk", default-features = false, features = ["namada-sdk", "std", "testing"] } +proptest.workspace = true +serde_json.workspace = true +tokio = {workspace = true, default-features = false} diff --git a/examples/generate_txs.rs b/examples/generate_txs.rs new file mode 100644 index 0000000000..384f092900 --- /dev/null +++ b/examples/generate_txs.rs @@ -0,0 +1,35 @@ +use std::path::PathBuf; + +use namada_sdk::signing::to_ledger_vector; +use namada_sdk::testing::arb_tx; +use namada_sdk::wallet::fs::FsWalletUtils; +use proptest::strategy::{Strategy, ValueTree}; +use proptest::test_runner::{Reason, TestRunner}; + +#[tokio::main] +async fn main() -> Result<(), Reason> { + let mut runner = TestRunner::default(); + let wallet = FsWalletUtils::new(PathBuf::from("wallet.toml")); + let mut debug_vectors = vec![]; + let mut test_vectors = vec![]; + for i in 0..1000 { + let (tx, tx_data) = arb_tx().new_tree(&mut runner)?.current(); + let mut ledger_vector = to_ledger_vector(&wallet, &tx) + .await + .expect("unable to construct test vector"); + ledger_vector.name = format!("{}_{}", i, ledger_vector.name); + test_vectors.push(ledger_vector.clone()); + debug_vectors.push((ledger_vector, tx, tx_data)); + } + let args: Vec<_> = std::env::args().collect(); + if args.len() < 3 { + eprintln!("Usage: namada-generator "); + return Result::Err(Reason::from("Incorrect command line arguments.")); + } + let json = serde_json::to_string(&test_vectors) + .expect("unable to serialize test vectors"); + std::fs::write(&args[1], json).expect("unable to save test vectors"); + std::fs::write(&args[2], format!("{:?}", debug_vectors)) + .expect("unable to save test vectors"); + Ok(()) +} From 3833c7e93cbb610728bef8edf0fe005528ec9cfa Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Sat, 9 Dec 2023 23:35:55 +0200 Subject: [PATCH 050/216] Removed generate_test_vector and the test vector shell script since they have been made redundant by the generate_txs example. --- apps/src/lib/client/tx.rs | 77 ------------ scripts/generator.sh | 256 -------------------------------------- sdk/src/signing.rs | 58 --------- 3 files changed, 391 deletions(-) delete mode 100755 scripts/generator.sh diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 74ef2bab4c..ed9a7e056c 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -221,12 +221,8 @@ pub async fn submit_reveal_aux( let (mut tx, signing_data, _epoch) = tx::build_reveal_pk(context, &args, &public_key).await?; - signing::generate_test_vector(context, &tx).await?; - sign(context, &mut tx, &args, signing_data).await?; - signing::generate_test_vector(context, &tx).await?; - context.submit(tx, &args).await?; } } @@ -241,8 +237,6 @@ pub async fn submit_bridge_pool_tx( let tx_args = args.tx.clone(); let (mut tx, signing_data, _epoch) = args.clone().build(namada).await?; - signing::generate_test_vector(namada, &tx).await?; - if args.tx.dump_tx { tx::dump_tx(namada.io(), &args.tx, tx); } else { @@ -250,8 +244,6 @@ pub async fn submit_bridge_pool_tx( sign(namada, &mut tx, &tx_args, signing_data).await?; - signing::generate_test_vector(namada, &tx).await?; - namada.submit(tx, &tx_args).await?; } @@ -269,15 +261,11 @@ where let (mut tx, signing_data, _epoch) = args.build(namada).await?; - signing::generate_test_vector(namada, &tx).await?; - if args.tx.dump_tx { tx::dump_tx(namada.io(), &args.tx, tx); } else { sign(namada, &mut tx, &args.tx, signing_data).await?; - signing::generate_test_vector(namada, &tx).await?; - namada.submit(tx, &args.tx).await?; } @@ -293,15 +281,11 @@ where { let (mut tx, signing_data, _epoch) = args.build(namada).await?; - signing::generate_test_vector(namada, &tx).await?; - if args.tx.dump_tx { tx::dump_tx(namada.io(), &args.tx, tx); } else { sign(namada, &mut tx, &args.tx, signing_data).await?; - signing::generate_test_vector(namada, &tx).await?; - namada.submit(tx, &args.tx).await?; } @@ -318,15 +302,11 @@ where let (mut tx, signing_data, _epoch) = tx::build_init_account(namada, &args).await?; - signing::generate_test_vector(namada, &tx).await?; - if args.tx.dump_tx { tx::dump_tx(namada.io(), &args.tx, tx); } else { sign(namada, &mut tx, &args.tx, signing_data).await?; - signing::generate_test_vector(namada, &tx).await?; - let result = namada.submit(tx, &args.tx).await?; if let ProcessTxResponse::Applied(response) = result { return Ok(response.initialized_accounts.first().cloned()); @@ -444,8 +424,6 @@ pub async fn submit_change_consensus_key( ) .await?; - signing::generate_test_vector(namada, &tx).await?; - if tx_args.dump_tx { tx::dump_tx(namada.io(), &tx_args, tx); } else { @@ -773,15 +751,11 @@ pub async fn submit_become_validator( ) .await?; - signing::generate_test_vector(namada, &tx).await?; - if tx_args.dump_tx { tx::dump_tx(namada.io(), &tx_args, tx); } else { sign(namada, &mut tx, &tx_args, signing_data).await?; - signing::generate_test_vector(namada, &tx).await?; - namada.submit(tx, &tx_args).await?.initialized_accounts(); if !tx_args.dry_run { @@ -942,7 +916,6 @@ pub async fn submit_transfer( let (mut tx, signing_data, tx_epoch) = args.clone().build(namada).await?; - signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx(namada.io(), &args.tx, tx); @@ -950,8 +923,6 @@ pub async fn submit_transfer( } else { sign(namada, &mut tx, &args.tx, signing_data).await?; - signing::generate_test_vector(namada, &tx).await?; - let result = namada.submit(tx, &args.tx).await?; let submission_epoch = rpc::query_and_print_epoch(namada).await; @@ -991,15 +962,12 @@ where { submit_reveal_aux(namada, args.tx.clone(), &args.source).await?; let (mut tx, signing_data, _epoch) = args.build(namada).await?; - signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx(namada.io(), &args.tx, tx); } else { sign(namada, &mut tx, &args.tx, signing_data).await?; - signing::generate_test_vector(namada, &tx).await?; - namada.submit(tx, &args.tx).await?; } @@ -1126,15 +1094,12 @@ where tx::build_default_proposal(namada, &args, proposal).await? }; - signing::generate_test_vector(namada, &tx_builder).await?; if args.tx.dump_tx { tx::dump_tx(namada.io(), &args.tx, tx_builder); } else { sign(namada, &mut tx_builder, &args.tx, signing_data).await?; - signing::generate_test_vector(namada, &tx_builder).await?; - namada.submit(tx_builder, &args.tx).await?; } @@ -1212,15 +1177,12 @@ where } else { args.build(namada).await? }; - signing::generate_test_vector(namada, &tx_builder).await?; if args.tx.dump_tx { tx::dump_tx(namada.io(), &args.tx, tx_builder); } else { sign(namada, &mut tx_builder, &args.tx, signing_data).await?; - signing::generate_test_vector(namada, &tx_builder).await?; - namada.submit(tx_builder, &args.tx).await?; } @@ -1332,15 +1294,12 @@ where let (mut tx, signing_data, _fee_unshield_epoch) = args.build(namada).await?; - signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx(namada.io(), &args.tx, tx); } else { sign(namada, &mut tx, &args.tx, signing_data).await?; - signing::generate_test_vector(namada, &tx).await?; - namada.submit(tx, &args.tx).await?; } @@ -1356,15 +1315,12 @@ where { let (mut tx, signing_data, _fee_unshield_epoch, latest_withdrawal_pre) = args.build(namada).await?; - signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx(namada.io(), &args.tx, tx); } else { sign(namada, &mut tx, &args.tx, signing_data).await?; - signing::generate_test_vector(namada, &tx).await?; - namada.submit(tx, &args.tx).await?; tx::query_unbonds(namada, args.clone(), latest_withdrawal_pre).await?; @@ -1382,15 +1338,12 @@ where { let (mut tx, signing_data, _fee_unshield_epoch) = args.build(namada).await?; - signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx(namada.io(), &args.tx, tx); } else { sign(namada, &mut tx, &args.tx, signing_data).await?; - signing::generate_test_vector(namada, &tx).await?; - namada.submit(tx, &args.tx).await?; } @@ -1406,15 +1359,12 @@ where { let (mut tx, signing_data, _fee_unshield_epoch) = args.build(namada).await?; - signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx(namada.io(), &args.tx, tx); } else { sign(namada, &mut tx, &args.tx, signing_data).await?; - signing::generate_test_vector(namada, &tx).await?; - namada.submit(tx, &args.tx).await?; } @@ -1429,15 +1379,12 @@ where ::Error: std::fmt::Display, { let (mut tx, signing_data) = args.build(namada).await?; - signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx(namada.io(), &args.tx, tx); } else { sign(namada, &mut tx, &args.tx, signing_data).await?; - signing::generate_test_vector(namada, &tx).await?; - namada.submit(tx, &args.tx).await?; } @@ -1453,15 +1400,12 @@ where { let (mut tx, signing_data, _fee_unshield_epoch) = args.build(namada).await?; - signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx(namada.io(), &args.tx, tx); } else { sign(namada, &mut tx, &args.tx, signing_data).await?; - signing::generate_test_vector(namada, &tx).await?; - namada.submit(tx, &args.tx).await?; } @@ -1477,15 +1421,12 @@ where { let (mut tx, signing_data, _fee_unshield_epoch) = args.build(namada).await?; - signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx(namada.io(), &args.tx, tx); } else { sign(namada, &mut tx, &args.tx, signing_data).await?; - signing::generate_test_vector(namada, &tx).await?; - namada.submit(tx, &args.tx).await?; } @@ -1501,7 +1442,6 @@ where // { // let (mut tx, signing_data, _fee_unshield_epoch) = // args.build(namada).await?; -// signing::generate_test_vector(namada, &tx).await?; // if args.tx.dump_tx { // tx::dump_tx(namada.io(), &args.tx, tx); @@ -1523,15 +1463,12 @@ where { let (mut tx, signing_data, _fee_unshield_epoch) = args.build(namada).await?; - signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx(namada.io(), &args.tx, tx); } else { sign(namada, &mut tx, &args.tx, signing_data).await?; - signing::generate_test_vector(namada, &tx).await?; - namada.submit(tx, &args.tx).await?; } @@ -1547,15 +1484,12 @@ where { let (mut tx, signing_data, _fee_unshield_epoch) = args.build(namada).await?; - signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx(namada.io(), &args.tx, tx); } else { sign(namada, &mut tx, &args.tx, signing_data).await?; - signing::generate_test_vector(namada, &tx).await?; - namada.submit(tx, &args.tx).await?; } @@ -1571,15 +1505,12 @@ where { let (mut tx, signing_data, _fee_unshield_epoch) = args.build(namada).await?; - signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx(namada.io(), &args.tx, tx); } else { sign(namada, &mut tx, &args.tx, signing_data).await?; - signing::generate_test_vector(namada, &tx).await?; - namada.submit(tx, &args.tx).await?; } @@ -1596,15 +1527,11 @@ where let (mut tx, signing_data, _fee_unshield_epoch) = args.build(namada).await?; - signing::generate_test_vector(namada, &tx).await?; - if args.tx.dump_tx { tx::dump_tx(namada.io(), &args.tx, tx); } else { sign(namada, &mut tx, &args.tx, signing_data).await?; - signing::generate_test_vector(namada, &tx).await?; - namada.submit(tx, &args.tx).await?; } @@ -1620,15 +1547,11 @@ where { let (mut tx, signing_data, _epoch) = args.build(namada).await?; - signing::generate_test_vector(namada, &tx).await?; - if args.tx.dump_tx { tx::dump_tx(namada.io(), &args.tx, tx); } else { sign(namada, &mut tx, &args.tx, signing_data).await?; - signing::generate_test_vector(namada, &tx).await?; - namada.submit(tx, &args.tx).await?; } diff --git a/scripts/generator.sh b/scripts/generator.sh deleted file mode 100755 index 1618d5fc9c..0000000000 --- a/scripts/generator.sh +++ /dev/null @@ -1,256 +0,0 @@ -#!/usr/bin/env bash - -# A script to generate some transaction test vectors. It must be executed at the -# root of the Namada repository. All transaction types except vote-proposal are -# tested. This is because vote-proposal needs to query RPC for delegation. This -# script assumes that the WASM scripts have already been built using -# `make build-wasm-scripts`. Run `./scripts/online_generator server` to start a -# server and then run `./scripts/online_generator client` to generate the test -# vectors. - -NAMADA_DIR="$(pwd)" -NAMADA_BASE_DIR_FILE="$(pwd)/namada_base_dir" -export NAMADA_LEDGER_LOG_PATH="$(pwd)/vectors.json" -export NAMADA_TX_LOG_PATH="$(pwd)/debugs.txt" -export NAMADA_DEV=false - -if [ "$#" -ne 1 ]; then - echo "Illegal number of parameters" -elif [ "$1" = "server" ]; then - cp genesis/e2e-tests-single-node.toml genesis/test-vectors-single-node.toml - - sed -i 's/^epochs_per_year = 31_536_000$/epochs_per_year = 262_800/' genesis/test-vectors-single-node.toml - - NAMADA_GENESIS_FILE=$(cargo run --bin namadac --package namada_apps --manifest-path Cargo.toml -- utils init-network --genesis-path genesis/test-vectors-single-node.toml --wasm-checksums-path wasm/checksums.json --chain-prefix e2e-test --unsafe-dont-encrypt --localhost --dont-archive --allow-duplicate-ip | grep 'Genesis file generated at ' | sed 's/^Genesis file generated at //') - - rm genesis/test-vectors-single-node.toml - - NAMADA_BASE_DIR=${NAMADA_GENESIS_FILE%.toml} - echo $NAMADA_BASE_DIR > $NAMADA_BASE_DIR_FILE - - sed -i 's/^mode = "RemoteEndpoint"$/mode = "Off"/' $NAMADA_BASE_DIR/config.toml - - cp wasm/*.wasm $NAMADA_BASE_DIR/wasm/ - - cp wasm/*.wasm $NAMADA_BASE_DIR/setup/validator-0/.namada/$(basename $NAMADA_BASE_DIR)/wasm/ - - cp $NAMADA_BASE_DIR/setup/other/wallet.toml $NAMADA_BASE_DIR/wallet.toml - - sed -i 's/^mode = "RemoteEndpoint"$/mode = "Off"/' $NAMADA_BASE_DIR/setup/validator-0/.namada/$(basename $NAMADA_BASE_DIR)/config.toml - - cargo run --bin namadan --package namada_apps --manifest-path Cargo.toml -- --base-dir $NAMADA_BASE_DIR/setup/validator-0/.namada/ ledger -elif [ "$1" = "client" ]; then - if test -f "$NAMADA_BASE_DIR_FILE"; then - NAMADA_BASE_DIR="$(cat $NAMADA_BASE_DIR_FILE)" - fi - - echo > $NAMADA_TX_LOG_PATH - - echo $'[' > $NAMADA_LEDGER_LOG_PATH - - ALBERT_ADDRESS=$(cargo run --bin namadaw -- address find --alias albert | sed 's/^Found address Established: //') - - echo '{ - "proposal": { - "author":"'$ALBERT_ADDRESS'", - "content":{ - "abstract":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros. Nullam sed ex justo. Ut at placerat ipsum, sit amet rhoncus libero. Sed blandit non purus non suscipit. Phasellus sed quam nec augue bibendum bibendum ut vitae urna. Sed odio diam, ornare nec sapien eget, congue viverra enim.", - "authors":"test@test.com", - "created":"2022-03-10T08:54:37Z", - "details":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros.", - "discussions-to":"www.github.com/anoma/aip/1", - "license":"MIT", - "motivation":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices.", - "requires":"2", - "title":"TheTitle" - }, - "grace_epoch":30, - "voting_end_epoch":24, - "voting_start_epoch":12 - } - }' > proposal_default.json - - echo '{ - "data":['$(od -An -tu1 -v wasm_for_tests/tx_proposal_code.wasm | tr '\n' ' ' | sed 's/\b\s\+\b/,/g')'], - "proposal": { - "author":"'$ALBERT_ADDRESS'", - "content":{ - "abstract":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros. Nullam sed ex justo. Ut at placerat ipsum, sit amet rhoncus libero. Sed blandit non purus non suscipit. Phasellus sed quam nec augue bibendum bibendum ut vitae urna. Sed odio diam, ornare nec sapien eget, congue viverra enim.", - "authors":"test@test.com", - "created":"2022-03-10T08:54:37Z", - "details":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros.", - "discussions-to":"www.github.com/anoma/aip/1", - "license":"MIT", - "motivation":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices.", - "requires":"2", - "title":"TheTitle" - }, - "grace_epoch":30, - "voting_end_epoch":24, - "voting_start_epoch":12 - } - }' > proposal_default_with_data.json - - echo '{ - "author":"'$ALBERT_ADDRESS'", - "content":{ - "abstract":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros. Nullam sed ex justo. Ut at placerat ipsum, sit amet rhoncus libero. Sed blandit non purus non suscipit. Phasellus sed quam nec augue bibendum bibendum ut vitae urna. Sed odio diam, ornare nec sapien eget, congue viverra enim.", - "authors":"test@test.com", - "created":"2022-03-10T08:54:37Z", - "details":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros.", - "discussions-to":"www.github.com/anoma/aip/1", - "license":"MIT", - "motivation":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices.", - "requires":"2", - "title":"TheTitle" - }, - "tally_epoch":1 - }' > proposal_offline.json - - echo '{ - "proposal": { - "author":"'$ALBERT_ADDRESS'", - "content":{ - "abstract":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros. Nullam sed ex justo. Ut at placerat ipsum, sit amet rhoncus libero. Sed blandit non purus non suscipit. Phasellus sed quam nec augue bibendum bibendum ut vitae urna. Sed odio diam, ornare nec sapien eget, congue viverra enim.", - "authors":"test@test.com", - "created":"2022-03-10T08:54:37Z", - "details":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros.", - "discussions-to":"www.github.com/anoma/aip/1", - "license":"MIT", - "motivation":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices.", - "requires":"2", - "title":"TheTitle" - }, - "grace_epoch":30, - "voting_end_epoch":24, - "voting_start_epoch":12 - }, - "data": {"add":"'$ALBERT_ADDRESS'","remove":[]} - }' > proposal_pgf_steward_add.json - - # proposal_default - - cargo run --bin namadac --features std -- bond --validator validator-0 --source Bertha --amount 900 --gas-token NAM --node 127.0.0.1:27657 - - cargo run --bin namadac --features std -- unjail-validator --validator Bertha --gas-token NAM --force --node 127.0.0.1:27657 - - cargo run --bin namadac --features std -- deactivate-validator --validator Bertha --gas-token NAM --force --node 127.0.0.1:27657 - - cargo run --bin namadac --features std -- reactivate-validator --validator Bertha --gas-token NAM --force --node 127.0.0.1:27657 - - cargo run --bin namadac --features std -- change-commission-rate --validator Bertha --commission-rate 0.02 --gas-token NAM --force --node 127.0.0.1:27657 - - PROPOSAL_ID_0=$(cargo run --bin namadac --features std -- init-proposal --force --data-path proposal_default.json --node 127.0.0.1:27657 | grep -o -P '(?<=/proposal/).*(?=/author)') - - cargo run --bin namadac --features std -- init-proposal --force --data-path proposal_default_with_data.json --node 127.0.0.1:27657 - - cargo run --bin namadac --features std -- --base-dir $NAMADA_BASE_DIR/setup/validator-0/.namada vote-proposal --force --proposal-id $PROPOSAL_ID_0 --vote yay --address validator-0 --node 127.0.0.1:27657 - - cargo run --bin namadac --features std -- vote-proposal --force --proposal-id $PROPOSAL_ID_0 --vote nay --address Bertha --node 127.0.0.1:27657 - - cargo run --bin namadac --features std -- vote-proposal --force --proposal-id $PROPOSAL_ID_0 --vote yay --address Albert --node 127.0.0.1:27657 - - # proposal_offline - - cargo run --bin namadac --features std -- bond --validator validator-0 --source Albert --amount 900 --gas-token NAM --node 127.0.0.1:27657 - - cargo run --bin namadac --features std -- change-commission-rate --validator Albert --commission-rate 0.05 --gas-token NAM --force --node 127.0.0.1:27657 - - PROPOSAL_OFFLINE_SIGNED=$(cargo run --bin namadac --features std -- init-proposal --force --data-path proposal_offline.json --signing-keys albert-key --offline --node 127.0.0.1:27657 | grep -o -P '(?<=Proposal serialized to:\s).*') - - cargo run --bin namadac --features std -- vote-proposal --data-path $PROPOSAL_OFFLINE_SIGNED --vote yay --address Albert --offline --node 127.0.0.1:27657 - - # pgf_governance_proposal - - cargo run --bin namadac --features std -- bond --validator validator-0 --source Bertha --amount 900 --gas-token NAM --ledger-address 127.0.0.1:27657 - - cargo run --bin namadac --features std -- change-commission-rate --validator Bertha --commission-rate 0.09 --gas-token NAM --force --node 127.0.0.1:27657 - - PROPOSAL_ID_0=$(cargo run --bin namadac --features std -- init-proposal --pgf-stewards --force --data-path proposal_pgf_steward_add.json --ledger-address 127.0.0.1:27657 | grep -o -P '(?<=/proposal/).*(?=/author)') - - PROPOSAL_ID_1=$(cargo run --bin namadac --features std -- init-proposal --pgf-stewards --force --data-path proposal_pgf_steward_add.json --ledger-address 127.0.0.1:27657 | grep -o -P '(?<=/proposal/).*(?=/author)') - - cargo run --bin namadac --features std -- --base-dir $NAMADA_BASE_DIR/setup/validator-0/.namada vote-proposal --force --proposal-id $PROPOSAL_ID_0 --vote yay --address validator-0 --ledger-address 127.0.0.1:27657 - - cargo run --bin namadac --features std -- vote-proposal --force --proposal-id $PROPOSAL_ID_0 --vote yay --address Bertha --signing-keys bertha-key --ledger-address 127.0.0.1:27657 - - cargo run --bin namadac --features std -- vote-proposal --force --proposal-id $PROPOSAL_ID_1 --vote yay --address Bertha --signing-keys bertha-key --ledger-address 127.0.0.1:27657 - - # non-proposal tests - - cargo run --bin namadac --features std -- transfer --source bertha --target christel --token btc --amount 23 --force --signing-keys bertha-key --ledger-address 127.0.0.1:27657 - - cargo run --bin namadac --features std -- bond --validator bertha --amount 25 --signing-keys bertha-key --force --ledger-address 127.0.0.1:27657 - - cargo run --bin namadac --features std -- change-commission-rate --validator Bertha --commission-rate 0.11 --gas-token NAM --force --node 127.0.0.1:27657 - - cargo run --bin namadac --features std -- reveal-pk --public-key albert-key --gas-payer albert-key --force --ledger-address 127.0.0.1:27657 - - cargo run --bin namadac --features std -- update-account --code-path vp_user.wasm --address bertha --signing-keys bertha-key --force --ledger-address 127.0.0.1:27657 - - cargo run --bin namadac --features std -- update-account --code-path vp_user.wasm --address bertha --public-keys albert-key,bertha-key --force --ledger-address 127.0.0.1:27657 - - cargo run --bin namadac --features std -- update-account --code-path vp_user.wasm --address bertha --public-keys albert-key,bertha-key,christel-key --threshold 2 --force --ledger-address 127.0.0.1:27657 - - cargo run --bin namadac --features std -- init-validator --email me@me.com --alias bertha-validator --account-keys bertha-key --commission-rate 0.05 --max-commission-rate-change 0.01 --signing-keys bertha-key --unsafe-dont-encrypt --force --ledger-address 127.0.0.1:27657 - - cargo run --bin namadac --features std -- init-validator --email me@me.com --alias validator-mult --account-keys albert-key,bertha-key --commission-rate 0.05 --max-commission-rate-change 0.01 --signing-keys albert-key,bertha-key --threshold 2 --unsafe-dont-encrypt --force --ledger-address 127.0.0.1:27657 - - # TODO works but panics - cargo run --bin namadac --features std -- unbond --validator christel --amount 5 --signing-keys christel-key --force --ledger-address 127.0.0.1:27657 - - cargo run --bin namadac --features std -- withdraw --validator albert --signing-keys albert-key --force --ledger-address 127.0.0.1:27657 - - cargo run --bin namadac --features std -- init-account --alias albert-account --public-keys albert-key --signing-keys albert-key --force --ledger-address 127.0.0.1:27657 - - cargo run --bin namadac --features std -- init-account --alias account-mul --public-keys albert-key,bertha-key,christel-key --signing-keys albert-key,bertha-key,christel-key --threshold 2 --force --ledger-address 127.0.0.1:27657 - - # TODO panics, no vector produced - # cargo run --bin namadac --features std -- tx --code-path $NAMADA_DIR/wasm_for_tests/tx_no_op.wasm --data-path README.md --signing-keys albert-key --owner albert --force --ledger-address 127.0.0.1:27657 - - cargo run --bin namadac --features std -- ibc-transfer --source bertha --receiver christel --token btc --amount 24 --channel-id channel-141 --signing-keys bertha-key --force --ledger-address 127.0.0.1:27657 - - cargo run --bin namadac --features std -- ibc-transfer --source albert --receiver bertha --token nam --amount 100000 --channel-id channel-0 --port-id transfer --signing-keys albert-key --force --ledger-address 127.0.0.1:27657 - - cargo run --bin namadac --features std -- ibc-transfer --source albert --receiver bertha --token nam --amount 100000 --channel-id channel-0 --port-id transfer --signing-keys albert-key --timeout-sec-offset 5 --force --ledger-address 127.0.0.1:27657 - - cargo run --bin namadaw -- masp add --alias a_spending_key --value zsknam1qqqqqqqqqqqqqq9v0sls5r5de7njx8ehu49pqgmqr9ygelg87l5x8y4s9r0pjlvu69au6gn3su5ewneas486hdccyayx32hxvt64p3d0hfuprpgcgv2q9gdx3jvxrn02f0nnp3jtdd6f5vwscfuyum083cvfv4jun75ak5sdgrm2pthzj3sflxc0jx0edrakx3vdcngrfjmru8ywkguru8mxss2uuqxdlglaz6undx5h8w7g70t2es850g48xzdkqay5qs0yw06rtxc9q0cqr --unsafe-dont-encrypt - - cargo run --bin namadaw -- masp add --alias b_spending_key --value zsknam1qqqqqqqqqqqqqqpagte43rsza46v55dlz8cffahv0fnr6eqacvnrkyuf9lmndgal7c2k4r7f7zu2yr5rjwr374unjjeuzrh6mquzy6grfdcnnu5clzaq2llqhr70a8yyx0p62aajqvrqjxrht3myuyypsvm725uyt5vm0fqzrzuuedtf6fala4r4nnazm9y9hq5yu6pq24arjskmpv4mdgfn3spffxxv8ugvym36kmnj45jcvvmm227vqjm5fq8882yhjsq97p7xrwqf599qq --unsafe-dont-encrypt - - cargo run --bin namadaw -- masp add --alias ab_payment_address --value znam1qp562jexfndtcw63equndlwgwawutf6l4p4xgkcvp9sjqf9x7kdlvc48mrh3stfvwk9s9fgsmhuz6 - - cargo run --bin namadaw -- masp add --alias aa_payment_address --value znam1qr57pyghrt5ek7v42nxsqdqggltwqrgj2hjlvm5sj0nr8hezzryxcu44qzcea7qdx6wh02cvt9jlu - - cargo run --bin namadaw -- masp add --alias bb_payment_address --value znam1qpsr9ass6lfmwlkamk3fpwapht94qqe8dq3slykkfd6wjnd4s9snlqszvxsksk3tegqv2yg9rcrzd - - # TODO vector produced only when epoch boundaries not straddled - cargo run --bin namadac --features std -- transfer --source albert --target aa_payment_address --token btc --amount 20 --force --ledger-address 127.0.0.1:27657 - - # TODO vector produced only when epoch boundaries not straddled - cargo run --bin namadac --features std -- transfer --gas-payer albert-key --source a_spending_key --target ab_payment_address --token btc --amount 7 --force --ledger-address 127.0.0.1:27657 - - # TODO fragile - until cargo run --bin namadac -- epoch --ledger-address 127.0.0.1:27657 | grep -m1 "Last committed epoch: 2" ; do sleep 10 ; done; - - # TODO vector produced only when epoch boundaries not straddled - cargo run --bin namadac --features std -- transfer --gas-payer albert-key --source a_spending_key --target bb_payment_address --token btc --amount 7 --force --ledger-address 127.0.0.1:27657 - - # TODO vector produced only when epoch boundaries not straddled - cargo run --bin namadac --features std -- transfer --gas-payer albert-key --source a_spending_key --target bb_payment_address --token btc --amount 6 --force --ledger-address 127.0.0.1:27657 - - # TODO vector produced only when epoch boundaries not straddled - cargo run --bin namadac --features std -- transfer --gas-payer albert-key --source b_spending_key --target bb_payment_address --token btc --amount 6 --force --ledger-address 127.0.0.1:27657 - - rm -f proposal_default.json - - rm -f proposal_default_with_data.json - - rm -f proposal_offline.json - - rm -f proposal_pgf_steward_add.json - - perl -0777 -i.original -pe 's/,\s*$//igs' $NAMADA_LEDGER_LOG_PATH - - echo $'\n]' >> $NAMADA_LEDGER_LOG_PATH -fi diff --git a/sdk/src/signing.rs b/sdk/src/signing.rs index 492d176f3b..ac7bd450b4 100644 --- a/sdk/src/signing.rs +++ b/sdk/src/signing.rs @@ -65,13 +65,6 @@ pub use crate::wallet::store::AddressVpType; use crate::wallet::{Wallet, WalletIo}; use crate::{args, display_line, rpc, Namada}; -#[cfg(feature = "std")] -/// Env. var specifying where to store signing test vectors -const ENV_VAR_LEDGER_LOG_PATH: &str = "NAMADA_LEDGER_LOG_PATH"; -#[cfg(feature = "std")] -/// Env. var specifying where to store transaction debug outputs -const ENV_VAR_TX_LOG_PATH: &str = "NAMADA_TX_LOG_PATH"; - /// A struture holding the signing data to craft a transaction #[derive(Clone)] pub struct SigningTxData { @@ -888,57 +881,6 @@ pub async fn make_ledger_masp_endpoints( } } -/// Internal method used to generate transaction test vectors -#[cfg(feature = "std")] -pub async fn generate_test_vector( - context: &impl Namada, - tx: &Tx, -) -> Result<(), Error> { - use std::env; - use std::fs::File; - use std::io::Write; - - if let Ok(path) = env::var(ENV_VAR_LEDGER_LOG_PATH) { - let mut tx = tx.clone(); - // Contract the large data blobs in the transaction - tx.wallet_filter(); - // Convert the transaction to Ledger format - let decoding = to_ledger_vector(&*context.wallet().await, &tx).await?; - let output = serde_json::to_string(&decoding) - .map_err(|e| Error::from(EncodingError::Serde(e.to_string())))?; - // Record the transaction at the identified path - let mut f = File::options() - .append(true) - .create(true) - .open(path) - .map_err(|e| { - Error::Other(format!("failed to open test vector file: {}", e)) - })?; - writeln!(f, "{},", output).map_err(|_| { - Error::Other("unable to write test vector to file".to_string()) - })?; - } - - // Attempt to decode the construction - if let Ok(path) = env::var(ENV_VAR_TX_LOG_PATH) { - let mut tx = tx.clone(); - // Contract the large data blobs in the transaction - tx.wallet_filter(); - // Record the transaction at the identified path - let mut f = File::options() - .append(true) - .create(true) - .open(path) - .map_err(|_| { - Error::Other("unable to write test vector to file".to_string()) - })?; - writeln!(f, "{:x?},", tx).map_err(|_| { - Error::Other("unable to write test vector to file".to_string()) - })?; - } - Ok(()) -} - /// Convert decimal numbers into the format used by Ledger. Specifically remove /// all insignificant zeros occuring after decimal point. fn to_ledger_decimal(amount: &str) -> String { From ebcf85a4da8e63afb354318833dd619a57902f5b Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Sat, 9 Dec 2023 23:56:44 +0200 Subject: [PATCH 051/216] Added a README to explain the new examples directory. --- examples/README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 examples/README.md diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000000..7ff0b4fbc2 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,17 @@ +# Examples +This directory contains examples and additional tooling to help in the +development of Namada. The currently provided examples are listed below: +## `generate-txs` +This utility serves to randomly generate Namada transaction test vectors +offline. These test vectors are useful for ensuring compatability with hardware +wallets. This example is included in the Namada repository in order to ensure +that the test vector generation logic is maintained and remains up to date with +the latest changes in transaction formats. +### Usage +This example is run as follows: +``` +cargo run --example generate-txs -- +``` +where `` is the path where the JSON test vectors will be stored +and `` is where rust `Debug` representations oof this data will be +stored. From 66c04159eda2a787863164b3cdf76d0304b85101 Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Fri, 17 Nov 2023 15:29:46 +0100 Subject: [PATCH 052/216] Unify cli for secret / spending key generation --- apps/src/lib/cli.rs | 90 ++++++++++++++++++-------------------- apps/src/lib/cli/wallet.rs | 40 +++++++++++------ sdk/src/args.rs | 6 ++- 3 files changed, 73 insertions(+), 63 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index c04f5a531b..da7ea8af9a 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -485,6 +485,8 @@ pub mod cmds { Address(WalletAddress), /// MASP key, address management commands Masp(WalletMasp), + /// TODO + NewGen(WalletNewGen), } impl Cmd for NamadaWallet { @@ -492,13 +494,15 @@ pub mod cmds { app.subcommand(WalletKey::def()) .subcommand(WalletAddress::def()) .subcommand(WalletMasp::def()) + .subcommand(WalletNewGen::def()) } fn parse(matches: &ArgMatches) -> Option { let key = SubCmd::parse(matches).map(Self::Key); let address = SubCmd::parse(matches).map(Self::Address); let masp = SubCmd::parse(matches).map(Self::Masp); - key.or(address).or(masp) + let gen_new = SubCmd::parse(matches).map(Self::NewGen); + key.or(address).or(masp).or(gen_new) } } @@ -525,7 +529,6 @@ pub mod cmds { #[allow(clippy::large_enum_variant)] pub enum WalletKey { Derive(KeyDerive), - Gen(KeyGen), Find(KeyFind), List(KeyList), Export(Export), @@ -536,12 +539,11 @@ pub mod cmds { fn parse(matches: &ArgMatches) -> Option { matches.subcommand_matches(Self::CMD).and_then(|matches| { - let generate = SubCmd::parse(matches).map(Self::Gen); let restore = SubCmd::parse(matches).map(Self::Derive); let lookup = SubCmd::parse(matches).map(Self::Find); let list = SubCmd::parse(matches).map(Self::List); let export = SubCmd::parse(matches).map(Self::Export); - generate.or(restore).or(lookup).or(list).or(export) + restore.or(lookup).or(list).or(export) }) } @@ -554,7 +556,6 @@ pub mod cmds { .subcommand_required(true) .arg_required_else_help(true) .subcommand(KeyDerive::def()) - .subcommand(KeyGen::def()) .subcommand(KeyFind::def()) .subcommand(KeyList::def()) .subcommand(Export::def()) @@ -587,17 +588,19 @@ pub mod cmds { } } - /// Generate a new keypair and an implicit address derived from it + /// In the transparent setting, generate a new keypair and an implicit + /// address derived from it. In the shielded setting, generate a new + /// spending key. #[derive(Clone, Debug)] - pub struct KeyGen(pub args::KeyAndAddressGen); + pub struct WalletNewGen(pub args::KeyGen); - impl SubCmd for KeyGen { + impl SubCmd for WalletNewGen { const CMD: &'static str = "gen"; fn parse(matches: &ArgMatches) -> Option { matches .subcommand_matches(Self::CMD) - .map(|matches| Self(args::KeyAndAddressGen::parse(matches))) + .map(|matches| Self(args::KeyGen::parse(matches))) } fn def() -> App { @@ -605,9 +608,9 @@ pub mod cmds { .about( "Generates a keypair with a given alias and derives the \ implicit address from its public key. The address will \ - be stored with the same alias.", + be stored with the same alias. TODO shielded", ) - .add_args::() + .add_args::() } } @@ -672,7 +675,6 @@ pub mod cmds { #[derive(Clone, Debug)] pub enum WalletMasp { GenPayAddr(MaspGenPayAddr), - GenSpendKey(MaspGenSpendKey), AddAddrKey(MaspAddAddrKey), ListPayAddrs, ListKeys(MaspListKeys), @@ -685,13 +687,12 @@ pub mod cmds { fn parse(matches: &ArgMatches) -> Option { matches.subcommand_matches(Self::CMD).and_then(|matches| { let genpa = SubCmd::parse(matches).map(Self::GenPayAddr); - let gensk = SubCmd::parse(matches).map(Self::GenSpendKey); let addak = SubCmd::parse(matches).map(Self::AddAddrKey); let listpa = ::parse(matches) .map(|_| Self::ListPayAddrs); let listsk = SubCmd::parse(matches).map(Self::ListKeys); let findak = SubCmd::parse(matches).map(Self::FindAddrKey); - gensk.or(genpa).or(addak).or(listpa).or(listsk).or(findak) + genpa.or(addak).or(listpa).or(listsk).or(findak) }) } @@ -704,7 +705,6 @@ pub mod cmds { ) .subcommand_required(true) .arg_required_else_help(true) - .subcommand(MaspGenSpendKey::def()) .subcommand(MaspGenPayAddr::def()) .subcommand(MaspAddAddrKey::def()) .subcommand(MaspListPayAddrs::def()) @@ -834,7 +834,6 @@ pub mod cmds { #[derive(Clone, Debug)] pub enum WalletAddress { - Gen(AddressGen), Derive(AddressDerive), Find(AddressOrAliasFind), List, @@ -846,13 +845,12 @@ pub mod cmds { fn parse(matches: &ArgMatches) -> Option { matches.subcommand_matches(Self::CMD).and_then(|matches| { - let gen = SubCmd::parse(matches).map(Self::Gen); let restore = SubCmd::parse(matches).map(Self::Derive); let find = SubCmd::parse(matches).map(Self::Find); let list = ::parse(matches).map(|_| Self::List); let add = SubCmd::parse(matches).map(Self::Add); - gen.or(restore).or(find).or(list).or(add) + restore.or(find).or(list).or(add) }) } @@ -864,7 +862,6 @@ pub mod cmds { ) .subcommand_required(true) .arg_required_else_help(true) - .subcommand(AddressGen::def()) .subcommand(AddressDerive::def()) .subcommand(AddressOrAliasFind::def()) .subcommand(AddressList::def()) @@ -872,30 +869,6 @@ pub mod cmds { } } - /// Generate a new keypair and an implicit address derived from it - #[derive(Clone, Debug)] - pub struct AddressGen(pub args::KeyAndAddressGen); - - impl SubCmd for AddressGen { - const CMD: &'static str = "gen"; - - fn parse(matches: &ArgMatches) -> Option { - matches.subcommand_matches(Self::CMD).map(|matches| { - AddressGen(args::KeyAndAddressGen::parse(matches)) - }) - } - - fn def() -> App { - App::new(Self::CMD) - .about( - "Generates a keypair with a given alias and derives the \ - implicit address from its public key. The address will \ - be stored with the same alias.", - ) - .add_args::() - } - } - /// Restore a keypair and an implicit address from the mnemonic code #[derive(Clone, Debug)] pub struct AddressDerive(pub args::KeyAndAddressDerive); @@ -3137,6 +3110,7 @@ pub mod args { pub const RAW_ADDRESS: Arg
= arg("address"); pub const RAW_ADDRESS_ESTABLISHED: Arg = arg("address"); pub const RAW_ADDRESS_OPT: ArgOpt
= RAW_ADDRESS.opt(); + pub const RAW_KEY_GEN: ArgFlag = flag("raw"); pub const RAW_PUBLIC_KEY: Arg = arg("public-key"); pub const RAW_PUBLIC_KEY_OPT: ArgOpt = arg_opt("public-key"); @@ -3148,6 +3122,7 @@ pub mod args { pub const SELF_BOND_AMOUNT: Arg = arg("self-bond-amount"); pub const SENDER: Arg = arg("sender"); + pub const SHIELDED: ArgFlag = flag("shielded"); pub const SIGNER: ArgOpt = arg_opt("signer"); pub const SIGNING_KEYS: ArgMulti = arg_multi("signing-keys"); @@ -6324,15 +6299,19 @@ pub mod args { } } - impl Args for KeyAndAddressGen { + impl Args for KeyGen { fn parse(matches: &ArgMatches) -> Self { let scheme = SCHEME.parse(matches); + let shielded = SHIELDED.parse(matches); + let raw = RAW_KEY_GEN.parse(matches); let alias = ALIAS_OPT.parse(matches); let alias_force = ALIAS_FORCE.parse(matches); let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); let derivation_path = HD_WALLET_DERIVATION_PATH.parse(matches); Self { scheme, + shielded, + raw, alias, alias_force, unsafe_dont_encrypt, @@ -6341,11 +6320,26 @@ pub mod args { } fn def(app: App) -> App { - app.arg(SCHEME.def().help( - "The type of key that should be generated. Argument must be \ - either ed25519 or secp256k1. If none provided, the default \ - key scheme is ed25519.", + app.arg(SCHEME.def().conflicts_with(SHIELDED.name).help( + "For the transparent pool, the type of key that should be \ + generated. Argument must be either ed25519 or secp256k1. If \ + none provided, the default key scheme is ed25519.\nNot \ + applicable for the shielded pool.", )) + .arg( + SHIELDED + .def() + .help("Generate a spending key for the shielded pool."), + ) + .arg( + RAW_KEY_GEN + .def() + .conflicts_with(HD_WALLET_DERIVATION_PATH.name) + .help( + "Generate a random non-HD secret / spending key. No \ + mnemonic code is generated.", + ), + ) .arg(ALIAS_OPT.def().help( "The key and address alias. If none provided, the alias will \ be the public key hash.", diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index 088e204d57..fdc94fb7ce 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -43,9 +43,6 @@ impl CliApi { cmds::WalletKey::Derive(cmds::KeyDerive(args)) => { key_and_address_derive(ctx, io, args).await } - cmds::WalletKey::Gen(cmds::KeyGen(args)) => { - key_and_address_gen(ctx, io, args) - } cmds::WalletKey::Find(cmds::KeyFind(args)) => { key_find(ctx, io, args) } @@ -57,9 +54,6 @@ impl CliApi { } }, cmds::NamadaWallet::Address(sub) => match sub { - cmds::WalletAddress::Gen(cmds::AddressGen(args)) => { - key_and_address_gen(ctx, io, args) - } cmds::WalletAddress::Derive(cmds::AddressDerive(args)) => { key_and_address_derive(ctx, io, args).await } @@ -72,9 +66,6 @@ impl CliApi { } }, cmds::NamadaWallet::Masp(sub) => match sub { - cmds::WalletMasp::GenSpendKey(cmds::MaspGenSpendKey(args)) => { - spending_key_gen(ctx, io, args) - } cmds::WalletMasp::GenPayAddr(cmds::MaspGenPayAddr(args)) => { let args = args.to_sdk(&mut ctx); payment_address_gen(ctx, io, args) @@ -92,6 +83,9 @@ impl CliApi { address_key_find(ctx, io, args) } }, + cmds::NamadaWallet::NewGen(cmds::WalletNewGen(args)) => { + key_gen(ctx, io, args) + } } Ok(()) } @@ -236,18 +230,26 @@ fn payment_addresses_list(ctx: Context, io: &impl Io) { fn spending_key_gen( ctx: Context, io: &impl Io, - args::MaspSpendKeyGen { + args::KeyGen { alias, alias_force, unsafe_dont_encrypt, - }: args::MaspSpendKeyGen, + .. + }: args::KeyGen, ) { + let alias = alias.unwrap_or_else(|| { + display_line!(io, "Missing alias."); + display_line!(io, "No changes are persisted. Exiting."); + cli::safe_exit(1) + }); let mut wallet = load_wallet(ctx); let alias = alias.to_lowercase(); let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); let (alias, _key) = wallet.gen_store_spending_key(alias, password, alias_force, &mut OsRng); - wallet.save().unwrap_or_else(|err| eprintln!("{}", err)); + wallet + .save() + .unwrap_or_else(|err| edisplay_line!(io, "{}", err)); display_line!( io, "Successfully added a spending key with alias: \"{}\"", @@ -463,18 +465,28 @@ async fn key_and_address_derive( ); } +/// TODO +fn key_gen(ctx: Context, io: &impl Io, args_key_gen: args::KeyGen) { + if !args_key_gen.shielded { + key_and_address_gen(ctx, io, args_key_gen) + } else { + spending_key_gen(ctx, io, args_key_gen) + } +} + /// Generate a new keypair and derive implicit address from it and store them in /// the wallet. fn key_and_address_gen( ctx: Context, io: &impl Io, - args::KeyAndAddressGen { + args::KeyGen { scheme, alias, alias_force, unsafe_dont_encrypt, derivation_path, - }: args::KeyAndAddressGen, + .. + }: args::KeyGen, ) { let mut wallet = load_wallet(ctx); let encryption_password = diff --git a/sdk/src/args.rs b/sdk/src/args.rs index 6599b0e9fb..b8bdc9492f 100644 --- a/sdk/src/args.rs +++ b/sdk/src/args.rs @@ -2079,9 +2079,13 @@ pub struct MaspPayAddrGen { /// Wallet generate key and implicit address arguments #[derive(Clone, Debug)] -pub struct KeyAndAddressGen { +pub struct KeyGen { /// Scheme type pub scheme: SchemeType, + /// Whether to generate a spending key for the shielded pool + pub shielded: bool, + /// Whether to generate a raw non-hd key + pub raw: bool, /// Key alias pub alias: Option, /// Whether to force overwrite the alias, if provided From 88ab8839ceeeb274aa553ac9387ae4f05a7c17d4 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Sun, 10 Dec 2023 00:14:22 +0200 Subject: [PATCH 053/216] Added a changelog entry. --- .../unreleased/improvements/2259-strategy-constructors.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/2259-strategy-constructors.md diff --git a/.changelog/unreleased/improvements/2259-strategy-constructors.md b/.changelog/unreleased/improvements/2259-strategy-constructors.md new file mode 100644 index 0000000000..a4b9a0bf76 --- /dev/null +++ b/.changelog/unreleased/improvements/2259-strategy-constructors.md @@ -0,0 +1,2 @@ +- Made test vector generation easier and reduced the difficulty of maintaining + the generation code. ([\#2259](https://github.com/anoma/namada/pull/2259)) \ No newline at end of file From a956a0cfe3b4f4208e922e5857f715029f1095c9 Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Fri, 17 Nov 2023 17:49:02 +0100 Subject: [PATCH 054/216] Unify cli for secret / spending key derivation --- apps/src/lib/cli.rs | 97 ++++++++++++++++++++------------------ apps/src/lib/cli/wallet.rs | 27 +++++++---- sdk/src/args.rs | 19 ++++++++ 3 files changed, 90 insertions(+), 53 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index da7ea8af9a..f4eedb6ce8 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -487,6 +487,8 @@ pub mod cmds { Masp(WalletMasp), /// TODO NewGen(WalletNewGen), + /// TODO + NewDerive(WalletNewDerive), } impl Cmd for NamadaWallet { @@ -495,6 +497,7 @@ pub mod cmds { .subcommand(WalletAddress::def()) .subcommand(WalletMasp::def()) .subcommand(WalletNewGen::def()) + .subcommand(WalletNewDerive::def()) } fn parse(matches: &ArgMatches) -> Option { @@ -502,7 +505,8 @@ pub mod cmds { let address = SubCmd::parse(matches).map(Self::Address); let masp = SubCmd::parse(matches).map(Self::Masp); let gen_new = SubCmd::parse(matches).map(Self::NewGen); - key.or(address).or(masp).or(gen_new) + let derive_new = SubCmd::parse(matches).map(Self::NewDerive); + key.or(address).or(masp).or(gen_new).or(derive_new) } } @@ -528,7 +532,6 @@ pub mod cmds { #[derive(Clone, Debug)] #[allow(clippy::large_enum_variant)] pub enum WalletKey { - Derive(KeyDerive), Find(KeyFind), List(KeyList), Export(Export), @@ -539,11 +542,10 @@ pub mod cmds { fn parse(matches: &ArgMatches) -> Option { matches.subcommand_matches(Self::CMD).and_then(|matches| { - let restore = SubCmd::parse(matches).map(Self::Derive); let lookup = SubCmd::parse(matches).map(Self::Find); let list = SubCmd::parse(matches).map(Self::List); let export = SubCmd::parse(matches).map(Self::Export); - restore.or(lookup).or(list).or(export) + lookup.or(list).or(export) }) } @@ -555,7 +557,6 @@ pub mod cmds { ) .subcommand_required(true) .arg_required_else_help(true) - .subcommand(KeyDerive::def()) .subcommand(KeyFind::def()) .subcommand(KeyList::def()) .subcommand(Export::def()) @@ -564,7 +565,7 @@ pub mod cmds { /// Restore a keypair and implicit address from the mnemonic code #[derive(Clone, Debug)] - pub struct KeyDerive(pub args::KeyAndAddressDerive); + pub struct KeyDerive(pub args::KeyDerive); impl SubCmd for KeyDerive { const CMD: &'static str = "derive"; @@ -572,7 +573,7 @@ pub mod cmds { fn parse(matches: &ArgMatches) -> Option { matches .subcommand_matches(Self::CMD) - .map(|matches| Self(args::KeyAndAddressDerive::parse(matches))) + .map(|matches| Self(args::KeyDerive::parse(matches))) } fn def() -> App { @@ -584,7 +585,7 @@ pub mod cmds { the given alias. A hardware wallet can be used, in which \ case a private key is not derivable.", ) - .add_args::() + .add_args::() } } @@ -614,6 +615,34 @@ pub mod cmds { } } + /// In the transparent setting, derive a keypair and implicit address from + /// the mnemonic code. + /// In the shielded setting, derive a spending key from the mnemonic code. + #[derive(Clone, Debug)] + pub struct WalletNewDerive(pub args::KeyDerive); + + impl SubCmd for WalletNewDerive { + const CMD: &'static str = "derive"; + + fn parse(matches: &ArgMatches) -> Option { + matches + .subcommand_matches(Self::CMD) + .map(|matches| Self(args::KeyDerive::parse(matches))) + } + + fn def() -> App { + App::new(Self::CMD) + .about( + "Derives a keypair from the given mnemonic code and HD \ + derivation path and derives the implicit address from \ + its public key. Stores the keypair and the address with \ + the given alias. A hardware wallet can be used, in which \ + case a private key is not derivable. TODO shielded", + ) + .add_args::() + } + } + #[derive(Clone, Debug)] pub struct KeyFind(pub args::KeyFind); @@ -834,7 +863,6 @@ pub mod cmds { #[derive(Clone, Debug)] pub enum WalletAddress { - Derive(AddressDerive), Find(AddressOrAliasFind), List, Add(AddressAdd), @@ -845,12 +873,11 @@ pub mod cmds { fn parse(matches: &ArgMatches) -> Option { matches.subcommand_matches(Self::CMD).and_then(|matches| { - let restore = SubCmd::parse(matches).map(Self::Derive); let find = SubCmd::parse(matches).map(Self::Find); let list = ::parse(matches).map(|_| Self::List); let add = SubCmd::parse(matches).map(Self::Add); - restore.or(find).or(list).or(add) + find.or(list).or(add) }) } @@ -862,39 +889,12 @@ pub mod cmds { ) .subcommand_required(true) .arg_required_else_help(true) - .subcommand(AddressDerive::def()) .subcommand(AddressOrAliasFind::def()) .subcommand(AddressList::def()) .subcommand(AddressAdd::def()) } } - /// Restore a keypair and an implicit address from the mnemonic code - #[derive(Clone, Debug)] - pub struct AddressDerive(pub args::KeyAndAddressDerive); - - impl SubCmd for AddressDerive { - const CMD: &'static str = "derive"; - - fn parse(matches: &ArgMatches) -> Option { - matches.subcommand_matches(Self::CMD).map(|matches| { - AddressDerive(args::KeyAndAddressDerive::parse(matches)) - }) - } - - fn def() -> App { - App::new(Self::CMD) - .about( - "Derives a keypair from the given mnemonic code and HD \ - derivation path and derives the implicit address from \ - its public key. Stores the keypair and the address with \ - the given alias. A hardware wallet can be used, in which \ - case a private key is not derivable.", - ) - .add_args::() - } - } - /// Find an address by its alias #[derive(Clone, Debug)] pub struct AddressOrAliasFind(pub args::AddressOrAliasFind); @@ -6247,9 +6247,10 @@ pub mod args { } } - impl Args for KeyAndAddressDerive { + impl Args for KeyDerive { fn parse(matches: &ArgMatches) -> Self { let scheme = SCHEME.parse(matches); + let shielded = SHIELDED.parse(matches); let alias = ALIAS_OPT.parse(matches); let alias_force = ALIAS_FORCE.parse(matches); let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); @@ -6257,6 +6258,7 @@ pub mod args { let derivation_path = HD_WALLET_DERIVATION_PATH.parse(matches); Self { scheme, + shielded, alias, alias_force, unsafe_dont_encrypt, @@ -6266,11 +6268,17 @@ pub mod args { } fn def(app: App) -> App { - app.arg(SCHEME.def().help( - "The type of key that should be added. Argument must be \ - either ed25519 or secp256k1. If none provided, the default \ - key scheme is ed25519.", + app.arg(SCHEME.def().conflicts_with(SHIELDED.name).help( + "For the transparent pool, the type of key that should be \ + derived. Argument must be either ed25519 or secp256k1. If \ + none provided, the default key scheme is ed25519.\nNot \ + applicable for the shielded pool.", )) + .arg( + SHIELDED + .def() + .help("Derive a spending key for the shielded pool."), + ) .arg(ALIAS_OPT.def().help( "The key and address alias. If none provided, the alias will \ be the public key hash.", @@ -6357,8 +6365,7 @@ pub mod args { scheme default path:\n- m/44'/60'/0'/0/0 for secp256k1 \ scheme\n- m/44'/877'/0'/0'/0' for ed25519 scheme.\nFor \ ed25519, all path indices will be promoted to hardened \ - indexes. If none specified, mnemonic code and derivation \ - path are not used.", + indexes. TODO", )) } } diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index fdc94fb7ce..14b2265510 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -40,9 +40,6 @@ impl CliApi { ) -> Result<()> { match cmd { cmds::NamadaWallet::Key(sub) => match sub { - cmds::WalletKey::Derive(cmds::KeyDerive(args)) => { - key_and_address_derive(ctx, io, args).await - } cmds::WalletKey::Find(cmds::KeyFind(args)) => { key_find(ctx, io, args) } @@ -54,9 +51,6 @@ impl CliApi { } }, cmds::NamadaWallet::Address(sub) => match sub { - cmds::WalletAddress::Derive(cmds::AddressDerive(args)) => { - key_and_address_derive(ctx, io, args).await - } cmds::WalletAddress::Find(cmds::AddressOrAliasFind(args)) => { address_or_alias_find(ctx, io, args) } @@ -86,6 +80,9 @@ impl CliApi { cmds::NamadaWallet::NewGen(cmds::WalletNewGen(args)) => { key_gen(ctx, io, args) } + cmds::NamadaWallet::NewDerive(cmds::WalletNewDerive(args)) => { + key_derive(ctx, io, args).await + } } Ok(()) } @@ -374,14 +371,15 @@ pub fn decode_derivation_path( async fn key_and_address_derive( ctx: Context, io: &impl Io, - args::KeyAndAddressDerive { + args::KeyDerive { scheme, alias, alias_force, unsafe_dont_encrypt, derivation_path, use_device, - }: args::KeyAndAddressDerive, + .. + }: args::KeyDerive, ) { let mut wallet = load_wallet(ctx); let derivation_path = decode_derivation_path(scheme, derivation_path) @@ -531,6 +529,19 @@ fn key_and_address_gen( ); } +/// TODO +async fn key_derive( + ctx: Context, + io: &impl Io, + args_key_derive: args::KeyDerive, +) { + if !args_key_derive.shielded { + key_and_address_derive(ctx, io, args_key_derive).await + } else { + todo!() + } +} + /// Find a keypair in the wallet store. fn key_find( ctx: Context, diff --git a/sdk/src/args.rs b/sdk/src/args.rs index b8bdc9492f..ab5bffdab9 100644 --- a/sdk/src/args.rs +++ b/sdk/src/args.rs @@ -2096,6 +2096,25 @@ pub struct KeyGen { pub derivation_path: String, } +/// Wallet restore key and implicit address arguments +#[derive(Clone, Debug)] +pub struct KeyDerive { + /// Scheme type + pub scheme: SchemeType, + /// Whether to generate a spending key for the shielded pool + pub shielded: bool, + /// Key alias + pub alias: Option, + /// Whether to force overwrite the alias, if provided + pub alias_force: bool, + /// Don't encrypt the keypair + pub unsafe_dont_encrypt: bool, + /// BIP44 derivation path + pub derivation_path: String, + /// Use device to generate key and address + pub use_device: bool, +} + /// Wallet restore key and implicit address arguments #[derive(Clone, Debug)] pub struct KeyAndAddressDerive { From 16b80ded8fa82bb08d96ebc973708ef4e74a6830 Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Mon, 20 Nov 2023 16:06:08 +0100 Subject: [PATCH 055/216] Unify cli for transparent / shielded key listing --- apps/src/lib/cli.rs | 58 ++++++++++++++++++++++++++++---------- apps/src/lib/cli/wallet.rs | 29 +++++++++++-------- sdk/src/args.rs | 4 ++- 3 files changed, 64 insertions(+), 27 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index f4eedb6ce8..bebcff3703 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -489,6 +489,8 @@ pub mod cmds { NewGen(WalletNewGen), /// TODO NewDerive(WalletNewDerive), + /// TODO + NewKeyList(WalletNewKeyList), } impl Cmd for NamadaWallet { @@ -498,6 +500,7 @@ pub mod cmds { .subcommand(WalletMasp::def()) .subcommand(WalletNewGen::def()) .subcommand(WalletNewDerive::def()) + .subcommand(WalletNewKeyList::def()) } fn parse(matches: &ArgMatches) -> Option { @@ -506,7 +509,12 @@ pub mod cmds { let masp = SubCmd::parse(matches).map(Self::Masp); let gen_new = SubCmd::parse(matches).map(Self::NewGen); let derive_new = SubCmd::parse(matches).map(Self::NewDerive); - key.or(address).or(masp).or(gen_new).or(derive_new) + let key_list_new = SubCmd::parse(matches).map(Self::NewKeyList); + key.or(address) + .or(masp) + .or(gen_new) + .or(derive_new) + .or(key_list_new) } } @@ -533,7 +541,6 @@ pub mod cmds { #[allow(clippy::large_enum_variant)] pub enum WalletKey { Find(KeyFind), - List(KeyList), Export(Export), } @@ -543,9 +550,8 @@ pub mod cmds { fn parse(matches: &ArgMatches) -> Option { matches.subcommand_matches(Self::CMD).and_then(|matches| { let lookup = SubCmd::parse(matches).map(Self::Find); - let list = SubCmd::parse(matches).map(Self::List); let export = SubCmd::parse(matches).map(Self::Export); - lookup.or(list).or(export) + lookup.or(export) }) } @@ -558,7 +564,6 @@ pub mod cmds { .subcommand_required(true) .arg_required_else_help(true) .subcommand(KeyFind::def()) - .subcommand(KeyList::def()) .subcommand(Export::def()) } } @@ -662,6 +667,25 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct WalletNewKeyList(pub args::KeyList); + + impl SubCmd for WalletNewKeyList { + const CMD: &'static str = "list"; + + fn parse(matches: &ArgMatches) -> Option { + matches + .subcommand_matches(Self::CMD) + .map(|matches| (Self(args::KeyList::parse(matches)))) + } + + fn def() -> App { + App::new(Self::CMD) + .about("List all known keys.") + .add_args::() + } + } + #[derive(Clone, Debug)] pub struct KeyList(pub args::KeyList); @@ -706,7 +730,6 @@ pub mod cmds { GenPayAddr(MaspGenPayAddr), AddAddrKey(MaspAddAddrKey), ListPayAddrs, - ListKeys(MaspListKeys), FindAddrKey(MaspFindAddrKey), } @@ -719,9 +742,8 @@ pub mod cmds { let addak = SubCmd::parse(matches).map(Self::AddAddrKey); let listpa = ::parse(matches) .map(|_| Self::ListPayAddrs); - let listsk = SubCmd::parse(matches).map(Self::ListKeys); let findak = SubCmd::parse(matches).map(Self::FindAddrKey); - genpa.or(addak).or(listpa).or(listsk).or(findak) + genpa.or(addak).or(listpa).or(findak) }) } @@ -737,7 +759,6 @@ pub mod cmds { .subcommand(MaspGenPayAddr::def()) .subcommand(MaspAddAddrKey::def()) .subcommand(MaspListPayAddrs::def()) - .subcommand(MaspListKeys::def()) .subcommand(MaspFindAddrKey::def()) } } @@ -6453,21 +6474,28 @@ pub mod args { impl Args for KeyList { fn parse(matches: &ArgMatches) -> Self { + let shielded = SHIELDED.parse(matches); let decrypt = DECRYPT.parse(matches); let unsafe_show_secret = UNSAFE_SHOW_SECRET.parse(matches); Self { + shielded, decrypt, unsafe_show_secret, } } fn def(app: App) -> App { - app.arg(DECRYPT.def().help("Decrypt keys that are encrypted.")) - .arg( - UNSAFE_SHOW_SECRET - .def() - .help("UNSAFE: Print the secret keys."), - ) + app.arg( + SHIELDED + .def() + .help("List spending keys for the shielded pool."), + ) + .arg(DECRYPT.def().help("Decrypt keys that are encrypted.")) + .arg( + UNSAFE_SHOW_SECRET + .def() + .help("UNSAFE: Print the secret keys."), + ) } } diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index 14b2265510..22e4d38c31 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -43,9 +43,6 @@ impl CliApi { cmds::WalletKey::Find(cmds::KeyFind(args)) => { key_find(ctx, io, args) } - cmds::WalletKey::List(cmds::KeyList(args)) => { - key_list(ctx, io, args) - } cmds::WalletKey::Export(cmds::Export(args)) => { key_export(ctx, io, args) } @@ -70,9 +67,6 @@ impl CliApi { cmds::WalletMasp::ListPayAddrs => { payment_addresses_list(ctx, io) } - cmds::WalletMasp::ListKeys(cmds::MaspListKeys(args)) => { - spending_keys_list(ctx, io, args) - } cmds::WalletMasp::FindAddrKey(cmds::MaspFindAddrKey(args)) => { address_key_find(ctx, io, args) } @@ -83,6 +77,9 @@ impl CliApi { cmds::NamadaWallet::NewDerive(cmds::WalletNewDerive(args)) => { key_derive(ctx, io, args).await } + cmds::NamadaWallet::NewKeyList(cmds::WalletNewKeyList(args)) => { + key_list(ctx, io, args) + } } Ok(()) } @@ -131,10 +128,11 @@ fn address_key_find( fn spending_keys_list( ctx: Context, io: &impl Io, - args::MaspKeysList { + args::KeyList { decrypt, unsafe_show_secret, - }: args::MaspKeysList, + .. + }: args::KeyList, ) { let wallet = load_wallet(ctx); let known_view_keys = wallet.get_viewing_keys(); @@ -542,6 +540,15 @@ async fn key_derive( } } +/// TODO +fn key_list(ctx: Context, io: &impl Io, args_key_list: args::KeyList) { + if !args_key_list.shielded { + transparent_keys_list(ctx, io, args_key_list) + } else { + spending_keys_list(ctx, io, args_key_list) + } +} + /// Find a keypair in the wallet store. fn key_find( ctx: Context, @@ -589,12 +596,13 @@ fn key_find( } /// List all known keys. -fn key_list( +fn transparent_keys_list( ctx: Context, io: &impl Io, args::KeyList { decrypt, unsafe_show_secret, + .. }: args::KeyList, ) { let wallet = load_wallet(ctx); @@ -602,8 +610,7 @@ fn key_list( if known_public_keys.is_empty() { display_line!( io, - "No known keys. Try `key gen --alias my-key` to generate a new \ - key.", + "No known keys. Try `gen --alias my-key` to generate a new key.", ); } else { let stdout = io::stdout(); diff --git a/sdk/src/args.rs b/sdk/src/args.rs index ab5bffdab9..3aa21d3e96 100644 --- a/sdk/src/args.rs +++ b/sdk/src/args.rs @@ -2166,7 +2166,9 @@ pub struct MaspKeysList { /// Wallet list keys arguments #[derive(Clone, Debug)] pub struct KeyList { - /// Don't decrypt keypairs + /// Whether to list spending keys of the shielded pool + pub shielded: bool, + /// Don't decrypt keys pub decrypt: bool, /// Show secret keys to user pub unsafe_show_secret: bool, From b345e35c5aecd0f7dc6b53f4f87a269fa2a4da1b Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Tue, 21 Nov 2023 15:45:09 +0100 Subject: [PATCH 056/216] Unify cli for transparent / shielded key / address listing / searching --- apps/src/lib/cli.rs | 366 +++++++++++++++---------------------- apps/src/lib/cli/wallet.rs | 174 +++++++++++------- sdk/src/args.rs | 67 +++---- 3 files changed, 274 insertions(+), 333 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index bebcff3703..b8580e8ba1 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -491,6 +491,12 @@ pub mod cmds { NewDerive(WalletNewDerive), /// TODO NewKeyList(WalletNewKeyList), + /// TODO + NewKeyFind(WalletNewKeyFind), + /// TODO + NewAddrList(WalletNewAddressList), + /// TODO + NewAddrFind(WalletNewAddressFind), } impl Cmd for NamadaWallet { @@ -501,6 +507,9 @@ pub mod cmds { .subcommand(WalletNewGen::def()) .subcommand(WalletNewDerive::def()) .subcommand(WalletNewKeyList::def()) + .subcommand(WalletNewKeyFind::def()) + .subcommand(WalletNewAddressList::def()) + .subcommand(WalletNewAddressFind::def()) } fn parse(matches: &ArgMatches) -> Option { @@ -510,11 +519,17 @@ pub mod cmds { let gen_new = SubCmd::parse(matches).map(Self::NewGen); let derive_new = SubCmd::parse(matches).map(Self::NewDerive); let key_list_new = SubCmd::parse(matches).map(Self::NewKeyList); + let key_find_new = SubCmd::parse(matches).map(Self::NewKeyFind); + let addr_list_new = SubCmd::parse(matches).map(Self::NewAddrList); + let addr_find_new = SubCmd::parse(matches).map(Self::NewAddrFind); key.or(address) .or(masp) .or(gen_new) .or(derive_new) .or(key_list_new) + .or(key_find_new) + .or(addr_list_new) + .or(addr_find_new) } } @@ -540,7 +555,6 @@ pub mod cmds { #[derive(Clone, Debug)] #[allow(clippy::large_enum_variant)] pub enum WalletKey { - Find(KeyFind), Export(Export), } @@ -549,9 +563,8 @@ pub mod cmds { fn parse(matches: &ArgMatches) -> Option { matches.subcommand_matches(Self::CMD).and_then(|matches| { - let lookup = SubCmd::parse(matches).map(Self::Find); let export = SubCmd::parse(matches).map(Self::Export); - lookup.or(export) + export }) } @@ -563,37 +576,10 @@ pub mod cmds { ) .subcommand_required(true) .arg_required_else_help(true) - .subcommand(KeyFind::def()) .subcommand(Export::def()) } } - /// Restore a keypair and implicit address from the mnemonic code - #[derive(Clone, Debug)] - pub struct KeyDerive(pub args::KeyDerive); - - impl SubCmd for KeyDerive { - const CMD: &'static str = "derive"; - - fn parse(matches: &ArgMatches) -> Option { - matches - .subcommand_matches(Self::CMD) - .map(|matches| Self(args::KeyDerive::parse(matches))) - } - - fn def() -> App { - App::new(Self::CMD) - .about( - "Derives a keypair from the given mnemonic code and HD \ - derivation path and derives the implicit address from \ - its public key. Stores the keypair and the address with \ - the given alias. A hardware wallet can be used, in which \ - case a private key is not derivable.", - ) - .add_args::() - } - } - /// In the transparent setting, generate a new keypair and an implicit /// address derived from it. In the shielded setting, generate a new /// spending key. @@ -611,7 +597,8 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) - .about( + .about("Generates a new transparent / shielded secret key.") + .long_about( "Generates a keypair with a given alias and derives the \ implicit address from its public key. The address will \ be stored with the same alias. TODO shielded", @@ -638,6 +625,9 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about( + "Derive transparent / shielded key from the mnemonic code.", + ) + .long_about( "Derives a keypair from the given mnemonic code and HD \ derivation path and derives the implicit address from \ its public key. Stores the keypair and the address with \ @@ -648,60 +638,97 @@ pub mod cmds { } } + /// TODO List all known shielded keys #[derive(Clone, Debug)] - pub struct KeyFind(pub args::KeyFind); + pub struct WalletNewKeyList(pub args::KeyList); - impl SubCmd for KeyFind { - const CMD: &'static str = "find"; + impl SubCmd for WalletNewKeyList { + const CMD: &'static str = "list-keys"; fn parse(matches: &ArgMatches) -> Option { matches .subcommand_matches(Self::CMD) - .map(|matches| (Self(args::KeyFind::parse(matches)))) + .map(|matches| (Self(args::KeyList::parse(matches)))) } fn def() -> App { App::new(Self::CMD) - .about("Searches for a keypair from a public key or an alias.") - .add_args::() + .about( + "TODO List all known keys. Lists all shielded keys in the \ + wallet.", + ) + .add_args::() } } + /// TODO Find a keypair in the wallet store + /// TODO Find the given shielded address or key #[derive(Clone, Debug)] - pub struct WalletNewKeyList(pub args::KeyList); + pub struct WalletNewKeyFind(pub args::KeyFind); - impl SubCmd for WalletNewKeyList { - const CMD: &'static str = "list"; + impl SubCmd for WalletNewKeyFind { + const CMD: &'static str = "find-key"; fn parse(matches: &ArgMatches) -> Option { matches .subcommand_matches(Self::CMD) - .map(|matches| (Self(args::KeyList::parse(matches)))) + .map(|matches| (Self(args::KeyFind::parse(matches)))) } fn def() -> App { App::new(Self::CMD) - .about("List all known keys.") + .about( + "TODO Searches for a keypair from a public key or an \ + alias. Find the given shielded address or key in the \ + wallet.", + ) .add_args::() } } + /// List known addresses + /// TODO List all known payment addresses #[derive(Clone, Debug)] - pub struct KeyList(pub args::KeyList); + pub struct WalletNewAddressList(pub args::AddressList); - impl SubCmd for KeyList { - const CMD: &'static str = "list"; + impl SubCmd for WalletNewAddressList { + const CMD: &'static str = "list-addr"; fn parse(matches: &ArgMatches) -> Option { matches .subcommand_matches(Self::CMD) - .map(|matches| (Self(args::KeyList::parse(matches)))) + .map(|matches| Self(args::AddressList::parse(matches))) } fn def() -> App { App::new(Self::CMD) - .about("List all known keys.") - .add_args::() + .about( + "TODO List all known addresses. Lists all payment \ + addresses in the wallet", + ) + .add_args::() + } + } + + /// Find an address by its alias + #[derive(Clone, Debug)] + pub struct WalletNewAddressFind(pub args::AddressFind); + + impl SubCmd for WalletNewAddressFind { + const CMD: &'static str = "find-addr"; + + fn parse(matches: &ArgMatches) -> Option { + matches + .subcommand_matches(Self::CMD) + .map(|matches| Self(args::AddressFind::parse(matches))) + } + + fn def() -> App { + App::new(Self::CMD) + .about( + "Find an address by its alias or an alias by its address.", + ) + .add_args::() } } @@ -729,8 +756,6 @@ pub mod cmds { pub enum WalletMasp { GenPayAddr(MaspGenPayAddr), AddAddrKey(MaspAddAddrKey), - ListPayAddrs, - FindAddrKey(MaspFindAddrKey), } impl SubCmd for WalletMasp { @@ -740,10 +765,7 @@ pub mod cmds { matches.subcommand_matches(Self::CMD).and_then(|matches| { let genpa = SubCmd::parse(matches).map(Self::GenPayAddr); let addak = SubCmd::parse(matches).map(Self::AddAddrKey); - let listpa = ::parse(matches) - .map(|_| Self::ListPayAddrs); - let findak = SubCmd::parse(matches).map(Self::FindAddrKey); - genpa.or(addak).or(listpa).or(findak) + genpa.or(addak) }) } @@ -758,65 +780,6 @@ pub mod cmds { .arg_required_else_help(true) .subcommand(MaspGenPayAddr::def()) .subcommand(MaspAddAddrKey::def()) - .subcommand(MaspListPayAddrs::def()) - .subcommand(MaspFindAddrKey::def()) - } - } - - /// Find the given shielded address or key - #[derive(Clone, Debug)] - pub struct MaspFindAddrKey(pub args::AddrKeyFind); - - impl SubCmd for MaspFindAddrKey { - const CMD: &'static str = "find"; - - fn parse(matches: &ArgMatches) -> Option { - matches - .subcommand_matches(Self::CMD) - .map(|matches| Self(args::AddrKeyFind::parse(matches))) - } - - fn def() -> App { - App::new(Self::CMD) - .about("Find the given shielded address or key in the wallet") - .add_args::() - } - } - - /// List all known shielded keys - #[derive(Clone, Debug)] - pub struct MaspListKeys(pub args::MaspKeysList); - - impl SubCmd for MaspListKeys { - const CMD: &'static str = "list-keys"; - - fn parse(matches: &ArgMatches) -> Option { - matches - .subcommand_matches(Self::CMD) - .map(|matches| Self(args::MaspKeysList::parse(matches))) - } - - fn def() -> App { - App::new(Self::CMD) - .about("Lists all shielded keys in the wallet") - .add_args::() - } - } - - /// List all known payment addresses - #[derive(Clone, Debug)] - pub struct MaspListPayAddrs; - - impl SubCmd for MaspListPayAddrs { - const CMD: &'static str = "list-addrs"; - - fn parse(matches: &ArgMatches) -> Option { - matches.subcommand_matches(Self::CMD).map(|_| Self) - } - - fn def() -> App { - App::new(Self::CMD) - .about("Lists all payment addresses in the wallet") } } @@ -884,8 +847,6 @@ pub mod cmds { #[derive(Clone, Debug)] pub enum WalletAddress { - Find(AddressOrAliasFind), - List, Add(AddressAdd), } @@ -894,11 +855,8 @@ pub mod cmds { fn parse(matches: &ArgMatches) -> Option { matches.subcommand_matches(Self::CMD).and_then(|matches| { - let find = SubCmd::parse(matches).map(Self::Find); - let list = - ::parse(matches).map(|_| Self::List); let add = SubCmd::parse(matches).map(Self::Add); - find.or(list).or(add) + add }) } @@ -910,34 +868,10 @@ pub mod cmds { ) .subcommand_required(true) .arg_required_else_help(true) - .subcommand(AddressOrAliasFind::def()) - .subcommand(AddressList::def()) .subcommand(AddressAdd::def()) } } - /// Find an address by its alias - #[derive(Clone, Debug)] - pub struct AddressOrAliasFind(pub args::AddressOrAliasFind); - - impl SubCmd for AddressOrAliasFind { - const CMD: &'static str = "find"; - - fn parse(matches: &ArgMatches) -> Option { - matches.subcommand_matches(Self::CMD).map(|matches| { - AddressOrAliasFind(args::AddressOrAliasFind::parse(matches)) - }) - } - - fn def() -> App { - App::new(Self::CMD) - .about( - "Find an address by its alias or an alias by its address.", - ) - .add_args::() - } - } - /// List known addresses #[derive(Clone, Debug)] pub struct AddressList; @@ -6391,95 +6325,46 @@ pub mod args { } } - impl Args for KeyFind { + impl Args for KeyList { fn parse(matches: &ArgMatches) -> Self { - let public_key = RAW_PUBLIC_KEY_OPT.parse(matches); - let alias = ALIAS_OPT.parse(matches); - let value = VALUE.parse(matches); + let shielded = SHIELDED.parse(matches); + let decrypt = DECRYPT.parse(matches); let unsafe_show_secret = UNSAFE_SHOW_SECRET.parse(matches); - Self { - public_key, - alias, - value, + shielded, + decrypt, unsafe_show_secret, } } fn def(app: App) -> App { app.arg( - RAW_PUBLIC_KEY_OPT - .def() - .help("A public key associated with the keypair.") - .conflicts_with_all([ALIAS_OPT.name, VALUE.name]), - ) - .arg( - ALIAS_OPT - .def() - .help("An alias associated with the keypair.") - .conflicts_with(VALUE.name), - ) - .arg( - VALUE + SHIELDED .def() - .help("A public key or alias associated with the keypair."), + .help("List spending keys for the shielded pool."), ) + .arg(DECRYPT.def().help("Decrypt keys that are encrypted.")) .arg( UNSAFE_SHOW_SECRET .def() - .help("UNSAFE: Print the secret key."), + .help("UNSAFE: Print the secret / spending keys."), ) } } - impl Args for AddrKeyFind { - fn parse(matches: &ArgMatches) -> Self { - let alias = ALIAS.parse(matches); - let unsafe_show_secret = UNSAFE_SHOW_SECRET.parse(matches); - Self { - alias, - unsafe_show_secret, - } - } - - fn def(app: App) -> App { - app.arg(ALIAS.def().help("The alias that is to be found.")) - .arg( - UNSAFE_SHOW_SECRET - .def() - .help("UNSAFE: Print the spending key values."), - ) - } - } - - impl Args for MaspKeysList { - fn parse(matches: &ArgMatches) -> Self { - let decrypt = DECRYPT.parse(matches); - let unsafe_show_secret = UNSAFE_SHOW_SECRET.parse(matches); - Self { - decrypt, - unsafe_show_secret, - } - } - - fn def(app: App) -> App { - app.arg(DECRYPT.def().help("Decrypt keys that are encrypted.")) - .arg( - UNSAFE_SHOW_SECRET - .def() - .help("UNSAFE: Print the spending key values."), - ) - } - } - - impl Args for KeyList { + impl Args for KeyFind { fn parse(matches: &ArgMatches) -> Self { let shielded = SHIELDED.parse(matches); - let decrypt = DECRYPT.parse(matches); + let alias = ALIAS_OPT.parse(matches); + let public_key = RAW_PUBLIC_KEY_OPT.parse(matches); + let value = VALUE.parse(matches); let unsafe_show_secret = UNSAFE_SHOW_SECRET.parse(matches); + Self { shielded, - decrypt, + alias, + public_key, + value, unsafe_show_secret, } } @@ -6488,31 +6373,59 @@ pub mod args { app.arg( SHIELDED .def() - .help("List spending keys for the shielded pool."), + .help("Find spending key for the shielded pool.") + .conflicts_with_all([VALUE.name, RAW_PUBLIC_KEY_OPT.name]), ) - .arg(DECRYPT.def().help("Decrypt keys that are encrypted.")) .arg( - UNSAFE_SHOW_SECRET + ALIAS_OPT + .def() + .help( + "TODO An alias associated with the keypair. The alias \ + that is to be found.", + ) + .conflicts_with(VALUE.name), + ) + .arg( + RAW_PUBLIC_KEY_OPT + .def() + .help("A public key associated with the keypair."), + ) + .arg( + VALUE .def() - .help("UNSAFE: Print the secret keys."), + .help("A public key or alias associated with the keypair."), + ) + .group( + ArgGroup::new("key_find_flags") + .args([ALIAS_OPT.name, RAW_PUBLIC_KEY_OPT.name, VALUE.name]) + .required(true), ) + .arg(PRE_GENESIS.def().help( + "Use pre-genesis wallet, instead of for the current chain, if \ + any.", + )) + .arg(UNSAFE_SHOW_SECRET.def().help( + "TODO UNSAFE: Print the secret key.Print the spending key \ + values.", + )) } } - impl Args for KeyExport { + impl Args for AddressList { fn parse(matches: &ArgMatches) -> Self { - let alias = ALIAS.parse(matches); - Self { alias } + let shielded = SHIELDED.parse(matches); + Self { shielded } } fn def(app: App) -> App { - app.arg( - ALIAS.def().help("The alias of the key you wish to export."), - ) + app.arg(PRE_GENESIS.def().help( + "Use pre-genesis wallet, instead of for the current chain, if \ + any.", + )) } } - impl Args for AddressOrAliasFind { + impl Args for AddressFind { fn parse(matches: &ArgMatches) -> Self { let alias = ALIAS_OPT.parse(matches); let address = RAW_ADDRESS_OPT.parse(matches); @@ -6567,6 +6480,19 @@ pub mod args { } } + impl Args for KeyExport { + fn parse(matches: &ArgMatches) -> Self { + let alias = ALIAS.parse(matches); + Self { alias } + } + + fn def(app: App) -> App { + app.arg( + ALIAS.def().help("The alias of the key you wish to export."), + ) + } + } + #[derive(Clone, Debug)] pub struct JoinNetwork { pub chain_id: ChainId, diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index 22e4d38c31..ffd08a50c0 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -40,18 +40,11 @@ impl CliApi { ) -> Result<()> { match cmd { cmds::NamadaWallet::Key(sub) => match sub { - cmds::WalletKey::Find(cmds::KeyFind(args)) => { - key_find(ctx, io, args) - } cmds::WalletKey::Export(cmds::Export(args)) => { key_export(ctx, io, args) } }, cmds::NamadaWallet::Address(sub) => match sub { - cmds::WalletAddress::Find(cmds::AddressOrAliasFind(args)) => { - address_or_alias_find(ctx, io, args) - } - cmds::WalletAddress::List => address_list(ctx, io), cmds::WalletAddress::Add(cmds::AddressAdd(args)) => { address_add(ctx, io, args) } @@ -64,12 +57,6 @@ impl CliApi { cmds::WalletMasp::AddAddrKey(cmds::MaspAddAddrKey(args)) => { address_key_add(ctx, io, args) } - cmds::WalletMasp::ListPayAddrs => { - payment_addresses_list(ctx, io) - } - cmds::WalletMasp::FindAddrKey(cmds::MaspFindAddrKey(args)) => { - address_key_find(ctx, io, args) - } }, cmds::NamadaWallet::NewGen(cmds::WalletNewGen(args)) => { key_gen(ctx, io, args) @@ -80,47 +67,17 @@ impl CliApi { cmds::NamadaWallet::NewKeyList(cmds::WalletNewKeyList(args)) => { key_list(ctx, io, args) } - } - Ok(()) - } -} - -/// Find shielded address or key -fn address_key_find( - ctx: Context, - io: &impl Io, - args::AddrKeyFind { - alias, - unsafe_show_secret, - }: args::AddrKeyFind, -) { - let mut wallet = load_wallet(ctx); - let alias = alias.to_lowercase(); - if let Ok(viewing_key) = wallet.find_viewing_key(&alias) { - // Check if alias is a viewing key - display_line!(io, "Viewing key: {}", viewing_key); - if unsafe_show_secret { - // Check if alias is also a spending key - match wallet.find_spending_key(&alias, None) { - Ok(spending_key) => { - display_line!(io, "Spending key: {}", spending_key) - } - Err(FindKeyError::KeyNotFound(_)) => {} - Err(err) => edisplay_line!(io, "{}", err), + cmds::NamadaWallet::NewKeyFind(cmds::WalletNewKeyFind(args)) => { + key_find(ctx, io, args) } + cmds::NamadaWallet::NewAddrList(cmds::WalletNewAddressList( + args, + )) => address_list(ctx, io, args), + cmds::NamadaWallet::NewAddrFind(cmds::WalletNewAddressFind( + args, + )) => address_or_alias_find(ctx, io, args), } - } else if let Some(payment_addr) = wallet.find_payment_addr(&alias) { - // Failing that, check if alias is a payment address - display_line!(io, "Payment address: {}", payment_addr); - } else { - // Otherwise alias cannot be referring to any shielded value - display_line!( - io, - "No shielded address or key with alias {} found. Use the commands \ - `masp list-addrs` and `masp list-keys` to see all the known \ - addresses and keys.", - alias.to_lowercase() - ); + Ok(()) } } @@ -140,8 +97,8 @@ fn spending_keys_list( if known_view_keys.is_empty() { display_line!( io, - "No known keys. Try `masp add --alias my-addr --value ...` to add \ - a new key to the wallet.", + "TODO No known keys. Try `masp add --alias my-addr --value ...` \ + to add a new key to the wallet.", ); } else { let stdout = io::stdout(); @@ -202,7 +159,11 @@ fn spending_keys_list( } /// List payment addresses. -fn payment_addresses_list(ctx: Context, io: &impl Io) { +fn payment_addresses_list( + ctx: Context, + io: &impl Io, + // args::AddressList { .. }: args::AddressList, +) { let wallet = load_wallet(ctx); let known_addresses = wallet.get_payment_addrs(); if known_addresses.is_empty() { @@ -366,7 +327,7 @@ pub fn decode_derivation_path( /// Derives a keypair and an implicit address from the mnemonic code in the /// wallet. -async fn key_and_address_derive( +async fn transparent_key_and_address_derive( ctx: Context, io: &impl Io, args::KeyDerive { @@ -461,18 +422,9 @@ async fn key_and_address_derive( ); } -/// TODO -fn key_gen(ctx: Context, io: &impl Io, args_key_gen: args::KeyGen) { - if !args_key_gen.shielded { - key_and_address_gen(ctx, io, args_key_gen) - } else { - spending_key_gen(ctx, io, args_key_gen) - } -} - /// Generate a new keypair and derive implicit address from it and store them in /// the wallet. -fn key_and_address_gen( +fn transparent_key_and_address_gen( ctx: Context, io: &impl Io, args::KeyGen { @@ -527,6 +479,15 @@ fn key_and_address_gen( ); } +/// TODO +fn key_gen(ctx: Context, io: &impl Io, args_key_gen: args::KeyGen) { + if !args_key_gen.shielded { + transparent_key_and_address_gen(ctx, io, args_key_gen) + } else { + spending_key_gen(ctx, io, args_key_gen) + } +} + /// TODO async fn key_derive( ctx: Context, @@ -534,7 +495,7 @@ async fn key_derive( args_key_derive: args::KeyDerive, ) { if !args_key_derive.shielded { - key_and_address_derive(ctx, io, args_key_derive).await + transparent_key_and_address_derive(ctx, io, args_key_derive).await } else { todo!() } @@ -549,8 +510,26 @@ fn key_list(ctx: Context, io: &impl Io, args_key_list: args::KeyList) { } } +/// TODO +fn key_find(ctx: Context, io: &impl Io, args_key_find: args::KeyFind) { + if !args_key_find.shielded { + transparent_key_address_find(ctx, io, args_key_find) + } else { + shielded_key_address_find(ctx, io, args_key_find) + } +} + +/// TODO +fn address_list(ctx: Context, io: &impl Io, args_key_list: args::AddressList) { + if !args_key_list.shielded { + transparent_addresses_list(ctx, io, args_key_list) + } else { + payment_addresses_list(ctx, io) + } +} + /// Find a keypair in the wallet store. -fn key_find( +fn transparent_key_address_find( ctx: Context, io: &impl Io, args::KeyFind { @@ -558,6 +537,7 @@ fn key_find( alias, value, unsafe_show_secret, + .. }: args::KeyFind, ) { let mut wallet = load_wallet(ctx); @@ -595,6 +575,53 @@ fn key_find( } } +/// Find shielded address or key +/// TODO this works for both keys and addresses +/// TODO split to enable finding alias by payment key +fn shielded_key_address_find( + ctx: Context, + io: &impl Io, + args::KeyFind { + alias, + unsafe_show_secret, + .. + }: args::KeyFind, +) { + let mut wallet = load_wallet(ctx); + let alias = alias.unwrap_or_else(|| { + display_line!(io, "Missing alias."); + display_line!(io, "No changes are persisted. Exiting."); + cli::safe_exit(1) + }); + let alias = alias.to_lowercase(); + if let Ok(viewing_key) = wallet.find_viewing_key(&alias) { + // Check if alias is a viewing key + display_line!(io, "Viewing key: {}", viewing_key); + if unsafe_show_secret { + // Check if alias is also a spending key + match wallet.find_spending_key(&alias, None) { + Ok(spending_key) => { + display_line!(io, "Spending key: {}", spending_key) + } + Err(FindKeyError::KeyNotFound(_)) => {} + Err(err) => edisplay_line!(io, "{}", err), + } + } + } else if let Some(payment_addr) = wallet.find_payment_addr(&alias) { + // Failing that, check if alias is a payment address + display_line!(io, "Payment address: {}", payment_addr); + } else { + // Otherwise alias cannot be referring to any shielded value + display_line!( + io, + "TODO No shielded address or key with alias {} found. Use the \ + commands `masp list-addrs` and `masp list-keys` to see all the \ + known addresses and keys.", + alias.to_lowercase() + ); + } +} + /// List all known keys. fn transparent_keys_list( ctx: Context, @@ -686,8 +713,13 @@ fn key_export( }) } -/// List all known addresses. -fn address_list(ctx: Context, io: &impl Io) { +/// List all known transparent addresses. +fn transparent_addresses_list( + ctx: Context, + io: &impl Io, + _args: args::AddressList, + // args::AddressList { is_pre_genesis, .. }: args::AddressList, +) { let wallet = load_wallet(ctx); let known_addresses = wallet.get_addresses(); if known_addresses.is_empty() { @@ -714,7 +746,7 @@ fn address_list(ctx: Context, io: &impl Io) { fn address_or_alias_find( ctx: Context, io: &impl Io, - args::AddressOrAliasFind { alias, address }: args::AddressOrAliasFind, + args::AddressFind { alias, address }: args::AddressFind, ) { let wallet = load_wallet(ctx); if address.is_some() && alias.is_some() { diff --git a/sdk/src/args.rs b/sdk/src/args.rs index 3aa21d3e96..50aa46d6c6 100644 --- a/sdk/src/args.rs +++ b/sdk/src/args.rs @@ -2115,53 +2115,29 @@ pub struct KeyDerive { pub use_device: bool, } -/// Wallet restore key and implicit address arguments -#[derive(Clone, Debug)] -pub struct KeyAndAddressDerive { - /// Scheme type - pub scheme: SchemeType, - /// Key alias - pub alias: Option, - /// Whether to force overwrite the alias, if provided - pub alias_force: bool, - /// Don't encrypt the keypair - pub unsafe_dont_encrypt: bool, - /// BIP44 derivation path - pub derivation_path: String, - /// Use device to generate key and address - pub use_device: bool, -} - -/// Wallet key lookup arguments +/// Wallet find shielded address or key arguments +/// TODO Wallet key lookup arguments #[derive(Clone, Debug)] pub struct KeyFind { - /// Public key to lookup keypair with - pub public_key: Option, + /// Whether to find shielded address by alias + pub shielded: bool, /// Key alias to lookup keypair with pub alias: Option, + /// Public key to lookup keypair with + pub public_key: Option, /// Public key hash to lookup keypair with pub value: Option, /// Show secret keys to user pub unsafe_show_secret: bool, } -/// Wallet find shielded address or key arguments -#[derive(Clone, Debug)] -pub struct AddrKeyFind { - /// Address/key alias - pub alias: String, - /// Show secret keys to user - pub unsafe_show_secret: bool, -} - -/// Wallet list shielded keys arguments -#[derive(Clone, Debug)] -pub struct MaspKeysList { - /// Don't decrypt spending keys - pub decrypt: bool, - /// Show secret keys to user - pub unsafe_show_secret: bool, -} +/// Wallet list shielded payment addresses arguments +// #[derive(Clone, Debug)] +// pub struct MaspListPayAddrs { +// /// List shielded payment address pre-genesis instead +// /// of a current chain +// pub is_pre_genesis: bool, +// } /// Wallet list keys arguments #[derive(Clone, Debug)] @@ -2174,22 +2150,29 @@ pub struct KeyList { pub unsafe_show_secret: bool, } -/// Wallet key export arguments +/// List transparent wallet addresses / shielded payment addresses #[derive(Clone, Debug)] -pub struct KeyExport { - /// Key alias - pub alias: String, +pub struct AddressList { + /// Whether to list payment addresses of the shielded pool + pub shielded: bool, } /// Wallet address lookup arguments #[derive(Clone, Debug)] -pub struct AddressOrAliasFind { +pub struct AddressFind { /// Alias to find pub alias: Option, /// Address to find pub address: Option
, } +/// Wallet key export arguments +#[derive(Clone, Debug)] +pub struct KeyExport { + /// Key alias + pub alias: String, +} + /// Wallet address add arguments #[derive(Clone, Debug)] pub struct AddressAdd { From 1947aa9a38d1c67c2ef41de3c3af526a304a52d4 Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Tue, 21 Nov 2023 17:34:50 +0100 Subject: [PATCH 057/216] Refactor key export --- apps/src/lib/cli.rs | 46 ++++++++++----------------------------------- 1 file changed, 10 insertions(+), 36 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index b8580e8ba1..06dfd32b50 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -479,8 +479,6 @@ pub mod cmds { #[allow(clippy::large_enum_variant)] #[derive(Clone, Debug)] pub enum NamadaWallet { - /// Key management commands - Key(WalletKey), /// Address management commands Address(WalletAddress), /// MASP key, address management commands @@ -497,12 +495,13 @@ pub mod cmds { NewAddrList(WalletNewAddressList), /// TODO NewAddrFind(WalletNewAddressFind), + /// TODO + NewKeyExport(WalletNewExportKey), } impl Cmd for NamadaWallet { fn add_sub(app: App) -> App { - app.subcommand(WalletKey::def()) - .subcommand(WalletAddress::def()) + app.subcommand(WalletAddress::def()) .subcommand(WalletMasp::def()) .subcommand(WalletNewGen::def()) .subcommand(WalletNewDerive::def()) @@ -510,10 +509,10 @@ pub mod cmds { .subcommand(WalletNewKeyFind::def()) .subcommand(WalletNewAddressList::def()) .subcommand(WalletNewAddressFind::def()) + .subcommand(WalletNewExportKey::def()) } fn parse(matches: &ArgMatches) -> Option { - let key = SubCmd::parse(matches).map(Self::Key); let address = SubCmd::parse(matches).map(Self::Address); let masp = SubCmd::parse(matches).map(Self::Masp); let gen_new = SubCmd::parse(matches).map(Self::NewGen); @@ -522,7 +521,8 @@ pub mod cmds { let key_find_new = SubCmd::parse(matches).map(Self::NewKeyFind); let addr_list_new = SubCmd::parse(matches).map(Self::NewAddrList); let addr_find_new = SubCmd::parse(matches).map(Self::NewAddrFind); - key.or(address) + let export_new = SubCmd::parse(matches).map(Self::NewKeyExport); + address .or(masp) .or(gen_new) .or(derive_new) @@ -530,6 +530,7 @@ pub mod cmds { .or(key_find_new) .or(addr_list_new) .or(addr_find_new) + .or(export_new) } } @@ -552,34 +553,6 @@ pub mod cmds { } } - #[derive(Clone, Debug)] - #[allow(clippy::large_enum_variant)] - pub enum WalletKey { - Export(Export), - } - - impl SubCmd for WalletKey { - const CMD: &'static str = "key"; - - fn parse(matches: &ArgMatches) -> Option { - matches.subcommand_matches(Self::CMD).and_then(|matches| { - let export = SubCmd::parse(matches).map(Self::Export); - export - }) - } - - fn def() -> App { - App::new(Self::CMD) - .about( - "Keypair management, including methods to generate and \ - look-up keys.", - ) - .subcommand_required(true) - .arg_required_else_help(true) - .subcommand(Export::def()) - } - } - /// In the transparent setting, generate a new keypair and an implicit /// address derived from it. In the shielded setting, generate a new /// spending key. @@ -732,10 +705,11 @@ pub mod cmds { } } + /// Export key #[derive(Clone, Debug)] - pub struct Export(pub args::KeyExport); + pub struct WalletNewExportKey(pub args::KeyExport); - impl SubCmd for Export { + impl SubCmd for WalletNewExportKey { const CMD: &'static str = "export"; fn parse(matches: &ArgMatches) -> Option { From 9efb0784d5c44a64295e71ce2e24372d8950266e Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Wed, 22 Nov 2023 00:52:41 +0100 Subject: [PATCH 058/216] Unify cli for key export / add --- apps/src/lib/cli.rs | 190 ++++++++++++------------------------- apps/src/lib/cli/wallet.rs | 55 +++++++---- sdk/src/args.rs | 12 ++- 3 files changed, 105 insertions(+), 152 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 06dfd32b50..5970a4fdfd 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -479,30 +479,29 @@ pub mod cmds { #[allow(clippy::large_enum_variant)] #[derive(Clone, Debug)] pub enum NamadaWallet { - /// Address management commands - Address(WalletAddress), /// MASP key, address management commands Masp(WalletMasp), - /// TODO + /// Key generation NewGen(WalletNewGen), - /// TODO + /// Key derivation NewDerive(WalletNewDerive), - /// TODO + /// Key list NewKeyList(WalletNewKeyList), - /// TODO + /// Key search NewKeyFind(WalletNewKeyFind), - /// TODO + /// Address list NewAddrList(WalletNewAddressList), - /// TODO + /// Address search NewAddrFind(WalletNewAddressFind), - /// TODO + /// Key export NewKeyExport(WalletNewExportKey), + /// Key import + NewKeyAddrAdd(WalletNewKeyAddressAdd), } impl Cmd for NamadaWallet { fn add_sub(app: App) -> App { - app.subcommand(WalletAddress::def()) - .subcommand(WalletMasp::def()) + app.subcommand(WalletMasp::def()) .subcommand(WalletNewGen::def()) .subcommand(WalletNewDerive::def()) .subcommand(WalletNewKeyList::def()) @@ -510,10 +509,10 @@ pub mod cmds { .subcommand(WalletNewAddressList::def()) .subcommand(WalletNewAddressFind::def()) .subcommand(WalletNewExportKey::def()) + .subcommand(WalletNewKeyAddressAdd::def()) } fn parse(matches: &ArgMatches) -> Option { - let address = SubCmd::parse(matches).map(Self::Address); let masp = SubCmd::parse(matches).map(Self::Masp); let gen_new = SubCmd::parse(matches).map(Self::NewGen); let derive_new = SubCmd::parse(matches).map(Self::NewDerive); @@ -522,15 +521,15 @@ pub mod cmds { let addr_list_new = SubCmd::parse(matches).map(Self::NewAddrList); let addr_find_new = SubCmd::parse(matches).map(Self::NewAddrFind); let export_new = SubCmd::parse(matches).map(Self::NewKeyExport); - address - .or(masp) - .or(gen_new) + let key_addr_add = SubCmd::parse(matches).map(Self::NewKeyAddrAdd); + masp.or(gen_new) .or(derive_new) .or(key_list_new) .or(key_find_new) .or(addr_list_new) .or(addr_find_new) .or(export_new) + .or(key_addr_add) } } @@ -725,11 +724,33 @@ pub mod cmds { } } + /// Add public / payment address to the wallet + #[derive(Clone, Debug)] + pub struct WalletNewKeyAddressAdd(pub args::KeyAddressAdd); + + impl SubCmd for WalletNewKeyAddressAdd { + const CMD: &'static str = "add"; + + fn parse(matches: &ArgMatches) -> Option { + matches + .subcommand_matches(Self::CMD) + .map(|matches| (Self(args::KeyAddressAdd::parse(matches)))) + } + + fn def() -> App { + App::new(Self::CMD) + .about( + "Adds the given transparent / payment address or key to \ + the wallet.", + ) + .add_args::() + } + } + #[allow(clippy::large_enum_variant)] #[derive(Clone, Debug)] pub enum WalletMasp { GenPayAddr(MaspGenPayAddr), - AddAddrKey(MaspAddAddrKey), } impl SubCmd for WalletMasp { @@ -738,8 +759,7 @@ pub mod cmds { fn parse(matches: &ArgMatches) -> Option { matches.subcommand_matches(Self::CMD).and_then(|matches| { let genpa = SubCmd::parse(matches).map(Self::GenPayAddr); - let addak = SubCmd::parse(matches).map(Self::AddAddrKey); - genpa.or(addak) + genpa }) } @@ -753,27 +773,6 @@ pub mod cmds { .subcommand_required(true) .arg_required_else_help(true) .subcommand(MaspGenPayAddr::def()) - .subcommand(MaspAddAddrKey::def()) - } - } - - /// Add a key or an address - #[derive(Clone, Debug)] - pub struct MaspAddAddrKey(pub args::MaspAddrKeyAdd); - - impl SubCmd for MaspAddAddrKey { - const CMD: &'static str = "add"; - - fn parse(matches: &ArgMatches) -> Option { - matches.subcommand_matches(Self::CMD).map(|matches| { - MaspAddAddrKey(args::MaspAddrKeyAdd::parse(matches)) - }) - } - - fn def() -> App { - App::new(Self::CMD) - .about("Adds the given payment address or key to the wallet") - .add_args::() } } @@ -819,33 +818,6 @@ pub mod cmds { } } - #[derive(Clone, Debug)] - pub enum WalletAddress { - Add(AddressAdd), - } - - impl SubCmd for WalletAddress { - const CMD: &'static str = "address"; - - fn parse(matches: &ArgMatches) -> Option { - matches.subcommand_matches(Self::CMD).and_then(|matches| { - let add = SubCmd::parse(matches).map(Self::Add); - add - }) - } - - fn def() -> App { - App::new(Self::CMD) - .about( - "Address management, including methods to generate and \ - look-up addresses.", - ) - .subcommand_required(true) - .arg_required_else_help(true) - .subcommand(AddressAdd::def()) - } - } - /// List known addresses #[derive(Clone, Debug)] pub struct AddressList; @@ -862,26 +834,6 @@ pub mod cmds { } } - /// Generate a new keypair and an implicit address derived from it - #[derive(Clone, Debug)] - pub struct AddressAdd(pub args::AddressAdd); - - impl SubCmd for AddressAdd { - const CMD: &'static str = "add"; - - fn parse(matches: &ArgMatches) -> Option { - matches - .subcommand_matches(Self::CMD) - .map(|matches| AddressAdd(args::AddressAdd::parse(matches))) - } - - fn def() -> App { - App::new(Self::CMD) - .about("Store an alias for an address in the wallet.") - .add_args::() - } - } - #[derive(Clone, Debug)] pub enum Ledger { Run(LedgerRun), @@ -3000,7 +2952,7 @@ pub mod args { pub const LEDGER_ADDRESS: Arg = arg("node"); pub const LOCALHOST: ArgFlag = flag("localhost"); - pub const MASP_VALUE: Arg = arg("value"); + pub const MASP_VALUE_OPT: ArgOpt = arg_opt("shielded-value"); pub const MAX_COMMISSION_RATE_CHANGE: Arg = arg("max-commission-rate-change"); pub const MAX_ETH_GAS: ArgOpt = arg_opt("max_eth-gas"); @@ -6048,41 +6000,6 @@ pub mod args { } } - impl Args for MaspAddrKeyAdd { - fn parse(matches: &ArgMatches) -> Self { - let alias = ALIAS.parse(matches); - let alias_force = ALIAS_FORCE.parse(matches); - let value = MASP_VALUE.parse(matches); - let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); - Self { - alias, - alias_force, - value, - unsafe_dont_encrypt, - } - } - - fn def(app: App) -> App { - app.arg( - ALIAS - .def() - .help("An alias to be associated with the new entry."), - ) - .arg(ALIAS_FORCE.def().help( - "Override the alias without confirmation if it already exists.", - )) - .arg( - MASP_VALUE - .def() - .help("A spending key, viewing key, or payment address."), - ) - .arg(UNSAFE_DONT_ENCRYPT.def().help( - "UNSAFE: Do not encrypt the keypair. Do not use this for keys \ - used in a live network.", - )) - } - } - impl Args for MaspSpendKeyGen { fn parse(matches: &ArgMatches) -> Self { let alias = ALIAS.parse(matches); @@ -6370,7 +6287,7 @@ pub mod args { .help("A public key or alias associated with the keypair."), ) .group( - ArgGroup::new("key_find_flags") + ArgGroup::new("key_find_args") .args([ALIAS_OPT.name, RAW_PUBLIC_KEY_OPT.name, VALUE.name]) .required(true), ) @@ -6425,15 +6342,19 @@ pub mod args { } } - impl Args for AddressAdd { + impl Args for KeyAddressAdd { fn parse(matches: &ArgMatches) -> Self { let alias = ALIAS.parse(matches); let alias_force = ALIAS_FORCE.parse(matches); - let address = RAW_ADDRESS.parse(matches); + let address = RAW_ADDRESS_OPT.parse(matches); + let value = MASP_VALUE_OPT.parse(matches); + let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); Self { alias, alias_force, address, + value, + unsafe_dont_encrypt, } } @@ -6441,16 +6362,29 @@ pub mod args { app.arg( ALIAS .def() - .help("An alias to be associated with the address."), + .help("An alias to be associated with the new entry."), ) .arg(ALIAS_FORCE.def().help( "Override the alias without confirmation if it already exists.", )) .arg( - RAW_ADDRESS + RAW_ADDRESS_OPT .def() - .help("The bech32m encoded address string."), + .help("The bech32m encoded transparent address string."), ) + .arg(MASP_VALUE_OPT.def().help( + "A spending key, viewing key, or payment address of the \ + shielded pool.", + )) + .group( + ArgGroup::new("key_address_add_args") + .args([RAW_ADDRESS_OPT.name, MASP_VALUE_OPT.name]) + .required(true), + ) + .arg(UNSAFE_DONT_ENCRYPT.def().help( + "UNSAFE: Do not encrypt the added keys. Do not use this for \ + keys used in a live network.", + )) } } diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index ffd08a50c0..e25707e454 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -39,24 +39,11 @@ impl CliApi { io: &impl Io, ) -> Result<()> { match cmd { - cmds::NamadaWallet::Key(sub) => match sub { - cmds::WalletKey::Export(cmds::Export(args)) => { - key_export(ctx, io, args) - } - }, - cmds::NamadaWallet::Address(sub) => match sub { - cmds::WalletAddress::Add(cmds::AddressAdd(args)) => { - address_add(ctx, io, args) - } - }, cmds::NamadaWallet::Masp(sub) => match sub { cmds::WalletMasp::GenPayAddr(cmds::MaspGenPayAddr(args)) => { let args = args.to_sdk(&mut ctx); payment_address_gen(ctx, io, args) } - cmds::WalletMasp::AddAddrKey(cmds::MaspAddAddrKey(args)) => { - address_key_add(ctx, io, args) - } }, cmds::NamadaWallet::NewGen(cmds::WalletNewGen(args)) => { key_gen(ctx, io, args) @@ -76,6 +63,12 @@ impl CliApi { cmds::NamadaWallet::NewAddrFind(cmds::WalletNewAddressFind( args, )) => address_or_alias_find(ctx, io, args), + cmds::NamadaWallet::NewKeyExport(cmds::WalletNewExportKey( + args, + )) => key_export(ctx, io, args), + cmds::NamadaWallet::NewKeyAddrAdd( + cmds::WalletNewKeyAddressAdd(args), + ) => key_address_add(ctx, io, args), } Ok(()) } @@ -251,16 +244,18 @@ fn payment_address_gen( } /// Add a viewing key, spending key, or payment address to wallet. -fn address_key_add( +fn shielded_key_address_add( ctx: Context, io: &impl Io, - args::MaspAddrKeyAdd { + args::KeyAddressAdd { alias, alias_force, value, unsafe_dont_encrypt, - }: args::MaspAddrKeyAdd, + .. + }: args::KeyAddressAdd, ) { + let value = value.unwrap(); // this should not fail let alias = alias.to_lowercase(); let mut wallet = load_wallet(ctx); let (alias, typ) = match value { @@ -528,6 +523,24 @@ fn address_list(ctx: Context, io: &impl Io, args_key_list: args::AddressList) { } } +/// TODO +fn key_address_add( + ctx: Context, + io: &impl Io, + args_key_addr_add: args::KeyAddressAdd, +) { + if args_key_addr_add.address.is_some() { + transparent_address_add(ctx, io, args_key_addr_add) + } else if args_key_addr_add.value.is_some() { + shielded_key_address_add(ctx, io, args_key_addr_add) + } else { + unreachable!( + "This should not happen as the group of address and value is \ + required." + ) + } +} + /// Find a keypair in the wallet store. fn transparent_key_address_find( ctx: Context, @@ -690,7 +703,7 @@ fn transparent_keys_list( } } -/// Export a keypair to a file. +/// Export a transparent keypair to a file. fn key_export( ctx: Context, io: &impl Io, @@ -780,15 +793,17 @@ fn address_or_alias_find( } /// Add an address to the wallet. -fn address_add( +fn transparent_address_add( ctx: Context, io: &impl Io, - args::AddressAdd { + args::KeyAddressAdd { alias, alias_force, address, - }: args::AddressAdd, + .. + }: args::KeyAddressAdd, ) { + let address = address.unwrap(); // this should not fail let mut wallet = load_wallet(ctx); if wallet .insert_address(alias.to_lowercase(), address, alias_force) diff --git a/sdk/src/args.rs b/sdk/src/args.rs index 50aa46d6c6..e2e595b166 100644 --- a/sdk/src/args.rs +++ b/sdk/src/args.rs @@ -2173,15 +2173,19 @@ pub struct KeyExport { pub alias: String, } -/// Wallet address add arguments +/// Wallet key / address add arguments #[derive(Clone, Debug)] -pub struct AddressAdd { +pub struct KeyAddressAdd { /// Address alias pub alias: String, /// Whether to force overwrite the alias pub alias_force: bool, - /// Address to add - pub address: Address, + /// Transparent address to add + pub address: Option
, + /// Any shielded value + pub value: Option, + /// Don't encrypt the keys + pub unsafe_dont_encrypt: bool, } /// Bridge pool batch recommendation. From 34be4924b0743d725f09dc868d12cb73b3c5c5fe Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Wed, 22 Nov 2023 10:18:28 +0100 Subject: [PATCH 059/216] Refactor payment key gen --- apps/src/lib/cli.rs | 132 ++++++------------------------------- apps/src/lib/cli/wallet.rs | 16 ++--- sdk/src/args.rs | 55 +++++----------- 3 files changed, 44 insertions(+), 159 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 5970a4fdfd..420a6c91e9 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -479,8 +479,6 @@ pub mod cmds { #[allow(clippy::large_enum_variant)] #[derive(Clone, Debug)] pub enum NamadaWallet { - /// MASP key, address management commands - Masp(WalletMasp), /// Key generation NewGen(WalletNewGen), /// Key derivation @@ -497,12 +495,13 @@ pub mod cmds { NewKeyExport(WalletNewExportKey), /// Key import NewKeyAddrAdd(WalletNewKeyAddressAdd), + /// Payment address generation + NewPayAddrGen(WalletNewPaymentAddressGen), } impl Cmd for NamadaWallet { fn add_sub(app: App) -> App { - app.subcommand(WalletMasp::def()) - .subcommand(WalletNewGen::def()) + app.subcommand(WalletNewGen::def()) .subcommand(WalletNewDerive::def()) .subcommand(WalletNewKeyList::def()) .subcommand(WalletNewKeyFind::def()) @@ -510,10 +509,10 @@ pub mod cmds { .subcommand(WalletNewAddressFind::def()) .subcommand(WalletNewExportKey::def()) .subcommand(WalletNewKeyAddressAdd::def()) + .subcommand(WalletNewPaymentAddressGen::def()) } fn parse(matches: &ArgMatches) -> Option { - let masp = SubCmd::parse(matches).map(Self::Masp); let gen_new = SubCmd::parse(matches).map(Self::NewGen); let derive_new = SubCmd::parse(matches).map(Self::NewDerive); let key_list_new = SubCmd::parse(matches).map(Self::NewKeyList); @@ -522,7 +521,8 @@ pub mod cmds { let addr_find_new = SubCmd::parse(matches).map(Self::NewAddrFind); let export_new = SubCmd::parse(matches).map(Self::NewKeyExport); let key_addr_add = SubCmd::parse(matches).map(Self::NewKeyAddrAdd); - masp.or(gen_new) + let pay_addr_gen = SubCmd::parse(matches).map(Self::NewPayAddrGen); + gen_new .or(derive_new) .or(key_list_new) .or(key_find_new) @@ -530,6 +530,7 @@ pub mod cmds { .or(addr_find_new) .or(export_new) .or(key_addr_add) + .or(pay_addr_gen) } } @@ -747,66 +748,19 @@ pub mod cmds { } } - #[allow(clippy::large_enum_variant)] - #[derive(Clone, Debug)] - pub enum WalletMasp { - GenPayAddr(MaspGenPayAddr), - } - - impl SubCmd for WalletMasp { - const CMD: &'static str = "masp"; - - fn parse(matches: &ArgMatches) -> Option { - matches.subcommand_matches(Self::CMD).and_then(|matches| { - let genpa = SubCmd::parse(matches).map(Self::GenPayAddr); - genpa - }) - } - - fn def() -> App { - App::new(Self::CMD) - .about( - "Multi-asset shielded pool address and keypair management \ - including methods to generate and look-up addresses and \ - keys.", - ) - .subcommand_required(true) - .arg_required_else_help(true) - .subcommand(MaspGenPayAddr::def()) - } - } - - /// Generate a spending key - #[derive(Clone, Debug)] - pub struct MaspGenSpendKey(pub args::MaspSpendKeyGen); - - impl SubCmd for MaspGenSpendKey { - const CMD: &'static str = "gen-key"; - - fn parse(matches: &ArgMatches) -> Option { - matches.subcommand_matches(Self::CMD).map(|matches| { - MaspGenSpendKey(args::MaspSpendKeyGen::parse(matches)) - }) - } - - fn def() -> App { - App::new(Self::CMD) - .about("Generates a random spending key") - .add_args::() - } - } - /// Generate a payment address from a viewing key or payment address #[derive(Clone, Debug)] - pub struct MaspGenPayAddr(pub args::MaspPayAddrGen); + pub struct WalletNewPaymentAddressGen( + pub args::PayAddressGen, + ); - impl SubCmd for MaspGenPayAddr { - const CMD: &'static str = "gen-addr"; + impl SubCmd for WalletNewPaymentAddressGen { + const CMD: &'static str = "gen-payment-addr"; fn parse(matches: &ArgMatches) -> Option { - matches.subcommand_matches(Self::CMD).map(|matches| { - MaspGenPayAddr(args::MaspPayAddrGen::parse(matches)) - }) + matches + .subcommand_matches(Self::CMD) + .map(|matches| Self(args::PayAddressGen::parse(matches))) } fn def() -> App { @@ -814,23 +768,7 @@ pub mod cmds { .about( "Generates a payment address from the given spending key", ) - .add_args::>() - } - } - - /// List known addresses - #[derive(Clone, Debug)] - pub struct AddressList; - - impl SubCmd for AddressList { - const CMD: &'static str = "list"; - - fn parse(matches: &ArgMatches) -> Option { - matches.subcommand_matches(Self::CMD).map(|_| Self) - } - - fn def() -> App { - App::new(Self::CMD).about("List all known addresses.") + .add_args::>() } } @@ -6000,36 +5938,8 @@ pub mod args { } } - impl Args for MaspSpendKeyGen { - fn parse(matches: &ArgMatches) -> Self { - let alias = ALIAS.parse(matches); - let alias_force = ALIAS_FORCE.parse(matches); - let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); - Self { - alias, - alias_force, - unsafe_dont_encrypt, - } - } - - fn def(app: App) -> App { - app.arg( - ALIAS - .def() - .help("An alias to be associated with the spending key."), - ) - .arg(ALIAS_FORCE.def().help( - "Override the alias without confirmation if it already exists.", - )) - .arg(UNSAFE_DONT_ENCRYPT.def().help( - "UNSAFE: Do not encrypt the keypair. Do not use this for keys \ - used in a live network.", - )) - } - } - - impl CliToSdk> for MaspPayAddrGen { - fn to_sdk(self, ctx: &mut Context) -> MaspPayAddrGen { + impl CliToSdk> for PayAddressGen { + fn to_sdk(self, ctx: &mut Context) -> PayAddressGen { use namada_sdk::wallet::Wallet; use crate::wallet::CliWalletUtils; @@ -6053,7 +5963,7 @@ pub mod args { } else { find_viewing_key(&mut ctx.borrow_mut_chain_or_exit().wallet) }; - MaspPayAddrGen:: { + PayAddressGen:: { alias: self.alias, alias_force: self.alias_force, viewing_key, @@ -6062,7 +5972,7 @@ pub mod args { } } - impl Args for MaspPayAddrGen { + impl Args for PayAddressGen { fn parse(matches: &ArgMatches) -> Self { let alias = ALIAS.parse(matches); let alias_force = ALIAS_FORCE.parse(matches); @@ -6335,7 +6245,7 @@ pub mod args { .help("The bech32m encoded address string."), ) .group( - ArgGroup::new("find_flags") + ArgGroup::new("addr_find_args") .args([ALIAS_OPT.name, RAW_ADDRESS_OPT.name]) .required(true), ) diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index e25707e454..311b067280 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -39,12 +39,6 @@ impl CliApi { io: &impl Io, ) -> Result<()> { match cmd { - cmds::NamadaWallet::Masp(sub) => match sub { - cmds::WalletMasp::GenPayAddr(cmds::MaspGenPayAddr(args)) => { - let args = args.to_sdk(&mut ctx); - payment_address_gen(ctx, io, args) - } - }, cmds::NamadaWallet::NewGen(cmds::WalletNewGen(args)) => { key_gen(ctx, io, args) } @@ -69,6 +63,12 @@ impl CliApi { cmds::NamadaWallet::NewKeyAddrAdd( cmds::WalletNewKeyAddressAdd(args), ) => key_address_add(ctx, io, args), + cmds::NamadaWallet::NewPayAddrGen( + cmds::WalletNewPaymentAddressGen(args), + ) => { + let args = args.to_sdk(&mut ctx); + payment_address_gen(ctx, io, args) + } } Ok(()) } @@ -210,13 +210,13 @@ fn spending_key_gen( fn payment_address_gen( ctx: Context, io: &impl Io, - args::MaspPayAddrGen { + args::PayAddressGen { alias, alias_force, viewing_key, pin, .. - }: args::MaspPayAddrGen, + }: args::PayAddressGen, ) { let mut wallet = load_wallet(ctx); let alias = alias.to_lowercase(); diff --git a/sdk/src/args.rs b/sdk/src/args.rs index e2e595b166..5be034817d 100644 --- a/sdk/src/args.rs +++ b/sdk/src/args.rs @@ -2040,43 +2040,6 @@ impl TxBuilder for Tx { } } -/// MASP add key or address arguments -#[derive(Clone, Debug)] -pub struct MaspAddrKeyAdd { - /// Key alias - pub alias: String, - /// Whether to force overwrite the alias - pub alias_force: bool, - /// Any MASP value - pub value: MaspValue, - /// Don't encrypt the keypair - pub unsafe_dont_encrypt: bool, -} - -/// MASP generate spending key arguments -#[derive(Clone, Debug)] -pub struct MaspSpendKeyGen { - /// Key alias - pub alias: String, - /// Whether to force overwrite the alias - pub alias_force: bool, - /// Don't encrypt the keypair - pub unsafe_dont_encrypt: bool, -} - -/// MASP generate payment address arguments -#[derive(Clone, Debug)] -pub struct MaspPayAddrGen { - /// Key alias - pub alias: String, - /// Whether to force overwrite the alias - pub alias_force: bool, - /// Viewing key - pub viewing_key: C::ViewingKey, - /// Pin - pub pin: bool, -} - /// Wallet generate key and implicit address arguments #[derive(Clone, Debug)] pub struct KeyGen { @@ -2115,8 +2078,7 @@ pub struct KeyDerive { pub use_device: bool, } -/// Wallet find shielded address or key arguments -/// TODO Wallet key lookup arguments +/// Wallet key lookup arguments #[derive(Clone, Debug)] pub struct KeyFind { /// Whether to find shielded address by alias @@ -2150,7 +2112,7 @@ pub struct KeyList { pub unsafe_show_secret: bool, } -/// List transparent wallet addresses / shielded payment addresses +/// List addresses arguments #[derive(Clone, Debug)] pub struct AddressList { /// Whether to list payment addresses of the shielded pool @@ -2188,6 +2150,19 @@ pub struct KeyAddressAdd { pub unsafe_dont_encrypt: bool, } +/// Generate payment address arguments +#[derive(Clone, Debug)] +pub struct PayAddressGen { + /// Key alias + pub alias: String, + /// Whether to force overwrite the alias + pub alias_force: bool, + /// Viewing key + pub viewing_key: C::ViewingKey, + /// Pin + pub pin: bool, +} + /// Bridge pool batch recommendation. #[derive(Clone, Debug)] pub struct RecommendBatch { From 1260d981b81b9745fb21714151d352e31ef90bf9 Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Wed, 22 Nov 2023 11:40:57 +0100 Subject: [PATCH 060/216] Restore raw key generation functionality --- apps/src/lib/cli/wallet.rs | 49 ++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index 311b067280..fa66a9a22c 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -424,6 +424,7 @@ fn transparent_key_and_address_gen( io: &impl Io, args::KeyGen { scheme, + raw, alias, alias_force, unsafe_dont_encrypt, @@ -434,23 +435,30 @@ fn transparent_key_and_address_gen( let mut wallet = load_wallet(ctx); let encryption_password = read_and_confirm_encryption_password(unsafe_dont_encrypt); - let derivation_path = decode_derivation_path(scheme, derivation_path) + let alias = if raw { + wallet.gen_store_secret_key( + scheme, + alias, + alias_force, + encryption_password, + &mut OsRng, + ) + } else { + let derivation_path = decode_derivation_path(scheme, derivation_path) + .unwrap_or_else(|err| { + edisplay_line!(io, "{}", err); + cli::safe_exit(1) + }); + let (_mnemonic, seed) = Wallet::::gen_hd_seed( + None, + &mut OsRng, + unsafe_dont_encrypt, + ) .unwrap_or_else(|err| { edisplay_line!(io, "{}", err); cli::safe_exit(1) }); - let mut rng = OsRng; - let (_mnemonic, seed) = Wallet::::gen_hd_seed( - None, - &mut rng, - unsafe_dont_encrypt, - ) - .unwrap_or_else(|err| { - edisplay_line!(io, "{}", err); - cli::safe_exit(1) - }); - let alias = wallet - .derive_store_hd_secret_key( + wallet.derive_store_hd_secret_key( scheme, alias, alias_force, @@ -458,12 +466,13 @@ fn transparent_key_and_address_gen( derivation_path, encryption_password, ) - .map(|x| x.0) - .unwrap_or_else(|err| { - eprintln!("{}", err); - println!("No changes are persisted. Exiting."); - cli::safe_exit(0); - }); + } + .map(|x| x.0) + .unwrap_or_else(|err| { + eprintln!("{}", err); + println!("No changes are persisted. Exiting."); + cli::safe_exit(0); + }); wallet .save() .unwrap_or_else(|err| edisplay_line!(io, "{}", err)); @@ -474,7 +483,7 @@ fn transparent_key_and_address_gen( ); } -/// TODO +/// Key generation fn key_gen(ctx: Context, io: &impl Io, args_key_gen: args::KeyGen) { if !args_key_gen.shielded { transparent_key_and_address_gen(ctx, io, args_key_gen) From 827fbc03fe1abb3632861a4c33c626748ce02c86 Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Wed, 22 Nov 2023 12:20:36 +0100 Subject: [PATCH 061/216] Rename structures --- apps/src/lib/cli.rs | 94 +++++++++++++++++++------------------- apps/src/lib/cli/wallet.rs | 36 +++++++-------- 2 files changed, 64 insertions(+), 66 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 420a6c91e9..dacf50d439 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -480,48 +480,48 @@ pub mod cmds { #[derive(Clone, Debug)] pub enum NamadaWallet { /// Key generation - NewGen(WalletNewGen), + KeyGen(WalletGen), /// Key derivation - NewDerive(WalletNewDerive), + KeyDerive(WalletDerive), /// Key list - NewKeyList(WalletNewKeyList), + KeyList(WalletListKeys), /// Key search - NewKeyFind(WalletNewKeyFind), + KeyFind(WalletFindKeys), /// Address list - NewAddrList(WalletNewAddressList), + AddrList(WalletListAddresses), /// Address search - NewAddrFind(WalletNewAddressFind), + AddrFind(WalletFindAddresses), /// Key export - NewKeyExport(WalletNewExportKey), + KeyExport(WalletExportKey), /// Key import - NewKeyAddrAdd(WalletNewKeyAddressAdd), + KeyAddrAdd(WalletAddKeyAddress), /// Payment address generation - NewPayAddrGen(WalletNewPaymentAddressGen), + PayAddrGen(WalletGenPaymentAddress), } impl Cmd for NamadaWallet { fn add_sub(app: App) -> App { - app.subcommand(WalletNewGen::def()) - .subcommand(WalletNewDerive::def()) - .subcommand(WalletNewKeyList::def()) - .subcommand(WalletNewKeyFind::def()) - .subcommand(WalletNewAddressList::def()) - .subcommand(WalletNewAddressFind::def()) - .subcommand(WalletNewExportKey::def()) - .subcommand(WalletNewKeyAddressAdd::def()) - .subcommand(WalletNewPaymentAddressGen::def()) + app.subcommand(WalletGen::def()) + .subcommand(WalletDerive::def()) + .subcommand(WalletListKeys::def()) + .subcommand(WalletFindKeys::def()) + .subcommand(WalletListAddresses::def()) + .subcommand(WalletFindAddresses::def()) + .subcommand(WalletExportKey::def()) + .subcommand(WalletAddKeyAddress::def()) + .subcommand(WalletGenPaymentAddress::def()) } fn parse(matches: &ArgMatches) -> Option { - let gen_new = SubCmd::parse(matches).map(Self::NewGen); - let derive_new = SubCmd::parse(matches).map(Self::NewDerive); - let key_list_new = SubCmd::parse(matches).map(Self::NewKeyList); - let key_find_new = SubCmd::parse(matches).map(Self::NewKeyFind); - let addr_list_new = SubCmd::parse(matches).map(Self::NewAddrList); - let addr_find_new = SubCmd::parse(matches).map(Self::NewAddrFind); - let export_new = SubCmd::parse(matches).map(Self::NewKeyExport); - let key_addr_add = SubCmd::parse(matches).map(Self::NewKeyAddrAdd); - let pay_addr_gen = SubCmd::parse(matches).map(Self::NewPayAddrGen); + let gen_new = SubCmd::parse(matches).map(Self::KeyGen); + let derive_new = SubCmd::parse(matches).map(Self::KeyDerive); + let key_list_new = SubCmd::parse(matches).map(Self::KeyList); + let key_find_new = SubCmd::parse(matches).map(Self::KeyFind); + let addr_list_new = SubCmd::parse(matches).map(Self::AddrList); + let addr_find_new = SubCmd::parse(matches).map(Self::AddrFind); + let export_new = SubCmd::parse(matches).map(Self::KeyExport); + let key_addr_add = SubCmd::parse(matches).map(Self::KeyAddrAdd); + let pay_addr_gen = SubCmd::parse(matches).map(Self::PayAddrGen); gen_new .or(derive_new) .or(key_list_new) @@ -557,9 +557,9 @@ pub mod cmds { /// address derived from it. In the shielded setting, generate a new /// spending key. #[derive(Clone, Debug)] - pub struct WalletNewGen(pub args::KeyGen); + pub struct WalletGen(pub args::KeyGen); - impl SubCmd for WalletNewGen { + impl SubCmd for WalletGen { const CMD: &'static str = "gen"; fn parse(matches: &ArgMatches) -> Option { @@ -584,9 +584,9 @@ pub mod cmds { /// the mnemonic code. /// In the shielded setting, derive a spending key from the mnemonic code. #[derive(Clone, Debug)] - pub struct WalletNewDerive(pub args::KeyDerive); + pub struct WalletDerive(pub args::KeyDerive); - impl SubCmd for WalletNewDerive { + impl SubCmd for WalletDerive { const CMD: &'static str = "derive"; fn parse(matches: &ArgMatches) -> Option { @@ -613,9 +613,9 @@ pub mod cmds { /// TODO List all known shielded keys #[derive(Clone, Debug)] - pub struct WalletNewKeyList(pub args::KeyList); + pub struct WalletListKeys(pub args::KeyList); - impl SubCmd for WalletNewKeyList { + impl SubCmd for WalletListKeys { const CMD: &'static str = "list-keys"; fn parse(matches: &ArgMatches) -> Option { @@ -637,10 +637,10 @@ pub mod cmds { /// TODO Find a keypair in the wallet store /// TODO Find the given shielded address or key #[derive(Clone, Debug)] - pub struct WalletNewKeyFind(pub args::KeyFind); + pub struct WalletFindKeys(pub args::KeyFind); - impl SubCmd for WalletNewKeyFind { - const CMD: &'static str = "find-key"; + impl SubCmd for WalletFindKeys { + const CMD: &'static str = "find-keys"; fn parse(matches: &ArgMatches) -> Option { matches @@ -662,9 +662,9 @@ pub mod cmds { /// List known addresses /// TODO List all known payment addresses #[derive(Clone, Debug)] - pub struct WalletNewAddressList(pub args::AddressList); + pub struct WalletListAddresses(pub args::AddressList); - impl SubCmd for WalletNewAddressList { + impl SubCmd for WalletListAddresses { const CMD: &'static str = "list-addr"; fn parse(matches: &ArgMatches) -> Option { @@ -685,9 +685,9 @@ pub mod cmds { /// Find an address by its alias #[derive(Clone, Debug)] - pub struct WalletNewAddressFind(pub args::AddressFind); + pub struct WalletFindAddresses(pub args::AddressFind); - impl SubCmd for WalletNewAddressFind { + impl SubCmd for WalletFindAddresses { const CMD: &'static str = "find-addr"; fn parse(matches: &ArgMatches) -> Option { @@ -707,9 +707,9 @@ pub mod cmds { /// Export key #[derive(Clone, Debug)] - pub struct WalletNewExportKey(pub args::KeyExport); + pub struct WalletExportKey(pub args::KeyExport); - impl SubCmd for WalletNewExportKey { + impl SubCmd for WalletExportKey { const CMD: &'static str = "export"; fn parse(matches: &ArgMatches) -> Option { @@ -727,9 +727,9 @@ pub mod cmds { /// Add public / payment address to the wallet #[derive(Clone, Debug)] - pub struct WalletNewKeyAddressAdd(pub args::KeyAddressAdd); + pub struct WalletAddKeyAddress(pub args::KeyAddressAdd); - impl SubCmd for WalletNewKeyAddressAdd { + impl SubCmd for WalletAddKeyAddress { const CMD: &'static str = "add"; fn parse(matches: &ArgMatches) -> Option { @@ -750,11 +750,9 @@ pub mod cmds { /// Generate a payment address from a viewing key or payment address #[derive(Clone, Debug)] - pub struct WalletNewPaymentAddressGen( - pub args::PayAddressGen, - ); + pub struct WalletGenPaymentAddress(pub args::PayAddressGen); - impl SubCmd for WalletNewPaymentAddressGen { + impl SubCmd for WalletGenPaymentAddress { const CMD: &'static str = "gen-payment-addr"; fn parse(matches: &ArgMatches) -> Option { diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index fa66a9a22c..6dd98d47dc 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -39,33 +39,33 @@ impl CliApi { io: &impl Io, ) -> Result<()> { match cmd { - cmds::NamadaWallet::NewGen(cmds::WalletNewGen(args)) => { + cmds::NamadaWallet::KeyGen(cmds::WalletGen(args)) => { key_gen(ctx, io, args) } - cmds::NamadaWallet::NewDerive(cmds::WalletNewDerive(args)) => { + cmds::NamadaWallet::KeyDerive(cmds::WalletDerive(args)) => { key_derive(ctx, io, args).await } - cmds::NamadaWallet::NewKeyList(cmds::WalletNewKeyList(args)) => { + cmds::NamadaWallet::KeyList(cmds::WalletListKeys(args)) => { key_list(ctx, io, args) } - cmds::NamadaWallet::NewKeyFind(cmds::WalletNewKeyFind(args)) => { + cmds::NamadaWallet::KeyFind(cmds::WalletFindKeys(args)) => { key_find(ctx, io, args) } - cmds::NamadaWallet::NewAddrList(cmds::WalletNewAddressList( - args, - )) => address_list(ctx, io, args), - cmds::NamadaWallet::NewAddrFind(cmds::WalletNewAddressFind( - args, - )) => address_or_alias_find(ctx, io, args), - cmds::NamadaWallet::NewKeyExport(cmds::WalletNewExportKey( + cmds::NamadaWallet::AddrList(cmds::WalletListAddresses(args)) => { + address_list(ctx, io, args) + } + cmds::NamadaWallet::AddrFind(cmds::WalletFindAddresses(args)) => { + address_or_alias_find(ctx, io, args) + } + cmds::NamadaWallet::KeyExport(cmds::WalletExportKey(args)) => { + key_export(ctx, io, args) + } + cmds::NamadaWallet::KeyAddrAdd(cmds::WalletAddKeyAddress(args)) => { + key_address_add(ctx, io, args) + } + cmds::NamadaWallet::PayAddrGen(cmds::WalletGenPaymentAddress( args, - )) => key_export(ctx, io, args), - cmds::NamadaWallet::NewKeyAddrAdd( - cmds::WalletNewKeyAddressAdd(args), - ) => key_address_add(ctx, io, args), - cmds::NamadaWallet::NewPayAddrGen( - cmds::WalletNewPaymentAddressGen(args), - ) => { + )) => { let args = args.to_sdk(&mut ctx); payment_address_gen(ctx, io, args) } From db564107b20531018bbff5c57c53ee2bb8b87407 Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Wed, 22 Nov 2023 12:24:50 +0100 Subject: [PATCH 062/216] Refactoring --- apps/src/lib/cli.rs | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index dacf50d439..09a28899f9 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -513,22 +513,21 @@ pub mod cmds { } fn parse(matches: &ArgMatches) -> Option { - let gen_new = SubCmd::parse(matches).map(Self::KeyGen); - let derive_new = SubCmd::parse(matches).map(Self::KeyDerive); - let key_list_new = SubCmd::parse(matches).map(Self::KeyList); - let key_find_new = SubCmd::parse(matches).map(Self::KeyFind); - let addr_list_new = SubCmd::parse(matches).map(Self::AddrList); - let addr_find_new = SubCmd::parse(matches).map(Self::AddrFind); - let export_new = SubCmd::parse(matches).map(Self::KeyExport); + let gen = SubCmd::parse(matches).map(Self::KeyGen); + let derive = SubCmd::parse(matches).map(Self::KeyDerive); + let key_list = SubCmd::parse(matches).map(Self::KeyList); + let key_find = SubCmd::parse(matches).map(Self::KeyFind); + let addr_list = SubCmd::parse(matches).map(Self::AddrList); + let addr_find = SubCmd::parse(matches).map(Self::AddrFind); + let export = SubCmd::parse(matches).map(Self::KeyExport); let key_addr_add = SubCmd::parse(matches).map(Self::KeyAddrAdd); let pay_addr_gen = SubCmd::parse(matches).map(Self::PayAddrGen); - gen_new - .or(derive_new) - .or(key_list_new) - .or(key_find_new) - .or(addr_list_new) - .or(addr_find_new) - .or(export_new) + gen.or(derive) + .or(key_list) + .or(key_find) + .or(addr_list) + .or(addr_find) + .or(export) .or(key_addr_add) .or(pay_addr_gen) } From fdcd859cae3fecfa2b1b2572cc53bc4e5116ff5c Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Wed, 22 Nov 2023 12:54:16 +0100 Subject: [PATCH 063/216] Make `--alias` obligatory for gen / derive --- apps/src/lib/cli.rs | 16 +++++++++------- apps/src/lib/cli/wallet.rs | 14 ++++---------- sdk/src/args.rs | 4 ++-- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 09a28899f9..59ac8375c3 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -571,9 +571,11 @@ pub mod cmds { App::new(Self::CMD) .about("Generates a new transparent / shielded secret key.") .long_about( - "Generates a keypair with a given alias and derives the \ - implicit address from its public key. The address will \ - be stored with the same alias. TODO shielded", + "In the transparent setting, generates a keypair with a \ + given alias and derives the implicit address from its \ + public key. The address will be stored with the same \ + alias. In the shielded setting, generates a new spending \ + key.", ) .add_args::() } @@ -6004,7 +6006,7 @@ pub mod args { fn parse(matches: &ArgMatches) -> Self { let scheme = SCHEME.parse(matches); let shielded = SHIELDED.parse(matches); - let alias = ALIAS_OPT.parse(matches); + let alias = ALIAS.parse(matches); let alias_force = ALIAS_FORCE.parse(matches); let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); let use_device = USE_DEVICE.parse(matches); @@ -6032,7 +6034,7 @@ pub mod args { .def() .help("Derive a spending key for the shielded pool."), ) - .arg(ALIAS_OPT.def().help( + .arg(ALIAS.def().help( "The key and address alias. If none provided, the alias will \ be the public key hash.", )) @@ -6065,7 +6067,7 @@ pub mod args { let scheme = SCHEME.parse(matches); let shielded = SHIELDED.parse(matches); let raw = RAW_KEY_GEN.parse(matches); - let alias = ALIAS_OPT.parse(matches); + let alias = ALIAS.parse(matches); let alias_force = ALIAS_FORCE.parse(matches); let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); let derivation_path = HD_WALLET_DERIVATION_PATH.parse(matches); @@ -6101,7 +6103,7 @@ pub mod args { mnemonic code is generated.", ), ) - .arg(ALIAS_OPT.def().help( + .arg(ALIAS.def().help( "The key and address alias. If none provided, the alias will \ be the public key hash.", )) diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index 6dd98d47dc..edc9d0192a 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -186,11 +186,6 @@ fn spending_key_gen( .. }: args::KeyGen, ) { - let alias = alias.unwrap_or_else(|| { - display_line!(io, "Missing alias."); - display_line!(io, "No changes are persisted. Exiting."); - cli::safe_exit(1) - }); let mut wallet = load_wallet(ctx); let alias = alias.to_lowercase(); let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); @@ -347,7 +342,7 @@ async fn transparent_key_and_address_derive( wallet .derive_key_from_mnemonic_code( scheme, - alias, + Some(alias), alias_force, derivation_path, None, @@ -390,13 +385,12 @@ async fn transparent_key_and_address_derive( let pubkey = common::PublicKey::try_from_slice(&response.public_key) .expect("unable to decode public key from hardware wallet"); - let pkh = PublicKeyHash::from(&pubkey); let address = Address::from_str(&response.address_str) .expect("unable to decode address from hardware wallet"); wallet .insert_public_key( - alias.unwrap_or_else(|| pkh.to_string()), + alias, pubkey, Some(address), Some(derivation_path), @@ -438,7 +432,7 @@ fn transparent_key_and_address_gen( let alias = if raw { wallet.gen_store_secret_key( scheme, - alias, + Some(alias), alias_force, encryption_password, &mut OsRng, @@ -460,7 +454,7 @@ fn transparent_key_and_address_gen( }); wallet.derive_store_hd_secret_key( scheme, - alias, + Some(alias), alias_force, seed, derivation_path, diff --git a/sdk/src/args.rs b/sdk/src/args.rs index 5be034817d..39a18e07c4 100644 --- a/sdk/src/args.rs +++ b/sdk/src/args.rs @@ -2050,7 +2050,7 @@ pub struct KeyGen { /// Whether to generate a raw non-hd key pub raw: bool, /// Key alias - pub alias: Option, + pub alias: String, /// Whether to force overwrite the alias, if provided pub alias_force: bool, /// Don't encrypt the keypair @@ -2067,7 +2067,7 @@ pub struct KeyDerive { /// Whether to generate a spending key for the shielded pool pub shielded: bool, /// Key alias - pub alias: Option, + pub alias: String, /// Whether to force overwrite the alias, if provided pub alias_force: bool, /// Don't encrypt the keypair From 5a9f794e7d565f8e1d0ff4a28cc3e34a92d2f7e6 Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Wed, 22 Nov 2023 13:28:04 +0100 Subject: [PATCH 064/216] Fix: normalize alias strings --- apps/src/lib/cli/wallet.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index edc9d0192a..199044e896 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -336,6 +336,7 @@ async fn transparent_key_and_address_derive( edisplay_line!(io, "{}", err); cli::safe_exit(1) }); + let alias = alias.to_lowercase(); let alias = if !use_device { let encryption_password = read_and_confirm_encryption_password(unsafe_dont_encrypt); @@ -426,6 +427,7 @@ fn transparent_key_and_address_gen( .. }: args::KeyGen, ) { + let alias = alias.to_lowercase(); let mut wallet = load_wallet(ctx); let encryption_password = read_and_confirm_encryption_password(unsafe_dont_encrypt); @@ -604,6 +606,7 @@ fn shielded_key_address_find( }: args::KeyFind, ) { let mut wallet = load_wallet(ctx); + // TODO let alias = alias.unwrap_or_else(|| { display_line!(io, "Missing alias."); display_line!(io, "No changes are persisted. Exiting."); @@ -712,12 +715,13 @@ fn key_export( io: &impl Io, args::KeyExport { alias }: args::KeyExport, ) { + let alias = alias.to_lowercase(); let mut wallet = load_wallet(ctx); wallet - .find_secret_key(alias.to_lowercase(), None) + .find_secret_key(&alias, None) .map(|keypair| { let file_data = keypair.serialize_to_vec(); - let file_name = format!("key_{}", alias.to_lowercase()); + let file_name = format!("key_{}", alias); let mut file = File::create(&file_name).unwrap(); file.write_all(file_data.as_ref()).unwrap(); @@ -771,14 +775,15 @@ fn address_or_alias_find( message." ); } else if alias.is_some() { - if let Some(address) = wallet.find_address(alias.as_ref().unwrap()) { + let alias = alias.unwrap().to_lowercase(); + if let Some(address) = wallet.find_address(&alias) { display_line!(io, "Found address {}", address.to_pretty_string()); } else { display_line!( io, "No address with alias {} found. Use the command `address \ list` to see all the known addresses.", - alias.unwrap().to_lowercase() + alias ); } } else if address.is_some() { @@ -806,10 +811,11 @@ fn transparent_address_add( .. }: args::KeyAddressAdd, ) { + let alias = alias.to_lowercase(); let address = address.unwrap(); // this should not fail let mut wallet = load_wallet(ctx); if wallet - .insert_address(alias.to_lowercase(), address, alias_force) + .insert_address(&alias, address, alias_force) .is_none() { edisplay_line!(io, "Address not added"); @@ -821,7 +827,7 @@ fn transparent_address_add( display_line!( io, "Successfully added a key and an address with alias: \"{}\"", - alias.to_lowercase() + alias ); } From f385aef86c95d0d6204cf126076507ab0b4f436c Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Wed, 22 Nov 2023 13:31:37 +0100 Subject: [PATCH 065/216] Add todo --- apps/src/lib/cli/wallet.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index 199044e896..ce8e3a26e0 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -162,8 +162,8 @@ fn payment_addresses_list( if known_addresses.is_empty() { display_line!( io, - "No known payment addresses. Try `masp gen-addr --alias my-addr` \ - to generate a new payment address.", + "TODO No known payment addresses. Try `masp gen-addr --alias \ + my-addr` to generate a new payment address.", ); } else { let stdout = io::stdout(); From 34dd9756359a8e612383d1a0b5a289f402bdbe49 Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Wed, 22 Nov 2023 13:32:00 +0100 Subject: [PATCH 066/216] Fix output message --- apps/src/lib/cli/wallet.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index ce8e3a26e0..cb29ac48c5 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -745,8 +745,8 @@ fn transparent_addresses_list( if known_addresses.is_empty() { display_line!( io, - "No known addresses. Try `address gen --alias my-addr` to \ - generate a new implicit address.", + "No known addresses. Try `gen --alias my-addr` to generate a new \ + implicit address.", ); } else { let stdout = io::stdout(); From 6f69e38762b4f431fddcb4b6154faddf83730bde Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Wed, 22 Nov 2023 13:59:28 +0100 Subject: [PATCH 067/216] Update help messages for `gen` and `derive` --- apps/src/lib/cli.rs | 37 ++++++++++++++++++------------------- apps/src/lib/cli/wallet.rs | 2 +- sdk/src/args.rs | 2 +- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 59ac8375c3..9cb49f0539 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -574,8 +574,10 @@ pub mod cmds { "In the transparent setting, generates a keypair with a \ given alias and derives the implicit address from its \ public key. The address will be stored with the same \ - alias. In the shielded setting, generates a new spending \ - key.", + alias.\nIn the shielded setting, generates a new \ + spending key with a given alias.\nIn both settings, by \ + default, an HD-key with a default derivation path is \ + generated, with a random mnemonic code.", ) .add_args::() } @@ -599,14 +601,17 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about( - "Derive transparent / shielded key from the mnemonic code.", + "Derive transparent / shielded key from the mnemonic code \ + or a seed stored on the hardware wallet device.", ) .long_about( - "Derives a keypair from the given mnemonic code and HD \ - derivation path and derives the implicit address from \ - its public key. Stores the keypair and the address with \ - the given alias. A hardware wallet can be used, in which \ - case a private key is not derivable. TODO shielded", + "In the transparent setting, derives a keypair from the \ + given mnemonic code and HD derivation path and derives \ + the implicit address from its public key. Stores the \ + keypair and the address with the given alias.\nIn the \ + shielded setting, derives a spending key.\nA hardware \ + wallet can be used, in which case the private key is not \ + derivable.", ) .add_args::() } @@ -6034,10 +6039,7 @@ pub mod args { .def() .help("Derive a spending key for the shielded pool."), ) - .arg(ALIAS.def().help( - "The key and address alias. If none provided, the alias will \ - be the public key hash.", - )) + .arg(ALIAS.def().help("The key and address alias.")) .arg( ALIAS_FORCE .def() @@ -6103,10 +6105,7 @@ pub mod args { mnemonic code is generated.", ), ) - .arg(ALIAS.def().help( - "The key and address alias. If none provided, the alias will \ - be the public key hash.", - )) + .arg(ALIAS.def().help("The key and address alias.")) .arg(ALIAS_FORCE.def().help( "Override the alias without confirmation if it already exists.", )) @@ -6115,12 +6114,12 @@ pub mod args { used in a live network.", )) .arg(HD_WALLET_DERIVATION_PATH.def().help( - "Generate a new key and wallet using BIP39 mnemonic code and \ - HD derivation path. Use keyword `default` to refer to a \ + "HD key derivation path. Use keyword `default` to refer to a \ scheme default path:\n- m/44'/60'/0'/0/0 for secp256k1 \ scheme\n- m/44'/877'/0'/0'/0' for ed25519 scheme.\nFor \ ed25519, all path indices will be promoted to hardened \ - indexes. TODO", + indexes. If none is specified, the scheme default path is \ + used.", )) } } diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index cb29ac48c5..84f5c6b1e9 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -488,7 +488,7 @@ fn key_gen(ctx: Context, io: &impl Io, args_key_gen: args::KeyGen) { } } -/// TODO +/// HD key derivation from mnemonic code async fn key_derive( ctx: Context, io: &impl Io, diff --git a/sdk/src/args.rs b/sdk/src/args.rs index 39a18e07c4..bde96938c4 100644 --- a/sdk/src/args.rs +++ b/sdk/src/args.rs @@ -2055,7 +2055,7 @@ pub struct KeyGen { pub alias_force: bool, /// Don't encrypt the keypair pub unsafe_dont_encrypt: bool, - /// BIP44 derivation path + /// BIP44 / ZIP32 derivation path pub derivation_path: String, } From 8faa98be841b288992c56ae54328a0067ec15aaf Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Wed, 22 Nov 2023 15:41:43 +0100 Subject: [PATCH 068/216] Implement export for MASP spending keys --- apps/src/lib/cli.rs | 10 +++++++--- apps/src/lib/cli/wallet.rs | 38 ++++++++++++++++++++++---------------- sdk/src/args.rs | 2 ++ 3 files changed, 31 insertions(+), 19 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 9cb49f0539..e8bc411318 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -726,7 +726,7 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) - .about("Exports a keypair to a file.") + .about("Exports a keypair / spending key to a file.") .add_args::() } } @@ -6298,14 +6298,18 @@ pub mod args { impl Args for KeyExport { fn parse(matches: &ArgMatches) -> Self { + let shielded = SHIELDED.parse(matches); let alias = ALIAS.parse(matches); - Self { alias } + Self { shielded, alias } } fn def(app: App) -> App { app.arg( - ALIAS.def().help("The alias of the key you wish to export."), + SHIELDED + .def() + .help("Whether to export the shielded spending key."), ) + .arg(ALIAS.def().help("The alias of the key you wish to export.")) } } diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index 84f5c6b1e9..1c8f1ddd15 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -709,28 +709,34 @@ fn transparent_keys_list( } } -/// Export a transparent keypair to a file. +/// Export a transparent keypair / MASP spending key to a file. fn key_export( ctx: Context, io: &impl Io, - args::KeyExport { alias }: args::KeyExport, + args::KeyExport { shielded, alias }: args::KeyExport, ) { let alias = alias.to_lowercase(); let mut wallet = load_wallet(ctx); - wallet - .find_secret_key(&alias, None) - .map(|keypair| { - let file_data = keypair.serialize_to_vec(); - let file_name = format!("key_{}", alias); - let mut file = File::create(&file_name).unwrap(); - - file.write_all(file_data.as_ref()).unwrap(); - display_line!(io, "Exported to file {}", file_name); - }) - .unwrap_or_else(|err| { - edisplay_line!(io, "{}", err); - cli::safe_exit(1) - }) + if !shielded { + wallet + .find_secret_key(&alias, None) + .map(|sk| Box::new(sk) as Box) + } else { + wallet + .find_spending_key(&alias, None) + .map(|spk| Box::new(spk) as Box) + } + .map(|key| { + let file_data = key.serialize_to_vec(); + let file_name = format!("key_{}", alias); + let mut file = File::create(&file_name).unwrap(); + file.write_all(file_data.as_ref()).unwrap(); + display_line!(io, "Exported to file {}", file_name); + }) + .unwrap_or_else(|err| { + edisplay_line!(io, "{}", err); + cli::safe_exit(1) + }) } /// List all known transparent addresses. diff --git a/sdk/src/args.rs b/sdk/src/args.rs index bde96938c4..0b57fe9c0e 100644 --- a/sdk/src/args.rs +++ b/sdk/src/args.rs @@ -2131,6 +2131,8 @@ pub struct AddressFind { /// Wallet key export arguments #[derive(Clone, Debug)] pub struct KeyExport { + /// Whether to export a MASP spending key + pub shielded: bool, /// Key alias pub alias: String, } From 4919903f987b7f1a553cd27aa56231a1cfccec9b Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Wed, 22 Nov 2023 15:53:19 +0100 Subject: [PATCH 069/216] Improve some messages, comments --- apps/src/lib/cli.rs | 5 ++++- apps/src/lib/cli/wallet.rs | 2 +- sdk/src/args.rs | 8 ++++---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index e8bc411318..229d5a48ff 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -726,7 +726,10 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) - .about("Exports a keypair / spending key to a file.") + .about( + "Exports a transparent keypair / shielded spending key to \ + a file.", + ) .add_args::() } } diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index 1c8f1ddd15..c3ff6287b2 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -357,7 +357,7 @@ async fn transparent_key_and_address_derive( .0 } else { let hidapi = HidApi::new().unwrap_or_else(|err| { - edisplay_line!(io, "Failed to create Hidapi: {}", err); + edisplay_line!(io, "Failed to create HidApi: {}", err); cli::safe_exit(1) }); let app = NamadaApp::new( diff --git a/sdk/src/args.rs b/sdk/src/args.rs index 0b57fe9c0e..33d90ff519 100644 --- a/sdk/src/args.rs +++ b/sdk/src/args.rs @@ -2064,7 +2064,7 @@ pub struct KeyGen { pub struct KeyDerive { /// Scheme type pub scheme: SchemeType, - /// Whether to generate a spending key for the shielded pool + /// Whether to generate a MASP spending key pub shielded: bool, /// Key alias pub alias: String, @@ -2081,7 +2081,7 @@ pub struct KeyDerive { /// Wallet key lookup arguments #[derive(Clone, Debug)] pub struct KeyFind { - /// Whether to find shielded address by alias + /// Whether to find a MASP address by alias pub shielded: bool, /// Key alias to lookup keypair with pub alias: Option, @@ -2104,7 +2104,7 @@ pub struct KeyFind { /// Wallet list keys arguments #[derive(Clone, Debug)] pub struct KeyList { - /// Whether to list spending keys of the shielded pool + /// Whether to list MASP spending keys pub shielded: bool, /// Don't decrypt keys pub decrypt: bool, @@ -2115,7 +2115,7 @@ pub struct KeyList { /// List addresses arguments #[derive(Clone, Debug)] pub struct AddressList { - /// Whether to list payment addresses of the shielded pool + /// Whether to list MASP payment addresses pub shielded: bool, } From a5b5382507b1bba4d08d960e3e854808ca6f872e Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Thu, 23 Nov 2023 16:39:14 +0100 Subject: [PATCH 070/216] Implement raw key add; simplify cli for add command --- apps/src/lib/cli.rs | 47 +++++++--------- apps/src/lib/cli/wallet.rs | 111 ++++++++++++++++++++++++++----------- sdk/src/args.rs | 7 +-- 3 files changed, 103 insertions(+), 62 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 229d5a48ff..eeefac52b7 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -749,10 +749,7 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) - .about( - "Adds the given transparent / payment address or key to \ - the wallet.", - ) + .about("Adds the given key or address to the wallet.") .add_args::() } } @@ -2986,7 +2983,8 @@ pub mod args { arg_opt("eth-cold-key"); pub const VALIDATOR_ETH_HOT_KEY: ArgOpt = arg_opt("eth-hot-key"); - pub const VALUE: ArgOpt = arg_opt("value"); + pub const VALUE: Arg = arg("value"); + pub const VALUE_OPT: ArgOpt = VALUE.opt(); pub const VIEWING_KEY: Arg = arg("key"); pub const VP: ArgOpt = arg_opt("vp"); pub const WALLET_ALIAS_FORCE: ArgFlag = flag("wallet-alias-force"); @@ -6159,7 +6157,7 @@ pub mod args { let shielded = SHIELDED.parse(matches); let alias = ALIAS_OPT.parse(matches); let public_key = RAW_PUBLIC_KEY_OPT.parse(matches); - let value = VALUE.parse(matches); + let value = VALUE_OPT.parse(matches); let unsafe_show_secret = UNSAFE_SHOW_SECRET.parse(matches); Self { @@ -6176,7 +6174,10 @@ pub mod args { SHIELDED .def() .help("Find spending key for the shielded pool.") - .conflicts_with_all([VALUE.name, RAW_PUBLIC_KEY_OPT.name]), + .conflicts_with_all([ + VALUE_OPT.name, + RAW_PUBLIC_KEY_OPT.name, + ]), ) .arg( ALIAS_OPT @@ -6185,7 +6186,7 @@ pub mod args { "TODO An alias associated with the keypair. The alias \ that is to be found.", ) - .conflicts_with(VALUE.name), + .conflicts_with(VALUE_OPT.name), ) .arg( RAW_PUBLIC_KEY_OPT @@ -6193,13 +6194,17 @@ pub mod args { .help("A public key associated with the keypair."), ) .arg( - VALUE + VALUE_OPT .def() .help("A public key or alias associated with the keypair."), ) .group( ArgGroup::new("key_find_args") - .args([ALIAS_OPT.name, RAW_PUBLIC_KEY_OPT.name, VALUE.name]) + .args([ + ALIAS_OPT.name, + RAW_PUBLIC_KEY_OPT.name, + VALUE_OPT.name, + ]) .required(true), ) .arg(PRE_GENESIS.def().help( @@ -6257,13 +6262,11 @@ pub mod args { fn parse(matches: &ArgMatches) -> Self { let alias = ALIAS.parse(matches); let alias_force = ALIAS_FORCE.parse(matches); - let address = RAW_ADDRESS_OPT.parse(matches); - let value = MASP_VALUE_OPT.parse(matches); + let value = VALUE.parse(matches); let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); Self { alias, alias_force, - address, value, unsafe_dont_encrypt, } @@ -6278,20 +6281,12 @@ pub mod args { .arg(ALIAS_FORCE.def().help( "Override the alias without confirmation if it already exists.", )) - .arg( - RAW_ADDRESS_OPT - .def() - .help("The bech32m encoded transparent address string."), - ) - .arg(MASP_VALUE_OPT.def().help( - "A spending key, viewing key, or payment address of the \ - shielded pool.", + .arg(VALUE.def().help( + "Any value of the following:\n- transparent pool secret key \ + string\n- the bech32m encoded transparent address string\n- \ + shielded pool spending key\n- shielded pool viewing key\n- \ + shielded pool payment address ", )) - .group( - ArgGroup::new("key_address_add_args") - .args([RAW_ADDRESS_OPT.name, MASP_VALUE_OPT.name]) - .required(true), - ) .arg(UNSAFE_DONT_ENCRYPT.def().help( "UNSAFE: Do not encrypt the added keys. Do not use this for \ keys used in a live network.", diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index c3ff6287b2..126af2d4ca 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -12,7 +12,7 @@ use ledger_namada_rs::{BIP44Path, NamadaApp}; use ledger_transport_hid::hidapi::HidApi; use ledger_transport_hid::TransportNativeHID; use masp_primitives::zip32::ExtendedFullViewingKey; -use namada::types::address::Address; +use namada::types::address::{Address, DecodeError}; use namada::types::io::Io; use namada::types::key::*; use namada::types::masp::{MaspValue, PaymentAddress}; @@ -242,18 +242,14 @@ fn payment_address_gen( fn shielded_key_address_add( ctx: Context, io: &impl Io, - args::KeyAddressAdd { - alias, - alias_force, - value, - unsafe_dont_encrypt, - .. - }: args::KeyAddressAdd, + alias: String, + alias_force: bool, + masp_value: MaspValue, + unsafe_dont_encrypt: bool, ) { - let value = value.unwrap(); // this should not fail let alias = alias.to_lowercase(); let mut wallet = load_wallet(ctx); - let (alias, typ) = match value { + let (alias, typ) = match masp_value { MaspValue::FullViewingKey(viewing_key) => { let alias = wallet .insert_viewing_key(alias, viewing_key, alias_force) @@ -501,7 +497,7 @@ async fn key_derive( } } -/// TODO +/// List keys fn key_list(ctx: Context, io: &impl Io, args_key_list: args::KeyList) { if !args_key_list.shielded { transparent_keys_list(ctx, io, args_key_list) @@ -510,7 +506,7 @@ fn key_list(ctx: Context, io: &impl Io, args_key_list: args::KeyList) { } } -/// TODO +/// Find key or address fn key_find(ctx: Context, io: &impl Io, args_key_find: args::KeyFind) { if !args_key_find.shielded { transparent_key_address_find(ctx, io, args_key_find) @@ -519,7 +515,7 @@ fn key_find(ctx: Context, io: &impl Io, args_key_find: args::KeyFind) { } } -/// TODO +/// List addresses fn address_list(ctx: Context, io: &impl Io, args_key_list: args::AddressList) { if !args_key_list.shielded { transparent_addresses_list(ctx, io, args_key_list) @@ -528,21 +524,78 @@ fn address_list(ctx: Context, io: &impl Io, args_key_list: args::AddressList) { } } -/// TODO +/// Value for add command +#[allow(clippy::large_enum_variant)] +#[derive(Debug)] +pub enum KeyAddrAddValue { + /// Transparent secret key + TranspSecretKey(common::SecretKey), + /// Transparent address + TranspAddress(Address), + /// Masp value + MASPValue(MaspValue), +} + +impl FromStr for KeyAddrAddValue { + type Err = DecodeError; + + fn from_str(s: &str) -> Result { + // Try to decode this value first as a secret key, then as an address, + // then as a MASP value + common::SecretKey::from_str(s) + .map(Self::TranspSecretKey) + .or_else(|_| Address::from_str(s).map(Self::TranspAddress)) + .or_else(|_| MaspValue::from_str(s).map(Self::MASPValue)) + } +} + +/// Add key or address fn key_address_add( ctx: Context, io: &impl Io, - args_key_addr_add: args::KeyAddressAdd, + args::KeyAddressAdd { + alias, + alias_force, + value, + unsafe_dont_encrypt, + .. + }: args::KeyAddressAdd, ) { - if args_key_addr_add.address.is_some() { - transparent_address_add(ctx, io, args_key_addr_add) - } else if args_key_addr_add.value.is_some() { - shielded_key_address_add(ctx, io, args_key_addr_add) - } else { - unreachable!( - "This should not happen as the group of address and value is \ - required." - ) + match KeyAddrAddValue::from_str(&value).unwrap_or_else(|err| { + edisplay_line!(io, "{}", err); + display_line!(io, "No changes are persisted. Exiting."); + cli::safe_exit(1) + }) { + KeyAddrAddValue::TranspSecretKey(sk) => { + let mut wallet = load_wallet(ctx); + let encryption_password = + read_and_confirm_encryption_password(unsafe_dont_encrypt); + wallet + .insert_keypair( + alias, + alias_force, + sk, + encryption_password, + None, + None, + ) + .unwrap_or_else(|err| { + edisplay_line!(io, "{}", err); + display_line!(io, "No changes are persisted. Exiting."); + cli::safe_exit(1); + }); + } + KeyAddrAddValue::TranspAddress(address) => { + transparent_address_add(ctx, io, alias, alias_force, address) + } + KeyAddrAddValue::MASPValue(masp_value) => shielded_key_address_add( + ctx, + io, + alias, + alias_force, + masp_value, + unsafe_dont_encrypt, + ), } } @@ -810,15 +863,11 @@ fn address_or_alias_find( fn transparent_address_add( ctx: Context, io: &impl Io, - args::KeyAddressAdd { - alias, - alias_force, - address, - .. - }: args::KeyAddressAdd, + alias: String, + alias_force: bool, + address: Address, ) { let alias = alias.to_lowercase(); - let address = address.unwrap(); // this should not fail let mut wallet = load_wallet(ctx); if wallet .insert_address(&alias, address, alias_force) diff --git a/sdk/src/args.rs b/sdk/src/args.rs index 33d90ff519..32d3c1eb50 100644 --- a/sdk/src/args.rs +++ b/sdk/src/args.rs @@ -13,7 +13,6 @@ use namada_core::types::dec::Dec; use namada_core::types::ethereum_events::EthAddress; use namada_core::types::keccak::KeccakHash; use namada_core::types::key::{common, SchemeType}; -use namada_core::types::masp::MaspValue; use namada_core::types::storage::Epoch; use namada_core::types::time::DateTimeUtc; use namada_core::types::transaction::GasLimit; @@ -2144,10 +2143,8 @@ pub struct KeyAddressAdd { pub alias: String, /// Whether to force overwrite the alias pub alias_force: bool, - /// Transparent address to add - pub address: Option
, - /// Any shielded value - pub value: Option, + /// Any supported value + pub value: String, /// Don't encrypt the keys pub unsafe_dont_encrypt: bool, } From 999a42564d104d6d49d3f7047da60f6dcfd5c003 Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Fri, 24 Nov 2023 15:00:18 +0100 Subject: [PATCH 071/216] Import key from file --- apps/src/lib/cli.rs | 60 +++++++++++++++++++++++++++++ apps/src/lib/cli/wallet.rs | 77 ++++++++++++++++++++++++++++++-------- sdk/src/args.rs | 13 +++++++ 3 files changed, 135 insertions(+), 15 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index eeefac52b7..14db8433af 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -494,6 +494,8 @@ pub mod cmds { /// Key export KeyExport(WalletExportKey), /// Key import + KeyImport(WalletImportKey), + /// Key / address add KeyAddrAdd(WalletAddKeyAddress), /// Payment address generation PayAddrGen(WalletGenPaymentAddress), @@ -508,6 +510,7 @@ pub mod cmds { .subcommand(WalletListAddresses::def()) .subcommand(WalletFindAddresses::def()) .subcommand(WalletExportKey::def()) + .subcommand(WalletImportKey::def()) .subcommand(WalletAddKeyAddress::def()) .subcommand(WalletGenPaymentAddress::def()) } @@ -520,6 +523,7 @@ pub mod cmds { let addr_list = SubCmd::parse(matches).map(Self::AddrList); let addr_find = SubCmd::parse(matches).map(Self::AddrFind); let export = SubCmd::parse(matches).map(Self::KeyExport); + let import = SubCmd::parse(matches).map(Self::KeyImport); let key_addr_add = SubCmd::parse(matches).map(Self::KeyAddrAdd); let pay_addr_gen = SubCmd::parse(matches).map(Self::PayAddrGen); gen.or(derive) @@ -528,6 +532,7 @@ pub mod cmds { .or(addr_list) .or(addr_find) .or(export) + .or(import) .or(key_addr_add) .or(pay_addr_gen) } @@ -734,6 +739,29 @@ pub mod cmds { } } + /// Import key from a file + #[derive(Clone, Debug)] + pub struct WalletImportKey(pub args::KeyImport); + + impl SubCmd for WalletImportKey { + const CMD: &'static str = "import"; + + fn parse(matches: &ArgMatches) -> Option { + matches + .subcommand_matches(Self::CMD) + .map(|matches| (Self(args::KeyImport::parse(matches)))) + } + + fn def() -> App { + App::new(Self::CMD) + .about( + "Imports a transparent keypair / shielded spending key \ + from a file.", + ) + .add_args::() + } + } + /// Add public / payment address to the wallet #[derive(Clone, Debug)] pub struct WalletAddKeyAddress(pub args::KeyAddressAdd); @@ -2856,6 +2884,7 @@ pub mod args { pub const FEE_AMOUNT_OPT: ArgOpt = arg_opt("gas-price"); pub const FEE_PAYER_OPT: ArgOpt = arg_opt("gas-payer"); + pub const FILE_PATH: Arg = arg("file"); pub const FORCE: ArgFlag = flag("force"); pub const GAS_LIMIT: ArgDefault = arg_default("gas-limit", DefaultFn(|| GasLimit::from(25_000))); @@ -6311,6 +6340,37 @@ pub mod args { } } + impl Args for KeyImport { + fn parse(matches: &ArgMatches) -> Self { + let file_path = FILE_PATH.parse(matches); + let alias = ALIAS.parse(matches); + let alias_force = ALIAS_FORCE.parse(matches); + let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); + Self { + alias, + alias_force, + file_path, + unsafe_dont_encrypt, + } + } + + fn def(app: App) -> App { + app.arg(FILE_PATH.def().help( + "Path to the file containing the key you wish to import.", + )) + .arg(ALIAS.def().help("The alias assigned to the.")) + .arg( + ALIAS_FORCE + .def() + .help("An alias to be associated with the imported entry."), + ) + .arg(UNSAFE_DONT_ENCRYPT.def().help( + "UNSAFE: Do not encrypt the added keys. Do not use this for \ + keys used in a live network.", + )) + } + } + #[derive(Clone, Debug)] pub struct JoinNetwork { pub chain_id: ChainId, diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index 126af2d4ca..d37c363854 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -60,6 +60,9 @@ impl CliApi { cmds::NamadaWallet::KeyExport(cmds::WalletExportKey(args)) => { key_export(ctx, io, args) } + cmds::NamadaWallet::KeyImport(cmds::WalletImportKey(args)) => { + key_import(ctx, io, args) + } cmds::NamadaWallet::KeyAddrAdd(cmds::WalletAddKeyAddress(args)) => { key_address_add(ctx, io, args) } @@ -549,28 +552,20 @@ impl FromStr for KeyAddrAddValue { } } -/// Add key or address -fn key_address_add( +fn add_key_or_address( ctx: Context, io: &impl Io, - args::KeyAddressAdd { - alias, - alias_force, - value, - unsafe_dont_encrypt, - .. - }: args::KeyAddressAdd, + alias: String, + alias_force: bool, + value: KeyAddrAddValue, + unsafe_dont_encrypt: bool, ) { - match KeyAddrAddValue::from_str(&value).unwrap_or_else(|err| { - edisplay_line!(io, "{}", err); - display_line!(io, "No changes are persisted. Exiting."); - cli::safe_exit(1) - }) { + match value { KeyAddrAddValue::TranspSecretKey(sk) => { let mut wallet = load_wallet(ctx); let encryption_password = read_and_confirm_encryption_password(unsafe_dont_encrypt); - wallet + let alias = wallet .insert_keypair( alias, alias_force, @@ -584,6 +579,14 @@ fn key_address_add( display_line!(io, "No changes are persisted. Exiting."); cli::safe_exit(1); }); + wallet + .save() + .unwrap_or_else(|err| edisplay_line!(io, "{}", err)); + display_line!( + io, + "Successfully added a key and an address with alias: \"{}\"", + alias + ); } KeyAddrAddValue::TranspAddress(address) => { transparent_address_add(ctx, io, alias, alias_force, address) @@ -599,6 +602,26 @@ fn key_address_add( } } +/// Add key or address +fn key_address_add( + ctx: Context, + io: &impl Io, + args::KeyAddressAdd { + alias, + alias_force, + value, + unsafe_dont_encrypt, + .. + }: args::KeyAddressAdd, +) { + let value = KeyAddrAddValue::from_str(&value).unwrap_or_else(|err| { + edisplay_line!(io, "{}", err); + display_line!(io, "No changes are persisted. Exiting."); + cli::safe_exit(1) + }); + add_key_or_address(ctx, io, alias, alias_force, value, unsafe_dont_encrypt) +} + /// Find a keypair in the wallet store. fn transparent_key_address_find( ctx: Context, @@ -792,6 +815,30 @@ fn key_export( }) } +/// Import a transparent keypair / MASP spending key from a file. +fn key_import( + ctx: Context, + io: &impl Io, + args::KeyImport { + file_path, + alias, + alias_force, + unsafe_dont_encrypt, + }: args::KeyImport, +) { + let file_data = std::fs::read_to_string(file_path).unwrap_or_else(|err| { + edisplay_line!(io, "{}", err); + display_line!(io, "No changes are persisted. Exiting."); + cli::safe_exit(1) + }); + let value = KeyAddrAddValue::from_str(&file_data).unwrap_or_else(|err| { + edisplay_line!(io, "{}", err); + display_line!(io, "No changes are persisted. Exiting."); + cli::safe_exit(1) + }); + add_key_or_address(ctx, io, alias, alias_force, value, unsafe_dont_encrypt) +} + /// List all known transparent addresses. fn transparent_addresses_list( ctx: Context, diff --git a/sdk/src/args.rs b/sdk/src/args.rs index 32d3c1eb50..df32d00a26 100644 --- a/sdk/src/args.rs +++ b/sdk/src/args.rs @@ -2136,6 +2136,19 @@ pub struct KeyExport { pub alias: String, } +/// Wallet key import arguments +#[derive(Clone, Debug)] +pub struct KeyImport { + /// File name + pub file_path: String, + /// Key alias + pub alias: String, + /// Whether to force overwrite the alias + pub alias_force: bool, + /// Don't encrypt the key + pub unsafe_dont_encrypt: bool, +} + /// Wallet key / address add arguments #[derive(Clone, Debug)] pub struct KeyAddressAdd { From 421aa7ec6739847b8d0e863f15cceb84e4537605 Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Fri, 24 Nov 2023 15:03:20 +0100 Subject: [PATCH 072/216] Fix comments and help messages --- apps/src/lib/cli.rs | 29 ++++++++++++++--------------- apps/src/lib/cli/wallet.rs | 14 +++++++------- sdk/src/args.rs | 6 +++--- 3 files changed, 24 insertions(+), 25 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 14db8433af..a0dc03c63e 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -622,7 +622,7 @@ pub mod cmds { } } - /// TODO List all known shielded keys + /// List all known keys #[derive(Clone, Debug)] pub struct WalletListKeys(pub args::KeyList); @@ -637,10 +637,7 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) - .about( - "TODO List all known keys. Lists all shielded keys in the \ - wallet.", - ) + .about("List all known secret / shielded keys in the wallet.") .add_args::() } } @@ -661,9 +658,11 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) - .about( - "TODO Searches for a keypair from a public key or an \ - alias. Find the given shielded address or key in the \ + .about("Key search") + .long_about( + "In the transparent setting, searches for a keypair from \ + a public key or an alias.\nIn the shielded setting, \ + searches for the given payment address or key in the \ wallet.", ) .add_args::() @@ -671,7 +670,6 @@ pub mod cmds { } /// List known addresses - /// TODO List all known payment addresses #[derive(Clone, Debug)] pub struct WalletListAddresses(pub args::AddressList); @@ -687,7 +685,7 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about( - "TODO List all known addresses. Lists all payment \ + "List all known transparent addresses / shielded payment \ addresses in the wallet", ) .add_args::() @@ -716,7 +714,7 @@ pub mod cmds { } } - /// Export key + /// Export key to a file #[derive(Clone, Debug)] pub struct WalletExportKey(pub args::KeyExport); @@ -6240,10 +6238,11 @@ pub mod args { "Use pre-genesis wallet, instead of for the current chain, if \ any.", )) - .arg(UNSAFE_SHOW_SECRET.def().help( - "TODO UNSAFE: Print the secret key.Print the spending key \ - values.", - )) + .arg( + UNSAFE_SHOW_SECRET + .def() + .help("UNSAFE: Print the secret / spending key."), + ) } } diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index d37c363854..cb187a420b 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -93,8 +93,8 @@ fn spending_keys_list( if known_view_keys.is_empty() { display_line!( io, - "TODO No known keys. Try `masp add --alias my-addr --value ...` \ - to add a new key to the wallet.", + "No known keys. Try `add --alias my-addr --value ...` to add a \ + new key to the wallet, or `gen --shielded` to generate a new key.", ); } else { let stdout = io::stdout(); @@ -165,7 +165,7 @@ fn payment_addresses_list( if known_addresses.is_empty() { display_line!( io, - "TODO No known payment addresses. Try `masp gen-addr --alias \ + "No known payment addresses. Try `masp gen-payment-addr --alias \ my-addr` to generate a new payment address.", ); } else { @@ -527,7 +527,7 @@ fn address_list(ctx: Context, io: &impl Io, args_key_list: args::AddressList) { } } -/// Value for add command +/// Value for wallet `add` command #[allow(clippy::large_enum_variant)] #[derive(Debug)] pub enum KeyAddrAddValue { @@ -709,9 +709,9 @@ fn shielded_key_address_find( // Otherwise alias cannot be referring to any shielded value display_line!( io, - "TODO No shielded address or key with alias {} found. Use the \ - commands `masp list-addrs` and `masp list-keys` to see all the \ - known addresses and keys.", + "No shielded address or key with alias {} found. Use the commands \ + `list-addr --shielded` and `list-keys --shielded` to see all the \ + known shielded addresses and keys.", alias.to_lowercase() ); } diff --git a/sdk/src/args.rs b/sdk/src/args.rs index df32d00a26..603f1b42b1 100644 --- a/sdk/src/args.rs +++ b/sdk/src/args.rs @@ -2050,7 +2050,7 @@ pub struct KeyGen { pub raw: bool, /// Key alias pub alias: String, - /// Whether to force overwrite the alias, if provided + /// Whether to force overwrite the alias pub alias_force: bool, /// Don't encrypt the keypair pub unsafe_dont_encrypt: bool, @@ -2067,7 +2067,7 @@ pub struct KeyDerive { pub shielded: bool, /// Key alias pub alias: String, - /// Whether to force overwrite the alias, if provided + /// Whether to force overwrite the alias pub alias_force: bool, /// Don't encrypt the keypair pub unsafe_dont_encrypt: bool, @@ -2158,7 +2158,7 @@ pub struct KeyAddressAdd { pub alias_force: bool, /// Any supported value pub value: String, - /// Don't encrypt the keys + /// Don't encrypt the key pub unsafe_dont_encrypt: bool, } From f236eef839d65b81e001ea2173e6dd7bf17b9da0 Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Fri, 24 Nov 2023 18:58:18 +0100 Subject: [PATCH 073/216] Merge key and address list --- apps/src/lib/cli.rs | 93 +++++++++++++------------------------- apps/src/lib/cli/wallet.rs | 90 +++++++++++++++++++----------------- sdk/src/args.rs | 23 +++------- 3 files changed, 86 insertions(+), 120 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index a0dc03c63e..613aaa3559 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -483,12 +483,10 @@ pub mod cmds { KeyGen(WalletGen), /// Key derivation KeyDerive(WalletDerive), - /// Key list - KeyList(WalletListKeys), + /// Key / address list + KeyAddrList(WalletListKeysAddresses), /// Key search KeyFind(WalletFindKeys), - /// Address list - AddrList(WalletListAddresses), /// Address search AddrFind(WalletFindAddresses), /// Key export @@ -505,9 +503,8 @@ pub mod cmds { fn add_sub(app: App) -> App { app.subcommand(WalletGen::def()) .subcommand(WalletDerive::def()) - .subcommand(WalletListKeys::def()) + .subcommand(WalletListKeysAddresses::def()) .subcommand(WalletFindKeys::def()) - .subcommand(WalletListAddresses::def()) .subcommand(WalletFindAddresses::def()) .subcommand(WalletExportKey::def()) .subcommand(WalletImportKey::def()) @@ -518,18 +515,16 @@ pub mod cmds { fn parse(matches: &ArgMatches) -> Option { let gen = SubCmd::parse(matches).map(Self::KeyGen); let derive = SubCmd::parse(matches).map(Self::KeyDerive); - let key_list = SubCmd::parse(matches).map(Self::KeyList); + let key_addr_list = SubCmd::parse(matches).map(Self::KeyAddrList); let key_find = SubCmd::parse(matches).map(Self::KeyFind); - let addr_list = SubCmd::parse(matches).map(Self::AddrList); let addr_find = SubCmd::parse(matches).map(Self::AddrFind); let export = SubCmd::parse(matches).map(Self::KeyExport); let import = SubCmd::parse(matches).map(Self::KeyImport); let key_addr_add = SubCmd::parse(matches).map(Self::KeyAddrAdd); let pay_addr_gen = SubCmd::parse(matches).map(Self::PayAddrGen); gen.or(derive) - .or(key_list) + .or(key_addr_list) .or(key_find) - .or(addr_list) .or(addr_find) .or(export) .or(import) @@ -622,23 +617,28 @@ pub mod cmds { } } - /// List all known keys + /// List known keys and addresses #[derive(Clone, Debug)] - pub struct WalletListKeys(pub args::KeyList); + pub struct WalletListKeysAddresses(pub args::KeyAddressList); - impl SubCmd for WalletListKeys { - const CMD: &'static str = "list-keys"; + impl SubCmd for WalletListKeysAddresses { + const CMD: &'static str = "list"; fn parse(matches: &ArgMatches) -> Option { matches .subcommand_matches(Self::CMD) - .map(|matches| (Self(args::KeyList::parse(matches)))) + .map(|matches| (Self(args::KeyAddressList::parse(matches)))) } fn def() -> App { App::new(Self::CMD) - .about("List all known secret / shielded keys in the wallet.") - .add_args::() + .about("List known keys and addresses in the wallet.") + .long_about( + "In the transparent setting, list known keypairs and \ + addresses.\nIn the shielded setting, list known spending \ + keys and payment addresses.", + ) + .add_args::() } } @@ -665,30 +665,7 @@ pub mod cmds { searches for the given payment address or key in the \ wallet.", ) - .add_args::() - } - } - - /// List known addresses - #[derive(Clone, Debug)] - pub struct WalletListAddresses(pub args::AddressList); - - impl SubCmd for WalletListAddresses { - const CMD: &'static str = "list-addr"; - - fn parse(matches: &ArgMatches) -> Option { - matches - .subcommand_matches(Self::CMD) - .map(|matches| Self(args::AddressList::parse(matches))) - } - - fn def() -> App { - App::new(Self::CMD) - .about( - "List all known transparent addresses / shielded payment \ - addresses in the wallet", - ) - .add_args::() + .add_args::() } } @@ -2918,8 +2895,9 @@ pub mod args { let raw = "127.0.0.1:26657"; TendermintAddress::from_str(raw).unwrap() })); - pub const LEDGER_ADDRESS: Arg = arg("node"); + pub const LIST_ADDRESSES_ONLY: ArgFlag = flag("addr"); + pub const LIST_KEYS_ONLY: ArgFlag = flag("keys"); pub const LOCALHOST: ArgFlag = flag("localhost"); pub const MASP_VALUE_OPT: ArgOpt = arg_opt("shielded-value"); pub const MAX_COMMISSION_RATE_CHANGE: Arg = @@ -6152,25 +6130,30 @@ pub mod args { } } - impl Args for KeyList { + impl Args for KeyAddressList { fn parse(matches: &ArgMatches) -> Self { let shielded = SHIELDED.parse(matches); let decrypt = DECRYPT.parse(matches); + let keys_only = LIST_KEYS_ONLY.parse(matches); + let addresses_only = LIST_ADDRESSES_ONLY.parse(matches); let unsafe_show_secret = UNSAFE_SHOW_SECRET.parse(matches); Self { shielded, decrypt, + keys_only, + addresses_only, unsafe_show_secret, } } fn def(app: App) -> App { - app.arg( - SHIELDED - .def() - .help("List spending keys for the shielded pool."), - ) + app.arg(SHIELDED.def().help( + "List spending keys and payment addresses for the shielded \ + pool.", + )) .arg(DECRYPT.def().help("Decrypt keys that are encrypted.")) + .arg(LIST_KEYS_ONLY.def().help("List only keys.")) + .arg(LIST_ADDRESSES_ONLY.def().help("List only addresses.")) .arg( UNSAFE_SHOW_SECRET .def() @@ -6246,20 +6229,6 @@ pub mod args { } } - impl Args for AddressList { - fn parse(matches: &ArgMatches) -> Self { - let shielded = SHIELDED.parse(matches); - Self { shielded } - } - - fn def(app: App) -> App { - app.arg(PRE_GENESIS.def().help( - "Use pre-genesis wallet, instead of for the current chain, if \ - any.", - )) - } - } - impl Args for AddressFind { fn parse(matches: &ArgMatches) -> Self { let alias = ALIAS_OPT.parse(matches); diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index cb187a420b..0bc90782c7 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -45,15 +45,12 @@ impl CliApi { cmds::NamadaWallet::KeyDerive(cmds::WalletDerive(args)) => { key_derive(ctx, io, args).await } - cmds::NamadaWallet::KeyList(cmds::WalletListKeys(args)) => { - key_list(ctx, io, args) - } + cmds::NamadaWallet::KeyAddrList(cmds::WalletListKeysAddresses( + args, + )) => key_address_list(ctx, io, args), cmds::NamadaWallet::KeyFind(cmds::WalletFindKeys(args)) => { key_find(ctx, io, args) } - cmds::NamadaWallet::AddrList(cmds::WalletListAddresses(args)) => { - address_list(ctx, io, args) - } cmds::NamadaWallet::AddrFind(cmds::WalletFindAddresses(args)) => { address_or_alias_find(ctx, io, args) } @@ -81,11 +78,8 @@ impl CliApi { fn spending_keys_list( ctx: Context, io: &impl Io, - args::KeyList { - decrypt, - unsafe_show_secret, - .. - }: args::KeyList, + decrypt: bool, + unsafe_show_secret: bool, ) { let wallet = load_wallet(ctx); let known_view_keys = wallet.get_viewing_keys(); @@ -155,17 +149,13 @@ fn spending_keys_list( } /// List payment addresses. -fn payment_addresses_list( - ctx: Context, - io: &impl Io, - // args::AddressList { .. }: args::AddressList, -) { +fn payment_addresses_list(ctx: Context, io: &impl Io) { let wallet = load_wallet(ctx); let known_addresses = wallet.get_payment_addrs(); if known_addresses.is_empty() { display_line!( io, - "No known payment addresses. Try `masp gen-payment-addr --alias \ + "No known payment addresses. Try `gen-payment-addr --alias \ my-addr` to generate a new payment address.", ); } else { @@ -501,11 +491,46 @@ async fn key_derive( } /// List keys -fn key_list(ctx: Context, io: &impl Io, args_key_list: args::KeyList) { - if !args_key_list.shielded { - transparent_keys_list(ctx, io, args_key_list) +fn key_list( + ctx: Context, + io: &impl Io, + args::KeyAddressList { + shielded, + decrypt, + unsafe_show_secret, + .. + }: args::KeyAddressList, +) { + if !shielded { + transparent_keys_list(ctx, io, decrypt, unsafe_show_secret) + } else { + spending_keys_list(ctx, io, decrypt, unsafe_show_secret) + } +} + +/// List addresses +fn address_list( + ctx: Context, + io: &impl Io, + args::KeyAddressList { shielded, .. }: args::KeyAddressList, +) { + if !shielded { + transparent_addresses_list(ctx, io) } else { - spending_keys_list(ctx, io, args_key_list) + payment_addresses_list(ctx, io) + } +} + +/// List keys and addresses +fn key_address_list( + ctx: Context, + io: &impl Io, + args_key_address_list: args::KeyAddressList, +) { + if !args_key_address_list.addresses_only { + key_list(ctx, io, args_key_address_list) + } else if !args_key_address_list.keys_only { + address_list(ctx, io, args_key_address_list) } } @@ -518,15 +543,6 @@ fn key_find(ctx: Context, io: &impl Io, args_key_find: args::KeyFind) { } } -/// List addresses -fn address_list(ctx: Context, io: &impl Io, args_key_list: args::AddressList) { - if !args_key_list.shielded { - transparent_addresses_list(ctx, io, args_key_list) - } else { - payment_addresses_list(ctx, io) - } -} - /// Value for wallet `add` command #[allow(clippy::large_enum_variant)] #[derive(Debug)] @@ -721,11 +737,8 @@ fn shielded_key_address_find( fn transparent_keys_list( ctx: Context, io: &impl Io, - args::KeyList { - decrypt, - unsafe_show_secret, - .. - }: args::KeyList, + decrypt: bool, + unsafe_show_secret: bool, ) { let wallet = load_wallet(ctx); let known_public_keys = wallet.get_public_keys(); @@ -840,12 +853,7 @@ fn key_import( } /// List all known transparent addresses. -fn transparent_addresses_list( - ctx: Context, - io: &impl Io, - _args: args::AddressList, - // args::AddressList { is_pre_genesis, .. }: args::AddressList, -) { +fn transparent_addresses_list(ctx: Context, io: &impl Io) { let wallet = load_wallet(ctx); let known_addresses = wallet.get_addresses(); if known_addresses.is_empty() { diff --git a/sdk/src/args.rs b/sdk/src/args.rs index 603f1b42b1..e21cba4e7b 100644 --- a/sdk/src/args.rs +++ b/sdk/src/args.rs @@ -2092,32 +2092,21 @@ pub struct KeyFind { pub unsafe_show_secret: bool, } -/// Wallet list shielded payment addresses arguments -// #[derive(Clone, Debug)] -// pub struct MaspListPayAddrs { -// /// List shielded payment address pre-genesis instead -// /// of a current chain -// pub is_pre_genesis: bool, -// } - -/// Wallet list keys arguments +/// Wallet list arguments #[derive(Clone, Debug)] -pub struct KeyList { +pub struct KeyAddressList { /// Whether to list MASP spending keys pub shielded: bool, /// Don't decrypt keys pub decrypt: bool, + /// List keys only + pub keys_only: bool, + /// List addresses only + pub addresses_only: bool, /// Show secret keys to user pub unsafe_show_secret: bool, } -/// List addresses arguments -#[derive(Clone, Debug)] -pub struct AddressList { - /// Whether to list MASP payment addresses - pub shielded: bool, -} - /// Wallet address lookup arguments #[derive(Clone, Debug)] pub struct AddressFind { From bf5c3ac4fc1e24afd61985d6f1912d7781417150 Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Fri, 24 Nov 2023 19:04:06 +0100 Subject: [PATCH 074/216] Fix typo --- apps/src/lib/cli.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 613aaa3559..c5842ff8e1 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1842,7 +1842,7 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) - .about("Change commission raate.") + .about("Change commission rate.") .add_args::>() } } From 14c450613f477b274645c965220b60ab90b75ae8 Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Mon, 27 Nov 2023 16:39:06 +0100 Subject: [PATCH 075/216] Improve help messages --- apps/src/lib/cli.rs | 8 ++++---- apps/src/lib/cli/wallet.rs | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index c5842ff8e1..ecb58d113c 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -636,7 +636,7 @@ pub mod cmds { .long_about( "In the transparent setting, list known keypairs and \ addresses.\nIn the shielded setting, list known spending \ - keys and payment addresses.", + / viewing keys and payment addresses.", ) .add_args::() } @@ -773,7 +773,7 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about( - "Generates a payment address from the given spending key", + "Generates a payment address from the given spending key.", ) .add_args::>() } @@ -6148,8 +6148,8 @@ pub mod args { fn def(app: App) -> App { app.arg(SHIELDED.def().help( - "List spending keys and payment addresses for the shielded \ - pool.", + "List viewing / spending keys and payment addresses for the \ + shielded pool.", )) .arg(DECRYPT.def().help("Decrypt keys that are encrypted.")) .arg(LIST_KEYS_ONLY.def().help("List only keys.")) diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index 0bc90782c7..873248bf41 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -88,7 +88,8 @@ fn spending_keys_list( display_line!( io, "No known keys. Try `add --alias my-addr --value ...` to add a \ - new key to the wallet, or `gen --shielded` to generate a new key.", + new key to the wallet, or `gen --shielded --alias my-key` to \ + generate a new key.", ); } else { let stdout = io::stdout(); From 93f429477b9894b8fcb31aa7c05c6fc3aa413e83 Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Fri, 8 Dec 2023 16:31:20 +0100 Subject: [PATCH 076/216] Store known payment addresses in bimap --- sdk/src/wallet/store.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/sdk/src/wallet/store.rs b/sdk/src/wallet/store.rs index 2ed2f4d915..8ba66df798 100644 --- a/sdk/src/wallet/store.rs +++ b/sdk/src/wallet/store.rs @@ -63,8 +63,8 @@ pub struct Store { view_keys: BTreeMap, /// Known spending keys spend_keys: BTreeMap>, - /// Known payment addresses - payment_addrs: BTreeMap, + /// Payment address book + payment_addrs: BiBTreeMap, /// Cryptographic keypairs secret_keys: BTreeMap>, /// Known public keys @@ -148,7 +148,15 @@ impl Store { &self, alias: impl AsRef, ) -> Option<&PaymentAddress> { - self.payment_addrs.get(&alias.into()) + self.payment_addrs.get_by_left(&alias.into()) + } + + /// Find an alias by the address if it's in the wallet. + pub fn find_alias_by_payment_addr( + &self, + payment_address: &PaymentAddress, + ) -> Option<&Alias> { + self.payment_addrs.get_by_right(payment_address) } /// Find the stored key by a public key. @@ -237,7 +245,7 @@ impl Store { } /// Get all known payment addresses by their alias. - pub fn get_payment_addrs(&self) -> &BTreeMap { + pub fn get_payment_addrs(&self) -> &BiBTreeMap { &self.payment_addrs } @@ -557,7 +565,7 @@ impl Store { /// Check if any map of the wallet contains the given alias pub fn contains_alias(&self, alias: &Alias) -> bool { - self.payment_addrs.contains_key(alias) + self.payment_addrs.contains_left(alias) || self.view_keys.contains_key(alias) || self.spend_keys.contains_key(alias) || self.secret_keys.contains_key(alias) @@ -569,7 +577,7 @@ impl Store { /// Completely remove the given alias from all maps in the wallet fn remove_alias(&mut self, alias: &Alias) { - self.payment_addrs.remove(alias); + self.payment_addrs.remove_by_left(alias); self.view_keys.remove(alias); self.spend_keys.remove(alias); self.secret_keys.remove(alias); From 6fe0c5480b16e9b53f05e9ffff6215c6fc8d44af Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Sat, 9 Dec 2023 20:17:15 +0100 Subject: [PATCH 077/216] Reverse find alias for payment address --- sdk/src/wallet/mod.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sdk/src/wallet/mod.rs b/sdk/src/wallet/mod.rs index c5fe54aea9..f17e4ba90f 100644 --- a/sdk/src/wallet/mod.rs +++ b/sdk/src/wallet/mod.rs @@ -417,6 +417,14 @@ impl Wallet { self.store.find_payment_addr(alias.as_ref()) } + /// Find an alias by the payment address if it's in the wallet. + pub fn find_alias_by_payment_addr( + &self, + payment_address: &PaymentAddress, + ) -> Option<&Alias> { + self.store.find_alias_by_payment_addr(payment_address) + } + /// Get all known keys by their alias, paired with PKH, if known. pub fn get_secret_keys( &self, From 21ee93167f4f07b16b6695d96c3366dff2ba0eaf Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Sat, 9 Dec 2023 21:01:15 +0100 Subject: [PATCH 078/216] Merge find-key and find-addr --- apps/src/lib/cli.rs | 180 ++++++++++------------ apps/src/lib/cli/wallet.rs | 300 +++++++++++++++++++++++++++---------- sdk/src/args.rs | 35 +++-- 3 files changed, 320 insertions(+), 195 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index ecb58d113c..78d42605fa 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -485,10 +485,8 @@ pub mod cmds { KeyDerive(WalletDerive), /// Key / address list KeyAddrList(WalletListKeysAddresses), - /// Key search - KeyFind(WalletFindKeys), - /// Address search - AddrFind(WalletFindAddresses), + /// Key / address search + KeyAddrFind(WalletFindKeysAddresses), /// Key export KeyExport(WalletExportKey), /// Key import @@ -504,8 +502,7 @@ pub mod cmds { app.subcommand(WalletGen::def()) .subcommand(WalletDerive::def()) .subcommand(WalletListKeysAddresses::def()) - .subcommand(WalletFindKeys::def()) - .subcommand(WalletFindAddresses::def()) + .subcommand(WalletFindKeysAddresses::def()) .subcommand(WalletExportKey::def()) .subcommand(WalletImportKey::def()) .subcommand(WalletAddKeyAddress::def()) @@ -516,16 +513,14 @@ pub mod cmds { let gen = SubCmd::parse(matches).map(Self::KeyGen); let derive = SubCmd::parse(matches).map(Self::KeyDerive); let key_addr_list = SubCmd::parse(matches).map(Self::KeyAddrList); - let key_find = SubCmd::parse(matches).map(Self::KeyFind); - let addr_find = SubCmd::parse(matches).map(Self::AddrFind); + let key_addr_find = SubCmd::parse(matches).map(Self::KeyAddrFind); let export = SubCmd::parse(matches).map(Self::KeyExport); let import = SubCmd::parse(matches).map(Self::KeyImport); let key_addr_add = SubCmd::parse(matches).map(Self::KeyAddrAdd); let pay_addr_gen = SubCmd::parse(matches).map(Self::PayAddrGen); gen.or(derive) .or(key_addr_list) - .or(key_find) - .or(addr_find) + .or(key_addr_find) .or(export) .or(import) .or(key_addr_add) @@ -642,52 +637,31 @@ pub mod cmds { } } - /// TODO Find a keypair in the wallet store - /// TODO Find the given shielded address or key + /// Find known keys and addresses #[derive(Clone, Debug)] - pub struct WalletFindKeys(pub args::KeyFind); + pub struct WalletFindKeysAddresses(pub args::KeyAddressFind); - impl SubCmd for WalletFindKeys { - const CMD: &'static str = "find-keys"; + impl SubCmd for WalletFindKeysAddresses { + const CMD: &'static str = "find"; fn parse(matches: &ArgMatches) -> Option { matches .subcommand_matches(Self::CMD) - .map(|matches| (Self(args::KeyFind::parse(matches)))) + .map(|matches| Self(args::KeyAddressFind::parse(matches))) } fn def() -> App { App::new(Self::CMD) - .about("Key search") + .about("Find known keys and addresses in the wallet.") .long_about( - "In the transparent setting, searches for a keypair from \ - a public key or an alias.\nIn the shielded setting, \ - searches for the given payment address or key in the \ - wallet.", + "In the transparent setting, searches for a keypair / \ + address by a given alias, public key, or a public key \ + hash. Looks up an alias of the given address.\nIn the \ + shielded setting, searches for a spending / viewing key \ + and payment address by a given alias. Looks up an alias \ + of the given payment address.", ) - .add_args::() - } - } - - /// Find an address by its alias - #[derive(Clone, Debug)] - pub struct WalletFindAddresses(pub args::AddressFind); - - impl SubCmd for WalletFindAddresses { - const CMD: &'static str = "find-addr"; - - fn parse(matches: &ArgMatches) -> Option { - matches - .subcommand_matches(Self::CMD) - .map(|matches| Self(args::AddressFind::parse(matches))) - } - - fn def() -> App { - App::new(Self::CMD) - .about( - "Find an address by its alias or an alias by its address.", - ) - .add_args::() + .add_args::() } } @@ -2746,7 +2720,7 @@ pub mod args { use namada::types::ethereum_events::EthAddress; use namada::types::keccak::KeccakHash; use namada::types::key::*; - use namada::types::masp::MaspValue; + use namada::types::masp::PaymentAddress; use namada::types::storage::{self, BlockHeight, Epoch}; use namada::types::time::DateTimeUtc; use namada::types::token; @@ -2896,10 +2870,9 @@ pub mod args { TendermintAddress::from_str(raw).unwrap() })); pub const LEDGER_ADDRESS: Arg = arg("node"); - pub const LIST_ADDRESSES_ONLY: ArgFlag = flag("addr"); - pub const LIST_KEYS_ONLY: ArgFlag = flag("keys"); + pub const LIST_FIND_ADDRESSES_ONLY: ArgFlag = flag("addr"); + pub const LIST_FIND_KEYS_ONLY: ArgFlag = flag("keys"); pub const LOCALHOST: ArgFlag = flag("localhost"); - pub const MASP_VALUE_OPT: ArgOpt = arg_opt("shielded-value"); pub const MAX_COMMISSION_RATE_CHANGE: Arg = arg("max-commission-rate-change"); pub const MAX_ETH_GAS: ArgOpt = arg_opt("max_eth-gas"); @@ -2939,9 +2912,15 @@ pub mod args { pub const RAW_ADDRESS_ESTABLISHED: Arg = arg("address"); pub const RAW_ADDRESS_OPT: ArgOpt
= RAW_ADDRESS.opt(); pub const RAW_KEY_GEN: ArgFlag = flag("raw"); + pub const RAW_PAYMENT_ADDRESS: Arg = arg("payment-address"); + pub const RAW_PAYMENT_ADDRESS_OPT: ArgOpt = + RAW_PAYMENT_ADDRESS.opt(); pub const RAW_PUBLIC_KEY: Arg = arg("public-key"); pub const RAW_PUBLIC_KEY_OPT: ArgOpt = - arg_opt("public-key"); + RAW_PUBLIC_KEY.opt(); + pub const RAW_PUBLIC_KEY_HASH: Arg = arg("public-key-hash"); + pub const RAW_PUBLIC_KEY_HASH_OPT: ArgOpt = + RAW_PUBLIC_KEY_HASH.opt(); pub const RECEIVER: Arg = arg("receiver"); pub const RELAYER: Arg
= arg("relayer"); pub const SAFE_MODE: ArgFlag = flag("safe-mode"); @@ -2989,7 +2968,6 @@ pub mod args { pub const VALIDATOR_ETH_HOT_KEY: ArgOpt = arg_opt("eth-hot-key"); pub const VALUE: Arg = arg("value"); - pub const VALUE_OPT: ArgOpt = VALUE.opt(); pub const VIEWING_KEY: Arg = arg("key"); pub const VP: ArgOpt = arg_opt("vp"); pub const WALLET_ALIAS_FORCE: ArgFlag = flag("wallet-alias-force"); @@ -6134,8 +6112,8 @@ pub mod args { fn parse(matches: &ArgMatches) -> Self { let shielded = SHIELDED.parse(matches); let decrypt = DECRYPT.parse(matches); - let keys_only = LIST_KEYS_ONLY.parse(matches); - let addresses_only = LIST_ADDRESSES_ONLY.parse(matches); + let keys_only = LIST_FIND_KEYS_ONLY.parse(matches); + let addresses_only = LIST_FIND_ADDRESSES_ONLY.parse(matches); let unsafe_show_secret = UNSAFE_SHOW_SECRET.parse(matches); Self { shielded, @@ -6152,8 +6130,12 @@ pub mod args { shielded pool.", )) .arg(DECRYPT.def().help("Decrypt keys that are encrypted.")) - .arg(LIST_KEYS_ONLY.def().help("List only keys.")) - .arg(LIST_ADDRESSES_ONLY.def().help("List only addresses.")) + .arg(LIST_FIND_KEYS_ONLY.def().help("List keys only.")) + .arg(LIST_FIND_ADDRESSES_ONLY.def().help("List addresses only.")) + .group(ArgGroup::new("only_group").args([ + LIST_FIND_KEYS_ONLY.name, + LIST_FIND_ADDRESSES_ONLY.name, + ])) .arg( UNSAFE_SHOW_SECRET .def() @@ -6162,61 +6144,83 @@ pub mod args { } } - impl Args for KeyFind { + impl Args for KeyAddressFind { fn parse(matches: &ArgMatches) -> Self { let shielded = SHIELDED.parse(matches); let alias = ALIAS_OPT.parse(matches); + let address = RAW_ADDRESS_OPT.parse(matches); let public_key = RAW_PUBLIC_KEY_OPT.parse(matches); - let value = VALUE_OPT.parse(matches); + let public_key_hash = RAW_PUBLIC_KEY_HASH_OPT.parse(matches); + let payment_address = RAW_PAYMENT_ADDRESS_OPT.parse(matches); + let keys_only = LIST_FIND_KEYS_ONLY.parse(matches); + let addresses_only = LIST_FIND_ADDRESSES_ONLY.parse(matches); let unsafe_show_secret = UNSAFE_SHOW_SECRET.parse(matches); - Self { shielded, alias, + address, public_key, - value, + public_key_hash, + payment_address, + keys_only, + addresses_only, unsafe_show_secret, } } fn def(app: App) -> App { app.arg( - SHIELDED - .def() - .help("Find spending key for the shielded pool.") - .conflicts_with_all([ - VALUE_OPT.name, - RAW_PUBLIC_KEY_OPT.name, - ]), + SHIELDED.def().help( + "Find keys and payment addresses for the shielded pool.", + ), ) .arg( ALIAS_OPT + .def() + .help("An alias associated with the keys / addresses."), + ) + .arg( + RAW_ADDRESS_OPT .def() .help( - "TODO An alias associated with the keypair. The alias \ - that is to be found.", + "The bech32m encoded string of a transparent address.", ) - .conflicts_with(VALUE_OPT.name), + .conflicts_with(SHIELDED.name), ) .arg( - RAW_PUBLIC_KEY_OPT - .def() - .help("A public key associated with the keypair."), + RAW_PUBLIC_KEY_OPT.def().help( + "A public key associated with the transparent keypair.", + ), ) + .arg(RAW_PUBLIC_KEY_HASH_OPT.def().help( + "A public key hash associated with the transparent keypair.", + )) .arg( - VALUE_OPT + RAW_PAYMENT_ADDRESS_OPT .def() - .help("A public key or alias associated with the keypair."), + .help( + "The bech32m encoded string of a shielded payment \ + address.", + ) + .conflicts_with(SHIELDED.name), ) .group( - ArgGroup::new("key_find_args") + ArgGroup::new("addr_find_args") .args([ ALIAS_OPT.name, + RAW_ADDRESS_OPT.name, RAW_PUBLIC_KEY_OPT.name, - VALUE_OPT.name, + RAW_PUBLIC_KEY_HASH_OPT.name, + RAW_PAYMENT_ADDRESS_OPT.name, ]) .required(true), ) + .arg(LIST_FIND_KEYS_ONLY.def().help("Find keys only.")) + .arg(LIST_FIND_ADDRESSES_ONLY.def().help("List addresses only.")) + .group(ArgGroup::new("only_group").args([ + LIST_FIND_KEYS_ONLY.name, + LIST_FIND_ADDRESSES_ONLY.name, + ])) .arg(PRE_GENESIS.def().help( "Use pre-genesis wallet, instead of for the current chain, if \ any.", @@ -6229,32 +6233,6 @@ pub mod args { } } - impl Args for AddressFind { - fn parse(matches: &ArgMatches) -> Self { - let alias = ALIAS_OPT.parse(matches); - let address = RAW_ADDRESS_OPT.parse(matches); - Self { alias, address } - } - - fn def(app: App) -> App { - app.arg( - ALIAS_OPT - .def() - .help("An alias associated with the address."), - ) - .arg( - RAW_ADDRESS_OPT - .def() - .help("The bech32m encoded address string."), - ) - .group( - ArgGroup::new("addr_find_args") - .args([ALIAS_OPT.name, RAW_ADDRESS_OPT.name]) - .required(true), - ) - } - } - impl Args for KeyAddressAdd { fn parse(matches: &ArgMatches) -> Self { let alias = ALIAS.parse(matches); diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index 873248bf41..7b98749867 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -48,12 +48,9 @@ impl CliApi { cmds::NamadaWallet::KeyAddrList(cmds::WalletListKeysAddresses( args, )) => key_address_list(ctx, io, args), - cmds::NamadaWallet::KeyFind(cmds::WalletFindKeys(args)) => { - key_find(ctx, io, args) - } - cmds::NamadaWallet::AddrFind(cmds::WalletFindAddresses(args)) => { - address_or_alias_find(ctx, io, args) - } + cmds::NamadaWallet::KeyAddrFind(cmds::WalletFindKeysAddresses( + args, + )) => key_address_find(ctx, io, args), cmds::NamadaWallet::KeyExport(cmds::WalletExportKey(args)) => { key_export(ctx, io, args) } @@ -535,12 +532,59 @@ fn key_address_list( } } -/// Find key or address -fn key_find(ctx: Context, io: &impl Io, args_key_find: args::KeyFind) { - if !args_key_find.shielded { - transparent_key_address_find(ctx, io, args_key_find) - } else { - shielded_key_address_find(ctx, io, args_key_find) +/// Find keys and addresses +fn key_address_find( + ctx: Context, + io: &impl Io, + args::KeyAddressFind { + shielded, + alias, + address, + public_key, + public_key_hash, + payment_address, + keys_only, + addresses_only, + unsafe_show_secret, + }: args::KeyAddressFind, +) { + if let Some(alias) = alias { + // Search keys and addresses by alias + if !shielded { + transparent_key_address_find_by_alias( + ctx, + io, + alias, + keys_only, + addresses_only, + unsafe_show_secret, + ) + } else { + shielded_key_address_find_by_alias( + ctx, + io, + alias, + keys_only, + addresses_only, + unsafe_show_secret, + ) + } + } else if address.is_some() { + // Search alias by address + transparent_address_or_alias_find(ctx, io, None, address) + } else if public_key.is_some() || public_key_hash.is_some() { + // Search transparent keypair by public key or public key hash + transparent_key_find( + ctx, + io, + None, + public_key, + public_key_hash, + unsafe_show_secret, + ) + } else if payment_address.is_some() { + // Search alias by MASP payment address + payment_address_or_alias_find(ctx, io, None, payment_address) } } @@ -640,22 +684,19 @@ fn key_address_add( } /// Find a keypair in the wallet store. -fn transparent_key_address_find( +fn transparent_key_find( ctx: Context, io: &impl Io, - args::KeyFind { - public_key, - alias, - value, - unsafe_show_secret, - .. - }: args::KeyFind, + alias: Option, + public_key: Option, + public_key_hash: Option, + unsafe_show_secret: bool, ) { let mut wallet = load_wallet(ctx); let found_keypair = match public_key { Some(pk) => wallet.find_key_by_pk(&pk, None), None => { - let alias = alias.or(value); + let alias = alias.or(public_key_hash); match alias { None => { edisplay_line!( @@ -686,27 +727,140 @@ fn transparent_key_address_find( } } -/// Find shielded address or key -/// TODO this works for both keys and addresses -/// TODO split to enable finding alias by payment key -fn shielded_key_address_find( +/// Find address (alias) by its alias (address). +fn transparent_address_or_alias_find( ctx: Context, io: &impl Io, - args::KeyFind { - alias, - unsafe_show_secret, - .. - }: args::KeyFind, + alias: Option, + address: Option
, +) { + let wallet = load_wallet(ctx); + if address.is_some() && alias.is_some() { + panic!( + "This should not be happening: clap should emit its own error \ + message." + ); + } else if alias.is_some() { + let alias = alias.unwrap().to_lowercase(); + if let Some(address) = wallet.find_address(&alias) { + display_line!(io, "Found address {}", address.to_pretty_string()); + } else { + display_line!( + io, + "No address with alias {} found. Use the command `list \ + --addr` to see all the known transparent addresses.", + alias + ); + } + } else if address.is_some() { + if let Some(alias) = wallet.find_alias(address.as_ref().unwrap()) { + display_line!(io, "Found alias {}", alias); + } else { + display_line!( + io, + "No address with alias {} found. Use the command `list \ + --addr` to see all the known transparent addresses.", + address.unwrap() + ); + } + } +} + +/// Find payment address (alias) by its alias (payment address). +fn payment_address_or_alias_find( + ctx: Context, + io: &impl Io, + alias: Option, + payment_address: Option, +) { + let wallet = load_wallet(ctx); + if payment_address.is_some() && alias.is_some() { + panic!( + "This should not be happening: clap should emit its own error \ + message." + ); + } else if alias.is_some() { + let alias = alias.unwrap().to_lowercase(); + if let Some(payment_addr) = wallet.find_payment_addr(&alias) { + display_line!(io, "Found payment address {}", payment_addr); + } else { + display_line!( + io, + "No payment address with alias {} found. Use the command \ + `list --shielded --addr` to see all the known payment \ + addresses.", + alias + ); + } + } else if payment_address.is_some() { + if let Some(alias) = + wallet.find_alias_by_payment_addr(payment_address.as_ref().unwrap()) + { + display_line!(io, "Found alias {}", alias); + } else { + display_line!( + io, + "No address with alias {} found. Use the command `list \ + --shielded --addr` to see all the known payment addresses.", + payment_address.unwrap() + ); + } + } +} + +/// Find transparent addresses and keys by alias +fn transparent_key_address_find_by_alias( + ctx: Context, + io: &impl Io, + alias: String, + keys_only: bool, + addresses_only: bool, + unsafe_show_secret: bool, ) { let mut wallet = load_wallet(ctx); - // TODO - let alias = alias.unwrap_or_else(|| { - display_line!(io, "Missing alias."); - display_line!(io, "No changes are persisted. Exiting."); - cli::safe_exit(1) - }); let alias = alias.to_lowercase(); - if let Ok(viewing_key) = wallet.find_viewing_key(&alias) { + if let Some(keypair) = (!addresses_only) + .then_some(()) + .and_then(|_| wallet.find_secret_key(&alias, None).ok()) + { + let pkh: PublicKeyHash = (&keypair.ref_to()).into(); + display_line!(io, "Public key hash: {}", pkh); + display_line!(io, "Public key: {}", keypair.ref_to()); + if unsafe_show_secret { + display_line!(io, "Secret key: {}", keypair); + } + } else if let Some(address) = (!keys_only) + .then_some(()) + .and_then(|_| wallet.find_address(&alias)) + { + display_line!(io, "Found address {}", address.to_pretty_string()); + } else if !addresses_only && !keys_only { + // Otherwise alias cannot be referring to any shielded value + display_line!( + io, + "No transparent address or key with alias {} found. Use the \ + command `list` to see all the known transparent addresses and \ + keys.", + alias + ); + } +} + +/// Find shielded payment address and keys by alias +fn shielded_key_address_find_by_alias( + ctx: Context, + io: &impl Io, + alias: String, + keys_only: bool, + addresses_only: bool, + unsafe_show_secret: bool, +) { + let mut wallet = load_wallet(ctx); + let alias = alias.to_lowercase(); + if let Some(viewing_key) = (!addresses_only) + .then_some(()) + .and_then(|_| wallet.find_viewing_key(&alias).ok()) + { // Check if alias is a viewing key display_line!(io, "Viewing key: {}", viewing_key); if unsafe_show_secret { @@ -719,17 +873,20 @@ fn shielded_key_address_find( Err(err) => edisplay_line!(io, "{}", err), } } - } else if let Some(payment_addr) = wallet.find_payment_addr(&alias) { + } else if let Some(payment_addr) = (!keys_only) + .then_some(()) + .and_then(|_| wallet.find_payment_addr(&alias)) + { // Failing that, check if alias is a payment address display_line!(io, "Payment address: {}", payment_addr); - } else { + } else if !addresses_only && !keys_only { // Otherwise alias cannot be referring to any shielded value display_line!( io, - "No shielded address or key with alias {} found. Use the commands \ - `list-addr --shielded` and `list-keys --shielded` to see all the \ - known shielded addresses and keys.", - alias.to_lowercase() + "No shielded payment address or key with alias {} found. Use the \ + command `list --shielded` to see all the known shielded \ + addresses and keys.", + alias ); } } @@ -877,42 +1034,33 @@ fn transparent_addresses_list(ctx: Context, io: &impl Io) { } } -/// Find address (alias) by its alias (address). -fn address_or_alias_find( +/// Add a transparent secret key to the wallet. +fn transparent_secret_key_add( ctx: Context, io: &impl Io, - args::AddressFind { alias, address }: args::AddressFind, + alias: String, + alias_force: bool, + sk: common::SecretKey, + unsafe_dont_encrypt: bool, ) { - let wallet = load_wallet(ctx); - if address.is_some() && alias.is_some() { - panic!( - "This should not be happening: clap should emit its own error \ - message." - ); - } else if alias.is_some() { - let alias = alias.unwrap().to_lowercase(); - if let Some(address) = wallet.find_address(&alias) { - display_line!(io, "Found address {}", address.to_pretty_string()); - } else { - display_line!( - io, - "No address with alias {} found. Use the command `address \ - list` to see all the known addresses.", - alias - ); - } - } else if address.is_some() { - if let Some(alias) = wallet.find_alias(address.as_ref().unwrap()) { - display_line!(io, "Found alias {}", alias); - } else { - display_line!( - io, - "No alias with address {} found. Use the command `address \ - list` to see all the known addresses.", - address.unwrap() - ); - } - } + let mut wallet = load_wallet(ctx); + let encryption_password = + read_and_confirm_encryption_password(unsafe_dont_encrypt); + let alias = wallet + .insert_keypair(alias, alias_force, sk, encryption_password, None, None) + .unwrap_or_else(|err| { + edisplay_line!(io, "{}", err); + display_line!(io, "No changes are persisted. Exiting."); + cli::safe_exit(1); + }); + wallet + .save() + .unwrap_or_else(|err| edisplay_line!(io, "{}", err)); + display_line!( + io, + "Successfully added a key and an address with alias: \"{}\"", + alias + ); } /// Add an address to the wallet. diff --git a/sdk/src/args.rs b/sdk/src/args.rs index e21cba4e7b..c5c070baa4 100644 --- a/sdk/src/args.rs +++ b/sdk/src/args.rs @@ -13,6 +13,7 @@ use namada_core::types::dec::Dec; use namada_core::types::ethereum_events::EthAddress; use namada_core::types::keccak::KeccakHash; use namada_core::types::key::{common, SchemeType}; +use namada_core::types::masp::PaymentAddress; use namada_core::types::storage::Epoch; use namada_core::types::time::DateTimeUtc; use namada_core::types::transaction::GasLimit; @@ -2077,21 +2078,6 @@ pub struct KeyDerive { pub use_device: bool, } -/// Wallet key lookup arguments -#[derive(Clone, Debug)] -pub struct KeyFind { - /// Whether to find a MASP address by alias - pub shielded: bool, - /// Key alias to lookup keypair with - pub alias: Option, - /// Public key to lookup keypair with - pub public_key: Option, - /// Public key hash to lookup keypair with - pub value: Option, - /// Show secret keys to user - pub unsafe_show_secret: bool, -} - /// Wallet list arguments #[derive(Clone, Debug)] pub struct KeyAddressList { @@ -2107,15 +2093,28 @@ pub struct KeyAddressList { pub unsafe_show_secret: bool, } -/// Wallet address lookup arguments +/// Wallet key / address lookup arguments #[derive(Clone, Debug)] -pub struct AddressFind { +pub struct KeyAddressFind { + /// Whether to find MASP keys / addresses + pub shielded: bool, /// Alias to find pub alias: Option, /// Address to find pub address: Option
, + /// Public key to lookup keypair with + pub public_key: Option, + /// Public key hash to lookup keypair with + pub public_key_hash: Option, + /// Payment address to find + pub payment_address: Option, + /// Find keys only + pub keys_only: bool, + /// Find addresses only + pub addresses_only: bool, + /// Show secret keys to user + pub unsafe_show_secret: bool, } - /// Wallet key export arguments #[derive(Clone, Debug)] pub struct KeyExport { From 5e581ff2058514de8896ba1272e6fb4af5c610a1 Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Sat, 9 Dec 2023 21:01:51 +0100 Subject: [PATCH 079/216] Improve message --- apps/src/lib/cli/wallet.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index 7b98749867..ddedaf607e 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -154,7 +154,7 @@ fn payment_addresses_list(ctx: Context, io: &impl Io) { display_line!( io, "No known payment addresses. Try `gen-payment-addr --alias \ - my-addr` to generate a new payment address.", + my-payment-addr` to generate a new payment address.", ); } else { let stdout = io::stdout(); From 8750ded6c33ee76fba8aa53fb3b0a7b47cf6b773 Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Sat, 9 Dec 2023 21:02:42 +0100 Subject: [PATCH 080/216] Refactor parsing of `add` command value --- apps/src/lib/cli/wallet.rs | 71 +++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index ddedaf607e..706f94dfed 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -588,27 +588,43 @@ fn key_address_find( } } -/// Value for wallet `add` command -#[allow(clippy::large_enum_variant)] #[derive(Debug)] -pub enum KeyAddrAddValue { +pub enum TransparentValue { /// Transparent secret key TranspSecretKey(common::SecretKey), /// Transparent address TranspAddress(Address), - /// Masp value - MASPValue(MaspValue), } -impl FromStr for KeyAddrAddValue { +impl FromStr for TransparentValue { type Err = DecodeError; fn from_str(s: &str) -> Result { - // Try to decode this value first as a secret key, then as an address, - // then as a MASP value + // Try to decode this value first as a secret key, then as an address common::SecretKey::from_str(s) .map(Self::TranspSecretKey) .or_else(|_| Address::from_str(s).map(Self::TranspAddress)) + } +} + +/// Value for wallet `add` command +#[allow(clippy::large_enum_variant)] +#[derive(Debug)] +pub enum KeyAddrAddValue { + /// Transparent value + TranspValue(TransparentValue), + /// Masp value + MASPValue(MaspValue), +} + +impl FromStr for KeyAddrAddValue { + type Err = DecodeError; + + fn from_str(s: &str) -> Result { + // Try to decode this value first as a transparent value, then as a MASP + // value + TransparentValue::from_str(s) + .map(Self::TranspValue) .or_else(|_| MaspValue::from_str(s).map(Self::MASPValue)) } } @@ -622,36 +638,19 @@ fn add_key_or_address( unsafe_dont_encrypt: bool, ) { match value { - KeyAddrAddValue::TranspSecretKey(sk) => { - let mut wallet = load_wallet(ctx); - let encryption_password = - read_and_confirm_encryption_password(unsafe_dont_encrypt); - let alias = wallet - .insert_keypair( - alias, - alias_force, - sk, - encryption_password, - None, - None, - ) - .unwrap_or_else(|err| { - edisplay_line!(io, "{}", err); - display_line!(io, "No changes are persisted. Exiting."); - cli::safe_exit(1); - }); - wallet - .save() - .unwrap_or_else(|err| edisplay_line!(io, "{}", err)); - display_line!( + KeyAddrAddValue::TranspValue(TransparentValue::TranspSecretKey(sk)) => { + transparent_secret_key_add( + ctx, io, - "Successfully added a key and an address with alias: \"{}\"", - alias - ); - } - KeyAddrAddValue::TranspAddress(address) => { - transparent_address_add(ctx, io, alias, alias_force, address) + alias, + alias_force, + sk, + unsafe_dont_encrypt, + ) } + KeyAddrAddValue::TranspValue(TransparentValue::TranspAddress( + address, + )) => transparent_address_add(ctx, io, alias, alias_force, address), KeyAddrAddValue::MASPValue(masp_value) => shielded_key_address_add( ctx, io, From efc24e27498a18ed15f16cfe920200483367cded Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Sat, 9 Dec 2023 21:03:36 +0100 Subject: [PATCH 081/216] Improve comment --- apps/src/lib/cli/wallet.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index 706f94dfed..b0bd6ba8bb 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -1062,7 +1062,7 @@ fn transparent_secret_key_add( ); } -/// Add an address to the wallet. +/// Add a transparent address to the wallet. fn transparent_address_add( ctx: Context, io: &impl Io, From 31554aa3452d94a90bbd7540493c30f470e2bb38 Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Sun, 10 Dec 2023 17:49:20 +0100 Subject: [PATCH 082/216] Fix help message --- apps/src/lib/cli.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 78d42605fa..187b497b93 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -6216,7 +6216,7 @@ pub mod args { .required(true), ) .arg(LIST_FIND_KEYS_ONLY.def().help("Find keys only.")) - .arg(LIST_FIND_ADDRESSES_ONLY.def().help("List addresses only.")) + .arg(LIST_FIND_ADDRESSES_ONLY.def().help("Find addresses only.")) .group(ArgGroup::new("only_group").args([ LIST_FIND_KEYS_ONLY.name, LIST_FIND_ADDRESSES_ONLY.name, From e44fff3f061e5d9acb12646fbe5936c8c05e2d17 Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Sun, 10 Dec 2023 17:27:03 +0100 Subject: [PATCH 083/216] Adapt e2e tests for new wallet cli --- tests/src/e2e/helpers.rs | 4 +-- tests/src/e2e/ledger_tests.rs | 2 +- tests/src/e2e/wallet_tests.rs | 51 +++++++++++------------------------ 3 files changed, 18 insertions(+), 39 deletions(-) diff --git a/tests/src/e2e/helpers.rs b/tests/src/e2e/helpers.rs index 9fe5194da6..730f4f1a55 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -107,7 +107,7 @@ pub fn find_address(test: &Test, alias: impl AsRef) -> Result
{ let mut find = run!( test, Bin::Wallet, - &["address", "find", "--alias", alias.as_ref()], + &["find", "--addr", "--alias", alias.as_ref()], Some(10) )?; let (unread, matched) = find.exp_regex("Found address .*")?; @@ -245,8 +245,8 @@ pub fn find_keypair( test, Bin::Wallet, &[ - "key", "find", + "--keys", "--alias", alias.as_ref(), "--unsafe-show-secret" diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index e11317d4fe..06bb2667c9 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -2975,7 +2975,7 @@ fn implicit_account_reveal_pk() -> Result<()> { let mut cmd = run!( test, Bin::Wallet, - &["key", "gen", "--alias", &key_alias, "--unsafe-dont-encrypt"], + &["gen", "--alias", &key_alias, "--unsafe-dont-encrypt"], Some(20), )?; cmd.assert_success(); diff --git a/tests/src/e2e/wallet_tests.rs b/tests/src/e2e/wallet_tests.rs index 167d67202e..a79da138b8 100644 --- a/tests/src/e2e/wallet_tests.rs +++ b/tests/src/e2e/wallet_tests.rs @@ -28,12 +28,8 @@ fn wallet_encrypted_key_cmds() -> Result<()> { let password = "VeRySeCuR3"; // 1. key gen - let mut cmd = run!( - test, - Bin::Wallet, - &["key", "gen", "--alias", key_alias], - Some(20), - )?; + let mut cmd = + run!(test, Bin::Wallet, &["gen", "--alias", key_alias], Some(20),)?; cmd.exp_string("Enter your encryption password:")?; cmd.send_line(password)?; @@ -50,7 +46,7 @@ fn wallet_encrypted_key_cmds() -> Result<()> { let mut cmd = run!( test, Bin::Wallet, - &["key", "find", "--alias", key_alias], + &["find", "--keys", "--alias", key_alias], Some(20), )?; @@ -60,7 +56,7 @@ fn wallet_encrypted_key_cmds() -> Result<()> { cmd.exp_string("Public key:")?; // 3. key list - let mut cmd = run!(test, Bin::Wallet, &["key", "list"], Some(20))?; + let mut cmd = run!(test, Bin::Wallet, &["list", "--keys"], Some(20))?; cmd.exp_string(&format!( "Alias \"{}\" (encrypted):", key_alias.to_lowercase() @@ -82,12 +78,8 @@ fn wallet_encrypted_key_cmds_env_var() -> Result<()> { env::set_var("NAMADA_WALLET_PASSWORD", password); // 1. key gen - let mut cmd = run!( - test, - Bin::Wallet, - &["key", "gen", "--alias", key_alias], - Some(20), - )?; + let mut cmd = + run!(test, Bin::Wallet, &["gen", "--alias", key_alias], Some(20),)?; cmd.exp_string("Enter BIP39 passphrase (empty for none): ")?; cmd.send_line("")?; @@ -101,7 +93,7 @@ fn wallet_encrypted_key_cmds_env_var() -> Result<()> { let mut cmd = run!( test, Bin::Wallet, - &["key", "find", "--alias", key_alias], + &["find", "--keys", "--alias", key_alias], Some(20), )?; @@ -109,7 +101,7 @@ fn wallet_encrypted_key_cmds_env_var() -> Result<()> { cmd.exp_string("Public key:")?; // 3. key list - let mut cmd = run!(test, Bin::Wallet, &["key", "list"], Some(20))?; + let mut cmd = run!(test, Bin::Wallet, &["list", "--keys"], Some(20))?; cmd.exp_string(&format!("Alias \"{}\" (encrypted):", key_alias))?; Ok(()) @@ -128,7 +120,7 @@ fn wallet_unencrypted_key_cmds() -> Result<()> { let mut cmd = run!( test, Bin::Wallet, - &["key", "gen", "--alias", key_alias, "--unsafe-dont-encrypt"], + &["gen", "--alias", key_alias, "--unsafe-dont-encrypt"], Some(20), )?; cmd.exp_string(&format!( @@ -140,7 +132,7 @@ fn wallet_unencrypted_key_cmds() -> Result<()> { let mut cmd = run!( test, Bin::Wallet, - &["key", "find", "--alias", key_alias], + &["find", "--keys", "--alias", key_alias], Some(20), )?; @@ -148,7 +140,7 @@ fn wallet_unencrypted_key_cmds() -> Result<()> { cmd.exp_string("Public key:")?; // 3. key list - let mut cmd = run!(test, Bin::Wallet, &["key", "list"], Some(20))?; + let mut cmd = run!(test, Bin::Wallet, &["list", "--keys"], Some(20))?; cmd.exp_string(&format!("Alias \"{}\" (not encrypted):", key_alias))?; Ok(()) @@ -170,13 +162,7 @@ fn wallet_address_cmds() -> Result<()> { let mut cmd = run!( test, Bin::Wallet, - &[ - "address", - "gen", - "--alias", - gen_address_alias, - "--unsafe-dont-encrypt", - ], + &["gen", "--alias", gen_address_alias, "--unsafe-dont-encrypt"], Some(20), )?; cmd.exp_string(&format!( @@ -188,14 +174,7 @@ fn wallet_address_cmds() -> Result<()> { let mut cmd = run!( test, Bin::Wallet, - &[ - "address", - "add", - "--address", - add_address, - "--alias", - add_address_alias, - ], + &["add", "--value", add_address, "--alias", add_address_alias], Some(20), )?; cmd.exp_string(&format!( @@ -207,13 +186,13 @@ fn wallet_address_cmds() -> Result<()> { let mut cmd = run!( test, Bin::Wallet, - &["address", "find", "--alias", gen_address_alias], + &["find", "--addr", "--alias", gen_address_alias], Some(20), )?; cmd.exp_string("Found address")?; // 4. address list - let mut cmd = run!(test, Bin::Wallet, &["address", "list"], Some(20))?; + let mut cmd = run!(test, Bin::Wallet, &["list", "--addr"], Some(20))?; cmd.exp_string(&format!("\"{}\":", gen_address_alias))?; cmd.exp_string(&format!("\"{}\":", add_address_alias))?; From ddfd340c258c295a53ede3c9cb35363e2ae69ffc Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 16 Oct 2023 10:40:56 +0100 Subject: [PATCH 084/216] Add Ethereum bridge tx events --- core/src/types/ethereum_structs.rs | 61 ++++++++++++++++++++++++++++++ core/src/types/transaction/mod.rs | 3 ++ sdk/src/events/mod.rs | 36 ++++++++++++++++++ shared/src/ledger/protocol/mod.rs | 2 + 4 files changed, 102 insertions(+) diff --git a/core/src/types/ethereum_structs.rs b/core/src/types/ethereum_structs.rs index f029edc4b6..656357d062 100644 --- a/core/src/types/ethereum_structs.rs +++ b/core/src/types/ethereum_structs.rs @@ -9,6 +9,67 @@ pub use ethbridge_structs::*; use num256::Uint256; use serde::{Deserialize, Serialize}; +use crate::types::keccak::KeccakHash; + +/// Status of some Bridge pool transfer. +#[derive( + Hash, + Clone, + Debug, + Eq, + PartialEq, + Ord, + PartialOrd, + BorshSerialize, + BorshDeserialize, +)] +pub enum BpTransferStatus { + /// The transfer has been relayed. + Relayed, + /// The transfer has expired. + Expired, +} + +/// Ethereum bridge events on Namada's event log. +#[derive( + Hash, + Clone, + Debug, + Eq, + PartialEq, + Ord, + PartialOrd, + BorshSerialize, + BorshDeserialize, +)] +pub enum EthBridgeEvent { + /// Bridge pool transfer status update event. + BridgePool { + /// Hash of the Bridge pool transfer. + tx_hash: KeccakHash, + /// Status of the Bridge pool transfer. + status: BpTransferStatus, + }, +} + +impl EthBridgeEvent { + /// Return a new Bridge pool expired transfer event. + pub const fn new_bridge_pool_expired(tx_hash: KeccakHash) -> Self { + Self::BridgePool { + tx_hash, + status: BpTransferStatus::Expired, + } + } + + /// Return a new Bridge pool relayed transfer event. + pub const fn new_bridge_pool_relayed(tx_hash: KeccakHash) -> Self { + Self::BridgePool { + tx_hash, + status: BpTransferStatus::Relayed, + } + } +} + /// This type must be able to represent any valid Ethereum block height. It must /// also be Borsh serializeable, so that it can be stored in blockchain storage. /// diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index 1ab89ba61f..6646e74c09 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -28,6 +28,7 @@ pub use wrapper::*; use crate::ledger::gas::{Gas, VpsGas}; use crate::types::address::Address; +use crate::types::ethereum_structs::EthBridgeEvent; use crate::types::hash::Hash; use crate::types::ibc::IbcEvent; use crate::types::storage; @@ -53,6 +54,8 @@ pub struct TxResult { pub initialized_accounts: Vec
, /// IBC events emitted by the transaction pub ibc_events: BTreeSet, + /// Ethereum bridge events emitted by the transaction + pub eth_bridge_events: BTreeSet, } impl TxResult { diff --git a/sdk/src/events/mod.rs b/sdk/src/events/mod.rs index 3ebd5dcae8..8c5dc8573f 100644 --- a/sdk/src/events/mod.rs +++ b/sdk/src/events/mod.rs @@ -8,6 +8,7 @@ use std::ops::{Index, IndexMut}; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; +use namada_core::types::ethereum_structs::{BpTransferStatus, EthBridgeEvent}; use namada_core::types::ibc::IbcEvent; use namada_core::types::transaction::TxType; use serde_json::Value; @@ -16,6 +17,37 @@ use serde_json::Value; use crate::error::{EncodingError, Error, EventError}; use crate::tendermint_proto::v0_37::abci::EventAttribute; +impl From for Event { + #[inline] + fn from(event: EthBridgeEvent) -> Event { + Self::from(&event) + } +} + +impl From<&EthBridgeEvent> for Event { + fn from(event: &EthBridgeEvent) -> Event { + match event { + EthBridgeEvent::BridgePool { tx_hash, status } => Event { + event_type: EventType::EthereumBridge, + level: EventLevel::Tx, + attributes: { + let mut attrs = HashMap::new(); + attrs.insert( + "kind".into(), + match status { + BpTransferStatus::Relayed => "bridge_pool_relayed", + BpTransferStatus::Expired => "bridge_pool_expired", + } + .into(), + ); + attrs.insert("tx_hash".into(), tx_hash.to_string()); + attrs + }, + }, + } + } +} + /// Indicates if an event is emitted do to /// an individual Tx or the nature of a finalized block #[derive(Clone, Debug, Eq, PartialEq, BorshSerialize, BorshDeserialize)] @@ -52,6 +84,8 @@ pub enum EventType { Proposal, /// The pgf payment PgfPayment, + /// Ethereum Bridge event + EthereumBridge, } impl Display for EventType { @@ -62,6 +96,7 @@ impl Display for EventType { EventType::Ibc(t) => write!(f, "{}", t), EventType::Proposal => write!(f, "proposal"), EventType::PgfPayment => write!(f, "pgf_payment"), + EventType::EthereumBridge => write!(f, "ethereum_bridge"), }?; Ok(()) } @@ -82,6 +117,7 @@ impl FromStr for EventType { "write_acknowledgement" => { Ok(EventType::Ibc("write_acknowledgement".to_string())) } + "ethereum_bridge" => Ok(EventType::EthereumBridge), _ => Err(EventError::InvalidEventType), } } diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index 3dd3a8710d..082c83d78e 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -194,6 +194,7 @@ where vps_result: VpsResult::default(), initialized_accounts: vec![], ibc_events: BTreeSet::default(), + eth_bridge_events: BTreeSet::default(), }) } TxType::Decrypted(DecryptedTx::Undecryptable) => { @@ -608,6 +609,7 @@ where vps_result, initialized_accounts, ibc_events, + eth_bridge_events: BTreeSet::default(), }) } From 7108d5c15e83e696f8b819add78fc9ea577fc19c Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 11 Dec 2023 14:35:42 +0100 Subject: [PATCH 085/216] Stricter checks on sapling bundle components --- shared/src/ledger/native_vp/masp.rs | 53 ++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/shared/src/ledger/native_vp/masp.rs b/shared/src/ledger/native_vp/masp.rs index b7efededcc..57b159408a 100644 --- a/shared/src/ledger/native_vp/masp.rs +++ b/shared/src/ledger/native_vp/masp.rs @@ -130,10 +130,20 @@ where transaction: &Transaction, ) -> Result { let mut revealed_nullifiers = HashSet::new(); - for description in transaction - .sapling_bundle() - .map_or(&vec![], |description| &description.shielded_spends) - { + let shielded_spends = match transaction.sapling_bundle() { + Some(bundle) if !bundle.shielded_spends.is_empty() => { + &bundle.shielded_spends + } + _ => { + tracing::debug!( + "Missing expected spend descriptions in shielded \ + transaction" + ); + return Ok(false); + } + }; + + for description in shielded_spends { let nullifier_key = Key::from(MASP.to_db_key()) .push(&MASP_NULLIFIERS_KEY.to_owned()) .expect("Cannot obtain a storage key") @@ -227,10 +237,20 @@ where &self, transaction: &Transaction, ) -> Result { - for description in transaction - .sapling_bundle() - .map_or(&vec![], |bundle| &bundle.shielded_spends) - { + let shielded_spends = match transaction.sapling_bundle() { + Some(bundle) if !bundle.shielded_spends.is_empty() => { + &bundle.shielded_spends + } + _ => { + tracing::debug!( + "Missing expected spend descriptions in shielded \ + transaction" + ); + return Ok(false); + } + }; + + for description in shielded_spends { let anchor_key = Key::from(MASP.to_db_key()) .push(&MASP_NOTE_COMMITMENT_ANCHOR_PREFIX.to_owned()) .expect("Cannot obtain a storage key") @@ -406,6 +426,14 @@ where // Non-masp sources add to transparent tx pool transparent_tx_pool += transp_amt; } + + // No shielded spends nor shielded converts are allowed + if shielded_tx.sapling_bundle().is_some_and(|bundle| { + !(bundle.shielded_spends.is_empty() + && bundle.shielded_converts.is_empty()) + }) { + return Ok(false); + } } else { // Handle shielded input // The following boundary conditions must be satisfied @@ -532,6 +560,7 @@ where // Handle shielded output // The following boundary conditions must be satisfied // 1. Zero transparent output + // 2. At least one shielded output // Satisfies 1. if let Some(transp_bundle) = shielded_tx.transparent_bundle() { @@ -544,6 +573,14 @@ where return Ok(false); } } + + // Staisfies 2. + if shielded_tx + .sapling_bundle() + .is_some_and(|bundle| bundle.shielded_outputs.is_empty()) + { + return Ok(false); + } } match transparent_tx_pool.partial_cmp(&I128Sum::zero()) { From f70fe9c4eadfa60fe168b12acf3143357e7e5b64 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 16 Oct 2023 12:08:00 +0100 Subject: [PATCH 086/216] RPC method to query status of selected Bridge pool transfers --- sdk/src/queries/mod.rs | 2 +- sdk/src/queries/shell/eth_bridge.rs | 129 +++++++++++++++++++++++++++- 2 files changed, 129 insertions(+), 2 deletions(-) diff --git a/sdk/src/queries/mod.rs b/sdk/src/queries/mod.rs index 354cd5b6d0..4dbc5173b8 100644 --- a/sdk/src/queries/mod.rs +++ b/sdk/src/queries/mod.rs @@ -16,7 +16,7 @@ use vp::{Vp, VP}; pub use self::shell::eth_bridge::{ Erc20FlowControl, GenBridgePoolProofReq, GenBridgePoolProofRsp, - TransferToErcArgs, + TransferToErcArgs, TransferToEthereumStatus, }; use crate::{MaybeSend, MaybeSync}; diff --git a/sdk/src/queries/shell/eth_bridge.rs b/sdk/src/queries/shell/eth_bridge.rs index b7a760126d..7dbd693fe1 100644 --- a/sdk/src/queries/shell/eth_bridge.rs +++ b/sdk/src/queries/shell/eth_bridge.rs @@ -1,11 +1,12 @@ //! Ethereum bridge related shell queries. use std::borrow::Cow; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; use borsh_ext::BorshSerializeExt; +use namada_core::hints; use namada_core::ledger::eth_bridge::storage::bridge_pool::get_key_from_hash; use namada_core::ledger::storage::merkle_tree::StoreRef; use namada_core::ledger::storage::{DBIter, StorageHasher, StoreType, DB}; @@ -42,8 +43,35 @@ use namada_ethereum_bridge::storage::{ use namada_proof_of_stake::pos_queries::PosQueries; use crate::eth_bridge::ethers::abi::AbiDecode; +use crate::events::EventType; use crate::queries::{EncodedResponseQuery, RequestCtx, RequestQuery}; +/// Container for the status of queried transfers to Ethereum. +#[derive( + Default, Debug, Clone, Eq, PartialEq, BorshSerialize, BorshDeserialize, +)] +pub struct TransferToEthereumStatus { + /// The block height at which the query was performed. + /// + /// This value may be used to busy wait while a Bridge pool + /// proof is being constructed for it, such that clients can + /// safely perform additional actions. + pub queried_height: BlockHeight, + /// Transfers in the query whose status it was determined + /// to be `pending`. + pub pending: HashSet, + /// Transfers in the query whose status it was determined + /// to be `relayed`. + pub relayed: HashSet, + /// Transfers in the query whose status it was determined + /// to be `expired`. + pub expired: HashSet, + /// Hashes pertaining to bogus data that might have been queried, + /// or transfers that were not in the event log, despite having + /// been relayed to Ethereum or expiring from the Bridge pool. + pub unrecognized: HashSet, +} + /// Contains information about the flow control of some ERC20 /// wrapped asset. #[derive( @@ -129,6 +157,11 @@ router! {ETH_BRIDGE, -> HashMap = transfer_to_ethereum_progress, + // Given a list of keccak hashes, check whether they have been + // relayed, expired or if they are still pending. + ( "pool" / "transfer_status" ) + -> TransferToEthereumStatus = (with_options pending_eth_transfer_status), + // Request a proof of a validator set signed off for // the given epoch. // @@ -175,6 +208,100 @@ router! {ETH_BRIDGE, -> Erc20FlowControl = get_erc20_flow_control, } +/// Given a list of keccak hashes, check whether they have been +/// relayed, expired or if they are still pending. +fn pending_eth_transfer_status( + ctx: RequestCtx<'_, D, H, V, T>, + request: &RequestQuery, +) -> storage_api::Result +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + let mut status = TransferToEthereumStatus { + queried_height: ctx.wl_storage.storage.get_last_block_height(), + ..Default::default() + }; + + let transfer_hashes: HashSet = + BorshDeserialize::try_from_slice(request.data.as_slice()) + .into_storage_result()?; + + // check which transfers in the Bridge pool match the requested hashes + let merkle_tree = ctx + .wl_storage + .storage + .get_merkle_tree( + ctx.wl_storage.storage.get_last_block_height(), + Some(StoreType::BridgePool), + ) + .expect("We should always be able to read the database"); + let stores = merkle_tree.stores(); + let store = match stores.store(&StoreType::BridgePool) { + StoreRef::BridgePool(store) => store, + _ => unreachable!(), + }; + if hints::likely(store.len() > transfer_hashes.len()) { + for hash in transfer_hashes.iter() { + if store.contains_key(hash) { + status.pending.insert(hash.clone()); + } + } + } else { + for hash in store.keys() { + if transfer_hashes.contains(hash) { + status.pending.insert(hash.clone()); + } + } + } + + // INVARIANT: transfers that are in the event log will have already + // been processed and therefore removed from the Bridge pool at the + // time of this query + let kind_key: String = "kind".into(); + let completed_transfers = ctx.event_log.iter().filter_map(|ev| { + if !matches!(&ev.event_type, EventType::EthereumBridge) { + return None; + } + let eth_event_kind = + ev.attributes.get(&kind_key).map(|k| k.as_str())?; + let is_relayed = match eth_event_kind { + "bridge_pool_relayed" => true, + "bridge_pool_expired" => false, + _ => return None, + }; + let tx_hash: KeccakHash = ev + .attributes + .get("tx_hash") + .expect("The transfer hash must be available") + .as_str() + .try_into() + .expect("We must have a valid KeccakHash"); + if !transfer_hashes.contains(&tx_hash) { + return None; + } + Some((tx_hash, is_relayed)) + }); + for (hash, is_relayed) in completed_transfers { + if hints::likely(is_relayed) { + status.relayed.insert(hash.clone()); + } else { + status.expired.insert(hash.clone()); + } + } + + let status = { + // any remaining transfers are returned as + // unrecognized hashes + status.unrecognized = transfer_hashes; + status + }; + Ok(EncodedResponseQuery { + data: status.serialize_to_vec(), + ..Default::default() + }) +} + /// Read the total supply and respective cap of some wrapped /// ERC20 token in Namada. fn get_erc20_flow_control( From ed44f9a0bc8cb1d781b15106c327c0bb73b3f2aa Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 17 Oct 2023 11:09:36 +0100 Subject: [PATCH 087/216] Optimize Bridge pool transfer status RPC query Avoids going through the entirety of the event log or Bridge pool, if all the requested transfer hashes have already been processed. --- sdk/src/queries/shell/eth_bridge.rs | 45 ++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/sdk/src/queries/shell/eth_bridge.rs b/sdk/src/queries/shell/eth_bridge.rs index 7dbd693fe1..a9d9eee280 100644 --- a/sdk/src/queries/shell/eth_bridge.rs +++ b/sdk/src/queries/shell/eth_bridge.rs @@ -218,15 +218,19 @@ where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { + let mut transfer_hashes: HashSet = + BorshDeserialize::try_from_slice(&request.data) + .into_storage_result()?; + + if transfer_hashes.is_empty() { + return Ok(Default::default()); + } + let mut status = TransferToEthereumStatus { queried_height: ctx.wl_storage.storage.get_last_block_height(), ..Default::default() }; - let transfer_hashes: HashSet = - BorshDeserialize::try_from_slice(request.data.as_slice()) - .into_storage_result()?; - // check which transfers in the Bridge pool match the requested hashes let merkle_tree = ctx .wl_storage @@ -242,19 +246,32 @@ where _ => unreachable!(), }; if hints::likely(store.len() > transfer_hashes.len()) { - for hash in transfer_hashes.iter() { - if store.contains_key(hash) { + transfer_hashes.retain(|hash| { + let transfer_in_pool = store.contains_key(hash); + if transfer_in_pool { status.pending.insert(hash.clone()); } - } + !transfer_in_pool + }); } else { for hash in store.keys() { - if transfer_hashes.contains(hash) { + if transfer_hashes.remove(hash) { status.pending.insert(hash.clone()); } + if transfer_hashes.is_empty() { + break; + } } } + if transfer_hashes.is_empty() { + let data = status.serialize_to_vec(); + return Ok(EncodedResponseQuery { + data, + ..Default::default() + }); + } + // INVARIANT: transfers that are in the event log will have already // been processed and therefore removed from the Bridge pool at the // time of this query @@ -277,17 +294,23 @@ where .as_str() .try_into() .expect("We must have a valid KeccakHash"); - if !transfer_hashes.contains(&tx_hash) { + if !transfer_hashes.remove(&tx_hash) { return None; } - Some((tx_hash, is_relayed)) + Some((tx_hash, is_relayed, transfer_hashes.is_empty())) }); - for (hash, is_relayed) in completed_transfers { + for (hash, is_relayed, early_exit) in completed_transfers { if hints::likely(is_relayed) { status.relayed.insert(hash.clone()); } else { status.expired.insert(hash.clone()); } + if early_exit { + // early drop of the transfer hashes, in + // case its storage capacity was big + transfer_hashes = Default::default(); + break; + } } let status = { From e797df5d3e75fd85678127f359f39be18ae006e5 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 16 Oct 2023 15:38:20 +0100 Subject: [PATCH 088/216] Test Bridge pool transfer status --- sdk/src/queries/shell/eth_bridge.rs | 126 ++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/sdk/src/queries/shell/eth_bridge.rs b/sdk/src/queries/shell/eth_bridge.rs index a9d9eee280..b3cefd1f49 100644 --- a/sdk/src/queries/shell/eth_bridge.rs +++ b/sdk/src/queries/shell/eth_bridge.rs @@ -1709,6 +1709,132 @@ mod test_ethbridge_router { Ok(f) if f.supply == supply_amount && f.cap == cap_amount ); } + + /// Test that querying the status of the Bridge pool + /// returns the expected keccak hashes. + #[tokio::test] + async fn test_bridge_pool_status() { + let mut client = TestClient::new(RPC); + + // write a transfer into the bridge pool + let transfer = PendingTransfer { + transfer: TransferToEthereum { + kind: TransferToEthereumKind::Erc20, + asset: EthAddress([0; 20]), + recipient: EthAddress([0; 20]), + sender: bertha_address(), + amount: 0.into(), + }, + gas_fee: GasFee { + token: nam(), + amount: 0.into(), + payer: bertha_address(), + }, + }; + client + .wl_storage + .write_bytes( + &get_pending_key(&transfer), + transfer.serialize_to_vec(), + ) + .expect("Test failed"); + + // write transfers into the event log + let mut transfer2 = transfer.clone(); + transfer2.transfer.amount = 1.into(); + let mut transfer3 = transfer.clone(); + transfer3.transfer.amount = 2.into(); + client.event_log.log_events(vec![ + ethereum_structs::EthBridgeEvent::BridgePool { + tx_hash: transfer2.keccak256(), + status: ethereum_structs::BpTransferStatus::Expired, + } + .into(), + ethereum_structs::EthBridgeEvent::BridgePool { + tx_hash: transfer3.keccak256(), + status: ethereum_structs::BpTransferStatus::Relayed, + } + .into(), + ]); + + // some arbitrary transfer - since it's neither in the + // Bridge pool nor in the event log, it is assumed it has + // either been relayed or that it has expired + let mut transfer4 = transfer.clone(); + transfer4.transfer.amount = 3.into(); + + // change block height + client.wl_storage.storage.block.height = 1.into(); + + // write bridge pool signed root + { + let signed_root = BridgePoolRootProof { + signatures: Default::default(), + data: (KeccakHash([0; 32]), 0.into()), + }; + let written_height = client.wl_storage.storage.block.height; + client + .wl_storage + .write_bytes( + &get_signed_root_key(), + (signed_root, written_height).serialize_to_vec(), + ) + .expect("Test failed"); + client + .wl_storage + .storage + .commit_block(MockDBWriteBatch) + .expect("Test failed"); + } + + // commit storage changes + client.wl_storage.commit_block().expect("Test failed"); + + // check transfer statuses + let status = RPC + .shell() + .eth_bridge() + .pending_eth_transfer_status( + &client, + Some( + { + let mut req = HashSet::new(); + req.insert(transfer.keccak256()); + req.insert(transfer2.keccak256()); + req.insert(transfer3.keccak256()); + req.insert(transfer4.keccak256()); + req + } + .serialize_to_vec(), + ), + None, + false, + ) + .await + .unwrap() + .data; + + assert_eq!( + status.pending, + HashSet::from([transfer.keccak256()]), + "unexpected pending transfers" + ); + assert_eq!( + status.expired, + HashSet::from([transfer2.keccak256()]), + "unexpected expired transfers" + ); + assert_eq!( + status.relayed, + HashSet::from([transfer3.keccak256()]), + "unexpected relayed transfers" + ); + assert_eq!( + status.unrecognized, + HashSet::from([transfer4.keccak256()]), + "unexpected unrecognized transfers" + ); + } } #[cfg(any(feature = "testing", test))] From 476a42758b87c3bfe75101947128940e162cbfc9 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 17 Oct 2023 10:09:53 +0100 Subject: [PATCH 089/216] Emit Bridge pool tx events --- .../transactions/ethereum_events/events.rs | 40 ++++++++++++++----- .../transactions/ethereum_events/mod.rs | 23 ++++++----- 2 files changed, 42 insertions(+), 21 deletions(-) diff --git a/ethereum_bridge/src/protocol/transactions/ethereum_events/events.rs b/ethereum_bridge/src/protocol/transactions/ethereum_events/events.rs index fa2be67104..898f55fff4 100644 --- a/ethereum_bridge/src/protocol/transactions/ethereum_events/events.rs +++ b/ethereum_bridge/src/protocol/transactions/ethereum_events/events.rs @@ -18,6 +18,7 @@ use namada_core::ledger::storage::traits::StorageHasher; use namada_core::ledger::storage::{DBIter, WlStorage, DB}; use namada_core::ledger::storage_api::{StorageRead, StorageWrite}; use namada_core::types::address::Address; +use namada_core::types::eth_abi::Encode; use namada_core::types::eth_bridge_pool::{ PendingTransfer, TransferToEthereumKind, }; @@ -25,6 +26,7 @@ use namada_core::types::ethereum_events::{ EthAddress, EthereumEvent, TransferToEthereum, TransferToNamada, TransfersToNamada, }; +use namada_core::types::ethereum_structs::EthBridgeEvent; use namada_core::types::storage::{BlockHeight, Key, KeySeg}; use namada_core::types::token; use namada_core::types::token::{balance_key, minted_balance_key}; @@ -39,7 +41,7 @@ use crate::storage::eth_bridge_queries::{EthAssetMint, EthBridgeQueries}; pub(super) fn act_on( wl_storage: &mut WlStorage, event: EthereumEvent, -) -> Result> +) -> Result<(BTreeSet, BTreeSet)> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, @@ -58,7 +60,7 @@ where } => act_on_transfers_to_eth(wl_storage, transfers, relayer), _ => { tracing::debug!(?event, "No actions taken for Ethereum event"); - Ok(BTreeSet::default()) + Ok(Default::default()) } } } @@ -66,7 +68,7 @@ where fn act_on_transfers_to_namada<'tx, D, H>( wl_storage: &mut WlStorage, transfer_event: TransfersToNamada, -) -> Result> +) -> Result<(BTreeSet, BTreeSet)> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, @@ -88,7 +90,11 @@ where transfers.iter(), )?; } - Ok(changed_keys) + Ok(( + changed_keys, + // no tx events when we get a transfer to namada + BTreeSet::new(), + )) } fn update_transfers_to_namada_state<'tx, D, H>( @@ -301,13 +307,14 @@ fn act_on_transfers_to_eth( wl_storage: &mut WlStorage, transfers: &[TransferToEthereum], relayer: &Address, -) -> Result> +) -> Result<(BTreeSet, BTreeSet)> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { tracing::debug!(?transfers, "Acting on transfers to Ethereum"); let mut changed_keys = BTreeSet::default(); + let mut tx_events = BTreeSet::default(); // the BP nonce should always be incremented, even if no valid // transfers to Ethereum were relayed. failing to do this @@ -363,10 +370,13 @@ where _ = changed_keys.insert(key); _ = changed_keys.insert(pool_balance_key); _ = changed_keys.insert(relayer_rewards_key); + _ = tx_events.insert(EthBridgeEvent::new_bridge_pool_relayed( + pending_transfer.keccak256(), + )); } if pending_keys.is_empty() { - return Ok(changed_keys); + return Ok((changed_keys, tx_events)); } // TODO the timeout height is min_num_blocks of an epoch for now @@ -383,13 +393,15 @@ where ) .expect("BlockHeight should be decoded"); if inserted_height <= timeout_height { - let mut keys = refund_transfer(wl_storage, key)?; + let (mut keys, mut new_tx_events) = + refund_transfer(wl_storage, key)?; changed_keys.append(&mut keys); + tx_events.append(&mut new_tx_events); } } } - Ok(changed_keys) + Ok((changed_keys, tx_events)) } fn increment_bp_nonce( @@ -412,12 +424,13 @@ where fn refund_transfer( wl_storage: &mut WlStorage, key: Key, -) -> Result> +) -> Result<(BTreeSet, BTreeSet)> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { let mut changed_keys = BTreeSet::default(); + let mut tx_events = BTreeSet::default(); let transfer = match wl_storage.read_bytes(&key)? { Some(v) => PendingTransfer::try_from_slice(&v[..])?, @@ -430,7 +443,12 @@ where wl_storage.delete(&key)?; _ = changed_keys.insert(key); - Ok(changed_keys) + // Emit expiration event + _ = tx_events.insert(EthBridgeEvent::new_bridge_pool_expired( + transfer.keccak256(), + )); + + Ok((changed_keys, tx_events)) } fn refund_transfer_fees( @@ -1034,7 +1052,7 @@ mod tests { .expect("Test failed"), ) .expect("Test failed"); - let mut changed_keys = act_on(&mut wl_storage, event).unwrap(); + let (mut changed_keys, _) = act_on(&mut wl_storage, event).unwrap(); for erc20 in [ random_erc20_token, diff --git a/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs b/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs index e9ff768fa0..54df928d04 100644 --- a/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs +++ b/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs @@ -12,6 +12,7 @@ use namada_core::ledger::storage::traits::StorageHasher; use namada_core::ledger::storage::{DBIter, WlStorage, DB}; use namada_core::types::address::Address; use namada_core::types::ethereum_events::EthereumEvent; +use namada_core::types::ethereum_structs::EthBridgeEvent; use namada_core::types::internal::ExpiredTx; use namada_core::types::storage::{BlockHeight, Epoch, Key}; use namada_core::types::token::Amount; @@ -77,14 +78,13 @@ where let voting_powers = utils::get_voting_powers(wl_storage, &updates)?; - changed_keys.append(&mut apply_updates( - wl_storage, - updates, - voting_powers, - )?); + let (mut apply_updates_keys, eth_bridge_events) = + apply_updates(wl_storage, updates, voting_powers)?; + changed_keys.append(&mut apply_updates_keys); Ok(TxResult { changed_keys, + eth_bridge_events, ..Default::default() }) } @@ -98,7 +98,7 @@ pub(super) fn apply_updates( wl_storage: &mut WlStorage, updates: HashSet, voting_powers: HashMap<(Address, BlockHeight), Amount>, -) -> Result +) -> Result<(ChangedKeys, BTreeSet)> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, @@ -110,6 +110,7 @@ where ); let mut changed_keys = BTreeSet::default(); + let mut tx_events = BTreeSet::default(); let mut confirmed = vec![]; for update in updates { // The order in which updates are applied to storage does not matter. @@ -123,17 +124,19 @@ where } if confirmed.is_empty() { tracing::debug!("No events were newly confirmed"); - return Ok(changed_keys); + return Ok((changed_keys, tx_events)); } tracing::debug!(n = confirmed.len(), "Events were newly confirmed",); // Right now, the order in which events are acted on does not matter. // For `TransfersToNamada` events, they can happen in any order. for event in confirmed { - let mut changed = events::act_on(wl_storage, event)?; + let (mut changed, mut new_tx_events) = + events::act_on(wl_storage, event)?; changed_keys.append(&mut changed); + tx_events.append(&mut new_tx_events); } - Ok(changed_keys) + Ok((changed_keys, tx_events)) } /// Apply an [`EthMsgUpdate`] to storage. Returns any keys changed and whether @@ -366,7 +369,7 @@ mod tests { )], ); - let changed_keys = + let (changed_keys, _) = apply_updates(&mut wl_storage, updates, voting_powers)?; let eth_msg_keys: vote_tallies::Keys = (&body).into(); From b2455c2cf7b0214423d5c02d2e5925ad521e4703 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 17 Oct 2023 10:23:01 +0100 Subject: [PATCH 090/216] Return Ethereum bridge events from FinalizeBlock --- .../lib/node/ledger/shell/finalize_block.rs | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 56c259ccbd..80532500d9 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -446,13 +446,28 @@ where .results .accept(tx_index); } - for ibc_event in &result.ibc_events { - // Add the IBC event besides the tx_event - let mut event = Event::from(ibc_event.clone()); - // Add the height for IBC event query - event["height"] = height.to_string(); - response.events.push(event); - } + // events from other sources + response.events.extend( + // ibc events + result + .ibc_events + .iter() + .cloned() + .map(|ibc_event| { + // Add the IBC event besides the tx_event + let mut event = Event::from(ibc_event); + // Add the height for IBC event query + event["height"] = height.to_string(); + event + }) + // eth bridge events + .chain( + result + .eth_bridge_events + .iter() + .map(Event::from), + ), + ); match serde_json::to_string( &result.initialized_accounts, ) { From be58e50e38f3f3b1030be823d8effacc849ba13b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 19 Oct 2023 10:17:24 +0100 Subject: [PATCH 091/216] Implement serde serialization for transfer to eth statuses --- core/src/types/keccak.rs | 56 ++++++++++++++++++++++++++--- sdk/src/queries/shell/eth_bridge.rs | 11 +++++- 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/core/src/types/keccak.rs b/core/src/types/keccak.rs index 333646160e..f6fc15724d 100644 --- a/core/src/types/keccak.rs +++ b/core/src/types/keccak.rs @@ -2,12 +2,12 @@ //! hash function in a way that is compatible with smart contracts //! on Ethereum. use std::convert::{TryFrom, TryInto}; -use std::fmt::Display; +use std::fmt; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use data_encoding::HEXUPPER; use ethabi::Token; -use serde::{Serialize, Serializer}; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use thiserror::Error; pub use tiny_keccak::{Hasher, Keccak}; @@ -51,8 +51,8 @@ impl KeccakHash { } } -impl Display for KeccakHash { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for KeccakHash { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for byte in &self.0 { write!(f, "{:02X}", byte)?; } @@ -118,6 +118,34 @@ impl Serialize for KeccakHash { } } +impl<'de> Deserialize<'de> for KeccakHash { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct KeccakVisitor; + + impl<'de> de::Visitor<'de> for KeccakVisitor { + type Value = KeccakHash; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "a string containing a keccak hash") + } + + fn visit_str(self, s: &str) -> Result + where + E: de::Error, + { + KeccakHash::try_from(s).map_err(|_| { + de::Error::invalid_value(de::Unexpected::Str(s), &self) + }) + } + } + + deserializer.deserialize_str(KeccakVisitor) + } +} + /// Hash bytes using Keccak pub fn keccak_hash>(bytes: T) -> KeccakHash { let mut output = [0; 32]; @@ -134,3 +162,23 @@ impl Encode<1> for KeccakHash { [Token::FixedBytes(self.0.to_vec())] } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_keccak_serde_roundtrip() { + let mut hash = KeccakHash([0; 32]); + + for i in 0..32 { + hash.0[i] = i as u8; + } + + let serialized = serde_json::to_string(&hash).unwrap(); + let deserialized: KeccakHash = + serde_json::from_str(&serialized).unwrap(); + + assert_eq!(deserialized, hash); + } +} diff --git a/sdk/src/queries/shell/eth_bridge.rs b/sdk/src/queries/shell/eth_bridge.rs index b3cefd1f49..03fd2b0851 100644 --- a/sdk/src/queries/shell/eth_bridge.rs +++ b/sdk/src/queries/shell/eth_bridge.rs @@ -41,6 +41,7 @@ use namada_ethereum_bridge::storage::{ bridge_contract_key, native_erc20_key, vote_tallies, }; use namada_proof_of_stake::pos_queries::PosQueries; +use serde::{Deserialize, Serialize}; use crate::eth_bridge::ethers::abi::AbiDecode; use crate::events::EventType; @@ -48,7 +49,15 @@ use crate::queries::{EncodedResponseQuery, RequestCtx, RequestQuery}; /// Container for the status of queried transfers to Ethereum. #[derive( - Default, Debug, Clone, Eq, PartialEq, BorshSerialize, BorshDeserialize, + Default, + Debug, + Clone, + Eq, + PartialEq, + BorshSerialize, + BorshDeserialize, + Serialize, + Deserialize, )] pub struct TransferToEthereumStatus { /// The block height at which the query was performed. From 25dab56a8ccdf730bfe32f5ca7cc92dae1199ca9 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 17 Oct 2023 12:49:51 +0100 Subject: [PATCH 092/216] SDK method to query status of selected Bridge pool transfers --- sdk/src/eth_bridge/bridge_pool.rs | 35 +++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/sdk/src/eth_bridge/bridge_pool.rs b/sdk/src/eth_bridge/bridge_pool.rs index 639257fc70..030ea5f488 100644 --- a/sdk/src/eth_bridge/bridge_pool.rs +++ b/sdk/src/eth_bridge/bridge_pool.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use std::cmp::Ordering; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::sync::Arc; use borsh_ext::BorshSerializeExt; @@ -36,7 +36,7 @@ use crate::io::Io; use crate::proto::Tx; use crate::queries::{ Client, GenBridgePoolProofReq, GenBridgePoolProofRsp, TransferToErcArgs, - RPC, + TransferToEthereumStatus, RPC, }; use crate::rpc::{query_storage_value, query_wasm_code_hash, validate_amount}; use crate::signing::aux_signing_data; @@ -700,6 +700,37 @@ where Ok(()) } +/// Query the status of a set of transfers to Ethreum, indexed +/// by their keccak hash. +/// +/// Any unrecognized hashes (i.e. not pertaining to transfers +/// in Namada's event log nor the Bridge pool) will be flagged +/// as such. Unrecognized transfers could have been relayed to +/// Ethereum, or could have expired from the Bridge pool. If +/// these scenarios verify, it should be possible to retrieve +/// the status of these transfers by querying CometBFT's block +/// data. +pub async fn query_eth_transfer_status( + client: &C, + transfers: T, +) -> Result +where + C: Client + Sync, + T: Into>, +{ + RPC.shell() + .eth_bridge() + .pending_eth_transfer_status( + client, + Some(transfers.into().serialize_to_vec()), + None, + false, + ) + .await + .map_err(|e| Error::Query(QueryError::General(e.to_string()))) + .map(|result| result.data) +} + mod recommendations { use std::collections::BTreeSet; From 61026c650edcd7c8f0dbfd77090fadc8acb4fa88 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 17 Oct 2023 12:51:56 +0100 Subject: [PATCH 093/216] SDK changelog update --- .changelog/unreleased/SDK/1995-eth-tx-emits-events.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/SDK/1995-eth-tx-emits-events.md diff --git a/.changelog/unreleased/SDK/1995-eth-tx-emits-events.md b/.changelog/unreleased/SDK/1995-eth-tx-emits-events.md new file mode 100644 index 0000000000..2ce8e5780b --- /dev/null +++ b/.changelog/unreleased/SDK/1995-eth-tx-emits-events.md @@ -0,0 +1,2 @@ +- Introduce a method to query the status (pending, relayed or expired) of Bridge + pool transfers ([\#1995](https://github.com/anoma/namada/pull/1995)) \ No newline at end of file From 379f750b2bcc9526d997db6f12bf358f1cbd5535 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 17 Oct 2023 12:53:26 +0100 Subject: [PATCH 094/216] Changelog for #1995 --- .changelog/unreleased/improvements/1995-eth-tx-emits-events.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/1995-eth-tx-emits-events.md diff --git a/.changelog/unreleased/improvements/1995-eth-tx-emits-events.md b/.changelog/unreleased/improvements/1995-eth-tx-emits-events.md new file mode 100644 index 0000000000..7ec43540cc --- /dev/null +++ b/.changelog/unreleased/improvements/1995-eth-tx-emits-events.md @@ -0,0 +1,2 @@ +- Emit Bridge pool transfer status update events from FinalizeBlock + ([\#1995](https://github.com/anoma/namada/pull/1995)) \ No newline at end of file From 3cc0c0a8248b5cf94cefea01e9fb7fabc592d097 Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Tue, 12 Dec 2023 16:54:07 +0100 Subject: [PATCH 095/216] Fix key / address listing --- apps/src/lib/cli.rs | 51 +++++++++----- apps/src/lib/cli/wallet.rs | 140 +++++++++++++++++++++---------------- sdk/src/args.rs | 10 +-- 3 files changed, 119 insertions(+), 82 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 187b497b93..4d4b2e69a6 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2948,6 +2948,7 @@ pub mod args { pub const TOKEN: Arg = arg("token"); pub const TRANSFER_SOURCE: Arg = arg("source"); pub const TRANSFER_TARGET: Arg = arg("target"); + pub const TRANSPARENT: ArgFlag = flag("transparent"); pub const TX_HASH: Arg = arg("tx-hash"); pub const THRESHOLD: ArgOpt = arg_opt("threshold"); pub const UNSAFE_DONT_ENCRYPT: ArgFlag = flag("unsafe-dont-encrypt"); @@ -6110,14 +6111,16 @@ pub mod args { impl Args for KeyAddressList { fn parse(matches: &ArgMatches) -> Self { - let shielded = SHIELDED.parse(matches); let decrypt = DECRYPT.parse(matches); + let transparent_only = TRANSPARENT.parse(matches); + let shielded_only = SHIELDED.parse(matches); let keys_only = LIST_FIND_KEYS_ONLY.parse(matches); let addresses_only = LIST_FIND_ADDRESSES_ONLY.parse(matches); let unsafe_show_secret = UNSAFE_SHOW_SECRET.parse(matches); Self { - shielded, decrypt, + transparent_only, + shielded_only, keys_only, addresses_only, unsafe_show_secret, @@ -6125,22 +6128,34 @@ pub mod args { } fn def(app: App) -> App { - app.arg(SHIELDED.def().help( - "List viewing / spending keys and payment addresses for the \ - shielded pool.", - )) - .arg(DECRYPT.def().help("Decrypt keys that are encrypted.")) - .arg(LIST_FIND_KEYS_ONLY.def().help("List keys only.")) - .arg(LIST_FIND_ADDRESSES_ONLY.def().help("List addresses only.")) - .group(ArgGroup::new("only_group").args([ - LIST_FIND_KEYS_ONLY.name, - LIST_FIND_ADDRESSES_ONLY.name, - ])) - .arg( - UNSAFE_SHOW_SECRET - .def() - .help("UNSAFE: Print the secret / spending keys."), - ) + app.arg(DECRYPT.def().help("Decrypt keys that are encrypted.")) + .arg( + TRANSPARENT + .def() + .help("List transparent keys / addresses only."), + ) + .arg( + SHIELDED.def().help( + "List keys / addresses of the shielded pool only.", + ), + ) + .group( + ArgGroup::new("only_group_1") + .args([TRANSPARENT.name, SHIELDED.name]), + ) + .arg(LIST_FIND_KEYS_ONLY.def().help("List keys only.")) + .arg( + LIST_FIND_ADDRESSES_ONLY.def().help("List addresses only."), + ) + .group(ArgGroup::new("only_group_2").args([ + LIST_FIND_KEYS_ONLY.name, + LIST_FIND_ADDRESSES_ONLY.name, + ])) + .arg( + UNSAFE_SHOW_SECRET + .def() + .help("UNSAFE: Print the secret / spending keys."), + ) } } diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index b0bd6ba8bb..fbab002af6 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -73,21 +73,23 @@ impl CliApi { /// List spending keys. fn spending_keys_list( - ctx: Context, + wallet: &Wallet, io: &impl Io, decrypt: bool, unsafe_show_secret: bool, + show_hint: bool, ) { - let wallet = load_wallet(ctx); let known_view_keys = wallet.get_viewing_keys(); let known_spend_keys = wallet.get_spending_keys(); if known_view_keys.is_empty() { - display_line!( - io, - "No known keys. Try `add --alias my-addr --value ...` to add a \ - new key to the wallet, or `gen --shielded --alias my-key` to \ - generate a new key.", - ); + if show_hint { + display_line!( + io, + "No known keys. Try `add --alias my-addr --value ...` to add \ + a new key to the wallet, or `gen --shielded --alias my-key` \ + to generate a new key.", + ); + } } else { let stdout = io::stdout(); let mut w = stdout.lock(); @@ -147,15 +149,20 @@ fn spending_keys_list( } /// List payment addresses. -fn payment_addresses_list(ctx: Context, io: &impl Io) { - let wallet = load_wallet(ctx); +fn payment_addresses_list( + wallet: &Wallet, + io: &impl Io, + show_hint: bool, +) { let known_addresses = wallet.get_payment_addrs(); if known_addresses.is_empty() { - display_line!( - io, - "No known payment addresses. Try `gen-payment-addr --alias \ - my-payment-addr` to generate a new payment address.", - ); + if show_hint { + display_line!( + io, + "No known payment addresses. Try `gen-payment-addr --alias \ + my-payment-addr` to generate a new payment address.", + ); + } } else { let stdout = io::stdout(); let mut w = stdout.lock(); @@ -488,47 +495,52 @@ async fn key_derive( } } -/// List keys -fn key_list( +/// List keys and addresses +fn key_address_list( ctx: Context, io: &impl Io, args::KeyAddressList { - shielded, decrypt, + transparent_only, + shielded_only, + keys_only, + addresses_only, unsafe_show_secret, - .. }: args::KeyAddressList, ) { - if !shielded { - transparent_keys_list(ctx, io, decrypt, unsafe_show_secret) - } else { - spending_keys_list(ctx, io, decrypt, unsafe_show_secret) - } -} - -/// List addresses -fn address_list( - ctx: Context, - io: &impl Io, - args::KeyAddressList { shielded, .. }: args::KeyAddressList, -) { - if !shielded { - transparent_addresses_list(ctx, io) - } else { - payment_addresses_list(ctx, io) + let wallet = load_wallet(ctx); + if !shielded_only { + if !addresses_only { + transparent_keys_list( + &wallet, + io, + decrypt, + unsafe_show_secret, + transparent_only && keys_only, + ) + } + if !keys_only { + transparent_addresses_list( + &wallet, + io, + transparent_only && addresses_only, + ) + } } -} -/// List keys and addresses -fn key_address_list( - ctx: Context, - io: &impl Io, - args_key_address_list: args::KeyAddressList, -) { - if !args_key_address_list.addresses_only { - key_list(ctx, io, args_key_address_list) - } else if !args_key_address_list.keys_only { - address_list(ctx, io, args_key_address_list) + if !transparent_only { + if !addresses_only { + spending_keys_list( + &wallet, + io, + decrypt, + unsafe_show_secret, + shielded_only && keys_only, + ) + } + if !keys_only { + payment_addresses_list(&wallet, io, shielded_only && addresses_only) + } } } @@ -892,18 +904,21 @@ fn shielded_key_address_find_by_alias( /// List all known keys. fn transparent_keys_list( - ctx: Context, + wallet: &Wallet, io: &impl Io, decrypt: bool, unsafe_show_secret: bool, + show_hint: bool, ) { - let wallet = load_wallet(ctx); let known_public_keys = wallet.get_public_keys(); if known_public_keys.is_empty() { - display_line!( - io, - "No known keys. Try `gen --alias my-key` to generate a new key.", - ); + if show_hint { + display_line!( + io, + "No known keys. Try `gen --alias my-key` to generate a new \ + key.", + ); + } } else { let stdout = io::stdout(); let mut w = stdout.lock(); @@ -1010,15 +1025,20 @@ fn key_import( } /// List all known transparent addresses. -fn transparent_addresses_list(ctx: Context, io: &impl Io) { - let wallet = load_wallet(ctx); +fn transparent_addresses_list( + wallet: &Wallet, + io: &impl Io, + show_hint: bool, +) { let known_addresses = wallet.get_addresses(); if known_addresses.is_empty() { - display_line!( - io, - "No known addresses. Try `gen --alias my-addr` to generate a new \ - implicit address.", - ); + if show_hint { + display_line!( + io, + "No known addresses. Try `gen --alias my-addr` to generate a \ + new implicit address.", + ); + } } else { let stdout = io::stdout(); let mut w = stdout.lock(); diff --git a/sdk/src/args.rs b/sdk/src/args.rs index c5c070baa4..ee18335dfd 100644 --- a/sdk/src/args.rs +++ b/sdk/src/args.rs @@ -2079,12 +2079,14 @@ pub struct KeyDerive { } /// Wallet list arguments -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug)] pub struct KeyAddressList { - /// Whether to list MASP spending keys - pub shielded: bool, - /// Don't decrypt keys + /// Whether to decrypt secret / spending keys pub decrypt: bool, + /// Whether to list transparent secret keys only + pub transparent_only: bool, + /// Whether to list MASP spending keys only + pub shielded_only: bool, /// List keys only pub keys_only: bool, /// List addresses only From c9092ca4eaf499e76efb53e916706fa6066857c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 12 Dec 2023 16:48:16 +0000 Subject: [PATCH 096/216] testing: use the appropriate address generators for address type --- .../src/ledger/governance/storage/proposal.rs | 13 ++++++---- core/src/types/eth_bridge_pool.rs | 11 +++++---- core/src/types/token.rs | 10 ++++---- core/src/types/transaction/account.rs | 4 ++-- core/src/types/transaction/governance.rs | 10 ++++---- core/src/types/transaction/pgf.rs | 6 ++--- core/src/types/transaction/pos.rs | 24 +++++++++---------- sdk/src/lib.rs | 14 ++++++----- 8 files changed, 51 insertions(+), 41 deletions(-) diff --git a/core/src/ledger/governance/storage/proposal.rs b/core/src/ledger/governance/storage/proposal.rs index 04e30b52c8..59f174edee 100644 --- a/core/src/ledger/governance/storage/proposal.rs +++ b/core/src/ledger/governance/storage/proposal.rs @@ -319,7 +319,7 @@ pub mod tests { use proptest::{collection, option, prop_compose}; use super::*; - use crate::types::address::testing::arb_address; + use crate::types::address::testing::arb_non_internal_address; use crate::types::hash::tests::arb_hash; use crate::types::token::testing::arb_amount; @@ -338,7 +338,7 @@ pub mod tests { prop_compose! { /// Generate an arbitrary PGF target pub fn arb_pgf_target()( - target in arb_address(), + target in arb_non_internal_address(), amount in arb_amount(), ) -> PGFTarget { PGFTarget { @@ -362,9 +362,12 @@ pub mod tests { .prop_map(ProposalType::Default) .boxed() .prop_union( - collection::hash_set(arb_add_remove(arb_address()), 0..10) - .prop_map(ProposalType::PGFSteward) - .boxed(), + collection::hash_set( + arb_add_remove(arb_non_internal_address()), + 0..10, + ) + .prop_map(ProposalType::PGFSteward) + .boxed(), ) .or(collection::vec(arb_pgf_action(), 0..10) .prop_map(ProposalType::PGFPayment) diff --git a/core/src/types/eth_bridge_pool.rs b/core/src/types/eth_bridge_pool.rs index d454437249..8ff790e1c6 100644 --- a/core/src/types/eth_bridge_pool.rs +++ b/core/src/types/eth_bridge_pool.rs @@ -306,9 +306,12 @@ pub struct GasFee { /// Tests and strategies for the Ethereum bridge pool pub mod tests { use proptest::prop_compose; + use proptest::strategy::Strategy; use super::*; - use crate::types::address::testing::arb_address; + use crate::types::address::testing::{ + arb_established_address, arb_non_internal_address, + }; use crate::types::ethereum_events::testing::arb_eth_address; use crate::types::token::testing::arb_amount; @@ -329,8 +332,8 @@ pub mod tests { /// Generate an arbitrary Ethereum gas fee pub fn arb_gas_fee()( amount in arb_amount(), - payer in arb_address(), - token in arb_address(), + payer in arb_non_internal_address(), + token in arb_established_address().prop_map(Address::Established), ) -> GasFee { GasFee { amount, @@ -359,7 +362,7 @@ pub mod tests { kind in arb_transfer_to_ethereum_kind(), asset in arb_eth_address(), recipient in arb_eth_address(), - sender in arb_address(), + sender in arb_non_internal_address(), amount in arb_amount(), ) -> TransferToEthereum { TransferToEthereum { diff --git a/core/src/types/token.rs b/core/src/types/token.rs index ae2f160c8f..8423e09fcf 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -1309,7 +1309,9 @@ pub mod tests { use proptest::{option, prop_compose}; use super::*; - use crate::types::address::testing::arb_address; + use crate::types::address::testing::{ + arb_established_address, arb_non_internal_address, + }; use crate::types::token::testing::arb_amount; prop_compose! { @@ -1332,9 +1334,9 @@ pub mod tests { prop_compose! { /// Generate a transfer pub fn arb_transfer()( - source in arb_address(), - target in arb_address(), - token in arb_address(), + source in arb_non_internal_address(), + target in arb_non_internal_address(), + token in arb_established_address().prop_map(Address::Established), amount in arb_denominated_amount(), key in option::of("[a-zA-Z0-9_]*"), ) -> Transfer { diff --git a/core/src/types/transaction/account.rs b/core/src/types/transaction/account.rs index 2e08b57235..6db1c6e585 100644 --- a/core/src/types/transaction/account.rs +++ b/core/src/types/transaction/account.rs @@ -58,7 +58,7 @@ pub mod tests { use proptest::{collection, option, prop_compose}; use super::*; - use crate::types::address::testing::arb_address; + use crate::types::address::testing::arb_non_internal_address; use crate::types::hash::tests::arb_hash; use crate::types::key::testing::arb_common_pk; @@ -84,7 +84,7 @@ pub mod tests { pub fn arb_update_account()( public_keys in collection::vec(arb_common_pk(), 0..10), )( - addr in arb_address(), + addr in arb_non_internal_address(), vp_code_hash in option::of(arb_hash()), threshold in option::of(0..=public_keys.len() as u8), public_keys in Just(public_keys), diff --git a/core/src/types/transaction/governance.rs b/core/src/types/transaction/governance.rs index 99b291c814..30927833d1 100644 --- a/core/src/types/transaction/governance.rs +++ b/core/src/types/transaction/governance.rs @@ -173,16 +173,16 @@ pub mod tests { use super::*; use crate::ledger::governance::storage::proposal::tests::arb_proposal_type; use crate::ledger::governance::storage::vote::tests::arb_proposal_vote; - use crate::types::address::testing::arb_address; + use crate::types::address::testing::arb_non_internal_address; use crate::types::hash::tests::arb_hash; - use crate::types::storage::tests::arb_epoch; + use crate::types::storage::testing::arb_epoch; prop_compose! { /// Generate a proposal initialization pub fn arb_init_proposal()( id: Option, content in arb_hash(), - author in arb_address(), + author in arb_non_internal_address(), r#type in arb_proposal_type(), voting_start_epoch in arb_epoch(), voting_end_epoch in arb_epoch(), @@ -205,8 +205,8 @@ pub mod tests { pub fn arb_vote_proposal()( id: u64, vote in arb_proposal_vote(), - voter in arb_address(), - delegations in collection::vec(arb_address(), 0..10), + voter in arb_non_internal_address(), + delegations in collection::vec(arb_non_internal_address(), 0..10), ) -> VoteProposalData { VoteProposalData { id, diff --git a/core/src/types/transaction/pgf.rs b/core/src/types/transaction/pgf.rs index 6a8c0c5fb4..02ee320253 100644 --- a/core/src/types/transaction/pgf.rs +++ b/core/src/types/transaction/pgf.rs @@ -36,15 +36,15 @@ pub struct UpdateStewardCommission { pub mod tests { use proptest::{collection, prop_compose}; - use crate::types::address::testing::arb_address; + use crate::types::address::testing::arb_non_internal_address; use crate::types::dec::testing::arb_dec; use crate::types::transaction::pgf::UpdateStewardCommission; prop_compose! { /// Generate an arbitraary steward commission update pub fn arb_update_steward_commission()( - steward in arb_address(), - commission in collection::hash_map(arb_address(), arb_dec(), 0..10), + steward in arb_non_internal_address(), + commission in collection::hash_map(arb_non_internal_address(), arb_dec(), 0..10), ) -> UpdateStewardCommission { UpdateStewardCommission { steward, diff --git a/core/src/types/transaction/pos.rs b/core/src/types/transaction/pos.rs index e353be333e..1ac5fc8a38 100644 --- a/core/src/types/transaction/pos.rs +++ b/core/src/types/transaction/pos.rs @@ -213,7 +213,7 @@ pub mod tests { use proptest::{option, prop_compose}; use super::*; - use crate::types::address::testing::arb_address; + use crate::types::address::testing::arb_non_internal_address; use crate::types::dec::testing::arb_dec; use crate::types::key::testing::{arb_common_pk, arb_pk}; use crate::types::token::testing::arb_amount; @@ -221,9 +221,9 @@ pub mod tests { prop_compose! { /// Generate a bond pub fn arb_bond()( - validator in arb_address(), + validator in arb_non_internal_address(), amount in arb_amount(), - source in option::of(arb_address()), + source in option::of(arb_non_internal_address()), ) -> Bond { Bond { validator, @@ -236,8 +236,8 @@ pub mod tests { prop_compose! { /// Generate an arbitrary withdraw pub fn arb_withdraw()( - validator in arb_address(), - source in option::of(arb_address()), + validator in arb_non_internal_address(), + source in option::of(arb_non_internal_address()), ) -> Withdraw { Withdraw { validator, @@ -249,7 +249,7 @@ pub mod tests { prop_compose! { /// Generate an arbitrary commission change pub fn arb_commission_change()( - validator in arb_address(), + validator in arb_non_internal_address(), new_rate in arb_dec(), ) -> CommissionChange { CommissionChange { @@ -262,7 +262,7 @@ pub mod tests { prop_compose! { /// Generate an arbitrary metadata change pub fn arb_metadata_change()( - validator in arb_address(), + validator in arb_non_internal_address(), email in option::of("[a-zA-Z0-9_]*"), description in option::of("[a-zA-Z0-9_]*"), website in option::of("[a-zA-Z0-9_]*"), @@ -283,7 +283,7 @@ pub mod tests { prop_compose! { /// Generate an arbitrary consensus key change pub fn arb_consensus_key_change()( - validator in arb_address(), + validator in arb_non_internal_address(), consensus_key in arb_common_pk(), ) -> ConsensusKeyChange { ConsensusKeyChange { @@ -296,7 +296,7 @@ pub mod tests { prop_compose! { /// Generate a validator initialization pub fn arb_become_validator()( - address in arb_address(), + address in arb_non_internal_address(), consensus_key in arb_common_pk(), eth_cold_key in arb_pk::(), eth_hot_key in arb_pk::(), @@ -327,9 +327,9 @@ pub mod tests { prop_compose! { /// Generate an arbitrary redelegation pub fn arb_redelegation()( - src_validator in arb_address(), - dest_validator in arb_address(), - owner in arb_address(), + src_validator in arb_non_internal_address(), + dest_validator in arb_non_internal_address(), + owner in arb_non_internal_address(), amount in arb_amount(), ) -> Redelegation { Redelegation { diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index c19976bf0c..bfc5b7181a 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -762,7 +762,9 @@ pub mod testing { use ibc::primitives::proto::Any; use namada_core::ledger::governance::storage::proposal::ProposalType; use namada_core::ledger::ibc::tests::arb_ibc_any; - use namada_core::types::address::testing::arb_address; + use namada_core::types::address::testing::{ + arb_established_address, arb_non_internal_address, + }; use namada_core::types::eth_bridge_pool::PendingTransfer; use namada_core::types::hash::tests::arb_hash; use namada_core::types::storage::tests::arb_epoch; @@ -881,7 +883,7 @@ pub mod testing { // Generate an arbitrary fee pub fn arb_fee()( amount_per_gas_unit in arb_denominated_amount(), - token in arb_address(), + token in arb_established_address().prop_map(Address::Established), ) -> Fee { Fee { amount_per_gas_unit, @@ -1188,7 +1190,7 @@ pub mod testing { pub fn arb_unjail_validator_tx()( mut header in arb_header(), wrapper in arb_wrapper_tx(), - address in arb_address(), + address in arb_non_internal_address(), code_hash in arb_hash(), ) -> (Tx, TxData) { header.tx_type = TxType::Wrapper(Box::new(wrapper)); @@ -1204,7 +1206,7 @@ pub mod testing { pub fn arb_deactivate_validator_tx()( mut header in arb_header(), wrapper in arb_wrapper_tx(), - address in arb_address(), + address in arb_non_internal_address(), code_hash in arb_hash(), ) -> (Tx, TxData) { header.tx_type = TxType::Wrapper(Box::new(wrapper)); @@ -1220,7 +1222,7 @@ pub mod testing { pub fn arb_reactivate_validator_tx()( mut header in arb_header(), wrapper in arb_wrapper_tx(), - address in arb_address(), + address in arb_non_internal_address(), code_hash in arb_hash(), ) -> (Tx, TxData) { header.tx_type = TxType::Wrapper(Box::new(wrapper)); @@ -1284,7 +1286,7 @@ pub mod testing { pub fn arb_resign_steward_tx()( mut header in arb_header(), wrapper in arb_wrapper_tx(), - steward in arb_address(), + steward in arb_non_internal_address(), code_hash in arb_hash(), ) -> (Tx, TxData) { header.tx_type = TxType::Wrapper(Box::new(wrapper)); From 6936ac2c3c564ac55eb6c45256fb313068752851 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 12 Dec 2023 17:10:17 +0000 Subject: [PATCH 097/216] renaming and restructuring testing modules for consistency --- .../src/ledger/governance/storage/proposal.rs | 6 +- core/src/ledger/governance/storage/vote.rs | 4 +- core/src/ledger/ibc/mod.rs | 4 +- core/src/types/eth_bridge_pool.rs | 4 +- core/src/types/hash.rs | 24 +++++--- core/src/types/storage.rs | 17 +++--- core/src/types/token.rs | 57 +++++++++---------- core/src/types/transaction/account.rs | 2 +- core/src/types/transaction/governance.rs | 6 +- sdk/src/lib.rs | 10 ++-- 10 files changed, 68 insertions(+), 66 deletions(-) diff --git a/core/src/ledger/governance/storage/proposal.rs b/core/src/ledger/governance/storage/proposal.rs index 59f174edee..5d4518ba08 100644 --- a/core/src/ledger/governance/storage/proposal.rs +++ b/core/src/ledger/governance/storage/proposal.rs @@ -313,14 +313,14 @@ impl Display for StorageProposal { } #[cfg(any(test, feature = "testing"))] -/// Tests and strategies for governance proposals -pub mod tests { +/// Testing helpers and and strategies for governance proposals +pub mod testing { use proptest::prelude::Strategy; use proptest::{collection, option, prop_compose}; use super::*; use crate::types::address::testing::arb_non_internal_address; - use crate::types::hash::tests::arb_hash; + use crate::types::hash::testing::arb_hash; use crate::types::token::testing::arb_amount; /// Generate an arbitrary add or removal of what's generated by the supplied diff --git a/core/src/ledger/governance/storage/vote.rs b/core/src/ledger/governance/storage/vote.rs index ed6b5e0318..9be49fa9b7 100644 --- a/core/src/ledger/governance/storage/vote.rs +++ b/core/src/ledger/governance/storage/vote.rs @@ -146,8 +146,8 @@ impl PartialEq for ProposalType { } #[cfg(any(test, feature = "testing"))] -/// Tests and strategies for governance votes -pub mod tests { +/// Testing helpers and strategies for governance votes +pub mod testing { use proptest::prelude::{Just, Strategy}; use proptest::prop_compose; diff --git a/core/src/ledger/ibc/mod.rs b/core/src/ledger/ibc/mod.rs index dfe61b7935..37b530fe9a 100644 --- a/core/src/ledger/ibc/mod.rs +++ b/core/src/ledger/ibc/mod.rs @@ -298,8 +298,8 @@ pub fn received_ibc_token( } #[cfg(any(test, feature = "testing"))] -/// Tests ans strategies for IBC -pub mod tests { +/// Testing helpers ans strategies for IBC +pub mod testing { use std::str::FromStr; use ibc::apps::transfer::types::packet::PacketData; diff --git a/core/src/types/eth_bridge_pool.rs b/core/src/types/eth_bridge_pool.rs index 8ff790e1c6..22539714ff 100644 --- a/core/src/types/eth_bridge_pool.rs +++ b/core/src/types/eth_bridge_pool.rs @@ -303,8 +303,8 @@ pub struct GasFee { } #[cfg(any(test, feature = "testing"))] -/// Tests and strategies for the Ethereum bridge pool -pub mod tests { +/// Testing helpers and strategies for the Ethereum bridge pool +pub mod testing { use proptest::prop_compose; use proptest::strategy::Strategy; diff --git a/core/src/types/hash.rs b/core/src/types/hash.rs index 17d4049a15..bb6c6e3acc 100644 --- a/core/src/types/hash.rs +++ b/core/src/types/hash.rs @@ -148,15 +148,28 @@ impl From for crate::tendermint::Hash { #[cfg(any(test, feature = "testing"))] /// Tests and strategies for hashes +pub mod testing { + use proptest::prop_compose; + + use super::*; + + prop_compose! { + /// Generate an arbitrary hash + pub fn arb_hash()(bytes: [u8; 32]) -> Hash { + Hash(bytes) + } + } +} + +#[cfg(test)] +/// Tests and strategies for hashes pub mod tests { use proptest::prelude::*; - #[cfg(test)] use proptest::string::{string_regex, RegexGeneratorStrategy}; use super::*; /// Returns a proptest strategy that yields hex encoded hashes. - #[cfg(test)] fn hex_encoded_hash_strat() -> RegexGeneratorStrategy { string_regex(r"[a-fA-F0-9]{64}").unwrap() } @@ -167,11 +180,4 @@ pub mod tests { let _: Hash = hex_hash.try_into().unwrap(); } } - - prop_compose! { - /// Generate an arbitrary hash - pub fn arb_hash()(bytes: [u8; 32]) -> Hash { - Hash(bytes) - } - } } diff --git a/core/src/types/storage.rs b/core/src/types/storage.rs index 4b2e331f5c..af0ca63340 100644 --- a/core/src/types/storage.rs +++ b/core/src/types/storage.rs @@ -1433,22 +1433,14 @@ impl GetEventNonce for InnerEthEventsQueue { } } -#[cfg(any(test, feature = "testing"))] +#[cfg(test)] /// Tests and strategies for storage pub mod tests { use proptest::prelude::*; use super::*; - #[cfg(test)] use crate::types::address::testing::arb_address; - prop_compose! { - /// Generate an arbitrary epoch - pub fn arb_epoch()(epoch: u64) -> Epoch { - Epoch(epoch) - } - } - proptest! { /// Tests that any key that doesn't contain reserved prefixes is valid. /// This test excludes key segments starting with `#` or `?` @@ -1880,6 +1872,13 @@ pub mod testing { arb_address, arb_non_internal_address, }; + prop_compose! { + /// Generate an arbitrary epoch + pub fn arb_epoch()(epoch: u64) -> Epoch { + Epoch(epoch) + } + } + /// Generate an arbitrary [`Key`]. pub fn arb_key() -> impl Strategy { prop_oneof![ diff --git a/core/src/types/token.rs b/core/src/types/token.rs index 8423e09fcf..4349db8902 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -1304,15 +1304,15 @@ pub enum TransferError { } #[cfg(any(test, feature = "testing"))] -/// Tests and strategies for tokens -pub mod tests { - use proptest::{option, prop_compose}; +/// Testing helpers and strategies for tokens +pub mod testing { + use proptest::option; + use proptest::prelude::*; use super::*; use crate::types::address::testing::{ arb_established_address, arb_non_internal_address, }; - use crate::types::token::testing::arb_amount; prop_compose! { /// Generate an arbitrary denomination @@ -1351,6 +1351,29 @@ pub mod tests { } } + /// Generate an arbitrary token amount + pub fn arb_amount() -> impl Strategy { + any::().prop_map(|val| Amount::from_uint(val, 0).unwrap()) + } + + /// Generate an arbitrary token amount up to and including given `max` value + pub fn arb_amount_ceiled(max: u64) -> impl Strategy { + (0..=max).prop_map(|val| Amount::from_uint(val, 0).unwrap()) + } + + /// Generate an arbitrary non-zero token amount up to and including given + /// `max` value + pub fn arb_amount_non_zero_ceiled( + max: u64, + ) -> impl Strategy { + (1..=max).prop_map(|val| Amount::from_uint(val, 0).unwrap()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] fn test_token_display() { let max = Amount::from_uint(u64::MAX, 0).expect("Test failed"); @@ -1608,29 +1631,3 @@ pub mod tests { ); } } - -/// Helpers for testing with addresses. -#[cfg(any(test, feature = "testing"))] -pub mod testing { - use proptest::prelude::*; - - use super::*; - - /// Generate an arbitrary token amount - pub fn arb_amount() -> impl Strategy { - any::().prop_map(|val| Amount::from_uint(val, 0).unwrap()) - } - - /// Generate an arbitrary token amount up to and including given `max` value - pub fn arb_amount_ceiled(max: u64) -> impl Strategy { - (0..=max).prop_map(|val| Amount::from_uint(val, 0).unwrap()) - } - - /// Generate an arbitrary non-zero token amount up to and including given - /// `max` value - pub fn arb_amount_non_zero_ceiled( - max: u64, - ) -> impl Strategy { - (1..=max).prop_map(|val| Amount::from_uint(val, 0).unwrap()) - } -} diff --git a/core/src/types/transaction/account.rs b/core/src/types/transaction/account.rs index 6db1c6e585..3e4271f6f0 100644 --- a/core/src/types/transaction/account.rs +++ b/core/src/types/transaction/account.rs @@ -59,7 +59,7 @@ pub mod tests { use super::*; use crate::types::address::testing::arb_non_internal_address; - use crate::types::hash::tests::arb_hash; + use crate::types::hash::testing::arb_hash; use crate::types::key::testing::arb_common_pk; prop_compose! { diff --git a/core/src/types/transaction/governance.rs b/core/src/types/transaction/governance.rs index 30927833d1..1f41055eaa 100644 --- a/core/src/types/transaction/governance.rs +++ b/core/src/types/transaction/governance.rs @@ -171,10 +171,10 @@ pub mod tests { use proptest::{collection, prop_compose}; use super::*; - use crate::ledger::governance::storage::proposal::tests::arb_proposal_type; - use crate::ledger::governance::storage::vote::tests::arb_proposal_vote; + use crate::ledger::governance::storage::proposal::testing::arb_proposal_type; + use crate::ledger::governance::storage::vote::testing::arb_proposal_vote; use crate::types::address::testing::arb_non_internal_address; - use crate::types::hash::tests::arb_hash; + use crate::types::hash::testing::arb_hash; use crate::types::storage::testing::arb_epoch; prop_compose! { diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index bfc5b7181a..9d2cff8071 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -761,14 +761,14 @@ where pub mod testing { use ibc::primitives::proto::Any; use namada_core::ledger::governance::storage::proposal::ProposalType; - use namada_core::ledger::ibc::tests::arb_ibc_any; + use namada_core::ledger::ibc::testing::arb_ibc_any; use namada_core::types::address::testing::{ arb_established_address, arb_non_internal_address, }; use namada_core::types::eth_bridge_pool::PendingTransfer; - use namada_core::types::hash::tests::arb_hash; - use namada_core::types::storage::tests::arb_epoch; - use namada_core::types::token::tests::{ + use namada_core::types::hash::testing::arb_hash; + use namada_core::types::storage::testing::arb_epoch; + use namada_core::types::token::testing::{ arb_denominated_amount, arb_transfer, }; use namada_core::types::token::Transfer; @@ -789,7 +789,7 @@ pub mod testing { use super::*; use crate::core::types::chain::ChainId; - use crate::core::types::eth_bridge_pool::tests::arb_pending_transfer; + use crate::core::types::eth_bridge_pool::testing::arb_pending_transfer; use crate::core::types::key::testing::arb_common_pk; use crate::core::types::time::{DateTime, DateTimeUtc, Utc}; use crate::core::types::transaction::account::tests::{ From 2218b64fcf696644a45c9b98e5dcb683ced4fb6c Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 13 Dec 2023 18:13:35 +0100 Subject: [PATCH 098/216] Removes useless MASP key from `vp_user` --- wasm/wasm_source/src/vp_user.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index 28bfcbdc9f..ee3d79b810 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -16,7 +16,6 @@ enum KeyType<'a> { Token { owner: &'a Address }, PoS, Vp(&'a Address), - Masp, PgfStward(&'a Address), GovernanceVote(&'a Address), Unknown, @@ -39,8 +38,6 @@ impl<'a> From<&'a storage::Key> for KeyType<'a> { Self::PgfStward(address) } else if let Some(address) = key.is_validity_predicate() { Self::Vp(address) - } else if token::is_masp_key(key) { - Self::Masp } else { Self::Unknown } @@ -158,7 +155,6 @@ fn validate_tx( is_vp_whitelisted(ctx, &vp_hash)? } } - KeyType::Masp => true, KeyType::Unknown => { if key.segments.get(0) == Some(&addr.to_db_key()) { // Unknown changes to this address space require a valid From f33ebe7b59f16768a1e6abf7dc3cb404f08288cd Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 13 Dec 2023 19:05:48 +0100 Subject: [PATCH 099/216] Removes useless fee unshielding epoch --- apps/src/lib/client/tx.rs | 50 +++++------- sdk/src/args.rs | 55 +++++-------- sdk/src/eth_bridge/bridge_pool.rs | 7 +- sdk/src/signing.rs | 20 +++-- sdk/src/tx.rs | 128 ++++++++++++------------------ 5 files changed, 101 insertions(+), 159 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 0317ec43f3..b0d8db7e5f 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -223,7 +223,7 @@ pub async fn submit_reveal_aux<'a>( "Submitting a tx to reveal the public key for address \ {address}..." ); - let (mut tx, signing_data, _epoch) = + let (mut tx, signing_data) = tx::build_reveal_pk(context, &args, &public_key).await?; signing::generate_test_vector(context, &tx).await?; @@ -244,7 +244,7 @@ pub async fn submit_bridge_pool_tx<'a, N: Namada<'a>>( args: args::EthereumBridgePool, ) -> Result<(), error::Error> { let tx_args = args.tx.clone(); - let (mut tx, signing_data, _epoch) = args.clone().build(namada).await?; + let (mut tx, signing_data) = args.clone().build(namada).await?; signing::generate_test_vector(namada, &tx).await?; @@ -272,7 +272,7 @@ where { submit_reveal_aux(namada, args.tx.clone(), &args.owner).await?; - let (mut tx, signing_data, _epoch) = args.build(namada).await?; + let (mut tx, signing_data) = args.build(namada).await?; signing::generate_test_vector(namada, &tx).await?; @@ -296,7 +296,7 @@ pub async fn submit_update_account<'a, N: Namada<'a>>( where ::Error: std::fmt::Display, { - let (mut tx, signing_data, _epoch) = args.build(namada).await?; + let (mut tx, signing_data) = args.build(namada).await?; signing::generate_test_vector(namada, &tx).await?; @@ -320,8 +320,7 @@ pub async fn submit_init_account<'a, N: Namada<'a>>( where ::Error: std::fmt::Display, { - let (mut tx, signing_data, _epoch) = - tx::build_init_account(namada, &args).await?; + let (mut tx, signing_data) = tx::build_init_account(namada, &args).await?; signing::generate_test_vector(namada, &tx).await?; @@ -919,7 +918,7 @@ where ::Error: std::fmt::Display, { submit_reveal_aux(namada, args.tx.clone(), &args.source).await?; - let (mut tx, signing_data, _epoch) = args.build(namada).await?; + let (mut tx, signing_data) = args.build(namada).await?; signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { @@ -945,8 +944,7 @@ where let current_epoch = rpc::query_and_print_epoch(namada).await; let governance_parameters = rpc::query_governance_parameters(namada.client()).await; - let (mut tx_builder, signing_data, _fee_unshield_epoch) = if args.is_offline - { + let (mut tx_builder, signing_data) = if args.is_offline { let proposal = OfflineProposal::try_from(args.proposal_data.as_ref()) .map_err(|e| { error::TxError::FailedGovernaneProposalDeserialize( @@ -1071,8 +1069,7 @@ pub async fn submit_vote_proposal<'a, N: Namada<'a>>( where ::Error: std::fmt::Display, { - let (mut tx_builder, signing_data, _fee_unshield_epoch) = if args.is_offline - { + let (mut tx_builder, signing_data) = if args.is_offline { let default_signer = Some(args.voter.clone()); let signing_data = aux_signing_data( namada, @@ -1247,8 +1244,7 @@ where let default_address = args.source.clone().unwrap_or(args.validator.clone()); submit_reveal_aux(namada, args.tx.clone(), &default_address).await?; - let (mut tx, signing_data, _fee_unshield_epoch) = - args.build(namada).await?; + let (mut tx, signing_data) = args.build(namada).await?; signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { @@ -1271,7 +1267,7 @@ pub async fn submit_unbond<'a, N: Namada<'a>>( where ::Error: std::fmt::Display, { - let (mut tx, signing_data, _fee_unshield_epoch, latest_withdrawal_pre) = + let (mut tx, signing_data, latest_withdrawal_pre) = args.build(namada).await?; signing::generate_test_vector(namada, &tx).await?; @@ -1297,8 +1293,7 @@ pub async fn submit_withdraw<'a, N: Namada<'a>>( where ::Error: std::fmt::Display, { - let (mut tx, signing_data, _fee_unshield_epoch) = - args.build(namada).await?; + let (mut tx, signing_data) = args.build(namada).await?; signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { @@ -1321,8 +1316,7 @@ pub async fn submit_claim_rewards<'a, N: Namada<'a>>( where ::Error: std::fmt::Display, { - let (mut tx, signing_data, _fee_unshield_epoch) = - args.build(namada).await?; + let (mut tx, signing_data) = args.build(namada).await?; signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { @@ -1368,8 +1362,7 @@ pub async fn submit_validator_commission_change<'a, N: Namada<'a>>( where ::Error: std::fmt::Display, { - let (mut tx, signing_data, _fee_unshield_epoch) = - args.build(namada).await?; + let (mut tx, signing_data) = args.build(namada).await?; signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { @@ -1392,8 +1385,7 @@ pub async fn submit_validator_metadata_change<'a, N: Namada<'a>>( where ::Error: std::fmt::Display, { - let (mut tx, signing_data, _fee_unshield_epoch) = - args.build(namada).await?; + let (mut tx, signing_data) = args.build(namada).await?; signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { @@ -1438,8 +1430,7 @@ pub async fn submit_unjail_validator<'a, N: Namada<'a>>( where ::Error: std::fmt::Display, { - let (mut tx, signing_data, _fee_unshield_epoch) = - args.build(namada).await?; + let (mut tx, signing_data) = args.build(namada).await?; signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { @@ -1462,8 +1453,7 @@ pub async fn submit_deactivate_validator<'a, N: Namada<'a>>( where ::Error: std::fmt::Display, { - let (mut tx, signing_data, _fee_unshield_epoch) = - args.build(namada).await?; + let (mut tx, signing_data) = args.build(namada).await?; signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { @@ -1486,8 +1476,7 @@ pub async fn submit_reactivate_validator<'a, N: Namada<'a>>( where ::Error: std::fmt::Display, { - let (mut tx, signing_data, _fee_unshield_epoch) = - args.build(namada).await?; + let (mut tx, signing_data) = args.build(namada).await?; signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { @@ -1510,8 +1499,7 @@ pub async fn submit_update_steward_commission<'a, N: Namada<'a>>( where ::Error: std::fmt::Display, { - let (mut tx, signing_data, _fee_unshield_epoch) = - args.build(namada).await?; + let (mut tx, signing_data) = args.build(namada).await?; signing::generate_test_vector(namada, &tx).await?; @@ -1535,7 +1523,7 @@ pub async fn submit_resign_steward<'a, N: Namada<'a>>( where ::Error: std::fmt::Display, { - let (mut tx, signing_data, _epoch) = args.build(namada).await?; + let (mut tx, signing_data) = args.build(namada).await?; signing::generate_test_vector(namada, &tx).await?; diff --git a/sdk/src/args.rs b/sdk/src/args.rs index 3359c08ec4..210cb26a55 100644 --- a/sdk/src/args.rs +++ b/sdk/src/args.rs @@ -178,8 +178,7 @@ impl TxCustom { pub async fn build<'a>( &self, context: &impl Namada<'a>, - ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> - { + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData)> { tx::build_custom(context, self).await } } @@ -398,8 +397,7 @@ impl TxIbcTransfer { pub async fn build<'a>( &self, context: &impl Namada<'a>, - ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> - { + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData)> { tx::build_ibc_transfer(context, self).await } } @@ -487,8 +485,7 @@ impl InitProposal { pub async fn build<'a>( &self, context: &impl Namada<'a>, - ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> - { + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData)> { let current_epoch = rpc::query_epoch(context.client()).await?; let governance_parameters = rpc::query_governance_parameters(context.client()).await; @@ -644,8 +641,7 @@ impl VoteProposal { pub async fn build<'a>( &self, context: &impl Namada<'a>, - ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> - { + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData)> { let current_epoch = rpc::query_epoch(context.client()).await?; tx::build_vote_proposal(context, self, current_epoch).await } @@ -717,8 +713,7 @@ impl TxInitAccount { pub async fn build<'a>( &self, context: &impl Namada<'a>, - ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> - { + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData)> { tx::build_init_account(context, self).await } } @@ -835,8 +830,7 @@ impl TxUpdateAccount { pub async fn build<'a>( &self, context: &impl Namada<'a>, - ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> - { + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData)> { tx::build_update_account(context, self).await } } @@ -913,8 +907,7 @@ impl Bond { pub async fn build<'a>( &self, context: &impl Namada<'a>, - ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> - { + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData)> { tx::build_bond(context, self).await } } @@ -943,7 +936,6 @@ impl Unbond { ) -> crate::error::Result<( crate::proto::Tx, SigningTxData, - Option, Option<(Epoch, token::Amount)>, )> { tx::build_unbond(context, self).await @@ -1094,8 +1086,7 @@ impl RevealPk { pub async fn build<'a>( &self, context: &impl Namada<'a>, - ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> - { + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData)> { tx::build_reveal_pk(context, &self.tx, &self.public_key).await } } @@ -1178,8 +1169,7 @@ impl Withdraw { pub async fn build<'a>( &self, context: &impl Namada<'a>, - ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> - { + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData)> { tx::build_withdraw(context, self).await } } @@ -1215,8 +1205,7 @@ impl ClaimRewards { pub async fn build<'a>( &self, context: &impl Namada<'a>, - ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> - { + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData)> { tx::build_claim_rewards(context, self).await } } @@ -1348,8 +1337,7 @@ impl CommissionRateChange { pub async fn build<'a>( &self, context: &impl Namada<'a>, - ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> - { + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData)> { tx::build_validator_commission_change(context, self).await } } @@ -1466,8 +1454,7 @@ impl MetaDataChange { pub async fn build<'a>( &self, context: &impl Namada<'a>, - ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> - { + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData)> { tx::build_validator_metadata_change(context, self).await } } @@ -1522,8 +1509,7 @@ impl UpdateStewardCommission { pub async fn build<'a>( &self, context: &impl Namada<'a>, - ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> - { + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData)> { tx::build_update_steward_commission(context, self).await } } @@ -1571,8 +1557,7 @@ impl ResignSteward { pub async fn build<'a>( &self, context: &impl Namada<'a>, - ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> - { + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData)> { tx::build_resign_steward(context, self).await } } @@ -1620,8 +1605,7 @@ impl TxUnjailValidator { pub async fn build<'a>( &self, context: &impl Namada<'a>, - ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> - { + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData)> { tx::build_unjail_validator(context, self).await } } @@ -1669,8 +1653,7 @@ impl TxDeactivateValidator { pub async fn build<'a>( &self, context: &impl Namada<'a>, - ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> - { + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData)> { tx::build_deactivate_validator(context, self).await } } @@ -1718,8 +1701,7 @@ impl TxReactivateValidator { pub async fn build<'a>( &self, context: &impl Namada<'a>, - ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> - { + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData)> { tx::build_reactivate_validator(context, self).await } } @@ -2291,8 +2273,7 @@ impl EthereumBridgePool { pub async fn build<'a>( self, context: &impl Namada<'a>, - ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> - { + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData)> { bridge_pool::build_bridge_pool_tx(context, self).await } } diff --git a/sdk/src/eth_bridge/bridge_pool.rs b/sdk/src/eth_bridge/bridge_pool.rs index e682521987..83f0ac5142 100644 --- a/sdk/src/eth_bridge/bridge_pool.rs +++ b/sdk/src/eth_bridge/bridge_pool.rs @@ -18,7 +18,6 @@ use namada_core::types::eth_bridge_pool::{ }; use namada_core::types::ethereum_events::EthAddress; use namada_core::types::keccak::KeccakHash; -use namada_core::types::storage::Epoch; use namada_core::types::token::{balance_key, Amount, DenominatedAmount}; use namada_core::types::voting_power::FractionalVotingPower; use owo_colors::OwoColorize; @@ -60,7 +59,7 @@ pub async fn build_bridge_pool_tx( fee_token, code_path, }: args::EthereumBridgePool, -) -> Result<(Tx, SigningTxData, Option), Error> { +) -> Result<(Tx, SigningTxData), Error> { let sender_ = sender.clone(); let (transfer, tx_code_hash, signing_data) = futures::try_join!( validate_bridge_pool_tx( @@ -98,7 +97,7 @@ pub async fn build_bridge_pool_tx( ) .add_data(transfer); - let epoch = prepare_tx( + prepare_tx( context, &tx_args, &mut tx, @@ -107,7 +106,7 @@ pub async fn build_bridge_pool_tx( ) .await?; - Ok((tx, signing_data, epoch)) + Ok((tx, signing_data)) } /// Perform client validation checks on a Bridge pool transfer. diff --git a/sdk/src/signing.rs b/sdk/src/signing.rs index 6784be92ba..8882c16569 100644 --- a/sdk/src/signing.rs +++ b/sdk/src/signing.rs @@ -457,7 +457,7 @@ pub async fn wrap_tx<'a, N: Namada<'a>>( tx_source_balance: Option, epoch: Epoch, fee_payer: common::PublicKey, -) -> Result, Error> { +) -> Result<(), Error> { let fee_payer_address = Address::from(&fee_payer); // Validate fee amount and token let gas_cost_key = parameter_storage::get_gas_cost_key(); @@ -535,9 +535,7 @@ pub async fn wrap_tx<'a, N: Namada<'a>>( let total_fee = fee_amount * u64::from(args.gas_limit); - let (unshield, unshielding_epoch) = match total_fee - .checked_sub(updated_balance) - { + let unshield = match total_fee.checked_sub(updated_balance) { Some(diff) if !diff.is_zero() => { if let Some(spending_key) = args.fee_unshield.clone() { // Unshield funds for fee payment @@ -567,7 +565,7 @@ pub async fn wrap_tx<'a, N: Namada<'a>>( builder: _, masp_tx: transaction, metadata: _data, - epoch: unshielding_epoch, + epoch: _unshielding_epoch, })) => { let spends = transaction .sapling_bundle() @@ -610,7 +608,7 @@ pub async fn wrap_tx<'a, N: Namada<'a>>( } updated_balance += total_fee; - (Some(transaction), Some(unshielding_epoch)) + Some(transaction) } Ok(None) => { if !args.force { @@ -622,7 +620,7 @@ pub async fn wrap_tx<'a, N: Namada<'a>>( )); } - (None, None) + None } Err(e) => { if !args.force { @@ -631,7 +629,7 @@ pub async fn wrap_tx<'a, N: Namada<'a>>( )); } - (None, None) + None } } } else { @@ -651,7 +649,7 @@ pub async fn wrap_tx<'a, N: Namada<'a>>( ))); } - (None, None) + None } } _ => { @@ -662,7 +660,7 @@ pub async fn wrap_tx<'a, N: Namada<'a>>( unshielding spending key will be ignored" ); } - (None, None) + None } }; @@ -686,7 +684,7 @@ pub async fn wrap_tx<'a, N: Namada<'a>>( unshield_section_hash, ); - Ok(unshielding_epoch) + Ok(()) } #[allow(clippy::result_large_err)] diff --git a/sdk/src/tx.rs b/sdk/src/tx.rs index 9378615890..ad3e6d73f8 100644 --- a/sdk/src/tx.rs +++ b/sdk/src/tx.rs @@ -179,14 +179,14 @@ pub async fn prepare_tx<'a>( tx: &mut Tx, fee_payer: common::PublicKey, tx_source_balance: Option, -) -> Result> { +) -> Result<()> { if !args.dry_run { let epoch = rpc::query_epoch(context.client()).await?; signing::wrap_tx(context, tx, args, tx_source_balance, epoch, fee_payer) .await } else { - Ok(None) + Ok(()) } } @@ -270,7 +270,7 @@ pub async fn build_reveal_pk<'a>( context: &impl Namada<'a>, args: &args::Tx, public_key: &common::PublicKey, -) -> Result<(Tx, SigningTxData, Option)> { +) -> Result<(Tx, SigningTxData)> { let signing_data = signing::aux_signing_data(context, args, None, Some(public_key.into())) .await?; @@ -285,7 +285,7 @@ pub async fn build_reveal_pk<'a>( None, ) .await - .map(|(tx, epoch)| (tx, signing_data, epoch)) + .map(|tx| (tx, signing_data)) } /// Broadcast a transaction to be included in the blockchain and checks that @@ -514,7 +514,7 @@ pub async fn build_validator_commission_change<'a>( rate, tx_code_path, }: &args::CommissionRateChange, -) -> Result<(Tx, SigningTxData, Option)> { +) -> Result<(Tx, SigningTxData)> { let default_signer = Some(validator.clone()); let signing_data = signing::aux_signing_data( context, @@ -614,7 +614,7 @@ pub async fn build_validator_commission_change<'a>( None, ) .await - .map(|(tx, epoch)| (tx, signing_data, epoch)) + .map(|tx| (tx, signing_data)) } /// Submit validator metadata change @@ -630,7 +630,7 @@ pub async fn build_validator_metadata_change<'a>( commission_rate, tx_code_path, }: &args::MetaDataChange, -) -> Result<(Tx, SigningTxData, Option)> { +) -> Result<(Tx, SigningTxData)> { let default_signer = Some(validator.clone()); let signing_data = signing::aux_signing_data( context, @@ -743,7 +743,7 @@ pub async fn build_validator_metadata_change<'a>( None, ) .await - .map(|(tx, epoch)| (tx, signing_data, epoch)) + .map(|tx| (tx, signing_data)) } /// Craft transaction to update a steward commission @@ -755,7 +755,7 @@ pub async fn build_update_steward_commission<'a>( commission, tx_code_path, }: &args::UpdateStewardCommission, -) -> Result<(Tx, SigningTxData, Option)> { +) -> Result<(Tx, SigningTxData)> { let default_signer = Some(steward.clone()); let signing_data = signing::aux_signing_data( context, @@ -802,7 +802,7 @@ pub async fn build_update_steward_commission<'a>( None, ) .await - .map(|(tx, epoch)| (tx, signing_data, epoch)) + .map(|tx| (tx, signing_data)) } /// Craft transaction to resign as a steward @@ -813,7 +813,7 @@ pub async fn build_resign_steward<'a>( steward, tx_code_path, }: &args::ResignSteward, -) -> Result<(Tx, SigningTxData, Option)> { +) -> Result<(Tx, SigningTxData)> { let default_signer = Some(steward.clone()); let signing_data = signing::aux_signing_data( context, @@ -842,7 +842,7 @@ pub async fn build_resign_steward<'a>( None, ) .await - .map(|(tx, epoch)| (tx, signing_data, epoch)) + .map(|tx| (tx, signing_data)) } /// Submit transaction to unjail a jailed validator @@ -853,7 +853,7 @@ pub async fn build_unjail_validator<'a>( validator, tx_code_path, }: &args::TxUnjailValidator, -) -> Result<(Tx, SigningTxData, Option)> { +) -> Result<(Tx, SigningTxData)> { let default_signer = Some(validator.clone()); let signing_data = signing::aux_signing_data( context, @@ -949,7 +949,7 @@ pub async fn build_unjail_validator<'a>( None, ) .await - .map(|(tx, epoch)| (tx, signing_data, epoch)) + .map(|tx| (tx, signing_data)) } /// Submit transaction to deactivate a validator @@ -960,7 +960,7 @@ pub async fn build_deactivate_validator<'a>( validator, tx_code_path, }: &args::TxDeactivateValidator, -) -> Result<(Tx, SigningTxData, Option)> { +) -> Result<(Tx, SigningTxData)> { let default_signer = Some(validator.clone()); let signing_data = signing::aux_signing_data( context, @@ -1020,7 +1020,7 @@ pub async fn build_deactivate_validator<'a>( None, ) .await - .map(|(tx, epoch)| (tx, signing_data, epoch)) + .map(|tx| (tx, signing_data)) } /// Submit transaction to deactivate a validator @@ -1031,7 +1031,7 @@ pub async fn build_reactivate_validator<'a>( validator, tx_code_path, }: &args::TxReactivateValidator, -) -> Result<(Tx, SigningTxData, Option)> { +) -> Result<(Tx, SigningTxData)> { let default_signer = Some(validator.clone()); let signing_data = signing::aux_signing_data( context, @@ -1090,7 +1090,7 @@ pub async fn build_reactivate_validator<'a>( None, ) .await - .map(|(tx, epoch)| (tx, signing_data, epoch)) + .map(|tx| (tx, signing_data)) } /// Redelegate bonded tokens from one validator to another @@ -1270,7 +1270,7 @@ pub async fn build_redelegation<'a>( None, ) .await - .map(|(tx, _epoch)| (tx, signing_data)) + .map(|tx| (tx, signing_data)) } /// Submit transaction to withdraw an unbond @@ -1282,7 +1282,7 @@ pub async fn build_withdraw<'a>( source, tx_code_path, }: &args::Withdraw, -) -> Result<(Tx, SigningTxData, Option)> { +) -> Result<(Tx, SigningTxData)> { let default_address = source.clone().unwrap_or(validator.clone()); let default_signer = Some(default_address.clone()); let signing_data = signing::aux_signing_data( @@ -1353,7 +1353,7 @@ pub async fn build_withdraw<'a>( None, ) .await - .map(|(tx, epoch)| (tx, signing_data, epoch)) + .map(|tx| (tx, signing_data)) } /// Submit transaction to withdraw an unbond @@ -1365,7 +1365,7 @@ pub async fn build_claim_rewards<'a>( source, tx_code_path, }: &args::ClaimRewards, -) -> Result<(Tx, SigningTxData, Option)> { +) -> Result<(Tx, SigningTxData)> { let default_address = source.clone().unwrap_or(validator.clone()); let default_signer = Some(default_address.clone()); let signing_data = signing::aux_signing_data( @@ -1401,7 +1401,7 @@ pub async fn build_claim_rewards<'a>( None, ) .await - .map(|(tx, epoch)| (tx, signing_data, epoch)) + .map(|tx| (tx, signing_data)) } /// Submit a transaction to unbond @@ -1414,12 +1414,7 @@ pub async fn build_unbond<'a>( source, tx_code_path, }: &args::Unbond, -) -> Result<( - Tx, - SigningTxData, - Option, - Option<(Epoch, token::Amount)>, -)> { +) -> Result<(Tx, SigningTxData, Option<(Epoch, token::Amount)>)> { // Require a positive amount of tokens to be bonded if amount.is_zero() { edisplay_line!( @@ -1498,7 +1493,7 @@ pub async fn build_unbond<'a>( source: source.clone(), }; - let (tx, epoch) = build( + let tx = build( context, tx_args, tx_code_path.clone(), @@ -1508,7 +1503,7 @@ pub async fn build_unbond<'a>( None, ) .await?; - Ok((tx, signing_data, epoch, latest_withdrawal_pre)) + Ok((tx, signing_data, latest_withdrawal_pre)) } /// Query the unbonds post-tx @@ -1593,7 +1588,7 @@ pub async fn build_bond<'a>( native_token, tx_code_path, }: &args::Bond, -) -> Result<(Tx, SigningTxData, Option)> { +) -> Result<(Tx, SigningTxData)> { // Require a positive amount of tokens to be bonded if amount.is_zero() { edisplay_line!( @@ -1693,7 +1688,7 @@ pub async fn build_bond<'a>( tx_source_balance, ) .await - .map(|(tx, epoch)| (tx, signing_data, epoch)) + .map(|tx| (tx, signing_data)) } /// Build a default proposal governance @@ -1709,7 +1704,7 @@ pub async fn build_default_proposal<'a>( tx_code_path, }: &args::InitProposal, proposal: DefaultProposal, -) -> Result<(Tx, SigningTxData, Option)> { +) -> Result<(Tx, SigningTxData)> { let default_signer = Some(proposal.proposal.author.clone()); let signing_data = signing::aux_signing_data( context, @@ -1746,7 +1741,7 @@ pub async fn build_default_proposal<'a>( None, // TODO: need to pay the fee to submit a proposal ) .await - .map(|(tx, epoch)| (tx, signing_data, epoch)) + .map(|tx| (tx, signing_data)) } /// Build a proposal vote @@ -1762,7 +1757,7 @@ pub async fn build_vote_proposal<'a>( tx_code_path, }: &args::VoteProposal, epoch: Epoch, -) -> Result<(Tx, SigningTxData, Option)> { +) -> Result<(Tx, SigningTxData)> { let default_signer = Some(voter.clone()); let signing_data = signing::aux_signing_data( context, @@ -1833,7 +1828,7 @@ pub async fn build_vote_proposal<'a>( None, ) .await - .map(|(tx, epoch)| (tx, signing_data, epoch)) + .map(|tx| (tx, signing_data)) } /// Build a pgf funding proposal governance @@ -1849,7 +1844,7 @@ pub async fn build_pgf_funding_proposal<'a>( tx_code_path, }: &args::InitProposal, proposal: PgfFundingProposal, -) -> Result<(Tx, SigningTxData, Option)> { +) -> Result<(Tx, SigningTxData)> { let default_signer = Some(proposal.proposal.author.clone()); let signing_data = signing::aux_signing_data( context, @@ -1878,7 +1873,7 @@ pub async fn build_pgf_funding_proposal<'a>( None, // TODO: need to pay the fee to submit a proposal ) .await - .map(|(tx, epoch)| (tx, signing_data, epoch)) + .map(|tx| (tx, signing_data)) } /// Build a pgf funding proposal governance @@ -1894,7 +1889,7 @@ pub async fn build_pgf_stewards_proposal<'a>( tx_code_path, }: &args::InitProposal, proposal: PgfStewardProposal, -) -> Result<(Tx, SigningTxData, Option)> { +) -> Result<(Tx, SigningTxData)> { let default_signer = Some(proposal.proposal.author.clone()); let signing_data = signing::aux_signing_data( context, @@ -1924,14 +1919,14 @@ pub async fn build_pgf_stewards_proposal<'a>( None, // TODO: need to pay the fee to submit a proposal ) .await - .map(|(tx, epoch)| (tx, signing_data, epoch)) + .map(|tx| (tx, signing_data)) } /// Submit an IBC transfer pub async fn build_ibc_transfer<'a>( context: &impl Namada<'a>, args: &args::TxIbcTransfer, -) -> Result<(Tx, SigningTxData, Option)> { +) -> Result<(Tx, SigningTxData)> { let default_signer = Some(args.source.clone()); let signing_data = signing::aux_signing_data( context, @@ -2043,7 +2038,7 @@ pub async fn build_ibc_transfer<'a>( ) .add_serialized_data(data); - let epoch = prepare_tx( + prepare_tx( context, &args.tx, &mut tx, @@ -2052,7 +2047,7 @@ pub async fn build_ibc_transfer<'a>( ) .await?; - Ok((tx, signing_data, epoch)) + Ok((tx, signing_data)) } /// Abstraction for helping build transactions @@ -2065,7 +2060,7 @@ pub async fn build<'a, F, D>( on_tx: F, gas_payer: &common::PublicKey, tx_source_balance: Option, -) -> Result<(Tx, Option)> +) -> Result where F: FnOnce(&mut Tx, &mut D) -> Result<()>, D: BorshSerialize, @@ -2091,7 +2086,7 @@ async fn build_pow_flag<'a, F, D>( on_tx: F, gas_payer: &common::PublicKey, tx_source_balance: Option, -) -> Result<(Tx, Option)> +) -> Result where F: FnOnce(&mut Tx, &mut D) -> Result<()>, D: BorshSerialize, @@ -2113,7 +2108,7 @@ where ) .add_data(data); - let epoch = prepare_tx( + prepare_tx( context, tx_args, &mut tx_builder, @@ -2121,7 +2116,7 @@ where tx_source_balance, ) .await?; - Ok((tx_builder, epoch)) + Ok(tx_builder) } /// Try to decode the given asset type and add its decoding to the supplied set. @@ -2318,7 +2313,7 @@ pub async fn build_transfer<'a, N: Namada<'a>>( }; Ok(()) }; - let (tx, unshielding_epoch) = build_pow_flag( + let tx = build_pow_flag( context, &args.tx, args.tx_code_path.clone(), @@ -2328,26 +2323,7 @@ pub async fn build_transfer<'a, N: Namada<'a>>( tx_source_balance, ) .await?; - // Manage the two masp epochs - let masp_epoch = match (unshielding_epoch, shielded_tx_epoch) { - (Some(fee_unshield_epoch), Some(transfer_unshield_epoch)) => { - // If the two masp epochs are different, either the wrapper or the - // inner tx will fail, so abort tx creation - if fee_unshield_epoch != transfer_unshield_epoch && !args.tx.force { - return Err(Error::Other( - "Fee unshielding masp tx and inner tx masp transaction \ - were crafted on an epoch boundary" - .to_string(), - )); - } - // Take the smaller of the two epochs - Some(fee_unshield_epoch.min(transfer_unshield_epoch)) - } - (Some(_fee_unshielding_epoch), None) => unshielding_epoch, - (None, Some(_transfer_unshield_epoch)) => shielded_tx_epoch, - (None, None) => None, - }; - Ok((tx, signing_data, masp_epoch)) + Ok((tx, signing_data, shielded_tx_epoch)) } /// Submit a transaction to initialize an account @@ -2360,7 +2336,7 @@ pub async fn build_init_account<'a>( public_keys, threshold, }: &args::TxInitAccount, -) -> Result<(Tx, SigningTxData, Option)> { +) -> Result<(Tx, SigningTxData)> { let signing_data = signing::aux_signing_data(context, tx_args, None, None).await?; @@ -2402,7 +2378,7 @@ pub async fn build_init_account<'a>( None, ) .await - .map(|(tx, epoch)| (tx, signing_data, epoch)) + .map(|tx| (tx, signing_data)) } /// Submit a transaction to update a VP @@ -2416,7 +2392,7 @@ pub async fn build_update_account<'a>( public_keys, threshold, }: &args::TxUpdateAccount, -) -> Result<(Tx, SigningTxData, Option)> { +) -> Result<(Tx, SigningTxData)> { let default_signer = Some(addr.clone()); let signing_data = signing::aux_signing_data( context, @@ -2484,7 +2460,7 @@ pub async fn build_update_account<'a>( None, ) .await - .map(|(tx, epoch)| (tx, signing_data, epoch)) + .map(|tx| (tx, signing_data)) } /// Submit a custom transaction @@ -2497,7 +2473,7 @@ pub async fn build_custom<'a>( serialized_tx, owner, }: &args::TxCustom, -) -> Result<(Tx, SigningTxData, Option)> { +) -> Result<(Tx, SigningTxData)> { let default_signer = Some(owner.clone()); let signing_data = signing::aux_signing_data( context, @@ -2526,7 +2502,7 @@ pub async fn build_custom<'a>( tx }; - let epoch = prepare_tx( + prepare_tx( context, tx_args, &mut tx, @@ -2535,7 +2511,7 @@ pub async fn build_custom<'a>( ) .await?; - Ok((tx, signing_data, epoch)) + Ok((tx, signing_data)) } /// Generate IBC shielded transfer From 616987fe73a04f92d63b5f1a9ffba614c5e75453 Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Wed, 13 Dec 2023 22:15:53 +0100 Subject: [PATCH 100/216] Fix `find` command; refactoring --- apps/src/lib/cli.rs | 89 ++++++------- apps/src/lib/cli/wallet.rs | 256 ++++++++++++++++++++++--------------- sdk/src/args.rs | 8 +- sdk/src/wallet/mod.rs | 25 +++- 4 files changed, 214 insertions(+), 164 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 4d4b2e69a6..3718685087 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -6111,57 +6111,54 @@ pub mod args { impl Args for KeyAddressList { fn parse(matches: &ArgMatches) -> Self { - let decrypt = DECRYPT.parse(matches); let transparent_only = TRANSPARENT.parse(matches); let shielded_only = SHIELDED.parse(matches); let keys_only = LIST_FIND_KEYS_ONLY.parse(matches); let addresses_only = LIST_FIND_ADDRESSES_ONLY.parse(matches); + let decrypt = DECRYPT.parse(matches); let unsafe_show_secret = UNSAFE_SHOW_SECRET.parse(matches); Self { - decrypt, transparent_only, shielded_only, keys_only, addresses_only, + decrypt, unsafe_show_secret, } } fn def(app: App) -> App { - app.arg(DECRYPT.def().help("Decrypt keys that are encrypted.")) - .arg( - TRANSPARENT - .def() - .help("List transparent keys / addresses only."), - ) - .arg( - SHIELDED.def().help( - "List keys / addresses of the shielded pool only.", - ), - ) - .group( - ArgGroup::new("only_group_1") - .args([TRANSPARENT.name, SHIELDED.name]), - ) - .arg(LIST_FIND_KEYS_ONLY.def().help("List keys only.")) - .arg( - LIST_FIND_ADDRESSES_ONLY.def().help("List addresses only."), - ) - .group(ArgGroup::new("only_group_2").args([ - LIST_FIND_KEYS_ONLY.name, - LIST_FIND_ADDRESSES_ONLY.name, - ])) - .arg( - UNSAFE_SHOW_SECRET - .def() - .help("UNSAFE: Print the secret / spending keys."), - ) + app.arg( + TRANSPARENT + .def() + .help("List transparent keys / addresses only."), + ) + .arg( + SHIELDED + .def() + .help("List keys / addresses of the shielded pool only."), + ) + .group( + ArgGroup::new("only_group_1") + .args([TRANSPARENT.name, SHIELDED.name]), + ) + .arg(LIST_FIND_KEYS_ONLY.def().help("List keys only.")) + .arg(LIST_FIND_ADDRESSES_ONLY.def().help("List addresses only.")) + .group(ArgGroup::new("only_group_2").args([ + LIST_FIND_KEYS_ONLY.name, + LIST_FIND_ADDRESSES_ONLY.name, + ])) + .arg(DECRYPT.def().help("Decrypt keys that are encrypted.")) + .arg( + UNSAFE_SHOW_SECRET + .def() + .help("UNSAFE: Print the secret / spending keys."), + ) } } impl Args for KeyAddressFind { fn parse(matches: &ArgMatches) -> Self { - let shielded = SHIELDED.parse(matches); let alias = ALIAS_OPT.parse(matches); let address = RAW_ADDRESS_OPT.parse(matches); let public_key = RAW_PUBLIC_KEY_OPT.parse(matches); @@ -6169,9 +6166,9 @@ pub mod args { let payment_address = RAW_PAYMENT_ADDRESS_OPT.parse(matches); let keys_only = LIST_FIND_KEYS_ONLY.parse(matches); let addresses_only = LIST_FIND_ADDRESSES_ONLY.parse(matches); + let decrypt = DECRYPT.parse(matches); let unsafe_show_secret = UNSAFE_SHOW_SECRET.parse(matches); Self { - shielded, alias, address, public_key, @@ -6179,28 +6176,21 @@ pub mod args { payment_address, keys_only, addresses_only, + decrypt, unsafe_show_secret, } } fn def(app: App) -> App { app.arg( - SHIELDED.def().help( - "Find keys and payment addresses for the shielded pool.", - ), - ) - .arg( ALIAS_OPT .def() .help("An alias associated with the keys / addresses."), ) .arg( - RAW_ADDRESS_OPT - .def() - .help( - "The bech32m encoded string of a transparent address.", - ) - .conflicts_with(SHIELDED.name), + RAW_ADDRESS_OPT.def().help( + "The bech32m encoded string of a transparent address.", + ), ) .arg( RAW_PUBLIC_KEY_OPT.def().help( @@ -6210,15 +6200,9 @@ pub mod args { .arg(RAW_PUBLIC_KEY_HASH_OPT.def().help( "A public key hash associated with the transparent keypair.", )) - .arg( - RAW_PAYMENT_ADDRESS_OPT - .def() - .help( - "The bech32m encoded string of a shielded payment \ - address.", - ) - .conflicts_with(SHIELDED.name), - ) + .arg(RAW_PAYMENT_ADDRESS_OPT.def().help( + "The bech32m encoded string of a shielded payment address.", + )) .group( ArgGroup::new("addr_find_args") .args([ @@ -6240,6 +6224,7 @@ pub mod args { "Use pre-genesis wallet, instead of for the current chain, if \ any.", )) + .arg(DECRYPT.def().help("Decrypt keys that are encrypted.")) .arg( UNSAFE_SHOW_SECRET .def() diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index fbab002af6..bfb9ec9eda 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -91,27 +91,26 @@ fn spending_keys_list( ); } } else { - let stdout = io::stdout(); - let mut w = stdout.lock(); - display_line!(io, &mut w; "Known keys:").unwrap(); + let mut w_lock = io::stdout().lock(); + display_line!(io, &mut w_lock; "Known shielded keys:").unwrap(); for (alias, key) in known_view_keys { - display!(io, &mut w; " Alias \"{}\"", alias).unwrap(); + display!(io, &mut w_lock; " Alias \"{}\"", alias).unwrap(); let spending_key_opt = known_spend_keys.get(&alias); // If this alias is associated with a spending key, indicate whether // or not the spending key is encrypted // TODO: consider turning if let into match if let Some(spending_key) = spending_key_opt { if spending_key.is_encrypted() { - display_line!(io, &mut w; " (encrypted):") + display_line!(io, &mut w_lock; " (encrypted):") } else { - display_line!(io, &mut w; " (not encrypted):") + display_line!(io, &mut w_lock; " (not encrypted):") } .unwrap(); } else { - display_line!(io, &mut w; ":").unwrap(); + display_line!(io, &mut w_lock; ":").unwrap(); } // Always print the corresponding viewing key - display_line!(io, &mut w; " Viewing Key: {}", key).unwrap(); + display_line!(io, &mut w_lock; " Viewing Key: {}", key).unwrap(); // A subset of viewing keys will have corresponding spending keys. // Print those too if they are available and requested. if unsafe_show_secret { @@ -121,7 +120,7 @@ fn spending_keys_list( // decrypted Ok(spending_key) => { display_line!(io, - &mut w; + &mut w_lock; " Spending key: {}", spending_key, ) .unwrap(); @@ -135,7 +134,7 @@ fn spending_keys_list( // been provided Err(err) => { display_line!(io, - &mut w; + &mut w_lock; " Couldn't decrypt the spending key: {}", err, ) @@ -164,11 +163,11 @@ fn payment_addresses_list( ); } } else { - let stdout = io::stdout(); - let mut w = stdout.lock(); - display_line!(io, &mut w; "Known payment addresses:").unwrap(); + let mut w_lock = io::stdout().lock(); + display_line!(io, &mut w_lock; "Known payment addresses:").unwrap(); for (alias, address) in sorted(known_addresses) { - display_line!(io, &mut w; " \"{}\": {}", alias, address).unwrap(); + display_line!(io, &mut w_lock; " \"{}\": {}", alias, address) + .unwrap(); } } } @@ -549,7 +548,6 @@ fn key_address_find( ctx: Context, io: &impl Io, args::KeyAddressFind { - shielded, alias, address, public_key, @@ -557,29 +555,33 @@ fn key_address_find( payment_address, keys_only, addresses_only, + decrypt, unsafe_show_secret, }: args::KeyAddressFind, ) { if let Some(alias) = alias { // Search keys and addresses by alias - if !shielded { - transparent_key_address_find_by_alias( - ctx, - io, - alias, - keys_only, - addresses_only, - unsafe_show_secret, - ) - } else { - shielded_key_address_find_by_alias( - ctx, - io, - alias, - keys_only, - addresses_only, - unsafe_show_secret, - ) + let mut wallet = load_wallet(ctx); + let found_transparent = transparent_key_address_find_by_alias( + &mut wallet, + io, + alias.clone(), + keys_only, + addresses_only, + decrypt, + unsafe_show_secret, + ); + let found_shielded = shielded_key_address_find_by_alias( + &mut wallet, + io, + alias.clone(), + keys_only, + addresses_only, + decrypt, + unsafe_show_secret, + ); + if !found_transparent && !found_shielded { + display_line!(io, "Alias \"{}\" not found.", alias); } } else if address.is_some() { // Search alias by address @@ -707,7 +709,7 @@ fn transparent_key_find( let found_keypair = match public_key { Some(pk) => wallet.find_key_by_pk(&pk, None), None => { - let alias = alias.or(public_key_hash); + let alias = alias.map(|a| a.to_lowercase()).or(public_key_hash); match alias { None => { edisplay_line!( @@ -717,9 +719,7 @@ fn transparent_key_find( ); cli::safe_exit(1) } - Some(alias) => { - wallet.find_secret_key(alias.to_lowercase(), None) - } + Some(alias) => wallet.find_secret_key(alias, None), } } }; @@ -821,85 +821,133 @@ fn payment_address_or_alias_find( /// Find transparent addresses and keys by alias fn transparent_key_address_find_by_alias( - ctx: Context, + wallet: &mut Wallet, io: &impl Io, alias: String, keys_only: bool, addresses_only: bool, + decrypt: bool, unsafe_show_secret: bool, -) { - let mut wallet = load_wallet(ctx); +) -> bool { let alias = alias.to_lowercase(); - if let Some(keypair) = (!addresses_only) - .then_some(()) - .and_then(|_| wallet.find_secret_key(&alias, None).ok()) - { - let pkh: PublicKeyHash = (&keypair.ref_to()).into(); - display_line!(io, "Public key hash: {}", pkh); - display_line!(io, "Public key: {}", keypair.ref_to()); - if unsafe_show_secret { - display_line!(io, "Secret key: {}", keypair); + let mut w_lock = io::stdout().lock(); + let mut found = false; + + // Find transparent keys + if !addresses_only { + // Check if alias is a public key + if let Ok(public_key) = wallet.find_public_key(&alias) { + found = true; + display_line!(io, &mut w_lock; "Found transparent keys:").unwrap(); + let encrypted = match wallet.is_encrypted_secret_key(&alias) { + None => "external", + Some(res) if res => "encrypted", + _ => "not encrypted", + }; + display_line!(io, + &mut w_lock; + " Alias \"{}\" ({}):", alias, encrypted, + ) + .unwrap(); + let pkh = PublicKeyHash::from(&public_key); + display_line!(io, &mut w_lock; " Public key hash: {}", pkh) + .unwrap(); + display_line!( + io, + &mut w_lock; + " Public key: {}", + public_key + ) + .unwrap(); + if decrypt { + // Check if alias is also a secret key + if let Ok(keypair) = wallet.find_secret_key(&alias, None) { + if unsafe_show_secret { + display_line!(io, &mut w_lock; " Secret key: {}", keypair) + .unwrap(); + } + } + } } - } else if let Some(address) = (!keys_only) - .then_some(()) - .and_then(|_| wallet.find_address(&alias)) - { - display_line!(io, "Found address {}", address.to_pretty_string()); - } else if !addresses_only && !keys_only { - // Otherwise alias cannot be referring to any shielded value - display_line!( - io, - "No transparent address or key with alias {} found. Use the \ - command `list` to see all the known transparent addresses and \ - keys.", - alias - ); } + + // Find transparent address + if !keys_only { + if let Some(address) = wallet.find_address(&alias) { + found = true; + display_line!(io, &mut w_lock; "Found transparent address:") + .unwrap(); + display_line!(io, + &mut w_lock; + " \"{}\": {}", alias, address.to_pretty_string(), + ) + .unwrap(); + } + } + + found } /// Find shielded payment address and keys by alias fn shielded_key_address_find_by_alias( - ctx: Context, + wallet: &mut Wallet, io: &impl Io, alias: String, keys_only: bool, addresses_only: bool, + decrypt: bool, unsafe_show_secret: bool, -) { - let mut wallet = load_wallet(ctx); +) -> bool { let alias = alias.to_lowercase(); - if let Some(viewing_key) = (!addresses_only) - .then_some(()) - .and_then(|_| wallet.find_viewing_key(&alias).ok()) - { + let mut w_lock = io::stdout().lock(); + let mut found = false; + + // Find shielded keys + if !addresses_only { + let encrypted = match wallet.is_encrypted_spending_key(&alias) { + None => "external", + Some(res) if res => "encrypted", + _ => "not encrypted", + }; // Check if alias is a viewing key - display_line!(io, "Viewing key: {}", viewing_key); - if unsafe_show_secret { - // Check if alias is also a spending key - match wallet.find_spending_key(&alias, None) { - Ok(spending_key) => { - display_line!(io, "Spending key: {}", spending_key) + if let Ok(viewing_key) = wallet.find_viewing_key(&alias) { + found = true; + display_line!(io, &mut w_lock; "Found shielded keys:").unwrap(); + display_line!(io, + &mut w_lock; + " Alias \"{}\" ({}):", alias, encrypted, + ) + .unwrap(); + display_line!(io, &mut w_lock; " Viewing key: {}", viewing_key) + .unwrap(); + if decrypt { + // Check if alias is also a spending key + match wallet.find_spending_key(&alias, None) { + Ok(spending_key) => { + if unsafe_show_secret { + display_line!(io, &mut w_lock; " Spending key: {}", spending_key).unwrap(); + } + } + Err(FindKeyError::KeyNotFound(_)) => {} + Err(err) => edisplay_line!(io, "{}", err), } - Err(FindKeyError::KeyNotFound(_)) => {} - Err(err) => edisplay_line!(io, "{}", err), } } - } else if let Some(payment_addr) = (!keys_only) - .then_some(()) - .and_then(|_| wallet.find_payment_addr(&alias)) - { - // Failing that, check if alias is a payment address - display_line!(io, "Payment address: {}", payment_addr); - } else if !addresses_only && !keys_only { - // Otherwise alias cannot be referring to any shielded value - display_line!( - io, - "No shielded payment address or key with alias {} found. Use the \ - command `list --shielded` to see all the known shielded \ - addresses and keys.", - alias - ); } + + // Find payment addresses + if !keys_only { + if let Some(payment_addr) = wallet.find_payment_addr(&alias) { + found = true; + display_line!(io, &mut w_lock; "Found payment address:").unwrap(); + display_line!(io, + &mut w_lock; + " \"{}\": {}", alias, payment_addr.to_string(), + ) + .unwrap(); + } + } + found } /// List all known keys. @@ -920,9 +968,8 @@ fn transparent_keys_list( ); } } else { - let stdout = io::stdout(); - let mut w = stdout.lock(); - display_line!(io, &mut w; "Known keys:").unwrap(); + let mut w_lock = io::stdout().lock(); + display_line!(io, &mut w_lock; "Known transparent keys:").unwrap(); let known_secret_keys = wallet.get_secret_keys(); for (alias, public_key) in known_public_keys { let stored_keypair = known_secret_keys.get(&alias); @@ -936,19 +983,19 @@ fn transparent_keys_list( Some(_) => "not encrypted", }; display_line!(io, - &mut w; + &mut w_lock; " Alias \"{}\" ({}):", alias, encrypted, ) .unwrap(); - display_line!(io, &mut w; " Public key hash: {}", PublicKeyHash::from(&public_key)) + display_line!(io, &mut w_lock; " Public key hash: {}", PublicKeyHash::from(&public_key)) .unwrap(); - display_line!(io, &mut w; " Public key: {}", public_key) + display_line!(io, &mut w_lock; " Public key: {}", public_key) .unwrap(); if let Some((stored_keypair, _pkh)) = stored_keypair { match stored_keypair.get::(decrypt, None) { Ok(keypair) if unsafe_show_secret => { display_line!(io, - &mut w; + &mut w_lock; " Secret key: {}", keypair, ) .unwrap(); @@ -959,7 +1006,7 @@ fn transparent_keys_list( } Err(err) => { display_line!(io, - &mut w; + &mut w_lock; " Couldn't decrypt the keypair: {}", err, ) .unwrap(); @@ -1040,12 +1087,11 @@ fn transparent_addresses_list( ); } } else { - let stdout = io::stdout(); - let mut w = stdout.lock(); - display_line!(io, &mut w; "Known addresses:").unwrap(); + let mut w_lock = io::stdout().lock(); + display_line!(io, &mut w_lock; "Known transparent addresses:").unwrap(); for (alias, address) in sorted(known_addresses) { display_line!(io, - &mut w; + &mut w_lock; " \"{}\": {}", alias, address.to_pretty_string(), ) .unwrap(); diff --git a/sdk/src/args.rs b/sdk/src/args.rs index ee18335dfd..dde5953025 100644 --- a/sdk/src/args.rs +++ b/sdk/src/args.rs @@ -2081,8 +2081,6 @@ pub struct KeyDerive { /// Wallet list arguments #[derive(Clone, Copy, Debug)] pub struct KeyAddressList { - /// Whether to decrypt secret / spending keys - pub decrypt: bool, /// Whether to list transparent secret keys only pub transparent_only: bool, /// Whether to list MASP spending keys only @@ -2091,6 +2089,8 @@ pub struct KeyAddressList { pub keys_only: bool, /// List addresses only pub addresses_only: bool, + /// Whether to decrypt secret / spending keys + pub decrypt: bool, /// Show secret keys to user pub unsafe_show_secret: bool, } @@ -2098,8 +2098,6 @@ pub struct KeyAddressList { /// Wallet key / address lookup arguments #[derive(Clone, Debug)] pub struct KeyAddressFind { - /// Whether to find MASP keys / addresses - pub shielded: bool, /// Alias to find pub alias: Option, /// Address to find @@ -2114,6 +2112,8 @@ pub struct KeyAddressFind { pub keys_only: bool, /// Find addresses only pub addresses_only: bool, + /// Whether to decrypt secret / spending keys + pub decrypt: bool, /// Show secret keys to user pub unsafe_show_secret: bool, } diff --git a/sdk/src/wallet/mod.rs b/sdk/src/wallet/mod.rs index f17e4ba90f..d59d042452 100644 --- a/sdk/src/wallet/mod.rs +++ b/sdk/src/wallet/mod.rs @@ -485,6 +485,26 @@ impl Wallet { .map(|(alias, value)| (alias.into(), value)) .collect() } + + /// Check if alias is an encrypted secret key + pub fn is_encrypted_secret_key( + &self, + alias: impl AsRef, + ) -> Option { + self.store + .find_secret_key(alias) + .map(|stored_keypair| stored_keypair.is_encrypted()) + } + + /// Check if alias is an encrypted spending key + pub fn is_encrypted_spending_key( + &self, + alias: impl AsRef, + ) -> Option { + self.store + .find_spending_key(alias) + .map(|stored_spend_key| stored_spend_key.is_encrypted()) + } } impl Wallet { @@ -567,9 +587,8 @@ impl Wallet { /// the keypair for the alias. /// If no encryption password is provided, the keypair will be stored raw /// without encryption. - /// Stores the key in decrypted key cache and - /// returns the alias of the key and a reference-counting pointer to the - /// key. + /// Stores the key in decrypted key cache and returns the alias of the key + /// and a reference-counting pointer to the key. pub fn gen_store_secret_key( &mut self, scheme: SchemeType, From bc36fa660c083a5cd4180dded8b7f67af5321173 Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Wed, 13 Dec 2023 22:41:32 +0100 Subject: [PATCH 101/216] Refactoring --- apps/src/lib/cli/wallet.rs | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index bfb9ec9eda..f7cc7af7a5 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -71,8 +71,8 @@ impl CliApi { } } -/// List spending keys. -fn spending_keys_list( +/// List shielded keys. +fn shielded_keys_list( wallet: &Wallet, io: &impl Io, decrypt: bool, @@ -94,21 +94,15 @@ fn spending_keys_list( let mut w_lock = io::stdout().lock(); display_line!(io, &mut w_lock; "Known shielded keys:").unwrap(); for (alias, key) in known_view_keys { - display!(io, &mut w_lock; " Alias \"{}\"", alias).unwrap(); let spending_key_opt = known_spend_keys.get(&alias); // If this alias is associated with a spending key, indicate whether // or not the spending key is encrypted - // TODO: consider turning if let into match - if let Some(spending_key) = spending_key_opt { - if spending_key.is_encrypted() { - display_line!(io, &mut w_lock; " (encrypted):") - } else { - display_line!(io, &mut w_lock; " (not encrypted):") - } - .unwrap(); - } else { - display_line!(io, &mut w_lock; ":").unwrap(); - } + let encrypted_status = match spending_key_opt { + None => "", + Some(spend_key) if spend_key.is_encrypted() => "(encrypted)", + _ => "(not encrypted)", + }; + display!(io, &mut w_lock; " Alias \"{}\" {}", alias, encrypted_status).unwrap(); // Always print the corresponding viewing key display_line!(io, &mut w_lock; " Viewing Key: {}", key).unwrap(); // A subset of viewing keys will have corresponding spending keys. @@ -529,7 +523,7 @@ fn key_address_list( if !transparent_only { if !addresses_only { - spending_keys_list( + shielded_keys_list( &wallet, io, decrypt, @@ -850,6 +844,7 @@ fn transparent_key_address_find_by_alias( ) .unwrap(); let pkh = PublicKeyHash::from(&public_key); + // Always print the public key and hash display_line!(io, &mut w_lock; " Public key hash: {}", pkh) .unwrap(); display_line!( @@ -860,7 +855,8 @@ fn transparent_key_address_find_by_alias( ) .unwrap(); if decrypt { - // Check if alias is also a secret key + // Check if alias is also a secret key. Decrypt and print it if + // requested. if let Ok(keypair) = wallet.find_secret_key(&alias, None) { if unsafe_show_secret { display_line!(io, &mut w_lock; " Secret key: {}", keypair) @@ -918,10 +914,12 @@ fn shielded_key_address_find_by_alias( " Alias \"{}\" ({}):", alias, encrypted, ) .unwrap(); + // Always print the viewing key display_line!(io, &mut w_lock; " Viewing key: {}", viewing_key) .unwrap(); if decrypt { - // Check if alias is also a spending key + // Check if alias is also a spending key. Decrypt and print it + // if requested. match wallet.find_spending_key(&alias, None) { Ok(spending_key) => { if unsafe_show_secret { @@ -987,10 +985,13 @@ fn transparent_keys_list( " Alias \"{}\" ({}):", alias, encrypted, ) .unwrap(); + // Always print the corresponding public key and hash display_line!(io, &mut w_lock; " Public key hash: {}", PublicKeyHash::from(&public_key)) .unwrap(); display_line!(io, &mut w_lock; " Public key: {}", public_key) .unwrap(); + // A subset of public keys will have corresponding secret keys. + // Print those too if they are available and requested. if let Some((stored_keypair, _pkh)) = stored_keypair { match stored_keypair.get::(decrypt, None) { Ok(keypair) if unsafe_show_secret => { From 5a3a5d67726313bdf11388472b8f8df4715ca1d8 Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Wed, 13 Dec 2023 23:13:55 +0100 Subject: [PATCH 102/216] Allow to add public keys --- apps/src/lib/cli.rs | 8 +++---- apps/src/lib/cli/wallet.rs | 44 ++++++++++++++++++++++++++++++++------ 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 3718685087..55e2cf09bd 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -6257,10 +6257,10 @@ pub mod args { "Override the alias without confirmation if it already exists.", )) .arg(VALUE.def().help( - "Any value of the following:\n- transparent pool secret key \ - string\n- the bech32m encoded transparent address string\n- \ - shielded pool spending key\n- shielded pool viewing key\n- \ - shielded pool payment address ", + "Any value of the following:\n- transparent pool secret \ + key\n- transparent pool public key\n- transparent pool \ + address\n- shielded pool spending key\n- shielded pool \ + viewing key\n- shielded pool payment address ", )) .arg(UNSAFE_DONT_ENCRYPT.def().help( "UNSAFE: Do not encrypt the added keys. Do not use this for \ diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index f7cc7af7a5..a52f8128c3 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -600,6 +600,8 @@ fn key_address_find( pub enum TransparentValue { /// Transparent secret key TranspSecretKey(common::SecretKey), + /// Transparent public key + TranspPublicKey(common::PublicKey), /// Transparent address TranspAddress(Address), } @@ -608,9 +610,13 @@ impl FromStr for TransparentValue { type Err = DecodeError; fn from_str(s: &str) -> Result { - // Try to decode this value first as a secret key, then as an address + // Try to decode this value first as a secret key, then as a public key, + // then as an address common::SecretKey::from_str(s) .map(Self::TranspSecretKey) + .or_else(|_| { + common::PublicKey::from_str(s).map(Self::TranspPublicKey) + }) .or_else(|_| Address::from_str(s).map(Self::TranspAddress)) } } @@ -656,6 +662,9 @@ fn add_key_or_address( unsafe_dont_encrypt, ) } + KeyAddrAddValue::TranspValue(TransparentValue::TranspPublicKey( + pubkey, + )) => transparent_public_key_add(ctx, io, alias, alias_force, pubkey), KeyAddrAddValue::TranspValue(TransparentValue::TranspAddress( address, )) => transparent_address_add(ctx, io, alias, alias_force, address), @@ -1129,6 +1138,33 @@ fn transparent_secret_key_add( ); } +/// Add a public key to the wallet. +fn transparent_public_key_add( + ctx: Context, + io: &impl Io, + alias: String, + alias_force: bool, + pubkey: common::PublicKey, +) { + let alias = alias.to_lowercase(); + let mut wallet = load_wallet(ctx); + if wallet + .insert_public_key(alias.clone(), pubkey, None, None, alias_force) + .is_none() + { + edisplay_line!(io, "Public key not added"); + cli::safe_exit(1); + } + wallet + .save() + .unwrap_or_else(|err| edisplay_line!(io, "{}", err)); + display_line!( + io, + "Successfully added public key with alias: \"{}\"", + alias + ); +} + /// Add a transparent address to the wallet. fn transparent_address_add( ctx: Context, @@ -1149,11 +1185,7 @@ fn transparent_address_add( wallet .save() .unwrap_or_else(|err| edisplay_line!(io, "{}", err)); - display_line!( - io, - "Successfully added a key and an address with alias: \"{}\"", - alias - ); + display_line!(io, "Successfully added address with alias: \"{}\"", alias); } /// Load wallet for chain when `ctx.chain.is_some()` or pre-genesis wallet when From bb8cd8611ca9a21fbe688b21ab50fd45edb135a2 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 13 Dec 2023 19:14:11 +0100 Subject: [PATCH 103/216] Minor refactor to masp fee unshield tests --- shared/src/ledger/protocol/mod.rs | 6 +--- tests/src/e2e/ledger_tests.rs | 3 ++ tests/src/integration/masp.rs | 55 ++++++++++++++++--------------- 3 files changed, 33 insertions(+), 31 deletions(-) diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index 3dd3a8710d..7af980b913 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -450,11 +450,7 @@ where This shouldn't happen." ); - Err(Error::FeeError(format!( - "{}. All the available transparent funds have been moved to \ - the block proposer", - e - ))) + Err(Error::FeeError(format!("{}", e))) } } } diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 9f584e2b24..0d4bd72b03 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -773,6 +773,9 @@ fn wrapper_disposable_signer() -> Result<()> { "--disposable-gas-payer", "--ledger-address", &validator_one_rpc, + // NOTE: Forcing the transaction will make the client produce a + // transfer without a masp object attached to it, so don't expect a + // failure from the masp vp here but from the check_fees function "--force", ]; let mut client = run!(test, Bin::Client, tx_args, Some(720))?; diff --git a/tests/src/integration/masp.rs b/tests/src/integration/masp.rs index c5b51a6eac..48c7752c83 100644 --- a/tests/src/integration/masp.rs +++ b/tests/src/integration/masp.rs @@ -1219,32 +1219,35 @@ fn wrapper_fee_unshielding() -> Result<()> { node.assert_success(); // 3. Invalid unshielding - // TODO: this test shall panic because of the panic in the sdk. Once the - // panics are removed from there, this test can be updated - let tx_run = run( - &node, - Bin::Client, - vec![ - "transfer", - "--source", - ALBERT, - "--target", - BERTHA, - "--token", - NAM, - "--amount", - "1", - "--gas-price", - "1000", - "--gas-spending-key", - B_SPENDING_KEY, - "--ledger-address", - validator_one_rpc, - "--force", - ], - ) - .is_err(); + let tx_args = vec![ + "transfer", + "--source", + ALBERT, + "--target", + BERTHA, + "--token", + NAM, + "--amount", + "1", + "--gas-price", + "1000", + "--gas-spending-key", + B_SPENDING_KEY, + "--ledger-address", + validator_one_rpc, + // NOTE: Forcing the transaction will make the client produce a + // transfer without a masp object attached to it, so don't expect a + // failure from the masp vp here but from the check_fees function + "--force", + ]; + + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, tx_args.clone())); + assert!( + captured.result.is_err(), + "{:?} unexpectedly succeeded", + tx_args + ); - assert!(tx_run); Ok(()) } From b1c6b792c8578fc90985253fbc75ba9c68482304 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 13 Dec 2023 19:54:15 -0500 Subject: [PATCH 104/216] reduce cases and fix `test_multiple_misbehaviors` --- .../lib/node/ledger/shell/finalize_block.rs | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 239b7b35da..8b1dbec5d7 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -3920,17 +3920,17 @@ mod test_finalize_block { /// `next_block_for_inflation` #[test] fn test_multiple_misbehaviors() -> storage_api::Result<()> { - for num_validators in 4u64..10u64 { - println!("NUM VALIDATORS = {}", num_validators); - test_multiple_misbehaviors_by_num_vals(num_validators)?; + for num_validators in &[4_u64, 6_u64, 9_u64] { + tracing::debug!("\nNUM VALIDATORS = {}", num_validators); + test_multiple_misbehaviors_by_num_vals(*num_validators)?; } Ok(()) } /// Current test procedure (prefixed by epoch in which the event occurs): - /// 0) Validator initial stake of 200_000 - /// 1) Delegate 67_231 to validator - /// 1) Self-unbond 154_654 + /// 0) Validator initial stake of 00_000 + /// 1) Delegate 37_231 to validator + /// 1) Self-unbond 84_654 /// 2) Unbond delegation of 18_000 /// 3) Self-bond 9_123 /// 4) Self-unbond 15_000 @@ -3949,7 +3949,7 @@ mod test_finalize_block { }); let mut params = read_pos_params(&shell.wl_storage).unwrap(); params.owned.unbonding_len = 4; - params.owned.max_validator_slots = 4; + params.owned.max_validator_slots = 50; write_pos_params(&mut shell.wl_storage, ¶ms.owned)?; // Slash pool balance @@ -3999,7 +3999,7 @@ mod test_finalize_block { // Make an account with balance and delegate some tokens let delegator = address::testing::gen_implicit_address(); - let del_1_amount = token::Amount::native_whole(67_231); + let del_1_amount = token::Amount::native_whole(37_231); let staking_token = shell.wl_storage.storage.native_token.clone(); credit_tokens( &mut shell.wl_storage, @@ -4019,7 +4019,7 @@ mod test_finalize_block { .unwrap(); // Self-unbond - let self_unbond_1_amount = token::Amount::native_whole(54_654); + let self_unbond_1_amount = token::Amount::native_whole(84_654); namada_proof_of_stake::unbond_tokens( &mut shell.wl_storage, None, @@ -4061,7 +4061,7 @@ mod test_finalize_block { shell.wl_storage.storage.block.epoch, ); let (current_epoch, _) = advance_epoch(&mut shell, &pkh1, &votes, None); - println!("\nUnbonding in epoch 2"); + tracing::debug!("\nUnbonding in epoch 2"); let del_unbond_1_amount = token::Amount::native_whole(18_000); namada_proof_of_stake::unbond_tokens( &mut shell.wl_storage, @@ -4106,7 +4106,7 @@ mod test_finalize_block { shell.wl_storage.storage.block.epoch, ); let (current_epoch, _) = advance_epoch(&mut shell, &pkh1, &votes, None); - println!("\nBonding in epoch 3"); + tracing::debug!("\nBonding in epoch 3"); let self_bond_1_amount = token::Amount::native_whole(9_123); namada_proof_of_stake::bond_tokens( @@ -4147,7 +4147,7 @@ mod test_finalize_block { ); let (current_epoch, _) = advance_epoch(&mut shell, &pkh1, &votes, None); assert_eq!(current_epoch.0, 5_u64); - println!("Delegating in epoch 5"); + tracing::debug!("Delegating in epoch 5"); // Delegate let del_2_amount = token::Amount::native_whole(8_144); @@ -4161,7 +4161,7 @@ mod test_finalize_block { ) .unwrap(); - println!("Advancing to epoch 6"); + tracing::debug!("Advancing to epoch 6"); // Advance to epoch 6 let votes = get_default_true_votes( @@ -4230,7 +4230,7 @@ mod test_finalize_block { .unwrap() ); - println!("Advancing to epoch 7"); + tracing::debug!("Advancing to epoch 7"); // Advance to epoch 7 let (current_epoch, _) = advance_epoch(&mut shell, &pkh1, &votes, None); @@ -4324,7 +4324,7 @@ mod test_finalize_block { + del_2_amount ); - println!("\nNow processing the infractions\n"); + tracing::debug!("\nNow processing the infractions\n"); // Advance to epoch 9, where the infractions committed in epoch 3 will // be processed @@ -4561,7 +4561,7 @@ mod test_finalize_block { let current_epoch = shell.wl_storage.storage.block.epoch; assert_eq!(current_epoch.0, 12_u64); - println!("\nCHECK BOND AND UNBOND DETAILS"); + tracing::debug!("\nCHECK BOND AND UNBOND DETAILS"); let details = namada_proof_of_stake::bonds_and_unbonds( &shell.wl_storage, None, @@ -4599,7 +4599,6 @@ mod test_finalize_block { del_details.bonds[0].amount, del_1_amount - del_unbond_1_amount ); - // TODO: decimal mult issues should be resolved with PR 1282 assert!( (del_details.bonds[0].slashed_amount.unwrap().change() - std::cmp::min( @@ -4621,9 +4620,6 @@ mod test_finalize_block { initial_stake - self_unbond_1_amount + self_bond_1_amount - self_unbond_2_amount ); - // TODO: not sure why this is correct??? (with + self_bond_1_amount - - // self_unbond_2_amount) - // TODO: Make sure this is sound and what we expect assert!( (self_details.bonds[0].slashed_amount.unwrap().change() - (std::cmp::min( @@ -4680,7 +4676,7 @@ mod test_finalize_block { assert_eq!(self_details.unbonds[2].amount, self_bond_1_amount); assert_eq!(self_details.unbonds[2].slashed_amount, None); - println!("\nWITHDRAWING DELEGATION UNBOND"); + tracing::debug!("\nWITHDRAWING DELEGATION UNBOND"); // let slash_pool_balance_pre_withdraw = slash_pool_balance; // Withdraw the delegation unbonds, which total to 18_000. This should // only be affected by the slashes in epoch 3 From 78717fd041c8fbc5be32b30ee3957c7adbd96838 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 13 Dec 2023 23:46:02 -0500 Subject: [PATCH 105/216] rewrite `test_purge_validator_information` solely in pos crate --- .../lib/node/ledger/shell/finalize_block.rs | 174 ------------------ proof_of_stake/src/tests.rs | 120 +++++++++++- 2 files changed, 118 insertions(+), 176 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 8b1dbec5d7..f046168915 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -4748,180 +4748,6 @@ mod test_finalize_block { Ok(()) } - #[test] - fn test_purge_validator_information() -> storage_api::Result<()> { - // Setup the network with pipeline_len = 2, unbonding_len = 4 - let num_validators = 4_u64; - let (mut shell, _recv, _, _) = setup_with_cfg(SetupCfg { - last_height: 0, - num_validators, - ..Default::default() - }); - let mut params = read_pos_params(&shell.wl_storage).unwrap(); - params.owned.unbonding_len = 4; - // params.owned.max_validator_slots = 3; - // params.owned.validator_stake_threshold = token::Amount::zero(); - write_pos_params(&mut shell.wl_storage, ¶ms.owned)?; - - let max_proposal_period = params.max_proposal_period; - let default_past_epochs = 2; - let consensus_val_set_len = max_proposal_period + default_past_epochs; - - let consensus_val_set = - namada_proof_of_stake::consensus_validator_set_handle(); - // let below_cap_val_set = - // namada_proof_of_stake::below_capacity_validator_set_handle(); - let validator_positions = - namada_proof_of_stake::validator_set_positions_handle(); - let all_validator_addresses = - namada_proof_of_stake::validator_addresses_handle(); - - let consensus_set: Vec = - read_consensus_validator_set_addresses_with_stake( - &shell.wl_storage, - Epoch::default(), - ) - .unwrap() - .into_iter() - .collect(); - let val1 = consensus_set[0].clone(); - let pkh1 = get_pkh_from_address( - &shell.wl_storage, - ¶ms, - val1.address, - Epoch::default(), - ); - - // Finalize block 1 - next_block_for_inflation(&mut shell, pkh1.to_vec(), vec![], None); - - let votes = get_default_true_votes(&shell.wl_storage, Epoch::default()); - assert!(!votes.is_empty()); - - let check_is_data = |storage: &WlStorage<_, _>, - start: Epoch, - end: Epoch| { - for ep in Epoch::iter_bounds_inclusive(start, end) { - assert!(!consensus_val_set.at(&ep).is_empty(storage).unwrap()); - // assert!(!below_cap_val_set.at(&ep).is_empty(storage). - // unwrap()); - assert!( - !validator_positions.at(&ep).is_empty(storage).unwrap() - ); - assert!( - !all_validator_addresses.at(&ep).is_empty(storage).unwrap() - ); - } - }; - - // Check that there is validator data for epochs 0 - pipeline_len - check_is_data(&shell.wl_storage, Epoch(0), Epoch(params.pipeline_len)); - - // Advance to epoch `default_past_epochs` - let mut current_epoch = Epoch(0); - for _ in 0..default_past_epochs { - let votes = get_default_true_votes( - &shell.wl_storage, - shell.wl_storage.storage.block.epoch, - ); - current_epoch = advance_epoch(&mut shell, &pkh1, &votes, None).0; - } - assert_eq!(shell.wl_storage.storage.block.epoch.0, default_past_epochs); - assert_eq!(current_epoch.0, default_past_epochs); - - check_is_data( - &shell.wl_storage, - Epoch(0), - Epoch(params.pipeline_len + default_past_epochs), - ); - - // Advance one more epoch, which should purge the data for epoch 0 in - // everything except the consensus validator set - let votes = get_default_true_votes( - &shell.wl_storage, - shell.wl_storage.storage.block.epoch, - ); - current_epoch = advance_epoch(&mut shell, &pkh1, &votes, None).0; - assert_eq!(current_epoch.0, default_past_epochs + 1); - - check_is_data( - &shell.wl_storage, - Epoch(1), - Epoch(params.pipeline_len + default_past_epochs + 1), - ); - assert!( - !consensus_val_set - .at(&Epoch(0)) - .is_empty(&shell.wl_storage) - .unwrap() - ); - assert!( - validator_positions - .at(&Epoch(0)) - .is_empty(&shell.wl_storage) - .unwrap() - ); - assert!( - all_validator_addresses - .at(&Epoch(0)) - .is_empty(&shell.wl_storage) - .unwrap() - ); - - // Advance to the epoch `consensus_val_set_len` + 1 - loop { - assert!( - !consensus_val_set - .at(&Epoch(0)) - .is_empty(&shell.wl_storage) - .unwrap() - ); - let votes = get_default_true_votes( - &shell.wl_storage, - shell.wl_storage.storage.block.epoch, - ); - current_epoch = advance_epoch(&mut shell, &pkh1, &votes, None).0; - if current_epoch.0 == consensus_val_set_len + 1 { - break; - } - } - - assert!( - consensus_val_set - .at(&Epoch(0)) - .is_empty(&shell.wl_storage) - .unwrap() - ); - - // Advance one more epoch - let votes = get_default_true_votes( - &shell.wl_storage, - shell.wl_storage.storage.block.epoch, - ); - current_epoch = advance_epoch(&mut shell, &pkh1, &votes, None).0; - for ep in Epoch::default().iter_range(2) { - assert!( - consensus_val_set - .at(&ep) - .is_empty(&shell.wl_storage) - .unwrap() - ); - } - for ep in Epoch::iter_bounds_inclusive( - Epoch(2), - current_epoch + params.pipeline_len, - ) { - assert!( - !consensus_val_set - .at(&ep) - .is_empty(&shell.wl_storage) - .unwrap() - ); - } - - Ok(()) - } - #[test] fn test_jail_validator_for_inactivity() -> storage_api::Result<()> { let num_validators = 5_u64; diff --git a/proof_of_stake/src/tests.rs b/proof_of_stake/src/tests.rs index 0398a7b3d7..39c577a447 100644 --- a/proof_of_stake/src/tests.rs +++ b/proof_of_stake/src/tests.rs @@ -11,6 +11,7 @@ use std::str::FromStr; use assert_matches::assert_matches; use namada_core::ledger::storage::testing::TestWlStorage; +use namada_core::ledger::storage::WlStorage; use namada_core::ledger::storage_api::collections::lazy_map::{ self, Collectable, NestedMap, }; @@ -42,7 +43,7 @@ use crate::epoched::DEFAULT_NUM_PAST_EPOCHS; use crate::parameters::testing::arb_pos_params; use crate::parameters::{OwnedPosParams, PosParams}; use crate::rewards::PosRewardsCalculator; -use crate::test_utils::test_init_genesis; +use crate::test_utils::{init_genesis_helper, test_init_genesis}; use crate::types::{ into_tm_voting_power, BondDetails, BondId, BondsAndUnbondsDetails, ConsensusValidator, EagerRedelegatedBondsMap, GenesisValidator, Position, @@ -70,7 +71,7 @@ use crate::{ slash_redelegation, slash_validator, slash_validator_redelegation, staking_token_address, total_bonded_handle, total_deltas_handle, total_unbonded_handle, unbond_handle, unbond_tokens, unjail_validator, - update_validator_deltas, update_validator_set, + update_validator_deltas, update_validator_set, validator_addresses_handle, validator_consensus_key_handle, validator_incoming_redelegations_handle, validator_outgoing_redelegations_handle, validator_set_positions_handle, validator_set_update_tendermint, validator_slashes_handle, @@ -133,6 +134,22 @@ proptest! { } } +proptest! { + // Generate arb valid input for `test_purge_validator_information_aux` + #![proptest_config(Config { + cases: 1, + .. Config::default() + })] + #[test] + fn test_purge_validator_information( + + genesis_validators in arb_genesis_validators(4..5, None), + + ) { + test_purge_validator_information_aux( genesis_validators) + } +} + proptest! { // Generate arb valid input for `test_slashes_with_unbonding_aux` #![proptest_config(Config { @@ -6881,3 +6898,102 @@ fn test_is_delegator_aux(mut validators: Vec) { .unwrap() ); } + +/// Test validator initialization. +fn test_purge_validator_information_aux(validators: Vec) { + let owned = OwnedPosParams { + unbonding_len: 4, + ..Default::default() + }; + + let mut s = TestWlStorage::default(); + let mut current_epoch = s.storage.block.epoch; + + // Genesis + let gov_params = + namada_core::ledger::governance::parameters::GovernanceParameters { + max_proposal_period: 5, + ..Default::default() + }; + + gov_params.init_storage(&mut s).unwrap(); + let params = crate::read_non_pos_owned_params(&s, owned).unwrap(); + init_genesis_helper(&mut s, ¶ms, validators.into_iter(), current_epoch) + .unwrap(); + + s.commit_block().unwrap(); + + let default_past_epochs = 2; + let consensus_val_set_len = + gov_params.max_proposal_period + default_past_epochs; + + let consensus_val_set = consensus_validator_set_handle(); + // let below_cap_val_set = below_capacity_validator_set_handle(); + let validator_positions = validator_set_positions_handle(); + let all_validator_addresses = validator_addresses_handle(); + + let check_is_data = |storage: &WlStorage<_, _>, + start: Epoch, + end: Epoch| { + for ep in Epoch::iter_bounds_inclusive(start, end) { + assert!(!consensus_val_set.at(&ep).is_empty(storage).unwrap()); + // assert!(!below_cap_val_set.at(&ep).is_empty(storage). + // unwrap()); + assert!(!validator_positions.at(&ep).is_empty(storage).unwrap()); + assert!( + !all_validator_addresses.at(&ep).is_empty(storage).unwrap() + ); + } + }; + + // Check that there is validator data for epochs 0 - pipeline_len + check_is_data(&s, current_epoch, Epoch(params.owned.pipeline_len)); + + // Advance to epoch 1 + for _ in 0..default_past_epochs { + current_epoch = advance_epoch(&mut s, ¶ms); + } + assert_eq!(s.storage.block.epoch.0, default_past_epochs); + assert_eq!(current_epoch.0, default_past_epochs); + + check_is_data( + &s, + Epoch(0), + Epoch(params.owned.pipeline_len + default_past_epochs), + ); + + current_epoch = advance_epoch(&mut s, ¶ms); + assert_eq!(current_epoch.0, default_past_epochs + 1); + + check_is_data( + &s, + Epoch(1), + Epoch(params.pipeline_len + default_past_epochs + 1), + ); + assert!(!consensus_val_set.at(&Epoch(0)).is_empty(&s).unwrap()); + assert!(validator_positions.at(&Epoch(0)).is_empty(&s).unwrap()); + assert!(all_validator_addresses.at(&Epoch(0)).is_empty(&s).unwrap()); + + // Advance to the epoch `consensus_val_set_len` + 1 + loop { + assert!(!consensus_val_set.at(&Epoch(0)).is_empty(&s).unwrap()); + + current_epoch = advance_epoch(&mut s, ¶ms); + if current_epoch.0 == consensus_val_set_len + 1 { + break; + } + } + + assert!(consensus_val_set.at(&Epoch(0)).is_empty(&s).unwrap()); + + current_epoch = advance_epoch(&mut s, ¶ms); + for ep in Epoch::default().iter_range(2) { + assert!(consensus_val_set.at(&ep).is_empty(&s).unwrap()); + } + for ep in Epoch::iter_bounds_inclusive( + Epoch(2), + current_epoch + params.pipeline_len, + ) { + assert!(!consensus_val_set.at(&ep).is_empty(&s).unwrap()); + } +} From f0ab7ac978152c11195ef83232b2afc143db0806 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 13 Dec 2023 23:53:56 -0500 Subject: [PATCH 106/216] changelog: add #2277 --- .changelog/unreleased/improvements/2277-speed-up-tests.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/2277-speed-up-tests.md diff --git a/.changelog/unreleased/improvements/2277-speed-up-tests.md b/.changelog/unreleased/improvements/2277-speed-up-tests.md new file mode 100644 index 0000000000..f15fc42ddd --- /dev/null +++ b/.changelog/unreleased/improvements/2277-speed-up-tests.md @@ -0,0 +1,2 @@ +- Enhances the speed of two PoS tests that run particularly longer than others + in CI. ([\#2277](https://github.com/anoma/namada/pull/2277)) \ No newline at end of file From 032ce0b3154977358546775e700b7055969de34e Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Thu, 14 Dec 2023 12:02:27 +0100 Subject: [PATCH 107/216] Expose store remove_alias functionality --- sdk/src/wallet/store.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/wallet/store.rs b/sdk/src/wallet/store.rs index 8ba66df798..f8a370b347 100644 --- a/sdk/src/wallet/store.rs +++ b/sdk/src/wallet/store.rs @@ -576,7 +576,7 @@ impl Store { } /// Completely remove the given alias from all maps in the wallet - fn remove_alias(&mut self, alias: &Alias) { + pub fn remove_alias(&mut self, alias: &Alias) { self.payment_addrs.remove_by_left(alias); self.view_keys.remove(alias); self.spend_keys.remove(alias); From ddc7370064708cf8f6a175634a62a7d485184314 Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Thu, 14 Dec 2023 12:04:26 +0100 Subject: [PATCH 108/216] Implement `remove` command --- apps/src/lib/cli.rs | 53 ++++++++++++++++++++++++++++++++++---- apps/src/lib/cli/wallet.rs | 18 +++++++++++++ sdk/src/args.rs | 9 +++++++ sdk/src/wallet/mod.rs | 5 ++++ 4 files changed, 80 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 55e2cf09bd..08fcbc4341 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -483,6 +483,8 @@ pub mod cmds { KeyGen(WalletGen), /// Key derivation KeyDerive(WalletDerive), + /// Payment address generation + PayAddrGen(WalletGenPaymentAddress), /// Key / address list KeyAddrList(WalletListKeysAddresses), /// Key / address search @@ -493,38 +495,42 @@ pub mod cmds { KeyImport(WalletImportKey), /// Key / address add KeyAddrAdd(WalletAddKeyAddress), - /// Payment address generation - PayAddrGen(WalletGenPaymentAddress), + /// Key / address remove + KeyAddrRemove(WalletRemoveKeyAddress), } impl Cmd for NamadaWallet { fn add_sub(app: App) -> App { app.subcommand(WalletGen::def()) .subcommand(WalletDerive::def()) + .subcommand(WalletGenPaymentAddress::def()) .subcommand(WalletListKeysAddresses::def()) .subcommand(WalletFindKeysAddresses::def()) .subcommand(WalletExportKey::def()) .subcommand(WalletImportKey::def()) .subcommand(WalletAddKeyAddress::def()) - .subcommand(WalletGenPaymentAddress::def()) + .subcommand(WalletRemoveKeyAddress::def()) } fn parse(matches: &ArgMatches) -> Option { let gen = SubCmd::parse(matches).map(Self::KeyGen); let derive = SubCmd::parse(matches).map(Self::KeyDerive); + let pay_addr_gen = SubCmd::parse(matches).map(Self::PayAddrGen); let key_addr_list = SubCmd::parse(matches).map(Self::KeyAddrList); let key_addr_find = SubCmd::parse(matches).map(Self::KeyAddrFind); let export = SubCmd::parse(matches).map(Self::KeyExport); let import = SubCmd::parse(matches).map(Self::KeyImport); let key_addr_add = SubCmd::parse(matches).map(Self::KeyAddrAdd); - let pay_addr_gen = SubCmd::parse(matches).map(Self::PayAddrGen); + let key_addr_remove = + SubCmd::parse(matches).map(Self::KeyAddrRemove); gen.or(derive) + .or(pay_addr_gen) .or(key_addr_list) .or(key_addr_find) .or(export) .or(import) .or(key_addr_add) - .or(pay_addr_gen) + .or(key_addr_remove) } } @@ -731,6 +737,29 @@ pub mod cmds { } } + /// Remove key / address + #[derive(Clone, Debug)] + pub struct WalletRemoveKeyAddress(pub args::KeyAddressRemove); + + impl SubCmd for WalletRemoveKeyAddress { + const CMD: &'static str = "remove"; + + fn parse(matches: &ArgMatches) -> Option { + matches + .subcommand_matches(Self::CMD) + .map(|matches| Self(args::KeyAddressRemove::parse(matches))) + } + + fn def() -> App { + App::new(Self::CMD) + .about( + "Remove the given alias and all associated keys / \ + addresses from the wallet.", + ) + .add_args::() + } + } + /// Generate a payment address from a viewing key or payment address #[derive(Clone, Debug)] pub struct WalletGenPaymentAddress(pub args::PayAddressGen); @@ -2808,6 +2837,7 @@ pub mod args { pub const DESTINATION_VALIDATOR: Arg = arg("destination-validator"); pub const DISCORD_OPT: ArgOpt = arg_opt("discord-handle"); + pub const DO_IT: ArgFlag = flag("do-it"); pub const DONT_ARCHIVE: ArgFlag = flag("dont-archive"); pub const DONT_PREFETCH_WASM: ArgFlag = flag("dont-prefetch-wasm"); pub const DRY_RUN_TX: ArgFlag = flag("dry-run"); @@ -6269,6 +6299,19 @@ pub mod args { } } + impl Args for KeyAddressRemove { + fn parse(matches: &ArgMatches) -> Self { + let alias = ALIAS.parse(matches); + let do_it = DO_IT.parse(matches); + Self { alias, do_it } + } + + fn def(app: App) -> App { + app.arg(ALIAS.def().help("An alias to be removed.")) + .arg(DO_IT.def().help("Confirm alias removal.").required(true)) + } + } + impl Args for KeyExport { fn parse(matches: &ArgMatches) -> Self { let shielded = SHIELDED.parse(matches); diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index a52f8128c3..c3571cdfb9 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -60,6 +60,9 @@ impl CliApi { cmds::NamadaWallet::KeyAddrAdd(cmds::WalletAddKeyAddress(args)) => { key_address_add(ctx, io, args) } + cmds::NamadaWallet::KeyAddrRemove( + cmds::WalletRemoveKeyAddress(args), + ) => key_address_remove(ctx, io, args), cmds::NamadaWallet::PayAddrGen(cmds::WalletGenPaymentAddress( args, )) => { @@ -699,6 +702,21 @@ fn key_address_add( add_key_or_address(ctx, io, alias, alias_force, value, unsafe_dont_encrypt) } +/// Remove keys and addresses +fn key_address_remove( + ctx: Context, + io: &impl Io, + args::KeyAddressRemove { alias, .. }: args::KeyAddressRemove, +) { + let alias = alias.to_lowercase(); + let mut wallet = load_wallet(ctx); + wallet.remove_all_by_alias(alias.clone()); + wallet + .save() + .unwrap_or_else(|err| edisplay_line!(io, "{}", err)); + display_line!(io, "Successfully removed alias: \"{}\"", alias); +} + /// Find a keypair in the wallet store. fn transparent_key_find( ctx: Context, diff --git a/sdk/src/args.rs b/sdk/src/args.rs index dde5953025..f313a50664 100644 --- a/sdk/src/args.rs +++ b/sdk/src/args.rs @@ -2152,6 +2152,15 @@ pub struct KeyAddressAdd { pub unsafe_dont_encrypt: bool, } +/// Wallet key / address remove arguments +#[derive(Clone, Debug)] +pub struct KeyAddressRemove { + /// Address alias + pub alias: String, + /// Confirmation to remove the alias + pub do_it: bool, +} + /// Generate payment address arguments #[derive(Clone, Debug)] pub struct PayAddressGen { diff --git a/sdk/src/wallet/mod.rs b/sdk/src/wallet/mod.rs index d59d042452..c69300052f 100644 --- a/sdk/src/wallet/mod.rs +++ b/sdk/src/wallet/mod.rs @@ -989,4 +989,9 @@ impl Wallet { pub fn extend(&mut self, wallet: Self) { self.store.extend(wallet.store) } + + /// Remove keys and addresses associated with the given alias + pub fn remove_all_by_alias(&mut self, alias: String) { + self.store.remove_alias(&alias.into()) + } } From 687b95e447989720561380580d50b79ca153c24e Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Thu, 14 Dec 2023 15:01:07 +0100 Subject: [PATCH 109/216] Fix key import --- apps/src/lib/cli/wallet.rs | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index c3571cdfb9..11c72ecc82 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -15,7 +15,7 @@ use masp_primitives::zip32::ExtendedFullViewingKey; use namada::types::address::{Address, DecodeError}; use namada::types::io::Io; use namada::types::key::*; -use namada::types::masp::{MaspValue, PaymentAddress}; +use namada::types::masp::{ExtendedSpendingKey, MaspValue, PaymentAddress}; use namada_sdk::masp::find_valid_diversifier; use namada_sdk::wallet::{ DecryptionError, DerivationPath, DerivationPathError, FindKeyError, Wallet, @@ -1086,17 +1086,37 @@ fn key_import( unsafe_dont_encrypt, }: args::KeyImport, ) { - let file_data = std::fs::read_to_string(file_path).unwrap_or_else(|err| { + let file_data = std::fs::read(file_path).unwrap_or_else(|err| { edisplay_line!(io, "{}", err); display_line!(io, "No changes are persisted. Exiting."); cli::safe_exit(1) }); - let value = KeyAddrAddValue::from_str(&file_data).unwrap_or_else(|err| { - edisplay_line!(io, "{}", err); + if let Ok(sk) = common::SecretKey::try_from_slice(&file_data) { + transparent_secret_key_add( + ctx, + io, + alias, + alias_force, + sk, + unsafe_dont_encrypt, + ); + } else if let Ok(spend_key) = + ExtendedSpendingKey::try_from_slice(&file_data) + { + let masp_value = MaspValue::ExtendedSpendingKey(spend_key); + shielded_key_address_add( + ctx, + io, + alias, + alias_force, + masp_value, + unsafe_dont_encrypt, + ); + } else { + display_line!(io, "Could not parse the data."); display_line!(io, "No changes are persisted. Exiting."); cli::safe_exit(1) - }); - add_key_or_address(ctx, io, alias, alias_force, value, unsafe_dont_encrypt) + } } /// List all known transparent addresses. From 2f2b4b03ac38073de5dd0ad83f0e592d0cd535c0 Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Thu, 14 Dec 2023 15:01:29 +0100 Subject: [PATCH 110/216] Minor --- apps/src/lib/cli/wallet.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index 11c72ecc82..fdf9e94ccb 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -972,6 +972,7 @@ fn shielded_key_address_find_by_alias( .unwrap(); } } + found } From 1d2b9356fc5fcbe3f03466937ab025c80002499c Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Thu, 14 Dec 2023 15:13:30 +0100 Subject: [PATCH 111/216] Fix export / import --- apps/src/lib/cli.rs | 12 ++++-------- apps/src/lib/cli/wallet.rs | 38 ++++++++++++++++++-------------------- sdk/src/args.rs | 2 -- 3 files changed, 22 insertions(+), 30 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 08fcbc4341..a940d78135 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -6314,18 +6314,14 @@ pub mod args { impl Args for KeyExport { fn parse(matches: &ArgMatches) -> Self { - let shielded = SHIELDED.parse(matches); let alias = ALIAS.parse(matches); - Self { shielded, alias } + Self { alias } } fn def(app: App) -> App { app.arg( - SHIELDED - .def() - .help("Whether to export the shielded spending key."), + ALIAS.def().help("The alias of the key you wish to export."), ) - .arg(ALIAS.def().help("The alias of the key you wish to export.")) } } @@ -6354,8 +6350,8 @@ pub mod args { .help("An alias to be associated with the imported entry."), ) .arg(UNSAFE_DONT_ENCRYPT.def().help( - "UNSAFE: Do not encrypt the added keys. Do not use this for \ - keys used in a live network.", + "UNSAFE: Do not encrypt the imported keys. Do not use this \ + for keys used in a live network.", )) } } diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index fdf9e94ccb..fdb2554428 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -1050,30 +1050,28 @@ fn transparent_keys_list( fn key_export( ctx: Context, io: &impl Io, - args::KeyExport { shielded, alias }: args::KeyExport, + args::KeyExport { alias }: args::KeyExport, ) { let alias = alias.to_lowercase(); let mut wallet = load_wallet(ctx); - if !shielded { - wallet - .find_secret_key(&alias, None) - .map(|sk| Box::new(sk) as Box) - } else { - wallet + let key_to_export = wallet + .find_secret_key(&alias, None) + .map(|sk| Box::new(sk) as Box) + .or(wallet .find_spending_key(&alias, None) - .map(|spk| Box::new(spk) as Box) - } - .map(|key| { - let file_data = key.serialize_to_vec(); - let file_name = format!("key_{}", alias); - let mut file = File::create(&file_name).unwrap(); - file.write_all(file_data.as_ref()).unwrap(); - display_line!(io, "Exported to file {}", file_name); - }) - .unwrap_or_else(|err| { - edisplay_line!(io, "{}", err); - cli::safe_exit(1) - }) + .map(|spk| Box::new(spk) as Box)); + key_to_export + .map(|key| { + let file_data = key.serialize_to_vec(); + let file_name = format!("key_{}", alias); + let mut file = File::create(&file_name).unwrap(); + file.write_all(file_data.as_ref()).unwrap(); + display_line!(io, "Exported to file {}", file_name); + }) + .unwrap_or_else(|err| { + edisplay_line!(io, "{}", err); + cli::safe_exit(1) + }) } /// Import a transparent keypair / MASP spending key from a file. diff --git a/sdk/src/args.rs b/sdk/src/args.rs index f313a50664..379727242b 100644 --- a/sdk/src/args.rs +++ b/sdk/src/args.rs @@ -2120,8 +2120,6 @@ pub struct KeyAddressFind { /// Wallet key export arguments #[derive(Clone, Debug)] pub struct KeyExport { - /// Whether to export a MASP spending key - pub shielded: bool, /// Key alias pub alias: String, } From df0b1963be268cae233a5eda023ef47df950e54f Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Thu, 14 Dec 2023 17:20:32 +0100 Subject: [PATCH 112/216] Improve output if keypair could not be decypted --- apps/src/lib/cli/wallet.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index fdb2554428..9bb37e1968 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -884,11 +884,15 @@ fn transparent_key_address_find_by_alias( if decrypt { // Check if alias is also a secret key. Decrypt and print it if // requested. - if let Ok(keypair) = wallet.find_secret_key(&alias, None) { - if unsafe_show_secret { - display_line!(io, &mut w_lock; " Secret key: {}", keypair) + match wallet.find_secret_key(&alias, None) { + Ok(keypair) => { + if unsafe_show_secret { + display_line!(io, &mut w_lock; " Secret key: {}", keypair) .unwrap(); + } } + Err(FindKeyError::KeyNotFound(_)) => {} + Err(err) => edisplay_line!(io, "{}", err), } } } From 7d9e33cbdf055f82e112f38a1be9a3017847e83b Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Thu, 14 Dec 2023 17:21:51 +0100 Subject: [PATCH 113/216] Improve decryption status line --- apps/src/lib/cli/wallet.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index 9bb37e1968..4b0ebe568b 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -101,11 +101,11 @@ fn shielded_keys_list( // If this alias is associated with a spending key, indicate whether // or not the spending key is encrypted let encrypted_status = match spending_key_opt { - None => "", - Some(spend_key) if spend_key.is_encrypted() => "(encrypted)", - _ => "(not encrypted)", + None => "external", + Some(spend_key) if spend_key.is_encrypted() => "encrypted", + _ => "not encrypted", }; - display!(io, &mut w_lock; " Alias \"{}\" {}", alias, encrypted_status).unwrap(); + display!(io, &mut w_lock; " Alias \"{}\" ({})", alias, encrypted_status).unwrap(); // Always print the corresponding viewing key display_line!(io, &mut w_lock; " Viewing Key: {}", key).unwrap(); // A subset of viewing keys will have corresponding spending keys. From 4f5a41952dcff7aef946eb60bf0521007d73075d Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 15 Dec 2023 10:52:43 +0100 Subject: [PATCH 114/216] Changelog #2282 --- .changelog/unreleased/SDK/2282-masp-misc-fixes.md | 2 ++ .changelog/unreleased/improvements/2282-masp-misc-fixes.md | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 .changelog/unreleased/SDK/2282-masp-misc-fixes.md create mode 100644 .changelog/unreleased/improvements/2282-masp-misc-fixes.md diff --git a/.changelog/unreleased/SDK/2282-masp-misc-fixes.md b/.changelog/unreleased/SDK/2282-masp-misc-fixes.md new file mode 100644 index 0000000000..abac63577f --- /dev/null +++ b/.changelog/unreleased/SDK/2282-masp-misc-fixes.md @@ -0,0 +1,2 @@ +- Removed useless epoch for fee unshielding. + ([\#2282](https://github.com/anoma/namada/pull/2282)) \ No newline at end of file diff --git a/.changelog/unreleased/improvements/2282-masp-misc-fixes.md b/.changelog/unreleased/improvements/2282-masp-misc-fixes.md new file mode 100644 index 0000000000..29ec4fe01e --- /dev/null +++ b/.changelog/unreleased/improvements/2282-masp-misc-fixes.md @@ -0,0 +1,2 @@ +- Removed useless epoch for fee unshielding and refactored tests. + ([\#2282](https://github.com/anoma/namada/pull/2282)) \ No newline at end of file From bfebccf2f2a3c617b961a00902415befdfd58f91 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 15 Dec 2023 12:13:32 +0100 Subject: [PATCH 115/216] [fix]: Masp conversions no longer requires all tokens be present in genesis --- core/src/ledger/masp_conversions.rs | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/core/src/ledger/masp_conversions.rs b/core/src/ledger/masp_conversions.rs index ab915c22e2..a1fef729ef 100644 --- a/core/src/ledger/masp_conversions.rs +++ b/core/src/ledger/masp_conversions.rs @@ -214,29 +214,15 @@ where }; use rayon::prelude::ParallelSlice; - use crate::types::address; - // The derived conversions will be placed in MASP address space let masp_addr = MASP; - let tokens = address::tokens(); - let mut masp_reward_keys: Vec<_> = tokens - .into_keys() - .map(|k| { - wl_storage - .storage - .conversion_state - .tokens - .get(k) - .unwrap_or_else(|| { - panic!( - "Could not find token alias {} in MASP conversion \ - state.", - k - ) - }) - .clone() - }) + let mut masp_reward_keys: Vec<_> = wl_storage + .storage + .conversion_state + .tokens + .values() + .cloned() .collect(); // Put the native rewards first because other inflation computations depend // on it From 20a208eeca4281b3a4a19b43cbe85478026659ea Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 15 Dec 2023 12:19:36 +0100 Subject: [PATCH 116/216] [chore]: Add changelog --- .../improvments/2285-remove-hardcoded-masp-tokens.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changelog/unreleased/improvments/2285-remove-hardcoded-masp-tokens.md diff --git a/.changelog/unreleased/improvments/2285-remove-hardcoded-masp-tokens.md b/.changelog/unreleased/improvments/2285-remove-hardcoded-masp-tokens.md new file mode 100644 index 0000000000..512ab72cb0 --- /dev/null +++ b/.changelog/unreleased/improvments/2285-remove-hardcoded-masp-tokens.md @@ -0,0 +1,5 @@ +- Previously, a hardcoded set of tokens were expected to be used in Masp conversions. + If these tokens did not have configs in genesis, this would lead to a panic after the first + epoch change. This PR fixes this to use the tokens found in genesis belonging to the MASP + rewards whitelist instead of hardcoding the tokens. + ([\#2285](https://github.com/anoma/namada/pull/2285)) \ No newline at end of file From 02a4b213520a228969e06287f8ead06699a237f8 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 15 Dec 2023 14:36:17 +0000 Subject: [PATCH 117/216] Move Bridge pool VP init logic to storage mod --- ethereum_bridge/src/lib.rs | 1 - ethereum_bridge/src/parameters.rs | 4 ++-- .../src/protocol/transactions/bridge_pool_roots.rs | 3 ++- ethereum_bridge/src/{ => storage}/bridge_pool_vp.rs | 0 ethereum_bridge/src/storage/mod.rs | 1 + shared/src/ledger/protocol/mod.rs | 4 ++-- 6 files changed, 7 insertions(+), 6 deletions(-) rename ethereum_bridge/src/{ => storage}/bridge_pool_vp.rs (100%) diff --git a/ethereum_bridge/src/lib.rs b/ethereum_bridge/src/lib.rs index f06c19d69f..3aed7e4062 100644 --- a/ethereum_bridge/src/lib.rs +++ b/ethereum_bridge/src/lib.rs @@ -1,6 +1,5 @@ extern crate core; -pub mod bridge_pool_vp; pub mod oracle; pub mod parameters; pub mod protocol; diff --git a/ethereum_bridge/src/parameters.rs b/ethereum_bridge/src/parameters.rs index 62d1f9a7a1..9f0ebbd3b6 100644 --- a/ethereum_bridge/src/parameters.rs +++ b/ethereum_bridge/src/parameters.rs @@ -17,7 +17,7 @@ use serde::{Deserialize, Serialize}; use crate::storage::eth_bridge_queries::{ EthBridgeEnabled, EthBridgeQueries, EthBridgeStatus, }; -use crate::{bridge_pool_vp, storage as bridge_storage, vp}; +use crate::{storage as bridge_storage, vp}; /// An ERC20 token whitelist entry. #[derive( @@ -244,7 +244,7 @@ impl EthereumBridgeParams { // Initialize the storage for the Ethereum Bridge VP. vp::init_storage(wl_storage); // Initialize the storage for the Bridge Pool VP. - bridge_pool_vp::init_storage(wl_storage); + bridge_storage::bridge_pool_vp::init_storage(wl_storage); } } diff --git a/ethereum_bridge/src/protocol/transactions/bridge_pool_roots.rs b/ethereum_bridge/src/protocol/transactions/bridge_pool_roots.rs index 42041d9666..f4ca9c9b29 100644 --- a/ethereum_bridge/src/protocol/transactions/bridge_pool_roots.rs +++ b/ethereum_bridge/src/protocol/transactions/bridge_pool_roots.rs @@ -241,7 +241,8 @@ mod test_apply_bp_roots_to_storage { use crate::protocol::transactions::votes::{ EpochedVotingPower, EpochedVotingPowerExt, }; - use crate::{bridge_pool_vp, test_utils}; + use crate::storage::bridge_pool_vp; + use crate::test_utils; /// The data needed to run a test. struct TestPackage { diff --git a/ethereum_bridge/src/bridge_pool_vp.rs b/ethereum_bridge/src/storage/bridge_pool_vp.rs similarity index 100% rename from ethereum_bridge/src/bridge_pool_vp.rs rename to ethereum_bridge/src/storage/bridge_pool_vp.rs diff --git a/ethereum_bridge/src/storage/mod.rs b/ethereum_bridge/src/storage/mod.rs index 0c2be3048a..c45a5a3571 100644 --- a/ethereum_bridge/src/storage/mod.rs +++ b/ethereum_bridge/src/storage/mod.rs @@ -1,4 +1,5 @@ //! Functionality for accessing the storage subspace +pub mod bridge_pool_vp; pub mod eth_bridge_queries; pub mod proof; pub mod vote_tallies; diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index 3dd3a8710d..88be4fba21 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -1117,8 +1117,8 @@ mod tests { }; use namada_ethereum_bridge::storage::eth_bridge_queries::EthBridgeQueries; use namada_ethereum_bridge::storage::proof::EthereumProof; - use namada_ethereum_bridge::storage::vote_tallies; - use namada_ethereum_bridge::{bridge_pool_vp, test_utils}; + use namada_ethereum_bridge::storage::{bridge_pool_vp, vote_tallies}; + use namada_ethereum_bridge::test_utils; use super::*; From abb5dd7181953984022af255264fce0dbca75286 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 15 Dec 2023 15:13:14 +0000 Subject: [PATCH 118/216] Move Ethereum Bridge VP initialization logic --- ethereum_bridge/src/lib.rs | 1 - ethereum_bridge/src/parameters.rs | 5 +++-- ethereum_bridge/src/storage/mod.rs | 1 + ethereum_bridge/src/storage/vp.rs | 3 +++ ethereum_bridge/src/{vp.rs => storage/vp/ethereum_bridge.rs} | 0 5 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 ethereum_bridge/src/storage/vp.rs rename ethereum_bridge/src/{vp.rs => storage/vp/ethereum_bridge.rs} (100%) diff --git a/ethereum_bridge/src/lib.rs b/ethereum_bridge/src/lib.rs index 3aed7e4062..0f09e5ecf4 100644 --- a/ethereum_bridge/src/lib.rs +++ b/ethereum_bridge/src/lib.rs @@ -6,4 +6,3 @@ pub mod protocol; pub mod storage; #[cfg(any(test, feature = "testing"))] pub mod test_utils; -pub mod vp; diff --git a/ethereum_bridge/src/parameters.rs b/ethereum_bridge/src/parameters.rs index 9f0ebbd3b6..93f70c6222 100644 --- a/ethereum_bridge/src/parameters.rs +++ b/ethereum_bridge/src/parameters.rs @@ -14,10 +14,11 @@ use namada_core::types::storage::Key; use namada_core::types::token::{DenominatedAmount, NATIVE_MAX_DECIMAL_PLACES}; use serde::{Deserialize, Serialize}; +use crate::storage as bridge_storage; use crate::storage::eth_bridge_queries::{ EthBridgeEnabled, EthBridgeQueries, EthBridgeStatus, }; -use crate::{storage as bridge_storage, vp}; +use crate::storage::vp; /// An ERC20 token whitelist entry. #[derive( @@ -242,7 +243,7 @@ impl EthereumBridgeParams { wl_storage.write_bytes(&key, encode(denom)).unwrap(); } // Initialize the storage for the Ethereum Bridge VP. - vp::init_storage(wl_storage); + vp::ethereum_bridge::init_storage(wl_storage); // Initialize the storage for the Bridge Pool VP. bridge_storage::bridge_pool_vp::init_storage(wl_storage); } diff --git a/ethereum_bridge/src/storage/mod.rs b/ethereum_bridge/src/storage/mod.rs index c45a5a3571..6793b805b3 100644 --- a/ethereum_bridge/src/storage/mod.rs +++ b/ethereum_bridge/src/storage/mod.rs @@ -3,6 +3,7 @@ pub mod bridge_pool_vp; pub mod eth_bridge_queries; pub mod proof; pub mod vote_tallies; +pub mod vp; pub use namada_core::ledger::eth_bridge::storage::{ bridge_pool, wrapped_erc20s, *, }; diff --git a/ethereum_bridge/src/storage/vp.rs b/ethereum_bridge/src/storage/vp.rs new file mode 100644 index 0000000000..4792e8af41 --- /dev/null +++ b/ethereum_bridge/src/storage/vp.rs @@ -0,0 +1,3 @@ +//! Validity predicate storage + +pub mod ethereum_bridge; diff --git a/ethereum_bridge/src/vp.rs b/ethereum_bridge/src/storage/vp/ethereum_bridge.rs similarity index 100% rename from ethereum_bridge/src/vp.rs rename to ethereum_bridge/src/storage/vp/ethereum_bridge.rs From 1487076e8a4c06678e9056c7cafb12f61ae051b0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 15 Dec 2023 15:16:42 +0000 Subject: [PATCH 119/216] Move Bridge pool VP once again --- ethereum_bridge/src/parameters.rs | 2 +- .../src/protocol/transactions/bridge_pool_roots.rs | 6 +++--- ethereum_bridge/src/storage/mod.rs | 1 - ethereum_bridge/src/storage/vp.rs | 1 + .../src/storage/{bridge_pool_vp.rs => vp/bridge_pool.rs} | 0 shared/src/ledger/protocol/mod.rs | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) rename ethereum_bridge/src/storage/{bridge_pool_vp.rs => vp/bridge_pool.rs} (100%) diff --git a/ethereum_bridge/src/parameters.rs b/ethereum_bridge/src/parameters.rs index 93f70c6222..239172f5cc 100644 --- a/ethereum_bridge/src/parameters.rs +++ b/ethereum_bridge/src/parameters.rs @@ -245,7 +245,7 @@ impl EthereumBridgeParams { // Initialize the storage for the Ethereum Bridge VP. vp::ethereum_bridge::init_storage(wl_storage); // Initialize the storage for the Bridge Pool VP. - bridge_storage::bridge_pool_vp::init_storage(wl_storage); + vp::bridge_pool::init_storage(wl_storage); } } diff --git a/ethereum_bridge/src/protocol/transactions/bridge_pool_roots.rs b/ethereum_bridge/src/protocol/transactions/bridge_pool_roots.rs index f4ca9c9b29..59672e1556 100644 --- a/ethereum_bridge/src/protocol/transactions/bridge_pool_roots.rs +++ b/ethereum_bridge/src/protocol/transactions/bridge_pool_roots.rs @@ -241,7 +241,7 @@ mod test_apply_bp_roots_to_storage { use crate::protocol::transactions::votes::{ EpochedVotingPower, EpochedVotingPowerExt, }; - use crate::storage::bridge_pool_vp; + use crate::storage::vp; use crate::test_utils; /// The data needed to run a test. @@ -274,7 +274,7 @@ mod test_apply_bp_roots_to_storage { wl_storage.storage.block.height = 1.into(); wl_storage.commit_block().unwrap(); - bridge_pool_vp::init_storage(&mut wl_storage); + vp::bridge_pool::init_storage(&mut wl_storage); test_utils::commit_bridge_pool_root_at_height( &mut wl_storage, &KeccakHash([1; 32]), @@ -814,7 +814,7 @@ mod test_apply_bp_roots_to_storage { ); // set up the bridge pool's storage - bridge_pool_vp::init_storage(&mut wl_storage); + vp::bridge_pool::init_storage(&mut wl_storage); test_utils::commit_bridge_pool_root_at_height( &mut wl_storage, &KeccakHash([1; 32]), diff --git a/ethereum_bridge/src/storage/mod.rs b/ethereum_bridge/src/storage/mod.rs index 6793b805b3..2d2cb00827 100644 --- a/ethereum_bridge/src/storage/mod.rs +++ b/ethereum_bridge/src/storage/mod.rs @@ -1,5 +1,4 @@ //! Functionality for accessing the storage subspace -pub mod bridge_pool_vp; pub mod eth_bridge_queries; pub mod proof; pub mod vote_tallies; diff --git a/ethereum_bridge/src/storage/vp.rs b/ethereum_bridge/src/storage/vp.rs index 4792e8af41..95abf8595a 100644 --- a/ethereum_bridge/src/storage/vp.rs +++ b/ethereum_bridge/src/storage/vp.rs @@ -1,3 +1,4 @@ //! Validity predicate storage +pub mod bridge_pool; pub mod ethereum_bridge; diff --git a/ethereum_bridge/src/storage/bridge_pool_vp.rs b/ethereum_bridge/src/storage/vp/bridge_pool.rs similarity index 100% rename from ethereum_bridge/src/storage/bridge_pool_vp.rs rename to ethereum_bridge/src/storage/vp/bridge_pool.rs diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index 88be4fba21..b320e50195 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -1117,7 +1117,7 @@ mod tests { }; use namada_ethereum_bridge::storage::eth_bridge_queries::EthBridgeQueries; use namada_ethereum_bridge::storage::proof::EthereumProof; - use namada_ethereum_bridge::storage::{bridge_pool_vp, vote_tallies}; + use namada_ethereum_bridge::storage::{vote_tallies, vp}; use namada_ethereum_bridge::test_utils; use super::*; @@ -1207,7 +1207,7 @@ mod tests { (validator_b, validator_b_stake), ]), ); - bridge_pool_vp::init_storage(&mut wl_storage); + vp::bridge_pool::init_storage(&mut wl_storage); let root = wl_storage.ethbridge_queries().get_bridge_pool_root(); let nonce = wl_storage.ethbridge_queries().get_bridge_pool_nonce(); From 60bbbb73bdbcd877c115660327bdf542d4d3fa47 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 15 Dec 2023 15:21:58 +0000 Subject: [PATCH 120/216] Move eth bridge parameters under storage mod --- apps/src/lib/config/genesis/templates.rs | 2 +- ethereum_bridge/src/lib.rs | 1 - .../src/protocol/transactions/ethereum_events/events.rs | 2 +- ethereum_bridge/src/storage/mod.rs | 1 + ethereum_bridge/src/{ => storage}/parameters.rs | 4 ---- ethereum_bridge/src/test_utils.rs | 2 +- sdk/src/eth_bridge/mod.rs | 2 +- sdk/src/queries/shell/eth_bridge.rs | 2 +- shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs | 4 ++-- shared/src/ledger/native_vp/ethereum_bridge/vp.rs | 2 +- 10 files changed, 9 insertions(+), 13 deletions(-) rename ethereum_bridge/src/{ => storage}/parameters.rs (99%) diff --git a/apps/src/lib/config/genesis/templates.rs b/apps/src/lib/config/genesis/templates.rs index 62209dd24d..8802eb63d7 100644 --- a/apps/src/lib/config/genesis/templates.rs +++ b/apps/src/lib/config/genesis/templates.rs @@ -6,7 +6,7 @@ use std::path::Path; use borsh::{BorshDeserialize, BorshSerialize}; use namada::core::types::{ethereum_structs, token}; -use namada::eth_bridge::parameters::{ +use namada::eth_bridge::storage::parameters::{ Contracts, Erc20WhitelistEntry, MinimumConfirmations, }; use namada::types::address::Address; diff --git a/ethereum_bridge/src/lib.rs b/ethereum_bridge/src/lib.rs index 0f09e5ecf4..028dead0fa 100644 --- a/ethereum_bridge/src/lib.rs +++ b/ethereum_bridge/src/lib.rs @@ -1,7 +1,6 @@ extern crate core; pub mod oracle; -pub mod parameters; pub mod protocol; pub mod storage; #[cfg(any(test, feature = "testing"))] diff --git a/ethereum_bridge/src/protocol/transactions/ethereum_events/events.rs b/ethereum_bridge/src/protocol/transactions/ethereum_events/events.rs index fa2be67104..6e5529af76 100644 --- a/ethereum_bridge/src/protocol/transactions/ethereum_events/events.rs +++ b/ethereum_bridge/src/protocol/transactions/ethereum_events/events.rs @@ -29,9 +29,9 @@ use namada_core::types::storage::{BlockHeight, Key, KeySeg}; use namada_core::types::token; use namada_core::types::token::{balance_key, minted_balance_key}; -use crate::parameters::read_native_erc20_address; use crate::protocol::transactions::update; use crate::storage::eth_bridge_queries::{EthAssetMint, EthBridgeQueries}; +use crate::storage::parameters::read_native_erc20_address; /// Updates storage based on the given confirmed `event`. For example, for a /// confirmed [`EthereumEvent::TransfersToNamada`], mint the corresponding diff --git a/ethereum_bridge/src/storage/mod.rs b/ethereum_bridge/src/storage/mod.rs index 2d2cb00827..b6e62979de 100644 --- a/ethereum_bridge/src/storage/mod.rs +++ b/ethereum_bridge/src/storage/mod.rs @@ -1,5 +1,6 @@ //! Functionality for accessing the storage subspace pub mod eth_bridge_queries; +pub mod parameters; pub mod proof; pub mod vote_tallies; pub mod vp; diff --git a/ethereum_bridge/src/parameters.rs b/ethereum_bridge/src/storage/parameters.rs similarity index 99% rename from ethereum_bridge/src/parameters.rs rename to ethereum_bridge/src/storage/parameters.rs index 239172f5cc..ea3b622d7e 100644 --- a/ethereum_bridge/src/parameters.rs +++ b/ethereum_bridge/src/storage/parameters.rs @@ -374,10 +374,6 @@ mod tests { use namada_core::types::ethereum_events::EthAddress; use super::*; - use crate::parameters::{ - ContractVersion, Contracts, EthereumBridgeParams, MinimumConfirmations, - UpgradeableContract, - }; /// Ensure we can serialize and deserialize a [`Config`] struct to and from /// TOML. This can fail if complex fields are ordered before simple fields diff --git a/ethereum_bridge/src/test_utils.rs b/ethereum_bridge/src/test_utils.rs index cdc33398ac..ef18ed5543 100644 --- a/ethereum_bridge/src/test_utils.rs +++ b/ethereum_bridge/src/test_utils.rs @@ -23,7 +23,7 @@ use namada_proof_of_stake::{ staking_token_address, BecomeValidator, }; -use crate::parameters::{ +use crate::storage::parameters::{ ContractVersion, Contracts, EthereumBridgeParams, MinimumConfirmations, UpgradeableContract, }; diff --git a/sdk/src/eth_bridge/mod.rs b/sdk/src/eth_bridge/mod.rs index b8577956ca..cfcc750444 100644 --- a/sdk/src/eth_bridge/mod.rs +++ b/sdk/src/eth_bridge/mod.rs @@ -11,8 +11,8 @@ use itertools::Either; pub use namada_core::ledger::eth_bridge::storage::wrapped_erc20s; pub use namada_core::ledger::eth_bridge::{ADDRESS, INTERNAL_ADDRESS}; pub use namada_core::types::ethereum_structs as structs; -pub use namada_ethereum_bridge::parameters::*; pub use namada_ethereum_bridge::storage::eth_bridge_queries::*; +pub use namada_ethereum_bridge::storage::parameters::*; pub use namada_ethereum_bridge::*; use num256::Uint256; diff --git a/sdk/src/queries/shell/eth_bridge.rs b/sdk/src/queries/shell/eth_bridge.rs index b7a760126d..01956c770a 100644 --- a/sdk/src/queries/shell/eth_bridge.rs +++ b/sdk/src/queries/shell/eth_bridge.rs @@ -29,11 +29,11 @@ use namada_core::types::vote_extensions::validator_set_update::{ ValidatorSetArgs, VotingPowersMap, }; use namada_core::types::voting_power::FractionalVotingPower; -use namada_ethereum_bridge::parameters::UpgradeableContract; use namada_ethereum_bridge::protocol::transactions::votes::{ EpochedVotingPower, EpochedVotingPowerExt, }; use namada_ethereum_bridge::storage::eth_bridge_queries::EthBridgeQueries; +use namada_ethereum_bridge::storage::parameters::UpgradeableContract; use namada_ethereum_bridge::storage::proof::{sort_sigs, EthereumProof}; use namada_ethereum_bridge::storage::vote_tallies::{eth_msgs_prefix, Keys}; use namada_ethereum_bridge::storage::{ diff --git a/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs b/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs index 52aa738fd4..fe76be5403 100644 --- a/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs @@ -23,7 +23,7 @@ use namada_core::ledger::eth_bridge::storage::bridge_pool::{ }; use namada_core::ledger::eth_bridge::storage::whitelist; use namada_core::ledger::eth_bridge::ADDRESS as BRIDGE_ADDRESS; -use namada_ethereum_bridge::parameters::read_native_erc20_address; +use namada_ethereum_bridge::storage::parameters::read_native_erc20_address; use namada_ethereum_bridge::storage::wrapped_erc20s; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; @@ -646,7 +646,7 @@ mod test_bridge_pool_vp { use namada_core::ledger::eth_bridge::storage::bridge_pool::get_signed_root_key; use namada_core::ledger::gas::TxGasMeter; use namada_core::types::address; - use namada_ethereum_bridge::parameters::{ + use namada_ethereum_bridge::storage::parameters::{ Contracts, EthereumBridgeParams, UpgradeableContract, }; diff --git a/shared/src/ledger/native_vp/ethereum_bridge/vp.rs b/shared/src/ledger/native_vp/ethereum_bridge/vp.rs index 7b6fd767b2..1209d41960 100644 --- a/shared/src/ledger/native_vp/ethereum_bridge/vp.rs +++ b/shared/src/ledger/native_vp/ethereum_bridge/vp.rs @@ -170,7 +170,7 @@ mod tests { use namada_core::ledger::eth_bridge::storage::wrapped_erc20s; use namada_core::ledger::gas::TxGasMeter; use namada_core::ledger::storage_api::StorageWrite; - use namada_ethereum_bridge::parameters::{ + use namada_ethereum_bridge::storage::parameters::{ Contracts, EthereumBridgeParams, UpgradeableContract, }; use rand::Rng; From d1009cd6e70715b0301840cbce090942b6156609 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 15 Dec 2023 15:31:22 +0000 Subject: [PATCH 121/216] Changelog --- .../unreleased/improvements/2288-refactor-ethbridge-crate.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/2288-refactor-ethbridge-crate.md diff --git a/.changelog/unreleased/improvements/2288-refactor-ethbridge-crate.md b/.changelog/unreleased/improvements/2288-refactor-ethbridge-crate.md new file mode 100644 index 0000000000..33f77bf902 --- /dev/null +++ b/.changelog/unreleased/improvements/2288-refactor-ethbridge-crate.md @@ -0,0 +1,2 @@ +- Refactor internal structure of the Ethereum bridge crate + ([\#2288](https://github.com/anoma/namada/pull/2288)) \ No newline at end of file From 651a7e34066121ec10d7c608042c45a2e883497b Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Thu, 14 Dec 2023 18:05:19 +0100 Subject: [PATCH 122/216] Adapt e2e tests to new wallet cli --- tests/src/e2e/helpers.rs | 4 ++- tests/src/e2e/wallet_tests.rs | 66 +++++++++++++++++++++++------------ 2 files changed, 47 insertions(+), 23 deletions(-) diff --git a/tests/src/e2e/helpers.rs b/tests/src/e2e/helpers.rs index 730f4f1a55..57446555fc 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -110,7 +110,8 @@ pub fn find_address(test: &Test, alias: impl AsRef) -> Result
{ &["find", "--addr", "--alias", alias.as_ref()], Some(10) )?; - let (unread, matched) = find.exp_regex("Found address .*")?; + find.exp_string("Found transparent address:")?; + let (unread, matched) = find.exp_regex("\".*\": .*")?; let address_str = strip_trailing_newline(&matched) .trim() .rsplit_once(' ') @@ -249,6 +250,7 @@ pub fn find_keypair( "--keys", "--alias", alias.as_ref(), + "--decrypt", "--unsafe-show-secret" ], Some(10) diff --git a/tests/src/e2e/wallet_tests.rs b/tests/src/e2e/wallet_tests.rs index a79da138b8..370bdac670 100644 --- a/tests/src/e2e/wallet_tests.rs +++ b/tests/src/e2e/wallet_tests.rs @@ -46,14 +46,19 @@ fn wallet_encrypted_key_cmds() -> Result<()> { let mut cmd = run!( test, Bin::Wallet, - &["find", "--keys", "--alias", key_alias], + &["find", "--keys", "--alias", key_alias, "--decrypt"], Some(20), )?; + cmd.exp_string("Found transparent keys:")?; + cmd.exp_string(&format!( + " Alias \"{}\" (encrypted):", + key_alias.to_lowercase() + ))?; + cmd.exp_string(" Public key hash:")?; + cmd.exp_string(" Public key:")?; cmd.exp_string("Enter your decryption password:")?; cmd.send_line(password)?; - cmd.exp_string("Public key hash:")?; - cmd.exp_string("Public key:")?; // 3. key list let mut cmd = run!(test, Bin::Wallet, &["list", "--keys"], Some(20))?; @@ -72,7 +77,7 @@ fn wallet_encrypted_key_cmds() -> Result<()> { #[test] fn wallet_encrypted_key_cmds_env_var() -> Result<()> { let test = setup::single_node_net()?; - let key_alias = "test_key_1"; + let key_alias = "Test_Key_1"; let password = "VeRySeCuR3"; env::set_var("NAMADA_WALLET_PASSWORD", password); @@ -86,23 +91,31 @@ fn wallet_encrypted_key_cmds_env_var() -> Result<()> { cmd.exp_string(&format!( "Successfully added a key and an address with alias: \"{}\"", - key_alias + key_alias.to_lowercase() ))?; // 2. key find let mut cmd = run!( test, Bin::Wallet, - &["find", "--keys", "--alias", key_alias], + &["find", "--keys", "--alias", key_alias, "--decrypt"], Some(20), )?; - cmd.exp_string("Public key hash:")?; - cmd.exp_string("Public key:")?; + cmd.exp_string("Found transparent keys:")?; + cmd.exp_string(&format!( + " Alias \"{}\" (encrypted):", + key_alias.to_lowercase() + ))?; + cmd.exp_string(" Public key hash:")?; + cmd.exp_string(" Public key:")?; // 3. key list let mut cmd = run!(test, Bin::Wallet, &["list", "--keys"], Some(20))?; - cmd.exp_string(&format!("Alias \"{}\" (encrypted):", key_alias))?; + cmd.exp_string(&format!( + " Alias \"{}\" (encrypted):", + key_alias.to_lowercase() + ))?; Ok(()) } @@ -114,7 +127,7 @@ fn wallet_encrypted_key_cmds_env_var() -> Result<()> { #[test] fn wallet_unencrypted_key_cmds() -> Result<()> { let test = setup::single_node_net()?; - let key_alias = "test_key_1"; + let key_alias = "Test_Key_1"; // 1. key gen let mut cmd = run!( @@ -125,7 +138,7 @@ fn wallet_unencrypted_key_cmds() -> Result<()> { )?; cmd.exp_string(&format!( "Successfully added a key and an address with alias: \"{}\"", - key_alias + key_alias.to_lowercase() ))?; // 2. key find @@ -136,12 +149,20 @@ fn wallet_unencrypted_key_cmds() -> Result<()> { Some(20), )?; - cmd.exp_string("Public key hash:")?; - cmd.exp_string("Public key:")?; + cmd.exp_string("Found transparent keys:")?; + cmd.exp_string(&format!( + " Alias \"{}\" (not encrypted):", + key_alias.to_lowercase() + ))?; + cmd.exp_string(" Public key hash:")?; + cmd.exp_string(" Public key:")?; // 3. key list let mut cmd = run!(test, Bin::Wallet, &["list", "--keys"], Some(20))?; - cmd.exp_string(&format!("Alias \"{}\" (not encrypted):", key_alias))?; + cmd.exp_string(&format!( + " Alias \"{}\" (not encrypted):", + key_alias.to_lowercase() + ))?; Ok(()) } @@ -154,8 +175,8 @@ fn wallet_unencrypted_key_cmds() -> Result<()> { #[test] fn wallet_address_cmds() -> Result<()> { let test = setup::single_node_net()?; - let gen_address_alias = "test_address_1"; - let add_address_alias = "test_address_2"; + let gen_address_alias = "Test_Address_1"; + let add_address_alias = "Test_Address_2"; let add_address = "tnam1q82t25z5f9gmnv5sztyr8ht9tqhrw4u875qjhy56"; // 1. address gen @@ -167,7 +188,7 @@ fn wallet_address_cmds() -> Result<()> { )?; cmd.exp_string(&format!( "Successfully added a key and an address with alias: \"{}\"", - gen_address_alias + gen_address_alias.to_lowercase() ))?; // 2. address add @@ -178,8 +199,8 @@ fn wallet_address_cmds() -> Result<()> { Some(20), )?; cmd.exp_string(&format!( - "Successfully added a key and an address with alias: \"{}\"", - add_address_alias + "Successfully added an address with alias: \"{}\"", + add_address_alias.to_lowercase() ))?; // 3. address find @@ -189,13 +210,14 @@ fn wallet_address_cmds() -> Result<()> { &["find", "--addr", "--alias", gen_address_alias], Some(20), )?; - cmd.exp_string("Found address")?; + cmd.exp_string("Found transparent address:")?; // 4. address list let mut cmd = run!(test, Bin::Wallet, &["list", "--addr"], Some(20))?; - cmd.exp_string(&format!("\"{}\":", gen_address_alias))?; - cmd.exp_string(&format!("\"{}\":", add_address_alias))?; + cmd.exp_string("Known transparent addresses:")?; + cmd.exp_string(&format!("\"{}\":", gen_address_alias.to_lowercase()))?; + cmd.exp_string(&format!("\"{}\":", add_address_alias.to_lowercase()))?; Ok(()) } From 859defbbf88c315e219a576b5ce22f7d1344dd2a Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Thu, 14 Dec 2023 23:39:03 +0100 Subject: [PATCH 123/216] Fix messages --- apps/src/lib/cli/wallet.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index 4b0ebe568b..21c5526dbe 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -105,7 +105,7 @@ fn shielded_keys_list( Some(spend_key) if spend_key.is_encrypted() => "encrypted", _ => "not encrypted", }; - display!(io, &mut w_lock; " Alias \"{}\" ({})", alias, encrypted_status).unwrap(); + display_line!(io, &mut w_lock; " Alias \"{}\" ({}):", alias, encrypted_status).unwrap(); // Always print the corresponding viewing key display_line!(io, &mut w_lock; " Viewing Key: {}", key).unwrap(); // A subset of viewing keys will have corresponding spending keys. @@ -1201,7 +1201,7 @@ fn transparent_public_key_add( .unwrap_or_else(|err| edisplay_line!(io, "{}", err)); display_line!( io, - "Successfully added public key with alias: \"{}\"", + "Successfully added a public key with alias: \"{}\"", alias ); } @@ -1226,7 +1226,11 @@ fn transparent_address_add( wallet .save() .unwrap_or_else(|err| edisplay_line!(io, "{}", err)); - display_line!(io, "Successfully added address with alias: \"{}\"", alias); + display_line!( + io, + "Successfully added an address with alias: \"{}\"", + alias + ); } /// Load wallet for chain when `ctx.chain.is_some()` or pre-genesis wallet when From 26ca60c8507e0d00eee8a443db385334b4c6afb1 Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Fri, 15 Dec 2023 01:39:02 +0100 Subject: [PATCH 124/216] Remove unused import --- apps/src/lib/cli/wallet.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index 21c5526dbe..c6de0d6c5c 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -20,7 +20,7 @@ use namada_sdk::masp::find_valid_diversifier; use namada_sdk::wallet::{ DecryptionError, DerivationPath, DerivationPathError, FindKeyError, Wallet, }; -use namada_sdk::{display, display_line, edisplay_line}; +use namada_sdk::{display_line, edisplay_line}; use rand_core::OsRng; use crate::cli; From 600529acab7f7c0124fe52e0d0648d192b892fec Mon Sep 17 00:00:00 2001 From: Aleksandr Karbyshev Date: Fri, 15 Dec 2023 17:40:29 +0100 Subject: [PATCH 125/216] Add changelog --- .../features/2260-wallet-cli-revamping-main-rebased.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changelog/unreleased/features/2260-wallet-cli-revamping-main-rebased.md diff --git a/.changelog/unreleased/features/2260-wallet-cli-revamping-main-rebased.md b/.changelog/unreleased/features/2260-wallet-cli-revamping-main-rebased.md new file mode 100644 index 0000000000..a174f62c37 --- /dev/null +++ b/.changelog/unreleased/features/2260-wallet-cli-revamping-main-rebased.md @@ -0,0 +1,7 @@ +- The wallet CLI structure has been significantly reworked and simplified. + Alias argument is now obligatory for key generation / derivation + commands. Feature of raw (non-HD) key generation has been restored, + which was removed in the previous release. Key export / import + functionality for both transparent and shielded key kinds has been + implemented. Additionally, several other improvements have been made. + ([\#2260](https://github.com/anoma/namada/pull/2260)) \ No newline at end of file From 00cf592f0588ac9688ae403adf37e1aa20841cc1 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 7 Dec 2023 15:39:52 -0500 Subject: [PATCH 126/216] refactor pos lib code --- apps/src/lib/bench_utils.rs | 8 +- .../lib/node/ledger/shell/finalize_block.rs | 160 +- apps/src/lib/node/ledger/shell/governance.rs | 3 +- apps/src/lib/node/ledger/shell/mod.rs | 12 +- .../lib/node/ledger/shell/prepare_proposal.rs | 9 +- .../lib/node/ledger/shell/process_proposal.rs | 2 +- apps/src/lib/node/ledger/shell/queries.rs | 2 +- .../src/lib/node/ledger/shell/testing/node.rs | 4 +- .../shell/vote_extensions/bridge_pool_vext.rs | 10 +- .../shell/vote_extensions/eth_events.rs | 4 +- .../shell/vote_extensions/val_set_update.rs | 7 +- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 2 +- benches/native_vps.rs | 6 +- benches/txs.rs | 21 +- .../transactions/bridge_pool_roots.rs | 2 +- .../transactions/ethereum_events/mod.rs | 18 +- .../src/protocol/transactions/votes.rs | 2 +- .../src/protocol/transactions/votes/update.rs | 4 +- .../src/storage/eth_bridge_queries.rs | 2 +- proof_of_stake/src/lib.rs | 6534 ++++------------- proof_of_stake/src/pos_queries.rs | 7 +- proof_of_stake/src/queries.rs | 457 ++ proof_of_stake/src/rewards.rs | 376 +- proof_of_stake/src/slashing.rs | 1124 +++ proof_of_stake/src/storage.rs | 1550 ++-- proof_of_stake/src/storage_key.rs | 852 +++ proof_of_stake/src/tests.rs | 152 +- proof_of_stake/src/tests/state_machine.rs | 98 +- proof_of_stake/src/tests/state_machine_v2.rs | 104 +- proof_of_stake/src/types.rs | 11 + proof_of_stake/src/validator_set_update.rs | 965 +++ sdk/src/queries/vp/pos.rs | 34 +- shared/src/ledger/native_vp/ibc/mod.rs | 2 +- shared/src/ledger/pos/vp.rs | 4 +- tests/src/native_vp/pos.rs | 6 +- tx_prelude/src/proof_of_stake.rs | 8 +- 36 files changed, 6386 insertions(+), 6176 deletions(-) create mode 100644 proof_of_stake/src/queries.rs create mode 100644 proof_of_stake/src/slashing.rs create mode 100644 proof_of_stake/src/storage_key.rs create mode 100644 proof_of_stake/src/validator_set_update.rs diff --git a/apps/src/lib/bench_utils.rs b/apps/src/lib/bench_utils.rs index c793c84fc0..6d6099e71c 100644 --- a/apps/src/lib/bench_utils.rs +++ b/apps/src/lib/bench_utils.rs @@ -217,7 +217,8 @@ impl Default for BenchShell { source: Some(defaults::albert_address()), }; let params = - proof_of_stake::read_pos_params(&shell.wl_storage).unwrap(); + proof_of_stake::storage::read_pos_params(&shell.wl_storage) + .unwrap(); let mut bench_shell = BenchShell { inner: shell, tempdir, @@ -398,13 +399,14 @@ impl BenchShell { pub fn advance_epoch(&mut self) { let params = - proof_of_stake::read_pos_params(&self.inner.wl_storage).unwrap(); + proof_of_stake::storage::read_pos_params(&self.inner.wl_storage) + .unwrap(); self.wl_storage.storage.block.epoch = self.wl_storage.storage.block.epoch.next(); let current_epoch = self.wl_storage.storage.block.epoch; - proof_of_stake::copy_validator_sets_and_positions( + proof_of_stake::validator_set_update::copy_validator_sets_and_positions( &mut self.wl_storage, ¶ms, current_epoch, diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 239b7b35da..ecf7b98ac2 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -13,7 +13,7 @@ use namada::ledger::storage::wl_storage::WriteLogAndStorage; use namada::ledger::storage::EPOCH_SWITCH_BLOCKS_DELAY; use namada::ledger::storage_api::token::credit_tokens; use namada::ledger::storage_api::{pgf, StorageRead, StorageWrite}; -use namada::proof_of_stake::{ +use namada::proof_of_stake::storage::{ find_validator_by_raw_hash, read_last_block_proposer_address, read_pos_params, read_total_stake, write_last_block_proposer_address, }; @@ -90,7 +90,7 @@ where } let pos_params = - namada_proof_of_stake::read_pos_params(&self.wl_storage)?; + namada_proof_of_stake::storage::read_pos_params(&self.wl_storage)?; if new_epoch { update_allowed_conversions(&mut self.wl_storage)?; @@ -99,7 +99,7 @@ where // Copy the new_epoch + pipeline_len - 1 validator set into // new_epoch + pipeline_len - namada_proof_of_stake::copy_validator_sets_and_positions( + namada_proof_of_stake::validator_set_update::copy_validator_sets_and_positions( &mut self.wl_storage, &pos_params, current_epoch, @@ -704,7 +704,7 @@ where let inflation = token::Amount::from_uint(inflation, 0) .expect("Should not fail Uint -> Amount conversion"); - namada_proof_of_stake::update_rewards_products_and_mint_inflation( + namada_proof_of_stake::rewards::update_rewards_products_and_mint_inflation( &mut self.wl_storage, ¶ms, last_epoch, @@ -829,7 +829,7 @@ where tracing::debug!( "Found last block proposer: {proposer_address}" ); - namada_proof_of_stake::log_block_rewards( + namada_proof_of_stake::rewards::log_block_rewards( &mut self.wl_storage, if new_epoch { current_epoch.prev() @@ -962,19 +962,19 @@ mod test_finalize_block { use namada::ledger::storage_api; use namada::ledger::storage_api::StorageWrite; use namada::proof_of_stake::storage::{ - is_validator_slashes_key, slashes_prefix, - }; - use namada::proof_of_stake::types::{ - BondId, SlashType, ValidatorState, WeightedValidator, - }; - use namada::proof_of_stake::{ enqueued_slashes_handle, get_num_consensus_validators, read_consensus_validator_set_addresses_with_stake, - read_validator_stake, rewards_accumulator_handle, unjail_validator, + read_validator_stake, rewards_accumulator_handle, validator_consensus_key_handle, validator_rewards_products_handle, validator_slashes_handle, validator_state_handle, write_pos_params, - ADDRESS as pos_address, }; + use namada::proof_of_stake::storage_key::{ + is_validator_slashes_key, slashes_prefix, + }; + use namada::proof_of_stake::types::{ + BondId, SlashType, ValidatorState, WeightedValidator, + }; + use namada::proof_of_stake::{unjail_validator, ADDRESS as pos_address}; use namada::proto::{Code, Data, Section, Signature}; use namada::types::dec::POS_DECIMAL_PRECISION; use namada::types::ethereum_events::{EthAddress, Uint as ethUint}; @@ -993,7 +993,7 @@ mod test_finalize_block { use namada::types::uint::Uint; use namada::types::vote_extensions::ethereum_events; use namada_sdk::eth_bridge::MinimumConfirmations; - use namada_sdk::proof_of_stake::{ + use namada_sdk::proof_of_stake::storage::{ liveness_missed_votes_handle, liveness_sum_missed_votes_handle, read_consensus_validator_set_addresses, }; @@ -1795,12 +1795,15 @@ mod test_finalize_block { // Keep applying finalize block let validator = shell.mode.get_validator_address().unwrap(); let pos_params = - namada_proof_of_stake::read_pos_params(&shell.wl_storage).unwrap(); - let consensus_key = - namada_proof_of_stake::validator_consensus_key_handle(validator) - .get(&shell.wl_storage, Epoch::default(), &pos_params) - .unwrap() + namada_proof_of_stake::storage::read_pos_params(&shell.wl_storage) .unwrap(); + let consensus_key = + namada_proof_of_stake::storage::validator_consensus_key_handle( + validator, + ) + .get(&shell.wl_storage, Epoch::default(), &pos_params) + .unwrap() + .unwrap(); let proposer_address = HEXUPPER .decode(consensus_key.tm_raw_hash().as_bytes()) .unwrap(); @@ -2341,7 +2344,7 @@ mod test_finalize_block { // Check the bond amounts for rewards up thru the withdrawable epoch let withdraw_epoch = current_epoch + params.withdrawable_epoch_offset(); let last_claim_epoch = - namada_proof_of_stake::get_last_reward_claim_epoch( + namada_proof_of_stake::storage::get_last_reward_claim_epoch( &shell.wl_storage, &validator.address, &validator.address, @@ -2459,7 +2462,7 @@ mod test_finalize_block { let validator = validator_set.pop_first().unwrap(); let commission_rate = - namada_proof_of_stake::validator_commission_rate_handle( + namada_proof_of_stake::storage::validator_commission_rate_handle( &validator.address, ) .get(&shell.wl_storage, Epoch(0), ¶ms) @@ -2673,8 +2676,10 @@ mod test_finalize_block { // Check that there's 3 unique consensus keys let consensus_keys = - namada_proof_of_stake::get_consensus_key_set(&shell.wl_storage) - .unwrap(); + namada_proof_of_stake::storage::get_consensus_key_set( + &shell.wl_storage, + ) + .unwrap(); assert_eq!(consensus_keys.len(), 3); // let ck1 = validator_consensus_key_handle(&validator) // .get(&storage, current_epoch, ¶ms) @@ -2728,8 +2733,10 @@ mod test_finalize_block { // Check that there's 5 unique consensus keys let consensus_keys = - namada_proof_of_stake::get_consensus_key_set(&shell.wl_storage) - .unwrap(); + namada_proof_of_stake::storage::get_consensus_key_set( + &shell.wl_storage, + ) + .unwrap(); assert_eq!(consensus_keys.len(), 5); // Advance to pipeline epoch @@ -2808,8 +2815,10 @@ mod test_finalize_block { // Check that there's 7 unique consensus keys let consensus_keys = - namada_proof_of_stake::get_consensus_key_set(&shell.wl_storage) - .unwrap(); + namada_proof_of_stake::storage::get_consensus_key_set( + &shell.wl_storage, + ) + .unwrap(); assert_eq!(consensus_keys.len(), 7); // Advance to pipeline epoch @@ -2871,8 +2880,10 @@ mod test_finalize_block { // Check that there's 8 unique consensus keys let consensus_keys = - namada_proof_of_stake::get_consensus_key_set(&shell.wl_storage) - .unwrap(); + namada_proof_of_stake::storage::get_consensus_key_set( + &shell.wl_storage, + ) + .unwrap(); assert_eq!(consensus_keys.len(), 8); // Advance to pipeline epoch @@ -3449,12 +3460,15 @@ mod test_finalize_block { let validator = shell.mode.get_validator_address().unwrap().to_owned(); let pos_params = - namada_proof_of_stake::read_pos_params(&shell.wl_storage).unwrap(); - let consensus_key = - namada_proof_of_stake::validator_consensus_key_handle(&validator) - .get(&shell.wl_storage, Epoch::default(), &pos_params) - .unwrap() + namada_proof_of_stake::storage::read_pos_params(&shell.wl_storage) .unwrap(); + let consensus_key = + namada_proof_of_stake::storage::validator_consensus_key_handle( + &validator, + ) + .get(&shell.wl_storage, Epoch::default(), &pos_params) + .unwrap() + .unwrap(); let proposer_address = HEXUPPER .decode(consensus_key.tm_raw_hash().as_bytes()) .unwrap(); @@ -4030,7 +4044,7 @@ mod test_finalize_block { ) .unwrap(); - let val_stake = namada_proof_of_stake::read_validator_stake( + let val_stake = namada_proof_of_stake::storage::read_validator_stake( &shell.wl_storage, ¶ms, &val1.address, @@ -4038,7 +4052,7 @@ mod test_finalize_block { ) .unwrap(); - let total_stake = namada_proof_of_stake::read_total_stake( + let total_stake = namada_proof_of_stake::storage::read_total_stake( &shell.wl_storage, ¶ms, current_epoch + params.pipeline_len, @@ -4073,14 +4087,14 @@ mod test_finalize_block { ) .unwrap(); - let val_stake = namada_proof_of_stake::read_validator_stake( + let val_stake = namada_proof_of_stake::storage::read_validator_stake( &shell.wl_storage, ¶ms, &val1.address, current_epoch + params.pipeline_len, ) .unwrap(); - let total_stake = namada_proof_of_stake::read_total_stake( + let total_stake = namada_proof_of_stake::storage::read_total_stake( &shell.wl_storage, ¶ms, current_epoch + params.pipeline_len, @@ -4218,16 +4232,18 @@ mod test_finalize_block { assert_eq!(enqueued_slash.r#type, SlashType::DuplicateVote); assert_eq!(enqueued_slash.rate, Dec::zero()); let last_slash = - namada_proof_of_stake::read_validator_last_slash_epoch( + namada_proof_of_stake::storage::read_validator_last_slash_epoch( &shell.wl_storage, &val1.address, ) .unwrap(); assert_eq!(last_slash, Some(misbehavior_epoch)); assert!( - namada_proof_of_stake::validator_slashes_handle(&val1.address) - .is_empty(&shell.wl_storage) - .unwrap() + namada_proof_of_stake::storage::validator_slashes_handle( + &val1.address + ) + .is_empty(&shell.wl_storage) + .unwrap() ); println!("Advancing to epoch 7"); @@ -4286,7 +4302,7 @@ mod test_finalize_block { assert_eq!(enqueued_slashes_8.len(&shell.wl_storage).unwrap(), 2_u64); assert_eq!(enqueued_slashes_9.len(&shell.wl_storage).unwrap(), 1_u64); let last_slash = - namada_proof_of_stake::read_validator_last_slash_epoch( + namada_proof_of_stake::storage::read_validator_last_slash_epoch( &shell.wl_storage, &val1.address, ) @@ -4302,18 +4318,21 @@ mod test_finalize_block { .unwrap() ); assert!( - namada_proof_of_stake::validator_slashes_handle(&val1.address) - .is_empty(&shell.wl_storage) - .unwrap() + namada_proof_of_stake::storage::validator_slashes_handle( + &val1.address + ) + .is_empty(&shell.wl_storage) + .unwrap() ); - let pre_stake_10 = namada_proof_of_stake::read_validator_stake( - &shell.wl_storage, - ¶ms, - &val1.address, - Epoch(10), - ) - .unwrap(); + let pre_stake_10 = + namada_proof_of_stake::storage::read_validator_stake( + &shell.wl_storage, + ¶ms, + &val1.address, + Epoch(10), + ) + .unwrap(); assert_eq!( pre_stake_10, initial_stake + del_1_amount @@ -4340,14 +4359,14 @@ mod test_finalize_block { let (current_epoch, _) = advance_epoch(&mut shell, &pkh1, &votes, None); assert_eq!(current_epoch.0, 9_u64); - let val_stake_3 = namada_proof_of_stake::read_validator_stake( + let val_stake_3 = namada_proof_of_stake::storage::read_validator_stake( &shell.wl_storage, ¶ms, &val1.address, Epoch(3), ) .unwrap(); - let val_stake_4 = namada_proof_of_stake::read_validator_stake( + let val_stake_4 = namada_proof_of_stake::storage::read_validator_stake( &shell.wl_storage, ¶ms, &val1.address, @@ -4355,13 +4374,13 @@ mod test_finalize_block { ) .unwrap(); - let tot_stake_3 = namada_proof_of_stake::read_total_stake( + let tot_stake_3 = namada_proof_of_stake::storage::read_total_stake( &shell.wl_storage, ¶ms, Epoch(3), ) .unwrap(); - let tot_stake_4 = namada_proof_of_stake::read_total_stake( + let tot_stake_4 = namada_proof_of_stake::storage::read_total_stake( &shell.wl_storage, ¶ms, Epoch(4), @@ -4385,7 +4404,9 @@ mod test_finalize_block { // There should be 2 slashes processed for the validator, each with rate // equal to the cubic slashing rate let val_slashes = - namada_proof_of_stake::validator_slashes_handle(&val1.address); + namada_proof_of_stake::storage::validator_slashes_handle( + &val1.address, + ); assert_eq!(val_slashes.len(&shell.wl_storage).unwrap(), 2u64); let is_rate_good = val_slashes .iter(&shell.wl_storage) @@ -4562,7 +4583,7 @@ mod test_finalize_block { assert_eq!(current_epoch.0, 12_u64); println!("\nCHECK BOND AND UNBOND DETAILS"); - let details = namada_proof_of_stake::bonds_and_unbonds( + let details = namada_proof_of_stake::queries::bonds_and_unbonds( &shell.wl_storage, None, None, @@ -4772,13 +4793,13 @@ mod test_finalize_block { let consensus_val_set_len = max_proposal_period + default_past_epochs; let consensus_val_set = - namada_proof_of_stake::consensus_validator_set_handle(); + namada_proof_of_stake::storage::consensus_validator_set_handle(); // let below_cap_val_set = // namada_proof_of_stake::below_capacity_validator_set_handle(); let validator_positions = - namada_proof_of_stake::validator_set_positions_handle(); + namada_proof_of_stake::storage::validator_set_positions_handle(); let all_validator_addresses = - namada_proof_of_stake::validator_addresses_handle(); + namada_proof_of_stake::storage::validator_addresses_handle(); let consensus_set: Vec = read_consensus_validator_set_addresses_with_stake( @@ -4959,13 +4980,14 @@ mod test_finalize_block { Epoch::default(), ); - let validator_stake = namada_proof_of_stake::read_validator_stake( - &shell.wl_storage, - ¶ms, - &val2, - Epoch::default(), - ) - .unwrap(); + let validator_stake = + namada_proof_of_stake::storage::read_validator_stake( + &shell.wl_storage, + ¶ms, + &val2, + Epoch::default(), + ) + .unwrap(); let val3 = initial_consensus_set[2].clone(); let val4 = initial_consensus_set[3].clone(); diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index 0bbdac54ce..4991310d09 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -19,8 +19,9 @@ use namada::ledger::protocol; use namada::ledger::storage::types::encode; use namada::ledger::storage::{DBIter, StorageHasher, DB}; use namada::ledger::storage_api::{pgf, token, StorageWrite}; +use namada::proof_of_stake::bond_amount; use namada::proof_of_stake::parameters::PosParams; -use namada::proof_of_stake::{bond_amount, read_total_stake}; +use namada::proof_of_stake::storage::read_total_stake; use namada::proto::{Code, Data}; use namada::types::address::Address; use namada::types::storage::Epoch; diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 8e33bf87bb..2d56c98410 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -51,7 +51,9 @@ use namada::ledger::storage::{ use namada::ledger::storage_api::tx::validate_tx_bytes; use namada::ledger::storage_api::{self, StorageRead}; use namada::ledger::{parameters, pos, protocol}; -use namada::proof_of_stake::{self, process_slashes, read_pos_params, slash}; +use namada::proof_of_stake::slashing::{process_slashes, slash}; +use namada::proof_of_stake::storage::read_pos_params; +use namada::proof_of_stake::{self}; use namada::proto::{self, Section, Tx}; use namada::types::address::Address; use namada::types::chain::ChainId; @@ -749,7 +751,7 @@ where let validator_raw_hash = tm_raw_hash_to_string(evidence.validator.address); let validator = - match proof_of_stake::find_validator_by_raw_hash( + match proof_of_stake::storage::find_validator_by_raw_hash( &self.wl_storage, &validator_raw_hash, ) @@ -1536,13 +1538,13 @@ where let (current_epoch, _gas) = self.wl_storage.storage.get_current_epoch(); let pos_params = - namada_proof_of_stake::read_pos_params(&self.wl_storage) + namada_proof_of_stake::storage::read_pos_params(&self.wl_storage) .expect("Could not find the PoS parameters"); let validator_set_update_fn = if is_genesis { namada_proof_of_stake::genesis_validator_set_tendermint } else { - namada_proof_of_stake::validator_set_update_tendermint + namada_proof_of_stake::validator_set_update::validator_set_update_tendermint }; validator_set_update_fn( @@ -1589,7 +1591,7 @@ mod test_utils { use namada::ledger::storage::{LastBlock, Sha256Hasher}; use namada::ledger::storage_api::StorageWrite; use namada::proof_of_stake::parameters::PosParams; - use namada::proof_of_stake::validator_consensus_key_handle; + use namada::proof_of_stake::storage::validator_consensus_key_handle; use namada::proto::{Code, Data}; use namada::tendermint::abci::types::VoteInfo; use namada::types::address; diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 6a4ca7ab2d..591d9b8af0 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -5,7 +5,7 @@ use namada::core::ledger::gas::TxGasMeter; use namada::ledger::pos::PosQueries; use namada::ledger::protocol::get_fee_unshielding_transaction; use namada::ledger::storage::{DBIter, StorageHasher, TempWlStorage, DB}; -use namada::proof_of_stake::find_validator_by_raw_hash; +use namada::proof_of_stake::storage::find_validator_by_raw_hash; use namada::proto::Tx; use namada::types::address::Address; use namada::types::internal::TxInQueue; @@ -382,11 +382,12 @@ mod test_prepare_proposal { use namada::ledger::gas::Gas; use namada::ledger::pos::PosQueries; use namada::ledger::replay_protection; - use namada::proof_of_stake::types::WeightedValidator; - use namada::proof_of_stake::{ + use namada::proof_of_stake::storage::{ consensus_validator_set_handle, - read_consensus_validator_set_addresses_with_stake, Epoch, + read_consensus_validator_set_addresses_with_stake, }; + use namada::proof_of_stake::types::WeightedValidator; + use namada::proof_of_stake::Epoch; use namada::proto::{Code, Data, Header, Section, Signature, Signed}; use namada::types::address::{self, Address}; use namada::types::ethereum_events::EthereumEvent; diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 7fad385eda..bfafbbfae1 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -8,7 +8,7 @@ use namada::ledger::pos::PosQueries; use namada::ledger::protocol::get_fee_unshielding_transaction; use namada::ledger::storage::TempWlStorage; use namada::ledger::storage_api::tx::validate_tx_bytes; -use namada::proof_of_stake::find_validator_by_raw_hash; +use namada::proof_of_stake::storage::find_validator_by_raw_hash; use namada::types::internal::TxInQueue; use namada::types::transaction::protocol::{ ethereum_tx_data_variants, ProtocolTxType, diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 84c494faca..6adb53969d 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -68,7 +68,7 @@ where mod test_queries { use namada::core::ledger::storage::EPOCH_SWITCH_BLOCKS_DELAY; use namada::ledger::pos::PosQueries; - use namada::proof_of_stake::read_consensus_validator_set_addresses_with_stake; + use namada::proof_of_stake::storage::read_consensus_validator_set_addresses_with_stake; use namada::proof_of_stake::types::WeightedValidator; use namada::tendermint::abci::types::VoteInfo; use namada::types::storage::Epoch; diff --git a/apps/src/lib/node/ledger/shell/testing/node.rs b/apps/src/lib/node/ledger/shell/testing/node.rs index 4f8fa13342..6f27f92d9b 100644 --- a/apps/src/lib/node/ledger/shell/testing/node.rs +++ b/apps/src/lib/node/ledger/shell/testing/node.rs @@ -20,11 +20,11 @@ use namada::ledger::storage::{ LastBlock, Sha256Hasher, EPOCH_SWITCH_BLOCKS_DELAY, }; use namada::proof_of_stake::pos_queries::PosQueries; -use namada::proof_of_stake::types::WeightedValidator; -use namada::proof_of_stake::{ +use namada::proof_of_stake::storage::{ read_consensus_validator_set_addresses_with_stake, validator_consensus_key_handle, }; +use namada::proof_of_stake::types::WeightedValidator; use namada::tendermint::abci::response::Info; use namada::tendermint::abci::types::VoteInfo; use namada::tendermint_rpc::SimpleRequest; diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/bridge_pool_vext.rs b/apps/src/lib/node/ledger/shell/vote_extensions/bridge_pool_vext.rs index 5e4f34ac2c..768d2e0ac6 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/bridge_pool_vext.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/bridge_pool_vext.rs @@ -203,14 +203,14 @@ mod test_bp_vote_extensions { use namada::core::ledger::eth_bridge::storage::bridge_pool::get_key_from_hash; use namada::ledger::pos::PosQueries; use namada::ledger::storage_api::StorageWrite; + use namada::proof_of_stake::storage::{ + consensus_validator_set_handle, + read_consensus_validator_set_addresses_with_stake, + }; use namada::proof_of_stake::types::{ Position as ValidatorPosition, WeightedValidator, }; - use namada::proof_of_stake::{ - become_validator, consensus_validator_set_handle, - read_consensus_validator_set_addresses_with_stake, BecomeValidator, - Epoch, - }; + use namada::proof_of_stake::{become_validator, BecomeValidator, Epoch}; use namada::proto::{SignableEthMessage, Signed}; use namada::tendermint::abci::types::VoteInfo; use namada::types::ethereum_events::Uint; diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index 127c4c4d0e..23c3013458 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -299,11 +299,11 @@ mod test_vote_extensions { use namada::eth_bridge::storage::bridge_pool; use namada::ledger::eth_bridge::EthBridgeQueries; use namada::ledger::pos::PosQueries; - use namada::proof_of_stake::types::WeightedValidator; - use namada::proof_of_stake::{ + use namada::proof_of_stake::storage::{ consensus_validator_set_handle, read_consensus_validator_set_addresses_with_stake, }; + use namada::proof_of_stake::types::WeightedValidator; use namada::tendermint::abci::types::VoteInfo; use namada::types::address::testing::gen_established_address; use namada::types::ethereum_events::{ diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index b4b5595a0e..91ecadbdc5 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -258,11 +258,12 @@ mod test_vote_extensions { NestedSubKey, SubKey, }; use namada::ledger::pos::PosQueries; - use namada::proof_of_stake::types::WeightedValidator; - use namada::proof_of_stake::{ + use namada::proof_of_stake::storage::{ consensus_validator_set_handle, - read_consensus_validator_set_addresses_with_stake, Epoch, + read_consensus_validator_set_addresses_with_stake, }; + use namada::proof_of_stake::types::WeightedValidator; + use namada::proof_of_stake::Epoch; use namada::tendermint::abci::types::VoteInfo; use namada::types::key::RefTo; use namada::types::vote_extensions::validator_set_update; diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 563d88ac59..a8140ce56a 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -5,7 +5,7 @@ use std::pin::Pin; use std::task::{Context, Poll}; use futures::future::FutureExt; -use namada::proof_of_stake::find_validator_by_raw_hash; +use namada::proof_of_stake::storage::find_validator_by_raw_hash; use namada::proto::Tx; use namada::types::hash::Hash; use namada::types::key::tm_raw_hash_to_string; diff --git a/benches/native_vps.rs b/benches/native_vps.rs index 7981216918..46c2cac5eb 100644 --- a/benches/native_vps.rs +++ b/benches/native_vps.rs @@ -110,7 +110,8 @@ fn governance(c: &mut Criterion) { let content_section = Section::ExtraData(Code::new(vec![], None)); let params = - proof_of_stake::read_pos_params(&shell.wl_storage).unwrap(); + proof_of_stake::storage::read_pos_params(&shell.wl_storage) + .unwrap(); let voting_start_epoch = Epoch(2 + params.pipeline_len + params.unbonding_len); // Must start after current epoch @@ -161,7 +162,8 @@ fn governance(c: &mut Criterion) { )); let params = - proof_of_stake::read_pos_params(&shell.wl_storage).unwrap(); + proof_of_stake::storage::read_pos_params(&shell.wl_storage) + .unwrap(); let voting_start_epoch = Epoch(2 + params.pipeline_len + params.unbonding_len); // Must start after current epoch diff --git a/benches/txs.rs b/benches/txs.rs index 454b50f401..50f4bd256a 100644 --- a/benches/txs.rs +++ b/benches/txs.rs @@ -25,8 +25,9 @@ use namada::ibc::core::host::types::identifiers::{ }; use namada::ledger::eth_bridge::read_native_erc20_address; use namada::ledger::storage_api::{StorageRead, StorageWrite}; +use namada::proof_of_stake::storage::read_pos_params; use namada::proof_of_stake::types::SlashType; -use namada::proof_of_stake::{self, read_pos_params, KeySeg}; +use namada::proof_of_stake::{self, KeySeg}; use namada::proto::{Code, Section}; use namada::types::address::{self, Address}; use namada::types::eth_bridge_pool::{GasFee, PendingTransfer}; @@ -285,9 +286,10 @@ fn withdraw(c: &mut Criterion) { shell.wl_storage.commit_tx(); // Advance Epoch for pipeline and unbonding length - let params = - proof_of_stake::read_pos_params(&shell.wl_storage) - .unwrap(); + let params = proof_of_stake::storage::read_pos_params( + &shell.wl_storage, + ) + .unwrap(); let advance_epochs = params.pipeline_len + params.unbonding_len; @@ -330,7 +332,7 @@ fn redelegate(c: &mut Criterion) { let shell = BenchShell::default(); // Find the other genesis validator let current_epoch = shell.wl_storage.get_block_epoch().unwrap(); - let validators = namada::proof_of_stake::read_consensus_validator_set_addresses(&shell.inner.wl_storage, current_epoch).unwrap(); + let validators = namada::proof_of_stake::storage::read_consensus_validator_set_addresses(&shell.inner.wl_storage, current_epoch).unwrap(); let validator_2 = validators.into_iter().find(|addr| addr != &defaults::validator_address()).expect("There must be another validator to redelegate to"); // Prepare the redelegation tx (shell, redelegation(validator_2)) @@ -835,7 +837,7 @@ fn unjail_validator(c: &mut Criterion) { let pos_params = read_pos_params(&shell.wl_storage).unwrap(); let current_epoch = shell.wl_storage.storage.block.epoch; let evidence_epoch = current_epoch.prev(); - proof_of_stake::slash( + proof_of_stake::slashing::slash( &mut shell.wl_storage, &pos_params, current_epoch, @@ -1055,9 +1057,10 @@ fn claim_rewards(c: &mut Criterion) { let mut shell = BenchShell::default(); // Advance Epoch for pipeline and unbonding length - let params = - proof_of_stake::read_pos_params(&shell.wl_storage) - .unwrap(); + let params = proof_of_stake::storage::read_pos_params( + &shell.wl_storage, + ) + .unwrap(); let advance_epochs = params.pipeline_len + params.unbonding_len; diff --git a/ethereum_bridge/src/protocol/transactions/bridge_pool_roots.rs b/ethereum_bridge/src/protocol/transactions/bridge_pool_roots.rs index 42041d9666..6fcf8fa93d 100644 --- a/ethereum_bridge/src/protocol/transactions/bridge_pool_roots.rs +++ b/ethereum_bridge/src/protocol/transactions/bridge_pool_roots.rs @@ -235,7 +235,7 @@ mod test_apply_bp_roots_to_storage { use namada_core::types::vote_extensions::bridge_pool_roots; use namada_core::types::voting_power::FractionalVotingPower; use namada_proof_of_stake::parameters::OwnedPosParams; - use namada_proof_of_stake::write_pos_params; + use namada_proof_of_stake::storage::write_pos_params; use super::*; use crate::protocol::transactions::votes::{ diff --git a/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs b/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs index e9ff768fa0..d15c71eaa8 100644 --- a/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs +++ b/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs @@ -708,10 +708,11 @@ mod tests { // commit then update the epoch wl_storage.storage.commit_block(MockDBWriteBatch).unwrap(); - let unbonding_len = namada_proof_of_stake::read_pos_params(&wl_storage) - .expect("Test failed") - .unbonding_len - + 1; + let unbonding_len = + namada_proof_of_stake::storage::read_pos_params(&wl_storage) + .expect("Test failed") + .unbonding_len + + 1; wl_storage.storage.last_epoch = wl_storage.storage.last_epoch + unbonding_len; wl_storage.storage.block.epoch = wl_storage.storage.last_epoch + 1_u64; @@ -844,10 +845,11 @@ mod tests { // commit then update the epoch wl_storage.storage.commit_block(MockDBWriteBatch).unwrap(); - let unbonding_len = namada_proof_of_stake::read_pos_params(&wl_storage) - .expect("Test failed") - .unbonding_len - + 1; + let unbonding_len = + namada_proof_of_stake::storage::read_pos_params(&wl_storage) + .expect("Test failed") + .unbonding_len + + 1; wl_storage.storage.last_epoch = wl_storage.storage.last_epoch + unbonding_len; wl_storage.storage.block.epoch = wl_storage.storage.last_epoch + 1_u64; diff --git a/ethereum_bridge/src/protocol/transactions/votes.rs b/ethereum_bridge/src/protocol/transactions/votes.rs index cc029e28f5..cdf7461047 100644 --- a/ethereum_bridge/src/protocol/transactions/votes.rs +++ b/ethereum_bridge/src/protocol/transactions/votes.rs @@ -190,7 +190,7 @@ mod tests { use namada_core::types::storage::BlockHeight; use namada_core::types::{address, token}; use namada_proof_of_stake::parameters::OwnedPosParams; - use namada_proof_of_stake::write_pos_params; + use namada_proof_of_stake::storage::write_pos_params; use super::*; use crate::test_utils; diff --git a/ethereum_bridge/src/protocol/transactions/votes/update.rs b/ethereum_bridge/src/protocol/transactions/votes/update.rs index a98be1859d..b6ac1d102d 100644 --- a/ethereum_bridge/src/protocol/transactions/votes/update.rs +++ b/ethereum_bridge/src/protocol/transactions/votes/update.rs @@ -220,7 +220,7 @@ mod tests { use crate::test_utils; mod helpers { - use namada_proof_of_stake::total_consensus_stake_key_handle; + use namada_proof_of_stake::storage::total_consensus_stake_handle; use super::*; @@ -279,7 +279,7 @@ mod tests { > FractionalVotingPower::TWO_THIRDS * total_stake, }; votes::storage::write(wl_storage, &keys, event, &tally, false)?; - total_consensus_stake_key_handle().set( + total_consensus_stake_handle().set( wl_storage, total_stake, 0u64.into(), diff --git a/ethereum_bridge/src/storage/eth_bridge_queries.rs b/ethereum_bridge/src/storage/eth_bridge_queries.rs index 06cb1ed024..5e132592de 100644 --- a/ethereum_bridge/src/storage/eth_bridge_queries.rs +++ b/ethereum_bridge/src/storage/eth_bridge_queries.rs @@ -22,7 +22,7 @@ use namada_core::types::voting_power::{ EthBridgeVotingPower, FractionalVotingPower, }; use namada_proof_of_stake::pos_queries::{ConsensusValidators, PosQueries}; -use namada_proof_of_stake::{ +use namada_proof_of_stake::storage::{ validator_eth_cold_key_handle, validator_eth_hot_key_handle, }; diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 3d79a345e9..089cf8e424 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -9,9 +9,13 @@ pub mod epoched; pub mod parameters; pub mod pos_queries; +pub mod queries; pub mod rewards; +pub mod slashing; pub mod storage; +pub mod storage_key; pub mod types; +pub mod validator_set_update; // pub mod validation; mod error; @@ -19,55 +23,67 @@ mod error; mod tests; use core::fmt::Debug; -use std::cmp::{self, Reverse}; -use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; +use std::cmp::{self}; +use std::collections::{BTreeMap, BTreeSet, HashSet}; -use borsh::BorshDeserialize; pub use error::*; use namada_core::ledger::storage_api::collections::lazy_map::{ - Collectable, LazyMap, NestedMap, NestedSubKey, SubKey, + Collectable, LazyMap, NestedSubKey, SubKey, }; -use namada_core::ledger::storage_api::collections::{LazyCollection, LazySet}; use namada_core::ledger::storage_api::{ - self, governance, token, ResultExt, StorageRead, StorageWrite, + self, token, StorageRead, StorageWrite, }; -use namada_core::types::address::{self, Address, InternalAddress}; +use namada_core::types::address::{Address, InternalAddress}; use namada_core::types::dec::Dec; -use namada_core::types::key::{ - common, protocol_pk_key, tm_consensus_key_raw_hash, PublicKeyTmRawHash, -}; +use namada_core::types::key::common; use namada_core::types::storage::BlockHeight; pub use namada_core::types::storage::{Epoch, Key, KeySeg}; -use once_cell::unsync::Lazy; pub use parameters::{OwnedPosParams, PosParams}; -use rewards::PosRewardsCalculator; -use storage::{ - bonds_for_source_prefix, bonds_prefix, consensus_keys_key, - get_validator_address_from_bond, is_bond_key, is_unbond_key, - is_validator_slashes_key, last_block_proposer_key, - last_pos_reward_claim_epoch_key, params_key, rewards_counter_key, - slashes_prefix, unbonds_for_source_prefix, unbonds_prefix, - validator_address_raw_hash_key, validator_description_key, - validator_discord_key, validator_email_key, validator_last_slash_key, - validator_max_commission_rate_change_key, validator_website_key, + +use crate::queries::{find_bonds, has_bonds}; +use crate::rewards::{ + add_rewards_to_counter, compute_current_rewards_from_bonds, + read_rewards_counter, take_rewards_from_counter, +}; +use crate::slashing::{ + apply_list_slashes, compute_amount_after_slashing_unbond, + compute_amount_after_slashing_withdraw, find_validator_slashes, +}; +use crate::storage::{ + below_capacity_validator_set_handle, bond_handle, + consensus_validator_set_handle, delegator_redelegated_bonds_handle, + delegator_redelegated_unbonds_handle, get_last_reward_claim_epoch, + liveness_missed_votes_handle, liveness_sum_missed_votes_handle, + read_consensus_validator_set_addresses, read_non_pos_owned_params, + read_pos_params, read_validator_last_slash_epoch, + read_validator_max_commission_rate_change, read_validator_stake, + total_bonded_handle, total_consensus_stake_handle, total_unbonded_handle, + try_insert_consensus_key, unbond_handle, update_total_deltas, + update_validator_deltas, validator_addresses_handle, + validator_commission_rate_handle, validator_consensus_key_handle, + validator_deltas_handle, validator_eth_cold_key_handle, + validator_eth_hot_key_handle, validator_incoming_redelegations_handle, + validator_outgoing_redelegations_handle, validator_protocol_key_handle, + validator_rewards_products_handle, validator_set_positions_handle, + validator_slashes_handle, validator_state_handle, + validator_total_redelegated_bonded_handle, + validator_total_redelegated_unbonded_handle, write_last_reward_claim_epoch, + write_pos_params, write_validator_address_raw_hash, + write_validator_description, write_validator_discord_handle, + write_validator_email, write_validator_max_commission_rate_change, + write_validator_metadata, write_validator_website, }; -use types::{ - into_tm_voting_power, BelowCapacityValidatorSet, - BelowCapacityValidatorSets, BondDetails, BondId, Bonds, - BondsAndUnbondsDetail, BondsAndUnbondsDetails, CommissionRates, - ConsensusValidator, ConsensusValidatorSet, ConsensusValidatorSets, - DelegatorRedelegatedBonded, DelegatorRedelegatedUnbonded, - EagerRedelegatedBondsMap, EpochedSlashes, IncomingRedelegations, - LivenessMissedVotes, LivenessSumMissedVotes, OutgoingRedelegations, - Position, RedelegatedBondsOrUnbonds, RedelegatedTokens, - ReverseOrdTokenAmount, RewardsAccumulator, RewardsProducts, Slash, - SlashType, SlashedAmount, Slashes, TotalConsensusStakes, TotalDeltas, - TotalRedelegatedBonded, TotalRedelegatedUnbonded, UnbondDetails, Unbonds, - ValidatorAddresses, ValidatorConsensusKeys, ValidatorDeltas, - ValidatorEthColdKeys, ValidatorEthHotKeys, ValidatorMetaData, - ValidatorPositionAddresses, ValidatorProtocolKeys, ValidatorSetPositions, - ValidatorSetUpdate, ValidatorState, ValidatorStates, - ValidatorTotalUnbonded, VoteInfo, WeightedValidator, +use crate::storage_key::{bonds_for_source_prefix, is_bond_key}; +use crate::types::{ + BondId, ConsensusValidator, ConsensusValidatorSet, + EagerRedelegatedBondsMap, RedelegatedBondsOrUnbonds, RedelegatedTokens, + ResultSlashing, Slash, Unbonds, ValidatorMetaData, ValidatorSetUpdate, + ValidatorState, VoteInfo, +}; +use crate::validator_set_update::{ + copy_validator_sets_and_positions, find_first_position, + get_max_below_capacity_validator_amount, insert_validator_into_set, + insert_validator_into_validator_set, update_validator_set, }; /// Address of the PoS account implemented as a native VP @@ -84,217 +100,6 @@ pub fn staking_token_address(storage: &impl StorageRead) -> Address { .expect("Must be able to read native token address") } -/// Get the storage handle to the epoched consensus validator set -pub fn consensus_validator_set_handle() -> ConsensusValidatorSets { - let key = storage::consensus_validator_set_key(); - ConsensusValidatorSets::open(key) -} - -/// Get the storage handle to the epoched below-capacity validator set -pub fn below_capacity_validator_set_handle() -> BelowCapacityValidatorSets { - let key = storage::below_capacity_validator_set_key(); - BelowCapacityValidatorSets::open(key) -} - -/// Get the storage handle to a PoS validator's consensus key (used for -/// signing block votes). -pub fn validator_consensus_key_handle( - validator: &Address, -) -> ValidatorConsensusKeys { - let key = storage::validator_consensus_key_key(validator); - ValidatorConsensusKeys::open(key) -} - -/// Get the storage handle to a PoS validator's protocol key key. -pub fn validator_protocol_key_handle( - validator: &Address, -) -> ValidatorProtocolKeys { - let key = protocol_pk_key(validator); - ValidatorProtocolKeys::open(key) -} - -/// Get the storage handle to a PoS validator's eth hot key. -pub fn validator_eth_hot_key_handle( - validator: &Address, -) -> ValidatorEthHotKeys { - let key = storage::validator_eth_hot_key_key(validator); - ValidatorEthHotKeys::open(key) -} - -/// Get the storage handle to a PoS validator's eth cold key. -pub fn validator_eth_cold_key_handle( - validator: &Address, -) -> ValidatorEthColdKeys { - let key = storage::validator_eth_cold_key_key(validator); - ValidatorEthColdKeys::open(key) -} - -/// Get the storage handle to the total consensus validator stake -pub fn total_consensus_stake_key_handle() -> TotalConsensusStakes { - let key = storage::total_consensus_stake_key(); - TotalConsensusStakes::open(key) -} - -/// Get the storage handle to a PoS validator's state -pub fn validator_state_handle(validator: &Address) -> ValidatorStates { - let key = storage::validator_state_key(validator); - ValidatorStates::open(key) -} - -/// Get the storage handle to a PoS validator's deltas -pub fn validator_deltas_handle(validator: &Address) -> ValidatorDeltas { - let key = storage::validator_deltas_key(validator); - ValidatorDeltas::open(key) -} - -/// Get the storage handle to the total deltas -pub fn total_deltas_handle() -> TotalDeltas { - let key = storage::total_deltas_key(); - TotalDeltas::open(key) -} - -/// Get the storage handle to the set of all validators -pub fn validator_addresses_handle() -> ValidatorAddresses { - let key = storage::validator_addresses_key(); - ValidatorAddresses::open(key) -} - -/// Get the storage handle to a PoS validator's commission rate -pub fn validator_commission_rate_handle( - validator: &Address, -) -> CommissionRates { - let key = storage::validator_commission_rate_key(validator); - CommissionRates::open(key) -} - -/// Get the storage handle to a bond, which is dynamically updated with when -/// unbonding -pub fn bond_handle(source: &Address, validator: &Address) -> Bonds { - let bond_id = BondId { - source: source.clone(), - validator: validator.clone(), - }; - let key = storage::bond_key(&bond_id); - Bonds::open(key) -} - -/// Get the storage handle to a validator's total bonds, which are not updated -/// due to unbonding -pub fn total_bonded_handle(validator: &Address) -> Bonds { - let key = storage::validator_total_bonded_key(validator); - Bonds::open(key) -} - -/// Get the storage handle to an unbond -pub fn unbond_handle(source: &Address, validator: &Address) -> Unbonds { - let bond_id = BondId { - source: source.clone(), - validator: validator.clone(), - }; - let key = storage::unbond_key(&bond_id); - Unbonds::open(key) -} - -/// Get the storage handle to a validator's total-unbonded map -pub fn total_unbonded_handle(validator: &Address) -> ValidatorTotalUnbonded { - let key = storage::validator_total_unbonded_key(validator); - ValidatorTotalUnbonded::open(key) -} - -/// Get the storage handle to a PoS validator's deltas -pub fn validator_set_positions_handle() -> ValidatorSetPositions { - let key = storage::validator_set_positions_key(); - ValidatorSetPositions::open(key) -} - -/// Get the storage handle to a PoS validator's slashes -pub fn validator_slashes_handle(validator: &Address) -> Slashes { - let key = storage::validator_slashes_key(validator); - Slashes::open(key) -} - -/// Get the storage handle to list of all slashes to be processed and ultimately -/// placed in the `validator_slashes_handle` -pub fn enqueued_slashes_handle() -> EpochedSlashes { - let key = storage::enqueued_slashes_key(); - EpochedSlashes::open(key) -} - -/// Get the storage handle to the rewards accumulator for the consensus -/// validators in a given epoch -pub fn rewards_accumulator_handle() -> RewardsAccumulator { - let key = storage::consensus_validator_rewards_accumulator_key(); - RewardsAccumulator::open(key) -} - -/// Get the storage handle to a validator's rewards products -pub fn validator_rewards_products_handle( - validator: &Address, -) -> RewardsProducts { - let key = storage::validator_rewards_product_key(validator); - RewardsProducts::open(key) -} - -/// Get the storage handle to a validator's incoming redelegations -pub fn validator_incoming_redelegations_handle( - validator: &Address, -) -> IncomingRedelegations { - let key = storage::validator_incoming_redelegations_key(validator); - IncomingRedelegations::open(key) -} - -/// Get the storage handle to a validator's outgoing redelegations -pub fn validator_outgoing_redelegations_handle( - validator: &Address, -) -> OutgoingRedelegations { - let key: Key = storage::validator_outgoing_redelegations_key(validator); - OutgoingRedelegations::open(key) -} - -/// Get the storage handle to a validator's total redelegated bonds -pub fn validator_total_redelegated_bonded_handle( - validator: &Address, -) -> TotalRedelegatedBonded { - let key: Key = storage::validator_total_redelegated_bonded_key(validator); - TotalRedelegatedBonded::open(key) -} - -/// Get the storage handle to a validator's outgoing redelegations -pub fn validator_total_redelegated_unbonded_handle( - validator: &Address, -) -> TotalRedelegatedUnbonded { - let key: Key = storage::validator_total_redelegated_unbonded_key(validator); - TotalRedelegatedUnbonded::open(key) -} - -/// Get the storage handle to a delegator's redelegated bonds information -pub fn delegator_redelegated_bonds_handle( - delegator: &Address, -) -> DelegatorRedelegatedBonded { - let key: Key = storage::delegator_redelegated_bonds_key(delegator); - DelegatorRedelegatedBonded::open(key) -} - -/// Get the storage handle to a delegator's redelegated unbonds information -pub fn delegator_redelegated_unbonds_handle( - delegator: &Address, -) -> DelegatorRedelegatedUnbonded { - let key: Key = storage::delegator_redelegated_unbonds_key(delegator); - DelegatorRedelegatedUnbonded::open(key) -} - -/// Get the storage handle to the missed votes for liveness tracking -pub fn liveness_missed_votes_handle() -> LivenessMissedVotes { - let key = storage::liveness_missed_votes_key(); - LivenessMissedVotes::open(key) -} - -/// Get the storage handle to the sum of missed votes for liveness tracking -pub fn liveness_sum_missed_votes_handle() -> LivenessSumMissedVotes { - let key = storage::liveness_sum_missed_votes_key(); - LivenessSumMissedVotes::open(key) -} - /// Init genesis. Requires that the governance parameters are initialized. pub fn init_genesis( storage: &mut S, @@ -339,4810 +144,1686 @@ where Ok(()) } -/// Read PoS parameters -pub fn read_pos_params(storage: &S) -> storage_api::Result -where - S: StorageRead, -{ - let params = storage - .read(¶ms_key()) - .transpose() - .expect("PosParams should always exist in storage after genesis")?; - read_non_pos_owned_params(storage, params) -} - -/// Read non-PoS-owned parameters to add them to `OwnedPosParams` to construct -/// `PosParams`. -pub fn read_non_pos_owned_params( +/// Check if the provided address is a validator address +pub fn is_validator( storage: &S, - owned: OwnedPosParams, -) -> storage_api::Result + address: &Address, +) -> storage_api::Result where S: StorageRead, { - let max_proposal_period = governance::get_max_proposal_period(storage)?; - Ok(PosParams { - owned, - max_proposal_period, - }) -} - -/// Write PoS parameters -pub fn write_pos_params( - storage: &mut S, - params: &OwnedPosParams, -) -> storage_api::Result<()> -where - S: StorageRead + StorageWrite, -{ - let key = params_key(); - storage.write(&key, params) + // TODO: should this check be made different? I suppose it does work but + // feels weird... + let rate = read_validator_max_commission_rate_change(storage, address)?; + Ok(rate.is_some()) } -/// Get the validator address given the raw hash of the Tendermint consensus key -pub fn find_validator_by_raw_hash( +/// Check if the provided address is a delegator address, optionally at a +/// particular epoch +pub fn is_delegator( storage: &S, - raw_hash: impl AsRef, -) -> storage_api::Result> + address: &Address, + epoch: Option, +) -> storage_api::Result where S: StorageRead, { - let key = validator_address_raw_hash_key(raw_hash); - storage.read(&key) + let prefix = bonds_for_source_prefix(address); + match epoch { + Some(epoch) => { + let iter = storage_api::iter_prefix_bytes(storage, &prefix)?; + for res in iter { + let (key, _) = res?; + if let Some((bond_id, bond_epoch)) = is_bond_key(&key) { + if bond_id.source != bond_id.validator + && bond_epoch <= epoch + { + return Ok(true); + } + } + } + Ok(false) + } + None => { + let iter = storage_api::iter_prefix_bytes(storage, &prefix)?; + for res in iter { + let (key, _) = res?; + if let Some((bond_id, _epoch)) = is_bond_key(&key) { + if bond_id.source != bond_id.validator { + return Ok(true); + } + } + } + Ok(false) + } + } } -/// Write PoS validator's address raw hash. -pub fn write_validator_address_raw_hash( +/// Self-bond tokens to a validator when `source` is `None` or equal to +/// the `validator` address, or delegate tokens from the `source` to the +/// `validator`. +pub fn bond_tokens( storage: &mut S, + source: Option<&Address>, validator: &Address, - consensus_key: &common::PublicKey, + amount: token::Amount, + current_epoch: Epoch, + offset_opt: Option, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, { - let raw_hash = tm_consensus_key_raw_hash(consensus_key); - storage.write(&validator_address_raw_hash_key(raw_hash), validator) + tracing::debug!( + "Bonding token amount {} at epoch {current_epoch}", + amount.to_string_native() + ); + if amount.is_zero() { + return Ok(()); + } + + // Transfer the bonded tokens from the source to PoS + if let Some(source) = source { + if source != validator && is_validator(storage, source)? { + return Err( + BondError::SourceMustNotBeAValidator(source.clone()).into() + ); + } + } + let source = source.unwrap_or(validator); + tracing::debug!("Source {source} --> Validator {validator}"); + + let staking_token = staking_token_address(storage); + token::transfer(storage, &staking_token, source, &ADDRESS, amount)?; + + let params = read_pos_params(storage)?; + let offset = offset_opt.unwrap_or(params.pipeline_len); + let offset_epoch = current_epoch + offset; + + // Check that the validator is actually a validator + let validator_state_handle = validator_state_handle(validator); + let state = validator_state_handle.get(storage, offset_epoch, ¶ms)?; + if state.is_none() { + return Err(BondError::NotAValidator(validator.clone()).into()); + } + + let bond_handle = bond_handle(source, validator); + let total_bonded_handle = total_bonded_handle(validator); + + if tracing::level_enabled!(tracing::Level::DEBUG) { + let bonds = find_bonds(storage, source, validator)?; + tracing::debug!("\nBonds before incrementing: {bonds:#?}"); + } + + // Initialize or update the bond at the pipeline offset + bond_handle.add(storage, amount, current_epoch, offset)?; + total_bonded_handle.add(storage, amount, current_epoch, offset)?; + + if tracing::level_enabled!(tracing::Level::DEBUG) { + let bonds = find_bonds(storage, source, validator)?; + tracing::debug!("\nBonds after incrementing: {bonds:#?}"); + } + + // Update the validator set + // Allow bonding even if the validator is jailed. However, if jailed, there + // must be no changes to the validator set. Check at the pipeline epoch. + let is_jailed_or_inactive_at_pipeline = matches!( + validator_state_handle.get(storage, offset_epoch, ¶ms)?, + Some(ValidatorState::Jailed) | Some(ValidatorState::Inactive) + ); + if !is_jailed_or_inactive_at_pipeline { + update_validator_set( + storage, + ¶ms, + validator, + amount.change(), + current_epoch, + offset_opt, + )?; + } + + // Update the validator and total deltas + update_validator_deltas( + storage, + ¶ms, + validator, + amount.change(), + current_epoch, + offset_opt, + )?; + + update_total_deltas( + storage, + ¶ms, + amount.change(), + current_epoch, + offset_opt, + )?; + + Ok(()) } -/// Read PoS validator's max commission rate change. -pub fn read_validator_max_commission_rate_change( +/// Compute total validator stake for the current epoch +fn compute_total_consensus_stake( storage: &S, - validator: &Address, -) -> storage_api::Result> + epoch: Epoch, +) -> storage_api::Result where S: StorageRead, { - let key = validator_max_commission_rate_change_key(validator); - storage.read(&key) + consensus_validator_set_handle() + .at(&epoch) + .iter(storage)? + .fold(Ok(token::Amount::zero()), |acc, entry| { + let acc = acc?; + let ( + NestedSubKey::Data { + key: amount, + nested_sub_key: _, + }, + _validator, + ) = entry?; + Ok(acc.checked_add(amount).expect( + "Total consensus stake computation should not overflow.", + )) + }) } -/// Write PoS validator's max commission rate change. -pub fn write_validator_max_commission_rate_change( +/// Compute and then store the total consensus stake +pub fn compute_and_store_total_consensus_stake( storage: &mut S, - validator: &Address, - change: Dec, + epoch: Epoch, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, { - let key = validator_max_commission_rate_change_key(validator); - storage.write(&key, change) -} + let total = compute_total_consensus_stake(storage, epoch)?; + tracing::debug!( + "Total consensus stake for epoch {}: {}", + epoch, + total.to_string_native() + ); + total_consensus_stake_handle().set(storage, total, epoch, 0) +} -/// Read the most recent slash epoch for the given epoch -pub fn read_validator_last_slash_epoch( - storage: &S, - validator: &Address, -) -> storage_api::Result> -where - S: StorageRead, -{ - let key = validator_last_slash_key(validator); - storage.read(&key) +/// Used below in `fn unbond_tokens` to update the bond and unbond amounts +#[derive(Eq, Hash, PartialEq)] +struct BondAndUnbondUpdates { + bond_start: Epoch, + new_bond_value: token::Change, + unbond_value: token::Change, } -/// Write the most recent slash epoch for the given epoch -pub fn write_validator_last_slash_epoch( +/// Unbond tokens that are bonded between a validator and a source (self or +/// delegator). +/// +/// This fn is also called during redelegation for a source validator, in +/// which case the `is_redelegation` param must be true. +pub fn unbond_tokens( storage: &mut S, + source: Option<&Address>, validator: &Address, - epoch: Epoch, -) -> storage_api::Result<()> + amount: token::Amount, + current_epoch: Epoch, + is_redelegation: bool, +) -> storage_api::Result where S: StorageRead + StorageWrite, { - let key = validator_last_slash_key(validator); - storage.write(&key, epoch) -} + if amount.is_zero() { + return Ok(ResultSlashing::default()); + } -/// Read last block proposer address. -pub fn read_last_block_proposer_address( - storage: &S, -) -> storage_api::Result> -where - S: StorageRead, -{ - let key = last_block_proposer_key(); - storage.read(&key) -} + let params = read_pos_params(storage)?; + let pipeline_epoch = current_epoch + params.pipeline_len; + let withdrawable_epoch = current_epoch + params.withdrawable_epoch_offset(); + tracing::debug!( + "Unbonding token amount {} at epoch {}, withdrawable at epoch {}", + amount.to_string_native(), + current_epoch, + withdrawable_epoch + ); -/// Write last block proposer address. -pub fn write_last_block_proposer_address( - storage: &mut S, - address: Address, -) -> storage_api::Result<()> -where - S: StorageRead + StorageWrite, -{ - let key = last_block_proposer_key(); - storage.write(&key, address) -} + // Make sure source is not some other validator + if let Some(source) = source { + if source != validator && is_validator(storage, source)? { + return Err( + BondError::SourceMustNotBeAValidator(source.clone()).into() + ); + } + } + // Make sure the target is actually a validator + if !is_validator(storage, validator)? { + return Err(BondError::NotAValidator(validator.clone()).into()); + } + // Make sure the validator is not currently frozen + if is_validator_frozen(storage, validator, current_epoch, ¶ms)? { + return Err(UnbondError::ValidatorIsFrozen(validator.clone()).into()); + } -/// Read PoS validator's delta value. -pub fn read_validator_deltas_value( - storage: &S, - validator: &Address, - epoch: &namada_core::types::storage::Epoch, -) -> storage_api::Result> -where - S: StorageRead, -{ - let handle = validator_deltas_handle(validator); - handle.get_delta_val(storage, *epoch) -} + let source = source.unwrap_or(validator); + let bonds_handle = bond_handle(source, validator); -/// Read PoS validator's stake (sum of deltas). -/// For non-validators and validators with `0` stake, this returns the default - -/// `token::Amount::zero()`. -pub fn read_validator_stake( - storage: &S, - params: &PosParams, - validator: &Address, - epoch: namada_core::types::storage::Epoch, -) -> storage_api::Result -where - S: StorageRead, -{ - let handle = validator_deltas_handle(validator); - let amount = handle - .get_sum(storage, epoch, params)? - .map(|change| { - debug_assert!(change.non_negative()); - token::Amount::from_change(change) - }) + // Make sure there are enough tokens left in the bond at the pipeline offset + let remaining_at_pipeline = bonds_handle + .get_sum(storage, pipeline_epoch, ¶ms)? .unwrap_or_default(); - Ok(amount) -} + if amount > remaining_at_pipeline { + return Err(UnbondError::UnbondAmountGreaterThanBond( + amount.to_string_native(), + remaining_at_pipeline.to_string_native(), + ) + .into()); + } -/// Add or remove PoS validator's stake delta value -pub fn update_validator_deltas( - storage: &mut S, - params: &OwnedPosParams, - validator: &Address, - delta: token::Change, - current_epoch: namada_core::types::storage::Epoch, - offset_opt: Option, -) -> storage_api::Result<()> -where - S: StorageRead + StorageWrite, -{ - let handle = validator_deltas_handle(validator); - let offset = offset_opt.unwrap_or(params.pipeline_len); - let val = handle - .get_delta_val(storage, current_epoch + offset)? - .unwrap_or_default(); - handle.set( - storage, - val.checked_add(&delta) - .expect("Validator deltas updated amount should not overflow"), - current_epoch, - offset, - ) -} + if tracing::level_enabled!(tracing::Level::DEBUG) { + let bonds = find_bonds(storage, source, validator)?; + tracing::debug!("\nBonds before decrementing: {bonds:#?}"); + } -/// Read PoS total stake (sum of deltas). -pub fn read_total_stake( - storage: &S, - params: &PosParams, - epoch: namada_core::types::storage::Epoch, -) -> storage_api::Result -where - S: StorageRead, -{ - let handle = total_deltas_handle(); - let amnt = handle - .get_sum(storage, epoch, params)? - .map(|change| { - debug_assert!(change.non_negative()); - token::Amount::from_change(change) - }) - .unwrap_or_default(); - Ok(amnt) -} + let unbonds = unbond_handle(source, validator); -/// Read all addresses from consensus validator set. -pub fn read_consensus_validator_set_addresses( - storage: &S, - epoch: namada_core::types::storage::Epoch, -) -> storage_api::Result> -where - S: StorageRead, -{ - consensus_validator_set_handle() - .at(&epoch) - .iter(storage)? - .map(|res| res.map(|(_sub_key, address)| address)) - .collect() -} + let redelegated_bonds = + delegator_redelegated_bonds_handle(source).at(validator); -/// Read all addresses from below-capacity validator set. -pub fn read_below_capacity_validator_set_addresses( - storage: &S, - epoch: namada_core::types::storage::Epoch, -) -> storage_api::Result> -where - S: StorageRead, -{ - below_capacity_validator_set_handle() - .at(&epoch) - .iter(storage)? - .map(|res| res.map(|(_sub_key, address)| address)) - .collect() -} + #[cfg(debug_assertions)] + let redel_bonds_pre = redelegated_bonds.collect_map(storage)?; -/// Read all addresses from the below-threshold set -pub fn read_below_threshold_validator_set_addresses( - storage: &S, - epoch: namada_core::types::storage::Epoch, -) -> storage_api::Result> -where - S: StorageRead, -{ - let params = read_pos_params(storage)?; - Ok(validator_addresses_handle() - .at(&epoch) - .iter(storage)? - .map(Result::unwrap) - .filter(|address| { - matches!( - validator_state_handle(address).get(storage, epoch, ¶ms), - Ok(Some(ValidatorState::BelowThreshold)) - ) - }) - .collect()) -} + // `resultUnbonding` + // Find the bonds to fully unbond (remove) and one to partially unbond, if + // necessary + let bonds_to_unbond = find_bonds_to_remove( + storage, + &bonds_handle.get_data_handler(), + amount, + )?; -/// Read all addresses from consensus validator set with their stake. -pub fn read_consensus_validator_set_addresses_with_stake( - storage: &S, - epoch: namada_core::types::storage::Epoch, -) -> storage_api::Result> -where - S: StorageRead, -{ - consensus_validator_set_handle() - .at(&epoch) - .iter(storage)? - .map(|res| { - res.map( - |( - NestedSubKey::Data { - key: bonded_stake, - nested_sub_key: _, - }, - address, - )| { - WeightedValidator { - address, - bonded_stake, - } - }, - ) - }) - .collect() -} + // `modifiedRedelegation` + // A bond may have both redelegated and non-redelegated tokens in it. If + // this is the case, compute the modified state of the redelegation. + let modified_redelegation = match bonds_to_unbond.new_entry { + Some((bond_epoch, new_bond_amount)) => { + if redelegated_bonds.contains(storage, &bond_epoch)? { + let cur_bond_amount = bonds_handle + .get_delta_val(storage, bond_epoch)? + .unwrap_or_default(); + compute_modified_redelegation( + storage, + &redelegated_bonds.at(&bond_epoch), + bond_epoch, + cur_bond_amount - new_bond_amount, + )? + } else { + ModifiedRedelegation::default() + } + } + None => ModifiedRedelegation::default(), + }; -/// Count the number of consensus validators -pub fn get_num_consensus_validators( - storage: &S, - epoch: namada_core::types::storage::Epoch, -) -> storage_api::Result -where - S: StorageRead, -{ - Ok(consensus_validator_set_handle() - .at(&epoch) - .iter(storage)? - .count() as u64) -} + // Compute the new unbonds eagerly + // `keysUnbonds` + // Get a set of epochs from which we're unbonding (fully and partially). + let bond_epochs_to_unbond = + if let Some((start_epoch, _)) = bonds_to_unbond.new_entry { + let mut to_remove = bonds_to_unbond.epochs.clone(); + to_remove.insert(start_epoch); + to_remove + } else { + bonds_to_unbond.epochs.clone() + }; -/// Read all addresses from below-capacity validator set with their stake. -pub fn read_below_capacity_validator_set_addresses_with_stake( - storage: &S, - epoch: namada_core::types::storage::Epoch, -) -> storage_api::Result> -where - S: StorageRead, -{ - below_capacity_validator_set_handle() - .at(&epoch) - .iter(storage)? - .map(|res| { - res.map( - |( - NestedSubKey::Data { - key: ReverseOrdTokenAmount(bonded_stake), - nested_sub_key: _, - }, - address, - )| { - WeightedValidator { - address, - bonded_stake, - } - }, - ) + // `newUnbonds` + // For each epoch we're unbonding, find the amount that's being unbonded. + // For full unbonds, this is the current bond value. For partial unbonds + // it is a difference between the current and new bond amount. + let new_unbonds_map = bond_epochs_to_unbond + .into_iter() + .map(|epoch| { + let cur_bond_value = bonds_handle + .get_delta_val(storage, epoch) + .unwrap() + .unwrap_or_default(); + let value = if let Some((start_epoch, new_bond_amount)) = + bonds_to_unbond.new_entry + { + if start_epoch == epoch { + cur_bond_value - new_bond_amount + } else { + cur_bond_value + } + } else { + cur_bond_value + }; + (epoch, value) }) - .collect() -} + .collect::>(); -/// Read all validator addresses. -pub fn read_all_validator_addresses( - storage: &S, - epoch: namada_core::types::storage::Epoch, -) -> storage_api::Result> -where - S: StorageRead, -{ - validator_addresses_handle() - .at(&epoch) - .iter(storage)? - .collect() -} - -/// Update PoS total deltas. -/// Note: for EpochedDelta, write the value to change storage by -pub fn update_total_deltas( - storage: &mut S, - params: &OwnedPosParams, - delta: token::Change, - current_epoch: namada_core::types::storage::Epoch, - offset_opt: Option, -) -> storage_api::Result<()> -where - S: StorageRead + StorageWrite, -{ - let handle = total_deltas_handle(); - let offset = offset_opt.unwrap_or(params.pipeline_len); - let val = handle - .get_delta_val(storage, current_epoch + offset)? - .unwrap_or_default(); - handle.set( - storage, - val.checked_add(&delta) - .expect("Total deltas updated amount should not overflow"), - current_epoch, - offset, - ) -} - -/// Check if the provided address is a validator address -pub fn is_validator( - storage: &S, - address: &Address, -) -> storage_api::Result -where - S: StorageRead, -{ - // TODO: should this check be made different? I suppose it does work but - // feels weird... - let rate = read_validator_max_commission_rate_change(storage, address)?; - Ok(rate.is_some()) -} + // `updatedBonded` + // Remove bonds for all the full unbonds. + for epoch in &bonds_to_unbond.epochs { + bonds_handle.get_data_handler().remove(storage, epoch)?; + } + // Replace bond amount for partial unbond, if any. + if let Some((bond_epoch, new_bond_amount)) = bonds_to_unbond.new_entry { + bonds_handle.set(storage, new_bond_amount, bond_epoch, 0)?; + } -/// Check if the provided address is a delegator address, optionally at a -/// particular epoch -pub fn is_delegator( - storage: &S, - address: &Address, - epoch: Option, -) -> storage_api::Result -where - S: StorageRead, -{ - let prefix = bonds_for_source_prefix(address); - match epoch { - Some(epoch) => { - let iter = storage_api::iter_prefix_bytes(storage, &prefix)?; - for res in iter { - let (key, _) = res?; - if let Some((bond_id, bond_epoch)) = is_bond_key(&key) { - if bond_id.source != bond_id.validator - && bond_epoch <= epoch - { - return Ok(true); - } - } - } - Ok(false) - } - None => { - let iter = storage_api::iter_prefix_bytes(storage, &prefix)?; - for res in iter { - let (key, _) = res?; - if let Some((bond_id, _epoch)) = is_bond_key(&key) { - if bond_id.source != bond_id.validator { - return Ok(true); - } - } - } - Ok(false) + // `updatedUnbonded` + // Update the unbonds in storage using the eager map computed above + if !is_redelegation { + for (start_epoch, &unbond_amount) in new_unbonds_map.iter() { + unbonds.at(start_epoch).update( + storage, + withdrawable_epoch, + |cur_val| cur_val.unwrap_or_default() + unbond_amount, + )?; } } -} -/// Self-bond tokens to a validator when `source` is `None` or equal to -/// the `validator` address, or delegate tokens from the `source` to the -/// `validator`. -pub fn bond_tokens( - storage: &mut S, - source: Option<&Address>, - validator: &Address, - amount: token::Amount, - current_epoch: Epoch, - offset_opt: Option, -) -> storage_api::Result<()> -where - S: StorageRead + StorageWrite, -{ - tracing::debug!( - "Bonding token amount {} at epoch {current_epoch}", - amount.to_string_native() - ); - if amount.is_zero() { - return Ok(()); - } + // `newRedelegatedUnbonds` + // This is what the delegator's redelegated unbonds would look like if this + // was the only unbond in the PoS system. We need to add these redelegated + // unbonds to the existing redelegated unbonds + let new_redelegated_unbonds = compute_new_redelegated_unbonds( + storage, + &redelegated_bonds, + &bonds_to_unbond.epochs, + &modified_redelegation, + )?; - // Transfer the bonded tokens from the source to PoS - if let Some(source) = source { - if source != validator && is_validator(storage, source)? { - return Err( - BondError::SourceMustNotBeAValidator(source.clone()).into() - ); + // `updatedRedelegatedBonded` + // NOTE: for now put this here after redelegated unbonds calc bc that one + // uses the pre-modified redelegated bonds from storage! + // First remove redelegation entries in epochs with full unbonds. + for epoch_to_remove in &bonds_to_unbond.epochs { + redelegated_bonds.remove_all(storage, epoch_to_remove)?; + } + if let Some(epoch) = modified_redelegation.epoch { + tracing::debug!("\nIs modified redelegation"); + if modified_redelegation.validators_to_remove.is_empty() { + redelegated_bonds.remove_all(storage, &epoch)?; + } else { + // Then update the redelegated bonds at this epoch + let rbonds = redelegated_bonds.at(&epoch); + update_redelegated_bonds(storage, &rbonds, &modified_redelegation)?; } } - let source = source.unwrap_or(validator); - tracing::debug!("Source {source} --> Validator {validator}"); - let staking_token = staking_token_address(storage); - token::transfer(storage, &staking_token, source, &ADDRESS, amount)?; + if !is_redelegation { + // `val updatedRedelegatedUnbonded` with updates applied below + // Delegator's redelegated unbonds to this validator. + let delegator_redelegated_unbonded = + delegator_redelegated_unbonds_handle(source).at(validator); - let params = read_pos_params(storage)?; - let offset = offset_opt.unwrap_or(params.pipeline_len); - let offset_epoch = current_epoch + offset; + // Quint `def updateRedelegatedUnbonded` with `val + // updatedRedelegatedUnbonded` together with last statement + // in `updatedDelegator.with("redelegatedUnbonded", ...` updated + // directly in storage + for (start, unbonds) in &new_redelegated_unbonds { + let this_redelegated_unbonded = delegator_redelegated_unbonded + .at(start) + .at(&withdrawable_epoch); - // Check that the validator is actually a validator - let validator_state_handle = validator_state_handle(validator); - let state = validator_state_handle.get(storage, offset_epoch, ¶ms)?; - if state.is_none() { - return Err(BondError::NotAValidator(validator.clone()).into()); + // Update the delegator's redelegated unbonds with the change + for (src_validator, redelegated_unbonds) in unbonds { + let redelegated_unbonded = + this_redelegated_unbonded.at(src_validator); + for (&redelegation_epoch, &change) in redelegated_unbonds { + redelegated_unbonded.update( + storage, + redelegation_epoch, + |current| current.unwrap_or_default() + change, + )?; + } + } + } } + // all `val updatedDelegator` changes are applied at this point - let bond_handle = bond_handle(source, validator); - let total_bonded_handle = total_bonded_handle(validator); - - if tracing::level_enabled!(tracing::Level::DEBUG) { - let bonds = find_bonds(storage, source, validator)?; - tracing::debug!("\nBonds before incrementing: {bonds:#?}"); + // `val updatedTotalBonded` and `val updatedTotalUnbonded` with updates + // Update the validator's total bonded and unbonded amounts + let total_bonded = total_bonded_handle(validator).get_data_handler(); + let total_unbonded = total_unbonded_handle(validator).at(&pipeline_epoch); + for (&start_epoch, &amount) in &new_unbonds_map { + total_bonded.update(storage, start_epoch, |current| { + current.unwrap_or_default() - amount + })?; + total_unbonded.update(storage, start_epoch, |current| { + current.unwrap_or_default() + amount + })?; } - // Initialize or update the bond at the pipeline offset - bond_handle.add(storage, amount, current_epoch, offset)?; - total_bonded_handle.add(storage, amount, current_epoch, offset)?; - - if tracing::level_enabled!(tracing::Level::DEBUG) { - let bonds = find_bonds(storage, source, validator)?; - tracing::debug!("\nBonds after incrementing: {bonds:#?}"); - } + let total_redelegated_bonded = + validator_total_redelegated_bonded_handle(validator); + let total_redelegated_unbonded = + validator_total_redelegated_unbonded_handle(validator); + for (redelegation_start_epoch, unbonds) in &new_redelegated_unbonds { + for (src_validator, changes) in unbonds { + for (bond_start_epoch, change) in changes { + // total redelegated bonded + let bonded_sub_map = total_redelegated_bonded + .at(redelegation_start_epoch) + .at(src_validator); + bonded_sub_map.update( + storage, + *bond_start_epoch, + |current| current.unwrap_or_default() - *change, + )?; - // Update the validator set - // Allow bonding even if the validator is jailed. However, if jailed, there - // must be no changes to the validator set. Check at the pipeline epoch. - let is_jailed_or_inactive_at_pipeline = matches!( - validator_state_handle.get(storage, offset_epoch, ¶ms)?, - Some(ValidatorState::Jailed) | Some(ValidatorState::Inactive) - ); - if !is_jailed_or_inactive_at_pipeline { - update_validator_set( - storage, - ¶ms, - validator, - amount.change(), - current_epoch, - offset_opt, - )?; + // total redelegated unbonded + let unbonded_sub_map = total_redelegated_unbonded + .at(&pipeline_epoch) + .at(redelegation_start_epoch) + .at(src_validator); + unbonded_sub_map.update( + storage, + *bond_start_epoch, + |current| current.unwrap_or_default() + *change, + )?; + } + } } - // Update the validator and total deltas - update_validator_deltas( + let slashes = find_validator_slashes(storage, validator)?; + // `val resultSlashing` + let result_slashing = compute_amount_after_slashing_unbond( storage, ¶ms, - validator, - amount.change(), - current_epoch, - offset_opt, - )?; - - update_total_deltas( - storage, - ¶ms, - amount.change(), - current_epoch, - offset_opt, + &new_unbonds_map, + &new_redelegated_unbonds, + slashes, )?; - - Ok(()) -} - -/// Insert the new validator into the right validator set (depending on its -/// stake) -fn insert_validator_into_validator_set( - storage: &mut S, - params: &PosParams, - address: &Address, - stake: token::Amount, - current_epoch: Epoch, - offset: u64, -) -> storage_api::Result<()> -where - S: StorageRead + StorageWrite, -{ - let target_epoch = current_epoch + offset; - let consensus_set = consensus_validator_set_handle().at(&target_epoch); - let below_cap_set = below_capacity_validator_set_handle().at(&target_epoch); - - let num_consensus_validators = - get_num_consensus_validators(storage, target_epoch)?; - - if stake < params.validator_stake_threshold { - validator_state_handle(address).set( - storage, - ValidatorState::BelowThreshold, - current_epoch, - offset, - )?; - } else if num_consensus_validators < params.max_validator_slots { - insert_validator_into_set( - &consensus_set.at(&stake), - storage, - &target_epoch, - address, - )?; - validator_state_handle(address).set( - storage, - ValidatorState::Consensus, - current_epoch, - offset, - )?; - } else { - // Check to see if the current genesis validator should replace one - // already in the consensus set - let min_consensus_amount = - get_min_consensus_validator_amount(&consensus_set, storage)?; - if stake > min_consensus_amount { - // Swap this genesis validator in and demote the last min consensus - // validator - let min_consensus_handle = consensus_set.at(&min_consensus_amount); - // Remove last min consensus validator - let last_min_consensus_position = - find_last_position(&min_consensus_handle, storage)?.expect( - "There must be always be at least 1 consensus validator", - ); - let removed = min_consensus_handle - .remove(storage, &last_min_consensus_position)? - .expect( - "There must be always be at least 1 consensus validator", - ); - // Insert last min consensus validator into the below-capacity set - insert_validator_into_set( - &below_cap_set.at(&min_consensus_amount.into()), - storage, - &target_epoch, - &removed, - )?; - validator_state_handle(&removed).set( - storage, - ValidatorState::BelowCapacity, - current_epoch, - offset, - )?; - // Insert the current genesis validator into the consensus set - insert_validator_into_set( - &consensus_set.at(&stake), - storage, - &target_epoch, - address, - )?; - // Update and set the validator states - validator_state_handle(address).set( - storage, - ValidatorState::Consensus, - current_epoch, - offset, - )?; - } else { - // Insert the current genesis validator into the below-capacity set - insert_validator_into_set( - &below_cap_set.at(&stake.into()), - storage, - &target_epoch, - address, - )?; - validator_state_handle(address).set( - storage, - ValidatorState::BelowCapacity, - current_epoch, - offset, - )?; - } - } - Ok(()) -} - -/// Update validator set at the pipeline epoch when a validator receives a new -/// bond and when its bond is unbonded (self-bond or delegation). -fn update_validator_set( - storage: &mut S, - params: &PosParams, - validator: &Address, - token_change: token::Change, - current_epoch: Epoch, - offset: Option, -) -> storage_api::Result<()> -where - S: StorageRead + StorageWrite, -{ - if token_change.is_zero() { - return Ok(()); - } - let offset = offset.unwrap_or(params.pipeline_len); - let epoch = current_epoch + offset; - tracing::debug!( - "Update epoch for validator set: {epoch}, validator: {validator}" + #[cfg(debug_assertions)] + let redel_bonds_post = redelegated_bonds.collect_map(storage)?; + debug_assert!( + result_slashing.sum <= amount, + "Amount after slashing ({}) must be <= requested amount to unbond \ + ({}).", + result_slashing.sum.to_string_native(), + amount.to_string_native(), ); - let consensus_validator_set = consensus_validator_set_handle(); - let below_capacity_validator_set = below_capacity_validator_set_handle(); - - // Validator sets at the pipeline offset - let consensus_val_handle = consensus_validator_set.at(&epoch); - let below_capacity_val_handle = below_capacity_validator_set.at(&epoch); - - let tokens_pre = read_validator_stake(storage, params, validator, epoch)?; - - let tokens_post = tokens_pre - .change() - .checked_add(&token_change) - .expect("Post-validator set update token amount has overflowed"); - debug_assert!(tokens_post.non_negative()); - let tokens_post = token::Amount::from_change(tokens_post); - - // If token amounts both before and after the action are below the threshold - // stake, do nothing - if tokens_pre < params.validator_stake_threshold - && tokens_post < params.validator_stake_threshold - { - return Ok(()); - } - - // The position is only set when the validator is in consensus or - // below_capacity set (not in below_threshold set) - let position = - read_validator_set_position(storage, validator, epoch, params)?; - if let Some(position) = position { - let consensus_vals_pre = consensus_val_handle.at(&tokens_pre); - - let in_consensus = if consensus_vals_pre.contains(storage, &position)? { - let val_address = consensus_vals_pre.get(storage, &position)?; - debug_assert!(val_address.is_some()); - val_address == Some(validator.clone()) - } else { - false - }; - - if in_consensus { - // It's initially consensus - tracing::debug!("Target validator is consensus"); - - // First remove the consensus validator - consensus_vals_pre.remove(storage, &position)?; - - let max_below_capacity_validator_amount = - get_max_below_capacity_validator_amount( - &below_capacity_val_handle, - storage, - )? - .unwrap_or_default(); - - if tokens_post < params.validator_stake_threshold { - tracing::debug!( - "Demoting this validator to the below-threshold set" - ); - // Set the validator state as below-threshold - validator_state_handle(validator).set( - storage, - ValidatorState::BelowThreshold, - current_epoch, - offset, - )?; - - // Remove the validator's position from storage - validator_set_positions_handle() - .at(&epoch) - .remove(storage, validator)?; - - // Promote the next below-cap validator if there is one - if let Some(max_bc_amount) = - get_max_below_capacity_validator_amount( - &below_capacity_val_handle, - storage, - )? - { - // Remove the max below-capacity validator first - let below_capacity_vals_max = - below_capacity_val_handle.at(&max_bc_amount.into()); - let lowest_position = - find_first_position(&below_capacity_vals_max, storage)? - .unwrap(); - let removed_max_below_capacity = below_capacity_vals_max - .remove(storage, &lowest_position)? - .expect("Must have been removed"); - - // Insert the previous max below-capacity validator into the - // consensus set - insert_validator_into_set( - &consensus_val_handle.at(&max_bc_amount), - storage, - &epoch, - &removed_max_below_capacity, - )?; - validator_state_handle(&removed_max_below_capacity).set( - storage, - ValidatorState::Consensus, - current_epoch, - offset, - )?; - } - } else if tokens_post < max_below_capacity_validator_amount { - tracing::debug!( - "Demoting this validator to the below-capacity set and \ - promoting another to the consensus set" - ); - // Place the validator into the below-capacity set and promote - // the lowest position max below-capacity - // validator. - - // Remove the max below-capacity validator first - let below_capacity_vals_max = below_capacity_val_handle - .at(&max_below_capacity_validator_amount.into()); - let lowest_position = - find_first_position(&below_capacity_vals_max, storage)? - .unwrap(); - let removed_max_below_capacity = below_capacity_vals_max - .remove(storage, &lowest_position)? - .expect("Must have been removed"); - - // Insert the previous max below-capacity validator into the - // consensus set - insert_validator_into_set( - &consensus_val_handle - .at(&max_below_capacity_validator_amount), - storage, - &epoch, - &removed_max_below_capacity, - )?; - validator_state_handle(&removed_max_below_capacity).set( - storage, - ValidatorState::Consensus, - current_epoch, - offset, - )?; - - // Insert the current validator into the below-capacity set - insert_validator_into_set( - &below_capacity_val_handle.at(&tokens_post.into()), - storage, - &epoch, - validator, - )?; - validator_state_handle(validator).set( - storage, - ValidatorState::BelowCapacity, - current_epoch, - offset, - )?; - } else { - tracing::debug!("Validator remains in consensus set"); - // The current validator should remain in the consensus set - - // place it into a new position - insert_validator_into_set( - &consensus_val_handle.at(&tokens_post), - storage, - &epoch, - validator, - )?; - } - } else { - // It's initially below-capacity - tracing::debug!("Target validator is below-capacity"); - - let below_capacity_vals_pre = - below_capacity_val_handle.at(&tokens_pre.into()); - let removed = below_capacity_vals_pre.remove(storage, &position)?; - debug_assert!(removed.is_some()); - debug_assert_eq!(&removed.unwrap(), validator); - - let min_consensus_validator_amount = - get_min_consensus_validator_amount( - &consensus_val_handle, - storage, - )?; - - if tokens_post > min_consensus_validator_amount { - // Place the validator into the consensus set and demote the - // last position min consensus validator to the - // below-capacity set - tracing::debug!( - "Inserting validator into the consensus set and demoting \ - a consensus validator to the below-capacity set" - ); - - insert_into_consensus_and_demote_to_below_cap( - storage, - validator, - tokens_post, - min_consensus_validator_amount, - current_epoch, - offset, - &consensus_val_handle, - &below_capacity_val_handle, - )?; - } else if tokens_post >= params.validator_stake_threshold { - tracing::debug!("Validator remains in below-capacity set"); - // The current validator should remain in the below-capacity set - insert_validator_into_set( - &below_capacity_val_handle.at(&tokens_post.into()), - storage, - &epoch, - validator, - )?; - validator_state_handle(validator).set( - storage, - ValidatorState::BelowCapacity, - current_epoch, - offset, - )?; - } else { - // The current validator is demoted to the below-threshold set - tracing::debug!( - "Demoting this validator to the below-threshold set" - ); - - validator_state_handle(validator).set( - storage, - ValidatorState::BelowThreshold, - current_epoch, - offset, - )?; - - // Remove the validator's position from storage - validator_set_positions_handle() - .at(&epoch) - .remove(storage, validator)?; - } - } - } else { - // At non-zero offset (0 is genesis only) - if offset > 0 { - // If there is no position at pipeline offset, then the validator - // must be in the below-threshold set - debug_assert!(tokens_pre < params.validator_stake_threshold); - } - tracing::debug!("Target validator is below-threshold"); - - // Move the validator into the appropriate set - let num_consensus_validators = - get_num_consensus_validators(storage, epoch)?; - if num_consensus_validators < params.max_validator_slots { - // Just insert into the consensus set - tracing::debug!("Inserting validator into the consensus set"); - - insert_validator_into_set( - &consensus_val_handle.at(&tokens_post), - storage, - &epoch, - validator, - )?; - validator_state_handle(validator).set( - storage, - ValidatorState::Consensus, - current_epoch, - offset, - )?; - } else { - let min_consensus_validator_amount = - get_min_consensus_validator_amount( - &consensus_val_handle, - storage, - )?; - if tokens_post > min_consensus_validator_amount { - // Insert this validator into consensus and demote one into the - // below-capacity - tracing::debug!( - "Inserting validator into the consensus set and demoting \ - a consensus validator to the below-capacity set" - ); - - insert_into_consensus_and_demote_to_below_cap( - storage, - validator, - tokens_post, - min_consensus_validator_amount, - current_epoch, - offset, - &consensus_val_handle, - &below_capacity_val_handle, - )?; - } else { - // Insert this validator into below-capacity - tracing::debug!( - "Inserting validator into the below-capacity set" - ); - - insert_validator_into_set( - &below_capacity_val_handle.at(&tokens_post.into()), - storage, - &epoch, - validator, - )?; - validator_state_handle(validator).set( - storage, - ValidatorState::BelowCapacity, - current_epoch, - offset, - )?; - } - } - } - - Ok(()) -} - -#[allow(clippy::too_many_arguments)] -fn insert_into_consensus_and_demote_to_below_cap( - storage: &mut S, - validator: &Address, - tokens_post: token::Amount, - min_consensus_amount: token::Amount, - current_epoch: Epoch, - offset: u64, - consensus_set: &ConsensusValidatorSet, - below_capacity_set: &BelowCapacityValidatorSet, -) -> storage_api::Result<()> -where - S: StorageRead + StorageWrite, -{ - // First, remove the last position min consensus validator - let consensus_vals_min = consensus_set.at(&min_consensus_amount); - let last_position_of_min_consensus_vals = - find_last_position(&consensus_vals_min, storage)? - .expect("There must be always be at least 1 consensus validator"); - let removed_min_consensus = consensus_vals_min - .remove(storage, &last_position_of_min_consensus_vals)? - .expect("There must be always be at least 1 consensus validator"); - - let offset_epoch = current_epoch + offset; - - // Insert the min consensus validator into the below-capacity - // set - insert_validator_into_set( - &below_capacity_set.at(&min_consensus_amount.into()), - storage, - &offset_epoch, - &removed_min_consensus, - )?; - validator_state_handle(&removed_min_consensus).set( - storage, - ValidatorState::BelowCapacity, - current_epoch, - offset, - )?; - - // Insert the current validator into the consensus set - insert_validator_into_set( - &consensus_set.at(&tokens_post), - storage, - &offset_epoch, - validator, - )?; - validator_state_handle(validator).set( - storage, - ValidatorState::Consensus, - current_epoch, - offset, - )?; - Ok(()) -} - -/// Copy the consensus and below-capacity validator sets and positions into a -/// future epoch. Also copies the epoched set of all known validators in the -/// network. -pub fn copy_validator_sets_and_positions( - storage: &mut S, - params: &PosParams, - current_epoch: Epoch, - target_epoch: Epoch, -) -> storage_api::Result<()> -where - S: StorageRead + StorageWrite, -{ - let prev_epoch = target_epoch.prev(); - - let consensus_validator_set = consensus_validator_set_handle(); - let below_capacity_validator_set = below_capacity_validator_set_handle(); - let (consensus, below_capacity) = ( - consensus_validator_set.at(&prev_epoch), - below_capacity_validator_set.at(&prev_epoch), + let change_after_slashing = -result_slashing.sum.change(); + // Update the validator set at the pipeline offset. Since unbonding from a + // jailed validator who is no longer frozen is allowed, only update the + // validator set if the validator is not jailed + let is_jailed_or_inactive_at_pipeline = matches!( + validator_state_handle(validator).get( + storage, + pipeline_epoch, + ¶ms + )?, + Some(ValidatorState::Jailed) | Some(ValidatorState::Inactive) ); - debug_assert!(!consensus.is_empty(storage)?); - - // Need to copy into memory here to avoid borrowing a ref - // simultaneously as immutable and mutable - let mut consensus_in_mem: HashMap<(token::Amount, Position), Address> = - HashMap::new(); - let mut below_cap_in_mem: HashMap< - (ReverseOrdTokenAmount, Position), - Address, - > = HashMap::new(); - - for val in consensus.iter(storage)? { - let ( - NestedSubKey::Data { - key: stake, - nested_sub_key: SubKey::Data(position), - }, - address, - ) = val?; - consensus_in_mem.insert((stake, position), address); - } - for val in below_capacity.iter(storage)? { - let ( - NestedSubKey::Data { - key: stake, - nested_sub_key: SubKey::Data(position), - }, - address, - ) = val?; - below_cap_in_mem.insert((stake, position), address); - } - - for ((val_stake, val_position), val_address) in consensus_in_mem.into_iter() - { - consensus_validator_set - .at(&target_epoch) - .at(&val_stake) - .insert(storage, val_position, val_address)?; - } - - for ((val_stake, val_position), val_address) in below_cap_in_mem.into_iter() - { - below_capacity_validator_set - .at(&target_epoch) - .at(&val_stake) - .insert(storage, val_position, val_address)?; - } - // Purge consensus and below-capacity validator sets - consensus_validator_set.update_data(storage, params, current_epoch)?; - below_capacity_validator_set.update_data(storage, params, current_epoch)?; - - // Copy validator positions - let mut positions = HashMap::::default(); - let validator_set_positions_handle = validator_set_positions_handle(); - let positions_handle = validator_set_positions_handle.at(&prev_epoch); - - for result in positions_handle.iter(storage)? { - let (validator, position) = result?; - positions.insert(validator, position); - } - - let new_positions_handle = validator_set_positions_handle.at(&target_epoch); - for (validator, position) in positions { - let prev = new_positions_handle.insert(storage, validator, position)?; - debug_assert!(prev.is_none()); - } - validator_set_positions_handle.set_last_update(storage, current_epoch)?; - - // Purge old epochs of validator positions - validator_set_positions_handle.update_data( - storage, - params, - current_epoch, - )?; - - // Copy set of all validator addresses - let mut all_validators = HashSet::
::default(); - let validator_addresses_handle = validator_addresses_handle(); - let all_validators_handle = validator_addresses_handle.at(&prev_epoch); - for result in all_validators_handle.iter(storage)? { - let validator = result?; - all_validators.insert(validator); - } - let new_all_validators_handle = - validator_addresses_handle.at(&target_epoch); - for validator in all_validators { - let was_in = new_all_validators_handle.insert(storage, validator)?; - debug_assert!(!was_in); - } - - // Purge old epochs of all validator addresses - validator_addresses_handle.update_data(storage, params, current_epoch)?; - - Ok(()) -} - -/// Compute total validator stake for the current epoch -fn compute_total_consensus_stake( - storage: &S, - epoch: Epoch, -) -> storage_api::Result -where - S: StorageRead, -{ - consensus_validator_set_handle() - .at(&epoch) - .iter(storage)? - .fold(Ok(token::Amount::zero()), |acc, entry| { - let acc = acc?; - let ( - NestedSubKey::Data { - key: amount, - nested_sub_key: _, - }, - _validator, - ) = entry?; - Ok(acc.checked_add(amount).expect( - "Total consensus stake computation should not overflow.", - )) - }) -} - -/// Compute and then store the total consensus stake -pub fn compute_and_store_total_consensus_stake( - storage: &mut S, - epoch: Epoch, -) -> storage_api::Result<()> -where - S: StorageRead + StorageWrite, -{ - let total = compute_total_consensus_stake(storage, epoch)?; - tracing::debug!( - "Total consensus stake for epoch {}: {}", - epoch, - total.to_string_native() - ); - total_consensus_stake_key_handle().set(storage, total, epoch, 0) -} - -/// Read the position of the validator in the subset of validators that have the -/// same bonded stake. This information is held in its own epoched structure in -/// addition to being inside the validator sets. -fn read_validator_set_position( - storage: &S, - validator: &Address, - epoch: Epoch, - _params: &PosParams, -) -> storage_api::Result> -where - S: StorageRead, -{ - let handle = validator_set_positions_handle(); - handle.get_data_handler().at(&epoch).get(storage, validator) -} - -/// Find the first (lowest) position in a validator set if it is not empty -fn find_first_position( - handle: &ValidatorPositionAddresses, - storage: &S, -) -> storage_api::Result> -where - S: StorageRead, -{ - let lowest_position = handle - .iter(storage)? - .next() - .transpose()? - .map(|(position, _addr)| position); - Ok(lowest_position) -} - -/// Find the last (greatest) position in a validator set if it is not empty -fn find_last_position( - handle: &ValidatorPositionAddresses, - storage: &S, -) -> storage_api::Result> -where - S: StorageRead, -{ - let position = handle - .iter(storage)? - .last() - .transpose()? - .map(|(position, _addr)| position); - Ok(position) -} - -/// Find next position in a validator set or 0 if empty -fn find_next_position( - handle: &ValidatorPositionAddresses, - storage: &S, -) -> storage_api::Result -where - S: StorageRead, -{ - let position_iter = handle.iter(storage)?; - let next = position_iter - .last() - .transpose()? - .map(|(position, _address)| position.next()) - .unwrap_or_default(); - Ok(next) -} - -fn get_min_consensus_validator_amount( - handle: &ConsensusValidatorSet, - storage: &S, -) -> storage_api::Result -where - S: StorageRead, -{ - Ok(handle - .iter(storage)? - .next() - .transpose()? - .map(|(subkey, _address)| match subkey { - NestedSubKey::Data { - key, - nested_sub_key: _, - } => key, - }) - .unwrap_or_default()) -} - -/// Returns `Ok(None)` when the below capacity set is empty. -fn get_max_below_capacity_validator_amount( - handle: &BelowCapacityValidatorSet, - storage: &S, -) -> storage_api::Result> -where - S: StorageRead, -{ - Ok(handle - .iter(storage)? - .next() - .transpose()? - .map(|(subkey, _address)| match subkey { - NestedSubKey::Data { - key, - nested_sub_key: _, - } => token::Amount::from(key), - })) -} - -fn insert_validator_into_set( - handle: &ValidatorPositionAddresses, - storage: &mut S, - epoch: &Epoch, - address: &Address, -) -> storage_api::Result<()> -where - S: StorageRead + StorageWrite, -{ - let next_position = find_next_position(handle, storage)?; - tracing::debug!( - "Inserting validator {} into position {:?} at epoch {}", - address.clone(), - next_position.clone(), - epoch.clone() - ); - handle.insert(storage, next_position, address.clone())?; - validator_set_positions_handle().at(epoch).insert( - storage, - address.clone(), - next_position, - )?; - Ok(()) -} - -/// Used below in `fn unbond_tokens` to update the bond and unbond amounts -#[derive(Eq, Hash, PartialEq)] -struct BondAndUnbondUpdates { - bond_start: Epoch, - new_bond_value: token::Change, - unbond_value: token::Change, -} - -/// Temp: In quint this is from `ResultUnbondTx` field `resultSlashing: {sum: -/// int, epochMap: Epoch -> int}` -#[derive(Debug, Default)] -pub struct ResultSlashing { - /// The token amount unbonded from the validator stake after accounting for - /// slashes - pub sum: token::Amount, - /// Map from bond start epoch to token amount after slashing - pub epoch_map: BTreeMap, -} - -/// Unbond tokens that are bonded between a validator and a source (self or -/// delegator). -/// -/// This fn is also called during redelegation for a source validator, in -/// which case the `is_redelegation` param must be true. -pub fn unbond_tokens( - storage: &mut S, - source: Option<&Address>, - validator: &Address, - amount: token::Amount, - current_epoch: Epoch, - is_redelegation: bool, -) -> storage_api::Result -where - S: StorageRead + StorageWrite, -{ - if amount.is_zero() { - return Ok(ResultSlashing::default()); - } - - let params = read_pos_params(storage)?; - let pipeline_epoch = current_epoch + params.pipeline_len; - let withdrawable_epoch = current_epoch + params.withdrawable_epoch_offset(); - tracing::debug!( - "Unbonding token amount {} at epoch {}, withdrawable at epoch {}", - amount.to_string_native(), - current_epoch, - withdrawable_epoch - ); - - // Make sure source is not some other validator - if let Some(source) = source { - if source != validator && is_validator(storage, source)? { - return Err( - BondError::SourceMustNotBeAValidator(source.clone()).into() - ); - } - } - // Make sure the target is actually a validator - if !is_validator(storage, validator)? { - return Err(BondError::NotAValidator(validator.clone()).into()); - } - // Make sure the validator is not currently frozen - if is_validator_frozen(storage, validator, current_epoch, ¶ms)? { - return Err(UnbondError::ValidatorIsFrozen(validator.clone()).into()); - } - - let source = source.unwrap_or(validator); - let bonds_handle = bond_handle(source, validator); - - // Make sure there are enough tokens left in the bond at the pipeline offset - let remaining_at_pipeline = bonds_handle - .get_sum(storage, pipeline_epoch, ¶ms)? - .unwrap_or_default(); - if amount > remaining_at_pipeline { - return Err(UnbondError::UnbondAmountGreaterThanBond( - amount.to_string_native(), - remaining_at_pipeline.to_string_native(), - ) - .into()); - } - - if tracing::level_enabled!(tracing::Level::DEBUG) { - let bonds = find_bonds(storage, source, validator)?; - tracing::debug!("\nBonds before decrementing: {bonds:#?}"); - } - - let unbonds = unbond_handle(source, validator); - - let redelegated_bonds = - delegator_redelegated_bonds_handle(source).at(validator); - - #[cfg(debug_assertions)] - let redel_bonds_pre = redelegated_bonds.collect_map(storage)?; - - // `resultUnbonding` - // Find the bonds to fully unbond (remove) and one to partially unbond, if - // necessary - let bonds_to_unbond = find_bonds_to_remove( - storage, - &bonds_handle.get_data_handler(), - amount, - )?; - - // `modifiedRedelegation` - // A bond may have both redelegated and non-redelegated tokens in it. If - // this is the case, compute the modified state of the redelegation. - let modified_redelegation = match bonds_to_unbond.new_entry { - Some((bond_epoch, new_bond_amount)) => { - if redelegated_bonds.contains(storage, &bond_epoch)? { - let cur_bond_amount = bonds_handle - .get_delta_val(storage, bond_epoch)? - .unwrap_or_default(); - compute_modified_redelegation( - storage, - &redelegated_bonds.at(&bond_epoch), - bond_epoch, - cur_bond_amount - new_bond_amount, - )? - } else { - ModifiedRedelegation::default() - } - } - None => ModifiedRedelegation::default(), - }; - - // Compute the new unbonds eagerly - // `keysUnbonds` - // Get a set of epochs from which we're unbonding (fully and partially). - let bond_epochs_to_unbond = - if let Some((start_epoch, _)) = bonds_to_unbond.new_entry { - let mut to_remove = bonds_to_unbond.epochs.clone(); - to_remove.insert(start_epoch); - to_remove - } else { - bonds_to_unbond.epochs.clone() - }; - - // `newUnbonds` - // For each epoch we're unbonding, find the amount that's being unbonded. - // For full unbonds, this is the current bond value. For partial unbonds - // it is a difference between the current and new bond amount. - let new_unbonds_map = bond_epochs_to_unbond - .into_iter() - .map(|epoch| { - let cur_bond_value = bonds_handle - .get_delta_val(storage, epoch) - .unwrap() - .unwrap_or_default(); - let value = if let Some((start_epoch, new_bond_amount)) = - bonds_to_unbond.new_entry - { - if start_epoch == epoch { - cur_bond_value - new_bond_amount - } else { - cur_bond_value - } - } else { - cur_bond_value - }; - (epoch, value) - }) - .collect::>(); - - // `updatedBonded` - // Remove bonds for all the full unbonds. - for epoch in &bonds_to_unbond.epochs { - bonds_handle.get_data_handler().remove(storage, epoch)?; - } - // Replace bond amount for partial unbond, if any. - if let Some((bond_epoch, new_bond_amount)) = bonds_to_unbond.new_entry { - bonds_handle.set(storage, new_bond_amount, bond_epoch, 0)?; - } - - // `updatedUnbonded` - // Update the unbonds in storage using the eager map computed above - if !is_redelegation { - for (start_epoch, &unbond_amount) in new_unbonds_map.iter() { - unbonds.at(start_epoch).update( - storage, - withdrawable_epoch, - |cur_val| cur_val.unwrap_or_default() + unbond_amount, - )?; - } - } - - // `newRedelegatedUnbonds` - // This is what the delegator's redelegated unbonds would look like if this - // was the only unbond in the PoS system. We need to add these redelegated - // unbonds to the existing redelegated unbonds - let new_redelegated_unbonds = compute_new_redelegated_unbonds( - storage, - &redelegated_bonds, - &bonds_to_unbond.epochs, - &modified_redelegation, - )?; - - // `updatedRedelegatedBonded` - // NOTE: for now put this here after redelegated unbonds calc bc that one - // uses the pre-modified redelegated bonds from storage! - // First remove redelegation entries in epochs with full unbonds. - for epoch_to_remove in &bonds_to_unbond.epochs { - redelegated_bonds.remove_all(storage, epoch_to_remove)?; - } - if let Some(epoch) = modified_redelegation.epoch { - tracing::debug!("\nIs modified redelegation"); - if modified_redelegation.validators_to_remove.is_empty() { - redelegated_bonds.remove_all(storage, &epoch)?; - } else { - // Then update the redelegated bonds at this epoch - let rbonds = redelegated_bonds.at(&epoch); - update_redelegated_bonds(storage, &rbonds, &modified_redelegation)?; - } - } - - if !is_redelegation { - // `val updatedRedelegatedUnbonded` with updates applied below - // Delegator's redelegated unbonds to this validator. - let delegator_redelegated_unbonded = - delegator_redelegated_unbonds_handle(source).at(validator); - - // Quint `def updateRedelegatedUnbonded` with `val - // updatedRedelegatedUnbonded` together with last statement - // in `updatedDelegator.with("redelegatedUnbonded", ...` updated - // directly in storage - for (start, unbonds) in &new_redelegated_unbonds { - let this_redelegated_unbonded = delegator_redelegated_unbonded - .at(start) - .at(&withdrawable_epoch); - - // Update the delegator's redelegated unbonds with the change - for (src_validator, redelegated_unbonds) in unbonds { - let redelegated_unbonded = - this_redelegated_unbonded.at(src_validator); - for (&redelegation_epoch, &change) in redelegated_unbonds { - redelegated_unbonded.update( - storage, - redelegation_epoch, - |current| current.unwrap_or_default() + change, - )?; - } - } - } - } - // all `val updatedDelegator` changes are applied at this point - - // `val updatedTotalBonded` and `val updatedTotalUnbonded` with updates - // Update the validator's total bonded and unbonded amounts - let total_bonded = total_bonded_handle(validator).get_data_handler(); - let total_unbonded = total_unbonded_handle(validator).at(&pipeline_epoch); - for (&start_epoch, &amount) in &new_unbonds_map { - total_bonded.update(storage, start_epoch, |current| { - current.unwrap_or_default() - amount - })?; - total_unbonded.update(storage, start_epoch, |current| { - current.unwrap_or_default() + amount - })?; - } - - let total_redelegated_bonded = - validator_total_redelegated_bonded_handle(validator); - let total_redelegated_unbonded = - validator_total_redelegated_unbonded_handle(validator); - for (redelegation_start_epoch, unbonds) in &new_redelegated_unbonds { - for (src_validator, changes) in unbonds { - for (bond_start_epoch, change) in changes { - // total redelegated bonded - let bonded_sub_map = total_redelegated_bonded - .at(redelegation_start_epoch) - .at(src_validator); - bonded_sub_map.update( - storage, - *bond_start_epoch, - |current| current.unwrap_or_default() - *change, - )?; - - // total redelegated unbonded - let unbonded_sub_map = total_redelegated_unbonded - .at(&pipeline_epoch) - .at(redelegation_start_epoch) - .at(src_validator); - unbonded_sub_map.update( - storage, - *bond_start_epoch, - |current| current.unwrap_or_default() + *change, - )?; - } - } - } - - let slashes = find_validator_slashes(storage, validator)?; - // `val resultSlashing` - let result_slashing = compute_amount_after_slashing_unbond( - storage, - ¶ms, - &new_unbonds_map, - &new_redelegated_unbonds, - slashes, - )?; - #[cfg(debug_assertions)] - let redel_bonds_post = redelegated_bonds.collect_map(storage)?; - debug_assert!( - result_slashing.sum <= amount, - "Amount after slashing ({}) must be <= requested amount to unbond \ - ({}).", - result_slashing.sum.to_string_native(), - amount.to_string_native(), - ); - - let change_after_slashing = -result_slashing.sum.change(); - // Update the validator set at the pipeline offset. Since unbonding from a - // jailed validator who is no longer frozen is allowed, only update the - // validator set if the validator is not jailed - let is_jailed_or_inactive_at_pipeline = matches!( - validator_state_handle(validator).get( - storage, - pipeline_epoch, - ¶ms - )?, - Some(ValidatorState::Jailed) | Some(ValidatorState::Inactive) - ); - if !is_jailed_or_inactive_at_pipeline { - update_validator_set( - storage, - ¶ms, - validator, - change_after_slashing, - current_epoch, - None, - )?; - } - - // Update the validator and total deltas at the pipeline offset - update_validator_deltas( - storage, - ¶ms, - validator, - change_after_slashing, - current_epoch, - None, - )?; - update_total_deltas( - storage, - ¶ms, - change_after_slashing, - current_epoch, - None, - )?; - - if tracing::level_enabled!(tracing::Level::DEBUG) { - let bonds = find_bonds(storage, source, validator)?; - tracing::debug!("\nBonds after decrementing: {bonds:#?}"); - } - - // Invariant: in the affected epochs, the delta of bonds must be >= delta of - // redelegated bonds deltas sum - #[cfg(debug_assertions)] - { - let mut epochs = bonds_to_unbond.epochs.clone(); - if let Some((epoch, _)) = bonds_to_unbond.new_entry { - epochs.insert(epoch); - } - for epoch in epochs { - let cur_bond = bonds_handle - .get_delta_val(storage, epoch)? - .unwrap_or_default(); - let redelegated_deltas = redelegated_bonds - .at(&epoch) - // Sum of redelegations from any src validator - .collect_map(storage)? - .into_values() - .map(|redeleg| redeleg.into_values().sum()) - .sum(); - debug_assert!( - cur_bond >= redelegated_deltas, - "After unbonding, in epoch {epoch} the bond amount {} must be \ - >= redelegated deltas at pipeline {}.\n\nredelegated_bonds \ - pre: {redel_bonds_pre:#?}\nredelegated_bonds post: \ - {redel_bonds_post:#?},\nmodified_redelegation: \ - {modified_redelegation:#?},\nbonds_to_unbond: \ - {bonds_to_unbond:#?}", - cur_bond.to_string_native(), - redelegated_deltas.to_string_native() - ); - } - } - - // Tally rewards (only call if this is not the first epoch) - if current_epoch > Epoch::default() { - let mut rewards = token::Amount::zero(); - - let last_claim_epoch = - get_last_reward_claim_epoch(storage, source, validator)? - .unwrap_or_default(); - let rewards_products = validator_rewards_products_handle(validator); - - for (start_epoch, slashed_amount) in &result_slashing.epoch_map { - // Stop collecting rewards at the moment the unbond is initiated - // (right now) - for ep in - Epoch::iter_bounds_inclusive(*start_epoch, current_epoch.prev()) - { - // Consider the last epoch when rewards were claimed - if ep < last_claim_epoch { - continue; - } - let rp = - rewards_products.get(storage, &ep)?.unwrap_or_default(); - rewards += rp * (*slashed_amount); - } - } - - // Update the rewards from the current unbonds first - add_rewards_to_counter(storage, source, validator, rewards)?; - } - - Ok(result_slashing) -} - -#[derive(Debug, Default, Eq, PartialEq)] -struct FoldRedelegatedBondsResult { - total_redelegated: token::Amount, - total_after_slashing: token::Amount, -} - -/// Iterates over a `redelegated_unbonds` and computes the both the sum of all -/// redelegated tokens and how much is left after applying all relevant slashes. -// `def foldAndSlashRedelegatedBondsMap` -fn fold_and_slash_redelegated_bonds( - storage: &S, - params: &OwnedPosParams, - redelegated_unbonds: &EagerRedelegatedBondsMap, - start_epoch: Epoch, - list_slashes: &[Slash], - slash_epoch_filter: impl Fn(Epoch) -> bool, -) -> FoldRedelegatedBondsResult -where - S: StorageRead, -{ - let mut result = FoldRedelegatedBondsResult::default(); - for (src_validator, bonds_map) in redelegated_unbonds { - for (bond_start, &change) in bonds_map { - // Merge the two lists of slashes - let mut merged: Vec = - // Look-up slashes for this validator ... - validator_slashes_handle(src_validator) - .iter(storage) - .unwrap() - .map(Result::unwrap) - .filter(|slash| { - params.in_redelegation_slashing_window( - slash.epoch, - params.redelegation_start_epoch_from_end( - start_epoch, - ), - start_epoch, - ) && *bond_start <= slash.epoch - && slash_epoch_filter(slash.epoch) - }) - // ... and add `list_slashes` - .chain(list_slashes.iter().cloned()) - .collect(); - - // Sort slashes by epoch - merged.sort_by(|s1, s2| s1.epoch.partial_cmp(&s2.epoch).unwrap()); - - result.total_redelegated += change; - result.total_after_slashing += - apply_list_slashes(params, &merged, change); - } - } - result -} - -/// Computes how much remains from an amount of tokens after applying a list of -/// slashes. -/// -/// - `slashes` - a list of slashes ordered by misbehaving epoch. -/// - `amount` - the amount of slashable tokens. -// `def applyListSlashes` -fn apply_list_slashes( - params: &OwnedPosParams, - slashes: &[Slash], - amount: token::Amount, -) -> token::Amount { - let mut final_amount = amount; - let mut computed_slashes = BTreeMap::::new(); - for slash in slashes { - let slashed_amount = - compute_slashable_amount(params, slash, amount, &computed_slashes); - final_amount = - final_amount.checked_sub(slashed_amount).unwrap_or_default(); - computed_slashes.insert(slash.epoch, slashed_amount); - } - final_amount -} - -/// Computes how much is left from a bond or unbond after applying a slash given -/// that a set of slashes may have been previously applied. -// `def computeSlashableAmount` -fn compute_slashable_amount( - params: &OwnedPosParams, - slash: &Slash, - amount: token::Amount, - computed_slashes: &BTreeMap, -) -> token::Amount { - let updated_amount = computed_slashes - .iter() - .filter(|(&epoch, _)| { - // Keep slashes that have been applied and processed before the - // current slash occurred. We use `<=` because slashes processed at - // `slash.epoch` (at the start of the epoch) are also processed - // before this slash occurred. - epoch + params.slash_processing_epoch_offset() <= slash.epoch - }) - .fold(amount, |acc, (_, &amnt)| { - acc.checked_sub(amnt).unwrap_or_default() - }); - updated_amount.mul_ceil(slash.rate) -} - -/// Epochs for full and partial unbonds. -#[derive(Debug, Default)] -struct BondsForRemovalRes { - /// Full unbond epochs - pub epochs: BTreeSet, - /// Partial unbond epoch associated with the new bond amount - pub new_entry: Option<(Epoch, token::Amount)>, -} - -/// In decreasing epoch order, decrement the non-zero bond amount entries until -/// the full `amount` has been removed. Returns a `BondsForRemovalRes` object -/// that contains the epochs for which the full bond amount is removed and -/// additionally information for the one epoch whose bond amount is partially -/// removed, if any. -fn find_bonds_to_remove( - storage: &S, - bonds_handle: &LazyMap, - amount: token::Amount, -) -> storage_api::Result -where - S: StorageRead, -{ - #[allow(clippy::needless_collect)] - let bonds: Vec> = bonds_handle.iter(storage)?.collect(); - - let mut bonds_for_removal = BondsForRemovalRes::default(); - let mut remaining = amount; - - for bond in bonds.into_iter().rev() { - let (bond_epoch, bond_amount) = bond?; - let to_unbond = cmp::min(bond_amount, remaining); - if to_unbond == bond_amount { - bonds_for_removal.epochs.insert(bond_epoch); - } else { - bonds_for_removal.new_entry = - Some((bond_epoch, bond_amount - to_unbond)); - } - remaining -= to_unbond; - if remaining.is_zero() { - break; - } - } - Ok(bonds_for_removal) -} - -#[derive(Debug, Default, PartialEq, Eq)] -struct ModifiedRedelegation { - epoch: Option, - validators_to_remove: BTreeSet
, - validator_to_modify: Option
, - epochs_to_remove: BTreeSet, - epoch_to_modify: Option, - new_amount: Option, -} - -/// Used in `fn unbond_tokens` to compute the modified state of a redelegation -/// if redelegated tokens are being unbonded. -fn compute_modified_redelegation( - storage: &S, - redelegated_bonds: &RedelegatedTokens, - start_epoch: Epoch, - amount_to_unbond: token::Amount, -) -> storage_api::Result -where - S: StorageRead, -{ - let mut modified_redelegation = ModifiedRedelegation::default(); - - let mut src_validators = BTreeSet::
::new(); - let mut total_redelegated = token::Amount::zero(); - for rb in redelegated_bonds.iter(storage)? { - let ( - NestedSubKey::Data { - key: src_validator, - nested_sub_key: _, - }, - amount, - ) = rb?; - total_redelegated += amount; - src_validators.insert(src_validator); - } - - modified_redelegation.epoch = Some(start_epoch); - - // If the total amount of redelegated bonds is less than the target amount, - // then all redelegated bonds must be unbonded. - if total_redelegated <= amount_to_unbond { - return Ok(modified_redelegation); - } - - let mut remaining = amount_to_unbond; - for src_validator in src_validators.into_iter() { - if remaining.is_zero() { - break; - } - let rbonds = redelegated_bonds.at(&src_validator); - let total_src_val_amount = rbonds - .iter(storage)? - .map(|res| { - let (_, amount) = res?; - Ok(amount) - }) - .sum::>()?; - - // TODO: move this into the `if total_redelegated <= remaining` branch - // below, then we don't have to remove it in `fn - // update_redelegated_bonds` when `validator_to_modify` is Some (and - // avoid `modified_redelegation.validators_to_remove.clone()`). - // It affects assumption 2. in `fn compute_new_redelegated_unbonds`, but - // that looks trivial to change. - // NOTE: not sure if this TODO is still relevant... - modified_redelegation - .validators_to_remove - .insert(src_validator.clone()); - if total_src_val_amount <= remaining { - remaining -= total_src_val_amount; - } else { - let bonds_to_remove = - find_bonds_to_remove(storage, &rbonds, remaining)?; - - remaining = token::Amount::zero(); - - // NOTE: When there are multiple `src_validators` from which we're - // unbonding, `validator_to_modify` cannot get overriden, because - // only one of them can be a partial unbond (`new_entry` - // is partial unbond) - if let Some((bond_epoch, new_bond_amount)) = - bonds_to_remove.new_entry - { - modified_redelegation.validator_to_modify = Some(src_validator); - modified_redelegation.epochs_to_remove = { - let mut epochs = bonds_to_remove.epochs; - // TODO: remove this insertion then we don't have to remove - // it again in `fn update_redelegated_bonds` - // when `epoch_to_modify` is Some (and avoid - // `modified_redelegation.epochs_to_remove.clone`) - // It affects assumption 3. in `fn - // compute_new_redelegated_unbonds`, but that also looks - // trivial to change. - epochs.insert(bond_epoch); - epochs - }; - modified_redelegation.epoch_to_modify = Some(bond_epoch); - modified_redelegation.new_amount = Some(new_bond_amount); - } else { - modified_redelegation.validator_to_modify = Some(src_validator); - modified_redelegation.epochs_to_remove = bonds_to_remove.epochs; - } - } - } - Ok(modified_redelegation) -} - -fn update_redelegated_bonds( - storage: &mut S, - redelegated_bonds: &RedelegatedTokens, - modified_redelegation: &ModifiedRedelegation, -) -> storage_api::Result<()> -where - S: StorageRead + StorageWrite, -{ - if let Some(val_to_modify) = &modified_redelegation.validator_to_modify { - let mut updated_vals_to_remove = - modified_redelegation.validators_to_remove.clone(); - updated_vals_to_remove.remove(val_to_modify); - - // Remove the updated_vals_to_remove keys from the - // redelegated_bonds map - for val in &updated_vals_to_remove { - redelegated_bonds.remove_all(storage, val)?; - } - - if let Some(epoch_to_modify) = modified_redelegation.epoch_to_modify { - let mut updated_epochs_to_remove = - modified_redelegation.epochs_to_remove.clone(); - updated_epochs_to_remove.remove(&epoch_to_modify); - let val_bonds_to_modify = redelegated_bonds.at(val_to_modify); - for epoch in updated_epochs_to_remove { - val_bonds_to_modify.remove(storage, &epoch)?; - } - val_bonds_to_modify.insert( - storage, - epoch_to_modify, - modified_redelegation.new_amount.unwrap(), - )?; - } else { - // Then remove to epochs_to_remove from the redelegated bonds of the - // val_to_modify - let val_bonds_to_modify = redelegated_bonds.at(val_to_modify); - for epoch in &modified_redelegation.epochs_to_remove { - val_bonds_to_modify.remove(storage, epoch)?; - } - } - } else { - // Remove all validators in modified_redelegation.validators_to_remove - // from redelegated_bonds - for val in &modified_redelegation.validators_to_remove { - redelegated_bonds.remove_all(storage, val)?; - } - } - Ok(()) -} - -/// Temp helper type to match quint model. -/// Result of `compute_new_redelegated_unbonds` that contains a map of -/// redelegated unbonds. -/// The map keys from outside in are: -/// -/// - redelegation end epoch where redeleg stops contributing to src validator -/// - src validator address -/// - src bond start epoch where it started contributing to src validator -type EagerRedelegatedUnbonds = BTreeMap; - -/// Computes a map of redelegated unbonds from a set of redelegated bonds. -/// -/// - `redelegated_bonds` - a map of redelegated bonds from epoch to -/// `RedelegatedTokens`. -/// - `epochs_to_remove` - a set of epochs that indicate the set of epochs -/// unbonded. -/// - `modified` record that represents a redelegated bond that it is only -/// partially unbonded. -/// -/// The function assumes that: -/// -/// 1. `modified.epoch` is not in the `epochs_to_remove` set. -/// 2. `modified.validator_to_modify` is in `modified.vals_to_remove`. -/// 3. `modified.epoch_to_modify` is in in `modified.epochs_to_remove`. -// `def computeNewRedelegatedUnbonds` from Quint -fn compute_new_redelegated_unbonds( - storage: &S, - redelegated_bonds: &RedelegatedBondsOrUnbonds, - epochs_to_remove: &BTreeSet, - modified: &ModifiedRedelegation, -) -> storage_api::Result -where - S: StorageRead + StorageWrite, -{ - let unbonded_epochs = if let Some(epoch) = modified.epoch { - debug_assert!( - !epochs_to_remove.contains(&epoch), - "1. assumption in `fn compute_new_redelegated_unbonds` doesn't \ - hold" - ); - let mut epochs = epochs_to_remove.clone(); - epochs.insert(epoch); - epochs - .iter() - .cloned() - .filter(|e| redelegated_bonds.contains(storage, e).unwrap()) - .collect::>() - } else { - epochs_to_remove - .iter() - .cloned() - .filter(|e| redelegated_bonds.contains(storage, e).unwrap()) - .collect::>() - }; - debug_assert!( - modified - .validator_to_modify - .as_ref() - .map(|validator| modified.validators_to_remove.contains(validator)) - .unwrap_or(true), - "2. assumption in `fn compute_new_redelegated_unbonds` doesn't hold" - ); - debug_assert!( - modified - .epoch_to_modify - .as_ref() - .map(|epoch| modified.epochs_to_remove.contains(epoch)) - .unwrap_or(true), - "3. assumption in `fn compute_new_redelegated_unbonds` doesn't hold" - ); - - // quint `newRedelegatedUnbonds` returned from - // `computeNewRedelegatedUnbonds` - let new_redelegated_unbonds: EagerRedelegatedUnbonds = unbonded_epochs - .into_iter() - .map(|start| { - let mut rbonds = EagerRedelegatedBondsMap::default(); - if modified - .epoch - .map(|redelegation_epoch| start != redelegation_epoch) - .unwrap_or(true) - || modified.validators_to_remove.is_empty() - { - for res in redelegated_bonds.at(&start).iter(storage).unwrap() { - let ( - NestedSubKey::Data { - key: validator, - nested_sub_key: SubKey::Data(epoch), - }, - amount, - ) = res.unwrap(); - rbonds - .entry(validator.clone()) - .or_default() - .insert(epoch, amount); - } - (start, rbonds) - } else { - for src_validator in &modified.validators_to_remove { - if modified - .validator_to_modify - .as_ref() - .map(|validator| src_validator != validator) - .unwrap_or(true) - { - let raw_bonds = - redelegated_bonds.at(&start).at(src_validator); - for res in raw_bonds.iter(storage).unwrap() { - let (bond_epoch, bond_amount) = res.unwrap(); - rbonds - .entry(src_validator.clone()) - .or_default() - .insert(bond_epoch, bond_amount); - } - } else { - for bond_start in &modified.epochs_to_remove { - let cur_redel_bond_amount = redelegated_bonds - .at(&start) - .at(src_validator) - .get(storage, bond_start) - .unwrap() - .unwrap_or_default(); - let raw_bonds = rbonds - .entry(src_validator.clone()) - .or_default(); - if modified - .epoch_to_modify - .as_ref() - .map(|epoch| bond_start != epoch) - .unwrap_or(true) - { - raw_bonds - .insert(*bond_start, cur_redel_bond_amount); - } else { - raw_bonds.insert( - *bond_start, - cur_redel_bond_amount - - modified - .new_amount - // Safe unwrap - it shouldn't - // get to - // this if it's None - .unwrap(), - ); - } - } - } - } - (start, rbonds) - } - }) - .collect(); - - Ok(new_redelegated_unbonds) -} - -/// Compute a token amount after slashing, given the initial amount and a set of -/// slashes. It is assumed that the input `slashes` are those commited while the -/// `amount` was contributing to voting power. -fn get_slashed_amount( - params: &PosParams, - amount: token::Amount, - slashes: &BTreeMap, -) -> storage_api::Result { - let mut updated_amount = amount; - let mut computed_amounts = Vec::::new(); - - for (&infraction_epoch, &slash_rate) in slashes { - let mut computed_to_remove = BTreeSet::>::new(); - for (ix, slashed_amount) in computed_amounts.iter().enumerate() { - // Update amount with slashes that happened more than unbonding_len - // epochs before this current slash - if slashed_amount.epoch + params.slash_processing_epoch_offset() - <= infraction_epoch - { - updated_amount = updated_amount - .checked_sub(slashed_amount.amount) - .unwrap_or_default(); - computed_to_remove.insert(Reverse(ix)); - } - } - // Invariant: `computed_to_remove` must be in reverse ord to avoid - // left-shift of the `computed_amounts` after call to `remove` - // invalidating the rest of the indices. - for item in computed_to_remove { - computed_amounts.remove(item.0); - } - computed_amounts.push(SlashedAmount { - amount: updated_amount.mul_ceil(slash_rate), - epoch: infraction_epoch, - }); - } - - let total_computed_amounts = computed_amounts - .into_iter() - .map(|slashed| slashed.amount) - .sum(); - - let final_amount = updated_amount - .checked_sub(total_computed_amounts) - .unwrap_or_default(); - - Ok(final_amount) -} - -// `def computeAmountAfterSlashingUnbond` -fn compute_amount_after_slashing_unbond( - storage: &S, - params: &OwnedPosParams, - unbonds: &BTreeMap, - redelegated_unbonds: &EagerRedelegatedUnbonds, - slashes: Vec, -) -> storage_api::Result -where - S: StorageRead, -{ - let mut result_slashing = ResultSlashing::default(); - for (&start_epoch, amount) in unbonds { - // `val listSlashes` - let list_slashes: Vec = slashes - .iter() - .filter(|slash| slash.epoch >= start_epoch) - .cloned() - .collect(); - // `val resultFold` - let result_fold = if let Some(redelegated_unbonds) = - redelegated_unbonds.get(&start_epoch) - { - fold_and_slash_redelegated_bonds( - storage, - params, - redelegated_unbonds, - start_epoch, - &list_slashes, - |_| true, - ) - } else { - FoldRedelegatedBondsResult::default() - }; - // `val totalNoRedelegated` - let total_not_redelegated = amount - .checked_sub(result_fold.total_redelegated) - .unwrap_or_default(); - // `val afterNoRedelegated` - let after_not_redelegated = - apply_list_slashes(params, &list_slashes, total_not_redelegated); - // `val amountAfterSlashing` - let amount_after_slashing = - after_not_redelegated + result_fold.total_after_slashing; - // Accumulation step - result_slashing.sum += amount_after_slashing; - result_slashing - .epoch_map - .insert(start_epoch, amount_after_slashing); - } - Ok(result_slashing) -} - -/// Compute from a set of unbonds (both redelegated and not) how much is left -/// after applying all relevant slashes. -// `def computeAmountAfterSlashingWithdraw` -fn compute_amount_after_slashing_withdraw( - storage: &S, - params: &OwnedPosParams, - unbonds_and_redelegated_unbonds: &BTreeMap< - (Epoch, Epoch), - (token::Amount, EagerRedelegatedBondsMap), - >, - slashes: Vec, -) -> storage_api::Result -where - S: StorageRead, -{ - let mut result_slashing = ResultSlashing::default(); - - for ((start_epoch, withdraw_epoch), (amount, redelegated_unbonds)) in - unbonds_and_redelegated_unbonds.iter() - { - // TODO: check if slashes in the same epoch can be - // folded into one effective slash - let end_epoch = *withdraw_epoch - - params.unbonding_len - - params.cubic_slashing_window_length; - // Find slashes that apply to `start_epoch..end_epoch` - let list_slashes = slashes - .iter() - .filter(|slash| { - // Started before the slash occurred - start_epoch <= &slash.epoch - // Ends after the slash - && end_epoch > slash.epoch - }) - .cloned() - .collect::>(); - - // Find the sum and the sum after slashing of the redelegated unbonds - let result_fold = fold_and_slash_redelegated_bonds( - storage, - params, - redelegated_unbonds, - *start_epoch, - &list_slashes, - |_| true, - ); - - // Unbond amount that didn't come from a redelegation - let total_not_redelegated = *amount - result_fold.total_redelegated; - // Find how much remains after slashing non-redelegated amount - let after_not_redelegated = - apply_list_slashes(params, &list_slashes, total_not_redelegated); - - // Add back the unbond and redelegated unbond amount after slashing - let amount_after_slashing = - after_not_redelegated + result_fold.total_after_slashing; - - result_slashing.sum += amount_after_slashing; - result_slashing - .epoch_map - .insert(*start_epoch, amount_after_slashing); - } - - Ok(result_slashing) -} - -/// Arguments to [`become_validator`]. -pub struct BecomeValidator<'a> { - /// Proof-of-stake parameters. - pub params: &'a PosParams, - /// The validator's address. - pub address: &'a Address, - /// The validator's consensus key, used by Tendermint. - pub consensus_key: &'a common::PublicKey, - /// The validator's protocol key. - pub protocol_key: &'a common::PublicKey, - /// The validator's Ethereum bridge cold key. - pub eth_cold_key: &'a common::PublicKey, - /// The validator's Ethereum bridge hot key. - pub eth_hot_key: &'a common::PublicKey, - /// The numeric value of the current epoch. - pub current_epoch: Epoch, - /// Commission rate. - pub commission_rate: Dec, - /// Max commission rate change. - pub max_commission_rate_change: Dec, - /// Validator metadata - pub metadata: ValidatorMetaData, - /// Optional offset to use instead of pipeline offset - pub offset_opt: Option, -} - -/// Initialize data for a new validator. -pub fn become_validator( - storage: &mut S, - args: BecomeValidator<'_>, -) -> storage_api::Result<()> -where - S: StorageRead + StorageWrite, -{ - let BecomeValidator { - params, - address, - consensus_key, - protocol_key, - eth_cold_key, - eth_hot_key, - current_epoch, - commission_rate, - max_commission_rate_change, - metadata, - offset_opt, - } = args; - let offset = offset_opt.unwrap_or(params.pipeline_len); - - if !address.is_established() { - return Err(storage_api::Error::new_const( - "The given address {address} is not established. Only an \ - established address can become a validator.", - )); - } - - if is_validator(storage, address)? { - return Err(storage_api::Error::new_const( - "The given address is already a validator", - )); - } - - // If the address is not yet a validator, it cannot have self-bonds, but it - // may have delegations. - if has_bonds(storage, address)? { - return Err(storage_api::Error::new_const( - "The given address has delegations and therefore cannot become a \ - validator. Unbond first.", - )); - } - - // This will fail if the key is already being used - try_insert_consensus_key(storage, consensus_key)?; - - let pipeline_epoch = current_epoch + offset; - validator_addresses_handle() - .at(&pipeline_epoch) - .insert(storage, address.clone())?; - - // Non-epoched validator data - write_validator_address_raw_hash(storage, address, consensus_key)?; - write_validator_max_commission_rate_change( - storage, - address, - max_commission_rate_change, - )?; - write_validator_metadata(storage, address, &metadata)?; - - // Epoched validator data - validator_consensus_key_handle(address).set( - storage, - consensus_key.clone(), - current_epoch, - offset, - )?; - validator_protocol_key_handle(address).set( - storage, - protocol_key.clone(), - current_epoch, - offset, - )?; - validator_eth_hot_key_handle(address).set( - storage, - eth_hot_key.clone(), - current_epoch, - offset, - )?; - validator_eth_cold_key_handle(address).set( - storage, - eth_cold_key.clone(), - current_epoch, - offset, - )?; - validator_commission_rate_handle(address).set( - storage, - commission_rate, - current_epoch, - offset, - )?; - validator_deltas_handle(address).set( - storage, - token::Change::zero(), - current_epoch, - offset, - )?; - - // The validator's stake at initialization is 0, so its state is immediately - // below-threshold - validator_state_handle(address).set( - storage, - ValidatorState::BelowThreshold, - current_epoch, - offset, - )?; - - insert_validator_into_validator_set( - storage, - params, - address, - token::Amount::zero(), - current_epoch, - offset, - )?; - - Ok(()) -} - -/// Consensus key change for a validator -pub fn change_consensus_key( - storage: &mut S, - validator: &Address, - consensus_key: &common::PublicKey, - current_epoch: Epoch, -) -> storage_api::Result<()> -where - S: StorageRead + StorageWrite, -{ - tracing::debug!("Changing consensus key for validator {}", validator); - - // Require that the new consensus key is an Ed25519 key - match consensus_key { - common::PublicKey::Ed25519(_) => {} - common::PublicKey::Secp256k1(_) => { - return Err(ConsensusKeyChangeError::MustBeEd25519.into()); - } - } - - // Check for uniqueness of the consensus key - try_insert_consensus_key(storage, consensus_key)?; - - // Set the new consensus key at the pipeline epoch - let params = read_pos_params(storage)?; - validator_consensus_key_handle(validator).set( - storage, - consensus_key.clone(), - current_epoch, - params.pipeline_len, - )?; - - // Write validator's new raw hash - write_validator_address_raw_hash(storage, validator, consensus_key)?; - - Ok(()) -} - -/// Withdraw tokens from those that have been unbonded from proof-of-stake -pub fn withdraw_tokens( - storage: &mut S, - source: Option<&Address>, - validator: &Address, - current_epoch: Epoch, -) -> storage_api::Result -where - S: StorageRead + StorageWrite, -{ - let params = read_pos_params(storage)?; - let source = source.unwrap_or(validator); - - tracing::debug!("Withdrawing tokens in epoch {current_epoch}"); - tracing::debug!("Source {} --> Validator {}", source, validator); - - let unbond_handle: Unbonds = unbond_handle(source, validator); - let redelegated_unbonds = - delegator_redelegated_unbonds_handle(source).at(validator); - - // Check that there are unbonded tokens available for withdrawal - if unbond_handle.is_empty(storage)? { - return Err(WithdrawError::NoUnbondFound(BondId { - source: source.clone(), - validator: validator.clone(), - }) - .into()); - } - - let mut unbonds_and_redelegated_unbonds: BTreeMap< - (Epoch, Epoch), - (token::Amount, EagerRedelegatedBondsMap), - > = BTreeMap::new(); - - for unbond in unbond_handle.iter(storage)? { - let ( - NestedSubKey::Data { - key: start_epoch, - nested_sub_key: SubKey::Data(withdraw_epoch), - }, - amount, - ) = unbond?; - - // Logging - tracing::debug!( - "Unbond delta ({start_epoch}..{withdraw_epoch}), amount {}", - amount.to_string_native() - ); - // Consider only unbonds that are eligible to be withdrawn - if withdraw_epoch > current_epoch { - tracing::debug!( - "Not yet withdrawable until epoch {withdraw_epoch}" - ); - continue; - } - - let mut eager_redelegated_unbonds = EagerRedelegatedBondsMap::default(); - let matching_redelegated_unbonds = - redelegated_unbonds.at(&start_epoch).at(&withdraw_epoch); - for ub in matching_redelegated_unbonds.iter(storage)? { - let ( - NestedSubKey::Data { - key: address, - nested_sub_key: SubKey::Data(epoch), - }, - amount, - ) = ub?; - eager_redelegated_unbonds - .entry(address) - .or_default() - .entry(epoch) - .or_insert(amount); - } - - unbonds_and_redelegated_unbonds.insert( - (start_epoch, withdraw_epoch), - (amount, eager_redelegated_unbonds), - ); - } - - let slashes = find_validator_slashes(storage, validator)?; - - // `val resultSlashing` - let result_slashing = compute_amount_after_slashing_withdraw( - storage, - ¶ms, - &unbonds_and_redelegated_unbonds, - slashes, - )?; - - let withdrawable_amount = result_slashing.sum; - tracing::debug!( - "Withdrawing total {}", - withdrawable_amount.to_string_native() - ); - - // `updateDelegator` with `unbonded` and `redelegeatedUnbonded` - for ((start_epoch, withdraw_epoch), _unbond_and_redelegations) in - unbonds_and_redelegated_unbonds - { - tracing::debug!("Remove ({start_epoch}..{withdraw_epoch}) from unbond"); - unbond_handle - .at(&start_epoch) - .remove(storage, &withdraw_epoch)?; - redelegated_unbonds - .at(&start_epoch) - .remove_all(storage, &withdraw_epoch)?; - - if unbond_handle.at(&start_epoch).is_empty(storage)? { - unbond_handle.remove_all(storage, &start_epoch)?; - } - if redelegated_unbonds.at(&start_epoch).is_empty(storage)? { - redelegated_unbonds.remove_all(storage, &start_epoch)?; - } - } - - // Transfer the withdrawable tokens from the PoS address back to the source - let staking_token = staking_token_address(storage); - token::transfer( - storage, - &staking_token, - &ADDRESS, - source, - withdrawable_amount, - )?; - - // TODO: Transfer the slashed tokens from the PoS address to the Slash Pool - // address - // token::transfer( - // storage, - // &staking_token, - // &ADDRESS, - // &SLASH_POOL_ADDRESS, - // total_slashed, - // )?; - - Ok(withdrawable_amount) -} - -/// Change the commission rate of a validator -pub fn change_validator_commission_rate( - storage: &mut S, - validator: &Address, - new_rate: Dec, - current_epoch: Epoch, -) -> storage_api::Result<()> -where - S: StorageRead + StorageWrite, -{ - if new_rate.is_negative() { - return Err(CommissionRateChangeError::NegativeRate( - new_rate, - validator.clone(), - ) - .into()); - } - - if new_rate > Dec::one() { - return Err(CommissionRateChangeError::LargerThanOne( - new_rate, - validator.clone(), - ) - .into()); - } - - let max_change = - read_validator_max_commission_rate_change(storage, validator)?; - if max_change.is_none() { - return Err(CommissionRateChangeError::NoMaxSetInStorage( - validator.clone(), - ) - .into()); - } - - let params = read_pos_params(storage)?; - let commission_handle = validator_commission_rate_handle(validator); - let pipeline_epoch = current_epoch + params.pipeline_len; - - let rate_at_pipeline = commission_handle - .get(storage, pipeline_epoch, ¶ms)? - .expect("Could not find a rate in given epoch"); - if new_rate == rate_at_pipeline { - return Ok(()); - } - let rate_before_pipeline = commission_handle - .get(storage, pipeline_epoch.prev(), ¶ms)? - .expect("Could not find a rate in given epoch"); - - let change_from_prev = new_rate.abs_diff(&rate_before_pipeline); - if change_from_prev > max_change.unwrap() { - return Err(CommissionRateChangeError::RateChangeTooLarge( - change_from_prev, - validator.clone(), - ) - .into()); - } - - commission_handle.set(storage, new_rate, current_epoch, params.pipeline_len) -} - -/// Check if the given consensus key is already being used to ensure uniqueness. -/// -/// If it's not being used, it will be inserted into the set that's being used -/// for this. If it's already used, this will return an Error. -pub fn try_insert_consensus_key( - storage: &mut S, - consensus_key: &common::PublicKey, -) -> storage_api::Result<()> -where - S: StorageRead + StorageWrite, -{ - let key = consensus_keys_key(); - LazySet::open(key).try_insert(storage, consensus_key.clone()) -} - -/// Get the unique set of consensus keys in storage -pub fn get_consensus_key_set( - storage: &S, -) -> storage_api::Result> -where - S: StorageRead, -{ - let key = consensus_keys_key(); - let lazy_set = LazySet::::open(key); - Ok(lazy_set.iter(storage)?.map(Result::unwrap).collect()) -} - -/// Check if the given consensus key is already being used to ensure uniqueness. -pub fn is_consensus_key_used( - storage: &S, - consensus_key: &common::PublicKey, -) -> storage_api::Result -where - S: StorageRead, -{ - let key = consensus_keys_key(); - let handle = LazySet::open(key); - handle.contains(storage, consensus_key) -} - -/// Get the total bond amount, including slashes, for a given bond ID and epoch. -/// Returns the bond amount after slashing. For future epochs the value is -/// subject to change. -pub fn bond_amount( - storage: &S, - bond_id: &BondId, - epoch: Epoch, -) -> storage_api::Result -where - S: StorageRead, -{ - let params = read_pos_params(storage)?; - // Outer key is the start epoch used to calculate slashes. The inner - // keys are discarded after applying slashes. - let mut amounts: BTreeMap = BTreeMap::default(); - - // Bonds - let bonds = - bond_handle(&bond_id.source, &bond_id.validator).get_data_handler(); - for next in bonds.iter(storage)? { - let (start, delta) = next?; - if start <= epoch { - let amount = amounts.entry(start).or_default(); - *amount += delta; - } - } - - // Add unbonds that are still contributing to stake - let unbonds = unbond_handle(&bond_id.source, &bond_id.validator); - for next in unbonds.iter(storage)? { - let ( - NestedSubKey::Data { - key: start, - nested_sub_key: SubKey::Data(withdrawable_epoch), - }, - delta, - ) = next?; - // This is the first epoch in which the unbond stops contributing to - // voting power - let end = withdrawable_epoch - params.withdrawable_epoch_offset() - + params.pipeline_len; - - if start <= epoch && end > epoch { - let amount = amounts.entry(start).or_default(); - *amount += delta; - } - } - - if bond_id.validator != bond_id.source { - // Add outgoing redelegations that are still contributing to the source - // validator's stake - let redelegated_bonds = - delegator_redelegated_bonds_handle(&bond_id.source); - for res in redelegated_bonds.iter(storage)? { - let ( - NestedSubKey::Data { - key: _dest_validator, - nested_sub_key: - NestedSubKey::Data { - key: end, - nested_sub_key: - NestedSubKey::Data { - key: src_validator, - nested_sub_key: SubKey::Data(start), - }, - }, - }, - delta, - ) = res?; - if src_validator == bond_id.validator - && start <= epoch - && end > epoch - { - let amount = amounts.entry(start).or_default(); - *amount += delta; - } - } - - // Add outgoing redelegation unbonds that are still contributing to - // the source validator's stake - let redelegated_unbonds = - delegator_redelegated_unbonds_handle(&bond_id.source); - for res in redelegated_unbonds.iter(storage)? { - let ( - NestedSubKey::Data { - key: _dest_validator, - nested_sub_key: - NestedSubKey::Data { - key: redelegation_epoch, - nested_sub_key: - NestedSubKey::Data { - key: _withdraw_epoch, - nested_sub_key: - NestedSubKey::Data { - key: src_validator, - nested_sub_key: SubKey::Data(start), - }, - }, - }, - }, - delta, - ) = res?; - if src_validator == bond_id.validator - // If the unbonded bond was redelegated after this epoch ... - && redelegation_epoch > epoch - // ... the start was before or at this epoch - && start <= epoch - { - let amount = amounts.entry(start).or_default(); - *amount += delta; - } - } - } - - if !amounts.is_empty() { - let slashes = find_validator_slashes(storage, &bond_id.validator)?; - - // Apply slashes - for (&start, amount) in amounts.iter_mut() { - let list_slashes = slashes - .iter() - .filter(|slash| { - let processing_epoch = - slash.epoch + params.slash_processing_epoch_offset(); - // Only use slashes that were processed before or at the - // epoch associated with the bond amount. This assumes - // that slashes are applied before inflation. - processing_epoch <= epoch && start <= slash.epoch - }) - .cloned() - .collect::>(); - - *amount = apply_list_slashes(¶ms, &list_slashes, *amount); - } - } - - Ok(amounts.values().cloned().sum()) -} - -/// Get bond amounts within the `claim_start..=claim_end` epoch range for -/// claiming rewards for a given bond ID. Returns a map of bond amounts -/// associated with every epoch within the given epoch range (accumulative) in -/// which an amount contributed to the validator's stake. -/// This function will only consider slashes that were processed before or at -/// the epoch in which we're calculating the bond amount to correspond to the -/// validator stake that was used to calculate reward products (slashes do *not* -/// retrospectively affect the rewards calculated before slash processing). -pub fn bond_amounts_for_rewards( - storage: &S, - bond_id: &BondId, - claim_start: Epoch, - claim_end: Epoch, -) -> storage_api::Result> -where - S: StorageRead, -{ - let params = read_pos_params(storage)?; - // Outer key is every epoch in which the a bond amount contributed to stake - // and the inner key is the start epoch used to calculate slashes. The inner - // keys are discarded after applying slashes. - let mut amounts: BTreeMap> = - BTreeMap::default(); - - // Only need to do bonds since rewwards are accumulated during - // `unbond_tokens` - let bonds = - bond_handle(&bond_id.source, &bond_id.validator).get_data_handler(); - for next in bonds.iter(storage)? { - let (start, delta) = next?; - - for ep in Epoch::iter_bounds_inclusive(claim_start, claim_end) { - // A bond that wasn't unbonded is added to all epochs up to - // `claim_end` - if start <= ep { - let amount = - amounts.entry(ep).or_default().entry(start).or_default(); - *amount += delta; - } - } - } - - if !amounts.is_empty() { - let slashes = find_validator_slashes(storage, &bond_id.validator)?; - let redelegated_bonded = - delegator_redelegated_bonds_handle(&bond_id.source) - .at(&bond_id.validator); - - // Apply slashes - for (&ep, amounts) in amounts.iter_mut() { - for (&start, amount) in amounts.iter_mut() { - let list_slashes = slashes - .iter() - .filter(|slash| { - let processing_epoch = slash.epoch - + params.slash_processing_epoch_offset(); - // Only use slashes that were processed before or at the - // epoch associated with the bond amount. This assumes - // that slashes are applied before inflation. - processing_epoch <= ep && start <= slash.epoch - }) - .cloned() - .collect::>(); - - let slash_epoch_filter = - |e: Epoch| e + params.slash_processing_epoch_offset() <= ep; - - let redelegated_bonds = - redelegated_bonded.at(&start).collect_map(storage)?; - - let result_fold = fold_and_slash_redelegated_bonds( - storage, - ¶ms, - &redelegated_bonds, - start, - &list_slashes, - slash_epoch_filter, - ); - - let total_not_redelegated = - *amount - result_fold.total_redelegated; - - let after_not_redelegated = apply_list_slashes( - ¶ms, - &list_slashes, - total_not_redelegated, - ); - - *amount = - after_not_redelegated + result_fold.total_after_slashing; - } - } - } - - Ok(amounts - .into_iter() - // Flatten the inner maps to discard bond start epochs - .map(|(ep, amounts)| (ep, amounts.values().cloned().sum())) - .collect()) -} - -/// Get the genesis consensus validators stake and consensus key for Tendermint, -/// converted from [`ValidatorSetUpdate`]s using the given function. -pub fn genesis_validator_set_tendermint( - storage: &S, - params: &PosParams, - current_epoch: Epoch, - mut f: impl FnMut(ValidatorSetUpdate) -> T, -) -> storage_api::Result> -where - S: StorageRead, -{ - let consensus_validator_handle = - consensus_validator_set_handle().at(¤t_epoch); - let iter = consensus_validator_handle.iter(storage)?; - - iter.map(|validator| { - let ( - NestedSubKey::Data { - key: new_stake, - nested_sub_key: _, - }, - address, - ) = validator?; - let consensus_key = validator_consensus_key_handle(&address) - .get(storage, current_epoch, params)? - .unwrap(); - let converted = f(ValidatorSetUpdate::Consensus(ConsensusValidator { - consensus_key, - bonded_stake: new_stake, - })); - Ok(converted) - }) - .collect() -} - -/// Communicate imminent validator set updates to Tendermint. This function is -/// called two blocks before the start of a new epoch because Tendermint -/// validator updates become active two blocks after the updates are submitted. -pub fn validator_set_update_tendermint( - storage: &S, - params: &PosParams, - current_epoch: Epoch, - f: impl FnMut(ValidatorSetUpdate) -> T, -) -> storage_api::Result> -where - S: StorageRead, -{ - tracing::debug!("Communicating validator set updates to Tendermint."); - // Because this is called 2 blocks before a start on an epoch, we're gonna - // give Tendermint updates for the next epoch - let next_epoch = current_epoch.next(); - - let new_consensus_validator_handle = - consensus_validator_set_handle().at(&next_epoch); - let prev_consensus_validator_handle = - consensus_validator_set_handle().at(¤t_epoch); - - let new_consensus_validators = new_consensus_validator_handle - .iter(storage)? - .map(|validator| { - let ( - NestedSubKey::Data { - key: new_stake, - nested_sub_key: _, - }, - address, - ) = validator.unwrap(); - - tracing::debug!( - "Consensus validator address {address}, stake {}", - new_stake.to_string_native() - ); - - let new_consensus_key = validator_consensus_key_handle(&address) - .get(storage, next_epoch, params) - .unwrap() - .unwrap(); + if !is_jailed_or_inactive_at_pipeline { + update_validator_set( + storage, + ¶ms, + validator, + change_after_slashing, + current_epoch, + None, + )?; + } - let old_consensus_key = validator_consensus_key_handle(&address) - .get(storage, current_epoch, params) - .unwrap(); + // Update the validator and total deltas at the pipeline offset + update_validator_deltas( + storage, + ¶ms, + validator, + change_after_slashing, + current_epoch, + None, + )?; + update_total_deltas( + storage, + ¶ms, + change_after_slashing, + current_epoch, + None, + )?; - // Check if the validator was consensus in the previous epoch with - // the same stake. If so, no updated is needed. - // Look up previous state and prev and current voting powers - if !prev_consensus_validator_handle.is_empty(storage).unwrap() { - let prev_state = validator_state_handle(&address) - .get(storage, current_epoch, params) - .unwrap(); - let prev_tm_voting_power = Lazy::new(|| { - let prev_validator_stake = read_validator_stake( - storage, - params, - &address, - current_epoch, - ) - .unwrap(); - into_tm_voting_power( - params.tm_votes_per_token, - prev_validator_stake, - ) - }); - let new_tm_voting_power = Lazy::new(|| { - into_tm_voting_power(params.tm_votes_per_token, new_stake) - }); - - // If it was in `Consensus` before and voting power has not - // changed, skip the update - if matches!(prev_state, Some(ValidatorState::Consensus)) - && *prev_tm_voting_power == *new_tm_voting_power - { - if old_consensus_key.as_ref().unwrap() == &new_consensus_key - { - tracing::debug!( - "skipping validator update, {address} is in \ - consensus set but voting power hasn't changed" - ); - return vec![]; - } else { - return vec![ - ValidatorSetUpdate::Consensus(ConsensusValidator { - consensus_key: new_consensus_key, - bonded_stake: new_stake, - }), - ValidatorSetUpdate::Deactivated( - old_consensus_key.unwrap(), - ), - ]; - } - } - // If both previous and current voting powers are 0, and the - // validator_stake_threshold is 0, skip update - if params.validator_stake_threshold.is_zero() - && *prev_tm_voting_power == 0 - && *new_tm_voting_power == 0 - { - tracing::info!( - "skipping validator update, {address} is in consensus \ - set but without voting power" - ); - return vec![]; - } - } + if tracing::level_enabled!(tracing::Level::DEBUG) { + let bonds = find_bonds(storage, source, validator)?; + tracing::debug!("\nBonds after decrementing: {bonds:#?}"); + } - tracing::debug!( - "{address} consensus key {}", - new_consensus_key.tm_raw_hash() + // Invariant: in the affected epochs, the delta of bonds must be >= delta of + // redelegated bonds deltas sum + #[cfg(debug_assertions)] + { + let mut epochs = bonds_to_unbond.epochs.clone(); + if let Some((epoch, _)) = bonds_to_unbond.new_entry { + epochs.insert(epoch); + } + for epoch in epochs { + let cur_bond = bonds_handle + .get_delta_val(storage, epoch)? + .unwrap_or_default(); + let redelegated_deltas = redelegated_bonds + .at(&epoch) + // Sum of redelegations from any src validator + .collect_map(storage)? + .into_values() + .map(|redeleg| redeleg.into_values().sum()) + .sum(); + debug_assert!( + cur_bond >= redelegated_deltas, + "After unbonding, in epoch {epoch} the bond amount {} must be \ + >= redelegated deltas at pipeline {}.\n\nredelegated_bonds \ + pre: {redel_bonds_pre:#?}\nredelegated_bonds post: \ + {redel_bonds_post:#?},\nmodified_redelegation: \ + {modified_redelegation:#?},\nbonds_to_unbond: \ + {bonds_to_unbond:#?}", + cur_bond.to_string_native(), + redelegated_deltas.to_string_native() ); + } + } - if old_consensus_key.as_ref() == Some(&new_consensus_key) - || old_consensus_key.is_none() - { - vec![ValidatorSetUpdate::Consensus(ConsensusValidator { - consensus_key: new_consensus_key, - bonded_stake: new_stake, - })] - } else { - vec![ - ValidatorSetUpdate::Consensus(ConsensusValidator { - consensus_key: new_consensus_key, - bonded_stake: new_stake, - }), - ValidatorSetUpdate::Deactivated(old_consensus_key.unwrap()), - ] - } - }); - - let prev_consensus_validators = prev_consensus_validator_handle - .iter(storage)? - .map(|validator| { - let ( - NestedSubKey::Data { - key: _prev_stake, - nested_sub_key: _, - }, - address, - ) = validator.unwrap(); - - let new_state = validator_state_handle(&address) - .get(storage, next_epoch, params) - .unwrap(); - - let prev_tm_voting_power = Lazy::new(|| { - let prev_validator_stake = read_validator_stake( - storage, - params, - &address, - current_epoch, - ) - .unwrap(); - into_tm_voting_power( - params.tm_votes_per_token, - prev_validator_stake, - ) - }); + // Tally rewards (only call if this is not the first epoch) + if current_epoch > Epoch::default() { + let mut rewards = token::Amount::zero(); - let old_consensus_key = validator_consensus_key_handle(&address) - .get(storage, current_epoch, params) - .unwrap() - .unwrap(); + let last_claim_epoch = + get_last_reward_claim_epoch(storage, source, validator)? + .unwrap_or_default(); + let rewards_products = validator_rewards_products_handle(validator); - // If the validator is still in the Consensus set, we accounted for - // it in the `new_consensus_validators` iterator above - if matches!(new_state, Some(ValidatorState::Consensus)) { - return vec![]; - } else if params.validator_stake_threshold.is_zero() - && *prev_tm_voting_power == 0 + for (start_epoch, slashed_amount) in &result_slashing.epoch_map { + // Stop collecting rewards at the moment the unbond is initiated + // (right now) + for ep in + Epoch::iter_bounds_inclusive(*start_epoch, current_epoch.prev()) { - // If the new state is not Consensus but its prev voting power - // was 0 and the stake threshold is 0, we can also skip the - // update - tracing::info!( - "skipping validator update, {address} is in consensus set \ - but without voting power" - ); - return vec![]; + // Consider the last epoch when rewards were claimed + if ep < last_claim_epoch { + continue; + } + let rp = + rewards_products.get(storage, &ep)?.unwrap_or_default(); + rewards += rp * (*slashed_amount); } + } - // The remaining validators were previously Consensus but no longer - // are, so they must be deactivated - let consensus_key = validator_consensus_key_handle(&address) - .get(storage, next_epoch, params) - .unwrap() - .unwrap(); - tracing::debug!( - "{address} consensus key {}", - consensus_key.tm_raw_hash() - ); - vec![ValidatorSetUpdate::Deactivated(old_consensus_key)] - }); + // Update the rewards from the current unbonds first + add_rewards_to_counter(storage, source, validator, rewards)?; + } - Ok(new_consensus_validators - .chain(prev_consensus_validators) - .flatten() - .map(f) - .collect()) + Ok(result_slashing) } -/// Find all validators to which a given bond `owner` (or source) has a -/// delegation -pub fn find_delegation_validators( - storage: &S, - owner: &Address, -) -> storage_api::Result> -where - S: StorageRead, -{ - let bonds_prefix = bonds_for_source_prefix(owner); - let mut delegations: HashSet
= HashSet::new(); - - for iter_result in storage_api::iter_prefix_bytes(storage, &bonds_prefix)? { - let (key, _bond_bytes) = iter_result?; - let validator_address = get_validator_address_from_bond(&key) - .ok_or_else(|| { - storage_api::Error::new_const( - "Delegation key should contain validator address.", - ) - })?; - delegations.insert(validator_address); - } - Ok(delegations) +#[derive(Debug, Default, Eq, PartialEq)] +struct FoldRedelegatedBondsResult { + total_redelegated: token::Amount, + total_after_slashing: token::Amount, } -/// Find all validators to which a given bond `owner` (or source) has a -/// delegation with the amount -pub fn find_delegations( +/// Iterates over a `redelegated_unbonds` and computes the both the sum of all +/// redelegated tokens and how much is left after applying all relevant slashes. +// `def foldAndSlashRedelegatedBondsMap` +fn fold_and_slash_redelegated_bonds( storage: &S, - owner: &Address, - epoch: &Epoch, -) -> storage_api::Result> + params: &OwnedPosParams, + redelegated_unbonds: &EagerRedelegatedBondsMap, + start_epoch: Epoch, + list_slashes: &[Slash], + slash_epoch_filter: impl Fn(Epoch) -> bool, +) -> FoldRedelegatedBondsResult where S: StorageRead, { - let bonds_prefix = bonds_for_source_prefix(owner); - let params = read_pos_params(storage)?; - let mut delegations: HashMap = HashMap::new(); - - for iter_result in storage_api::iter_prefix_bytes(storage, &bonds_prefix)? { - let (key, _bond_bytes) = iter_result?; - let validator_address = get_validator_address_from_bond(&key) - .ok_or_else(|| { - storage_api::Error::new_const( - "Delegation key should contain validator address.", - ) - })?; - let deltas_sum = bond_handle(owner, &validator_address) - .get_sum(storage, *epoch, ¶ms)? - .unwrap_or_default(); - delegations.insert(validator_address, deltas_sum); + let mut result = FoldRedelegatedBondsResult::default(); + for (src_validator, bonds_map) in redelegated_unbonds { + for (bond_start, &change) in bonds_map { + // Merge the two lists of slashes + let mut merged: Vec = + // Look-up slashes for this validator ... + validator_slashes_handle(src_validator) + .iter(storage) + .unwrap() + .map(Result::unwrap) + .filter(|slash| { + params.in_redelegation_slashing_window( + slash.epoch, + params.redelegation_start_epoch_from_end( + start_epoch, + ), + start_epoch, + ) && *bond_start <= slash.epoch + && slash_epoch_filter(slash.epoch) + }) + // ... and add `list_slashes` + .chain(list_slashes.iter().cloned()) + .collect(); + + // Sort slashes by epoch + merged.sort_by(|s1, s2| s1.epoch.partial_cmp(&s2.epoch).unwrap()); + + result.total_redelegated += change; + result.total_after_slashing += + apply_list_slashes(params, &merged, change); + } } - Ok(delegations) + result } -/// Find if the given source address has any bonds. -pub fn has_bonds(storage: &S, source: &Address) -> storage_api::Result -where - S: StorageRead, -{ - let max_epoch = Epoch(u64::MAX); - let delegations = find_delegations(storage, source, &max_epoch)?; - Ok(!delegations - .values() - .cloned() - .sum::() - .is_zero()) +/// Epochs for full and partial unbonds. +#[derive(Debug, Default)] +struct BondsForRemovalRes { + /// Full unbond epochs + pub epochs: BTreeSet, + /// Partial unbond epoch associated with the new bond amount + pub new_entry: Option<(Epoch, token::Amount)>, } -/// Find PoS slashes applied to a validator, if any -pub fn find_validator_slashes( +/// In decreasing epoch order, decrement the non-zero bond amount entries until +/// the full `amount` has been removed. Returns a `BondsForRemovalRes` object +/// that contains the epochs for which the full bond amount is removed and +/// additionally information for the one epoch whose bond amount is partially +/// removed, if any. +fn find_bonds_to_remove( storage: &S, - validator: &Address, -) -> storage_api::Result> + bonds_handle: &LazyMap, + amount: token::Amount, +) -> storage_api::Result where S: StorageRead, { - validator_slashes_handle(validator).iter(storage)?.collect() + #[allow(clippy::needless_collect)] + let bonds: Vec> = bonds_handle.iter(storage)?.collect(); + + let mut bonds_for_removal = BondsForRemovalRes::default(); + let mut remaining = amount; + + for bond in bonds.into_iter().rev() { + let (bond_epoch, bond_amount) = bond?; + let to_unbond = cmp::min(bond_amount, remaining); + if to_unbond == bond_amount { + bonds_for_removal.epochs.insert(bond_epoch); + } else { + bonds_for_removal.new_entry = + Some((bond_epoch, bond_amount - to_unbond)); + } + remaining -= to_unbond; + if remaining.is_zero() { + break; + } + } + Ok(bonds_for_removal) } -/// Find raw bond deltas for the given source and validator address. -pub fn find_bonds( - storage: &S, - source: &Address, - validator: &Address, -) -> storage_api::Result> -where - S: StorageRead, -{ - bond_handle(source, validator) - .get_data_handler() - .iter(storage)? - .collect() +#[derive(Debug, Default, PartialEq, Eq)] +struct ModifiedRedelegation { + epoch: Option, + validators_to_remove: BTreeSet
, + validator_to_modify: Option
, + epochs_to_remove: BTreeSet, + epoch_to_modify: Option, + new_amount: Option, } -/// Find raw unbond deltas for the given source and validator address. -pub fn find_unbonds( +/// Used in `fn unbond_tokens` to compute the modified state of a redelegation +/// if redelegated tokens are being unbonded. +fn compute_modified_redelegation( storage: &S, - source: &Address, - validator: &Address, -) -> storage_api::Result> + redelegated_bonds: &RedelegatedTokens, + start_epoch: Epoch, + amount_to_unbond: token::Amount, +) -> storage_api::Result where S: StorageRead, { - unbond_handle(source, validator) - .iter(storage)? - .map(|next_result| { - let ( - NestedSubKey::Data { - key: start_epoch, - nested_sub_key: SubKey::Data(withdraw_epoch), - }, - amount, - ) = next_result?; - Ok(((start_epoch, withdraw_epoch), amount)) - }) - .collect() -} + let mut modified_redelegation = ModifiedRedelegation::default(); + + let mut src_validators = BTreeSet::
::new(); + let mut total_redelegated = token::Amount::zero(); + for rb in redelegated_bonds.iter(storage)? { + let ( + NestedSubKey::Data { + key: src_validator, + nested_sub_key: _, + }, + amount, + ) = rb?; + total_redelegated += amount; + src_validators.insert(src_validator); + } + + modified_redelegation.epoch = Some(start_epoch); -/// Collect the details of all bonds and unbonds that match the source and -/// validator arguments. If either source or validator is `None`, then grab the -/// information for all sources or validators, respectively. -pub fn bonds_and_unbonds( - storage: &S, - source: Option
, - validator: Option
, -) -> storage_api::Result -where - S: StorageRead, -{ - let params = read_pos_params(storage)?; + // If the total amount of redelegated bonds is less than the target amount, + // then all redelegated bonds must be unbonded. + if total_redelegated <= amount_to_unbond { + return Ok(modified_redelegation); + } - match (source.clone(), validator.clone()) { - (Some(source), Some(validator)) => { - find_bonds_and_unbonds_details(storage, ¶ms, source, validator) + let mut remaining = amount_to_unbond; + for src_validator in src_validators.into_iter() { + if remaining.is_zero() { + break; } - _ => { - get_multiple_bonds_and_unbonds(storage, ¶ms, source, validator) + let rbonds = redelegated_bonds.at(&src_validator); + let total_src_val_amount = rbonds + .iter(storage)? + .map(|res| { + let (_, amount) = res?; + Ok(amount) + }) + .sum::>()?; + + // TODO: move this into the `if total_redelegated <= remaining` branch + // below, then we don't have to remove it in `fn + // update_redelegated_bonds` when `validator_to_modify` is Some (and + // avoid `modified_redelegation.validators_to_remove.clone()`). + // It affects assumption 2. in `fn compute_new_redelegated_unbonds`, but + // that looks trivial to change. + // NOTE: not sure if this TODO is still relevant... + modified_redelegation + .validators_to_remove + .insert(src_validator.clone()); + if total_src_val_amount <= remaining { + remaining -= total_src_val_amount; + } else { + let bonds_to_remove = + find_bonds_to_remove(storage, &rbonds, remaining)?; + + remaining = token::Amount::zero(); + + // NOTE: When there are multiple `src_validators` from which we're + // unbonding, `validator_to_modify` cannot get overriden, because + // only one of them can be a partial unbond (`new_entry` + // is partial unbond) + if let Some((bond_epoch, new_bond_amount)) = + bonds_to_remove.new_entry + { + modified_redelegation.validator_to_modify = Some(src_validator); + modified_redelegation.epochs_to_remove = { + let mut epochs = bonds_to_remove.epochs; + // TODO: remove this insertion then we don't have to remove + // it again in `fn update_redelegated_bonds` + // when `epoch_to_modify` is Some (and avoid + // `modified_redelegation.epochs_to_remove.clone`) + // It affects assumption 3. in `fn + // compute_new_redelegated_unbonds`, but that also looks + // trivial to change. + epochs.insert(bond_epoch); + epochs + }; + modified_redelegation.epoch_to_modify = Some(bond_epoch); + modified_redelegation.new_amount = Some(new_bond_amount); + } else { + modified_redelegation.validator_to_modify = Some(src_validator); + modified_redelegation.epochs_to_remove = bonds_to_remove.epochs; + } } } + Ok(modified_redelegation) } -/// Collect the details of all of the enqueued slashes to be processed in future -/// epochs into a nested map -pub fn find_all_enqueued_slashes( - storage: &S, - epoch: Epoch, -) -> storage_api::Result>>> +fn update_redelegated_bonds( + storage: &mut S, + redelegated_bonds: &RedelegatedTokens, + modified_redelegation: &ModifiedRedelegation, +) -> storage_api::Result<()> where - S: StorageRead, + S: StorageRead + StorageWrite, { - let mut enqueued = HashMap::>>::new(); - for res in enqueued_slashes_handle().get_data_handler().iter(storage)? { - let ( - NestedSubKey::Data { - key: processing_epoch, - nested_sub_key: - NestedSubKey::Data { - key: address, - nested_sub_key: _, - }, - }, - slash, - ) = res?; - if processing_epoch <= epoch { - continue; - } + if let Some(val_to_modify) = &modified_redelegation.validator_to_modify { + let mut updated_vals_to_remove = + modified_redelegation.validators_to_remove.clone(); + updated_vals_to_remove.remove(val_to_modify); - let slashes = enqueued - .entry(address) - .or_default() - .entry(processing_epoch) - .or_default(); - slashes.push(slash); - } - Ok(enqueued) -} + // Remove the updated_vals_to_remove keys from the + // redelegated_bonds map + for val in &updated_vals_to_remove { + redelegated_bonds.remove_all(storage, val)?; + } -/// Find all slashes and the associated validators in the PoS system -pub fn find_all_slashes( - storage: &S, -) -> storage_api::Result>> -where - S: StorageRead, -{ - let mut slashes: HashMap> = HashMap::new(); - let slashes_iter = storage_api::iter_prefix_bytes( - storage, - &slashes_prefix(), - )? - .filter_map(|result| { - if let Ok((key, val_bytes)) = result { - if let Some(validator) = is_validator_slashes_key(&key) { - let slash: Slash = - BorshDeserialize::try_from_slice(&val_bytes).ok()?; - return Some((validator, slash)); + if let Some(epoch_to_modify) = modified_redelegation.epoch_to_modify { + let mut updated_epochs_to_remove = + modified_redelegation.epochs_to_remove.clone(); + updated_epochs_to_remove.remove(&epoch_to_modify); + let val_bonds_to_modify = redelegated_bonds.at(val_to_modify); + for epoch in updated_epochs_to_remove { + val_bonds_to_modify.remove(storage, &epoch)?; + } + val_bonds_to_modify.insert( + storage, + epoch_to_modify, + modified_redelegation.new_amount.unwrap(), + )?; + } else { + // Then remove to epochs_to_remove from the redelegated bonds of the + // val_to_modify + let val_bonds_to_modify = redelegated_bonds.at(val_to_modify); + for epoch in &modified_redelegation.epochs_to_remove { + val_bonds_to_modify.remove(storage, epoch)?; } } - None - }); - - slashes_iter.for_each(|(address, slash)| match slashes.get(&address) { - Some(vec) => { - let mut vec = vec.clone(); - vec.push(slash); - slashes.insert(address, vec); - } - None => { - slashes.insert(address, vec![slash]); + } else { + // Remove all validators in modified_redelegation.validators_to_remove + // from redelegated_bonds + for val in &modified_redelegation.validators_to_remove { + redelegated_bonds.remove_all(storage, val)?; } - }); - Ok(slashes) + } + Ok(()) } -fn get_multiple_bonds_and_unbonds( +/// Temp helper type to match quint model. +/// Result of `compute_new_redelegated_unbonds` that contains a map of +/// redelegated unbonds. +/// The map keys from outside in are: +/// +/// - redelegation end epoch where redeleg stops contributing to src validator +/// - src validator address +/// - src bond start epoch where it started contributing to src validator +type EagerRedelegatedUnbonds = BTreeMap; + +/// Computes a map of redelegated unbonds from a set of redelegated bonds. +/// +/// - `redelegated_bonds` - a map of redelegated bonds from epoch to +/// `RedelegatedTokens`. +/// - `epochs_to_remove` - a set of epochs that indicate the set of epochs +/// unbonded. +/// - `modified` record that represents a redelegated bond that it is only +/// partially unbonded. +/// +/// The function assumes that: +/// +/// 1. `modified.epoch` is not in the `epochs_to_remove` set. +/// 2. `modified.validator_to_modify` is in `modified.vals_to_remove`. +/// 3. `modified.epoch_to_modify` is in in `modified.epochs_to_remove`. +// `def computeNewRedelegatedUnbonds` from Quint +fn compute_new_redelegated_unbonds( storage: &S, - params: &PosParams, - source: Option
, - validator: Option
, -) -> storage_api::Result + redelegated_bonds: &RedelegatedBondsOrUnbonds, + epochs_to_remove: &BTreeSet, + modified: &ModifiedRedelegation, +) -> storage_api::Result where - S: StorageRead, + S: StorageRead + StorageWrite, { + let unbonded_epochs = if let Some(epoch) = modified.epoch { + debug_assert!( + !epochs_to_remove.contains(&epoch), + "1. assumption in `fn compute_new_redelegated_unbonds` doesn't \ + hold" + ); + let mut epochs = epochs_to_remove.clone(); + epochs.insert(epoch); + epochs + .iter() + .cloned() + .filter(|e| redelegated_bonds.contains(storage, e).unwrap()) + .collect::>() + } else { + epochs_to_remove + .iter() + .cloned() + .filter(|e| redelegated_bonds.contains(storage, e).unwrap()) + .collect::>() + }; debug_assert!( - source.is_none() || validator.is_none(), - "Use `find_bonds_and_unbonds_details` when full bond ID is known" + modified + .validator_to_modify + .as_ref() + .map(|validator| modified.validators_to_remove.contains(validator)) + .unwrap_or(true), + "2. assumption in `fn compute_new_redelegated_unbonds` doesn't hold" + ); + debug_assert!( + modified + .epoch_to_modify + .as_ref() + .map(|epoch| modified.epochs_to_remove.contains(epoch)) + .unwrap_or(true), + "3. assumption in `fn compute_new_redelegated_unbonds` doesn't hold" ); - let mut slashes_cache = HashMap::>::new(); - // Applied slashes grouped by validator address - let mut applied_slashes = HashMap::>::new(); - - // TODO: if validator is `Some`, look-up all its bond owners (including - // self-bond, if any) first - let prefix = match source.as_ref() { - Some(source) => bonds_for_source_prefix(source), - None => bonds_prefix(), - }; - // We have to iterate raw bytes, cause the epoched data `last_update` field - // gets matched here too - let mut raw_bonds = storage_api::iter_prefix_bytes(storage, &prefix)? - .filter_map(|result| { - if let Ok((key, val_bytes)) = result { - if let Some((bond_id, start)) = is_bond_key(&key) { - if source.is_some() - && source.as_ref().unwrap() != &bond_id.source - { - return None; - } - if validator.is_some() - && validator.as_ref().unwrap() != &bond_id.validator - { - return None; - } - let change: token::Amount = - BorshDeserialize::try_from_slice(&val_bytes).ok()?; - if change.is_zero() { - return None; - } - return Some((bond_id, start, change)); + // quint `newRedelegatedUnbonds` returned from + // `computeNewRedelegatedUnbonds` + let new_redelegated_unbonds: EagerRedelegatedUnbonds = unbonded_epochs + .into_iter() + .map(|start| { + let mut rbonds = EagerRedelegatedBondsMap::default(); + if modified + .epoch + .map(|redelegation_epoch| start != redelegation_epoch) + .unwrap_or(true) + || modified.validators_to_remove.is_empty() + { + for res in redelegated_bonds.at(&start).iter(storage).unwrap() { + let ( + NestedSubKey::Data { + key: validator, + nested_sub_key: SubKey::Data(epoch), + }, + amount, + ) = res.unwrap(); + rbonds + .entry(validator.clone()) + .or_default() + .insert(epoch, amount); } - } - None - }); - - let prefix = match source.as_ref() { - Some(source) => unbonds_for_source_prefix(source), - None => unbonds_prefix(), - }; - let mut raw_unbonds = storage_api::iter_prefix_bytes(storage, &prefix)? - .filter_map(|result| { - if let Ok((key, val_bytes)) = result { - if let Some((bond_id, start, withdraw)) = is_unbond_key(&key) { - if source.is_some() - && source.as_ref().unwrap() != &bond_id.source - { - return None; - } - if validator.is_some() - && validator.as_ref().unwrap() != &bond_id.validator + (start, rbonds) + } else { + for src_validator in &modified.validators_to_remove { + if modified + .validator_to_modify + .as_ref() + .map(|validator| src_validator != validator) + .unwrap_or(true) { - return None; - } - match (source.clone(), validator.clone()) { - (None, Some(validator)) => { - if bond_id.validator != validator { - return None; - } + let raw_bonds = + redelegated_bonds.at(&start).at(src_validator); + for res in raw_bonds.iter(storage).unwrap() { + let (bond_epoch, bond_amount) = res.unwrap(); + rbonds + .entry(src_validator.clone()) + .or_default() + .insert(bond_epoch, bond_amount); } - (Some(owner), None) => { - if owner != bond_id.source { - return None; + } else { + for bond_start in &modified.epochs_to_remove { + let cur_redel_bond_amount = redelegated_bonds + .at(&start) + .at(src_validator) + .get(storage, bond_start) + .unwrap() + .unwrap_or_default(); + let raw_bonds = rbonds + .entry(src_validator.clone()) + .or_default(); + if modified + .epoch_to_modify + .as_ref() + .map(|epoch| bond_start != epoch) + .unwrap_or(true) + { + raw_bonds + .insert(*bond_start, cur_redel_bond_amount); + } else { + raw_bonds.insert( + *bond_start, + cur_redel_bond_amount + - modified + .new_amount + // Safe unwrap - it shouldn't + // get to + // this if it's None + .unwrap(), + ); } } - _ => {} } - let amount: token::Amount = - BorshDeserialize::try_from_slice(&val_bytes).ok()?; - return Some((bond_id, start, withdraw, amount)); } + (start, rbonds) } - None - }); - - let mut bonds_and_unbonds = - HashMap::, Vec)>::new(); - - raw_bonds.try_for_each(|(bond_id, start, change)| { - if !slashes_cache.contains_key(&bond_id.validator) { - let slashes = find_validator_slashes(storage, &bond_id.validator)?; - slashes_cache.insert(bond_id.validator.clone(), slashes); - } - let slashes = slashes_cache - .get(&bond_id.validator) - .expect("We must have inserted it if it's not cached already"); - let validator = bond_id.validator.clone(); - let (bonds, _unbonds) = bonds_and_unbonds.entry(bond_id).or_default(); - bonds.push(make_bond_details( - params, - &validator, - change, - start, - slashes, - &mut applied_slashes, - )); - Ok::<_, storage_api::Error>(()) - })?; - - raw_unbonds.try_for_each(|(bond_id, start, withdraw, amount)| { - if !slashes_cache.contains_key(&bond_id.validator) { - let slashes = find_validator_slashes(storage, &bond_id.validator)?; - slashes_cache.insert(bond_id.validator.clone(), slashes); - } - let slashes = slashes_cache - .get(&bond_id.validator) - .expect("We must have inserted it if it's not cached already"); - let validator = bond_id.validator.clone(); - let (_bonds, unbonds) = bonds_and_unbonds.entry(bond_id).or_default(); - unbonds.push(make_unbond_details( - params, - &validator, - amount, - (start, withdraw), - slashes, - &mut applied_slashes, - )); - Ok::<_, storage_api::Error>(()) - })?; - - Ok(bonds_and_unbonds - .into_iter() - .map(|(bond_id, (bonds, unbonds))| { - let details = BondsAndUnbondsDetail { - bonds, - unbonds, - slashes: applied_slashes - .get(&bond_id.validator) - .cloned() - .unwrap_or_default(), - }; - (bond_id, details) - }) - .collect()) -} - -fn find_bonds_and_unbonds_details( - storage: &S, - params: &PosParams, - source: Address, - validator: Address, -) -> storage_api::Result -where - S: StorageRead, -{ - let slashes = find_validator_slashes(storage, &validator)?; - let mut applied_slashes = HashMap::>::new(); - - let bonds = find_bonds(storage, &source, &validator)? - .into_iter() - .filter(|(_start, amount)| *amount > token::Amount::zero()) - .map(|(start, amount)| { - make_bond_details( - params, - &validator, - amount, - start, - &slashes, - &mut applied_slashes, - ) }) .collect(); - let unbonds = find_unbonds(storage, &source, &validator)? - .into_iter() - .map(|(epoch_range, change)| { - make_unbond_details( - params, - &validator, - change, - epoch_range, - &slashes, - &mut applied_slashes, - ) - }) - .collect(); - - let details = BondsAndUnbondsDetail { - bonds, - unbonds, - slashes: applied_slashes.get(&validator).cloned().unwrap_or_default(), - }; - let bond_id = BondId { source, validator }; - Ok(HashMap::from_iter([(bond_id, details)])) -} - -fn make_bond_details( - params: &PosParams, - validator: &Address, - deltas_sum: token::Amount, - start: Epoch, - slashes: &[Slash], - applied_slashes: &mut HashMap>, -) -> BondDetails { - let prev_applied_slashes = applied_slashes - .clone() - .get(validator) - .cloned() - .unwrap_or_default(); - - let mut slash_rates_by_epoch = BTreeMap::::new(); - - let validator_slashes = - applied_slashes.entry(validator.clone()).or_default(); - for slash in slashes { - if slash.epoch >= start { - let cur_rate = slash_rates_by_epoch.entry(slash.epoch).or_default(); - *cur_rate = cmp::min(Dec::one(), *cur_rate + slash.rate); - - if !prev_applied_slashes.iter().any(|s| s == slash) { - validator_slashes.push(slash.clone()); - } - } - } - - let slashed_amount = if slash_rates_by_epoch.is_empty() { - None - } else { - let amount_after_slashing = - get_slashed_amount(params, deltas_sum, &slash_rates_by_epoch) - .unwrap(); - Some(deltas_sum - amount_after_slashing) - }; - - BondDetails { - start, - amount: deltas_sum, - slashed_amount, - } + Ok(new_redelegated_unbonds) } -fn make_unbond_details( - params: &PosParams, - validator: &Address, - amount: token::Amount, - (start, withdraw): (Epoch, Epoch), - slashes: &[Slash], - applied_slashes: &mut HashMap>, -) -> UnbondDetails { - let prev_applied_slashes = applied_slashes - .clone() - .get(validator) - .cloned() - .unwrap_or_default(); - let mut slash_rates_by_epoch = BTreeMap::::new(); - - let validator_slashes = - applied_slashes.entry(validator.clone()).or_default(); - for slash in slashes { - if slash.epoch >= start - && slash.epoch - < withdraw - .checked_sub( - params.unbonding_len - + params.cubic_slashing_window_length, - ) - .unwrap_or_default() - { - let cur_rate = slash_rates_by_epoch.entry(slash.epoch).or_default(); - *cur_rate = cmp::min(Dec::one(), *cur_rate + slash.rate); - - if !prev_applied_slashes.iter().any(|s| s == slash) { - validator_slashes.push(slash.clone()); - } - } - } - - let slashed_amount = if slash_rates_by_epoch.is_empty() { - None - } else { - let amount_after_slashing = - get_slashed_amount(params, amount, &slash_rates_by_epoch).unwrap(); - Some(amount - amount_after_slashing) - }; - - UnbondDetails { - start, - withdraw, - amount, - slashed_amount, - } +/// Arguments to [`become_validator`]. +pub struct BecomeValidator<'a> { + /// Proof-of-stake parameters. + pub params: &'a PosParams, + /// The validator's address. + pub address: &'a Address, + /// The validator's consensus key, used by Tendermint. + pub consensus_key: &'a common::PublicKey, + /// The validator's protocol key. + pub protocol_key: &'a common::PublicKey, + /// The validator's Ethereum bridge cold key. + pub eth_cold_key: &'a common::PublicKey, + /// The validator's Ethereum bridge hot key. + pub eth_hot_key: &'a common::PublicKey, + /// The numeric value of the current epoch. + pub current_epoch: Epoch, + /// Commission rate. + pub commission_rate: Dec, + /// Max commission rate change. + pub max_commission_rate_change: Dec, + /// Validator metadata + pub metadata: ValidatorMetaData, + /// Optional offset to use instead of pipeline offset + pub offset_opt: Option, } -/// Tally a running sum of the fraction of rewards owed to each validator in -/// the consensus set. This is used to keep track of the rewards due to each -/// consensus validator over the lifetime of an epoch. -pub fn log_block_rewards( +/// Initialize data for a new validator. +pub fn become_validator( storage: &mut S, - epoch: impl Into, - proposer_address: &Address, - votes: Vec, + args: BecomeValidator<'_>, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, { - // The votes correspond to the last committed block (n-1 if we are - // finalizing block n) - - let epoch: Epoch = epoch.into(); - let params = read_pos_params(storage)?; - let consensus_validators = consensus_validator_set_handle().at(&epoch); - - // Get total stake of the consensus validator set - let total_consensus_stake = - get_total_consensus_stake(storage, epoch, ¶ms)?; - - // Get set of signing validator addresses and the combined stake of - // these signers - let mut signer_set: HashSet
= HashSet::new(); - let mut total_signing_stake = token::Amount::zero(); - for VoteInfo { - validator_address, - validator_vp, - } in votes - { - if validator_vp == 0 { - continue; - } - // Ensure that the validator is not currently jailed or other - let state = validator_state_handle(&validator_address) - .get(storage, epoch, ¶ms)?; - if state != Some(ValidatorState::Consensus) { - return Err(InflationError::ExpectedValidatorInConsensus( - validator_address, - state, - )) - .into_storage_result(); - } - - let stake_from_deltas = - read_validator_stake(storage, ¶ms, &validator_address, epoch)?; - - // Ensure TM stake updates properly with a debug_assert - if cfg!(debug_assertions) { - debug_assert_eq!( - into_tm_voting_power( - params.tm_votes_per_token, - stake_from_deltas, - ), - i64::try_from(validator_vp).unwrap_or_default(), - ); - } + let BecomeValidator { + params, + address, + consensus_key, + protocol_key, + eth_cold_key, + eth_hot_key, + current_epoch, + commission_rate, + max_commission_rate_change, + metadata, + offset_opt, + } = args; + let offset = offset_opt.unwrap_or(params.pipeline_len); - signer_set.insert(validator_address); - total_signing_stake += stake_from_deltas; + if !address.is_established() { + return Err(storage_api::Error::new_const( + "The given address {address} is not established. Only an \ + established address can become a validator.", + )); } - // Get the block rewards coefficients (proposing, signing/voting, - // consensus set status) - let rewards_calculator = PosRewardsCalculator { - proposer_reward: params.block_proposer_reward, - signer_reward: params.block_vote_reward, - signing_stake: total_signing_stake, - total_stake: total_consensus_stake, - }; - let coeffs = rewards_calculator - .get_reward_coeffs() - .map_err(InflationError::Rewards) - .into_storage_result()?; - tracing::debug!( - "PoS rewards coefficients {coeffs:?}, inputs: {rewards_calculator:?}." - ); - - // tracing::debug!( - // "TOTAL SIGNING STAKE (LOGGING BLOCK REWARDS) = {}", - // signing_stake - // ); - - // Compute the fractional block rewards for each consensus validator and - // update the reward accumulators - let consensus_stake_unscaled: Dec = total_consensus_stake.into(); - let signing_stake_unscaled: Dec = total_signing_stake.into(); - let mut values: HashMap = HashMap::new(); - for validator in consensus_validators.iter(storage)? { - let ( - NestedSubKey::Data { - key: stake, - nested_sub_key: _, - }, - address, - ) = validator?; - - if stake.is_zero() { - continue; - } - - let mut rewards_frac = Dec::zero(); - let stake_unscaled: Dec = stake.into(); - // tracing::debug!( - // "NAMADA VALIDATOR STAKE (LOGGING BLOCK REWARDS) OF EPOCH {} = - // {}", epoch, stake - // ); - - // Proposer reward - if address == *proposer_address { - rewards_frac += coeffs.proposer_coeff; - } - // Signer reward - if signer_set.contains(&address) { - let signing_frac = stake_unscaled / signing_stake_unscaled; - rewards_frac += coeffs.signer_coeff * signing_frac; - } - // Consensus validator reward - rewards_frac += coeffs.active_val_coeff - * (stake_unscaled / consensus_stake_unscaled); - - // To be added to the rewards accumulator - values.insert(address, rewards_frac); - } - for (address, value) in values.into_iter() { - // Update the rewards accumulator - rewards_accumulator_handle().update(storage, address, |prev| { - prev.unwrap_or_default() + value - })?; + if is_validator(storage, address)? { + return Err(storage_api::Error::new_const( + "The given address is already a validator", + )); } - Ok(()) -} + // If the address is not yet a validator, it cannot have self-bonds, but it + // may have delegations. + if has_bonds(storage, address)? { + return Err(storage_api::Error::new_const( + "The given address has delegations and therefore cannot become a \ + validator. Unbond first.", + )); + } -#[derive(Clone, Debug)] -struct Rewards { - product: Dec, - commissions: token::Amount, -} + // This will fail if the key is already being used + try_insert_consensus_key(storage, consensus_key)?; -/// Update validator and delegators rewards products and mint the inflation -/// tokens into the PoS account. -/// Any left-over inflation tokens from rounding error of the sum of the -/// rewards is given to the governance address. -pub fn update_rewards_products_and_mint_inflation( - storage: &mut S, - params: &PosParams, - last_epoch: Epoch, - num_blocks_in_last_epoch: u64, - inflation: token::Amount, - staking_token: &Address, -) -> storage_api::Result<()> -where - S: StorageRead + StorageWrite, -{ - // Read the rewards accumulator and calculate the new rewards products - // for the previous epoch - let mut reward_tokens_remaining = inflation; - let mut new_rewards_products: HashMap = HashMap::new(); - let mut accumulators_sum = Dec::zero(); - for acc in rewards_accumulator_handle().iter(storage)? { - let (validator, value) = acc?; - accumulators_sum += value; - - // Get reward token amount for this validator - let fractional_claim = value / num_blocks_in_last_epoch; - let reward_tokens = fractional_claim * inflation; - - // Get validator stake at the last epoch - let stake = Dec::from(read_validator_stake( - storage, params, &validator, last_epoch, - )?); - - let commission_rate = validator_commission_rate_handle(&validator) - .get(storage, last_epoch, params)? - .expect("Should be able to find validator commission rate"); - - // Calculate the reward product from the whole validator stake and take - // out the commissions. Because we're using the whole stake to work with - // a single product, we're also taking out commission on validator's - // self-bonds, but it is then included in the rewards claimable by the - // validator so they get it back. - let product = - (Dec::one() - commission_rate) * Dec::from(reward_tokens) / stake; - - // Tally the commission tokens earned by the validator. - // TODO: think abt Dec rounding and if `new_product` should be used - // instead of `reward_tokens` - let commissions = commission_rate * reward_tokens; - - new_rewards_products.insert( - validator, - Rewards { - product, - commissions, - }, - ); + let pipeline_epoch = current_epoch + offset; + validator_addresses_handle() + .at(&pipeline_epoch) + .insert(storage, address.clone())?; - reward_tokens_remaining -= reward_tokens; - } - for ( - validator, - Rewards { - product, - commissions, - }, - ) in new_rewards_products - { - validator_rewards_products_handle(&validator) - .insert(storage, last_epoch, product)?; - // The commissions belong to the validator - add_rewards_to_counter(storage, &validator, &validator, commissions)?; - } + // Non-epoched validator data + write_validator_address_raw_hash(storage, address, consensus_key)?; + write_validator_max_commission_rate_change( + storage, + address, + max_commission_rate_change, + )?; + write_validator_metadata(storage, address, &metadata)?; - // Mint tokens to the PoS account for the last epoch's inflation - let pos_reward_tokens = inflation - reward_tokens_remaining; - tracing::info!( - "Minting tokens for PoS rewards distribution into the PoS account. \ - Amount: {}. Total inflation: {}, number of blocks in the last epoch: \ - {num_blocks_in_last_epoch}, reward accumulators sum: \ - {accumulators_sum}.", - pos_reward_tokens.to_string_native(), - inflation.to_string_native(), - ); - token::credit_tokens( + // Epoched validator data + validator_consensus_key_handle(address).set( + storage, + consensus_key.clone(), + current_epoch, + offset, + )?; + validator_protocol_key_handle(address).set( + storage, + protocol_key.clone(), + current_epoch, + offset, + )?; + validator_eth_hot_key_handle(address).set( + storage, + eth_hot_key.clone(), + current_epoch, + offset, + )?; + validator_eth_cold_key_handle(address).set( + storage, + eth_cold_key.clone(), + current_epoch, + offset, + )?; + validator_commission_rate_handle(address).set( storage, - staking_token, - &address::POS, - pos_reward_tokens, + commission_rate, + current_epoch, + offset, + )?; + validator_deltas_handle(address).set( + storage, + token::Change::zero(), + current_epoch, + offset, )?; - if reward_tokens_remaining > token::Amount::zero() { - tracing::info!( - "Minting tokens remaining from PoS rewards distribution into the \ - Governance account. Amount: {}.", - reward_tokens_remaining.to_string_native() - ); - token::credit_tokens( - storage, - staking_token, - &address::GOV, - reward_tokens_remaining, - )?; - } + // The validator's stake at initialization is 0, so its state is immediately + // below-threshold + validator_state_handle(address).set( + storage, + ValidatorState::BelowThreshold, + current_epoch, + offset, + )?; - // Clear validator rewards accumulators - storage.delete_prefix( - // The prefix of `rewards_accumulator_handle` - &storage::consensus_validator_rewards_accumulator_key(), + insert_validator_into_validator_set( + storage, + params, + address, + token::Amount::zero(), + current_epoch, + offset, )?; Ok(()) } -/// Calculate the cubic slashing rate using all slashes within a window around -/// the given infraction epoch. There is no cap on the rate applied within this -/// function. -pub fn compute_cubic_slash_rate( - storage: &S, - params: &PosParams, - infraction_epoch: Epoch, -) -> storage_api::Result -where - S: StorageRead, -{ - tracing::debug!( - "Computing the cubic slash rate for infraction epoch \ - {infraction_epoch}." - ); - let mut sum_vp_fraction = Dec::zero(); - let (start_epoch, end_epoch) = - params.cubic_slash_epoch_window(infraction_epoch); - - for epoch in Epoch::iter_bounds_inclusive(start_epoch, end_epoch) { - let consensus_stake = - Dec::from(get_total_consensus_stake(storage, epoch, params)?); - tracing::debug!( - "Total consensus stake in epoch {}: {}", - epoch, - consensus_stake - ); - let processing_epoch = epoch + params.slash_processing_epoch_offset(); - let slashes = enqueued_slashes_handle().at(&processing_epoch); - let infracting_stake = slashes.iter(storage)?.fold( - Ok(Dec::zero()), - |acc: storage_api::Result, res| { - let acc = acc?; - let ( - NestedSubKey::Data { - key: validator, - nested_sub_key: _, - }, - _slash, - ) = res?; - - let validator_stake = - read_validator_stake(storage, params, &validator, epoch)?; - // tracing::debug!("Val {} stake: {}", &validator, - // validator_stake); - - Ok(acc + Dec::from(validator_stake)) - }, - )?; - sum_vp_fraction += infracting_stake / consensus_stake; - } - let cubic_rate = - Dec::new(9, 0).unwrap() * sum_vp_fraction * sum_vp_fraction; - tracing::debug!("Cubic slash rate: {}", cubic_rate); - Ok(cubic_rate) -} - -/// Record a slash for a misbehavior that has been received from Tendermint and -/// then jail the validator, removing it from the validator set. The slash rate -/// will be computed at a later epoch. -#[allow(clippy::too_many_arguments)] -pub fn slash( +/// Consensus key change for a validator +pub fn change_consensus_key( storage: &mut S, - params: &PosParams, - current_epoch: Epoch, - evidence_epoch: Epoch, - evidence_block_height: impl Into, - slash_type: SlashType, validator: &Address, - validator_set_update_epoch: Epoch, + consensus_key: &common::PublicKey, + current_epoch: Epoch, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, { - let evidence_block_height: u64 = evidence_block_height.into(); - let slash = Slash { - epoch: evidence_epoch, - block_height: evidence_block_height, - r#type: slash_type, - rate: Dec::zero(), // Let the rate be 0 initially before processing - }; - // Need `+1` because we process at the beginning of a new epoch - let processing_epoch = - evidence_epoch + params.slash_processing_epoch_offset(); - - // Add the slash to the list of enqueued slashes to be processed at a later - // epoch - enqueued_slashes_handle() - .get_data_handler() - .at(&processing_epoch) - .at(validator) - .push(storage, slash)?; - - // Update the most recent slash (infraction) epoch for the validator - let last_slash_epoch = read_validator_last_slash_epoch(storage, validator)?; - if last_slash_epoch.is_none() - || evidence_epoch.0 > last_slash_epoch.unwrap_or_default().0 - { - write_validator_last_slash_epoch(storage, validator, evidence_epoch)?; + tracing::debug!("Changing consensus key for validator {}", validator); + + // Require that the new consensus key is an Ed25519 key + match consensus_key { + common::PublicKey::Ed25519(_) => {} + common::PublicKey::Secp256k1(_) => { + return Err(ConsensusKeyChangeError::MustBeEd25519.into()); + } } - // Jail the validator and update validator sets - jail_validator( + // Check for uniqueness of the consensus key + try_insert_consensus_key(storage, consensus_key)?; + + // Set the new consensus key at the pipeline epoch + let params = read_pos_params(storage)?; + validator_consensus_key_handle(validator).set( storage, - params, - validator, + consensus_key.clone(), current_epoch, - validator_set_update_epoch, + params.pipeline_len, )?; - // No other actions are performed here until the epoch in which the slash is - // processed. + // Write validator's new raw hash + write_validator_address_raw_hash(storage, validator, consensus_key)?; Ok(()) } -/// Process enqueued slashes that were discovered earlier. This function is -/// called upon a new epoch. The final slash rate considering according to the -/// cubic slashing rate is computed. Then, each slash is recorded in storage -/// along with its computed rate, and stake is deducted from the affected -/// validators. -pub fn process_slashes( +/// Withdraw tokens from those that have been unbonded from proof-of-stake +pub fn withdraw_tokens( storage: &mut S, + source: Option<&Address>, + validator: &Address, current_epoch: Epoch, -) -> storage_api::Result<()> +) -> storage_api::Result where S: StorageRead + StorageWrite, { let params = read_pos_params(storage)?; + let source = source.unwrap_or(validator); - if current_epoch.0 < params.slash_processing_epoch_offset() { - return Ok(()); - } - let infraction_epoch = - current_epoch - params.slash_processing_epoch_offset(); + tracing::debug!("Withdrawing tokens in epoch {current_epoch}"); + tracing::debug!("Source {} --> Validator {}", source, validator); - // Slashes to be processed in the current epoch - let enqueued_slashes = enqueued_slashes_handle().at(¤t_epoch); - if enqueued_slashes.is_empty(storage)? { - return Ok(()); - } - tracing::debug!( - "Processing slashes at the beginning of epoch {} (committed in epoch \ - {})", - current_epoch, - infraction_epoch - ); + let unbond_handle: Unbonds = unbond_handle(source, validator); + let redelegated_unbonds = + delegator_redelegated_unbonds_handle(source).at(validator); - // Compute the cubic slash rate - let cubic_slash_rate = - compute_cubic_slash_rate(storage, ¶ms, infraction_epoch)?; + // Check that there are unbonded tokens available for withdrawal + if unbond_handle.is_empty(storage)? { + return Err(WithdrawError::NoUnbondFound(BondId { + source: source.clone(), + validator: validator.clone(), + }) + .into()); + } - // Collect the enqueued slashes and update their rates - let mut eager_validator_slashes: BTreeMap> = - BTreeMap::new(); - let mut eager_validator_slash_rates: HashMap = HashMap::new(); + let mut unbonds_and_redelegated_unbonds: BTreeMap< + (Epoch, Epoch), + (token::Amount, EagerRedelegatedBondsMap), + > = BTreeMap::new(); - // `slashPerValidator` and `slashesMap` while also updating in storage - for enqueued_slash in enqueued_slashes.iter(storage)? { + for unbond in unbond_handle.iter(storage)? { let ( NestedSubKey::Data { - key: validator, - nested_sub_key: _, + key: start_epoch, + nested_sub_key: SubKey::Data(withdraw_epoch), }, - enqueued_slash, - ) = enqueued_slash?; - debug_assert_eq!(enqueued_slash.epoch, infraction_epoch); - - let slash_rate = cmp::min( - Dec::one(), - cmp::max( - enqueued_slash.r#type.get_slash_rate(¶ms), - cubic_slash_rate, - ), - ); - let updated_slash = Slash { - epoch: enqueued_slash.epoch, - block_height: enqueued_slash.block_height, - r#type: enqueued_slash.r#type, - rate: slash_rate, - }; + amount, + ) = unbond?; - let cur_slashes = eager_validator_slashes - .entry(validator.clone()) - .or_default(); - cur_slashes.push(updated_slash); - let cur_rate = - eager_validator_slash_rates.entry(validator).or_default(); - *cur_rate = cmp::min(Dec::one(), *cur_rate + slash_rate); - } + // Logging + tracing::debug!( + "Unbond delta ({start_epoch}..{withdraw_epoch}), amount {}", + amount.to_string_native() + ); + // Consider only unbonds that are eligible to be withdrawn + if withdraw_epoch > current_epoch { + tracing::debug!( + "Not yet withdrawable until epoch {withdraw_epoch}" + ); + continue; + } - // Update the epochs of enqueued slashes in storage - enqueued_slashes_handle().update_data(storage, ¶ms, current_epoch)?; + let mut eager_redelegated_unbonds = EagerRedelegatedBondsMap::default(); + let matching_redelegated_unbonds = + redelegated_unbonds.at(&start_epoch).at(&withdraw_epoch); + for ub in matching_redelegated_unbonds.iter(storage)? { + let ( + NestedSubKey::Data { + key: address, + nested_sub_key: SubKey::Data(epoch), + }, + amount, + ) = ub?; + eager_redelegated_unbonds + .entry(address) + .or_default() + .entry(epoch) + .or_insert(amount); + } - // `resultSlashing` - let mut map_validator_slash: EagerRedelegatedBondsMap = BTreeMap::new(); - for (validator, slash_rate) in eager_validator_slash_rates { - process_validator_slash( - storage, - ¶ms, - &validator, - slash_rate, - current_epoch, - &mut map_validator_slash, - )?; + unbonds_and_redelegated_unbonds.insert( + (start_epoch, withdraw_epoch), + (amount, eager_redelegated_unbonds), + ); } - tracing::debug!("Slashed amounts for validators: {map_validator_slash:#?}"); - // Now update the remaining parts of storage + let slashes = find_validator_slashes(storage, validator)?; - // Write slashes themselves into storage - for (validator, slashes) in eager_validator_slashes { - let validator_slashes = validator_slashes_handle(&validator); - for slash in slashes { - validator_slashes.push(storage, slash)?; - } - } + // `val resultSlashing` + let result_slashing = compute_amount_after_slashing_withdraw( + storage, + ¶ms, + &unbonds_and_redelegated_unbonds, + slashes, + )?; - // Update the validator stakes - for (validator, slash_amounts) in map_validator_slash { - let mut slash_acc = token::Amount::zero(); + let withdrawable_amount = result_slashing.sum; + tracing::debug!( + "Withdrawing total {}", + withdrawable_amount.to_string_native() + ); - // Update validator sets first because it needs to be able to read - // validator stake before we make any changes to it - for (&epoch, &slash_amount) in &slash_amounts { - let state = validator_state_handle(&validator) - .get(storage, epoch, ¶ms)? - .unwrap(); - if state != ValidatorState::Jailed { - update_validator_set( - storage, - ¶ms, - &validator, - -slash_amount.change(), - epoch, - Some(0), - )?; - } - } - // Then update validator and total deltas - for (epoch, slash_amount) in slash_amounts { - let slash_delta = slash_amount - slash_acc; - slash_acc += slash_delta; + // `updateDelegator` with `unbonded` and `redelegeatedUnbonded` + for ((start_epoch, withdraw_epoch), _unbond_and_redelegations) in + unbonds_and_redelegated_unbonds + { + tracing::debug!("Remove ({start_epoch}..{withdraw_epoch}) from unbond"); + unbond_handle + .at(&start_epoch) + .remove(storage, &withdraw_epoch)?; + redelegated_unbonds + .at(&start_epoch) + .remove_all(storage, &withdraw_epoch)?; - update_validator_deltas( - storage, - ¶ms, - &validator, - -slash_delta.change(), - epoch, - Some(0), - )?; - update_total_deltas( - storage, - ¶ms, - -slash_delta.change(), - epoch, - Some(0), - )?; + if unbond_handle.at(&start_epoch).is_empty(storage)? { + unbond_handle.remove_all(storage, &start_epoch)?; + } + if redelegated_unbonds.at(&start_epoch).is_empty(storage)? { + redelegated_unbonds.remove_all(storage, &start_epoch)?; } - - // TODO: should we clear some storage here as is done in Quint?? - // Possibly make the `unbonded` LazyMaps epoched so that it is done - // automatically? } - Ok(()) + // Transfer the withdrawable tokens from the PoS address back to the source + let staking_token = staking_token_address(storage); + token::transfer( + storage, + &staking_token, + &ADDRESS, + source, + withdrawable_amount, + )?; + + // TODO: Transfer the slashed tokens from the PoS address to the Slash Pool + // address + // token::transfer( + // storage, + // &staking_token, + // &ADDRESS, + // &SLASH_POOL_ADDRESS, + // total_slashed, + // )?; + + Ok(withdrawable_amount) } -/// Process a slash by (i) slashing the misbehaving validator; and (ii) any -/// validator to which it has redelegated some tokens and the slash misbehaving -/// epoch is wihtin the redelegation slashing window. -/// -/// `validator` - the misbehaving validator. -/// `slash_rate` - the slash rate. -/// `slashed_amounts_map` - a map from validator address to a map from epoch to -/// already processed slash amounts. -/// -/// Adds any newly processed slash amount of any involved validator to -/// `slashed_amounts_map`. -// Quint `processSlash` -fn process_validator_slash( +/// Change the commission rate of a validator +pub fn change_validator_commission_rate( storage: &mut S, - params: &PosParams, validator: &Address, - slash_rate: Dec, + new_rate: Dec, current_epoch: Epoch, - slashed_amount_map: &mut EagerRedelegatedBondsMap, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, { - // `resultSlashValidator - let result_slash = slash_validator( - storage, - params, - validator, - slash_rate, - current_epoch, - &slashed_amount_map - .get(validator) - .cloned() - .unwrap_or_default(), - )?; - - // `updatedSlashedAmountMap` - let validator_slashes = - slashed_amount_map.entry(validator.clone()).or_default(); - *validator_slashes = result_slash; + if new_rate.is_negative() { + return Err(CommissionRateChangeError::NegativeRate( + new_rate, + validator.clone(), + ) + .into()); + } - // `outgoingRedelegation` - let outgoing_redelegations = - validator_outgoing_redelegations_handle(validator); + if new_rate > Dec::one() { + return Err(CommissionRateChangeError::LargerThanOne( + new_rate, + validator.clone(), + ) + .into()); + } - // Final loop in `processSlash` - let dest_validators = outgoing_redelegations - .iter(storage)? - .map(|res| { - let ( - NestedSubKey::Data { - key: dest_validator, - nested_sub_key: _, - }, - _redelegation, - ) = res?; - Ok(dest_validator) - }) - .collect::>>()?; + let max_change = + read_validator_max_commission_rate_change(storage, validator)?; + if max_change.is_none() { + return Err(CommissionRateChangeError::NoMaxSetInStorage( + validator.clone(), + ) + .into()); + } - for dest_validator in dest_validators { - let to_modify = slashed_amount_map - .entry(dest_validator.clone()) - .or_default(); + let params = read_pos_params(storage)?; + let commission_handle = validator_commission_rate_handle(validator); + let pipeline_epoch = current_epoch + params.pipeline_len; - tracing::debug!( - "Slashing {} redelegation to {}", - validator, - &dest_validator - ); + let rate_at_pipeline = commission_handle + .get(storage, pipeline_epoch, ¶ms)? + .expect("Could not find a rate in given epoch"); + if new_rate == rate_at_pipeline { + return Ok(()); + } + let rate_before_pipeline = commission_handle + .get(storage, pipeline_epoch.prev(), ¶ms)? + .expect("Could not find a rate in given epoch"); - // `slashValidatorRedelegation` - slash_validator_redelegation( - storage, - params, - validator, - current_epoch, - &outgoing_redelegations.at(&dest_validator), - &validator_slashes_handle(validator), - &validator_total_redelegated_unbonded_handle(&dest_validator), - slash_rate, - to_modify, - )?; + let change_from_prev = new_rate.abs_diff(&rate_before_pipeline); + if change_from_prev > max_change.unwrap() { + return Err(CommissionRateChangeError::RateChangeTooLarge( + change_from_prev, + validator.clone(), + ) + .into()); } - Ok(()) + commission_handle.set(storage, new_rate, current_epoch, params.pipeline_len) } -/// In the context of a redelegation, the function computes how much a validator -/// (the destination validator of the redelegation) should be slashed due to the -/// misbehaving of a second validator (the source validator of the -/// redelegation). The function computes how much the validator whould be -/// slashed at all epochs between the current epoch (curEpoch) + 1 and the -/// current epoch + 1 + PIPELINE_OFFSET, accounting for any tokens of the -/// redelegation already unbonded. -/// -/// - `src_validator` - the source validator -/// - `outgoing_redelegations` - a map from pair of epochs to int that includes -/// all the redelegations from the source validator to the destination -/// validator. -/// - The outer key is epoch at which the bond started at the source -/// validator. -/// - The inner key is epoch at which the redelegation started (the epoch at -/// which was issued). -/// - `slashes` a list of slashes of the source validator. -/// - `dest_total_redelegated_unbonded` - a map of unbonded redelegated tokens -/// at the destination validator. -/// - `slash_rate` - the rate of the slash being processed. -/// - `dest_slashed_amounts` - a map from epoch to already processed slash -/// amounts. -/// -/// Adds any newly processed slash amount to `dest_slashed_amounts`. -#[allow(clippy::too_many_arguments)] -fn slash_validator_redelegation( +/// Get the total bond amount, including slashes, for a given bond ID and epoch. +/// Returns the bond amount after slashing. For future epochs the value is +/// subject to change. +pub fn bond_amount( storage: &S, - params: &OwnedPosParams, - src_validator: &Address, - current_epoch: Epoch, - outgoing_redelegations: &NestedMap>, - slashes: &Slashes, - dest_total_redelegated_unbonded: &TotalRedelegatedUnbonded, - slash_rate: Dec, - dest_slashed_amounts: &mut BTreeMap, -) -> storage_api::Result<()> + bond_id: &BondId, + epoch: Epoch, +) -> storage_api::Result where S: StorageRead, { - let infraction_epoch = - current_epoch - params.slash_processing_epoch_offset(); + let params = read_pos_params(storage)?; + // Outer key is the start epoch used to calculate slashes. The inner + // keys are discarded after applying slashes. + let mut amounts: BTreeMap = BTreeMap::default(); - for res in outgoing_redelegations.iter(storage)? { + // Bonds + let bonds = + bond_handle(&bond_id.source, &bond_id.validator).get_data_handler(); + for next in bonds.iter(storage)? { + let (start, delta) = next?; + if start <= epoch { + let amount = amounts.entry(start).or_default(); + *amount += delta; + } + } + + // Add unbonds that are still contributing to stake + let unbonds = unbond_handle(&bond_id.source, &bond_id.validator); + for next in unbonds.iter(storage)? { let ( NestedSubKey::Data { - key: bond_start, - nested_sub_key: SubKey::Data(redel_start), + key: start, + nested_sub_key: SubKey::Data(withdrawable_epoch), }, - amount, - ) = res?; + delta, + ) = next?; + // This is the first epoch in which the unbond stops contributing to + // voting power + let end = withdrawable_epoch - params.withdrawable_epoch_offset() + + params.pipeline_len; - if params.in_redelegation_slashing_window( - infraction_epoch, - redel_start, - params.redelegation_end_epoch_from_start(redel_start), - ) && bond_start <= infraction_epoch - { - slash_redelegation( - storage, - params, - amount, - bond_start, - params.redelegation_end_epoch_from_start(redel_start), - src_validator, - current_epoch, - slashes, - dest_total_redelegated_unbonded, - slash_rate, - dest_slashed_amounts, - )?; + if start <= epoch && end > epoch { + let amount = amounts.entry(start).or_default(); + *amount += delta; } } - Ok(()) -} - -#[allow(clippy::too_many_arguments)] -fn slash_redelegation( - storage: &S, - params: &OwnedPosParams, - amount: token::Amount, - bond_start: Epoch, - redel_bond_start: Epoch, - src_validator: &Address, - current_epoch: Epoch, - slashes: &Slashes, - total_redelegated_unbonded: &TotalRedelegatedUnbonded, - slash_rate: Dec, - slashed_amounts: &mut BTreeMap, -) -> storage_api::Result<()> -where - S: StorageRead, -{ - tracing::debug!( - "\nSlashing redelegation amount {} - bond start {} and \ - redel_bond_start {} - at rate {}\n", - amount.to_string_native(), - bond_start, - redel_bond_start, - slash_rate - ); - - let infraction_epoch = - current_epoch - params.slash_processing_epoch_offset(); - - // Slash redelegation destination validator from the next epoch only - // as they won't be jailed - let set_update_epoch = current_epoch.next(); - - let mut init_tot_unbonded = - Epoch::iter_bounds_inclusive(infraction_epoch.next(), set_update_epoch) - .map(|epoch| { - let redelegated_unbonded = total_redelegated_unbonded - .at(&epoch) - .at(&redel_bond_start) - .at(src_validator) - .get(storage, &bond_start)? - .unwrap_or_default(); - Ok(redelegated_unbonded) - }) - .sum::>()?; - - for epoch in Epoch::iter_range(set_update_epoch, params.pipeline_len) { - let updated_total_unbonded = { - let redelegated_unbonded = total_redelegated_unbonded - .at(&epoch) - .at(&redel_bond_start) - .at(src_validator) - .get(storage, &bond_start)? - .unwrap_or_default(); - init_tot_unbonded + redelegated_unbonded - }; - - let list_slashes = slashes - .iter(storage)? - .map(Result::unwrap) - .filter(|slash| { - params.in_redelegation_slashing_window( - slash.epoch, - params.redelegation_start_epoch_from_end(redel_bond_start), - redel_bond_start, - ) && bond_start <= slash.epoch - && slash.epoch + params.slash_processing_epoch_offset() - // We're looking for slashes that were processed before or in the epoch - // in which slashes that are currently being processed - // occurred. Because we're slashing in the beginning of an - // epoch, we're also taking slashes that were processed in - // the infraction epoch as they would still be processed - // before any infraction occurred. - <= infraction_epoch - }) - .collect::>(); - - let slashable_amount = amount - .checked_sub(updated_total_unbonded) - .unwrap_or_default(); + if bond_id.validator != bond_id.source { + // Add outgoing redelegations that are still contributing to the source + // validator's stake + let redelegated_bonds = + delegator_redelegated_bonds_handle(&bond_id.source); + for res in redelegated_bonds.iter(storage)? { + let ( + NestedSubKey::Data { + key: _dest_validator, + nested_sub_key: + NestedSubKey::Data { + key: end, + nested_sub_key: + NestedSubKey::Data { + key: src_validator, + nested_sub_key: SubKey::Data(start), + }, + }, + }, + delta, + ) = res?; + if src_validator == bond_id.validator + && start <= epoch + && end > epoch + { + let amount = amounts.entry(start).or_default(); + *amount += delta; + } + } - let slashed = - apply_list_slashes(params, &list_slashes, slashable_amount) - .mul_ceil(slash_rate); + // Add outgoing redelegation unbonds that are still contributing to + // the source validator's stake + let redelegated_unbonds = + delegator_redelegated_unbonds_handle(&bond_id.source); + for res in redelegated_unbonds.iter(storage)? { + let ( + NestedSubKey::Data { + key: _dest_validator, + nested_sub_key: + NestedSubKey::Data { + key: redelegation_epoch, + nested_sub_key: + NestedSubKey::Data { + key: _withdraw_epoch, + nested_sub_key: + NestedSubKey::Data { + key: src_validator, + nested_sub_key: SubKey::Data(start), + }, + }, + }, + }, + delta, + ) = res?; + if src_validator == bond_id.validator + // If the unbonded bond was redelegated after this epoch ... + && redelegation_epoch > epoch + // ... the start was before or at this epoch + && start <= epoch + { + let amount = amounts.entry(start).or_default(); + *amount += delta; + } + } + } - let list_slashes = slashes - .iter(storage)? - .map(Result::unwrap) - .filter(|slash| { - params.in_redelegation_slashing_window( - slash.epoch, - params.redelegation_start_epoch_from_end(redel_bond_start), - redel_bond_start, - ) && bond_start <= slash.epoch - }) - .collect::>(); + if !amounts.is_empty() { + let slashes = find_validator_slashes(storage, &bond_id.validator)?; - let slashable_stake = - apply_list_slashes(params, &list_slashes, slashable_amount) - .mul_ceil(slash_rate); + // Apply slashes + for (&start, amount) in amounts.iter_mut() { + let list_slashes = slashes + .iter() + .filter(|slash| { + let processing_epoch = + slash.epoch + params.slash_processing_epoch_offset(); + // Only use slashes that were processed before or at the + // epoch associated with the bond amount. This assumes + // that slashes are applied before inflation. + processing_epoch <= epoch && start <= slash.epoch + }) + .cloned() + .collect::>(); - init_tot_unbonded = updated_total_unbonded; - let to_slash = cmp::min(slashed, slashable_stake); - if !to_slash.is_zero() { - let map_value = slashed_amounts.entry(epoch).or_default(); - *map_value += to_slash; + *amount = apply_list_slashes(¶ms, &list_slashes, *amount); } } - Ok(()) + Ok(amounts.values().cloned().sum()) } -/// Computes for a given validator and a slash how much should be slashed at all -/// epochs between the currentÃ¥ epoch (curEpoch) + 1 and the current epoch + 1 + -/// PIPELINE_OFFSET, accounting for any tokens already unbonded. -/// -/// - `validator` - the misbehaving validator. -/// - `slash_rate` - the rate of the slash being processed. -/// - `slashed_amounts_map` - a map from epoch to already processed slash -/// amounts. -/// -/// Returns a map that adds any newly processed slash amount to -/// `slashed_amounts_map`. -// `def slashValidator` -fn slash_validator( +/// Get bond amounts within the `claim_start..=claim_end` epoch range for +/// claiming rewards for a given bond ID. Returns a map of bond amounts +/// associated with every epoch within the given epoch range (accumulative) in +/// which an amount contributed to the validator's stake. +/// This function will only consider slashes that were processed before or at +/// the epoch in which we're calculating the bond amount to correspond to the +/// validator stake that was used to calculate reward products (slashes do *not* +/// retrospectively affect the rewards calculated before slash processing). +pub fn bond_amounts_for_rewards( storage: &S, - params: &OwnedPosParams, - validator: &Address, - slash_rate: Dec, - current_epoch: Epoch, - slashed_amounts_map: &BTreeMap, + bond_id: &BondId, + claim_start: Epoch, + claim_end: Epoch, ) -> storage_api::Result> where S: StorageRead, { - tracing::debug!("Slashing validator {} at rate {}", validator, slash_rate); - let infraction_epoch = - current_epoch - params.slash_processing_epoch_offset(); - - let total_unbonded = total_unbonded_handle(validator); - let total_redelegated_unbonded = - validator_total_redelegated_unbonded_handle(validator); - let total_bonded = total_bonded_handle(validator); - let total_redelegated_bonded = - validator_total_redelegated_bonded_handle(validator); - - let mut slashed_amounts = slashed_amounts_map.clone(); + let params = read_pos_params(storage)?; + // Outer key is every epoch in which the a bond amount contributed to stake + // and the inner key is the start epoch used to calculate slashes. The inner + // keys are discarded after applying slashes. + let mut amounts: BTreeMap> = + BTreeMap::default(); - let mut tot_bonds = total_bonded - .get_data_handler() - .iter(storage)? - .map(Result::unwrap) - .filter(|&(epoch, bonded)| { - epoch <= infraction_epoch && bonded > 0.into() - }) - .collect::>(); - - let mut redelegated_bonds = tot_bonds - .keys() - .filter(|&epoch| { - !total_redelegated_bonded - .at(epoch) - .is_empty(storage) - .unwrap() - }) - .map(|epoch| { - let tot_redel_bonded = total_redelegated_bonded - .at(epoch) - .collect_map(storage) - .unwrap(); - (*epoch, tot_redel_bonded) - }) - .collect::>(); - - let mut sum = token::Amount::zero(); - - let eps = current_epoch - .iter_range(params.pipeline_len) - .collect::>(); - for epoch in eps.into_iter().rev() { - let amount = tot_bonds.iter().fold( - token::Amount::zero(), - |acc, (bond_start, bond_amount)| { - acc + compute_slash_bond_at_epoch( - storage, - params, - validator, - epoch, - infraction_epoch, - *bond_start, - *bond_amount, - redelegated_bonds.get(bond_start), - slash_rate, - ) - .unwrap() - }, - ); + // Only need to do bonds since rewwards are accumulated during + // `unbond_tokens` + let bonds = + bond_handle(&bond_id.source, &bond_id.validator).get_data_handler(); + for next in bonds.iter(storage)? { + let (start, delta) = next?; - let new_bonds = total_unbonded.at(&epoch); - tot_bonds = new_bonds - .collect_map(storage) - .unwrap() - .into_iter() - .filter(|(ep, _)| *ep <= infraction_epoch) - .collect::>(); - - let new_redelegated_bonds = tot_bonds - .keys() - .filter(|&ep| { - !total_redelegated_unbonded.at(ep).is_empty(storage).unwrap() - }) - .map(|ep| { - ( - *ep, - total_redelegated_unbonded - .at(&epoch) - .at(ep) - .collect_map(storage) - .unwrap(), - ) - }) - .collect::>(); + for ep in Epoch::iter_bounds_inclusive(claim_start, claim_end) { + // A bond that wasn't unbonded is added to all epochs up to + // `claim_end` + if start <= ep { + let amount = + amounts.entry(ep).or_default().entry(start).or_default(); + *amount += delta; + } + } + } - redelegated_bonds = new_redelegated_bonds; + if !amounts.is_empty() { + let slashes = find_validator_slashes(storage, &bond_id.validator)?; + let redelegated_bonded = + delegator_redelegated_bonds_handle(&bond_id.source) + .at(&bond_id.validator); - // `newSum` - sum += amount; + // Apply slashes + for (&ep, amounts) in amounts.iter_mut() { + for (&start, amount) in amounts.iter_mut() { + let list_slashes = slashes + .iter() + .filter(|slash| { + let processing_epoch = slash.epoch + + params.slash_processing_epoch_offset(); + // Only use slashes that were processed before or at the + // epoch associated with the bond amount. This assumes + // that slashes are applied before inflation. + processing_epoch <= ep && start <= slash.epoch + }) + .cloned() + .collect::>(); - // `newSlashesMap` - let cur = slashed_amounts.entry(epoch).or_default(); - *cur += sum; - } - // Hack - should this be done differently? (think this is safe) - let pipeline_epoch = current_epoch + params.pipeline_len; - let last_amt = slashed_amounts - .get(&pipeline_epoch.prev()) - .cloned() - .unwrap(); - slashed_amounts.insert(pipeline_epoch, last_amt); + let slash_epoch_filter = + |e: Epoch| e + params.slash_processing_epoch_offset() <= ep; - Ok(slashed_amounts) -} + let redelegated_bonds = + redelegated_bonded.at(&start).collect_map(storage)?; -/// Get the remaining token amount in a bond after applying a set of slashes. -/// -/// - `validator` - the bond's validator -/// - `epoch` - the latest slash epoch to consider. -/// - `start` - the start epoch of the bond -/// - `redelegated_bonds` -fn compute_bond_at_epoch( - storage: &S, - params: &OwnedPosParams, - validator: &Address, - epoch: Epoch, - start: Epoch, - amount: token::Amount, - redelegated_bonds: Option<&EagerRedelegatedBondsMap>, -) -> storage_api::Result -where - S: StorageRead, -{ - let list_slashes = validator_slashes_handle(validator) - .iter(storage)? - .map(Result::unwrap) - .filter(|slash| { - start <= slash.epoch - && slash.epoch + params.slash_processing_epoch_offset() <= epoch - }) - .collect::>(); + let result_fold = fold_and_slash_redelegated_bonds( + storage, + ¶ms, + &redelegated_bonds, + start, + &list_slashes, + slash_epoch_filter, + ); - let slash_epoch_filter = - |e: Epoch| e + params.slash_processing_epoch_offset() <= epoch; + let total_not_redelegated = + *amount - result_fold.total_redelegated; - let result_fold = redelegated_bonds - .map(|redelegated_bonds| { - fold_and_slash_redelegated_bonds( - storage, - params, - redelegated_bonds, - start, - &list_slashes, - slash_epoch_filter, - ) - }) - .unwrap_or_default(); + let after_not_redelegated = apply_list_slashes( + ¶ms, + &list_slashes, + total_not_redelegated, + ); - let total_not_redelegated = amount - result_fold.total_redelegated; - let after_not_redelegated = - apply_list_slashes(params, &list_slashes, total_not_redelegated); + *amount = + after_not_redelegated + result_fold.total_after_slashing; + } + } + } - Ok(after_not_redelegated + result_fold.total_after_slashing) + Ok(amounts + .into_iter() + // Flatten the inner maps to discard bond start epochs + .map(|(ep, amounts)| (ep, amounts.values().cloned().sum())) + .collect()) } -/// Uses `fn compute_bond_at_epoch` to compute the token amount to slash in -/// order to prevent overslashing. -#[allow(clippy::too_many_arguments)] -fn compute_slash_bond_at_epoch( +/// Get the genesis consensus validators stake and consensus key for Tendermint, +/// converted from [`ValidatorSetUpdate`]s using the given function. +pub fn genesis_validator_set_tendermint( storage: &S, - params: &OwnedPosParams, - validator: &Address, - epoch: Epoch, - infraction_epoch: Epoch, - bond_start: Epoch, - bond_amount: token::Amount, - redelegated_bonds: Option<&EagerRedelegatedBondsMap>, - slash_rate: Dec, -) -> storage_api::Result + params: &PosParams, + current_epoch: Epoch, + mut f: impl FnMut(ValidatorSetUpdate) -> T, +) -> storage_api::Result> where S: StorageRead, { - let amount_due = compute_bond_at_epoch( - storage, - params, - validator, - infraction_epoch, - bond_start, - bond_amount, - redelegated_bonds, - )? - .mul_ceil(slash_rate); - let slashable_amount = compute_bond_at_epoch( - storage, - params, - validator, - epoch, - bond_start, - bond_amount, - redelegated_bonds, - )?; - Ok(cmp::min(amount_due, slashable_amount)) + let consensus_validator_handle = + consensus_validator_set_handle().at(¤t_epoch); + let iter = consensus_validator_handle.iter(storage)?; + + iter.map(|validator| { + let ( + NestedSubKey::Data { + key: new_stake, + nested_sub_key: _, + }, + address, + ) = validator?; + let consensus_key = validator_consensus_key_handle(&address) + .get(storage, current_epoch, params)? + .unwrap(); + let converted = f(ValidatorSetUpdate::Consensus(ConsensusValidator { + consensus_key, + bonded_stake: new_stake, + })); + Ok(converted) + }) + .collect() } /// Unjail a validator that is currently jailed. @@ -5240,36 +1921,11 @@ pub fn get_total_consensus_stake( where S: StorageRead, { - total_consensus_stake_key_handle() + total_consensus_stake_handle() .get(storage, epoch, params) .map(|o| o.expect("Total consensus stake could not be retrieved.")) } -/// Find slashes applicable to a validator with inclusive `start` and exclusive -/// `end` epoch. -#[allow(dead_code)] -fn find_slashes_in_range( - storage: &S, - start: Epoch, - end: Option, - validator: &Address, -) -> storage_api::Result> -where - S: StorageRead, -{ - let mut slashes = BTreeMap::::new(); - for slash in validator_slashes_handle(validator).iter(storage)? { - let slash = slash?; - if start <= slash.epoch - && end.map(|end| slash.epoch < end).unwrap_or(true) - { - let cur_rate = slashes.entry(slash.epoch).or_default(); - *cur_rate = cmp::min(*cur_rate + slash.rate, Dec::one()); - } - } - Ok(slashes) -} - /// Redelegate bonded tokens from a source validator to a destination validator pub fn redelegate_tokens( storage: &mut S, @@ -5564,7 +2220,6 @@ where fn deactivate_consensus_validator( storage: &mut S, - validator: &Address, target_epoch: Epoch, stake: token::Amount, @@ -5867,6 +2522,7 @@ pub mod test_utils { use super::*; use crate::parameters::PosParams; + use crate::storage::read_non_pos_owned_params; use crate::types::GenesisValidator; /// Helper function to intialize storage with PoS data @@ -5945,152 +2601,12 @@ pub mod test_utils { { let gov_params = namada_core::ledger::governance::parameters::GovernanceParameters::default(); gov_params.init_storage(storage)?; - let params = crate::read_non_pos_owned_params(storage, owned)?; + let params = read_non_pos_owned_params(storage, owned)?; init_genesis_helper(storage, ¶ms, validators, current_epoch)?; Ok(params) } } -/// Read PoS validator's email. -pub fn read_validator_email( - storage: &S, - validator: &Address, -) -> storage_api::Result> -where - S: StorageRead, -{ - storage.read(&validator_email_key(validator)) -} - -/// Write PoS validator's email. The email cannot be removed, so an empty string -/// will result in an error. -pub fn write_validator_email( - storage: &mut S, - validator: &Address, - email: &String, -) -> storage_api::Result<()> -where - S: StorageRead + StorageWrite, -{ - let key = validator_email_key(validator); - if email.is_empty() { - Err(MetadataError::CannotRemoveEmail.into()) - } else { - storage.write(&key, email) - } -} - -/// Read PoS validator's description. -pub fn read_validator_description( - storage: &S, - validator: &Address, -) -> storage_api::Result> -where - S: StorageRead, -{ - storage.read(&validator_description_key(validator)) -} - -/// Write PoS validator's description. If the provided arg is an empty string, -/// remove the data. -pub fn write_validator_description( - storage: &mut S, - validator: &Address, - description: &String, -) -> storage_api::Result<()> -where - S: StorageRead + StorageWrite, -{ - let key = validator_description_key(validator); - if description.is_empty() { - storage.delete(&key) - } else { - storage.write(&key, description) - } -} - -/// Read PoS validator's website. -pub fn read_validator_website( - storage: &S, - validator: &Address, -) -> storage_api::Result> -where - S: StorageRead, -{ - storage.read(&validator_website_key(validator)) -} - -/// Write PoS validator's website. If the provided arg is an empty string, -/// remove the data. -pub fn write_validator_website( - storage: &mut S, - validator: &Address, - website: &String, -) -> storage_api::Result<()> -where - S: StorageRead + StorageWrite, -{ - let key = validator_website_key(validator); - if website.is_empty() { - storage.delete(&key) - } else { - storage.write(&key, website) - } -} - -/// Read PoS validator's discord handle. -pub fn read_validator_discord_handle( - storage: &S, - validator: &Address, -) -> storage_api::Result> -where - S: StorageRead, -{ - storage.read(&validator_discord_key(validator)) -} - -/// Write PoS validator's discord handle. If the provided arg is an empty -/// string, remove the data. -pub fn write_validator_discord_handle( - storage: &mut S, - validator: &Address, - discord_handle: &String, -) -> storage_api::Result<()> -where - S: StorageRead + StorageWrite, -{ - let key = validator_discord_key(validator); - if discord_handle.is_empty() { - storage.delete(&key) - } else { - storage.write(&key, discord_handle) - } -} - -/// Write validator's metadata. -pub fn write_validator_metadata( - storage: &mut S, - validator: &Address, - metadata: &ValidatorMetaData, -) -> storage_api::Result<()> -where - S: StorageRead + StorageWrite, -{ - // Email is the only required field in the metadata - write_validator_email(storage, validator, &metadata.email)?; - - if let Some(description) = metadata.description.as_ref() { - write_validator_description(storage, validator, description)?; - } - if let Some(website) = metadata.website.as_ref() { - write_validator_website(storage, validator, website)?; - } - if let Some(discord) = metadata.discord_handle.as_ref() { - write_validator_discord_handle(storage, validator, discord)?; - } - Ok(()) -} - /// Change validator's metadata. In addition to changing any of the data from /// [`ValidatorMetaData`], the validator's commission rate can be changed within /// here as well. @@ -6131,63 +2647,6 @@ where Ok(()) } -/// Compute the current available rewards amount due only to existing bonds. -/// This does not include pending rewards held in the rewards counter due to -/// unbonds and redelegations. -pub fn compute_current_rewards_from_bonds( - storage: &S, - source: &Address, - validator: &Address, - current_epoch: Epoch, -) -> storage_api::Result -where - S: StorageRead, -{ - if current_epoch == Epoch::default() { - // Nothing to claim in the first epoch - return Ok(token::Amount::zero()); - } - - let last_claim_epoch = - get_last_reward_claim_epoch(storage, source, validator)?; - if let Some(last_epoch) = last_claim_epoch { - if last_epoch == current_epoch { - // Already claimed in this epoch - return Ok(token::Amount::zero()); - } - } - - let mut reward_tokens = token::Amount::zero(); - - // Want to claim from `last_claim_epoch` to `current_epoch.prev()` since - // rewards are computed at the end of an epoch - let (claim_start, claim_end) = ( - last_claim_epoch.unwrap_or_default(), - // Safe because of the check above - current_epoch.prev(), - ); - let bond_amounts = bond_amounts_for_rewards( - storage, - &BondId { - source: source.clone(), - validator: validator.clone(), - }, - claim_start, - claim_end, - )?; - - let rewards_products = validator_rewards_products_handle(validator); - for (ep, bond_amount) in bond_amounts { - debug_assert!(ep >= claim_start); - debug_assert!(ep <= claim_end); - let rp = rewards_products.get(storage, &ep)?.unwrap_or_default(); - let reward = rp * bond_amount; - reward_tokens += reward; - } - - Ok(reward_tokens) -} - /// Claim available rewards, triggering an immediate transfer of tokens from the /// PoS account to the source address. pub fn claim_reward_tokens( @@ -6248,77 +2707,6 @@ where Ok(rewards_from_bonds + rewards_from_counter) } -/// Get the last epoch in which rewards were claimed from storage, if any -pub fn get_last_reward_claim_epoch( - storage: &S, - delegator: &Address, - validator: &Address, -) -> storage_api::Result> -where - S: StorageRead, -{ - let key = last_pos_reward_claim_epoch_key(delegator, validator); - storage.read(&key) -} - -fn write_last_reward_claim_epoch( - storage: &mut S, - delegator: &Address, - validator: &Address, - epoch: Epoch, -) -> storage_api::Result<()> -where - S: StorageRead + StorageWrite, -{ - let key = last_pos_reward_claim_epoch_key(delegator, validator); - storage.write(&key, epoch) -} - -/// Read the current token value in the rewards counter. -fn read_rewards_counter( - storage: &S, - source: &Address, - validator: &Address, -) -> storage_api::Result -where - S: StorageRead, -{ - let key = rewards_counter_key(source, validator); - Ok(storage.read::(&key)?.unwrap_or_default()) -} - -/// Add tokens to a rewards counter. -fn add_rewards_to_counter( - storage: &mut S, - source: &Address, - validator: &Address, - new_rewards: token::Amount, -) -> storage_api::Result<()> -where - S: StorageRead + StorageWrite, -{ - let key = rewards_counter_key(source, validator); - let current_rewards = - storage.read::(&key)?.unwrap_or_default(); - storage.write(&key, current_rewards + new_rewards) -} - -/// Take tokens from a rewards counter. Deletes the record after reading. -fn take_rewards_from_counter( - storage: &mut S, - source: &Address, - validator: &Address, -) -> storage_api::Result -where - S: StorageRead + StorageWrite, -{ - let key = rewards_counter_key(source, validator); - let current_rewards = - storage.read::(&key)?.unwrap_or_default(); - storage.delete(&key)?; - Ok(current_rewards) -} - /// Jail a validator by removing it from and updating the validator sets and /// changing a its state to `Jailed`. Validators are jailed for liveness and for /// misbehaving. diff --git a/proof_of_stake/src/pos_queries.rs b/proof_of_stake/src/pos_queries.rs index c0d0fbaf28..6effbcfc51 100644 --- a/proof_of_stake/src/pos_queries.rs +++ b/proof_of_stake/src/pos_queries.rs @@ -11,11 +11,12 @@ use namada_core::types::storage::{BlockHeight, Epoch}; use namada_core::types::{key, token}; use thiserror::Error; +use crate::storage::find_validator_by_raw_hash; use crate::types::WeightedValidator; use crate::{ - consensus_validator_set_handle, find_validator_by_raw_hash, - get_total_consensus_stake, read_pos_params, validator_eth_cold_key_handle, - validator_eth_hot_key_handle, ConsensusValidatorSet, PosParams, + consensus_validator_set_handle, get_total_consensus_stake, read_pos_params, + validator_eth_cold_key_handle, validator_eth_hot_key_handle, + ConsensusValidatorSet, PosParams, }; /// Errors returned by [`PosQueries`] operations. diff --git a/proof_of_stake/src/queries.rs b/proof_of_stake/src/queries.rs new file mode 100644 index 0000000000..137d5fdf7a --- /dev/null +++ b/proof_of_stake/src/queries.rs @@ -0,0 +1,457 @@ +//! Queriezzz + +use std::cmp; +use std::collections::{BTreeMap, HashMap, HashSet}; + +use borsh::BorshDeserialize; +use namada_core::ledger::storage_api::collections::lazy_map::{ + NestedSubKey, SubKey, +}; +use namada_core::ledger::storage_api::{self, StorageRead}; +use namada_core::types::address::Address; +use namada_core::types::dec::Dec; +use namada_core::types::storage::Epoch; +use namada_core::types::token; + +use crate::slashing::{find_validator_slashes, get_slashed_amount}; +use crate::storage::{bond_handle, read_pos_params, unbond_handle}; +use crate::types::{ + BondDetails, BondId, BondsAndUnbondsDetail, BondsAndUnbondsDetails, Slash, + UnbondDetails, +}; +use crate::{storage_key, PosParams}; + +/// Find all validators to which a given bond `owner` (or source) has a +/// delegation +pub fn find_delegation_validators( + storage: &S, + owner: &Address, +) -> storage_api::Result> +where + S: StorageRead, +{ + let bonds_prefix = storage_key::bonds_for_source_prefix(owner); + let mut delegations: HashSet
= HashSet::new(); + + for iter_result in storage_api::iter_prefix_bytes(storage, &bonds_prefix)? { + let (key, _bond_bytes) = iter_result?; + let validator_address = storage_key::get_validator_address_from_bond( + &key, + ) + .ok_or_else(|| { + storage_api::Error::new_const( + "Delegation key should contain validator address.", + ) + })?; + delegations.insert(validator_address); + } + Ok(delegations) +} + +/// Find all validators to which a given bond `owner` (or source) has a +/// delegation with the amount +pub fn find_delegations( + storage: &S, + owner: &Address, + epoch: &Epoch, +) -> storage_api::Result> +where + S: StorageRead, +{ + let bonds_prefix = storage_key::bonds_for_source_prefix(owner); + let params = read_pos_params(storage)?; + let mut delegations: HashMap = HashMap::new(); + + for iter_result in storage_api::iter_prefix_bytes(storage, &bonds_prefix)? { + let (key, _bond_bytes) = iter_result?; + let validator_address = storage_key::get_validator_address_from_bond( + &key, + ) + .ok_or_else(|| { + storage_api::Error::new_const( + "Delegation key should contain validator address.", + ) + })?; + let deltas_sum = bond_handle(owner, &validator_address) + .get_sum(storage, *epoch, ¶ms)? + .unwrap_or_default(); + delegations.insert(validator_address, deltas_sum); + } + Ok(delegations) +} + +/// Find if the given source address has any bonds. +pub fn has_bonds(storage: &S, source: &Address) -> storage_api::Result +where + S: StorageRead, +{ + let max_epoch = Epoch(u64::MAX); + let delegations = find_delegations(storage, source, &max_epoch)?; + Ok(!delegations + .values() + .cloned() + .sum::() + .is_zero()) +} + +/// Find raw bond deltas for the given source and validator address. +pub fn find_bonds( + storage: &S, + source: &Address, + validator: &Address, +) -> storage_api::Result> +where + S: StorageRead, +{ + bond_handle(source, validator) + .get_data_handler() + .iter(storage)? + .collect() +} + +/// Find raw unbond deltas for the given source and validator address. +pub fn find_unbonds( + storage: &S, + source: &Address, + validator: &Address, +) -> storage_api::Result> +where + S: StorageRead, +{ + unbond_handle(source, validator) + .iter(storage)? + .map(|next_result| { + let ( + NestedSubKey::Data { + key: start_epoch, + nested_sub_key: SubKey::Data(withdraw_epoch), + }, + amount, + ) = next_result?; + Ok(((start_epoch, withdraw_epoch), amount)) + }) + .collect() +} + +/// Collect the details of all bonds and unbonds that match the source and +/// validator arguments. If either source or validator is `None`, then grab the +/// information for all sources or validators, respectively. +pub fn bonds_and_unbonds( + storage: &S, + source: Option
, + validator: Option
, +) -> storage_api::Result +where + S: StorageRead, +{ + let params = read_pos_params(storage)?; + + match (source.clone(), validator.clone()) { + (Some(source), Some(validator)) => { + find_bonds_and_unbonds_details(storage, ¶ms, source, validator) + } + _ => { + get_multiple_bonds_and_unbonds(storage, ¶ms, source, validator) + } + } +} + +fn get_multiple_bonds_and_unbonds( + storage: &S, + params: &PosParams, + source: Option
, + validator: Option
, +) -> storage_api::Result +where + S: StorageRead, +{ + debug_assert!( + source.is_none() || validator.is_none(), + "Use `find_bonds_and_unbonds_details` when full bond ID is known" + ); + let mut slashes_cache = HashMap::>::new(); + // Applied slashes grouped by validator address + let mut applied_slashes = HashMap::>::new(); + + // TODO: if validator is `Some`, look-up all its bond owners (including + // self-bond, if any) first + + let prefix = match source.as_ref() { + Some(source) => storage_key::bonds_for_source_prefix(source), + None => storage_key::bonds_prefix(), + }; + // We have to iterate raw bytes, cause the epoched data `last_update` field + // gets matched here too + let mut raw_bonds = storage_api::iter_prefix_bytes(storage, &prefix)? + .filter_map(|result| { + if let Ok((key, val_bytes)) = result { + if let Some((bond_id, start)) = storage_key::is_bond_key(&key) { + if source.is_some() + && source.as_ref().unwrap() != &bond_id.source + { + return None; + } + if validator.is_some() + && validator.as_ref().unwrap() != &bond_id.validator + { + return None; + } + let change: token::Amount = + BorshDeserialize::try_from_slice(&val_bytes).ok()?; + if change.is_zero() { + return None; + } + return Some((bond_id, start, change)); + } + } + None + }); + + let prefix = match source.as_ref() { + Some(source) => storage_key::unbonds_for_source_prefix(source), + None => storage_key::unbonds_prefix(), + }; + let mut raw_unbonds = storage_api::iter_prefix_bytes(storage, &prefix)? + .filter_map(|result| { + if let Ok((key, val_bytes)) = result { + if let Some((bond_id, start, withdraw)) = + storage_key::is_unbond_key(&key) + { + if source.is_some() + && source.as_ref().unwrap() != &bond_id.source + { + return None; + } + if validator.is_some() + && validator.as_ref().unwrap() != &bond_id.validator + { + return None; + } + match (source.clone(), validator.clone()) { + (None, Some(validator)) => { + if bond_id.validator != validator { + return None; + } + } + (Some(owner), None) => { + if owner != bond_id.source { + return None; + } + } + _ => {} + } + let amount: token::Amount = + BorshDeserialize::try_from_slice(&val_bytes).ok()?; + return Some((bond_id, start, withdraw, amount)); + } + } + None + }); + + let mut bonds_and_unbonds = + HashMap::, Vec)>::new(); + + raw_bonds.try_for_each(|(bond_id, start, change)| { + if !slashes_cache.contains_key(&bond_id.validator) { + let slashes = find_validator_slashes(storage, &bond_id.validator)?; + slashes_cache.insert(bond_id.validator.clone(), slashes); + } + let slashes = slashes_cache + .get(&bond_id.validator) + .expect("We must have inserted it if it's not cached already"); + let validator = bond_id.validator.clone(); + let (bonds, _unbonds) = bonds_and_unbonds.entry(bond_id).or_default(); + bonds.push(make_bond_details( + params, + &validator, + change, + start, + slashes, + &mut applied_slashes, + )); + Ok::<_, storage_api::Error>(()) + })?; + + raw_unbonds.try_for_each(|(bond_id, start, withdraw, amount)| { + if !slashes_cache.contains_key(&bond_id.validator) { + let slashes = find_validator_slashes(storage, &bond_id.validator)?; + slashes_cache.insert(bond_id.validator.clone(), slashes); + } + let slashes = slashes_cache + .get(&bond_id.validator) + .expect("We must have inserted it if it's not cached already"); + let validator = bond_id.validator.clone(); + let (_bonds, unbonds) = bonds_and_unbonds.entry(bond_id).or_default(); + unbonds.push(make_unbond_details( + params, + &validator, + amount, + (start, withdraw), + slashes, + &mut applied_slashes, + )); + Ok::<_, storage_api::Error>(()) + })?; + + Ok(bonds_and_unbonds + .into_iter() + .map(|(bond_id, (bonds, unbonds))| { + let details = BondsAndUnbondsDetail { + bonds, + unbonds, + slashes: applied_slashes + .get(&bond_id.validator) + .cloned() + .unwrap_or_default(), + }; + (bond_id, details) + }) + .collect()) +} + +fn find_bonds_and_unbonds_details( + storage: &S, + params: &PosParams, + source: Address, + validator: Address, +) -> storage_api::Result +where + S: StorageRead, +{ + let slashes = find_validator_slashes(storage, &validator)?; + let mut applied_slashes = HashMap::>::new(); + + let bonds = find_bonds(storage, &source, &validator)? + .into_iter() + .filter(|(_start, amount)| *amount > token::Amount::zero()) + .map(|(start, amount)| { + make_bond_details( + params, + &validator, + amount, + start, + &slashes, + &mut applied_slashes, + ) + }) + .collect(); + + let unbonds = find_unbonds(storage, &source, &validator)? + .into_iter() + .map(|(epoch_range, change)| { + make_unbond_details( + params, + &validator, + change, + epoch_range, + &slashes, + &mut applied_slashes, + ) + }) + .collect(); + + let details = BondsAndUnbondsDetail { + bonds, + unbonds, + slashes: applied_slashes.get(&validator).cloned().unwrap_or_default(), + }; + let bond_id = BondId { source, validator }; + Ok(HashMap::from_iter([(bond_id, details)])) +} + +fn make_bond_details( + params: &PosParams, + validator: &Address, + deltas_sum: token::Amount, + start: Epoch, + slashes: &[Slash], + applied_slashes: &mut HashMap>, +) -> BondDetails { + let prev_applied_slashes = applied_slashes + .clone() + .get(validator) + .cloned() + .unwrap_or_default(); + + let mut slash_rates_by_epoch = BTreeMap::::new(); + + let validator_slashes = + applied_slashes.entry(validator.clone()).or_default(); + for slash in slashes { + if slash.epoch >= start { + let cur_rate = slash_rates_by_epoch.entry(slash.epoch).or_default(); + *cur_rate = cmp::min(Dec::one(), *cur_rate + slash.rate); + + if !prev_applied_slashes.iter().any(|s| s == slash) { + validator_slashes.push(slash.clone()); + } + } + } + + let slashed_amount = if slash_rates_by_epoch.is_empty() { + None + } else { + let amount_after_slashing = + get_slashed_amount(params, deltas_sum, &slash_rates_by_epoch) + .unwrap(); + Some(deltas_sum - amount_after_slashing) + }; + + BondDetails { + start, + amount: deltas_sum, + slashed_amount, + } +} + +fn make_unbond_details( + params: &PosParams, + validator: &Address, + amount: token::Amount, + (start, withdraw): (Epoch, Epoch), + slashes: &[Slash], + applied_slashes: &mut HashMap>, +) -> UnbondDetails { + let prev_applied_slashes = applied_slashes + .clone() + .get(validator) + .cloned() + .unwrap_or_default(); + let mut slash_rates_by_epoch = BTreeMap::::new(); + + let validator_slashes = + applied_slashes.entry(validator.clone()).or_default(); + for slash in slashes { + if slash.epoch >= start + && slash.epoch + < withdraw + .checked_sub( + params.unbonding_len + + params.cubic_slashing_window_length, + ) + .unwrap_or_default() + { + let cur_rate = slash_rates_by_epoch.entry(slash.epoch).or_default(); + *cur_rate = cmp::min(Dec::one(), *cur_rate + slash.rate); + + if !prev_applied_slashes.iter().any(|s| s == slash) { + validator_slashes.push(slash.clone()); + } + } + } + + let slashed_amount = if slash_rates_by_epoch.is_empty() { + None + } else { + let amount_after_slashing = + get_slashed_amount(params, amount, &slash_rates_by_epoch).unwrap(); + Some(amount - amount_after_slashing) + }; + + UnbondDetails { + start, + withdraw, + amount, + slashed_amount, + } +} diff --git a/proof_of_stake/src/rewards.rs b/proof_of_stake/src/rewards.rs index 449c8a9867..3b19bd6b79 100644 --- a/proof_of_stake/src/rewards.rs +++ b/proof_of_stake/src/rewards.rs @@ -1,10 +1,31 @@ //! PoS rewards distribution. +use std::collections::{HashMap, HashSet}; + +use namada_core::ledger::storage_api::collections::lazy_map::NestedSubKey; +use namada_core::ledger::storage_api::token::credit_tokens; +use namada_core::ledger::storage_api::{ + self, ResultExt, StorageRead, StorageWrite, +}; +use namada_core::types::address::{self, Address}; use namada_core::types::dec::Dec; -use namada_core::types::token::Amount; +use namada_core::types::storage::Epoch; +use namada_core::types::token::{self, Amount}; use namada_core::types::uint::{Uint, I256}; use thiserror::Error; +use crate::storage::{ + consensus_validator_set_handle, get_last_reward_claim_epoch, + read_pos_params, read_validator_stake, rewards_accumulator_handle, + validator_commission_rate_handle, validator_rewards_products_handle, + validator_state_handle, +}; +use crate::types::{into_tm_voting_power, BondId, ValidatorState, VoteInfo}; +use crate::{ + bond_amounts_for_rewards, get_total_consensus_stake, storage_key, + InflationError, PosParams, +}; + /// This is equal to 0.01. const MIN_PROPOSER_REWARD: Dec = Dec(I256(Uint([10000000000u64, 0u64, 0u64, 0u64]))); @@ -99,3 +120,356 @@ impl PosRewardsCalculator { / 3u64 } } + +/// Tally a running sum of the fraction of rewards owed to each validator in +/// the consensus set. This is used to keep track of the rewards due to each +/// consensus validator over the lifetime of an epoch. +pub fn log_block_rewards( + storage: &mut S, + epoch: impl Into, + proposer_address: &Address, + votes: Vec, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + // The votes correspond to the last committed block (n-1 if we are + // finalizing block n) + + let epoch: Epoch = epoch.into(); + let params = read_pos_params(storage)?; + let consensus_validators = consensus_validator_set_handle().at(&epoch); + + // Get total stake of the consensus validator set + let total_consensus_stake = + get_total_consensus_stake(storage, epoch, ¶ms)?; + + // Get set of signing validator addresses and the combined stake of + // these signers + let mut signer_set: HashSet
= HashSet::new(); + let mut total_signing_stake = token::Amount::zero(); + for VoteInfo { + validator_address, + validator_vp, + } in votes + { + if validator_vp == 0 { + continue; + } + // Ensure that the validator is not currently jailed or other + let state = validator_state_handle(&validator_address) + .get(storage, epoch, ¶ms)?; + if state != Some(ValidatorState::Consensus) { + return Err(InflationError::ExpectedValidatorInConsensus( + validator_address, + state, + )) + .into_storage_result(); + } + + let stake_from_deltas = + read_validator_stake(storage, ¶ms, &validator_address, epoch)?; + + // Ensure TM stake updates properly with a debug_assert + if cfg!(debug_assertions) { + debug_assert_eq!( + into_tm_voting_power( + params.tm_votes_per_token, + stake_from_deltas, + ), + i64::try_from(validator_vp).unwrap_or_default(), + ); + } + + signer_set.insert(validator_address); + total_signing_stake += stake_from_deltas; + } + + // Get the block rewards coefficients (proposing, signing/voting, + // consensus set status) + let rewards_calculator = PosRewardsCalculator { + proposer_reward: params.block_proposer_reward, + signer_reward: params.block_vote_reward, + signing_stake: total_signing_stake, + total_stake: total_consensus_stake, + }; + let coeffs = rewards_calculator + .get_reward_coeffs() + .map_err(InflationError::Rewards) + .into_storage_result()?; + tracing::debug!( + "PoS rewards coefficients {coeffs:?}, inputs: {rewards_calculator:?}." + ); + + // tracing::debug!( + // "TOTAL SIGNING STAKE (LOGGING BLOCK REWARDS) = {}", + // signing_stake + // ); + + // Compute the fractional block rewards for each consensus validator and + // update the reward accumulators + let consensus_stake_unscaled: Dec = total_consensus_stake.into(); + let signing_stake_unscaled: Dec = total_signing_stake.into(); + let mut values: HashMap = HashMap::new(); + for validator in consensus_validators.iter(storage)? { + let ( + NestedSubKey::Data { + key: stake, + nested_sub_key: _, + }, + address, + ) = validator?; + + if stake.is_zero() { + continue; + } + + let mut rewards_frac = Dec::zero(); + let stake_unscaled: Dec = stake.into(); + // tracing::debug!( + // "NAMADA VALIDATOR STAKE (LOGGING BLOCK REWARDS) OF EPOCH {} = + // {}", epoch, stake + // ); + + // Proposer reward + if address == *proposer_address { + rewards_frac += coeffs.proposer_coeff; + } + // Signer reward + if signer_set.contains(&address) { + let signing_frac = stake_unscaled / signing_stake_unscaled; + rewards_frac += coeffs.signer_coeff * signing_frac; + } + // Consensus validator reward + rewards_frac += coeffs.active_val_coeff + * (stake_unscaled / consensus_stake_unscaled); + + // To be added to the rewards accumulator + values.insert(address, rewards_frac); + } + for (address, value) in values.into_iter() { + // Update the rewards accumulator + rewards_accumulator_handle().update(storage, address, |prev| { + prev.unwrap_or_default() + value + })?; + } + + Ok(()) +} + +#[derive(Clone, Debug)] +struct Rewards { + product: Dec, + commissions: token::Amount, +} + +/// Update validator and delegators rewards products and mint the inflation +/// tokens into the PoS account. +/// Any left-over inflation tokens from rounding error of the sum of the +/// rewards is given to the governance address. +pub fn update_rewards_products_and_mint_inflation( + storage: &mut S, + params: &PosParams, + last_epoch: Epoch, + num_blocks_in_last_epoch: u64, + inflation: token::Amount, + staking_token: &Address, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + // Read the rewards accumulator and calculate the new rewards products + // for the previous epoch + let mut reward_tokens_remaining = inflation; + let mut new_rewards_products: HashMap = HashMap::new(); + let mut accumulators_sum = Dec::zero(); + for acc in rewards_accumulator_handle().iter(storage)? { + let (validator, value) = acc?; + accumulators_sum += value; + + // Get reward token amount for this validator + let fractional_claim = value / num_blocks_in_last_epoch; + let reward_tokens = fractional_claim * inflation; + + // Get validator stake at the last epoch + let stake = Dec::from(read_validator_stake( + storage, params, &validator, last_epoch, + )?); + + let commission_rate = validator_commission_rate_handle(&validator) + .get(storage, last_epoch, params)? + .expect("Should be able to find validator commission rate"); + + // Calculate the reward product from the whole validator stake and take + // out the commissions. Because we're using the whole stake to work with + // a single product, we're also taking out commission on validator's + // self-bonds, but it is then included in the rewards claimable by the + // validator so they get it back. + let product = + (Dec::one() - commission_rate) * Dec::from(reward_tokens) / stake; + + // Tally the commission tokens earned by the validator. + // TODO: think abt Dec rounding and if `new_product` should be used + // instead of `reward_tokens` + let commissions = commission_rate * reward_tokens; + + new_rewards_products.insert( + validator, + Rewards { + product, + commissions, + }, + ); + + reward_tokens_remaining -= reward_tokens; + } + for ( + validator, + Rewards { + product, + commissions, + }, + ) in new_rewards_products + { + validator_rewards_products_handle(&validator) + .insert(storage, last_epoch, product)?; + // The commissions belong to the validator + add_rewards_to_counter(storage, &validator, &validator, commissions)?; + } + + // Mint tokens to the PoS account for the last epoch's inflation + let pos_reward_tokens = inflation - reward_tokens_remaining; + tracing::info!( + "Minting tokens for PoS rewards distribution into the PoS account. \ + Amount: {}. Total inflation: {}, number of blocks in the last epoch: \ + {num_blocks_in_last_epoch}, reward accumulators sum: \ + {accumulators_sum}.", + pos_reward_tokens.to_string_native(), + inflation.to_string_native(), + ); + credit_tokens(storage, staking_token, &address::POS, pos_reward_tokens)?; + + if reward_tokens_remaining > token::Amount::zero() { + tracing::info!( + "Minting tokens remaining from PoS rewards distribution into the \ + Governance account. Amount: {}.", + reward_tokens_remaining.to_string_native() + ); + credit_tokens( + storage, + staking_token, + &address::GOV, + reward_tokens_remaining, + )?; + } + + // Clear validator rewards accumulators + storage.delete_prefix( + // The prefix of `rewards_accumulator_handle` + &storage_key::consensus_validator_rewards_accumulator_key(), + )?; + + Ok(()) +} + +/// Compute the current available rewards amount due only to existing bonds. +/// This does not include pending rewards held in the rewards counter due to +/// unbonds and redelegations. +pub fn compute_current_rewards_from_bonds( + storage: &S, + source: &Address, + validator: &Address, + current_epoch: Epoch, +) -> storage_api::Result +where + S: StorageRead, +{ + if current_epoch == Epoch::default() { + // Nothing to claim in the first epoch + return Ok(token::Amount::zero()); + } + + let last_claim_epoch = + get_last_reward_claim_epoch(storage, source, validator)?; + if let Some(last_epoch) = last_claim_epoch { + if last_epoch == current_epoch { + // Already claimed in this epoch + return Ok(token::Amount::zero()); + } + } + + let mut reward_tokens = token::Amount::zero(); + + // Want to claim from `last_claim_epoch` to `current_epoch.prev()` since + // rewards are computed at the end of an epoch + let (claim_start, claim_end) = ( + last_claim_epoch.unwrap_or_default(), + // Safe because of the check above + current_epoch.prev(), + ); + let bond_amounts = bond_amounts_for_rewards( + storage, + &BondId { + source: source.clone(), + validator: validator.clone(), + }, + claim_start, + claim_end, + )?; + + let rewards_products = validator_rewards_products_handle(validator); + for (ep, bond_amount) in bond_amounts { + debug_assert!(ep >= claim_start); + debug_assert!(ep <= claim_end); + let rp = rewards_products.get(storage, &ep)?.unwrap_or_default(); + let reward = rp * bond_amount; + reward_tokens += reward; + } + + Ok(reward_tokens) +} + +/// Add tokens to a rewards counter. +pub fn add_rewards_to_counter( + storage: &mut S, + source: &Address, + validator: &Address, + new_rewards: token::Amount, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let key = storage_key::rewards_counter_key(source, validator); + let current_rewards = + storage.read::(&key)?.unwrap_or_default(); + storage.write(&key, current_rewards + new_rewards) +} + +/// Take tokens from a rewards counter. Deletes the record after reading. +pub fn take_rewards_from_counter( + storage: &mut S, + source: &Address, + validator: &Address, +) -> storage_api::Result +where + S: StorageRead + StorageWrite, +{ + let key = storage_key::rewards_counter_key(source, validator); + let current_rewards = + storage.read::(&key)?.unwrap_or_default(); + storage.delete(&key)?; + Ok(current_rewards) +} + +/// Read the current token value in the rewards counter. +pub fn read_rewards_counter( + storage: &S, + source: &Address, + validator: &Address, +) -> storage_api::Result +where + S: StorageRead, +{ + let key = storage_key::rewards_counter_key(source, validator); + Ok(storage.read::(&key)?.unwrap_or_default()) +} diff --git a/proof_of_stake/src/slashing.rs b/proof_of_stake/src/slashing.rs new file mode 100644 index 0000000000..9e07cca0c7 --- /dev/null +++ b/proof_of_stake/src/slashing.rs @@ -0,0 +1,1124 @@ +//! Slashing tingzzzz + +use std::cmp::{self, Reverse}; +use std::collections::{BTreeMap, BTreeSet, HashMap}; + +use borsh::BorshDeserialize; +use namada_core::ledger::storage_api::collections::lazy_map::{ + Collectable, NestedMap, NestedSubKey, SubKey, +}; +use namada_core::ledger::storage_api::collections::LazyMap; +use namada_core::ledger::storage_api::{self, StorageRead, StorageWrite}; +use namada_core::types::address::Address; +use namada_core::types::dec::Dec; +use namada_core::types::storage::Epoch; +use namada_core::types::token; + +use crate::storage::{ + enqueued_slashes_handle, read_pos_params, read_validator_last_slash_epoch, + read_validator_stake, total_bonded_handle, total_unbonded_handle, + update_total_deltas, update_validator_deltas, + validator_outgoing_redelegations_handle, validator_slashes_handle, + validator_state_handle, validator_total_redelegated_bonded_handle, + validator_total_redelegated_unbonded_handle, + write_validator_last_slash_epoch, +}; +use crate::types::{ + EagerRedelegatedBondsMap, ResultSlashing, Slash, SlashType, SlashedAmount, + Slashes, TotalRedelegatedUnbonded, ValidatorState, +}; +use crate::validator_set_update::update_validator_set; +use crate::{ + fold_and_slash_redelegated_bonds, get_total_consensus_stake, + jail_validator, storage_key, EagerRedelegatedUnbonds, + FoldRedelegatedBondsResult, OwnedPosParams, PosParams, +}; + +/// Calculate the cubic slashing rate using all slashes within a window around +/// the given infraction epoch. There is no cap on the rate applied within this +/// function. +pub fn compute_cubic_slash_rate( + storage: &S, + params: &PosParams, + infraction_epoch: Epoch, +) -> storage_api::Result +where + S: StorageRead, +{ + tracing::debug!( + "Computing the cubic slash rate for infraction epoch \ + {infraction_epoch}." + ); + let mut sum_vp_fraction = Dec::zero(); + let (start_epoch, end_epoch) = + params.cubic_slash_epoch_window(infraction_epoch); + + for epoch in Epoch::iter_bounds_inclusive(start_epoch, end_epoch) { + let consensus_stake = + Dec::from(get_total_consensus_stake(storage, epoch, params)?); + tracing::debug!( + "Total consensus stake in epoch {}: {}", + epoch, + consensus_stake + ); + let processing_epoch = epoch + params.slash_processing_epoch_offset(); + let slashes = enqueued_slashes_handle().at(&processing_epoch); + let infracting_stake = slashes.iter(storage)?.fold( + Ok(Dec::zero()), + |acc: storage_api::Result, res| { + let acc = acc?; + let ( + NestedSubKey::Data { + key: validator, + nested_sub_key: _, + }, + _slash, + ) = res?; + + let validator_stake = + read_validator_stake(storage, params, &validator, epoch)?; + // tracing::debug!("Val {} stake: {}", &validator, + // validator_stake); + + Ok(acc + Dec::from(validator_stake)) + }, + )?; + sum_vp_fraction += infracting_stake / consensus_stake; + } + let cubic_rate = + Dec::new(9, 0).unwrap() * sum_vp_fraction * sum_vp_fraction; + tracing::debug!("Cubic slash rate: {}", cubic_rate); + Ok(cubic_rate) +} + +/// Record a slash for a misbehavior that has been received from Tendermint and +/// then jail the validator, removing it from the validator set. The slash rate +/// will be computed at a later epoch. +#[allow(clippy::too_many_arguments)] +pub fn slash( + storage: &mut S, + params: &PosParams, + current_epoch: Epoch, + evidence_epoch: Epoch, + evidence_block_height: impl Into, + slash_type: SlashType, + validator: &Address, + validator_set_update_epoch: Epoch, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let evidence_block_height: u64 = evidence_block_height.into(); + let slash = Slash { + epoch: evidence_epoch, + block_height: evidence_block_height, + r#type: slash_type, + rate: Dec::zero(), // Let the rate be 0 initially before processing + }; + // Need `+1` because we process at the beginning of a new epoch + let processing_epoch = + evidence_epoch + params.slash_processing_epoch_offset(); + + // Add the slash to the list of enqueued slashes to be processed at a later + // epoch + enqueued_slashes_handle() + .get_data_handler() + .at(&processing_epoch) + .at(validator) + .push(storage, slash)?; + + // Update the most recent slash (infraction) epoch for the validator + let last_slash_epoch = read_validator_last_slash_epoch(storage, validator)?; + if last_slash_epoch.is_none() + || evidence_epoch.0 > last_slash_epoch.unwrap_or_default().0 + { + write_validator_last_slash_epoch(storage, validator, evidence_epoch)?; + } + + // Jail the validator and update validator sets + jail_validator( + storage, + params, + validator, + current_epoch, + validator_set_update_epoch, + )?; + + // No other actions are performed here until the epoch in which the slash is + // processed. + + Ok(()) +} + +/// Process enqueued slashes that were discovered earlier. This function is +/// called upon a new epoch. The final slash rate considering according to the +/// cubic slashing rate is computed. Then, each slash is recorded in storage +/// along with its computed rate, and stake is deducted from the affected +/// validators. +pub fn process_slashes( + storage: &mut S, + current_epoch: Epoch, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let params = read_pos_params(storage)?; + + if current_epoch.0 < params.slash_processing_epoch_offset() { + return Ok(()); + } + let infraction_epoch = + current_epoch - params.slash_processing_epoch_offset(); + + // Slashes to be processed in the current epoch + let enqueued_slashes = enqueued_slashes_handle().at(¤t_epoch); + if enqueued_slashes.is_empty(storage)? { + return Ok(()); + } + tracing::debug!( + "Processing slashes at the beginning of epoch {} (committed in epoch \ + {})", + current_epoch, + infraction_epoch + ); + + // Compute the cubic slash rate + let cubic_slash_rate = + compute_cubic_slash_rate(storage, ¶ms, infraction_epoch)?; + + // Collect the enqueued slashes and update their rates + let mut eager_validator_slashes: BTreeMap> = + BTreeMap::new(); + let mut eager_validator_slash_rates: HashMap = HashMap::new(); + + // `slashPerValidator` and `slashesMap` while also updating in storage + for enqueued_slash in enqueued_slashes.iter(storage)? { + let ( + NestedSubKey::Data { + key: validator, + nested_sub_key: _, + }, + enqueued_slash, + ) = enqueued_slash?; + debug_assert_eq!(enqueued_slash.epoch, infraction_epoch); + + let slash_rate = cmp::min( + Dec::one(), + cmp::max( + enqueued_slash.r#type.get_slash_rate(¶ms), + cubic_slash_rate, + ), + ); + let updated_slash = Slash { + epoch: enqueued_slash.epoch, + block_height: enqueued_slash.block_height, + r#type: enqueued_slash.r#type, + rate: slash_rate, + }; + + let cur_slashes = eager_validator_slashes + .entry(validator.clone()) + .or_default(); + cur_slashes.push(updated_slash); + let cur_rate = + eager_validator_slash_rates.entry(validator).or_default(); + *cur_rate = cmp::min(Dec::one(), *cur_rate + slash_rate); + } + + // Update the epochs of enqueued slashes in storage + enqueued_slashes_handle().update_data(storage, ¶ms, current_epoch)?; + + // `resultSlashing` + let mut map_validator_slash: EagerRedelegatedBondsMap = BTreeMap::new(); + for (validator, slash_rate) in eager_validator_slash_rates { + process_validator_slash( + storage, + ¶ms, + &validator, + slash_rate, + current_epoch, + &mut map_validator_slash, + )?; + } + tracing::debug!("Slashed amounts for validators: {map_validator_slash:#?}"); + + // Now update the remaining parts of storage + + // Write slashes themselves into storage + for (validator, slashes) in eager_validator_slashes { + let validator_slashes = validator_slashes_handle(&validator); + for slash in slashes { + validator_slashes.push(storage, slash)?; + } + } + + // Update the validator stakes + for (validator, slash_amounts) in map_validator_slash { + let mut slash_acc = token::Amount::zero(); + + // Update validator sets first because it needs to be able to read + // validator stake before we make any changes to it + for (&epoch, &slash_amount) in &slash_amounts { + let state = validator_state_handle(&validator) + .get(storage, epoch, ¶ms)? + .unwrap(); + if state != ValidatorState::Jailed { + update_validator_set( + storage, + ¶ms, + &validator, + -slash_amount.change(), + epoch, + Some(0), + )?; + } + } + // Then update validator and total deltas + for (epoch, slash_amount) in slash_amounts { + let slash_delta = slash_amount - slash_acc; + slash_acc += slash_delta; + + update_validator_deltas( + storage, + ¶ms, + &validator, + -slash_delta.change(), + epoch, + Some(0), + )?; + update_total_deltas( + storage, + ¶ms, + -slash_delta.change(), + epoch, + Some(0), + )?; + } + + // TODO: should we clear some storage here as is done in Quint?? + // Possibly make the `unbonded` LazyMaps epoched so that it is done + // automatically? + } + + Ok(()) +} + +/// Process a slash by (i) slashing the misbehaving validator; and (ii) any +/// validator to which it has redelegated some tokens and the slash misbehaving +/// epoch is wihtin the redelegation slashing window. +/// +/// `validator` - the misbehaving validator. +/// `slash_rate` - the slash rate. +/// `slashed_amounts_map` - a map from validator address to a map from epoch to +/// already processed slash amounts. +/// +/// Adds any newly processed slash amount of any involved validator to +/// `slashed_amounts_map`. +// Quint `processSlash` +fn process_validator_slash( + storage: &mut S, + params: &PosParams, + validator: &Address, + slash_rate: Dec, + current_epoch: Epoch, + slashed_amount_map: &mut EagerRedelegatedBondsMap, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + // `resultSlashValidator + let result_slash = slash_validator( + storage, + params, + validator, + slash_rate, + current_epoch, + &slashed_amount_map + .get(validator) + .cloned() + .unwrap_or_default(), + )?; + + // `updatedSlashedAmountMap` + let validator_slashes = + slashed_amount_map.entry(validator.clone()).or_default(); + *validator_slashes = result_slash; + + // `outgoingRedelegation` + let outgoing_redelegations = + validator_outgoing_redelegations_handle(validator); + + // Final loop in `processSlash` + let dest_validators = outgoing_redelegations + .iter(storage)? + .map(|res| { + let ( + NestedSubKey::Data { + key: dest_validator, + nested_sub_key: _, + }, + _redelegation, + ) = res?; + Ok(dest_validator) + }) + .collect::>>()?; + + for dest_validator in dest_validators { + let to_modify = slashed_amount_map + .entry(dest_validator.clone()) + .or_default(); + + tracing::debug!( + "Slashing {} redelegation to {}", + validator, + &dest_validator + ); + + // `slashValidatorRedelegation` + slash_validator_redelegation( + storage, + params, + validator, + current_epoch, + &outgoing_redelegations.at(&dest_validator), + &validator_slashes_handle(validator), + &validator_total_redelegated_unbonded_handle(&dest_validator), + slash_rate, + to_modify, + )?; + } + + Ok(()) +} + +/// In the context of a redelegation, the function computes how much a validator +/// (the destination validator of the redelegation) should be slashed due to the +/// misbehaving of a second validator (the source validator of the +/// redelegation). The function computes how much the validator whould be +/// slashed at all epochs between the current epoch (curEpoch) + 1 and the +/// current epoch + 1 + PIPELINE_OFFSET, accounting for any tokens of the +/// redelegation already unbonded. +/// +/// - `src_validator` - the source validator +/// - `outgoing_redelegations` - a map from pair of epochs to int that includes +/// all the redelegations from the source validator to the destination +/// validator. +/// - The outer key is epoch at which the bond started at the source +/// validator. +/// - The inner key is epoch at which the redelegation started (the epoch at +/// which was issued). +/// - `slashes` a list of slashes of the source validator. +/// - `dest_total_redelegated_unbonded` - a map of unbonded redelegated tokens +/// at the destination validator. +/// - `slash_rate` - the rate of the slash being processed. +/// - `dest_slashed_amounts` - a map from epoch to already processed slash +/// amounts. +/// +/// Adds any newly processed slash amount to `dest_slashed_amounts`. +#[allow(clippy::too_many_arguments)] +pub fn slash_validator_redelegation( + storage: &S, + params: &OwnedPosParams, + src_validator: &Address, + current_epoch: Epoch, + outgoing_redelegations: &NestedMap>, + slashes: &Slashes, + dest_total_redelegated_unbonded: &TotalRedelegatedUnbonded, + slash_rate: Dec, + dest_slashed_amounts: &mut BTreeMap, +) -> storage_api::Result<()> +where + S: StorageRead, +{ + let infraction_epoch = + current_epoch - params.slash_processing_epoch_offset(); + + for res in outgoing_redelegations.iter(storage)? { + let ( + NestedSubKey::Data { + key: bond_start, + nested_sub_key: SubKey::Data(redel_start), + }, + amount, + ) = res?; + + if params.in_redelegation_slashing_window( + infraction_epoch, + redel_start, + params.redelegation_end_epoch_from_start(redel_start), + ) && bond_start <= infraction_epoch + { + slash_redelegation( + storage, + params, + amount, + bond_start, + params.redelegation_end_epoch_from_start(redel_start), + src_validator, + current_epoch, + slashes, + dest_total_redelegated_unbonded, + slash_rate, + dest_slashed_amounts, + )?; + } + } + + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +pub fn slash_redelegation( + storage: &S, + params: &OwnedPosParams, + amount: token::Amount, + bond_start: Epoch, + redel_bond_start: Epoch, + src_validator: &Address, + current_epoch: Epoch, + slashes: &Slashes, + total_redelegated_unbonded: &TotalRedelegatedUnbonded, + slash_rate: Dec, + slashed_amounts: &mut BTreeMap, +) -> storage_api::Result<()> +where + S: StorageRead, +{ + tracing::debug!( + "\nSlashing redelegation amount {} - bond start {} and \ + redel_bond_start {} - at rate {}\n", + amount.to_string_native(), + bond_start, + redel_bond_start, + slash_rate + ); + + let infraction_epoch = + current_epoch - params.slash_processing_epoch_offset(); + + // Slash redelegation destination validator from the next epoch only + // as they won't be jailed + let set_update_epoch = current_epoch.next(); + + let mut init_tot_unbonded = + Epoch::iter_bounds_inclusive(infraction_epoch.next(), set_update_epoch) + .map(|epoch| { + let redelegated_unbonded = total_redelegated_unbonded + .at(&epoch) + .at(&redel_bond_start) + .at(src_validator) + .get(storage, &bond_start)? + .unwrap_or_default(); + Ok(redelegated_unbonded) + }) + .sum::>()?; + + for epoch in Epoch::iter_range(set_update_epoch, params.pipeline_len) { + let updated_total_unbonded = { + let redelegated_unbonded = total_redelegated_unbonded + .at(&epoch) + .at(&redel_bond_start) + .at(src_validator) + .get(storage, &bond_start)? + .unwrap_or_default(); + init_tot_unbonded + redelegated_unbonded + }; + + let list_slashes = slashes + .iter(storage)? + .map(Result::unwrap) + .filter(|slash| { + params.in_redelegation_slashing_window( + slash.epoch, + params.redelegation_start_epoch_from_end(redel_bond_start), + redel_bond_start, + ) && bond_start <= slash.epoch + && slash.epoch + params.slash_processing_epoch_offset() + // We're looking for slashes that were processed before or in the epoch + // in which slashes that are currently being processed + // occurred. Because we're slashing in the beginning of an + // epoch, we're also taking slashes that were processed in + // the infraction epoch as they would still be processed + // before any infraction occurred. + <= infraction_epoch + }) + .collect::>(); + + let slashable_amount = amount + .checked_sub(updated_total_unbonded) + .unwrap_or_default(); + + let slashed = + apply_list_slashes(params, &list_slashes, slashable_amount) + .mul_ceil(slash_rate); + + let list_slashes = slashes + .iter(storage)? + .map(Result::unwrap) + .filter(|slash| { + params.in_redelegation_slashing_window( + slash.epoch, + params.redelegation_start_epoch_from_end(redel_bond_start), + redel_bond_start, + ) && bond_start <= slash.epoch + }) + .collect::>(); + + let slashable_stake = + apply_list_slashes(params, &list_slashes, slashable_amount) + .mul_ceil(slash_rate); + + init_tot_unbonded = updated_total_unbonded; + let to_slash = cmp::min(slashed, slashable_stake); + if !to_slash.is_zero() { + let map_value = slashed_amounts.entry(epoch).or_default(); + *map_value += to_slash; + } + } + + Ok(()) +} + +/// Computes for a given validator and a slash how much should be slashed at all +/// epochs between the currentÃ¥ epoch (curEpoch) + 1 and the current epoch + 1 + +/// PIPELINE_OFFSET, accounting for any tokens already unbonded. +/// +/// - `validator` - the misbehaving validator. +/// - `slash_rate` - the rate of the slash being processed. +/// - `slashed_amounts_map` - a map from epoch to already processed slash +/// amounts. +/// +/// Returns a map that adds any newly processed slash amount to +/// `slashed_amounts_map`. +// `def slashValidator` +pub fn slash_validator( + storage: &S, + params: &OwnedPosParams, + validator: &Address, + slash_rate: Dec, + current_epoch: Epoch, + slashed_amounts_map: &BTreeMap, +) -> storage_api::Result> +where + S: StorageRead, +{ + tracing::debug!("Slashing validator {} at rate {}", validator, slash_rate); + let infraction_epoch = + current_epoch - params.slash_processing_epoch_offset(); + + let total_unbonded = total_unbonded_handle(validator); + let total_redelegated_unbonded = + validator_total_redelegated_unbonded_handle(validator); + let total_bonded = total_bonded_handle(validator); + let total_redelegated_bonded = + validator_total_redelegated_bonded_handle(validator); + + let mut slashed_amounts = slashed_amounts_map.clone(); + + let mut tot_bonds = total_bonded + .get_data_handler() + .iter(storage)? + .map(Result::unwrap) + .filter(|&(epoch, bonded)| { + epoch <= infraction_epoch && bonded > 0.into() + }) + .collect::>(); + + let mut redelegated_bonds = tot_bonds + .keys() + .filter(|&epoch| { + !total_redelegated_bonded + .at(epoch) + .is_empty(storage) + .unwrap() + }) + .map(|epoch| { + let tot_redel_bonded = total_redelegated_bonded + .at(epoch) + .collect_map(storage) + .unwrap(); + (*epoch, tot_redel_bonded) + }) + .collect::>(); + + let mut sum = token::Amount::zero(); + + let eps = current_epoch + .iter_range(params.pipeline_len) + .collect::>(); + for epoch in eps.into_iter().rev() { + let amount = tot_bonds.iter().fold( + token::Amount::zero(), + |acc, (bond_start, bond_amount)| { + acc + compute_slash_bond_at_epoch( + storage, + params, + validator, + epoch, + infraction_epoch, + *bond_start, + *bond_amount, + redelegated_bonds.get(bond_start), + slash_rate, + ) + .unwrap() + }, + ); + + let new_bonds = total_unbonded.at(&epoch); + tot_bonds = new_bonds + .collect_map(storage) + .unwrap() + .into_iter() + .filter(|(ep, _)| *ep <= infraction_epoch) + .collect::>(); + + let new_redelegated_bonds = tot_bonds + .keys() + .filter(|&ep| { + !total_redelegated_unbonded.at(ep).is_empty(storage).unwrap() + }) + .map(|ep| { + ( + *ep, + total_redelegated_unbonded + .at(&epoch) + .at(ep) + .collect_map(storage) + .unwrap(), + ) + }) + .collect::>(); + + redelegated_bonds = new_redelegated_bonds; + + // `newSum` + sum += amount; + + // `newSlashesMap` + let cur = slashed_amounts.entry(epoch).or_default(); + *cur += sum; + } + // Hack - should this be done differently? (think this is safe) + let pipeline_epoch = current_epoch + params.pipeline_len; + let last_amt = slashed_amounts + .get(&pipeline_epoch.prev()) + .cloned() + .unwrap(); + slashed_amounts.insert(pipeline_epoch, last_amt); + + Ok(slashed_amounts) +} + +/// Get the remaining token amount in a bond after applying a set of slashes. +/// +/// - `validator` - the bond's validator +/// - `epoch` - the latest slash epoch to consider. +/// - `start` - the start epoch of the bond +/// - `redelegated_bonds` +pub fn compute_bond_at_epoch( + storage: &S, + params: &OwnedPosParams, + validator: &Address, + epoch: Epoch, + start: Epoch, + amount: token::Amount, + redelegated_bonds: Option<&EagerRedelegatedBondsMap>, +) -> storage_api::Result +where + S: StorageRead, +{ + let list_slashes = validator_slashes_handle(validator) + .iter(storage)? + .map(Result::unwrap) + .filter(|slash| { + start <= slash.epoch + && slash.epoch + params.slash_processing_epoch_offset() <= epoch + }) + .collect::>(); + + let slash_epoch_filter = + |e: Epoch| e + params.slash_processing_epoch_offset() <= epoch; + + let result_fold = redelegated_bonds + .map(|redelegated_bonds| { + fold_and_slash_redelegated_bonds( + storage, + params, + redelegated_bonds, + start, + &list_slashes, + slash_epoch_filter, + ) + }) + .unwrap_or_default(); + + let total_not_redelegated = amount - result_fold.total_redelegated; + let after_not_redelegated = + apply_list_slashes(params, &list_slashes, total_not_redelegated); + + Ok(after_not_redelegated + result_fold.total_after_slashing) +} + +/// Uses `fn compute_bond_at_epoch` to compute the token amount to slash in +/// order to prevent overslashing. +#[allow(clippy::too_many_arguments)] +pub fn compute_slash_bond_at_epoch( + storage: &S, + params: &OwnedPosParams, + validator: &Address, + epoch: Epoch, + infraction_epoch: Epoch, + bond_start: Epoch, + bond_amount: token::Amount, + redelegated_bonds: Option<&EagerRedelegatedBondsMap>, + slash_rate: Dec, +) -> storage_api::Result +where + S: StorageRead, +{ + let amount_due = compute_bond_at_epoch( + storage, + params, + validator, + infraction_epoch, + bond_start, + bond_amount, + redelegated_bonds, + )? + .mul_ceil(slash_rate); + let slashable_amount = compute_bond_at_epoch( + storage, + params, + validator, + epoch, + bond_start, + bond_amount, + redelegated_bonds, + )?; + Ok(cmp::min(amount_due, slashable_amount)) +} + +/// Find slashes applicable to a validator with inclusive `start` and exclusive +/// `end` epoch. +#[allow(dead_code)] +pub fn find_slashes_in_range( + storage: &S, + start: Epoch, + end: Option, + validator: &Address, +) -> storage_api::Result> +where + S: StorageRead, +{ + let mut slashes = BTreeMap::::new(); + for slash in validator_slashes_handle(validator).iter(storage)? { + let slash = slash?; + if start <= slash.epoch + && end.map(|end| slash.epoch < end).unwrap_or(true) + { + let cur_rate = slashes.entry(slash.epoch).or_default(); + *cur_rate = cmp::min(*cur_rate + slash.rate, Dec::one()); + } + } + Ok(slashes) +} + +/// Computes how much remains from an amount of tokens after applying a list of +/// slashes. +/// +/// - `slashes` - a list of slashes ordered by misbehaving epoch. +/// - `amount` - the amount of slashable tokens. +// `def applyListSlashes` +pub fn apply_list_slashes( + params: &OwnedPosParams, + slashes: &[Slash], + amount: token::Amount, +) -> token::Amount { + let mut final_amount = amount; + let mut computed_slashes = BTreeMap::::new(); + for slash in slashes { + let slashed_amount = + compute_slashable_amount(params, slash, amount, &computed_slashes); + final_amount = + final_amount.checked_sub(slashed_amount).unwrap_or_default(); + computed_slashes.insert(slash.epoch, slashed_amount); + } + final_amount +} + +/// Computes how much is left from a bond or unbond after applying a slash given +/// that a set of slashes may have been previously applied. +// `def computeSlashableAmount` +pub fn compute_slashable_amount( + params: &OwnedPosParams, + slash: &Slash, + amount: token::Amount, + computed_slashes: &BTreeMap, +) -> token::Amount { + let updated_amount = computed_slashes + .iter() + .filter(|(&epoch, _)| { + // Keep slashes that have been applied and processed before the + // current slash occurred. We use `<=` because slashes processed at + // `slash.epoch` (at the start of the epoch) are also processed + // before this slash occurred. + epoch + params.slash_processing_epoch_offset() <= slash.epoch + }) + .fold(amount, |acc, (_, &amnt)| { + acc.checked_sub(amnt).unwrap_or_default() + }); + updated_amount.mul_ceil(slash.rate) +} + +/// Find all slashes and the associated validators in the PoS system +pub fn find_all_slashes( + storage: &S, +) -> storage_api::Result>> +where + S: StorageRead, +{ + let mut slashes: HashMap> = HashMap::new(); + let slashes_iter = storage_api::iter_prefix_bytes( + storage, + &storage_key::slashes_prefix(), + )? + .filter_map(|result| { + if let Ok((key, val_bytes)) = result { + if let Some(validator) = storage_key::is_validator_slashes_key(&key) + { + let slash: Slash = + BorshDeserialize::try_from_slice(&val_bytes).ok()?; + return Some((validator, slash)); + } + } + None + }); + + slashes_iter.for_each(|(address, slash)| match slashes.get(&address) { + Some(vec) => { + let mut vec = vec.clone(); + vec.push(slash); + slashes.insert(address, vec); + } + None => { + slashes.insert(address, vec![slash]); + } + }); + Ok(slashes) +} + +/// Collect the details of all of the enqueued slashes to be processed in future +/// epochs into a nested map +pub fn find_all_enqueued_slashes( + storage: &S, + epoch: Epoch, +) -> storage_api::Result>>> +where + S: StorageRead, +{ + let mut enqueued = HashMap::>>::new(); + for res in enqueued_slashes_handle().get_data_handler().iter(storage)? { + let ( + NestedSubKey::Data { + key: processing_epoch, + nested_sub_key: + NestedSubKey::Data { + key: address, + nested_sub_key: _, + }, + }, + slash, + ) = res?; + if processing_epoch <= epoch { + continue; + } + + let slashes = enqueued + .entry(address) + .or_default() + .entry(processing_epoch) + .or_default(); + slashes.push(slash); + } + Ok(enqueued) +} + +/// Find PoS slashes applied to a validator, if any +pub fn find_validator_slashes( + storage: &S, + validator: &Address, +) -> storage_api::Result> +where + S: StorageRead, +{ + validator_slashes_handle(validator).iter(storage)?.collect() +} + +/// Compute a token amount after slashing, given the initial amount and a set of +/// slashes. It is assumed that the input `slashes` are those commited while the +/// `amount` was contributing to voting power. +pub fn get_slashed_amount( + params: &PosParams, + amount: token::Amount, + slashes: &BTreeMap, +) -> storage_api::Result { + let mut updated_amount = amount; + let mut computed_amounts = Vec::::new(); + + for (&infraction_epoch, &slash_rate) in slashes { + let mut computed_to_remove = BTreeSet::>::new(); + for (ix, slashed_amount) in computed_amounts.iter().enumerate() { + // Update amount with slashes that happened more than unbonding_len + // epochs before this current slash + if slashed_amount.epoch + params.slash_processing_epoch_offset() + <= infraction_epoch + { + updated_amount = updated_amount + .checked_sub(slashed_amount.amount) + .unwrap_or_default(); + computed_to_remove.insert(Reverse(ix)); + } + } + // Invariant: `computed_to_remove` must be in reverse ord to avoid + // left-shift of the `computed_amounts` after call to `remove` + // invalidating the rest of the indices. + for item in computed_to_remove { + computed_amounts.remove(item.0); + } + computed_amounts.push(SlashedAmount { + amount: updated_amount.mul_ceil(slash_rate), + epoch: infraction_epoch, + }); + } + + let total_computed_amounts = computed_amounts + .into_iter() + .map(|slashed| slashed.amount) + .sum(); + + let final_amount = updated_amount + .checked_sub(total_computed_amounts) + .unwrap_or_default(); + + Ok(final_amount) +} + +// `def computeAmountAfterSlashingUnbond` +pub fn compute_amount_after_slashing_unbond( + storage: &S, + params: &OwnedPosParams, + unbonds: &BTreeMap, + redelegated_unbonds: &EagerRedelegatedUnbonds, + slashes: Vec, +) -> storage_api::Result +where + S: StorageRead, +{ + let mut result_slashing = ResultSlashing::default(); + for (&start_epoch, amount) in unbonds { + // `val listSlashes` + let list_slashes: Vec = slashes + .iter() + .filter(|slash| slash.epoch >= start_epoch) + .cloned() + .collect(); + // `val resultFold` + let result_fold = if let Some(redelegated_unbonds) = + redelegated_unbonds.get(&start_epoch) + { + fold_and_slash_redelegated_bonds( + storage, + params, + redelegated_unbonds, + start_epoch, + &list_slashes, + |_| true, + ) + } else { + FoldRedelegatedBondsResult::default() + }; + // `val totalNoRedelegated` + let total_not_redelegated = amount + .checked_sub(result_fold.total_redelegated) + .unwrap_or_default(); + // `val afterNoRedelegated` + let after_not_redelegated = + apply_list_slashes(params, &list_slashes, total_not_redelegated); + // `val amountAfterSlashing` + let amount_after_slashing = + after_not_redelegated + result_fold.total_after_slashing; + // Accumulation step + result_slashing.sum += amount_after_slashing; + result_slashing + .epoch_map + .insert(start_epoch, amount_after_slashing); + } + Ok(result_slashing) +} + +/// Compute from a set of unbonds (both redelegated and not) how much is left +/// after applying all relevant slashes. +// `def computeAmountAfterSlashingWithdraw` +pub fn compute_amount_after_slashing_withdraw( + storage: &S, + params: &OwnedPosParams, + unbonds_and_redelegated_unbonds: &BTreeMap< + (Epoch, Epoch), + (token::Amount, EagerRedelegatedBondsMap), + >, + slashes: Vec, +) -> storage_api::Result +where + S: StorageRead, +{ + let mut result_slashing = ResultSlashing::default(); + + for ((start_epoch, withdraw_epoch), (amount, redelegated_unbonds)) in + unbonds_and_redelegated_unbonds.iter() + { + // TODO: check if slashes in the same epoch can be + // folded into one effective slash + let end_epoch = *withdraw_epoch + - params.unbonding_len + - params.cubic_slashing_window_length; + // Find slashes that apply to `start_epoch..end_epoch` + let list_slashes = slashes + .iter() + .filter(|slash| { + // Started before the slash occurred + start_epoch <= &slash.epoch + // Ends after the slash + && end_epoch > slash.epoch + }) + .cloned() + .collect::>(); + + // Find the sum and the sum after slashing of the redelegated unbonds + let result_fold = fold_and_slash_redelegated_bonds( + storage, + params, + redelegated_unbonds, + *start_epoch, + &list_slashes, + |_| true, + ); + + // Unbond amount that didn't come from a redelegation + let total_not_redelegated = *amount - result_fold.total_redelegated; + // Find how much remains after slashing non-redelegated amount + let after_not_redelegated = + apply_list_slashes(params, &list_slashes, total_not_redelegated); + + // Add back the unbond and redelegated unbond amount after slashing + let amount_after_slashing = + after_not_redelegated + result_fold.total_after_slashing; + + result_slashing.sum += amount_after_slashing; + result_slashing + .epoch_map + .insert(*start_epoch, amount_after_slashing); + } + + Ok(result_slashing) +} diff --git a/proof_of_stake/src/storage.rs b/proof_of_stake/src/storage.rs index b76760650b..bab677b3a0 100644 --- a/proof_of_stake/src/storage.rs +++ b/proof_of_stake/src/storage.rs @@ -1,852 +1,848 @@ -//! Proof-of-Stake storage keys and storage integration. +//! PoS functions for reading and writing to storage and lazy collection handles +//! associated with given `storage_key`s. -use namada_core::ledger::storage_api::collections::{lazy_map, lazy_vec}; -use namada_core::types::address::Address; -use namada_core::types::storage::{DbKeySeg, Epoch, Key, KeySeg}; - -use super::ADDRESS; -use crate::epoched::LAZY_MAP_SUB_KEY; -use crate::types::BondId; - -const PARAMS_STORAGE_KEY: &str = "params"; -const VALIDATOR_ADDRESSES_KEY: &str = "validator_addresses"; -#[allow(missing_docs)] -pub const VALIDATOR_STORAGE_PREFIX: &str = "validator"; -const VALIDATOR_ADDRESS_RAW_HASH: &str = "address_raw_hash"; -const VALIDATOR_CONSENSUS_KEY_STORAGE_KEY: &str = "consensus_key"; -const VALIDATOR_ETH_COLD_KEY_STORAGE_KEY: &str = "eth_cold_key"; -const VALIDATOR_ETH_HOT_KEY_STORAGE_KEY: &str = "eth_hot_key"; -const VALIDATOR_STATE_STORAGE_KEY: &str = "state"; -const VALIDATOR_DELTAS_STORAGE_KEY: &str = "deltas"; -const VALIDATOR_COMMISSION_RATE_STORAGE_KEY: &str = "commission_rate"; -const VALIDATOR_MAX_COMMISSION_CHANGE_STORAGE_KEY: &str = - "max_commission_rate_change"; -const VALIDATOR_REWARDS_PRODUCT_KEY: &str = "validator_rewards_product"; -const VALIDATOR_LAST_KNOWN_PRODUCT_EPOCH_KEY: &str = - "last_known_rewards_product_epoch"; -const SLASHES_PREFIX: &str = "slash"; -const ENQUEUED_SLASHES_KEY: &str = "enqueued_slashes"; -const VALIDATOR_LAST_SLASH_EPOCH: &str = "last_slash_epoch"; -const BOND_STORAGE_KEY: &str = "bond"; -const UNBOND_STORAGE_KEY: &str = "unbond"; -const VALIDATOR_TOTAL_BONDED_STORAGE_KEY: &str = "total_bonded"; -const VALIDATOR_TOTAL_UNBONDED_STORAGE_KEY: &str = "total_unbonded"; -const VALIDATOR_SETS_STORAGE_PREFIX: &str = "validator_sets"; -const CONSENSUS_VALIDATOR_SET_STORAGE_KEY: &str = "consensus"; -const BELOW_CAPACITY_VALIDATOR_SET_STORAGE_KEY: &str = "below_capacity"; -const TOTAL_CONSENSUS_STAKE_STORAGE_KEY: &str = "total_consensus_stake"; -const TOTAL_DELTAS_STORAGE_KEY: &str = "total_deltas"; -const VALIDATOR_SET_POSITIONS_KEY: &str = "validator_set_positions"; -const CONSENSUS_KEYS: &str = "consensus_keys"; -const LAST_BLOCK_PROPOSER_STORAGE_KEY: &str = "last_block_proposer"; -const CONSENSUS_VALIDATOR_SET_ACCUMULATOR_STORAGE_KEY: &str = - "validator_rewards_accumulator"; -const LAST_REWARD_CLAIM_EPOCH: &str = "last_reward_claim_epoch"; -const REWARDS_COUNTER_KEY: &str = "validator_rewards_commissions"; -const VALIDATOR_INCOMING_REDELEGATIONS_KEY: &str = "incoming_redelegations"; -const VALIDATOR_OUTGOING_REDELEGATIONS_KEY: &str = "outgoing_redelegations"; -const VALIDATOR_TOTAL_REDELEGATED_BONDED_KEY: &str = "total_redelegated_bonded"; -const VALIDATOR_TOTAL_REDELEGATED_UNBONDED_KEY: &str = - "total_redelegated_unbonded"; -const DELEGATOR_REDELEGATED_BONDS_KEY: &str = "delegator_redelegated_bonds"; -const DELEGATOR_REDELEGATED_UNBONDS_KEY: &str = "delegator_redelegated_unbonds"; -const VALIDATOR_EMAIL_KEY: &str = "email"; -const VALIDATOR_DESCRIPTION_KEY: &str = "description"; -const VALIDATOR_WEBSITE_KEY: &str = "website"; -const VALIDATOR_DISCORD_KEY: &str = "discord_handle"; -const LIVENESS_PREFIX: &str = "liveness"; -const LIVENESS_MISSED_VOTES: &str = "missed_votes"; -const LIVENESS_MISSED_VOTES_SUM: &str = "sum_missed_votes"; - -/// Is the given key a PoS storage key? -pub fn is_pos_key(key: &Key) -> bool { - match &key.segments.get(0) { - Some(DbKeySeg::AddressSeg(addr)) => addr == &ADDRESS, - _ => false, - } -} - -/// Storage key for PoS parameters. -pub fn params_key() -> Key { - Key::from(ADDRESS.to_db_key()) - .push(&PARAMS_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} - -/// Is storage key for PoS parameters? -pub fn is_params_key(key: &Key) -> bool { - matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(key)] if addr == &ADDRESS && key == PARAMS_STORAGE_KEY) -} - -/// Storage key prefix for validator data. -fn validator_prefix(validator: &Address) -> Key { - Key::from(ADDRESS.to_db_key()) - .push(&VALIDATOR_STORAGE_PREFIX.to_owned()) - .expect("Cannot obtain a storage key") - .push(&validator.to_db_key()) - .expect("Cannot obtain a storage key") -} - -/// Storage key for validator's address raw hash for look-up from raw hash of an -/// address to address. -pub fn validator_address_raw_hash_key(raw_hash: impl AsRef) -> Key { - let raw_hash = raw_hash.as_ref().to_owned(); - Key::from(ADDRESS.to_db_key()) - .push(&VALIDATOR_ADDRESS_RAW_HASH.to_owned()) - .expect("Cannot obtain a storage key") - .push(&raw_hash) - .expect("Cannot obtain a storage key") -} - -/// Is storage key for validator's address raw hash? -pub fn is_validator_address_raw_hash_key(key: &Key) -> Option<&str> { - match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(prefix), - DbKeySeg::StringSeg(raw_hash), - ] if addr == &ADDRESS && prefix == VALIDATOR_ADDRESS_RAW_HASH => { - Some(raw_hash) - } - _ => None, - } -} +use std::collections::{BTreeSet, HashSet}; -/// Storage key for validator's consensus key. -pub fn validator_consensus_key_key(validator: &Address) -> Key { - validator_prefix(validator) - .push(&VALIDATOR_CONSENSUS_KEY_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} - -/// Is storage key for validator's consensus key? -pub fn is_validator_consensus_key_key(key: &Key) -> Option<&Address> { - match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(prefix), - DbKeySeg::AddressSeg(validator), - DbKeySeg::StringSeg(key), - ] if addr == &ADDRESS - && prefix == VALIDATOR_STORAGE_PREFIX - && key == VALIDATOR_CONSENSUS_KEY_STORAGE_KEY => - { - Some(validator) - } - _ => None, - } +use namada_core::ledger::storage_api::collections::lazy_map::NestedSubKey; +use namada_core::ledger::storage_api::collections::{LazyCollection, LazySet}; +use namada_core::ledger::storage_api::governance::get_max_proposal_period; +use namada_core::ledger::storage_api::{ + self, Result, StorageRead, StorageWrite, +}; +use namada_core::types::address::Address; +use namada_core::types::dec::Dec; +use namada_core::types::key::{ + common, protocol_pk_key, tm_consensus_key_raw_hash, +}; +use namada_core::types::storage::Epoch; +use namada_core::types::token; + +use crate::storage_key::consensus_keys_key; +use crate::types::{ + BelowCapacityValidatorSets, BondId, Bonds, CommissionRates, + ConsensusValidatorSets, DelegatorRedelegatedBonded, + DelegatorRedelegatedUnbonded, EpochedSlashes, IncomingRedelegations, + LivenessMissedVotes, LivenessSumMissedVotes, OutgoingRedelegations, + ReverseOrdTokenAmount, RewardsAccumulator, RewardsProducts, Slashes, + TotalConsensusStakes, TotalDeltas, TotalRedelegatedBonded, + TotalRedelegatedUnbonded, Unbonds, ValidatorAddresses, + ValidatorConsensusKeys, ValidatorDeltas, ValidatorEthColdKeys, + ValidatorEthHotKeys, ValidatorMetaData, ValidatorProtocolKeys, + ValidatorSetPositions, ValidatorState, ValidatorStates, + ValidatorTotalUnbonded, WeightedValidator, +}; +use crate::{storage_key, MetadataError, OwnedPosParams, PosParams}; + +// ---- Storage handles ---- + +/// Get the storage handle to the epoched consensus validator set +pub fn consensus_validator_set_handle() -> ConsensusValidatorSets { + let key = storage_key::consensus_validator_set_key(); + ConsensusValidatorSets::open(key) +} + +/// Get the storage handle to the epoched below-capacity validator set +pub fn below_capacity_validator_set_handle() -> BelowCapacityValidatorSets { + let key = storage_key::below_capacity_validator_set_key(); + BelowCapacityValidatorSets::open(key) +} + +/// Get the storage handle to a PoS validator's consensus key (used for +/// signing block votes). +pub fn validator_consensus_key_handle( + validator: &Address, +) -> ValidatorConsensusKeys { + let key = storage_key::validator_consensus_key_key(validator); + ValidatorConsensusKeys::open(key) } -/// Storage key for validator's eth cold key. -pub fn validator_eth_cold_key_key(validator: &Address) -> Key { - validator_prefix(validator) - .push(&VALIDATOR_ETH_COLD_KEY_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} - -/// Is storage key for validator's eth cold key? -pub fn is_validator_eth_cold_key_key(key: &Key) -> Option<&Address> { - match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(prefix), - DbKeySeg::AddressSeg(validator), - DbKeySeg::StringSeg(key), - ] if addr == &ADDRESS - && prefix == VALIDATOR_STORAGE_PREFIX - && key == VALIDATOR_ETH_COLD_KEY_STORAGE_KEY => - { - Some(validator) - } - _ => None, - } +/// Get the storage handle to a PoS validator's protocol key key. +pub fn validator_protocol_key_handle( + validator: &Address, +) -> ValidatorProtocolKeys { + let key = protocol_pk_key(validator); + ValidatorProtocolKeys::open(key) } -/// Storage key for validator's eth hot key. -pub fn validator_eth_hot_key_key(validator: &Address) -> Key { - validator_prefix(validator) - .push(&VALIDATOR_ETH_HOT_KEY_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} - -/// Is storage key for validator's eth hot key? -pub fn is_validator_eth_hot_key_key(key: &Key) -> Option<&Address> { - match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(prefix), - DbKeySeg::AddressSeg(validator), - DbKeySeg::StringSeg(key), - ] if addr == &ADDRESS - && prefix == VALIDATOR_STORAGE_PREFIX - && key == VALIDATOR_ETH_HOT_KEY_STORAGE_KEY => - { - Some(validator) - } - _ => None, - } +/// Get the storage handle to a PoS validator's eth hot key. +pub fn validator_eth_hot_key_handle( + validator: &Address, +) -> ValidatorEthHotKeys { + let key = storage_key::validator_eth_hot_key_key(validator); + ValidatorEthHotKeys::open(key) } -/// Storage key for validator's commission rate. -pub fn validator_commission_rate_key(validator: &Address) -> Key { - validator_prefix(validator) - .push(&VALIDATOR_COMMISSION_RATE_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} - -/// Is storage key for validator's commission rate? -pub fn is_validator_commission_rate_key( - key: &Key, -) -> Option<(&Address, Epoch)> { - match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(prefix), - DbKeySeg::AddressSeg(validator), - DbKeySeg::StringSeg(key), - DbKeySeg::StringSeg(lazy_map), - DbKeySeg::StringSeg(data), - DbKeySeg::StringSeg(epoch), - ] if addr == &ADDRESS - && prefix == VALIDATOR_STORAGE_PREFIX - && key == VALIDATOR_COMMISSION_RATE_STORAGE_KEY - && lazy_map == LAZY_MAP_SUB_KEY - && data == lazy_map::DATA_SUBKEY => - { - let epoch = Epoch::parse(epoch.clone()) - .expect("Should be able to parse the epoch"); - Some((validator, epoch)) - } - _ => None, - } +/// Get the storage handle to a PoS validator's eth cold key. +pub fn validator_eth_cold_key_handle( + validator: &Address, +) -> ValidatorEthColdKeys { + let key = storage_key::validator_eth_cold_key_key(validator); + ValidatorEthColdKeys::open(key) } -/// Storage key for validator's maximum commission rate change per epoch. -pub fn validator_max_commission_rate_change_key(validator: &Address) -> Key { - validator_prefix(validator) - .push(&VALIDATOR_MAX_COMMISSION_CHANGE_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} - -/// Is storage key for validator's maximum commission rate change per epoch? -pub fn is_validator_max_commission_rate_change_key( - key: &Key, -) -> Option<&Address> { - match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(prefix), - DbKeySeg::AddressSeg(validator), - DbKeySeg::StringSeg(key), - ] if addr == &ADDRESS - && prefix == VALIDATOR_STORAGE_PREFIX - && key == VALIDATOR_MAX_COMMISSION_CHANGE_STORAGE_KEY => - { - Some(validator) - } - _ => None, - } +/// Get the storage handle to the total consensus validator stake +pub fn total_consensus_stake_handle() -> TotalConsensusStakes { + let key = storage_key::total_consensus_stake_key(); + TotalConsensusStakes::open(key) } -/// Is storage key for some piece of validator metadata? -pub fn is_validator_metadata_key(key: &Key) -> Option<&Address> { - match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(prefix), - DbKeySeg::AddressSeg(validator), - DbKeySeg::StringSeg(metadata), - ] if addr == &ADDRESS - && prefix == VALIDATOR_STORAGE_PREFIX - && matches!( - metadata.as_str(), - VALIDATOR_EMAIL_KEY - | VALIDATOR_DESCRIPTION_KEY - | VALIDATOR_WEBSITE_KEY - | VALIDATOR_DISCORD_KEY - ) => - { - Some(validator) - } - _ => None, - } +/// Get the storage handle to a PoS validator's state +pub fn validator_state_handle(validator: &Address) -> ValidatorStates { + let key = storage_key::validator_state_key(validator); + ValidatorStates::open(key) } -/// Storage key for validator's rewards products. -pub fn validator_rewards_product_key(validator: &Address) -> Key { - validator_prefix(validator) - .push(&VALIDATOR_REWARDS_PRODUCT_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} - -/// Is storage key for validator's rewards products? -pub fn is_validator_rewards_product_key(key: &Key) -> Option<&Address> { - match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(prefix), - DbKeySeg::AddressSeg(validator), - DbKeySeg::StringSeg(key), - ] if addr == &ADDRESS - && prefix == VALIDATOR_STORAGE_PREFIX - && key == VALIDATOR_REWARDS_PRODUCT_KEY => - { - Some(validator) - } - _ => None, - } +/// Get the storage handle to a PoS validator's deltas +pub fn validator_deltas_handle(validator: &Address) -> ValidatorDeltas { + let key = storage_key::validator_deltas_key(validator); + ValidatorDeltas::open(key) } -/// Storage prefix for rewards counter. -pub fn rewards_counter_prefix() -> Key { - Key::from(ADDRESS.to_db_key()) - .push(&REWARDS_COUNTER_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} - -/// Storage key for rewards counter. -pub fn rewards_counter_key(source: &Address, validator: &Address) -> Key { - rewards_counter_prefix() - .push(&source.to_db_key()) - .expect("Cannot obtain a storage key") - .push(&validator.to_db_key()) - .expect("Cannot obtain a storage key") -} - -/// Storage key for a validator's incoming redelegations, where the prefixed -/// validator is the destination validator. -pub fn validator_incoming_redelegations_key(validator: &Address) -> Key { - validator_prefix(validator) - .push(&VALIDATOR_INCOMING_REDELEGATIONS_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} - -/// Storage key for a validator's outgoing redelegations, where the prefixed -/// validator is the source validator. -pub fn validator_outgoing_redelegations_key(validator: &Address) -> Key { - validator_prefix(validator) - .push(&VALIDATOR_OUTGOING_REDELEGATIONS_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} - -/// Storage key for validator's total-redelegated-bonded amount to track for -/// slashing -pub fn validator_total_redelegated_bonded_key(validator: &Address) -> Key { - validator_prefix(validator) - .push(&VALIDATOR_TOTAL_REDELEGATED_BONDED_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} - -/// Storage key for validator's total-redelegated-unbonded amount to track for -/// slashing -pub fn validator_total_redelegated_unbonded_key(validator: &Address) -> Key { - validator_prefix(validator) - .push(&VALIDATOR_TOTAL_REDELEGATED_UNBONDED_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} - -/// Storage key prefix for all delegators' redelegated bonds. -pub fn delegator_redelegated_bonds_prefix() -> Key { - Key::from(ADDRESS.to_db_key()) - .push(&DELEGATOR_REDELEGATED_BONDS_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} - -/// Storage key for a particular delegator's redelegated bond information. -pub fn delegator_redelegated_bonds_key(delegator: &Address) -> Key { - delegator_redelegated_bonds_prefix() - .push(&delegator.to_db_key()) - .expect("Cannot obtain a storage key") -} - -/// Storage key prefix for all delegators' redelegated unbonds. -pub fn delegator_redelegated_unbonds_prefix() -> Key { - Key::from(ADDRESS.to_db_key()) - .push(&DELEGATOR_REDELEGATED_UNBONDS_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} - -/// Storage key for a particular delegator's redelegated unbond information. -pub fn delegator_redelegated_unbonds_key(delegator: &Address) -> Key { - delegator_redelegated_unbonds_prefix() - .push(&delegator.to_db_key()) - .expect("Cannot obtain a storage key") -} - -/// Storage key for validator's last known rewards product epoch. -pub fn validator_last_known_product_epoch_key(validator: &Address) -> Key { - validator_prefix(validator) - .push(&VALIDATOR_LAST_KNOWN_PRODUCT_EPOCH_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} - -/// Is storage key for validator's last known rewards product epoch? -pub fn is_validator_last_known_product_epoch_key( - key: &Key, -) -> Option<&Address> { - match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(prefix), - DbKeySeg::AddressSeg(validator), - DbKeySeg::StringSeg(key), - ] if addr == &ADDRESS - && prefix == VALIDATOR_STORAGE_PREFIX - && key == VALIDATOR_LAST_KNOWN_PRODUCT_EPOCH_KEY => - { - Some(validator) - } - _ => None, - } +/// Get the storage handle to the total deltas +pub fn total_deltas_handle() -> TotalDeltas { + let key = storage_key::total_deltas_key(); + TotalDeltas::open(key) } -/// Storage key for validator's consensus key. -pub fn validator_state_key(validator: &Address) -> Key { - validator_prefix(validator) - .push(&VALIDATOR_STATE_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} - -/// Is storage key for validator's state? -pub fn is_validator_state_key(key: &Key) -> Option<(&Address, Epoch)> { - match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(prefix), - DbKeySeg::AddressSeg(validator), - DbKeySeg::StringSeg(key), - DbKeySeg::StringSeg(lazy_map), - DbKeySeg::StringSeg(data), - DbKeySeg::StringSeg(epoch), - ] if addr == &ADDRESS - && prefix == VALIDATOR_STORAGE_PREFIX - && key == VALIDATOR_STATE_STORAGE_KEY - && lazy_map == LAZY_MAP_SUB_KEY - && data == lazy_map::DATA_SUBKEY => - { - let epoch = Epoch::parse(epoch.clone()) - .expect("Should be able to parse the epoch"); - Some((validator, epoch)) - } - _ => None, - } +/// Get the storage handle to the set of all validators +pub fn validator_addresses_handle() -> ValidatorAddresses { + let key = storage_key::validator_addresses_key(); + ValidatorAddresses::open(key) } -/// Storage key for validator's deltas. -pub fn validator_deltas_key(validator: &Address) -> Key { - validator_prefix(validator) - .push(&VALIDATOR_DELTAS_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} - -/// Is storage key for validator's total deltas? -pub fn is_validator_deltas_key(key: &Key) -> Option<&Address> { - match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(prefix), - DbKeySeg::AddressSeg(validator), - DbKeySeg::StringSeg(key), - DbKeySeg::StringSeg(lazy_map), - DbKeySeg::StringSeg(data), - DbKeySeg::StringSeg(_epoch), - ] if addr == &ADDRESS - && prefix == VALIDATOR_STORAGE_PREFIX - && key == VALIDATOR_DELTAS_STORAGE_KEY - && lazy_map == LAZY_MAP_SUB_KEY - && data == lazy_map::DATA_SUBKEY => - { - Some(validator) - } - _ => None, - } +/// Get the storage handle to a PoS validator's commission rate +pub fn validator_commission_rate_handle( + validator: &Address, +) -> CommissionRates { + let key = storage_key::validator_commission_rate_key(validator); + CommissionRates::open(key) } -/// Storage prefix for all active validators (consensus, below-capacity, jailed) -pub fn validator_addresses_key() -> Key { - Key::from(ADDRESS.to_db_key()) - .push(&VALIDATOR_ADDRESSES_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} - -/// Storage prefix for slashes. -pub fn slashes_prefix() -> Key { - Key::from(ADDRESS.to_db_key()) - .push(&SLASHES_PREFIX.to_owned()) - .expect("Cannot obtain a storage key") -} - -/// Storage key for all slashes. -pub fn enqueued_slashes_key() -> Key { - // slashes_prefix() - Key::from(ADDRESS.to_db_key()) - .push(&ENQUEUED_SLASHES_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} - -/// Storage key for validator's slashes. -pub fn validator_slashes_key(validator: &Address) -> Key { - slashes_prefix() - .push(&validator.to_db_key()) - .expect("Cannot obtain a storage key") -} - -/// Is storage key for a validator's slashes -pub fn is_validator_slashes_key(key: &Key) -> Option
{ - if key.segments.len() >= 5 { - match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(prefix), - DbKeySeg::AddressSeg(validator), - DbKeySeg::StringSeg(data), - DbKeySeg::StringSeg(_index), - ] if addr == &ADDRESS - && prefix == SLASHES_PREFIX - && data == lazy_vec::DATA_SUBKEY => - { - Some(validator.clone()) - } - _ => None, - } - } else { - None - } +/// Get the storage handle to a bond, which is dynamically updated with when +/// unbonding +pub fn bond_handle(source: &Address, validator: &Address) -> Bonds { + let bond_id = BondId { + source: source.clone(), + validator: validator.clone(), + }; + let key = storage_key::bond_key(&bond_id); + Bonds::open(key) } -/// Storage key for the last (most recent) epoch in which a slashable offense -/// was detected for a given validator -pub fn validator_last_slash_key(validator: &Address) -> Key { - validator_prefix(validator) - .push(&VALIDATOR_LAST_SLASH_EPOCH.to_owned()) - .expect("Cannot obtain a storage key") -} - -/// Storage key prefix for all bonds. -pub fn bonds_prefix() -> Key { - Key::from(ADDRESS.to_db_key()) - .push(&BOND_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} - -/// Storage key prefix for all bonds of the given source address. -pub fn bonds_for_source_prefix(source: &Address) -> Key { - bonds_prefix() - .push(&source.to_db_key()) - .expect("Cannot obtain a storage key") -} - -/// Storage key for a bond with the given ID (source and validator). -pub fn bond_key(bond_id: &BondId) -> Key { - bonds_for_source_prefix(&bond_id.source) - .push(&bond_id.validator.to_db_key()) - .expect("Cannot obtain a storage key") -} - -/// Is storage key for a bond? Returns the bond ID and bond start epoch if so. -pub fn is_bond_key(key: &Key) -> Option<(BondId, Epoch)> { - if key.segments.len() >= 7 { - match &key.segments[..7] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(prefix), - DbKeySeg::AddressSeg(source), - DbKeySeg::AddressSeg(validator), - DbKeySeg::StringSeg(lazy_map), - DbKeySeg::StringSeg(data), - DbKeySeg::StringSeg(epoch_str), - ] if addr == &ADDRESS - && prefix == BOND_STORAGE_KEY - && lazy_map == crate::epoched::LAZY_MAP_SUB_KEY - && data == lazy_map::DATA_SUBKEY => - { - let start = Epoch::parse(epoch_str.clone()).ok()?; - Some(( - BondId { - source: source.clone(), - validator: validator.clone(), - }, - start, - )) - } - _ => None, - } - } else { - None - } +/// Get the storage handle to a validator's total bonds, which are not updated +/// due to unbonding +pub fn total_bonded_handle(validator: &Address) -> Bonds { + let key = storage_key::validator_total_bonded_key(validator); + Bonds::open(key) } -/// Storage key for the total bonds for a given validator. -pub fn validator_total_bonded_key(validator: &Address) -> Key { - Key::from(ADDRESS.to_db_key()) - .push(&VALIDATOR_TOTAL_BONDED_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") - .push(&validator.to_db_key()) - .expect("Cannot obtain a storage key") -} - -/// Storage key prefix for all unbonds. -pub fn unbonds_prefix() -> Key { - Key::from(ADDRESS.to_db_key()) - .push(&UNBOND_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} - -/// Storage key prefix for all unbonds of the given source address. -pub fn unbonds_for_source_prefix(source: &Address) -> Key { - unbonds_prefix() - .push(&source.to_db_key()) - .expect("Cannot obtain a storage key") -} - -/// Storage key for an unbond with the given ID (source and validator). -pub fn unbond_key(bond_id: &BondId) -> Key { - unbonds_for_source_prefix(&bond_id.source) - .push(&bond_id.validator.to_db_key()) - .expect("Cannot obtain a storage key") -} - -/// Is storage key for an unbond? Returns the bond ID and unbond start and -/// withdraw epoch if it is. -pub fn is_unbond_key(key: &Key) -> Option<(BondId, Epoch, Epoch)> { - if key.segments.len() >= 8 { - match &key.segments[..8] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(prefix), - DbKeySeg::AddressSeg(source), - DbKeySeg::AddressSeg(validator), - DbKeySeg::StringSeg(data_1), - DbKeySeg::StringSeg(start_epoch_str), - DbKeySeg::StringSeg(data_2), - DbKeySeg::StringSeg(withdraw_epoch_str), - ] if addr == &ADDRESS - && prefix == UNBOND_STORAGE_KEY - && data_1 == lazy_map::DATA_SUBKEY - && data_2 == lazy_map::DATA_SUBKEY => - { - let withdraw = Epoch::parse(withdraw_epoch_str.clone()).ok()?; - let start = Epoch::parse(start_epoch_str.clone()).ok()?; - Some(( - BondId { - source: source.clone(), - validator: validator.clone(), - }, - start, - withdraw, - )) - } - _ => None, - } - } else { - None - } +/// Get the storage handle to an unbond +pub fn unbond_handle(source: &Address, validator: &Address) -> Unbonds { + let bond_id = BondId { + source: source.clone(), + validator: validator.clone(), + }; + let key = storage_key::unbond_key(&bond_id); + Unbonds::open(key) } -/// Storage key for validator's total-unbonded amount to track for slashing -pub fn validator_total_unbonded_key(validator: &Address) -> Key { - validator_prefix(validator) - .push(&VALIDATOR_TOTAL_UNBONDED_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") +/// Get the storage handle to a validator's total-unbonded map +pub fn total_unbonded_handle(validator: &Address) -> ValidatorTotalUnbonded { + let key = storage_key::validator_total_unbonded_key(validator); + ValidatorTotalUnbonded::open(key) } -/// Storage prefix for validator sets. -pub fn validator_sets_prefix() -> Key { - Key::from(ADDRESS.to_db_key()) - .push(&VALIDATOR_SETS_STORAGE_PREFIX.to_owned()) - .expect("Cannot obtain a storage key") +/// Get the storage handle to a PoS validator's deltas +pub fn validator_set_positions_handle() -> ValidatorSetPositions { + let key = storage_key::validator_set_positions_key(); + ValidatorSetPositions::open(key) } -/// Storage key for consensus validator set -pub fn consensus_validator_set_key() -> Key { - validator_sets_prefix() - .push(&CONSENSUS_VALIDATOR_SET_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") +/// Get the storage handle to a PoS validator's slashes +pub fn validator_slashes_handle(validator: &Address) -> Slashes { + let key = storage_key::validator_slashes_key(validator); + Slashes::open(key) } -/// Storage key for below-capacity validator set -pub fn below_capacity_validator_set_key() -> Key { - validator_sets_prefix() - .push(&BELOW_CAPACITY_VALIDATOR_SET_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") +/// Get the storage handle to list of all slashes to be processed and ultimately +/// placed in the `validator_slashes_handle` +pub fn enqueued_slashes_handle() -> EpochedSlashes { + let key = storage_key::enqueued_slashes_key(); + EpochedSlashes::open(key) } -/// Is storage key for the consensus validator set? -pub fn is_consensus_validator_set_key(key: &Key) -> bool { - matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(key), DbKeySeg::StringSeg(set_type), DbKeySeg::StringSeg(lazy_map), DbKeySeg::StringSeg(data), DbKeySeg::StringSeg(_epoch), DbKeySeg::StringSeg(_), DbKeySeg::StringSeg(_amount), DbKeySeg::StringSeg(_), DbKeySeg::StringSeg(_position)] if addr == &ADDRESS && key == VALIDATOR_SETS_STORAGE_PREFIX && set_type == CONSENSUS_VALIDATOR_SET_STORAGE_KEY && lazy_map == LAZY_MAP_SUB_KEY && data == lazy_map::DATA_SUBKEY) +/// Get the storage handle to the rewards accumulator for the consensus +/// validators in a given epoch +pub fn rewards_accumulator_handle() -> RewardsAccumulator { + let key = storage_key::consensus_validator_rewards_accumulator_key(); + RewardsAccumulator::open(key) } -/// Is storage key for the below-capacity validator set? -pub fn is_below_capacity_validator_set_key(key: &Key) -> bool { - matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(key), DbKeySeg::StringSeg(set_type), DbKeySeg::StringSeg(lazy_map), DbKeySeg::StringSeg(data), DbKeySeg::StringSeg(_epoch), DbKeySeg::StringSeg(_), DbKeySeg::StringSeg(_amount), DbKeySeg::StringSeg(_), DbKeySeg::StringSeg(_position)] if addr == &ADDRESS && key == VALIDATOR_SETS_STORAGE_PREFIX && set_type == BELOW_CAPACITY_VALIDATOR_SET_STORAGE_KEY && lazy_map == LAZY_MAP_SUB_KEY && data == lazy_map::DATA_SUBKEY) -} - -/// Storage key for total consensus stake -pub fn total_consensus_stake_key() -> Key { - Key::from(ADDRESS.to_db_key()) - .push(&TOTAL_CONSENSUS_STAKE_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a total consensus stake key") +/// Get the storage handle to a validator's rewards products +pub fn validator_rewards_products_handle( + validator: &Address, +) -> RewardsProducts { + let key = storage_key::validator_rewards_product_key(validator); + RewardsProducts::open(key) } -/// Is storage key for the total consensus stake? -pub fn is_total_consensus_stake_key(key: &Key) -> bool { - matches!(&key.segments[..], [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(key) - ] if addr == &ADDRESS && key == TOTAL_CONSENSUS_STAKE_STORAGE_KEY) +/// Get the storage handle to a validator's incoming redelegations +pub fn validator_incoming_redelegations_handle( + validator: &Address, +) -> IncomingRedelegations { + let key = storage_key::validator_incoming_redelegations_key(validator); + IncomingRedelegations::open(key) } -/// Storage key for total deltas of all validators. -pub fn total_deltas_key() -> Key { - Key::from(ADDRESS.to_db_key()) - .push(&TOTAL_DELTAS_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") +/// Get the storage handle to a validator's outgoing redelegations +pub fn validator_outgoing_redelegations_handle( + validator: &Address, +) -> OutgoingRedelegations { + let key = storage_key::validator_outgoing_redelegations_key(validator); + OutgoingRedelegations::open(key) } -/// Is storage key for total deltas of all validators? -pub fn is_total_deltas_key(key: &Key) -> Option<&String> { - match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(key), - DbKeySeg::StringSeg(lazy_map), - DbKeySeg::StringSeg(data), - DbKeySeg::StringSeg(epoch), - ] if addr == &ADDRESS - && key == TOTAL_DELTAS_STORAGE_KEY - && lazy_map == LAZY_MAP_SUB_KEY - && data == lazy_map::DATA_SUBKEY => - { - Some(epoch) - } - _ => None, - } +/// Get the storage handle to a validator's total redelegated bonds +pub fn validator_total_redelegated_bonded_handle( + validator: &Address, +) -> TotalRedelegatedBonded { + let key = storage_key::validator_total_redelegated_bonded_key(validator); + TotalRedelegatedBonded::open(key) } -/// Storage key for block proposer address of the previous block. -pub fn last_block_proposer_key() -> Key { - Key::from(ADDRESS.to_db_key()) - .push(&LAST_BLOCK_PROPOSER_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") +/// Get the storage handle to a validator's outgoing redelegations +pub fn validator_total_redelegated_unbonded_handle( + validator: &Address, +) -> TotalRedelegatedUnbonded { + let key = storage_key::validator_total_redelegated_unbonded_key(validator); + TotalRedelegatedUnbonded::open(key) } -/// Is storage key for block proposer address of the previous block? -pub fn is_last_block_proposer_key(key: &Key) -> bool { - matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(key)] if addr == &ADDRESS && key == LAST_BLOCK_PROPOSER_STORAGE_KEY) +/// Get the storage handle to a delegator's redelegated bonds information +pub fn delegator_redelegated_bonds_handle( + delegator: &Address, +) -> DelegatorRedelegatedBonded { + let key = storage_key::delegator_redelegated_bonds_key(delegator); + DelegatorRedelegatedBonded::open(key) } -/// Storage key for the consensus validator set rewards accumulator. -pub fn consensus_validator_rewards_accumulator_key() -> Key { - Key::from(ADDRESS.to_db_key()) - .push(&CONSENSUS_VALIDATOR_SET_ACCUMULATOR_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") +/// Get the storage handle to a delegator's redelegated unbonds information +pub fn delegator_redelegated_unbonds_handle( + delegator: &Address, +) -> DelegatorRedelegatedUnbonded { + let key = storage_key::delegator_redelegated_unbonds_key(delegator); + DelegatorRedelegatedUnbonded::open(key) +} + +/// Get the storage handle to the missed votes for liveness tracking +pub fn liveness_missed_votes_handle() -> LivenessMissedVotes { + let key = storage_key::liveness_missed_votes_key(); + LivenessMissedVotes::open(key) +} + +/// Get the storage handle to the sum of missed votes for liveness tracking +pub fn liveness_sum_missed_votes_handle() -> LivenessSumMissedVotes { + let key = storage_key::liveness_sum_missed_votes_key(); + LivenessSumMissedVotes::open(key) +} + +// ---- Storage read + write ---- + +/// Read PoS parameters +pub fn read_pos_params(storage: &S) -> storage_api::Result +where + S: StorageRead, +{ + let params = storage + .read(&storage_key::params_key()) + .transpose() + .expect("PosParams should always exist in storage after genesis")?; + read_non_pos_owned_params(storage, params) +} + +/// Read non-PoS-owned parameters to add them to `OwnedPosParams` to construct +/// `PosParams`. +pub fn read_non_pos_owned_params( + storage: &S, + owned: OwnedPosParams, +) -> storage_api::Result +where + S: StorageRead, +{ + let max_proposal_period = get_max_proposal_period(storage)?; + Ok(PosParams { + owned, + max_proposal_period, + }) +} + +/// Write PoS parameters +pub fn write_pos_params( + storage: &mut S, + params: &OwnedPosParams, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let key = storage_key::params_key(); + storage.write(&key, params) +} + +/// Get the validator address given the raw hash of the Tendermint consensus key +pub fn find_validator_by_raw_hash( + storage: &S, + raw_hash: impl AsRef, +) -> storage_api::Result> +where + S: StorageRead, +{ + let key = storage_key::validator_address_raw_hash_key(raw_hash); + storage.read(&key) +} + +/// Write PoS validator's address raw hash. +pub fn write_validator_address_raw_hash( + storage: &mut S, + validator: &Address, + consensus_key: &common::PublicKey, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let raw_hash = tm_consensus_key_raw_hash(consensus_key); + storage.write( + &storage_key::validator_address_raw_hash_key(raw_hash), + validator, + ) +} + +/// Read PoS validator's max commission rate change. +pub fn read_validator_max_commission_rate_change( + storage: &S, + validator: &Address, +) -> storage_api::Result> +where + S: StorageRead, +{ + let key = storage_key::validator_max_commission_rate_change_key(validator); + storage.read(&key) } -/// Is storage key for the consensus validator set? -pub fn is_consensus_validator_set_accumulator_key(key: &Key) -> bool { - matches!(&key.segments[..], [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(key), - ] if addr == &ADDRESS - && key == CONSENSUS_VALIDATOR_SET_ACCUMULATOR_STORAGE_KEY) +/// Write PoS validator's max commission rate change. +pub fn write_validator_max_commission_rate_change( + storage: &mut S, + validator: &Address, + change: Dec, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let key = storage_key::validator_max_commission_rate_change_key(validator); + storage.write(&key, change) +} + +/// Read the most recent slash epoch for the given epoch +pub fn read_validator_last_slash_epoch( + storage: &S, + validator: &Address, +) -> storage_api::Result> +where + S: StorageRead, +{ + let key = storage_key::validator_last_slash_key(validator); + storage.read(&key) } -/// Storage prefix for epoch at which an account last claimed PoS inflationary -/// rewards. -pub fn last_pos_reward_claim_epoch_prefix() -> Key { - Key::from(ADDRESS.to_db_key()) - .push(&LAST_REWARD_CLAIM_EPOCH.to_owned()) - .expect("Cannot obtain a storage key") +/// Write the most recent slash epoch for the given epoch +pub fn write_validator_last_slash_epoch( + storage: &mut S, + validator: &Address, + epoch: Epoch, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let key = storage_key::validator_last_slash_key(validator); + storage.write(&key, epoch) +} + +/// Read last block proposer address. +pub fn read_last_block_proposer_address( + storage: &S, +) -> storage_api::Result> +where + S: StorageRead, +{ + let key = storage_key::last_block_proposer_key(); + storage.read(&key) +} + +/// Write last block proposer address. +pub fn write_last_block_proposer_address( + storage: &mut S, + address: Address, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let key = storage_key::last_block_proposer_key(); + storage.write(&key, address) +} + +/// Read PoS validator's delta value. +pub fn read_validator_deltas_value( + storage: &S, + validator: &Address, + epoch: &namada_core::types::storage::Epoch, +) -> storage_api::Result> +where + S: StorageRead, +{ + let handle = validator_deltas_handle(validator); + handle.get_delta_val(storage, *epoch) +} + +/// Read PoS validator's stake (sum of deltas). +/// For non-validators and validators with `0` stake, this returns the default - +/// `token::Amount::zero()`. +pub fn read_validator_stake( + storage: &S, + params: &PosParams, + validator: &Address, + epoch: namada_core::types::storage::Epoch, +) -> storage_api::Result +where + S: StorageRead, +{ + let handle = validator_deltas_handle(validator); + let amount = handle + .get_sum(storage, epoch, params)? + .map(|change| { + debug_assert!(change.non_negative()); + token::Amount::from_change(change) + }) + .unwrap_or_default(); + Ok(amount) +} + +/// Add or remove PoS validator's stake delta value +pub fn update_validator_deltas( + storage: &mut S, + params: &OwnedPosParams, + validator: &Address, + delta: token::Change, + current_epoch: namada_core::types::storage::Epoch, + offset_opt: Option, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let handle = validator_deltas_handle(validator); + let offset = offset_opt.unwrap_or(params.pipeline_len); + let val = handle + .get_delta_val(storage, current_epoch + offset)? + .unwrap_or_default(); + handle.set( + storage, + val.checked_add(&delta) + .expect("Validator deltas updated amount should not overflow"), + current_epoch, + offset, + ) +} + +/// Read PoS total stake (sum of deltas). +pub fn read_total_stake( + storage: &S, + params: &PosParams, + epoch: namada_core::types::storage::Epoch, +) -> storage_api::Result +where + S: StorageRead, +{ + let handle = total_deltas_handle(); + let amnt = handle + .get_sum(storage, epoch, params)? + .map(|change| { + debug_assert!(change.non_negative()); + token::Amount::from_change(change) + }) + .unwrap_or_default(); + Ok(amnt) +} + +/// Read all addresses from consensus validator set. +pub fn read_consensus_validator_set_addresses( + storage: &S, + epoch: namada_core::types::storage::Epoch, +) -> storage_api::Result> +where + S: StorageRead, +{ + consensus_validator_set_handle() + .at(&epoch) + .iter(storage)? + .map(|res| res.map(|(_sub_key, address)| address)) + .collect() +} + +/// Read all addresses from below-capacity validator set. +pub fn read_below_capacity_validator_set_addresses( + storage: &S, + epoch: namada_core::types::storage::Epoch, +) -> storage_api::Result> +where + S: StorageRead, +{ + below_capacity_validator_set_handle() + .at(&epoch) + .iter(storage)? + .map(|res| res.map(|(_sub_key, address)| address)) + .collect() +} + +/// Read all addresses from the below-threshold set +pub fn read_below_threshold_validator_set_addresses( + storage: &S, + epoch: namada_core::types::storage::Epoch, +) -> storage_api::Result> +where + S: StorageRead, +{ + let params = read_pos_params(storage)?; + Ok(validator_addresses_handle() + .at(&epoch) + .iter(storage)? + .map(Result::unwrap) + .filter(|address| { + matches!( + validator_state_handle(address).get(storage, epoch, ¶ms), + Ok(Some(ValidatorState::BelowThreshold)) + ) + }) + .collect()) +} + +/// Read all addresses from consensus validator set with their stake. +pub fn read_consensus_validator_set_addresses_with_stake( + storage: &S, + epoch: namada_core::types::storage::Epoch, +) -> storage_api::Result> +where + S: StorageRead, +{ + consensus_validator_set_handle() + .at(&epoch) + .iter(storage)? + .map(|res| { + res.map( + |( + NestedSubKey::Data { + key: bonded_stake, + nested_sub_key: _, + }, + address, + )| { + WeightedValidator { + address, + bonded_stake, + } + }, + ) + }) + .collect() +} + +/// Count the number of consensus validators +pub fn get_num_consensus_validators( + storage: &S, + epoch: namada_core::types::storage::Epoch, +) -> storage_api::Result +where + S: StorageRead, +{ + Ok(consensus_validator_set_handle() + .at(&epoch) + .iter(storage)? + .count() as u64) +} + +/// Read all addresses from below-capacity validator set with their stake. +pub fn read_below_capacity_validator_set_addresses_with_stake( + storage: &S, + epoch: namada_core::types::storage::Epoch, +) -> storage_api::Result> +where + S: StorageRead, +{ + below_capacity_validator_set_handle() + .at(&epoch) + .iter(storage)? + .map(|res| { + res.map( + |( + NestedSubKey::Data { + key: ReverseOrdTokenAmount(bonded_stake), + nested_sub_key: _, + }, + address, + )| { + WeightedValidator { + address, + bonded_stake, + } + }, + ) + }) + .collect() +} + +/// Read all validator addresses. +pub fn read_all_validator_addresses( + storage: &S, + epoch: namada_core::types::storage::Epoch, +) -> storage_api::Result> +where + S: StorageRead, +{ + validator_addresses_handle() + .at(&epoch) + .iter(storage)? + .collect() +} + +/// Update PoS total deltas. +/// Note: for EpochedDelta, write the value to change storage by +pub fn update_total_deltas( + storage: &mut S, + params: &OwnedPosParams, + delta: token::Change, + current_epoch: namada_core::types::storage::Epoch, + offset_opt: Option, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let handle = total_deltas_handle(); + let offset = offset_opt.unwrap_or(params.pipeline_len); + let val = handle + .get_delta_val(storage, current_epoch + offset)? + .unwrap_or_default(); + handle.set( + storage, + val.checked_add(&delta) + .expect("Total deltas updated amount should not overflow"), + current_epoch, + offset, + ) +} + +/// Read PoS validator's email. +pub fn read_validator_email( + storage: &S, + validator: &Address, +) -> storage_api::Result> +where + S: StorageRead, +{ + storage.read(&storage_key::validator_email_key(validator)) } -/// Storage key for epoch at which an account last claimed PoS inflationary -/// rewards. -pub fn last_pos_reward_claim_epoch_key( - delegator: &Address, +/// Write PoS validator's email. The email cannot be removed, so an empty string +/// will result in an error. +pub fn write_validator_email( + storage: &mut S, validator: &Address, -) -> Key { - last_pos_reward_claim_epoch_prefix() - .push(&delegator.to_db_key()) - .expect("Cannot obtain a storage key") - .push(&validator.to_db_key()) - .expect("Cannot obtain a storage key") -} - -/// Get validator address from bond key -pub fn get_validator_address_from_bond(key: &Key) -> Option
{ - match key.get_at(3) { - Some(segment) => match segment { - DbKeySeg::AddressSeg(addr) => Some(addr.clone()), - DbKeySeg::StringSeg(_) => None, - }, - None => None, + email: &String, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let key = storage_key::validator_email_key(validator); + if email.is_empty() { + Err(MetadataError::CannotRemoveEmail.into()) + } else { + storage.write(&key, email) } } -/// Storage key for validator set positions -pub fn validator_set_positions_key() -> Key { - Key::from(ADDRESS.to_db_key()) - .push(&VALIDATOR_SET_POSITIONS_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} - -/// Storage key for consensus keys set. -pub fn consensus_keys_key() -> Key { - Key::from(ADDRESS.to_db_key()) - .push(&CONSENSUS_KEYS.to_owned()) - .expect("Cannot obtain a storage key") -} - -/// Is storage key for consensus keys set? -pub fn is_consensus_keys_key(key: &Key) -> bool { - matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(key)] if addr == &ADDRESS && key == CONSENSUS_KEYS) +/// Read PoS validator's description. +pub fn read_validator_description( + storage: &S, + validator: &Address, +) -> storage_api::Result> +where + S: StorageRead, +{ + storage.read(&storage_key::validator_description_key(validator)) } -/// Storage key for a validator's email -pub fn validator_email_key(validator: &Address) -> Key { - validator_prefix(validator) - .push(&VALIDATOR_EMAIL_KEY.to_owned()) - .expect("Cannot obtain a storage key") +/// Write PoS validator's description. If the provided arg is an empty string, +/// remove the data. +pub fn write_validator_description( + storage: &mut S, + validator: &Address, + description: &String, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let key = storage_key::validator_description_key(validator); + if description.is_empty() { + storage.delete(&key) + } else { + storage.write(&key, description) + } } -/// Storage key for a validator's description -pub fn validator_description_key(validator: &Address) -> Key { - validator_prefix(validator) - .push(&VALIDATOR_DESCRIPTION_KEY.to_owned()) - .expect("Cannot obtain a storage key") +/// Read PoS validator's website. +pub fn read_validator_website( + storage: &S, + validator: &Address, +) -> storage_api::Result> +where + S: StorageRead, +{ + storage.read(&storage_key::validator_website_key(validator)) } -/// Storage key for a validator's website -pub fn validator_website_key(validator: &Address) -> Key { - validator_prefix(validator) - .push(&VALIDATOR_WEBSITE_KEY.to_owned()) - .expect("Cannot obtain a storage key") +/// Write PoS validator's website. If the provided arg is an empty string, +/// remove the data. +pub fn write_validator_website( + storage: &mut S, + validator: &Address, + website: &String, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let key = storage_key::validator_website_key(validator); + if website.is_empty() { + storage.delete(&key) + } else { + storage.write(&key, website) + } } -/// Storage key for a validator's discord handle -pub fn validator_discord_key(validator: &Address) -> Key { - validator_prefix(validator) - .push(&VALIDATOR_DISCORD_KEY.to_owned()) - .expect("Cannot obtain a storage key") +/// Read PoS validator's discord handle. +pub fn read_validator_discord_handle( + storage: &S, + validator: &Address, +) -> storage_api::Result> +where + S: StorageRead, +{ + storage.read(&storage_key::validator_discord_key(validator)) } -/// Storage prefix for the liveness data of the cosnensus validator set. -pub fn liveness_data_prefix() -> Key { - Key::from(ADDRESS.to_db_key()) - .push(&LIVENESS_PREFIX.to_owned()) - .expect("Cannot obtain a storage key") +/// Write PoS validator's discord handle. If the provided arg is an empty +/// string, remove the data. +pub fn write_validator_discord_handle( + storage: &mut S, + validator: &Address, + discord_handle: &String, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let key = storage_key::validator_discord_key(validator); + if discord_handle.is_empty() { + storage.delete(&key) + } else { + storage.write(&key, discord_handle) + } } -/// Storage key for the liveness records. -pub fn liveness_missed_votes_key() -> Key { - liveness_data_prefix() - .push(&LIVENESS_MISSED_VOTES.to_owned()) - .expect("Cannot obtain a storage key") +/// Write validator's metadata. +pub fn write_validator_metadata( + storage: &mut S, + validator: &Address, + metadata: &ValidatorMetaData, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + // Email is the only required field in the metadata + write_validator_email(storage, validator, &metadata.email)?; + + if let Some(description) = metadata.description.as_ref() { + write_validator_description(storage, validator, description)?; + } + if let Some(website) = metadata.website.as_ref() { + write_validator_website(storage, validator, website)?; + } + if let Some(discord) = metadata.discord_handle.as_ref() { + write_validator_discord_handle(storage, validator, discord)?; + } + Ok(()) } -/// Storage key for the liveness data. -pub fn liveness_sum_missed_votes_key() -> Key { - liveness_data_prefix() - .push(&LIVENESS_MISSED_VOTES_SUM.to_owned()) - .expect("Cannot obtain a storage key") +/// Get the last epoch in which rewards were claimed from storage, if any +pub fn get_last_reward_claim_epoch( + storage: &S, + delegator: &Address, + validator: &Address, +) -> storage_api::Result> +where + S: StorageRead, +{ + let key = + storage_key::last_pos_reward_claim_epoch_key(delegator, validator); + storage.read(&key) +} + +/// Write the last epoch in which rewards were claimed for the +/// delegator-validator pair +pub fn write_last_reward_claim_epoch( + storage: &mut S, + delegator: &Address, + validator: &Address, + epoch: Epoch, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let key = + storage_key::last_pos_reward_claim_epoch_key(delegator, validator); + storage.write(&key, epoch) +} + +/// Check if the given consensus key is already being used to ensure uniqueness. +/// +/// If it's not being used, it will be inserted into the set that's being used +/// for this. If it's already used, this will return an Error. +pub fn try_insert_consensus_key( + storage: &mut S, + consensus_key: &common::PublicKey, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let key = consensus_keys_key(); + LazySet::open(key).try_insert(storage, consensus_key.clone()) +} + +/// Get the unique set of consensus keys in storage +pub fn get_consensus_key_set( + storage: &S, +) -> storage_api::Result> +where + S: StorageRead, +{ + let key = consensus_keys_key(); + let lazy_set = LazySet::::open(key); + Ok(lazy_set.iter(storage)?.map(Result::unwrap).collect()) +} + +/// Check if the given consensus key is already being used to ensure uniqueness. +pub fn is_consensus_key_used( + storage: &S, + consensus_key: &common::PublicKey, +) -> storage_api::Result +where + S: StorageRead, +{ + let key = consensus_keys_key(); + let handle = LazySet::open(key); + handle.contains(storage, consensus_key) } diff --git a/proof_of_stake/src/storage_key.rs b/proof_of_stake/src/storage_key.rs new file mode 100644 index 0000000000..b76760650b --- /dev/null +++ b/proof_of_stake/src/storage_key.rs @@ -0,0 +1,852 @@ +//! Proof-of-Stake storage keys and storage integration. + +use namada_core::ledger::storage_api::collections::{lazy_map, lazy_vec}; +use namada_core::types::address::Address; +use namada_core::types::storage::{DbKeySeg, Epoch, Key, KeySeg}; + +use super::ADDRESS; +use crate::epoched::LAZY_MAP_SUB_KEY; +use crate::types::BondId; + +const PARAMS_STORAGE_KEY: &str = "params"; +const VALIDATOR_ADDRESSES_KEY: &str = "validator_addresses"; +#[allow(missing_docs)] +pub const VALIDATOR_STORAGE_PREFIX: &str = "validator"; +const VALIDATOR_ADDRESS_RAW_HASH: &str = "address_raw_hash"; +const VALIDATOR_CONSENSUS_KEY_STORAGE_KEY: &str = "consensus_key"; +const VALIDATOR_ETH_COLD_KEY_STORAGE_KEY: &str = "eth_cold_key"; +const VALIDATOR_ETH_HOT_KEY_STORAGE_KEY: &str = "eth_hot_key"; +const VALIDATOR_STATE_STORAGE_KEY: &str = "state"; +const VALIDATOR_DELTAS_STORAGE_KEY: &str = "deltas"; +const VALIDATOR_COMMISSION_RATE_STORAGE_KEY: &str = "commission_rate"; +const VALIDATOR_MAX_COMMISSION_CHANGE_STORAGE_KEY: &str = + "max_commission_rate_change"; +const VALIDATOR_REWARDS_PRODUCT_KEY: &str = "validator_rewards_product"; +const VALIDATOR_LAST_KNOWN_PRODUCT_EPOCH_KEY: &str = + "last_known_rewards_product_epoch"; +const SLASHES_PREFIX: &str = "slash"; +const ENQUEUED_SLASHES_KEY: &str = "enqueued_slashes"; +const VALIDATOR_LAST_SLASH_EPOCH: &str = "last_slash_epoch"; +const BOND_STORAGE_KEY: &str = "bond"; +const UNBOND_STORAGE_KEY: &str = "unbond"; +const VALIDATOR_TOTAL_BONDED_STORAGE_KEY: &str = "total_bonded"; +const VALIDATOR_TOTAL_UNBONDED_STORAGE_KEY: &str = "total_unbonded"; +const VALIDATOR_SETS_STORAGE_PREFIX: &str = "validator_sets"; +const CONSENSUS_VALIDATOR_SET_STORAGE_KEY: &str = "consensus"; +const BELOW_CAPACITY_VALIDATOR_SET_STORAGE_KEY: &str = "below_capacity"; +const TOTAL_CONSENSUS_STAKE_STORAGE_KEY: &str = "total_consensus_stake"; +const TOTAL_DELTAS_STORAGE_KEY: &str = "total_deltas"; +const VALIDATOR_SET_POSITIONS_KEY: &str = "validator_set_positions"; +const CONSENSUS_KEYS: &str = "consensus_keys"; +const LAST_BLOCK_PROPOSER_STORAGE_KEY: &str = "last_block_proposer"; +const CONSENSUS_VALIDATOR_SET_ACCUMULATOR_STORAGE_KEY: &str = + "validator_rewards_accumulator"; +const LAST_REWARD_CLAIM_EPOCH: &str = "last_reward_claim_epoch"; +const REWARDS_COUNTER_KEY: &str = "validator_rewards_commissions"; +const VALIDATOR_INCOMING_REDELEGATIONS_KEY: &str = "incoming_redelegations"; +const VALIDATOR_OUTGOING_REDELEGATIONS_KEY: &str = "outgoing_redelegations"; +const VALIDATOR_TOTAL_REDELEGATED_BONDED_KEY: &str = "total_redelegated_bonded"; +const VALIDATOR_TOTAL_REDELEGATED_UNBONDED_KEY: &str = + "total_redelegated_unbonded"; +const DELEGATOR_REDELEGATED_BONDS_KEY: &str = "delegator_redelegated_bonds"; +const DELEGATOR_REDELEGATED_UNBONDS_KEY: &str = "delegator_redelegated_unbonds"; +const VALIDATOR_EMAIL_KEY: &str = "email"; +const VALIDATOR_DESCRIPTION_KEY: &str = "description"; +const VALIDATOR_WEBSITE_KEY: &str = "website"; +const VALIDATOR_DISCORD_KEY: &str = "discord_handle"; +const LIVENESS_PREFIX: &str = "liveness"; +const LIVENESS_MISSED_VOTES: &str = "missed_votes"; +const LIVENESS_MISSED_VOTES_SUM: &str = "sum_missed_votes"; + +/// Is the given key a PoS storage key? +pub fn is_pos_key(key: &Key) -> bool { + match &key.segments.get(0) { + Some(DbKeySeg::AddressSeg(addr)) => addr == &ADDRESS, + _ => false, + } +} + +/// Storage key for PoS parameters. +pub fn params_key() -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&PARAMS_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for PoS parameters? +pub fn is_params_key(key: &Key) -> bool { + matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(key)] if addr == &ADDRESS && key == PARAMS_STORAGE_KEY) +} + +/// Storage key prefix for validator data. +fn validator_prefix(validator: &Address) -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&VALIDATOR_STORAGE_PREFIX.to_owned()) + .expect("Cannot obtain a storage key") + .push(&validator.to_db_key()) + .expect("Cannot obtain a storage key") +} + +/// Storage key for validator's address raw hash for look-up from raw hash of an +/// address to address. +pub fn validator_address_raw_hash_key(raw_hash: impl AsRef) -> Key { + let raw_hash = raw_hash.as_ref().to_owned(); + Key::from(ADDRESS.to_db_key()) + .push(&VALIDATOR_ADDRESS_RAW_HASH.to_owned()) + .expect("Cannot obtain a storage key") + .push(&raw_hash) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for validator's address raw hash? +pub fn is_validator_address_raw_hash_key(key: &Key) -> Option<&str> { + match &key.segments[..] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::StringSeg(raw_hash), + ] if addr == &ADDRESS && prefix == VALIDATOR_ADDRESS_RAW_HASH => { + Some(raw_hash) + } + _ => None, + } +} + +/// Storage key for validator's consensus key. +pub fn validator_consensus_key_key(validator: &Address) -> Key { + validator_prefix(validator) + .push(&VALIDATOR_CONSENSUS_KEY_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for validator's consensus key? +pub fn is_validator_consensus_key_key(key: &Key) -> Option<&Address> { + match &key.segments[..] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(validator), + DbKeySeg::StringSeg(key), + ] if addr == &ADDRESS + && prefix == VALIDATOR_STORAGE_PREFIX + && key == VALIDATOR_CONSENSUS_KEY_STORAGE_KEY => + { + Some(validator) + } + _ => None, + } +} + +/// Storage key for validator's eth cold key. +pub fn validator_eth_cold_key_key(validator: &Address) -> Key { + validator_prefix(validator) + .push(&VALIDATOR_ETH_COLD_KEY_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for validator's eth cold key? +pub fn is_validator_eth_cold_key_key(key: &Key) -> Option<&Address> { + match &key.segments[..] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(validator), + DbKeySeg::StringSeg(key), + ] if addr == &ADDRESS + && prefix == VALIDATOR_STORAGE_PREFIX + && key == VALIDATOR_ETH_COLD_KEY_STORAGE_KEY => + { + Some(validator) + } + _ => None, + } +} + +/// Storage key for validator's eth hot key. +pub fn validator_eth_hot_key_key(validator: &Address) -> Key { + validator_prefix(validator) + .push(&VALIDATOR_ETH_HOT_KEY_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for validator's eth hot key? +pub fn is_validator_eth_hot_key_key(key: &Key) -> Option<&Address> { + match &key.segments[..] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(validator), + DbKeySeg::StringSeg(key), + ] if addr == &ADDRESS + && prefix == VALIDATOR_STORAGE_PREFIX + && key == VALIDATOR_ETH_HOT_KEY_STORAGE_KEY => + { + Some(validator) + } + _ => None, + } +} + +/// Storage key for validator's commission rate. +pub fn validator_commission_rate_key(validator: &Address) -> Key { + validator_prefix(validator) + .push(&VALIDATOR_COMMISSION_RATE_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for validator's commission rate? +pub fn is_validator_commission_rate_key( + key: &Key, +) -> Option<(&Address, Epoch)> { + match &key.segments[..] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(validator), + DbKeySeg::StringSeg(key), + DbKeySeg::StringSeg(lazy_map), + DbKeySeg::StringSeg(data), + DbKeySeg::StringSeg(epoch), + ] if addr == &ADDRESS + && prefix == VALIDATOR_STORAGE_PREFIX + && key == VALIDATOR_COMMISSION_RATE_STORAGE_KEY + && lazy_map == LAZY_MAP_SUB_KEY + && data == lazy_map::DATA_SUBKEY => + { + let epoch = Epoch::parse(epoch.clone()) + .expect("Should be able to parse the epoch"); + Some((validator, epoch)) + } + _ => None, + } +} + +/// Storage key for validator's maximum commission rate change per epoch. +pub fn validator_max_commission_rate_change_key(validator: &Address) -> Key { + validator_prefix(validator) + .push(&VALIDATOR_MAX_COMMISSION_CHANGE_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for validator's maximum commission rate change per epoch? +pub fn is_validator_max_commission_rate_change_key( + key: &Key, +) -> Option<&Address> { + match &key.segments[..] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(validator), + DbKeySeg::StringSeg(key), + ] if addr == &ADDRESS + && prefix == VALIDATOR_STORAGE_PREFIX + && key == VALIDATOR_MAX_COMMISSION_CHANGE_STORAGE_KEY => + { + Some(validator) + } + _ => None, + } +} + +/// Is storage key for some piece of validator metadata? +pub fn is_validator_metadata_key(key: &Key) -> Option<&Address> { + match &key.segments[..] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(validator), + DbKeySeg::StringSeg(metadata), + ] if addr == &ADDRESS + && prefix == VALIDATOR_STORAGE_PREFIX + && matches!( + metadata.as_str(), + VALIDATOR_EMAIL_KEY + | VALIDATOR_DESCRIPTION_KEY + | VALIDATOR_WEBSITE_KEY + | VALIDATOR_DISCORD_KEY + ) => + { + Some(validator) + } + _ => None, + } +} + +/// Storage key for validator's rewards products. +pub fn validator_rewards_product_key(validator: &Address) -> Key { + validator_prefix(validator) + .push(&VALIDATOR_REWARDS_PRODUCT_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for validator's rewards products? +pub fn is_validator_rewards_product_key(key: &Key) -> Option<&Address> { + match &key.segments[..] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(validator), + DbKeySeg::StringSeg(key), + ] if addr == &ADDRESS + && prefix == VALIDATOR_STORAGE_PREFIX + && key == VALIDATOR_REWARDS_PRODUCT_KEY => + { + Some(validator) + } + _ => None, + } +} + +/// Storage prefix for rewards counter. +pub fn rewards_counter_prefix() -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&REWARDS_COUNTER_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Storage key for rewards counter. +pub fn rewards_counter_key(source: &Address, validator: &Address) -> Key { + rewards_counter_prefix() + .push(&source.to_db_key()) + .expect("Cannot obtain a storage key") + .push(&validator.to_db_key()) + .expect("Cannot obtain a storage key") +} + +/// Storage key for a validator's incoming redelegations, where the prefixed +/// validator is the destination validator. +pub fn validator_incoming_redelegations_key(validator: &Address) -> Key { + validator_prefix(validator) + .push(&VALIDATOR_INCOMING_REDELEGATIONS_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Storage key for a validator's outgoing redelegations, where the prefixed +/// validator is the source validator. +pub fn validator_outgoing_redelegations_key(validator: &Address) -> Key { + validator_prefix(validator) + .push(&VALIDATOR_OUTGOING_REDELEGATIONS_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Storage key for validator's total-redelegated-bonded amount to track for +/// slashing +pub fn validator_total_redelegated_bonded_key(validator: &Address) -> Key { + validator_prefix(validator) + .push(&VALIDATOR_TOTAL_REDELEGATED_BONDED_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Storage key for validator's total-redelegated-unbonded amount to track for +/// slashing +pub fn validator_total_redelegated_unbonded_key(validator: &Address) -> Key { + validator_prefix(validator) + .push(&VALIDATOR_TOTAL_REDELEGATED_UNBONDED_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Storage key prefix for all delegators' redelegated bonds. +pub fn delegator_redelegated_bonds_prefix() -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&DELEGATOR_REDELEGATED_BONDS_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Storage key for a particular delegator's redelegated bond information. +pub fn delegator_redelegated_bonds_key(delegator: &Address) -> Key { + delegator_redelegated_bonds_prefix() + .push(&delegator.to_db_key()) + .expect("Cannot obtain a storage key") +} + +/// Storage key prefix for all delegators' redelegated unbonds. +pub fn delegator_redelegated_unbonds_prefix() -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&DELEGATOR_REDELEGATED_UNBONDS_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Storage key for a particular delegator's redelegated unbond information. +pub fn delegator_redelegated_unbonds_key(delegator: &Address) -> Key { + delegator_redelegated_unbonds_prefix() + .push(&delegator.to_db_key()) + .expect("Cannot obtain a storage key") +} + +/// Storage key for validator's last known rewards product epoch. +pub fn validator_last_known_product_epoch_key(validator: &Address) -> Key { + validator_prefix(validator) + .push(&VALIDATOR_LAST_KNOWN_PRODUCT_EPOCH_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for validator's last known rewards product epoch? +pub fn is_validator_last_known_product_epoch_key( + key: &Key, +) -> Option<&Address> { + match &key.segments[..] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(validator), + DbKeySeg::StringSeg(key), + ] if addr == &ADDRESS + && prefix == VALIDATOR_STORAGE_PREFIX + && key == VALIDATOR_LAST_KNOWN_PRODUCT_EPOCH_KEY => + { + Some(validator) + } + _ => None, + } +} + +/// Storage key for validator's consensus key. +pub fn validator_state_key(validator: &Address) -> Key { + validator_prefix(validator) + .push(&VALIDATOR_STATE_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for validator's state? +pub fn is_validator_state_key(key: &Key) -> Option<(&Address, Epoch)> { + match &key.segments[..] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(validator), + DbKeySeg::StringSeg(key), + DbKeySeg::StringSeg(lazy_map), + DbKeySeg::StringSeg(data), + DbKeySeg::StringSeg(epoch), + ] if addr == &ADDRESS + && prefix == VALIDATOR_STORAGE_PREFIX + && key == VALIDATOR_STATE_STORAGE_KEY + && lazy_map == LAZY_MAP_SUB_KEY + && data == lazy_map::DATA_SUBKEY => + { + let epoch = Epoch::parse(epoch.clone()) + .expect("Should be able to parse the epoch"); + Some((validator, epoch)) + } + _ => None, + } +} + +/// Storage key for validator's deltas. +pub fn validator_deltas_key(validator: &Address) -> Key { + validator_prefix(validator) + .push(&VALIDATOR_DELTAS_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for validator's total deltas? +pub fn is_validator_deltas_key(key: &Key) -> Option<&Address> { + match &key.segments[..] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(validator), + DbKeySeg::StringSeg(key), + DbKeySeg::StringSeg(lazy_map), + DbKeySeg::StringSeg(data), + DbKeySeg::StringSeg(_epoch), + ] if addr == &ADDRESS + && prefix == VALIDATOR_STORAGE_PREFIX + && key == VALIDATOR_DELTAS_STORAGE_KEY + && lazy_map == LAZY_MAP_SUB_KEY + && data == lazy_map::DATA_SUBKEY => + { + Some(validator) + } + _ => None, + } +} + +/// Storage prefix for all active validators (consensus, below-capacity, jailed) +pub fn validator_addresses_key() -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&VALIDATOR_ADDRESSES_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Storage prefix for slashes. +pub fn slashes_prefix() -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&SLASHES_PREFIX.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Storage key for all slashes. +pub fn enqueued_slashes_key() -> Key { + // slashes_prefix() + Key::from(ADDRESS.to_db_key()) + .push(&ENQUEUED_SLASHES_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Storage key for validator's slashes. +pub fn validator_slashes_key(validator: &Address) -> Key { + slashes_prefix() + .push(&validator.to_db_key()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for a validator's slashes +pub fn is_validator_slashes_key(key: &Key) -> Option
{ + if key.segments.len() >= 5 { + match &key.segments[..] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(validator), + DbKeySeg::StringSeg(data), + DbKeySeg::StringSeg(_index), + ] if addr == &ADDRESS + && prefix == SLASHES_PREFIX + && data == lazy_vec::DATA_SUBKEY => + { + Some(validator.clone()) + } + _ => None, + } + } else { + None + } +} + +/// Storage key for the last (most recent) epoch in which a slashable offense +/// was detected for a given validator +pub fn validator_last_slash_key(validator: &Address) -> Key { + validator_prefix(validator) + .push(&VALIDATOR_LAST_SLASH_EPOCH.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Storage key prefix for all bonds. +pub fn bonds_prefix() -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&BOND_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Storage key prefix for all bonds of the given source address. +pub fn bonds_for_source_prefix(source: &Address) -> Key { + bonds_prefix() + .push(&source.to_db_key()) + .expect("Cannot obtain a storage key") +} + +/// Storage key for a bond with the given ID (source and validator). +pub fn bond_key(bond_id: &BondId) -> Key { + bonds_for_source_prefix(&bond_id.source) + .push(&bond_id.validator.to_db_key()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for a bond? Returns the bond ID and bond start epoch if so. +pub fn is_bond_key(key: &Key) -> Option<(BondId, Epoch)> { + if key.segments.len() >= 7 { + match &key.segments[..7] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(source), + DbKeySeg::AddressSeg(validator), + DbKeySeg::StringSeg(lazy_map), + DbKeySeg::StringSeg(data), + DbKeySeg::StringSeg(epoch_str), + ] if addr == &ADDRESS + && prefix == BOND_STORAGE_KEY + && lazy_map == crate::epoched::LAZY_MAP_SUB_KEY + && data == lazy_map::DATA_SUBKEY => + { + let start = Epoch::parse(epoch_str.clone()).ok()?; + Some(( + BondId { + source: source.clone(), + validator: validator.clone(), + }, + start, + )) + } + _ => None, + } + } else { + None + } +} + +/// Storage key for the total bonds for a given validator. +pub fn validator_total_bonded_key(validator: &Address) -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&VALIDATOR_TOTAL_BONDED_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") + .push(&validator.to_db_key()) + .expect("Cannot obtain a storage key") +} + +/// Storage key prefix for all unbonds. +pub fn unbonds_prefix() -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&UNBOND_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Storage key prefix for all unbonds of the given source address. +pub fn unbonds_for_source_prefix(source: &Address) -> Key { + unbonds_prefix() + .push(&source.to_db_key()) + .expect("Cannot obtain a storage key") +} + +/// Storage key for an unbond with the given ID (source and validator). +pub fn unbond_key(bond_id: &BondId) -> Key { + unbonds_for_source_prefix(&bond_id.source) + .push(&bond_id.validator.to_db_key()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for an unbond? Returns the bond ID and unbond start and +/// withdraw epoch if it is. +pub fn is_unbond_key(key: &Key) -> Option<(BondId, Epoch, Epoch)> { + if key.segments.len() >= 8 { + match &key.segments[..8] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(source), + DbKeySeg::AddressSeg(validator), + DbKeySeg::StringSeg(data_1), + DbKeySeg::StringSeg(start_epoch_str), + DbKeySeg::StringSeg(data_2), + DbKeySeg::StringSeg(withdraw_epoch_str), + ] if addr == &ADDRESS + && prefix == UNBOND_STORAGE_KEY + && data_1 == lazy_map::DATA_SUBKEY + && data_2 == lazy_map::DATA_SUBKEY => + { + let withdraw = Epoch::parse(withdraw_epoch_str.clone()).ok()?; + let start = Epoch::parse(start_epoch_str.clone()).ok()?; + Some(( + BondId { + source: source.clone(), + validator: validator.clone(), + }, + start, + withdraw, + )) + } + _ => None, + } + } else { + None + } +} + +/// Storage key for validator's total-unbonded amount to track for slashing +pub fn validator_total_unbonded_key(validator: &Address) -> Key { + validator_prefix(validator) + .push(&VALIDATOR_TOTAL_UNBONDED_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Storage prefix for validator sets. +pub fn validator_sets_prefix() -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&VALIDATOR_SETS_STORAGE_PREFIX.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Storage key for consensus validator set +pub fn consensus_validator_set_key() -> Key { + validator_sets_prefix() + .push(&CONSENSUS_VALIDATOR_SET_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Storage key for below-capacity validator set +pub fn below_capacity_validator_set_key() -> Key { + validator_sets_prefix() + .push(&BELOW_CAPACITY_VALIDATOR_SET_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for the consensus validator set? +pub fn is_consensus_validator_set_key(key: &Key) -> bool { + matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(key), DbKeySeg::StringSeg(set_type), DbKeySeg::StringSeg(lazy_map), DbKeySeg::StringSeg(data), DbKeySeg::StringSeg(_epoch), DbKeySeg::StringSeg(_), DbKeySeg::StringSeg(_amount), DbKeySeg::StringSeg(_), DbKeySeg::StringSeg(_position)] if addr == &ADDRESS && key == VALIDATOR_SETS_STORAGE_PREFIX && set_type == CONSENSUS_VALIDATOR_SET_STORAGE_KEY && lazy_map == LAZY_MAP_SUB_KEY && data == lazy_map::DATA_SUBKEY) +} + +/// Is storage key for the below-capacity validator set? +pub fn is_below_capacity_validator_set_key(key: &Key) -> bool { + matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(key), DbKeySeg::StringSeg(set_type), DbKeySeg::StringSeg(lazy_map), DbKeySeg::StringSeg(data), DbKeySeg::StringSeg(_epoch), DbKeySeg::StringSeg(_), DbKeySeg::StringSeg(_amount), DbKeySeg::StringSeg(_), DbKeySeg::StringSeg(_position)] if addr == &ADDRESS && key == VALIDATOR_SETS_STORAGE_PREFIX && set_type == BELOW_CAPACITY_VALIDATOR_SET_STORAGE_KEY && lazy_map == LAZY_MAP_SUB_KEY && data == lazy_map::DATA_SUBKEY) +} + +/// Storage key for total consensus stake +pub fn total_consensus_stake_key() -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&TOTAL_CONSENSUS_STAKE_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a total consensus stake key") +} + +/// Is storage key for the total consensus stake? +pub fn is_total_consensus_stake_key(key: &Key) -> bool { + matches!(&key.segments[..], [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(key) + ] if addr == &ADDRESS && key == TOTAL_CONSENSUS_STAKE_STORAGE_KEY) +} + +/// Storage key for total deltas of all validators. +pub fn total_deltas_key() -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&TOTAL_DELTAS_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for total deltas of all validators? +pub fn is_total_deltas_key(key: &Key) -> Option<&String> { + match &key.segments[..] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(key), + DbKeySeg::StringSeg(lazy_map), + DbKeySeg::StringSeg(data), + DbKeySeg::StringSeg(epoch), + ] if addr == &ADDRESS + && key == TOTAL_DELTAS_STORAGE_KEY + && lazy_map == LAZY_MAP_SUB_KEY + && data == lazy_map::DATA_SUBKEY => + { + Some(epoch) + } + _ => None, + } +} + +/// Storage key for block proposer address of the previous block. +pub fn last_block_proposer_key() -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&LAST_BLOCK_PROPOSER_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for block proposer address of the previous block? +pub fn is_last_block_proposer_key(key: &Key) -> bool { + matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(key)] if addr == &ADDRESS && key == LAST_BLOCK_PROPOSER_STORAGE_KEY) +} + +/// Storage key for the consensus validator set rewards accumulator. +pub fn consensus_validator_rewards_accumulator_key() -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&CONSENSUS_VALIDATOR_SET_ACCUMULATOR_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for the consensus validator set? +pub fn is_consensus_validator_set_accumulator_key(key: &Key) -> bool { + matches!(&key.segments[..], [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(key), + ] if addr == &ADDRESS + && key == CONSENSUS_VALIDATOR_SET_ACCUMULATOR_STORAGE_KEY) +} + +/// Storage prefix for epoch at which an account last claimed PoS inflationary +/// rewards. +pub fn last_pos_reward_claim_epoch_prefix() -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&LAST_REWARD_CLAIM_EPOCH.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Storage key for epoch at which an account last claimed PoS inflationary +/// rewards. +pub fn last_pos_reward_claim_epoch_key( + delegator: &Address, + validator: &Address, +) -> Key { + last_pos_reward_claim_epoch_prefix() + .push(&delegator.to_db_key()) + .expect("Cannot obtain a storage key") + .push(&validator.to_db_key()) + .expect("Cannot obtain a storage key") +} + +/// Get validator address from bond key +pub fn get_validator_address_from_bond(key: &Key) -> Option
{ + match key.get_at(3) { + Some(segment) => match segment { + DbKeySeg::AddressSeg(addr) => Some(addr.clone()), + DbKeySeg::StringSeg(_) => None, + }, + None => None, + } +} + +/// Storage key for validator set positions +pub fn validator_set_positions_key() -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&VALIDATOR_SET_POSITIONS_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Storage key for consensus keys set. +pub fn consensus_keys_key() -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&CONSENSUS_KEYS.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for consensus keys set? +pub fn is_consensus_keys_key(key: &Key) -> bool { + matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(key)] if addr == &ADDRESS && key == CONSENSUS_KEYS) +} + +/// Storage key for a validator's email +pub fn validator_email_key(validator: &Address) -> Key { + validator_prefix(validator) + .push(&VALIDATOR_EMAIL_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Storage key for a validator's description +pub fn validator_description_key(validator: &Address) -> Key { + validator_prefix(validator) + .push(&VALIDATOR_DESCRIPTION_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Storage key for a validator's website +pub fn validator_website_key(validator: &Address) -> Key { + validator_prefix(validator) + .push(&VALIDATOR_WEBSITE_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Storage key for a validator's discord handle +pub fn validator_discord_key(validator: &Address) -> Key { + validator_prefix(validator) + .push(&VALIDATOR_DISCORD_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Storage prefix for the liveness data of the cosnensus validator set. +pub fn liveness_data_prefix() -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&LIVENESS_PREFIX.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Storage key for the liveness records. +pub fn liveness_missed_votes_key() -> Key { + liveness_data_prefix() + .push(&LIVENESS_MISSED_VOTES.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Storage key for the liveness data. +pub fn liveness_sum_missed_votes_key() -> Key { + liveness_data_prefix() + .push(&LIVENESS_MISSED_VOTES_SUM.to_owned()) + .expect("Cannot obtain a storage key") +} diff --git a/proof_of_stake/src/tests.rs b/proof_of_stake/src/tests.rs index 0398a7b3d7..f79eb582de 100644 --- a/proof_of_stake/src/tests.rs +++ b/proof_of_stake/src/tests.rs @@ -41,7 +41,25 @@ use test_log::test; use crate::epoched::DEFAULT_NUM_PAST_EPOCHS; use crate::parameters::testing::arb_pos_params; use crate::parameters::{OwnedPosParams, PosParams}; -use crate::rewards::PosRewardsCalculator; +use crate::queries::bonds_and_unbonds; +use crate::rewards::{ + log_block_rewards, update_rewards_products_and_mint_inflation, + PosRewardsCalculator, +}; +use crate::slashing::{ + compute_bond_at_epoch, compute_slash_bond_at_epoch, + compute_slashable_amount, process_slashes, slash, slash_redelegation, + slash_validator, slash_validator_redelegation, +}; +use crate::storage::{ + find_validator_by_raw_hash, get_consensus_key_set, + get_num_consensus_validators, + read_below_capacity_validator_set_addresses_with_stake, + read_below_threshold_validator_set_addresses, + read_consensus_validator_set_addresses_with_stake, read_total_stake, + read_validator_deltas_value, rewards_accumulator_handle, + total_deltas_handle, +}; use crate::test_utils::test_init_genesis; use crate::types::{ into_tm_voting_power, BondDetails, BondId, BondsAndUnbondsDetails, @@ -49,32 +67,24 @@ use crate::types::{ RedelegatedTokens, ReverseOrdTokenAmount, Slash, SlashType, UnbondDetails, ValidatorSetUpdate, ValidatorState, VoteInfo, WeightedValidator, }; +use crate::validator_set_update::validator_set_update_tendermint; use crate::{ apply_list_slashes, become_validator, below_capacity_validator_set_handle, - bond_handle, bond_tokens, bonds_and_unbonds, change_consensus_key, + bond_handle, bond_tokens, change_consensus_key, compute_amount_after_slashing_unbond, compute_amount_after_slashing_withdraw, - compute_and_store_total_consensus_stake, compute_bond_at_epoch, - compute_modified_redelegation, compute_new_redelegated_unbonds, - compute_slash_bond_at_epoch, compute_slashable_amount, - consensus_validator_set_handle, copy_validator_sets_and_positions, - delegator_redelegated_bonds_handle, delegator_redelegated_unbonds_handle, - find_bonds_to_remove, find_validator_by_raw_hash, - fold_and_slash_redelegated_bonds, get_consensus_key_set, - get_num_consensus_validators, insert_validator_into_validator_set, - is_validator, process_slashes, - read_below_capacity_validator_set_addresses_with_stake, - read_below_threshold_validator_set_addresses, - read_consensus_validator_set_addresses_with_stake, read_total_stake, - read_validator_deltas_value, read_validator_stake, slash, - slash_redelegation, slash_validator, slash_validator_redelegation, - staking_token_address, total_bonded_handle, total_deltas_handle, - total_unbonded_handle, unbond_handle, unbond_tokens, unjail_validator, - update_validator_deltas, update_validator_set, + compute_and_store_total_consensus_stake, compute_modified_redelegation, + compute_new_redelegated_unbonds, consensus_validator_set_handle, + copy_validator_sets_and_positions, delegator_redelegated_bonds_handle, + delegator_redelegated_unbonds_handle, find_bonds_to_remove, + fold_and_slash_redelegated_bonds, insert_validator_into_validator_set, + is_validator, read_validator_stake, staking_token_address, + total_bonded_handle, total_unbonded_handle, unbond_handle, unbond_tokens, + unjail_validator, update_validator_deltas, update_validator_set, validator_consensus_key_handle, validator_incoming_redelegations_handle, validator_outgoing_redelegations_handle, validator_set_positions_handle, - validator_set_update_tendermint, validator_slashes_handle, - validator_state_handle, validator_total_redelegated_bonded_handle, + validator_slashes_handle, validator_state_handle, + validator_total_redelegated_bonded_handle, validator_total_redelegated_unbonded_handle, withdraw_tokens, write_pos_params, write_validator_address_raw_hash, BecomeValidator, EagerRedelegatedUnbonds, FoldRedelegatedBondsResult, ModifiedRedelegation, @@ -1222,7 +1232,7 @@ fn test_slashes_with_unbonding_aux( s.commit_block().unwrap(); current_epoch = advance_epoch(&mut s, ¶ms); - super::process_slashes(&mut s, current_epoch).unwrap(); + process_slashes(&mut s, current_epoch).unwrap(); // Discover first slash let slash_0_evidence_epoch = current_epoch; @@ -1247,7 +1257,7 @@ fn test_slashes_with_unbonding_aux( slash_0_evidence_epoch + params.slash_processing_epoch_offset(); while current_epoch < unfreeze_epoch { current_epoch = advance_epoch(&mut s, ¶ms); - super::process_slashes(&mut s, current_epoch).unwrap(); + process_slashes(&mut s, current_epoch).unwrap(); } // Advance more epochs randomly from the generated delay @@ -1285,7 +1295,7 @@ fn test_slashes_with_unbonding_aux( let withdraw_epoch = unbond_epoch + params.withdrawable_epoch_offset(); while current_epoch < withdraw_epoch { current_epoch = advance_epoch(&mut s, ¶ms); - super::process_slashes(&mut s, current_epoch).unwrap(); + process_slashes(&mut s, current_epoch).unwrap(); } let token = staking_token_address(&s); let val_balance_pre = read_balance(&s, &token, val_addr).unwrap(); @@ -1294,8 +1304,7 @@ fn test_slashes_with_unbonding_aux( source: val_addr.clone(), validator: val_addr.clone(), }; - let binding = - super::bonds_and_unbonds(&s, None, Some(val_addr.clone())).unwrap(); + let binding = bonds_and_unbonds(&s, None, Some(val_addr.clone())).unwrap(); let details = binding.get(&bond_id).unwrap(); let exp_withdraw_from_details = details.unbonds[0].amount - details.unbonds[0].slashed_amount.unwrap_or_default(); @@ -2398,7 +2407,7 @@ fn test_unjail_validator_aux( s.commit_block().unwrap(); current_epoch = advance_epoch(&mut s, ¶ms); - super::process_slashes(&mut s, current_epoch).unwrap(); + process_slashes(&mut s, current_epoch).unwrap(); // Discover first slash let slash_0_evidence_epoch = current_epoch; @@ -2449,7 +2458,7 @@ fn test_unjail_validator_aux( slash_0_evidence_epoch + params.slash_processing_epoch_offset(); while current_epoch < unfreeze_epoch + 4u64 { current_epoch = advance_epoch(&mut s, ¶ms); - super::process_slashes(&mut s, current_epoch).unwrap(); + process_slashes(&mut s, current_epoch).unwrap(); } // Unjail the validator @@ -2483,7 +2492,7 @@ fn test_unjail_validator_aux( // Advance another epoch current_epoch = advance_epoch(&mut s, ¶ms); - super::process_slashes(&mut s, current_epoch).unwrap(); + process_slashes(&mut s, current_epoch).unwrap(); let second_att = unjail_validator(&mut s, val_addr, current_epoch); assert!(second_att.is_err()); @@ -4471,7 +4480,7 @@ fn test_simple_redelegation_aux( for _ in 0..5 { current_epoch = advance_epoch(&mut storage, ¶ms); - super::process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes(&mut storage, current_epoch).unwrap(); } let init_epoch = current_epoch; @@ -4515,11 +4524,11 @@ fn test_simple_redelegation_aux( // Advance three epochs current_epoch = advance_epoch(&mut storage, ¶ms); - super::process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes(&mut storage, current_epoch).unwrap(); current_epoch = advance_epoch(&mut storage, ¶ms); - super::process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes(&mut storage, current_epoch).unwrap(); current_epoch = advance_epoch(&mut storage, ¶ms); - super::process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes(&mut storage, current_epoch).unwrap(); // Redelegate in epoch 3 println!( @@ -4667,11 +4676,11 @@ fn test_simple_redelegation_aux( // Advance three epochs current_epoch = advance_epoch(&mut storage, ¶ms); - super::process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes(&mut storage, current_epoch).unwrap(); current_epoch = advance_epoch(&mut storage, ¶ms); - super::process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes(&mut storage, current_epoch).unwrap(); current_epoch = advance_epoch(&mut storage, ¶ms); - super::process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes(&mut storage, current_epoch).unwrap(); // Unbond in epoch 5 from dest_validator println!( @@ -4796,7 +4805,7 @@ fn test_simple_redelegation_aux( // Advance to withdrawal epoch loop { current_epoch = advance_epoch(&mut storage, ¶ms); - super::process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes(&mut storage, current_epoch).unwrap(); if current_epoch == unbond_end { break; } @@ -4867,7 +4876,7 @@ fn test_redelegation_with_slashing_aux( for _ in 0..5 { current_epoch = advance_epoch(&mut storage, ¶ms); - super::process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes(&mut storage, current_epoch).unwrap(); } let init_epoch = current_epoch; @@ -4911,11 +4920,11 @@ fn test_redelegation_with_slashing_aux( // Advance three epochs current_epoch = advance_epoch(&mut storage, ¶ms); - super::process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes(&mut storage, current_epoch).unwrap(); current_epoch = advance_epoch(&mut storage, ¶ms); - super::process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes(&mut storage, current_epoch).unwrap(); current_epoch = advance_epoch(&mut storage, ¶ms); - super::process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes(&mut storage, current_epoch).unwrap(); // Redelegate in epoch 8 println!( @@ -5063,11 +5072,11 @@ fn test_redelegation_with_slashing_aux( // Advance three epochs current_epoch = advance_epoch(&mut storage, ¶ms); - super::process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes(&mut storage, current_epoch).unwrap(); current_epoch = advance_epoch(&mut storage, ¶ms); - super::process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes(&mut storage, current_epoch).unwrap(); current_epoch = advance_epoch(&mut storage, ¶ms); - super::process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes(&mut storage, current_epoch).unwrap(); // Unbond in epoch 11 from dest_validator println!( @@ -5154,7 +5163,7 @@ fn test_redelegation_with_slashing_aux( // Advance one epoch current_epoch = advance_epoch(&mut storage, ¶ms); - super::process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes(&mut storage, current_epoch).unwrap(); // Discover evidence slash( @@ -5209,7 +5218,7 @@ fn test_redelegation_with_slashing_aux( // Advance to withdrawal epoch loop { current_epoch = advance_epoch(&mut storage, ¶ms); - super::process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes(&mut storage, current_epoch).unwrap(); if current_epoch == unbond_end { break; } @@ -5288,7 +5297,7 @@ fn test_chain_redelegations_aux(mut validators: Vec) { // Advance one epoch current_epoch = advance_epoch(&mut storage, ¶ms); - super::process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes(&mut storage, current_epoch).unwrap(); // Redelegate in epoch 1 to dest_validator let redel_amount_1: token::Amount = 58.into(); @@ -5401,9 +5410,9 @@ fn test_chain_redelegations_aux(mut validators: Vec) { // Attempt to redelegate in epoch 3 to dest_validator current_epoch = advance_epoch(&mut storage, ¶ms); - super::process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes(&mut storage, current_epoch).unwrap(); current_epoch = advance_epoch(&mut storage, ¶ms); - super::process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes(&mut storage, current_epoch).unwrap(); let redel_amount_2: token::Amount = 23.into(); let redel_att = super::redelegate_tokens( @@ -5422,7 +5431,7 @@ fn test_chain_redelegations_aux(mut validators: Vec) { redel_end.prev() + params.slash_processing_epoch_offset(); loop { current_epoch = advance_epoch(&mut storage, ¶ms); - super::process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes(&mut storage, current_epoch).unwrap(); if current_epoch == epoch_can_redel.prev() { break; } @@ -5441,7 +5450,7 @@ fn test_chain_redelegations_aux(mut validators: Vec) { // Advance one more epoch current_epoch = advance_epoch(&mut storage, ¶ms); - super::process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes(&mut storage, current_epoch).unwrap(); // Redelegate from dest_validator to dest_validator_2 now super::redelegate_tokens( @@ -5808,7 +5817,7 @@ fn test_overslashing_aux(mut validators: Vec) { // Advance to processing epoch 1 loop { current_epoch = advance_epoch(&mut storage, ¶ms); - super::process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes(&mut storage, current_epoch).unwrap(); if current_epoch == processing_epoch_1 { break; } @@ -5845,7 +5854,7 @@ fn test_overslashing_aux(mut validators: Vec) { // Advance to processing epoch 2 loop { current_epoch = advance_epoch(&mut storage, ¶ms); - super::process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes(&mut storage, current_epoch).unwrap(); if current_epoch == processing_epoch_2 { break; } @@ -5963,7 +5972,7 @@ fn test_unslashed_bond_amount_aux(validators: Vec) { // Advance an epoch current_epoch = advance_epoch(&mut storage, ¶ms); - super::process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes(&mut storage, current_epoch).unwrap(); // Bond to validator 1 super::bond_tokens( @@ -6011,7 +6020,7 @@ fn test_unslashed_bond_amount_aux(validators: Vec) { // Advance an epoch current_epoch = advance_epoch(&mut storage, ¶ms); - super::process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes(&mut storage, current_epoch).unwrap(); // Bond to validator 1 super::bond_tokens( @@ -6108,7 +6117,7 @@ fn test_log_block_rewards_aux( tracing::info!(?proposer_address,); // Rewards accumulator should be empty at first - let rewards_handle = crate::rewards_accumulator_handle(); + let rewards_handle = rewards_accumulator_handle(); assert!(rewards_handle.is_empty(&s).unwrap()); let mut last_rewards = BTreeMap::default(); @@ -6159,7 +6168,7 @@ fn test_log_block_rewards_aux( }; let (votes, signing_stake, non_voters) = prep_votes(current_epoch); - crate::log_block_rewards( + log_block_rewards( &mut s, current_epoch, &proposer_address, @@ -6278,8 +6287,7 @@ fn test_log_block_rewards_aux( } s.commit_block().unwrap(); - last_rewards = - crate::rewards_accumulator_handle().collect_map(&s).unwrap(); + last_rewards = rewards_accumulator_handle().collect_map(&s).unwrap(); let rewards_sum: Dec = last_rewards.values().copied().sum(); let expected_sum = Dec::one() * (i as u64 + 1); @@ -6336,7 +6344,7 @@ fn test_update_rewards_products_aux(validators: Vec) { // Assign some reward accumulator values to consensus validator for validator in &consensus_set { - crate::rewards_accumulator_handle() + rewards_accumulator_handle() .insert( &mut s, validator.clone(), @@ -6348,7 +6356,7 @@ fn test_update_rewards_products_aux(validators: Vec) { // Distribute inflation into rewards let last_epoch = current_epoch.prev(); let inflation = token::Amount::native_whole(10_000_000); - crate::update_rewards_products_and_mint_inflation( + update_rewards_products_and_mint_inflation( &mut s, ¶ms, last_epoch, @@ -6380,7 +6388,7 @@ fn test_update_rewards_products_aux(validators: Vec) { ); // Rewards accumulator must be cleared out - let rewards_handle = crate::rewards_accumulator_handle(); + let rewards_handle = rewards_accumulator_handle(); assert!(rewards_handle.is_empty(&s).unwrap()); } @@ -6467,7 +6475,7 @@ fn test_slashed_bond_amount_aux(validators: Vec) { // Advance an epoch to 1 current_epoch = advance_epoch(&mut storage, ¶ms); - super::process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes(&mut storage, current_epoch).unwrap(); // Bond to validator 1 super::bond_tokens( @@ -6515,7 +6523,7 @@ fn test_slashed_bond_amount_aux(validators: Vec) { // Advance an epoch to ep 2 current_epoch = advance_epoch(&mut storage, ¶ms); - super::process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes(&mut storage, current_epoch).unwrap(); // Bond to validator 1 super::bond_tokens( @@ -6553,11 +6561,11 @@ fn test_slashed_bond_amount_aux(validators: Vec) { // Advance two epochs to ep 4 for _ in 0..2 { current_epoch = advance_epoch(&mut storage, ¶ms); - super::process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes(&mut storage, current_epoch).unwrap(); } // Find some slashes committed in various epochs - super::slash( + slash( &mut storage, ¶ms, current_epoch, @@ -6568,7 +6576,7 @@ fn test_slashed_bond_amount_aux(validators: Vec) { current_epoch, ) .unwrap(); - super::slash( + slash( &mut storage, ¶ms, current_epoch, @@ -6579,7 +6587,7 @@ fn test_slashed_bond_amount_aux(validators: Vec) { current_epoch, ) .unwrap(); - super::slash( + slash( &mut storage, ¶ms, current_epoch, @@ -6590,7 +6598,7 @@ fn test_slashed_bond_amount_aux(validators: Vec) { current_epoch, ) .unwrap(); - super::slash( + slash( &mut storage, ¶ms, current_epoch, @@ -6605,7 +6613,7 @@ fn test_slashed_bond_amount_aux(validators: Vec) { // Advance such that these slashes are all processed for _ in 0..params.slash_processing_epoch_offset() { current_epoch = advance_epoch(&mut storage, ¶ms); - super::process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes(&mut storage, current_epoch).unwrap(); } let pipeline_epoch = current_epoch + params.pipeline_len; @@ -6811,7 +6819,7 @@ fn test_is_delegator_aux(mut validators: Vec) { // Advance to epoch 1 current_epoch = advance_epoch(&mut storage, ¶ms); - super::process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes(&mut storage, current_epoch).unwrap(); // Delegate in epoch 1 to validator1 let del1_epoch = current_epoch; @@ -6827,7 +6835,7 @@ fn test_is_delegator_aux(mut validators: Vec) { // Advance to epoch 2 current_epoch = advance_epoch(&mut storage, ¶ms); - super::process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes(&mut storage, current_epoch).unwrap(); // Delegate in epoch 2 to validator2 let del2_epoch = current_epoch; diff --git a/proof_of_stake/src/tests/state_machine.rs b/proof_of_stake/src/tests/state_machine.rs index 781d276105..395c57d7ef 100644 --- a/proof_of_stake/src/tests/state_machine.rs +++ b/proof_of_stake/src/tests/state_machine.rs @@ -29,6 +29,13 @@ use test_log::test; use crate::parameters::testing::arb_rate; use crate::parameters::PosParams; +use crate::storage::{ + enqueued_slashes_handle, read_all_validator_addresses, + read_below_capacity_validator_set_addresses, + read_below_capacity_validator_set_addresses_with_stake, + read_below_threshold_validator_set_addresses, + read_consensus_validator_set_addresses_with_stake, +}; use crate::tests::arb_params_and_genesis_validators; use crate::types::{ BondId, EagerRedelegatedBondsMap, GenesisValidator, ReverseOrdTokenAmount, @@ -36,7 +43,6 @@ use crate::types::{ }; use crate::{ below_capacity_validator_set_handle, consensus_validator_set_handle, - enqueued_slashes_handle, read_below_threshold_validator_set_addresses, read_pos_params, redelegate_tokens, validator_deltas_handle, validator_slashes_handle, validator_state_handle, BondsForRemovalRes, EagerRedelegatedUnbonds, FoldRedelegatedBondsResult, ModifiedRedelegation, @@ -547,9 +553,10 @@ impl StateMachineTest for ConcretePosState { .unwrap(); // Find delegations - let delegations_pre = - crate::find_delegations(&state.s, &id.source, &pipeline) - .unwrap(); + let delegations_pre = crate::queries::find_delegations( + &state.s, &id.source, &pipeline, + ) + .unwrap(); // Apply redelegation let result = redelegate_tokens( @@ -668,7 +675,7 @@ impl StateMachineTest for ConcretePosState { // updated with redelegation. For the source reduced by the // redelegation amount and for the destination increased by // the redelegation amount, less any slashes. - let delegations_post = crate::find_delegations( + let delegations_post = crate::queries::find_delegations( &state.s, &id.source, &pipeline, ) .unwrap(); @@ -769,13 +776,13 @@ impl ConcretePosState { // Post-condition: Consensus validator sets at pipeline offset // must be the same as at the epoch before it. let consensus_set_before_pipeline = - crate::read_consensus_validator_set_addresses_with_stake( + read_consensus_validator_set_addresses_with_stake( &self.s, before_pipeline, ) .unwrap(); let consensus_set_at_pipeline = - crate::read_consensus_validator_set_addresses_with_stake( + read_consensus_validator_set_addresses_with_stake( &self.s, pipeline, ) .unwrap(); @@ -787,13 +794,13 @@ impl ConcretePosState { // Post-condition: Below-capacity validator sets at pipeline // offset must be the same as at the epoch before it. let below_cap_before_pipeline = - crate::read_below_capacity_validator_set_addresses_with_stake( + read_below_capacity_validator_set_addresses_with_stake( &self.s, before_pipeline, ) .unwrap(); let below_cap_at_pipeline = - crate::read_below_capacity_validator_set_addresses_with_stake( + read_below_capacity_validator_set_addresses_with_stake( &self.s, pipeline, ) .unwrap(); @@ -1171,21 +1178,18 @@ impl ConcretePosState { || (num_occurrences == 0 && validator_is_jailed) ); - let consensus_set = - crate::read_consensus_validator_set_addresses_with_stake( - &self.s, pipeline, - ) - .unwrap(); + let consensus_set = read_consensus_validator_set_addresses_with_stake( + &self.s, pipeline, + ) + .unwrap(); let below_cap_set = - crate::read_below_capacity_validator_set_addresses_with_stake( + read_below_capacity_validator_set_addresses_with_stake( &self.s, pipeline, ) .unwrap(); let below_thresh_set = - crate::read_below_threshold_validator_set_addresses( - &self.s, pipeline, - ) - .unwrap(); + read_below_threshold_validator_set_addresses(&self.s, pipeline) + .unwrap(); let weighted = WeightedValidator { bonded_stake: stake_at_pipeline, address: id.validator, @@ -1310,21 +1314,17 @@ impl ConcretePosState { .contains(address) ); assert!( - !crate::read_below_capacity_validator_set_addresses( - &self.s, epoch - ) - .unwrap() - .contains(address) + !read_below_capacity_validator_set_addresses(&self.s, epoch) + .unwrap() + .contains(address) ); assert!( - !crate::read_below_threshold_validator_set_addresses( - &self.s, epoch - ) - .unwrap() - .contains(address) + !read_below_threshold_validator_set_addresses(&self.s, epoch) + .unwrap() + .contains(address) ); assert!( - !crate::read_all_validator_addresses(&self.s, epoch) + !read_all_validator_addresses(&self.s, epoch) .unwrap() .contains(address) ); @@ -1333,17 +1333,14 @@ impl ConcretePosState { crate::read_consensus_validator_set_addresses(&self.s, pipeline) .unwrap() .contains(address); - let in_bc = crate::read_below_capacity_validator_set_addresses( - &self.s, pipeline, - ) - .unwrap() - .contains(address); + let in_bc = + read_below_capacity_validator_set_addresses(&self.s, pipeline) + .unwrap() + .contains(address); let in_below_thresh = - crate::read_below_threshold_validator_set_addresses( - &self.s, pipeline, - ) - .unwrap() - .contains(address); + read_below_threshold_validator_set_addresses(&self.s, pipeline) + .unwrap() + .contains(address); assert!(in_below_thresh && !in_consensus && !in_bc); } @@ -1410,7 +1407,7 @@ impl ConcretePosState { let abs_enqueued = ref_state.enqueued_slashes.clone(); let mut conc_enqueued: BTreeMap>> = BTreeMap::new(); - crate::enqueued_slashes_handle() + enqueued_slashes_handle() .get_data_handler() .iter(&self.s) .unwrap() @@ -1764,7 +1761,7 @@ impl ConcretePosState { for WeightedValidator { bonded_stake, address: validator, - } in crate::read_consensus_validator_set_addresses_with_stake( + } in read_consensus_validator_set_addresses_with_stake( &self.s, epoch, ) .unwrap() @@ -1821,11 +1818,10 @@ impl ConcretePosState { for WeightedValidator { bonded_stake, address: validator, - } in - crate::read_below_capacity_validator_set_addresses_with_stake( - &self.s, epoch, - ) - .unwrap() + } in read_below_capacity_validator_set_addresses_with_stake( + &self.s, epoch, + ) + .unwrap() { let deltas_stake = validator_deltas_handle(&validator) .get_sum(&self.s, epoch, params) @@ -1873,10 +1869,8 @@ impl ConcretePosState { } for validator in - crate::read_below_threshold_validator_set_addresses( - &self.s, epoch, - ) - .unwrap() + read_below_threshold_validator_set_addresses(&self.s, epoch) + .unwrap() { let stake = crate::read_validator_stake( &self.s, params, &validator, epoch, @@ -1921,7 +1915,7 @@ impl ConcretePosState { // Jailed validators not in a set let all_validators = - crate::read_all_validator_addresses(&self.s, epoch).unwrap(); + read_all_validator_addresses(&self.s, epoch).unwrap(); for validator in all_validators { let state = validator_state_handle(&validator) diff --git a/proof_of_stake/src/tests/state_machine_v2.rs b/proof_of_stake/src/tests/state_machine_v2.rs index cf59a59238..828a2eb766 100644 --- a/proof_of_stake/src/tests/state_machine_v2.rs +++ b/proof_of_stake/src/tests/state_machine_v2.rs @@ -32,6 +32,15 @@ use yansi::Paint; use super::utils::DbgPrintDiff; use crate::parameters::testing::arb_rate; use crate::parameters::PosParams; +use crate::queries::find_delegations; +use crate::slashing::find_slashes_in_range; +use crate::storage::{ + enqueued_slashes_handle, read_all_validator_addresses, + read_below_capacity_validator_set_addresses, + read_below_capacity_validator_set_addresses_with_stake, + read_below_threshold_validator_set_addresses, + read_consensus_validator_set_addresses_with_stake, +}; use crate::tests::arb_params_and_genesis_validators; use crate::tests::utils::pause_for_enter; use crate::types::{ @@ -41,10 +50,8 @@ use crate::types::{ use crate::{ below_capacity_validator_set_handle, bond_handle, consensus_validator_set_handle, delegator_redelegated_bonds_handle, - enqueued_slashes_handle, find_slashes_in_range, - read_below_threshold_validator_set_addresses, read_pos_params, - redelegate_tokens, validator_deltas_handle, validator_slashes_handle, - validator_state_handle, RedelegationError, + read_pos_params, redelegate_tokens, validator_deltas_handle, + validator_slashes_handle, validator_state_handle, RedelegationError, }; prop_state_machine! { @@ -2454,8 +2461,7 @@ impl StateMachineTest for ConcretePosState { // Find delegations let delegations_pre = - crate::find_delegations(&state.s, &id.source, &pipeline) - .unwrap(); + find_delegations(&state.s, &id.source, &pipeline).unwrap(); // Apply redelegation let result = redelegate_tokens( @@ -2607,10 +2613,9 @@ impl StateMachineTest for ConcretePosState { // updated with redelegation. For the source reduced by the // redelegation amount and for the destination increased by // the redelegation amount, less any slashes. - let delegations_post = crate::find_delegations( - &state.s, &id.source, &pipeline, - ) - .unwrap(); + let delegations_post = + find_delegations(&state.s, &id.source, &pipeline) + .unwrap(); let src_delegation_pre = delegations_pre .get(&id.validator) .cloned() @@ -2725,13 +2730,13 @@ impl ConcretePosState { // Post-condition: Consensus validator sets at pipeline offset // must be the same as at the epoch before it. let consensus_set_before_pipeline = - crate::read_consensus_validator_set_addresses_with_stake( + read_consensus_validator_set_addresses_with_stake( &self.s, before_pipeline, ) .unwrap(); let consensus_set_at_pipeline = - crate::read_consensus_validator_set_addresses_with_stake( + read_consensus_validator_set_addresses_with_stake( &self.s, pipeline, ) .unwrap(); @@ -2743,13 +2748,13 @@ impl ConcretePosState { // Post-condition: Below-capacity validator sets at pipeline // offset must be the same as at the epoch before it. let below_cap_before_pipeline = - crate::read_below_capacity_validator_set_addresses_with_stake( + read_below_capacity_validator_set_addresses_with_stake( &self.s, before_pipeline, ) .unwrap(); let below_cap_at_pipeline = - crate::read_below_capacity_validator_set_addresses_with_stake( + read_below_capacity_validator_set_addresses_with_stake( &self.s, pipeline, ) .unwrap(); @@ -2938,21 +2943,18 @@ impl ConcretePosState { || (num_occurrences == 0 && validator_is_jailed) ); - let consensus_set = - crate::read_consensus_validator_set_addresses_with_stake( - &self.s, pipeline, - ) - .unwrap(); + let consensus_set = read_consensus_validator_set_addresses_with_stake( + &self.s, pipeline, + ) + .unwrap(); let below_cap_set = - crate::read_below_capacity_validator_set_addresses_with_stake( + read_below_capacity_validator_set_addresses_with_stake( &self.s, pipeline, ) .unwrap(); let below_thresh_set = - crate::read_below_threshold_validator_set_addresses( - &self.s, pipeline, - ) - .unwrap(); + read_below_threshold_validator_set_addresses(&self.s, pipeline) + .unwrap(); let weighted = WeightedValidator { bonded_stake: stake_at_pipeline, address: id.validator, @@ -3015,21 +3017,17 @@ impl ConcretePosState { .contains(address) ); assert!( - !crate::read_below_capacity_validator_set_addresses( - &self.s, epoch - ) - .unwrap() - .contains(address) + !read_below_capacity_validator_set_addresses(&self.s, epoch) + .unwrap() + .contains(address) ); assert!( - !crate::read_below_threshold_validator_set_addresses( - &self.s, epoch - ) - .unwrap() - .contains(address) + !read_below_threshold_validator_set_addresses(&self.s, epoch) + .unwrap() + .contains(address) ); assert!( - !crate::read_all_validator_addresses(&self.s, epoch) + !read_all_validator_addresses(&self.s, epoch) .unwrap() .contains(address) ); @@ -3038,17 +3036,14 @@ impl ConcretePosState { crate::read_consensus_validator_set_addresses(&self.s, pipeline) .unwrap() .contains(address); - let in_bc = crate::read_below_capacity_validator_set_addresses( - &self.s, pipeline, - ) - .unwrap() - .contains(address); + let in_bc = + read_below_capacity_validator_set_addresses(&self.s, pipeline) + .unwrap() + .contains(address); let in_below_thresh = - crate::read_below_threshold_validator_set_addresses( - &self.s, pipeline, - ) - .unwrap() - .contains(address); + read_below_threshold_validator_set_addresses(&self.s, pipeline) + .unwrap() + .contains(address); assert!(in_below_thresh && !in_consensus && !in_bc); } @@ -3204,7 +3199,7 @@ impl ConcretePosState { for WeightedValidator { bonded_stake, address: validator, - } in crate::read_consensus_validator_set_addresses_with_stake( + } in read_consensus_validator_set_addresses_with_stake( &self.s, epoch, ) .unwrap() @@ -3274,11 +3269,10 @@ impl ConcretePosState { for WeightedValidator { bonded_stake, address: validator, - } in - crate::read_below_capacity_validator_set_addresses_with_stake( - &self.s, epoch, - ) - .unwrap() + } in read_below_capacity_validator_set_addresses_with_stake( + &self.s, epoch, + ) + .unwrap() { let deltas_stake = validator_deltas_handle(&validator) .get_sum(&self.s, epoch, params) @@ -3359,10 +3353,8 @@ impl ConcretePosState { } for validator in - crate::read_below_threshold_validator_set_addresses( - &self.s, epoch, - ) - .unwrap() + read_below_threshold_validator_set_addresses(&self.s, epoch) + .unwrap() { let conc_stake = validator_deltas_handle(&validator) .get_sum(&self.s, epoch, params) @@ -3425,7 +3417,7 @@ impl ConcretePosState { // Jailed validators not in a set let all_validators = - crate::read_all_validator_addresses(&self.s, epoch).unwrap(); + read_all_validator_addresses(&self.s, epoch).unwrap(); for val in all_validators { let state = validator_state_handle(&val) diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 20c9b2a0ca..faba695af2 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -590,6 +590,17 @@ pub struct VoteInfo { pub validator_vp: u64, } +/// Temp: In quint this is from `ResultUnbondTx` field `resultSlashing: {sum: +/// int, epochMap: Epoch -> int}` +#[derive(Debug, Default)] +pub struct ResultSlashing { + /// The token amount unbonded from the validator stake after accounting for + /// slashes + pub sum: token::Amount, + /// Map from bond start epoch to token amount after slashing + pub epoch_map: BTreeMap, +} + /// Bonds and unbonds with all details (slashes and rewards, if any) /// grouped by their bond IDs. pub type BondsAndUnbondsDetails = HashMap; diff --git a/proof_of_stake/src/validator_set_update.rs b/proof_of_stake/src/validator_set_update.rs new file mode 100644 index 0000000000..b33a3f7927 --- /dev/null +++ b/proof_of_stake/src/validator_set_update.rs @@ -0,0 +1,965 @@ +//! Validator set updates + +use std::collections::{HashMap, HashSet}; + +use namada_core::ledger::storage_api::collections::lazy_map::{ + NestedSubKey, SubKey, +}; +use namada_core::ledger::storage_api::{self, StorageRead, StorageWrite}; +use namada_core::types::address::Address; +use namada_core::types::key::PublicKeyTmRawHash; +use namada_core::types::storage::Epoch; +use namada_core::types::token; +use once_cell::unsync::Lazy; + +use crate::storage::{ + below_capacity_validator_set_handle, consensus_validator_set_handle, + get_num_consensus_validators, read_validator_stake, + validator_addresses_handle, validator_consensus_key_handle, + validator_set_positions_handle, validator_state_handle, +}; +use crate::types::{ + into_tm_voting_power, BelowCapacityValidatorSet, ConsensusValidator, + ConsensusValidatorSet, Position, ReverseOrdTokenAmount, + ValidatorPositionAddresses, ValidatorSetUpdate, ValidatorState, +}; +use crate::PosParams; + +/// Update validator set at the pipeline epoch when a validator receives a new +/// bond and when its bond is unbonded (self-bond or delegation). +pub fn update_validator_set( + storage: &mut S, + params: &PosParams, + validator: &Address, + token_change: token::Change, + current_epoch: Epoch, + offset: Option, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + if token_change.is_zero() { + return Ok(()); + } + let offset = offset.unwrap_or(params.pipeline_len); + let epoch = current_epoch + offset; + tracing::debug!( + "Update epoch for validator set: {epoch}, validator: {validator}" + ); + let consensus_validator_set = consensus_validator_set_handle(); + let below_capacity_validator_set = below_capacity_validator_set_handle(); + + // Validator sets at the pipeline offset + let consensus_val_handle = consensus_validator_set.at(&epoch); + let below_capacity_val_handle = below_capacity_validator_set.at(&epoch); + + let tokens_pre = read_validator_stake(storage, params, validator, epoch)?; + + let tokens_post = tokens_pre + .change() + .checked_add(&token_change) + .expect("Post-validator set update token amount has overflowed"); + debug_assert!(tokens_post.non_negative()); + let tokens_post = token::Amount::from_change(tokens_post); + + // If token amounts both before and after the action are below the threshold + // stake, do nothing + if tokens_pre < params.validator_stake_threshold + && tokens_post < params.validator_stake_threshold + { + return Ok(()); + } + + // The position is only set when the validator is in consensus or + // below_capacity set (not in below_threshold set) + let position = + read_validator_set_position(storage, validator, epoch, params)?; + if let Some(position) = position { + let consensus_vals_pre = consensus_val_handle.at(&tokens_pre); + + let in_consensus = if consensus_vals_pre.contains(storage, &position)? { + let val_address = consensus_vals_pre.get(storage, &position)?; + debug_assert!(val_address.is_some()); + val_address == Some(validator.clone()) + } else { + false + }; + + if in_consensus { + // It's initially consensus + tracing::debug!("Target validator is consensus"); + + // First remove the consensus validator + consensus_vals_pre.remove(storage, &position)?; + + let max_below_capacity_validator_amount = + get_max_below_capacity_validator_amount( + &below_capacity_val_handle, + storage, + )? + .unwrap_or_default(); + + if tokens_post < params.validator_stake_threshold { + tracing::debug!( + "Demoting this validator to the below-threshold set" + ); + // Set the validator state as below-threshold + validator_state_handle(validator).set( + storage, + ValidatorState::BelowThreshold, + current_epoch, + offset, + )?; + + // Remove the validator's position from storage + validator_set_positions_handle() + .at(&epoch) + .remove(storage, validator)?; + + // Promote the next below-cap validator if there is one + if let Some(max_bc_amount) = + get_max_below_capacity_validator_amount( + &below_capacity_val_handle, + storage, + )? + { + // Remove the max below-capacity validator first + let below_capacity_vals_max = + below_capacity_val_handle.at(&max_bc_amount.into()); + let lowest_position = + find_first_position(&below_capacity_vals_max, storage)? + .unwrap(); + let removed_max_below_capacity = below_capacity_vals_max + .remove(storage, &lowest_position)? + .expect("Must have been removed"); + + // Insert the previous max below-capacity validator into the + // consensus set + insert_validator_into_set( + &consensus_val_handle.at(&max_bc_amount), + storage, + &epoch, + &removed_max_below_capacity, + )?; + validator_state_handle(&removed_max_below_capacity).set( + storage, + ValidatorState::Consensus, + current_epoch, + offset, + )?; + } + } else if tokens_post < max_below_capacity_validator_amount { + tracing::debug!( + "Demoting this validator to the below-capacity set and \ + promoting another to the consensus set" + ); + // Place the validator into the below-capacity set and promote + // the lowest position max below-capacity + // validator. + + // Remove the max below-capacity validator first + let below_capacity_vals_max = below_capacity_val_handle + .at(&max_below_capacity_validator_amount.into()); + let lowest_position = + find_first_position(&below_capacity_vals_max, storage)? + .unwrap(); + let removed_max_below_capacity = below_capacity_vals_max + .remove(storage, &lowest_position)? + .expect("Must have been removed"); + + // Insert the previous max below-capacity validator into the + // consensus set + insert_validator_into_set( + &consensus_val_handle + .at(&max_below_capacity_validator_amount), + storage, + &epoch, + &removed_max_below_capacity, + )?; + validator_state_handle(&removed_max_below_capacity).set( + storage, + ValidatorState::Consensus, + current_epoch, + offset, + )?; + + // Insert the current validator into the below-capacity set + insert_validator_into_set( + &below_capacity_val_handle.at(&tokens_post.into()), + storage, + &epoch, + validator, + )?; + validator_state_handle(validator).set( + storage, + ValidatorState::BelowCapacity, + current_epoch, + offset, + )?; + } else { + tracing::debug!("Validator remains in consensus set"); + // The current validator should remain in the consensus set - + // place it into a new position + insert_validator_into_set( + &consensus_val_handle.at(&tokens_post), + storage, + &epoch, + validator, + )?; + } + } else { + // It's initially below-capacity + tracing::debug!("Target validator is below-capacity"); + + let below_capacity_vals_pre = + below_capacity_val_handle.at(&tokens_pre.into()); + let removed = below_capacity_vals_pre.remove(storage, &position)?; + debug_assert!(removed.is_some()); + debug_assert_eq!(&removed.unwrap(), validator); + + let min_consensus_validator_amount = + get_min_consensus_validator_amount( + &consensus_val_handle, + storage, + )?; + + if tokens_post > min_consensus_validator_amount { + // Place the validator into the consensus set and demote the + // last position min consensus validator to the + // below-capacity set + tracing::debug!( + "Inserting validator into the consensus set and demoting \ + a consensus validator to the below-capacity set" + ); + + insert_into_consensus_and_demote_to_below_cap( + storage, + validator, + tokens_post, + min_consensus_validator_amount, + current_epoch, + offset, + &consensus_val_handle, + &below_capacity_val_handle, + )?; + } else if tokens_post >= params.validator_stake_threshold { + tracing::debug!("Validator remains in below-capacity set"); + // The current validator should remain in the below-capacity set + insert_validator_into_set( + &below_capacity_val_handle.at(&tokens_post.into()), + storage, + &epoch, + validator, + )?; + validator_state_handle(validator).set( + storage, + ValidatorState::BelowCapacity, + current_epoch, + offset, + )?; + } else { + // The current validator is demoted to the below-threshold set + tracing::debug!( + "Demoting this validator to the below-threshold set" + ); + + validator_state_handle(validator).set( + storage, + ValidatorState::BelowThreshold, + current_epoch, + offset, + )?; + + // Remove the validator's position from storage + validator_set_positions_handle() + .at(&epoch) + .remove(storage, validator)?; + } + } + } else { + // At non-zero offset (0 is genesis only) + if offset > 0 { + // If there is no position at pipeline offset, then the validator + // must be in the below-threshold set + debug_assert!(tokens_pre < params.validator_stake_threshold); + } + tracing::debug!("Target validator is below-threshold"); + + // Move the validator into the appropriate set + let num_consensus_validators = + get_num_consensus_validators(storage, epoch)?; + if num_consensus_validators < params.max_validator_slots { + // Just insert into the consensus set + tracing::debug!("Inserting validator into the consensus set"); + + insert_validator_into_set( + &consensus_val_handle.at(&tokens_post), + storage, + &epoch, + validator, + )?; + validator_state_handle(validator).set( + storage, + ValidatorState::Consensus, + current_epoch, + offset, + )?; + } else { + let min_consensus_validator_amount = + get_min_consensus_validator_amount( + &consensus_val_handle, + storage, + )?; + if tokens_post > min_consensus_validator_amount { + // Insert this validator into consensus and demote one into the + // below-capacity + tracing::debug!( + "Inserting validator into the consensus set and demoting \ + a consensus validator to the below-capacity set" + ); + + insert_into_consensus_and_demote_to_below_cap( + storage, + validator, + tokens_post, + min_consensus_validator_amount, + current_epoch, + offset, + &consensus_val_handle, + &below_capacity_val_handle, + )?; + } else { + // Insert this validator into below-capacity + tracing::debug!( + "Inserting validator into the below-capacity set" + ); + + insert_validator_into_set( + &below_capacity_val_handle.at(&tokens_post.into()), + storage, + &epoch, + validator, + )?; + validator_state_handle(validator).set( + storage, + ValidatorState::BelowCapacity, + current_epoch, + offset, + )?; + } + } + } + + Ok(()) +} + +/// Insert the new validator into the right validator set (depending on its +/// stake) +pub fn insert_validator_into_validator_set( + storage: &mut S, + params: &PosParams, + address: &Address, + stake: token::Amount, + current_epoch: Epoch, + offset: u64, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let target_epoch = current_epoch + offset; + let consensus_set = consensus_validator_set_handle().at(&target_epoch); + let below_cap_set = below_capacity_validator_set_handle().at(&target_epoch); + + let num_consensus_validators = + get_num_consensus_validators(storage, target_epoch)?; + + if stake < params.validator_stake_threshold { + validator_state_handle(address).set( + storage, + ValidatorState::BelowThreshold, + current_epoch, + offset, + )?; + } else if num_consensus_validators < params.max_validator_slots { + insert_validator_into_set( + &consensus_set.at(&stake), + storage, + &target_epoch, + address, + )?; + validator_state_handle(address).set( + storage, + ValidatorState::Consensus, + current_epoch, + offset, + )?; + } else { + // Check to see if the current genesis validator should replace one + // already in the consensus set + let min_consensus_amount = + get_min_consensus_validator_amount(&consensus_set, storage)?; + if stake > min_consensus_amount { + // Swap this genesis validator in and demote the last min consensus + // validator + let min_consensus_handle = consensus_set.at(&min_consensus_amount); + // Remove last min consensus validator + let last_min_consensus_position = + find_last_position(&min_consensus_handle, storage)?.expect( + "There must be always be at least 1 consensus validator", + ); + let removed = min_consensus_handle + .remove(storage, &last_min_consensus_position)? + .expect( + "There must be always be at least 1 consensus validator", + ); + // Insert last min consensus validator into the below-capacity set + insert_validator_into_set( + &below_cap_set.at(&min_consensus_amount.into()), + storage, + &target_epoch, + &removed, + )?; + validator_state_handle(&removed).set( + storage, + ValidatorState::BelowCapacity, + current_epoch, + offset, + )?; + // Insert the current genesis validator into the consensus set + insert_validator_into_set( + &consensus_set.at(&stake), + storage, + &target_epoch, + address, + )?; + // Update and set the validator states + validator_state_handle(address).set( + storage, + ValidatorState::Consensus, + current_epoch, + offset, + )?; + } else { + // Insert the current genesis validator into the below-capacity set + insert_validator_into_set( + &below_cap_set.at(&stake.into()), + storage, + &target_epoch, + address, + )?; + validator_state_handle(address).set( + storage, + ValidatorState::BelowCapacity, + current_epoch, + offset, + )?; + } + } + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +fn insert_into_consensus_and_demote_to_below_cap( + storage: &mut S, + validator: &Address, + tokens_post: token::Amount, + min_consensus_amount: token::Amount, + current_epoch: Epoch, + offset: u64, + consensus_set: &ConsensusValidatorSet, + below_capacity_set: &BelowCapacityValidatorSet, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + // First, remove the last position min consensus validator + let consensus_vals_min = consensus_set.at(&min_consensus_amount); + let last_position_of_min_consensus_vals = + find_last_position(&consensus_vals_min, storage)? + .expect("There must be always be at least 1 consensus validator"); + let removed_min_consensus = consensus_vals_min + .remove(storage, &last_position_of_min_consensus_vals)? + .expect("There must be always be at least 1 consensus validator"); + + let offset_epoch = current_epoch + offset; + + // Insert the min consensus validator into the below-capacity + // set + insert_validator_into_set( + &below_capacity_set.at(&min_consensus_amount.into()), + storage, + &offset_epoch, + &removed_min_consensus, + )?; + validator_state_handle(&removed_min_consensus).set( + storage, + ValidatorState::BelowCapacity, + current_epoch, + offset, + )?; + + // Insert the current validator into the consensus set + insert_validator_into_set( + &consensus_set.at(&tokens_post), + storage, + &offset_epoch, + validator, + )?; + validator_state_handle(validator).set( + storage, + ValidatorState::Consensus, + current_epoch, + offset, + )?; + Ok(()) +} + +/// Find the first (lowest) position in a validator set if it is not empty +pub fn find_first_position( + handle: &ValidatorPositionAddresses, + storage: &S, +) -> storage_api::Result> +where + S: StorageRead, +{ + let lowest_position = handle + .iter(storage)? + .next() + .transpose()? + .map(|(position, _addr)| position); + Ok(lowest_position) +} + +/// Find the last (greatest) position in a validator set if it is not empty +fn find_last_position( + handle: &ValidatorPositionAddresses, + storage: &S, +) -> storage_api::Result> +where + S: StorageRead, +{ + let position = handle + .iter(storage)? + .last() + .transpose()? + .map(|(position, _addr)| position); + Ok(position) +} + +/// Find next position in a validator set or 0 if empty +fn find_next_position( + handle: &ValidatorPositionAddresses, + storage: &S, +) -> storage_api::Result +where + S: StorageRead, +{ + let position_iter = handle.iter(storage)?; + let next = position_iter + .last() + .transpose()? + .map(|(position, _address)| position.next()) + .unwrap_or_default(); + Ok(next) +} + +fn get_min_consensus_validator_amount( + handle: &ConsensusValidatorSet, + storage: &S, +) -> storage_api::Result +where + S: StorageRead, +{ + Ok(handle + .iter(storage)? + .next() + .transpose()? + .map(|(subkey, _address)| match subkey { + NestedSubKey::Data { + key, + nested_sub_key: _, + } => key, + }) + .unwrap_or_default()) +} + +/// Returns `Ok(None)` when the below capacity set is empty. +pub fn get_max_below_capacity_validator_amount( + handle: &BelowCapacityValidatorSet, + storage: &S, +) -> storage_api::Result> +where + S: StorageRead, +{ + Ok(handle + .iter(storage)? + .next() + .transpose()? + .map(|(subkey, _address)| match subkey { + NestedSubKey::Data { + key, + nested_sub_key: _, + } => token::Amount::from(key), + })) +} + +pub fn insert_validator_into_set( + handle: &ValidatorPositionAddresses, + storage: &mut S, + epoch: &Epoch, + address: &Address, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let next_position = find_next_position(handle, storage)?; + tracing::debug!( + "Inserting validator {} into position {:?} at epoch {}", + address.clone(), + next_position.clone(), + epoch.clone() + ); + handle.insert(storage, next_position, address.clone())?; + validator_set_positions_handle().at(epoch).insert( + storage, + address.clone(), + next_position, + )?; + Ok(()) +} + +/// Communicate imminent validator set updates to Tendermint. This function is +/// called two blocks before the start of a new epoch because Tendermint +/// validator updates become active two blocks after the updates are submitted. +pub fn validator_set_update_tendermint( + storage: &S, + params: &PosParams, + current_epoch: Epoch, + f: impl FnMut(ValidatorSetUpdate) -> T, +) -> storage_api::Result> +where + S: StorageRead, +{ + tracing::debug!("Communicating validator set updates to Tendermint."); + // Because this is called 2 blocks before a start on an epoch, we're gonna + // give Tendermint updates for the next epoch + let next_epoch = current_epoch.next(); + + let new_consensus_validator_handle = + consensus_validator_set_handle().at(&next_epoch); + let prev_consensus_validator_handle = + consensus_validator_set_handle().at(¤t_epoch); + + let new_consensus_validators = new_consensus_validator_handle + .iter(storage)? + .map(|validator| { + let ( + NestedSubKey::Data { + key: new_stake, + nested_sub_key: _, + }, + address, + ) = validator.unwrap(); + + tracing::debug!( + "Consensus validator address {address}, stake {}", + new_stake.to_string_native() + ); + + let new_consensus_key = validator_consensus_key_handle(&address) + .get(storage, next_epoch, params) + .unwrap() + .unwrap(); + + let old_consensus_key = validator_consensus_key_handle(&address) + .get(storage, current_epoch, params) + .unwrap(); + + // Check if the validator was consensus in the previous epoch with + // the same stake. If so, no updated is needed. + // Look up previous state and prev and current voting powers + if !prev_consensus_validator_handle.is_empty(storage).unwrap() { + let prev_state = validator_state_handle(&address) + .get(storage, current_epoch, params) + .unwrap(); + let prev_tm_voting_power = Lazy::new(|| { + let prev_validator_stake = read_validator_stake( + storage, + params, + &address, + current_epoch, + ) + .unwrap(); + into_tm_voting_power( + params.tm_votes_per_token, + prev_validator_stake, + ) + }); + let new_tm_voting_power = Lazy::new(|| { + into_tm_voting_power(params.tm_votes_per_token, new_stake) + }); + + // If it was in `Consensus` before and voting power has not + // changed, skip the update + if matches!(prev_state, Some(ValidatorState::Consensus)) + && *prev_tm_voting_power == *new_tm_voting_power + { + if old_consensus_key.as_ref().unwrap() == &new_consensus_key + { + tracing::debug!( + "skipping validator update, {address} is in \ + consensus set but voting power hasn't changed" + ); + return vec![]; + } else { + return vec![ + ValidatorSetUpdate::Consensus(ConsensusValidator { + consensus_key: new_consensus_key, + bonded_stake: new_stake, + }), + ValidatorSetUpdate::Deactivated( + old_consensus_key.unwrap(), + ), + ]; + } + } + // If both previous and current voting powers are 0, and the + // validator_stake_threshold is 0, skip update + if params.validator_stake_threshold.is_zero() + && *prev_tm_voting_power == 0 + && *new_tm_voting_power == 0 + { + tracing::info!( + "skipping validator update, {address} is in consensus \ + set but without voting power" + ); + return vec![]; + } + } + + tracing::debug!( + "{address} consensus key {}", + new_consensus_key.tm_raw_hash() + ); + + if old_consensus_key.as_ref() == Some(&new_consensus_key) + || old_consensus_key.is_none() + { + vec![ValidatorSetUpdate::Consensus(ConsensusValidator { + consensus_key: new_consensus_key, + bonded_stake: new_stake, + })] + } else { + vec![ + ValidatorSetUpdate::Consensus(ConsensusValidator { + consensus_key: new_consensus_key, + bonded_stake: new_stake, + }), + ValidatorSetUpdate::Deactivated(old_consensus_key.unwrap()), + ] + } + }); + + let prev_consensus_validators = prev_consensus_validator_handle + .iter(storage)? + .map(|validator| { + let ( + NestedSubKey::Data { + key: _prev_stake, + nested_sub_key: _, + }, + address, + ) = validator.unwrap(); + + let new_state = validator_state_handle(&address) + .get(storage, next_epoch, params) + .unwrap(); + + let prev_tm_voting_power = Lazy::new(|| { + let prev_validator_stake = read_validator_stake( + storage, + params, + &address, + current_epoch, + ) + .unwrap(); + into_tm_voting_power( + params.tm_votes_per_token, + prev_validator_stake, + ) + }); + + let old_consensus_key = validator_consensus_key_handle(&address) + .get(storage, current_epoch, params) + .unwrap() + .unwrap(); + + // If the validator is still in the Consensus set, we accounted for + // it in the `new_consensus_validators` iterator above + if matches!(new_state, Some(ValidatorState::Consensus)) { + return vec![]; + } else if params.validator_stake_threshold.is_zero() + && *prev_tm_voting_power == 0 + { + // If the new state is not Consensus but its prev voting power + // was 0 and the stake threshold is 0, we can also skip the + // update + tracing::info!( + "skipping validator update, {address} is in consensus set \ + but without voting power" + ); + return vec![]; + } + + // The remaining validators were previously Consensus but no longer + // are, so they must be deactivated + let consensus_key = validator_consensus_key_handle(&address) + .get(storage, next_epoch, params) + .unwrap() + .unwrap(); + tracing::debug!( + "{address} consensus key {}", + consensus_key.tm_raw_hash() + ); + vec![ValidatorSetUpdate::Deactivated(old_consensus_key)] + }); + + Ok(new_consensus_validators + .chain(prev_consensus_validators) + .flatten() + .map(f) + .collect()) +} + +/// Copy the consensus and below-capacity validator sets and positions into a +/// future epoch. Also copies the epoched set of all known validators in the +/// network. +pub fn copy_validator_sets_and_positions( + storage: &mut S, + params: &PosParams, + current_epoch: Epoch, + target_epoch: Epoch, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let prev_epoch = target_epoch.prev(); + + let consensus_validator_set = consensus_validator_set_handle(); + let below_capacity_validator_set = below_capacity_validator_set_handle(); + + let (consensus, below_capacity) = ( + consensus_validator_set.at(&prev_epoch), + below_capacity_validator_set.at(&prev_epoch), + ); + debug_assert!(!consensus.is_empty(storage)?); + + // Need to copy into memory here to avoid borrowing a ref + // simultaneously as immutable and mutable + let mut consensus_in_mem: HashMap<(token::Amount, Position), Address> = + HashMap::new(); + let mut below_cap_in_mem: HashMap< + (ReverseOrdTokenAmount, Position), + Address, + > = HashMap::new(); + + for val in consensus.iter(storage)? { + let ( + NestedSubKey::Data { + key: stake, + nested_sub_key: SubKey::Data(position), + }, + address, + ) = val?; + consensus_in_mem.insert((stake, position), address); + } + for val in below_capacity.iter(storage)? { + let ( + NestedSubKey::Data { + key: stake, + nested_sub_key: SubKey::Data(position), + }, + address, + ) = val?; + below_cap_in_mem.insert((stake, position), address); + } + + for ((val_stake, val_position), val_address) in consensus_in_mem.into_iter() + { + consensus_validator_set + .at(&target_epoch) + .at(&val_stake) + .insert(storage, val_position, val_address)?; + } + + for ((val_stake, val_position), val_address) in below_cap_in_mem.into_iter() + { + below_capacity_validator_set + .at(&target_epoch) + .at(&val_stake) + .insert(storage, val_position, val_address)?; + } + // Purge consensus and below-capacity validator sets + consensus_validator_set.update_data(storage, params, current_epoch)?; + below_capacity_validator_set.update_data(storage, params, current_epoch)?; + + // Copy validator positions + let mut positions = HashMap::::default(); + let validator_set_positions_handle = validator_set_positions_handle(); + let positions_handle = validator_set_positions_handle.at(&prev_epoch); + + for result in positions_handle.iter(storage)? { + let (validator, position) = result?; + positions.insert(validator, position); + } + + let new_positions_handle = validator_set_positions_handle.at(&target_epoch); + for (validator, position) in positions { + let prev = new_positions_handle.insert(storage, validator, position)?; + debug_assert!(prev.is_none()); + } + validator_set_positions_handle.set_last_update(storage, current_epoch)?; + + // Purge old epochs of validator positions + validator_set_positions_handle.update_data( + storage, + params, + current_epoch, + )?; + + // Copy set of all validator addresses + let mut all_validators = HashSet::
::default(); + let validator_addresses_handle = validator_addresses_handle(); + let all_validators_handle = validator_addresses_handle.at(&prev_epoch); + for result in all_validators_handle.iter(storage)? { + let validator = result?; + all_validators.insert(validator); + } + let new_all_validators_handle = + validator_addresses_handle.at(&target_epoch); + for validator in all_validators { + let was_in = new_all_validators_handle.insert(storage, validator)?; + debug_assert!(!was_in); + } + + // Purge old epochs of all validator addresses + validator_addresses_handle.update_data(storage, params, current_epoch)?; + + Ok(()) +} + +/// Read the position of the validator in the subset of validators that have the +/// same bonded stake. This information is held in its own epoched structure in +/// addition to being inside the validator sets. +fn read_validator_set_position( + storage: &S, + validator: &Address, + epoch: Epoch, + _params: &PosParams, +) -> storage_api::Result> +where + S: StorageRead, +{ + let handle = validator_set_positions_handle(); + handle.get_data_handler().at(&epoch).get(storage, validator) +} diff --git a/sdk/src/queries/vp/pos.rs b/sdk/src/queries/vp/pos.rs index f5ceb06e11..113745a313 100644 --- a/sdk/src/queries/vp/pos.rs +++ b/sdk/src/queries/vp/pos.rs @@ -12,14 +12,14 @@ use namada_core::types::key::common; use namada_core::types::storage::Epoch; use namada_core::types::token; use namada_proof_of_stake::parameters::PosParams; -use namada_proof_of_stake::types::{ - BondId, BondsAndUnbondsDetail, BondsAndUnbondsDetails, CommissionPair, - Slash, ValidatorMetaData, ValidatorState, WeightedValidator, +use namada_proof_of_stake::queries::{ + find_delegation_validators, find_delegations, }; -use namada_proof_of_stake::{ - self, bond_amount, bond_handle, find_all_enqueued_slashes, - find_all_slashes, find_delegation_validators, find_delegations, - query_reward_tokens, read_all_validator_addresses, +use namada_proof_of_stake::slashing::{ + find_all_enqueued_slashes, find_all_slashes, +}; +use namada_proof_of_stake::storage::{ + bond_handle, read_all_validator_addresses, read_below_capacity_validator_set_addresses_with_stake, read_consensus_validator_set_addresses_with_stake, read_pos_params, read_total_stake, read_validator_description, @@ -29,6 +29,11 @@ use namada_proof_of_stake::{ validator_commission_rate_handle, validator_incoming_redelegations_handle, validator_slashes_handle, validator_state_handle, }; +use namada_proof_of_stake::types::{ + BondId, BondsAndUnbondsDetail, BondsAndUnbondsDetails, CommissionPair, + Slash, ValidatorMetaData, ValidatorState, WeightedValidator, +}; +use namada_proof_of_stake::{self, bond_amount, query_reward_tokens}; use crate::queries::types::RequestCtx; @@ -546,7 +551,11 @@ where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - namada_proof_of_stake::bonds_and_unbonds(ctx.wl_storage, source, validator) + namada_proof_of_stake::queries::bonds_and_unbonds( + ctx.wl_storage, + source, + validator, + ) } /// Find all the validator addresses to whom the given `owner` address has @@ -622,7 +631,10 @@ where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - namada_proof_of_stake::find_validator_by_raw_hash(ctx.wl_storage, tm_addr) + namada_proof_of_stake::storage::find_validator_by_raw_hash( + ctx.wl_storage, + tm_addr, + ) } /// Native validator address by looking up the Tendermint address @@ -633,7 +645,7 @@ where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - namada_proof_of_stake::get_consensus_key_set(ctx.wl_storage) + namada_proof_of_stake::storage::get_consensus_key_set(ctx.wl_storage) } /// Find if the given source address has any bonds. @@ -645,7 +657,7 @@ where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - namada_proof_of_stake::has_bonds(ctx.wl_storage, &source) + namada_proof_of_stake::queries::has_bonds(ctx.wl_storage, &source) } /// Client-only methods for the router type are composed from router functions. diff --git a/shared/src/ledger/native_vp/ibc/mod.rs b/shared/src/ledger/native_vp/ibc/mod.rs index a4ca254537..e4778ff82f 100644 --- a/shared/src/ledger/native_vp/ibc/mod.rs +++ b/shared/src/ledger/native_vp/ibc/mod.rs @@ -19,7 +19,7 @@ use namada_core::ledger::storage::{self as ledger_storage, StorageHasher}; use namada_core::proto::Tx; use namada_core::types::address::Address; use namada_core::types::storage::Key; -use namada_proof_of_stake::read_pos_params; +use namada_proof_of_stake::storage::read_pos_params; use thiserror::Error; use crate::ibc::core::host::types::identifiers::ChainId as IbcChainId; diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index 506ef489ca..298746340c 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -7,11 +7,11 @@ use namada_core::ledger::storage_api::governance; pub use namada_proof_of_stake; pub use namada_proof_of_stake::parameters::PosParams; // use namada_proof_of_stake::validation::validate; -use namada_proof_of_stake::read_pos_params; +use namada_proof_of_stake::storage::read_pos_params; +use namada_proof_of_stake::storage_key::is_params_key; pub use namada_proof_of_stake::types; use thiserror::Error; -use super::is_params_key; use crate::ledger::native_vp::{self, Ctx, NativeVp}; // use crate::ledger::pos::{ // is_validator_address_raw_hash_key, diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index 82d52aa746..1bbe50f362 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -574,11 +574,11 @@ pub mod testing { use namada::proof_of_stake::epoched::DynEpochOffset; use namada::proof_of_stake::parameters::testing::arb_rate; use namada::proof_of_stake::parameters::PosParams; - use namada::proof_of_stake::types::{BondId, ValidatorState}; - use namada::proof_of_stake::{ + use namada::proof_of_stake::storage::{ get_num_consensus_validators, read_pos_params, unbond_handle, - ADDRESS as POS_ADDRESS, }; + use namada::proof_of_stake::types::{BondId, ValidatorState}; + use namada::proof_of_stake::ADDRESS as POS_ADDRESS; use namada::types::key::common::PublicKey; use namada::types::key::RefTo; use namada::types::storage::Epoch; diff --git a/tx_prelude/src/proof_of_stake.rs b/tx_prelude/src/proof_of_stake.rs index 15250e760a..3b7883361d 100644 --- a/tx_prelude/src/proof_of_stake.rs +++ b/tx_prelude/src/proof_of_stake.rs @@ -5,15 +5,15 @@ use namada_core::types::key::common; use namada_core::types::transaction::pos::BecomeValidator; use namada_core::types::{key, token}; pub use namada_proof_of_stake::parameters::PosParams; -use namada_proof_of_stake::types::ValidatorMetaData; +use namada_proof_of_stake::storage::read_pos_params; +use namada_proof_of_stake::types::{ResultSlashing, ValidatorMetaData}; use namada_proof_of_stake::{ become_validator, bond_tokens, change_consensus_key, change_validator_commission_rate, change_validator_metadata, claim_reward_tokens, deactivate_validator, reactivate_validator, - read_pos_params, redelegate_tokens, unbond_tokens, unjail_validator, - withdraw_tokens, + redelegate_tokens, unbond_tokens, unjail_validator, withdraw_tokens, }; -pub use namada_proof_of_stake::{parameters, types, ResultSlashing}; +pub use namada_proof_of_stake::{parameters, types}; use super::*; From aaa2674c1552e0133f189eab22bcbe0cbc52df14 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 7 Dec 2023 19:36:13 -0500 Subject: [PATCH 127/216] refactor pos/tests --- proof_of_stake/src/tests.rs | 6893 +---------------- proof_of_stake/src/tests/helpers.rs | 173 + proof_of_stake/src/tests/state_machine.rs | 29 +- proof_of_stake/src/tests/state_machine_v2.rs | 30 +- proof_of_stake/src/tests/test_helper_fns.rs | 2034 +++++ proof_of_stake/src/tests/test_pos.rs | 1653 ++++ .../src/tests/test_slash_and_redel.rs | 1495 ++++ proof_of_stake/src/tests/test_validator.rs | 1191 +++ 8 files changed, 6582 insertions(+), 6916 deletions(-) create mode 100644 proof_of_stake/src/tests/helpers.rs create mode 100644 proof_of_stake/src/tests/test_helper_fns.rs create mode 100644 proof_of_stake/src/tests/test_pos.rs create mode 100644 proof_of_stake/src/tests/test_slash_and_redel.rs create mode 100644 proof_of_stake/src/tests/test_validator.rs diff --git a/proof_of_stake/src/tests.rs b/proof_of_stake/src/tests.rs index f79eb582de..86cb3d6ca1 100644 --- a/proof_of_stake/src/tests.rs +++ b/proof_of_stake/src/tests.rs @@ -1,6891 +1,8 @@ -//! PoS system tests - +mod helpers; mod state_machine; mod state_machine_v2; +mod test_helper_fns; +mod test_pos; +mod test_slash_and_redel; +mod test_validator; mod utils; - -use std::cmp::{max, min}; -use std::collections::{BTreeMap, BTreeSet, HashSet}; -use std::ops::{Deref, Range}; -use std::str::FromStr; - -use assert_matches::assert_matches; -use namada_core::ledger::storage::testing::TestWlStorage; -use namada_core::ledger::storage_api::collections::lazy_map::{ - self, Collectable, NestedMap, -}; -use namada_core::ledger::storage_api::collections::LazyCollection; -use namada_core::ledger::storage_api::token::{credit_tokens, read_balance}; -use namada_core::ledger::storage_api::StorageRead; -use namada_core::types::address::testing::{ - address_from_simple_seed, arb_established_address, established_address_1, - established_address_2, established_address_3, -}; -use namada_core::types::address::{Address, EstablishedAddressGen}; -use namada_core::types::dec::Dec; -use namada_core::types::key::common::{PublicKey, SecretKey}; -use namada_core::types::key::testing::{ - arb_common_keypair, common_sk_from_simple_seed, gen_keypair, -}; -use namada_core::types::key::RefTo; -use namada_core::types::storage::{BlockHeight, Epoch, Key}; -use namada_core::types::token::testing::arb_amount_non_zero_ceiled; -use namada_core::types::token::NATIVE_MAX_DECIMAL_PLACES; -use namada_core::types::{address, key, token}; -use proptest::prelude::*; -use proptest::test_runner::Config; -// Use `RUST_LOG=info` (or another tracing level) and `--nocapture` to see -// `tracing` logs from tests -use test_log::test; - -use crate::epoched::DEFAULT_NUM_PAST_EPOCHS; -use crate::parameters::testing::arb_pos_params; -use crate::parameters::{OwnedPosParams, PosParams}; -use crate::queries::bonds_and_unbonds; -use crate::rewards::{ - log_block_rewards, update_rewards_products_and_mint_inflation, - PosRewardsCalculator, -}; -use crate::slashing::{ - compute_bond_at_epoch, compute_slash_bond_at_epoch, - compute_slashable_amount, process_slashes, slash, slash_redelegation, - slash_validator, slash_validator_redelegation, -}; -use crate::storage::{ - find_validator_by_raw_hash, get_consensus_key_set, - get_num_consensus_validators, - read_below_capacity_validator_set_addresses_with_stake, - read_below_threshold_validator_set_addresses, - read_consensus_validator_set_addresses_with_stake, read_total_stake, - read_validator_deltas_value, rewards_accumulator_handle, - total_deltas_handle, -}; -use crate::test_utils::test_init_genesis; -use crate::types::{ - into_tm_voting_power, BondDetails, BondId, BondsAndUnbondsDetails, - ConsensusValidator, EagerRedelegatedBondsMap, GenesisValidator, Position, - RedelegatedTokens, ReverseOrdTokenAmount, Slash, SlashType, UnbondDetails, - ValidatorSetUpdate, ValidatorState, VoteInfo, WeightedValidator, -}; -use crate::validator_set_update::validator_set_update_tendermint; -use crate::{ - apply_list_slashes, become_validator, below_capacity_validator_set_handle, - bond_handle, bond_tokens, change_consensus_key, - compute_amount_after_slashing_unbond, - compute_amount_after_slashing_withdraw, - compute_and_store_total_consensus_stake, compute_modified_redelegation, - compute_new_redelegated_unbonds, consensus_validator_set_handle, - copy_validator_sets_and_positions, delegator_redelegated_bonds_handle, - delegator_redelegated_unbonds_handle, find_bonds_to_remove, - fold_and_slash_redelegated_bonds, insert_validator_into_validator_set, - is_validator, read_validator_stake, staking_token_address, - total_bonded_handle, total_unbonded_handle, unbond_handle, unbond_tokens, - unjail_validator, update_validator_deltas, update_validator_set, - validator_consensus_key_handle, validator_incoming_redelegations_handle, - validator_outgoing_redelegations_handle, validator_set_positions_handle, - validator_slashes_handle, validator_state_handle, - validator_total_redelegated_bonded_handle, - validator_total_redelegated_unbonded_handle, withdraw_tokens, - write_pos_params, write_validator_address_raw_hash, BecomeValidator, - EagerRedelegatedUnbonds, FoldRedelegatedBondsResult, ModifiedRedelegation, - RedelegationError, -}; - -proptest! { - // Generate arb valid input for `test_test_init_genesis_aux` - #![proptest_config(Config { - cases: 100, - .. Config::default() - })] - #[test] - fn test_test_init_genesis( - - (pos_params, genesis_validators) in arb_params_and_genesis_validators(Some(5), 1..10), - start_epoch in (0_u64..1000).prop_map(Epoch), - - ) { - test_test_init_genesis_aux(pos_params, start_epoch, genesis_validators) - } -} - -proptest! { - // Generate arb valid input for `test_bonds_aux` - #![proptest_config(Config { - cases: 100, - .. Config::default() - })] - #[test] - fn test_bonds( - - (pos_params, genesis_validators) in arb_params_and_genesis_validators(Some(5), 1..3), - - ) { - test_bonds_aux(pos_params, genesis_validators) - } -} - -proptest! { - // Generate arb valid input for `test_become_validator_aux` - #![proptest_config(Config { - cases: 100, - .. Config::default() - })] - #[test] - fn test_become_validator( - - (pos_params, genesis_validators) in arb_params_and_genesis_validators(Some(5), 1..3), - new_validator in arb_established_address().prop_map(Address::Established), - new_validator_consensus_key in arb_common_keypair(), - - ) { - test_become_validator_aux(pos_params, new_validator, - new_validator_consensus_key, genesis_validators) - } -} - -proptest! { - // Generate arb valid input for `test_slashes_with_unbonding_aux` - #![proptest_config(Config { - cases: 100, - .. Config::default() - })] - #[test] - fn test_slashes_with_unbonding( - (params, genesis_validators, unbond_delay) - in test_slashes_with_unbonding_params() - ) { - test_slashes_with_unbonding_aux( - params, genesis_validators, unbond_delay) - } -} - -proptest! { - // Generate arb valid input for `test_unjail_validator_aux` - #![proptest_config(Config { - cases: 100, - .. Config::default() - })] - #[test] - fn test_unjail_validator( - (pos_params, genesis_validators) - in arb_params_and_genesis_validators(Some(4),6..9) - ) { - test_unjail_validator_aux(pos_params, - genesis_validators) - } -} - -proptest! { - // Generate arb valid input for `test_simple_redelegation_aux` - #![proptest_config(Config { - cases: 100, - .. Config::default() - })] - #[test] - fn test_simple_redelegation( - - genesis_validators in arb_genesis_validators(2..4, None), - (amount_delegate, amount_redelegate, amount_unbond) in arb_redelegation_amounts(20) - - ) { - test_simple_redelegation_aux(genesis_validators, amount_delegate, amount_redelegate, amount_unbond) - } -} - -proptest! { - // Generate arb valid input for `test_simple_redelegation_aux` - #![proptest_config(Config { - cases: 100, - .. Config::default() - })] - #[test] - fn test_redelegation_with_slashing( - - genesis_validators in arb_genesis_validators(2..4, None), - (amount_delegate, amount_redelegate, amount_unbond) in arb_redelegation_amounts(20) - - ) { - test_redelegation_with_slashing_aux(genesis_validators, amount_delegate, amount_redelegate, amount_unbond) - } -} - -proptest! { - // Generate arb valid input for `test_chain_redelegations_aux` - #![proptest_config(Config { - cases: 100, - .. Config::default() - })] - #[test] - fn test_chain_redelegations( - - genesis_validators in arb_genesis_validators(3..4, None), - - ) { - test_chain_redelegations_aux(genesis_validators) - } -} - -proptest! { - // Generate arb valid input for `test_overslashing_aux` - #![proptest_config(Config { - cases: 1, - .. Config::default() - })] - #[test] - fn test_overslashing( - - genesis_validators in arb_genesis_validators(4..5, None), - - ) { - test_overslashing_aux(genesis_validators) - } -} - -proptest! { - // Generate arb valid input for `test_unslashed_bond_amount_aux` - #![proptest_config(Config { - cases: 1, - .. Config::default() - })] - #[test] - fn test_unslashed_bond_amount( - - genesis_validators in arb_genesis_validators(4..5, None), - - ) { - test_unslashed_bond_amount_aux(genesis_validators) - } -} - -proptest! { - // Generate arb valid input for `test_slashed_bond_amount_aux` - #![proptest_config(Config { - cases: 1, - .. Config::default() - })] - #[test] - fn test_slashed_bond_amount( - - genesis_validators in arb_genesis_validators(4..5, None), - - ) { - test_slashed_bond_amount_aux(genesis_validators) - } -} - -proptest! { - // Generate arb valid input for `test_log_block_rewards_aux` - #![proptest_config(Config { - cases: 1, - .. Config::default() - })] - #[test] - fn test_log_block_rewards( - genesis_validators in arb_genesis_validators(4..10, None), - params in arb_pos_params(Some(5)) - - ) { - test_log_block_rewards_aux(genesis_validators, params) - } -} - -proptest! { - // Generate arb valid input for `test_update_rewards_products_aux` - #![proptest_config(Config { - cases: 1, - .. Config::default() - })] - #[test] - fn test_update_rewards_products( - genesis_validators in arb_genesis_validators(4..10, None), - - ) { - test_update_rewards_products_aux(genesis_validators) - } -} - -proptest! { - // Generate arb valid input for `test_consensus_key_change` - #![proptest_config(Config { - cases: 1, - .. Config::default() - })] - #[test] - fn test_consensus_key_change( - - genesis_validators in arb_genesis_validators(1..2, None), - - ) { - test_consensus_key_change_aux(genesis_validators) - } -} - -proptest! { - // Generate arb valid input for `test_is_delegator` - #![proptest_config(Config { - cases: 100, - .. Config::default() - })] - #[test] - fn test_is_delegator( - - genesis_validators in arb_genesis_validators(2..3, None), - - ) { - test_is_delegator_aux(genesis_validators) - } -} - -fn arb_params_and_genesis_validators( - num_max_validator_slots: Option, - val_size: Range, -) -> impl Strategy)> { - let params = arb_pos_params(num_max_validator_slots); - params.prop_flat_map(move |params| { - let validators = arb_genesis_validators( - val_size.clone(), - Some(params.validator_stake_threshold), - ); - (Just(params), validators) - }) -} - -fn test_slashes_with_unbonding_params() --> impl Strategy, u64)> { - let params = arb_pos_params(Some(5)); - params.prop_flat_map(|params| { - let unbond_delay = 0..(params.slash_processing_epoch_offset() * 2); - // Must have at least 4 validators so we can slash one and the cubic - // slash rate will be less than 100% - let validators = arb_genesis_validators(4..10, None); - (Just(params), validators, unbond_delay) - }) -} - -/// Test genesis initialization -fn test_test_init_genesis_aux( - params: OwnedPosParams, - start_epoch: Epoch, - mut validators: Vec, -) { - println!( - "Test inputs: {params:?}, {start_epoch}, genesis validators: \ - {validators:#?}" - ); - let mut s = TestWlStorage::default(); - s.storage.block.epoch = start_epoch; - - validators.sort_by(|a, b| b.tokens.cmp(&a.tokens)); - let params = test_init_genesis( - &mut s, - params, - validators.clone().into_iter(), - start_epoch, - ) - .unwrap(); - - let mut bond_details = bonds_and_unbonds(&s, None, None).unwrap(); - assert!(bond_details.iter().all(|(_id, details)| { - details.unbonds.is_empty() && details.slashes.is_empty() - })); - - for (i, validator) in validators.into_iter().enumerate() { - let addr = &validator.address; - let self_bonds = bond_details - .remove(&BondId { - source: addr.clone(), - validator: addr.clone(), - }) - .unwrap(); - assert_eq!(self_bonds.bonds.len(), 1); - assert_eq!( - self_bonds.bonds[0], - BondDetails { - start: start_epoch, - amount: validator.tokens, - slashed_amount: None, - } - ); - - let state = validator_state_handle(&validator.address) - .get(&s, start_epoch, ¶ms) - .unwrap(); - if (i as u64) < params.max_validator_slots - && validator.tokens >= params.validator_stake_threshold - { - // should be in consensus set - let handle = consensus_validator_set_handle().at(&start_epoch); - assert!(handle.at(&validator.tokens).iter(&s).unwrap().any( - |result| { - let (_pos, addr) = result.unwrap(); - addr == validator.address - } - )); - assert_eq!(state, Some(ValidatorState::Consensus)); - } else if validator.tokens >= params.validator_stake_threshold { - // Should be in below-capacity set if its tokens are greater than - // `validator_stake_threshold` - let handle = below_capacity_validator_set_handle().at(&start_epoch); - assert!(handle.at(&validator.tokens.into()).iter(&s).unwrap().any( - |result| { - let (_pos, addr) = result.unwrap(); - addr == validator.address - } - )); - assert_eq!(state, Some(ValidatorState::BelowCapacity)); - } else { - // Should be in below-threshold - let bt_addresses = - read_below_threshold_validator_set_addresses(&s, start_epoch) - .unwrap(); - assert!( - bt_addresses - .into_iter() - .any(|addr| { addr == validator.address }) - ); - assert_eq!(state, Some(ValidatorState::BelowThreshold)); - } - } -} - -/// Test bonding -/// NOTE: copy validator sets each time we advance the epoch -fn test_bonds_aux(params: OwnedPosParams, validators: Vec) { - // This can be useful for debugging: - // params.pipeline_len = 2; - // params.unbonding_len = 4; - println!("\nTest inputs: {params:?}, genesis validators: {validators:#?}"); - let mut s = TestWlStorage::default(); - - // Genesis - let start_epoch = s.storage.block.epoch; - let mut current_epoch = s.storage.block.epoch; - let params = test_init_genesis( - &mut s, - params, - validators.clone().into_iter(), - current_epoch, - ) - .unwrap(); - s.commit_block().unwrap(); - - // Advance to epoch 1 - current_epoch = advance_epoch(&mut s, ¶ms); - let self_bond_epoch = current_epoch; - - let validator = validators.first().unwrap(); - - // Read some data before submitting bond - let pipeline_epoch = current_epoch + params.pipeline_len; - let staking_token = staking_token_address(&s); - let pos_balance_pre = s - .read::(&token::balance_key( - &staking_token, - &super::ADDRESS, - )) - .unwrap() - .unwrap_or_default(); - let total_stake_before = - read_total_stake(&s, ¶ms, pipeline_epoch).unwrap(); - - // Self-bond - let amount_self_bond = token::Amount::from_uint(100_500_000, 0).unwrap(); - credit_tokens(&mut s, &staking_token, &validator.address, amount_self_bond) - .unwrap(); - bond_tokens( - &mut s, - None, - &validator.address, - amount_self_bond, - current_epoch, - None, - ) - .unwrap(); - - // Check the bond delta - let self_bond = bond_handle(&validator.address, &validator.address); - let delta = self_bond.get_delta_val(&s, pipeline_epoch).unwrap(); - assert_eq!(delta, Some(amount_self_bond)); - - // Check the validator in the validator set - let set = - read_consensus_validator_set_addresses_with_stake(&s, pipeline_epoch) - .unwrap(); - assert!(set.into_iter().any( - |WeightedValidator { - bonded_stake, - address, - }| { - address == validator.address - && bonded_stake == validator.tokens + amount_self_bond - } - )); - - let val_deltas = - read_validator_deltas_value(&s, &validator.address, &pipeline_epoch) - .unwrap(); - assert_eq!(val_deltas, Some(amount_self_bond.change())); - - let total_deltas_handle = total_deltas_handle(); - assert_eq!( - current_epoch, - total_deltas_handle.get_last_update(&s).unwrap().unwrap() - ); - let total_stake_after = - read_total_stake(&s, ¶ms, pipeline_epoch).unwrap(); - assert_eq!(total_stake_before + amount_self_bond, total_stake_after); - - // Check bond details after self-bond - let self_bond_id = BondId { - source: validator.address.clone(), - validator: validator.address.clone(), - }; - let check_bond_details = |ix, bond_details: BondsAndUnbondsDetails| { - println!("Check index {ix}"); - let details = bond_details.get(&self_bond_id).unwrap(); - assert_eq!( - details.bonds.len(), - 2, - "Contains genesis and newly added self-bond" - ); - dbg!(&details.bonds); - assert_eq!( - details.bonds[0], - BondDetails { - start: start_epoch, - amount: validator.tokens, - slashed_amount: None - }, - ); - assert_eq!( - details.bonds[1], - BondDetails { - start: pipeline_epoch, - amount: amount_self_bond, - slashed_amount: None - }, - ); - }; - // Try to call it with different combinations of owner/validator args - check_bond_details(0, bonds_and_unbonds(&s, None, None).unwrap()); - check_bond_details( - 1, - bonds_and_unbonds(&s, Some(validator.address.clone()), None).unwrap(), - ); - check_bond_details( - 2, - bonds_and_unbonds(&s, None, Some(validator.address.clone())).unwrap(), - ); - check_bond_details( - 3, - bonds_and_unbonds( - &s, - Some(validator.address.clone()), - Some(validator.address.clone()), - ) - .unwrap(), - ); - - // Get a non-validating account with tokens - let delegator = address::testing::gen_implicit_address(); - let amount_del = token::Amount::from_uint(201_000_000, 0).unwrap(); - credit_tokens(&mut s, &staking_token, &delegator, amount_del).unwrap(); - let balance_key = token::balance_key(&staking_token, &delegator); - let balance = s - .read::(&balance_key) - .unwrap() - .unwrap_or_default(); - assert_eq!(balance, amount_del); - - // Advance to epoch 3 - advance_epoch(&mut s, ¶ms); - current_epoch = advance_epoch(&mut s, ¶ms); - let pipeline_epoch = current_epoch + params.pipeline_len; - - // Delegation - let delegation_epoch = current_epoch; - bond_tokens( - &mut s, - Some(&delegator), - &validator.address, - amount_del, - current_epoch, - None, - ) - .unwrap(); - let val_stake_pre = read_validator_stake( - &s, - ¶ms, - &validator.address, - pipeline_epoch.prev(), - ) - .unwrap(); - let val_stake_post = - read_validator_stake(&s, ¶ms, &validator.address, pipeline_epoch) - .unwrap(); - assert_eq!(validator.tokens + amount_self_bond, val_stake_pre); - assert_eq!( - validator.tokens + amount_self_bond + amount_del, - val_stake_post - ); - let delegation = bond_handle(&delegator, &validator.address); - assert_eq!( - delegation - .get_sum(&s, pipeline_epoch.prev(), ¶ms) - .unwrap() - .unwrap_or_default(), - token::Amount::zero() - ); - assert_eq!( - delegation - .get_sum(&s, pipeline_epoch, ¶ms) - .unwrap() - .unwrap_or_default(), - amount_del - ); - - // Check delegation bonds details after delegation - let delegation_bond_id = BondId { - source: delegator.clone(), - validator: validator.address.clone(), - }; - let check_bond_details = |ix, bond_details: BondsAndUnbondsDetails| { - println!("Check index {ix}"); - assert_eq!(bond_details.len(), 1); - let details = bond_details.get(&delegation_bond_id).unwrap(); - assert_eq!(details.bonds.len(), 1,); - dbg!(&details.bonds); - assert_eq!( - details.bonds[0], - BondDetails { - start: pipeline_epoch, - amount: amount_del, - slashed_amount: None - }, - ); - }; - // Try to call it with different combinations of owner/validator args - check_bond_details( - 0, - bonds_and_unbonds(&s, Some(delegator.clone()), None).unwrap(), - ); - check_bond_details( - 1, - bonds_and_unbonds( - &s, - Some(delegator.clone()), - Some(validator.address.clone()), - ) - .unwrap(), - ); - - // Check all bond details (self-bonds and delegation) - let check_bond_details = |ix, bond_details: BondsAndUnbondsDetails| { - println!("Check index {ix}"); - let self_bond_details = bond_details.get(&self_bond_id).unwrap(); - let delegation_details = bond_details.get(&delegation_bond_id).unwrap(); - assert_eq!( - self_bond_details.bonds.len(), - 2, - "Contains genesis and newly added self-bond" - ); - assert_eq!( - self_bond_details.bonds[0], - BondDetails { - start: start_epoch, - amount: validator.tokens, - slashed_amount: None - }, - ); - assert_eq!(self_bond_details.bonds[1].amount, amount_self_bond); - assert_eq!( - delegation_details.bonds[0], - BondDetails { - start: pipeline_epoch, - amount: amount_del, - slashed_amount: None - }, - ); - }; - // Try to call it with different combinations of owner/validator args - check_bond_details(0, bonds_and_unbonds(&s, None, None).unwrap()); - check_bond_details( - 1, - bonds_and_unbonds(&s, None, Some(validator.address.clone())).unwrap(), - ); - - // Advance to epoch 5 - for _ in 0..2 { - current_epoch = advance_epoch(&mut s, ¶ms); - } - let pipeline_epoch = current_epoch + params.pipeline_len; - - // Unbond the self-bond with an amount that will remove all of the self-bond - // executed after genesis and some of the genesis bond - let amount_self_unbond: token::Amount = - amount_self_bond + (validator.tokens / 2); - // When the difference is 0, only the non-genesis self-bond is unbonded - let unbonded_genesis_self_bond = - amount_self_unbond - amount_self_bond != token::Amount::zero(); - dbg!( - amount_self_unbond, - amount_self_bond, - unbonded_genesis_self_bond - ); - let self_unbond_epoch = s.storage.block.epoch; - - unbond_tokens( - &mut s, - None, - &validator.address, - amount_self_unbond, - current_epoch, - false, - ) - .unwrap(); - - let val_stake_pre = read_validator_stake( - &s, - ¶ms, - &validator.address, - pipeline_epoch.prev(), - ) - .unwrap(); - - let val_stake_post = - read_validator_stake(&s, ¶ms, &validator.address, pipeline_epoch) - .unwrap(); - - let val_delta = - read_validator_deltas_value(&s, &validator.address, &pipeline_epoch) - .unwrap(); - let unbond = unbond_handle(&validator.address, &validator.address); - - assert_eq!(val_delta, Some(-amount_self_unbond.change())); - assert_eq!( - unbond - .at(&Epoch::default()) - .get( - &s, - &(pipeline_epoch - + params.unbonding_len - + params.cubic_slashing_window_length) - ) - .unwrap(), - if unbonded_genesis_self_bond { - Some(amount_self_unbond - amount_self_bond) - } else { - None - } - ); - assert_eq!( - unbond - .at(&(self_bond_epoch + params.pipeline_len)) - .get( - &s, - &(pipeline_epoch - + params.unbonding_len - + params.cubic_slashing_window_length) - ) - .unwrap(), - Some(amount_self_bond) - ); - assert_eq!( - val_stake_pre, - validator.tokens + amount_self_bond + amount_del - ); - assert_eq!( - val_stake_post, - validator.tokens + amount_self_bond + amount_del - amount_self_unbond - ); - - // Check all bond and unbond details (self-bonds and delegation) - let check_bond_details = |ix, bond_details: BondsAndUnbondsDetails| { - println!("Check index {ix}"); - dbg!(&bond_details); - assert_eq!(bond_details.len(), 2); - let self_bond_details = bond_details.get(&self_bond_id).unwrap(); - let delegation_details = bond_details.get(&delegation_bond_id).unwrap(); - assert_eq!( - self_bond_details.bonds.len(), - 1, - "Contains only part of the genesis bond now" - ); - assert_eq!( - self_bond_details.bonds[0], - BondDetails { - start: start_epoch, - amount: validator.tokens + amount_self_bond - - amount_self_unbond, - slashed_amount: None - }, - ); - assert_eq!( - delegation_details.bonds[0], - BondDetails { - start: delegation_epoch + params.pipeline_len, - amount: amount_del, - slashed_amount: None - }, - ); - assert_eq!( - self_bond_details.unbonds.len(), - if unbonded_genesis_self_bond { 2 } else { 1 }, - "Contains a full unbond of the last self-bond and an unbond from \ - the genesis bond" - ); - if unbonded_genesis_self_bond { - assert_eq!( - self_bond_details.unbonds[0], - UnbondDetails { - start: start_epoch, - withdraw: self_unbond_epoch - + params.pipeline_len - + params.unbonding_len - + params.cubic_slashing_window_length, - amount: amount_self_unbond - amount_self_bond, - slashed_amount: None - } - ); - } - assert_eq!( - self_bond_details.unbonds[usize::from(unbonded_genesis_self_bond)], - UnbondDetails { - start: self_bond_epoch + params.pipeline_len, - withdraw: self_unbond_epoch - + params.pipeline_len - + params.unbonding_len - + params.cubic_slashing_window_length, - amount: amount_self_bond, - slashed_amount: None - } - ); - }; - check_bond_details( - 0, - bonds_and_unbonds(&s, None, Some(validator.address.clone())).unwrap(), - ); - - // Unbond delegation - let amount_undel = token::Amount::from_uint(1_000_000, 0).unwrap(); - unbond_tokens( - &mut s, - Some(&delegator), - &validator.address, - amount_undel, - current_epoch, - false, - ) - .unwrap(); - - let val_stake_pre = read_validator_stake( - &s, - ¶ms, - &validator.address, - pipeline_epoch.prev(), - ) - .unwrap(); - let val_stake_post = - read_validator_stake(&s, ¶ms, &validator.address, pipeline_epoch) - .unwrap(); - let val_delta = - read_validator_deltas_value(&s, &validator.address, &pipeline_epoch) - .unwrap(); - let unbond = unbond_handle(&delegator, &validator.address); - - assert_eq!( - val_delta, - Some(-(amount_self_unbond + amount_undel).change()) - ); - assert_eq!( - unbond - .at(&(delegation_epoch + params.pipeline_len)) - .get( - &s, - &(pipeline_epoch - + params.unbonding_len - + params.cubic_slashing_window_length) - ) - .unwrap(), - Some(amount_undel) - ); - assert_eq!( - val_stake_pre, - validator.tokens + amount_self_bond + amount_del - ); - assert_eq!( - val_stake_post, - validator.tokens + amount_self_bond - amount_self_unbond + amount_del - - amount_undel - ); - - let withdrawable_offset = params.unbonding_len - + params.pipeline_len - + params.cubic_slashing_window_length; - - // Advance to withdrawable epoch - for _ in 0..withdrawable_offset { - current_epoch = advance_epoch(&mut s, ¶ms); - } - - dbg!(current_epoch); - - let pos_balance = s - .read::(&token::balance_key( - &staking_token, - &super::ADDRESS, - )) - .unwrap(); - - assert_eq!( - Some(pos_balance_pre + amount_self_bond + amount_del), - pos_balance - ); - - // Withdraw the self-unbond - withdraw_tokens(&mut s, None, &validator.address, current_epoch).unwrap(); - let unbond = unbond_handle(&validator.address, &validator.address); - let unbond_iter = unbond.iter(&s).unwrap().next(); - assert!(unbond_iter.is_none()); - - let pos_balance = s - .read::(&token::balance_key( - &staking_token, - &super::ADDRESS, - )) - .unwrap(); - assert_eq!( - Some( - pos_balance_pre + amount_self_bond - amount_self_unbond - + amount_del - ), - pos_balance - ); - - // Withdraw the delegation unbond - withdraw_tokens( - &mut s, - Some(&delegator), - &validator.address, - current_epoch, - ) - .unwrap(); - let unbond = unbond_handle(&delegator, &validator.address); - let unbond_iter = unbond.iter(&s).unwrap().next(); - assert!(unbond_iter.is_none()); - - let pos_balance = s - .read::(&token::balance_key( - &staking_token, - &super::ADDRESS, - )) - .unwrap(); - assert_eq!( - Some( - pos_balance_pre + amount_self_bond - amount_self_unbond - + amount_del - - amount_undel - ), - pos_balance - ); -} - -/// Test validator initialization. -fn test_become_validator_aux( - params: OwnedPosParams, - new_validator: Address, - new_validator_consensus_key: SecretKey, - validators: Vec, -) { - println!( - "Test inputs: {params:?}, new validator: {new_validator}, genesis \ - validators: {validators:#?}" - ); - - let mut s = TestWlStorage::default(); - - // Genesis - let mut current_epoch = s.storage.block.epoch; - let params = test_init_genesis( - &mut s, - params, - validators.clone().into_iter(), - current_epoch, - ) - .unwrap(); - s.commit_block().unwrap(); - - // Advance to epoch 1 - current_epoch = advance_epoch(&mut s, ¶ms); - - let num_consensus_before = - get_num_consensus_validators(&s, current_epoch + params.pipeline_len) - .unwrap(); - let num_validators_over_thresh = validators - .iter() - .filter(|validator| { - validator.tokens >= params.validator_stake_threshold - }) - .count(); - - assert_eq!( - min( - num_validators_over_thresh as u64, - params.max_validator_slots - ), - num_consensus_before - ); - assert!(!is_validator(&s, &new_validator).unwrap()); - - // Credit the `new_validator` account - let staking_token = staking_token_address(&s); - let amount = token::Amount::from_uint(100_500_000, 0).unwrap(); - // Credit twice the amount as we're gonna bond it in delegation first, then - // self-bond - credit_tokens(&mut s, &staking_token, &new_validator, amount * 2).unwrap(); - - // Add a delegation from `new_validator` to `genesis_validator` - let genesis_validator = &validators.first().unwrap().address; - bond_tokens( - &mut s, - Some(&new_validator), - genesis_validator, - amount, - current_epoch, - None, - ) - .unwrap(); - - let consensus_key = new_validator_consensus_key.to_public(); - let protocol_sk = common_sk_from_simple_seed(0); - let protocol_key = protocol_sk.to_public(); - let eth_hot_key = key::common::PublicKey::Secp256k1( - key::testing::gen_keypair::().ref_to(), - ); - let eth_cold_key = key::common::PublicKey::Secp256k1( - key::testing::gen_keypair::().ref_to(), - ); - - // Try to become a validator - it should fail as there is a delegation - let result = become_validator( - &mut s, - BecomeValidator { - params: ¶ms, - address: &new_validator, - consensus_key: &consensus_key, - protocol_key: &protocol_key, - eth_cold_key: ð_cold_key, - eth_hot_key: ð_hot_key, - current_epoch, - commission_rate: Dec::new(5, 2).expect("Dec creation failed"), - max_commission_rate_change: Dec::new(5, 2) - .expect("Dec creation failed"), - metadata: Default::default(), - offset_opt: None, - }, - ); - assert!(result.is_err()); - assert!(!is_validator(&s, &new_validator).unwrap()); - - // Unbond the delegation - unbond_tokens( - &mut s, - Some(&new_validator), - genesis_validator, - amount, - current_epoch, - false, - ) - .unwrap(); - - // Try to become a validator account again - it should pass now - become_validator( - &mut s, - BecomeValidator { - params: ¶ms, - address: &new_validator, - consensus_key: &consensus_key, - protocol_key: &protocol_key, - eth_cold_key: ð_cold_key, - eth_hot_key: ð_hot_key, - current_epoch, - commission_rate: Dec::new(5, 2).expect("Dec creation failed"), - max_commission_rate_change: Dec::new(5, 2) - .expect("Dec creation failed"), - metadata: Default::default(), - offset_opt: None, - }, - ) - .unwrap(); - assert!(is_validator(&s, &new_validator).unwrap()); - - let num_consensus_after = - get_num_consensus_validators(&s, current_epoch + params.pipeline_len) - .unwrap(); - // The new validator is initialized with no stake and thus is in the - // below-threshold set - assert_eq!(num_consensus_before, num_consensus_after); - - // Advance to epoch 2 - current_epoch = advance_epoch(&mut s, ¶ms); - - // Self-bond to the new validator - bond_tokens(&mut s, None, &new_validator, amount, current_epoch, None) - .unwrap(); - - // Check the bond delta - let bond_handle = bond_handle(&new_validator, &new_validator); - let pipeline_epoch = current_epoch + params.pipeline_len; - let delta = bond_handle.get_delta_val(&s, pipeline_epoch).unwrap(); - assert_eq!(delta, Some(amount)); - - // Check the validator in the validator set - - // If the consensus validator slots are full and all the genesis validators - // have stake GTE the new validator's self-bond amount, the validator should - // be added to the below-capacity set, or the consensus otherwise - if params.max_validator_slots <= validators.len() as u64 - && validators - .iter() - .all(|validator| validator.tokens >= amount) - { - let set = read_below_capacity_validator_set_addresses_with_stake( - &s, - pipeline_epoch, - ) - .unwrap(); - assert!(set.into_iter().any( - |WeightedValidator { - bonded_stake, - address, - }| { - address == new_validator && bonded_stake == amount - } - )); - } else { - let set = read_consensus_validator_set_addresses_with_stake( - &s, - pipeline_epoch, - ) - .unwrap(); - assert!(set.into_iter().any( - |WeightedValidator { - bonded_stake, - address, - }| { - address == new_validator && bonded_stake == amount - } - )); - } - - // Advance to epoch 3 - current_epoch = advance_epoch(&mut s, ¶ms); - - // Unbond the self-bond - unbond_tokens(&mut s, None, &new_validator, amount, current_epoch, false) - .unwrap(); - - let withdrawable_offset = params.unbonding_len + params.pipeline_len; - - // Advance to withdrawable epoch - for _ in 0..withdrawable_offset { - current_epoch = advance_epoch(&mut s, ¶ms); - } - - // Withdraw the self-bond - withdraw_tokens(&mut s, None, &new_validator, current_epoch).unwrap(); -} - -fn test_slashes_with_unbonding_aux( - mut params: OwnedPosParams, - validators: Vec, - unbond_delay: u64, -) { - // This can be useful for debugging: - params.pipeline_len = 2; - params.unbonding_len = 4; - println!("\nTest inputs: {params:?}, genesis validators: {validators:#?}"); - let mut s = TestWlStorage::default(); - - // Find the validator with the least stake to avoid the cubic slash rate - // going to 100% - let validator = - itertools::Itertools::sorted_by_key(validators.iter(), |v| v.tokens) - .next() - .unwrap(); - let val_addr = &validator.address; - let val_tokens = validator.tokens; - println!( - "Validator that will misbehave addr {val_addr}, tokens {}", - val_tokens.to_string_native() - ); - - // Genesis - // let start_epoch = s.storage.block.epoch; - let mut current_epoch = s.storage.block.epoch; - let params = test_init_genesis( - &mut s, - params, - validators.clone().into_iter(), - current_epoch, - ) - .unwrap(); - s.commit_block().unwrap(); - - current_epoch = advance_epoch(&mut s, ¶ms); - process_slashes(&mut s, current_epoch).unwrap(); - - // Discover first slash - let slash_0_evidence_epoch = current_epoch; - // let slash_0_processing_epoch = - // slash_0_evidence_epoch + params.slash_processing_epoch_offset(); - let evidence_block_height = BlockHeight(0); // doesn't matter for slashing logic - let slash_0_type = SlashType::DuplicateVote; - slash( - &mut s, - ¶ms, - current_epoch, - slash_0_evidence_epoch, - evidence_block_height, - slash_0_type, - val_addr, - current_epoch.next(), - ) - .unwrap(); - - // Advance to an epoch in which we can unbond - let unfreeze_epoch = - slash_0_evidence_epoch + params.slash_processing_epoch_offset(); - while current_epoch < unfreeze_epoch { - current_epoch = advance_epoch(&mut s, ¶ms); - process_slashes(&mut s, current_epoch).unwrap(); - } - - // Advance more epochs randomly from the generated delay - for _ in 0..unbond_delay { - current_epoch = advance_epoch(&mut s, ¶ms); - } - - // Unbond half of the tokens - let unbond_amount = Dec::new(5, 1).unwrap() * val_tokens; - println!("Going to unbond {}", unbond_amount.to_string_native()); - let unbond_epoch = current_epoch; - unbond_tokens(&mut s, None, val_addr, unbond_amount, unbond_epoch, false) - .unwrap(); - - // Discover second slash - let slash_1_evidence_epoch = current_epoch; - // Ensure that both slashes happen before `unbond_epoch + pipeline` - let _slash_1_processing_epoch = - slash_1_evidence_epoch + params.slash_processing_epoch_offset(); - let evidence_block_height = BlockHeight(0); // doesn't matter for slashing logic - let slash_1_type = SlashType::DuplicateVote; - slash( - &mut s, - ¶ms, - current_epoch, - slash_1_evidence_epoch, - evidence_block_height, - slash_1_type, - val_addr, - current_epoch.next(), - ) - .unwrap(); - - // Advance to an epoch in which we can withdraw - let withdraw_epoch = unbond_epoch + params.withdrawable_epoch_offset(); - while current_epoch < withdraw_epoch { - current_epoch = advance_epoch(&mut s, ¶ms); - process_slashes(&mut s, current_epoch).unwrap(); - } - let token = staking_token_address(&s); - let val_balance_pre = read_balance(&s, &token, val_addr).unwrap(); - - let bond_id = BondId { - source: val_addr.clone(), - validator: val_addr.clone(), - }; - let binding = bonds_and_unbonds(&s, None, Some(val_addr.clone())).unwrap(); - let details = binding.get(&bond_id).unwrap(); - let exp_withdraw_from_details = details.unbonds[0].amount - - details.unbonds[0].slashed_amount.unwrap_or_default(); - - withdraw_tokens(&mut s, None, val_addr, current_epoch).unwrap(); - - let val_balance_post = read_balance(&s, &token, val_addr).unwrap(); - let withdrawn_tokens = val_balance_post - val_balance_pre; - println!("Withdrew {} tokens", withdrawn_tokens.to_string_native()); - - assert_eq!(exp_withdraw_from_details, withdrawn_tokens); - - let slash_rate_0 = validator_slashes_handle(val_addr) - .get(&s, 0) - .unwrap() - .unwrap() - .rate; - let slash_rate_1 = validator_slashes_handle(val_addr) - .get(&s, 1) - .unwrap() - .unwrap() - .rate; - println!("Slash 0 rate {slash_rate_0}, slash 1 rate {slash_rate_1}"); - - let expected_withdrawn_amount = Dec::from( - (Dec::one() - slash_rate_1) - * (Dec::one() - slash_rate_0) - * unbond_amount, - ); - // Allow some rounding error, 1 NAMNAM per each slash - let rounding_error_tolerance = - Dec::new(2, NATIVE_MAX_DECIMAL_PLACES).unwrap(); - assert!( - dbg!(expected_withdrawn_amount.abs_diff(&Dec::from(withdrawn_tokens))) - <= rounding_error_tolerance - ); - - // TODO: finish once implemented - // let slash_0 = decimal_mult_amount(slash_rate_0, val_tokens); - // let slash_1 = decimal_mult_amount(slash_rate_1, val_tokens - slash_0); - // let expected_slash_pool = slash_0 + slash_1; - // let slash_pool_balance = - // read_balance(&s, &token, &SLASH_POOL_ADDRESS).unwrap(); - // assert_eq!(expected_slash_pool, slash_pool_balance); -} - -#[test] -fn test_validator_raw_hash() { - let mut storage = TestWlStorage::default(); - let address = address::testing::established_address_1(); - let consensus_sk = key::testing::keypair_1(); - let consensus_pk = consensus_sk.to_public(); - let expected_raw_hash = key::tm_consensus_key_raw_hash(&consensus_pk); - - assert!( - find_validator_by_raw_hash(&storage, &expected_raw_hash) - .unwrap() - .is_none() - ); - write_validator_address_raw_hash(&mut storage, &address, &consensus_pk) - .unwrap(); - let found = - find_validator_by_raw_hash(&storage, &expected_raw_hash).unwrap(); - assert_eq!(found, Some(address)); -} - -#[test] -fn test_validator_sets() { - let mut s = TestWlStorage::default(); - // Only 3 consensus validator slots - let params = OwnedPosParams { - max_validator_slots: 3, - ..Default::default() - }; - let addr_seed = "seed"; - let mut address_gen = EstablishedAddressGen::new(addr_seed); - let mut sk_seed = 0; - let mut gen_validator = || { - let res = ( - address_gen.generate_address(addr_seed), - key::testing::common_sk_from_simple_seed(sk_seed).to_public(), - ); - // bump the sk seed - sk_seed += 1; - res - }; - - // Create genesis validators - let ((val1, pk1), stake1) = - (gen_validator(), token::Amount::native_whole(1)); - let ((val2, pk2), stake2) = - (gen_validator(), token::Amount::native_whole(1)); - let ((val3, pk3), stake3) = - (gen_validator(), token::Amount::native_whole(10)); - let ((val4, pk4), stake4) = - (gen_validator(), token::Amount::native_whole(1)); - let ((val5, pk5), stake5) = - (gen_validator(), token::Amount::native_whole(100)); - let ((val6, pk6), stake6) = - (gen_validator(), token::Amount::native_whole(1)); - let ((val7, pk7), stake7) = - (gen_validator(), token::Amount::native_whole(1)); - println!("\nval1: {val1}, {pk1}, {}", stake1.to_string_native()); - println!("val2: {val2}, {pk2}, {}", stake2.to_string_native()); - println!("val3: {val3}, {pk3}, {}", stake3.to_string_native()); - println!("val4: {val4}, {pk4}, {}", stake4.to_string_native()); - println!("val5: {val5}, {pk5}, {}", stake5.to_string_native()); - println!("val6: {val6}, {pk6}, {}", stake6.to_string_native()); - println!("val7: {val7}, {pk7}, {}", stake7.to_string_native()); - - let start_epoch = Epoch::default(); - let epoch = start_epoch; - - let protocol_sk_1 = common_sk_from_simple_seed(0); - let protocol_sk_2 = common_sk_from_simple_seed(1); - - let params = test_init_genesis( - &mut s, - params, - [ - GenesisValidator { - address: val1.clone(), - tokens: stake1, - consensus_key: pk1.clone(), - protocol_key: protocol_sk_1.to_public(), - eth_hot_key: key::common::PublicKey::Secp256k1( - key::testing::gen_keypair::() - .ref_to(), - ), - eth_cold_key: key::common::PublicKey::Secp256k1( - key::testing::gen_keypair::() - .ref_to(), - ), - commission_rate: Dec::new(1, 1).expect("Dec creation failed"), - max_commission_rate_change: Dec::new(1, 1) - .expect("Dec creation failed"), - metadata: Default::default(), - }, - GenesisValidator { - address: val2.clone(), - tokens: stake2, - consensus_key: pk2.clone(), - protocol_key: protocol_sk_2.to_public(), - eth_hot_key: key::common::PublicKey::Secp256k1( - key::testing::gen_keypair::() - .ref_to(), - ), - eth_cold_key: key::common::PublicKey::Secp256k1( - key::testing::gen_keypair::() - .ref_to(), - ), - commission_rate: Dec::new(1, 1).expect("Dec creation failed"), - max_commission_rate_change: Dec::new(1, 1) - .expect("Dec creation failed"), - metadata: Default::default(), - }, - ] - .into_iter(), - epoch, - ) - .unwrap(); - - // A helper to insert a non-genesis validator - let insert_validator = |s: &mut TestWlStorage, - addr, - pk: &PublicKey, - stake: token::Amount, - epoch: Epoch| { - insert_validator_into_validator_set( - s, - ¶ms, - addr, - stake, - epoch, - params.pipeline_len, - ) - .unwrap(); - - update_validator_deltas(s, ¶ms, addr, stake.change(), epoch, None) - .unwrap(); - - // Set their consensus key (needed for - // `validator_set_update_tendermint` fn) - validator_consensus_key_handle(addr) - .set(s, pk.clone(), epoch, params.pipeline_len) - .unwrap(); - }; - - // Advance to EPOCH 1 - // - // We cannot call `get_tendermint_set_updates` for the genesis state as - // `validator_set_update_tendermint` is only called 2 blocks before the - // start of an epoch and so we need to give it a predecessor epoch (see - // `get_tendermint_set_updates`), which we cannot have on the first - // epoch. In any way, the initial validator set is given to Tendermint - // from InitChain, so `validator_set_update_tendermint` is - // not being used for it. - let epoch = advance_epoch(&mut s, ¶ms); - let pipeline_epoch = epoch + params.pipeline_len; - - // Insert another validator with the greater stake 10 NAM - insert_validator(&mut s, &val3, &pk3, stake3, epoch); - // Insert validator with stake 1 NAM - insert_validator(&mut s, &val4, &pk4, stake4, epoch); - - // Validator `val3` and `val4` will be added at pipeline offset (2) - epoch - // 3 - let val3_and_4_epoch = pipeline_epoch; - - let consensus_vals: Vec<_> = consensus_validator_set_handle() - .at(&pipeline_epoch) - .iter(&s) - .unwrap() - .map(Result::unwrap) - .collect(); - - assert_eq!(consensus_vals.len(), 3); - assert!(matches!( - &consensus_vals[0], - (lazy_map::NestedSubKey::Data { - key: stake, - nested_sub_key: lazy_map::SubKey::Data(position), - }, address) - if address == &val1 && stake == &stake1 && *position == Position(0) - )); - assert!(matches!( - &consensus_vals[1], - (lazy_map::NestedSubKey::Data { - key: stake, - nested_sub_key: lazy_map::SubKey::Data(position), - }, address) - if address == &val2 && stake == &stake2 && *position == Position(1) - )); - assert!(matches!( - &consensus_vals[2], - (lazy_map::NestedSubKey::Data { - key: stake, - nested_sub_key: lazy_map::SubKey::Data(position), - }, address) - if address == &val3 && stake == &stake3 && *position == Position(0) - )); - - // Check tendermint validator set updates - there should be none - let tm_updates = get_tendermint_set_updates(&s, ¶ms, epoch); - assert!(tm_updates.is_empty()); - - // Advance to EPOCH 2 - let epoch = advance_epoch(&mut s, ¶ms); - let pipeline_epoch = epoch + params.pipeline_len; - - // Insert another validator with a greater stake still 1000 NAM. It should - // replace 2nd consensus validator with stake 1, which should become - // below-capacity - insert_validator(&mut s, &val5, &pk5, stake5, epoch); - // Validator `val5` will be added at pipeline offset (2) - epoch 4 - let val5_epoch = pipeline_epoch; - - let consensus_vals: Vec<_> = consensus_validator_set_handle() - .at(&pipeline_epoch) - .iter(&s) - .unwrap() - .map(Result::unwrap) - .collect(); - - assert_eq!(consensus_vals.len(), 3); - assert!(matches!( - &consensus_vals[0], - (lazy_map::NestedSubKey::Data { - key: stake, - nested_sub_key: lazy_map::SubKey::Data(position), - }, address) - if address == &val1 && stake == &stake1 && *position == Position(0) - )); - assert!(matches!( - &consensus_vals[1], - (lazy_map::NestedSubKey::Data { - key: stake, - nested_sub_key: lazy_map::SubKey::Data(position), - }, address) - if address == &val3 && stake == &stake3 && *position == Position(0) - )); - assert!(matches!( - &consensus_vals[2], - (lazy_map::NestedSubKey::Data { - key: stake, - nested_sub_key: lazy_map::SubKey::Data(position), - }, address) - if address == &val5 && stake == &stake5 && *position == Position(0) - )); - - let below_capacity_vals: Vec<_> = below_capacity_validator_set_handle() - .at(&pipeline_epoch) - .iter(&s) - .unwrap() - .map(Result::unwrap) - .collect(); - - assert_eq!(below_capacity_vals.len(), 2); - assert!(matches!( - &below_capacity_vals[0], - (lazy_map::NestedSubKey::Data { - key: ReverseOrdTokenAmount(stake), - nested_sub_key: lazy_map::SubKey::Data(position), - }, address) - if address == &val4 && stake == &stake4 && *position == Position(0) - )); - assert!(matches!( - &below_capacity_vals[1], - (lazy_map::NestedSubKey::Data { - key: ReverseOrdTokenAmount(stake), - nested_sub_key: lazy_map::SubKey::Data(position), - }, address) - if address == &val2 && stake == &stake2 && *position == Position(1) - )); - - // Advance to EPOCH 3 - let epoch = advance_epoch(&mut s, ¶ms); - let pipeline_epoch = epoch + params.pipeline_len; - - // Check tendermint validator set updates - assert_eq!( - val3_and_4_epoch, epoch, - "val3 and val4 are in the validator sets now" - ); - let tm_updates = get_tendermint_set_updates(&s, ¶ms, epoch); - // `val4` is newly added below-capacity, must be skipped in updated in TM - assert_eq!(tm_updates.len(), 1); - assert_eq!( - tm_updates[0], - ValidatorSetUpdate::Consensus(ConsensusValidator { - consensus_key: pk3, - bonded_stake: stake3, - }) - ); - - // Insert another validator with a stake 1 NAM. It should be added to the - // below-capacity set - insert_validator(&mut s, &val6, &pk6, stake6, epoch); - // Validator `val6` will be added at pipeline offset (2) - epoch 5 - let val6_epoch = pipeline_epoch; - - let below_capacity_vals: Vec<_> = below_capacity_validator_set_handle() - .at(&pipeline_epoch) - .iter(&s) - .unwrap() - .map(Result::unwrap) - .collect(); - - assert_eq!(below_capacity_vals.len(), 3); - assert!(matches!( - &below_capacity_vals[0], - (lazy_map::NestedSubKey::Data { - key: ReverseOrdTokenAmount(stake), - nested_sub_key: lazy_map::SubKey::Data(position), - }, address) - if address == &val4 && stake == &stake4 && *position == Position(0) - )); - assert!(matches!( - &below_capacity_vals[1], - (lazy_map::NestedSubKey::Data { - key: ReverseOrdTokenAmount(stake), - nested_sub_key: lazy_map::SubKey::Data(position), - }, address) - if address == &val2 && stake == &stake2 && *position == Position(1) - )); - assert!(matches!( - &below_capacity_vals[2], - (lazy_map::NestedSubKey::Data { - key: ReverseOrdTokenAmount(stake), - nested_sub_key: lazy_map::SubKey::Data(position), - }, address) - if address == &val6 && stake == &stake6 && *position == Position(2) - )); - - // Advance to EPOCH 4 - let epoch = advance_epoch(&mut s, ¶ms); - let pipeline_epoch = epoch + params.pipeline_len; - - // Check tendermint validator set updates - assert_eq!(val5_epoch, epoch, "val5 is in the validator sets now"); - let tm_updates = get_tendermint_set_updates(&s, ¶ms, epoch); - assert_eq!(tm_updates.len(), 2); - assert_eq!( - tm_updates[0], - ValidatorSetUpdate::Consensus(ConsensusValidator { - consensus_key: pk5, - bonded_stake: stake5, - }) - ); - assert_eq!(tm_updates[1], ValidatorSetUpdate::Deactivated(pk2)); - - // Unbond some stake from val1, it should be be swapped with the greatest - // below-capacity validator val2 into the below-capacity set. The stake of - // val1 will go below 1 NAM, which is the validator_stake_threshold, so it - // will enter the below-threshold validator set. - let unbond = token::Amount::from_uint(500_000, 0).unwrap(); - let stake1 = stake1 - unbond; - println!("val1 {val1} new stake {}", stake1.to_string_native()); - // Because `update_validator_set` and `update_validator_deltas` are - // effective from pipeline offset, we use pipeline epoch for the rest of the - // checks - update_validator_set(&mut s, ¶ms, &val1, -unbond.change(), epoch, None) - .unwrap(); - update_validator_deltas( - &mut s, - ¶ms, - &val1, - -unbond.change(), - epoch, - None, - ) - .unwrap(); - // Epoch 6 - let val1_unbond_epoch = pipeline_epoch; - - let consensus_vals: Vec<_> = consensus_validator_set_handle() - .at(&pipeline_epoch) - .iter(&s) - .unwrap() - .map(Result::unwrap) - .collect(); - - assert_eq!(consensus_vals.len(), 3); - assert!(matches!( - &consensus_vals[0], - (lazy_map::NestedSubKey::Data { - key: stake, - nested_sub_key: lazy_map::SubKey::Data(position), - }, address) - if address == &val4 && stake == &stake4 && *position == Position(0) - )); - assert!(matches!( - &consensus_vals[1], - (lazy_map::NestedSubKey::Data { - key: stake, - nested_sub_key: lazy_map::SubKey::Data(position), - }, address) - if address == &val3 && stake == &stake3 && *position == Position(0) - )); - assert!(matches!( - &consensus_vals[2], - (lazy_map::NestedSubKey::Data { - key: stake, - nested_sub_key: lazy_map::SubKey::Data(position), - }, address) - if address == &val5 && stake == &stake5 && *position == Position(0) - )); - - let below_capacity_vals: Vec<_> = below_capacity_validator_set_handle() - .at(&pipeline_epoch) - .iter(&s) - .unwrap() - .map(Result::unwrap) - .collect(); - - assert_eq!(below_capacity_vals.len(), 2); - assert!(matches!( - &below_capacity_vals[0], - (lazy_map::NestedSubKey::Data { - key: ReverseOrdTokenAmount(stake), - nested_sub_key: lazy_map::SubKey::Data(position), - }, address) - if address == &val2 && stake == &stake2 && *position == Position(1) - )); - assert!(matches!( - &below_capacity_vals[1], - (lazy_map::NestedSubKey::Data { - key: ReverseOrdTokenAmount(stake), - nested_sub_key: lazy_map::SubKey::Data(position), - }, address) - if address == &val6 && stake == &stake6 && *position == Position(2) - )); - - let below_threshold_vals = - read_below_threshold_validator_set_addresses(&s, pipeline_epoch) - .unwrap() - .into_iter() - .collect::>(); - - assert_eq!(below_threshold_vals.len(), 1); - assert_eq!(&below_threshold_vals[0], &val1); - - // Advance to EPOCH 5 - let epoch = advance_epoch(&mut s, ¶ms); - let pipeline_epoch = epoch + params.pipeline_len; - - // Check tendermint validator set updates - assert_eq!(val6_epoch, epoch, "val6 is in the validator sets now"); - let tm_updates = get_tendermint_set_updates(&s, ¶ms, epoch); - assert!(tm_updates.is_empty()); - - // Insert another validator with stake 1 - it should be added to below - // capacity set - insert_validator(&mut s, &val7, &pk7, stake7, epoch); - // Epoch 7 - let val7_epoch = pipeline_epoch; - - let consensus_vals: Vec<_> = consensus_validator_set_handle() - .at(&pipeline_epoch) - .iter(&s) - .unwrap() - .map(Result::unwrap) - .collect(); - - assert_eq!(consensus_vals.len(), 3); - assert!(matches!( - &consensus_vals[0], - (lazy_map::NestedSubKey::Data { - key: stake, - nested_sub_key: lazy_map::SubKey::Data(position), - }, address) - if address == &val4 && stake == &stake4 && *position == Position(0) - )); - assert!(matches!( - &consensus_vals[1], - (lazy_map::NestedSubKey::Data { - key: stake, - nested_sub_key: lazy_map::SubKey::Data(position), - }, address) - if address == &val3 && stake == &stake3 && *position == Position(0) - )); - assert!(matches!( - &consensus_vals[2], - (lazy_map::NestedSubKey::Data { - key: stake, - nested_sub_key: lazy_map::SubKey::Data(position), - }, address) - if address == &val5 && stake == &stake5 && *position == Position(0) - )); - - let below_capacity_vals: Vec<_> = below_capacity_validator_set_handle() - .at(&pipeline_epoch) - .iter(&s) - .unwrap() - .map(Result::unwrap) - .collect(); - - assert_eq!(below_capacity_vals.len(), 3); - assert!(matches!( - &below_capacity_vals[0], - (lazy_map::NestedSubKey::Data { - key: ReverseOrdTokenAmount(stake), - nested_sub_key: lazy_map::SubKey::Data(position), - }, address) - if address == &val2 && stake == &stake2 && *position == Position(1) - )); - assert!(matches!( - &below_capacity_vals[1], - (lazy_map::NestedSubKey::Data { - key: ReverseOrdTokenAmount(stake), - nested_sub_key: lazy_map::SubKey::Data(position), - }, address) - if address == &val6 && stake == &stake6 && *position == Position(2) - )); - assert!(matches!( - &below_capacity_vals[2], - ( - lazy_map::NestedSubKey::Data { - key: ReverseOrdTokenAmount(stake), - nested_sub_key: lazy_map::SubKey::Data(position), - }, - address - ) - if address == &val7 && stake == &stake7 && *position == Position(3) - )); - - let below_threshold_vals = - read_below_threshold_validator_set_addresses(&s, pipeline_epoch) - .unwrap() - .into_iter() - .collect::>(); - - assert_eq!(below_threshold_vals.len(), 1); - assert_eq!(&below_threshold_vals[0], &val1); - - // Advance to EPOCH 6 - let epoch = advance_epoch(&mut s, ¶ms); - let pipeline_epoch = epoch + params.pipeline_len; - - // Check tendermint validator set updates - assert_eq!(val1_unbond_epoch, epoch, "val1's unbond is applied now"); - let tm_updates = get_tendermint_set_updates(&s, ¶ms, epoch); - assert_eq!(tm_updates.len(), 2); - assert_eq!( - tm_updates[0], - ValidatorSetUpdate::Consensus(ConsensusValidator { - consensus_key: pk4.clone(), - bonded_stake: stake4, - }) - ); - assert_eq!(tm_updates[1], ValidatorSetUpdate::Deactivated(pk1)); - - // Bond some stake to val6, it should be be swapped with the lowest - // consensus validator val2 into the consensus set - let bond = token::Amount::from_uint(500_000, 0).unwrap(); - let stake6 = stake6 + bond; - println!("val6 {val6} new stake {}", stake6.to_string_native()); - update_validator_set(&mut s, ¶ms, &val6, bond.change(), epoch, None) - .unwrap(); - update_validator_deltas(&mut s, ¶ms, &val6, bond.change(), epoch, None) - .unwrap(); - let val6_bond_epoch = pipeline_epoch; - - let consensus_vals: Vec<_> = consensus_validator_set_handle() - .at(&pipeline_epoch) - .iter(&s) - .unwrap() - .map(Result::unwrap) - .collect(); - - assert_eq!(consensus_vals.len(), 3); - assert!(matches!( - &consensus_vals[0], - (lazy_map::NestedSubKey::Data { - key: stake, - nested_sub_key: lazy_map::SubKey::Data(position), - }, address) - if address == &val6 && stake == &stake6 && *position == Position(0) - )); - assert!(matches!( - &consensus_vals[1], - (lazy_map::NestedSubKey::Data { - key: stake, - nested_sub_key: lazy_map::SubKey::Data(position), - }, address) - if address == &val3 && stake == &stake3 && *position == Position(0) - )); - assert!(matches!( - &consensus_vals[2], - (lazy_map::NestedSubKey::Data { - key: stake, - nested_sub_key: lazy_map::SubKey::Data(position), - }, address) - if address == &val5 && stake == &stake5 && *position == Position(0) - )); - - let below_capacity_vals: Vec<_> = below_capacity_validator_set_handle() - .at(&pipeline_epoch) - .iter(&s) - .unwrap() - .map(Result::unwrap) - .collect(); - - assert_eq!(below_capacity_vals.len(), 3); - dbg!(&below_capacity_vals); - assert!(matches!( - &below_capacity_vals[0], - (lazy_map::NestedSubKey::Data { - key: ReverseOrdTokenAmount(stake), - nested_sub_key: lazy_map::SubKey::Data(position), - }, address) - if address == &val2 && stake == &stake2 && *position == Position(1) - )); - assert!(matches!( - &below_capacity_vals[1], - (lazy_map::NestedSubKey::Data { - key: ReverseOrdTokenAmount(stake), - nested_sub_key: lazy_map::SubKey::Data(position), - }, address) - if address == &val7 && stake == &stake7 && *position == Position(3) - )); - assert!(matches!( - &below_capacity_vals[2], - ( - lazy_map::NestedSubKey::Data { - key: ReverseOrdTokenAmount(stake), - nested_sub_key: lazy_map::SubKey::Data(position), - }, - address - ) - if address == &val4 && stake == &stake4 && *position == Position(4) - )); - - let below_threshold_vals = - read_below_threshold_validator_set_addresses(&s, pipeline_epoch) - .unwrap() - .into_iter() - .collect::>(); - - assert_eq!(below_threshold_vals.len(), 1); - assert_eq!(&below_threshold_vals[0], &val1); - - // Advance to EPOCH 7 - let epoch = advance_epoch(&mut s, ¶ms); - assert_eq!(val7_epoch, epoch, "val6 is in the validator sets now"); - - // Check tendermint validator set updates - let tm_updates = get_tendermint_set_updates(&s, ¶ms, epoch); - assert!(tm_updates.is_empty()); - - // Advance to EPOCH 8 - let epoch = advance_epoch(&mut s, ¶ms); - - // Check tendermint validator set updates - assert_eq!(val6_bond_epoch, epoch, "val5's bond is applied now"); - let tm_updates = get_tendermint_set_updates(&s, ¶ms, epoch); - dbg!(&tm_updates); - assert_eq!(tm_updates.len(), 2); - assert_eq!( - tm_updates[0], - ValidatorSetUpdate::Consensus(ConsensusValidator { - consensus_key: pk6, - bonded_stake: stake6, - }) - ); - assert_eq!(tm_updates[1], ValidatorSetUpdate::Deactivated(pk4)); - - // Check that the below-capacity validator set was purged for the old epochs - // but that the consensus_validator_set was not - let last_epoch = epoch; - for e in Epoch::iter_bounds_inclusive( - start_epoch, - last_epoch - .sub_or_default(Epoch(DEFAULT_NUM_PAST_EPOCHS)) - .sub_or_default(Epoch(1)), - ) { - assert!( - !consensus_validator_set_handle() - .at(&e) - .is_empty(&s) - .unwrap() - ); - assert!( - below_capacity_validator_set_handle() - .at(&e) - .is_empty(&s) - .unwrap() - ); - } -} - -/// When a consensus set validator with 0 voting power adds a bond in the same -/// epoch as another below-capacity set validator with 0 power, but who adds -/// more bonds than the validator who is in the consensus set, they get swapped -/// in the sets. But if both of their new voting powers are still 0 after -/// bonding, the newly below-capacity validator must not be given to tendermint -/// with 0 voting power, because it wasn't it its set before -#[test] -fn test_validator_sets_swap() { - let mut s = TestWlStorage::default(); - // Only 2 consensus validator slots - let params = OwnedPosParams { - max_validator_slots: 2, - // Set the stake threshold to 0 so no validators are in the - // below-threshold set - validator_stake_threshold: token::Amount::zero(), - // Set 0.1 votes per token - tm_votes_per_token: Dec::new(1, 1).expect("Dec creation failed"), - ..Default::default() - }; - - let addr_seed = "seed"; - let mut address_gen = EstablishedAddressGen::new(addr_seed); - let mut sk_seed = 0; - let mut gen_validator = || { - let res = ( - address_gen.generate_address(addr_seed), - key::testing::common_sk_from_simple_seed(sk_seed).to_public(), - ); - // bump the sk seed - sk_seed += 1; - res - }; - - // Start with two genesis validators, one with 1 voting power and other 0 - let epoch = Epoch::default(); - // 1M voting power - let ((val1, pk1), stake1) = - (gen_validator(), token::Amount::native_whole(10)); - // 0 voting power - let ((val2, pk2), stake2) = - (gen_validator(), token::Amount::from_uint(5, 0).unwrap()); - // 0 voting power - let ((val3, pk3), stake3) = - (gen_validator(), token::Amount::from_uint(5, 0).unwrap()); - println!("val1: {val1}, {pk1}, {}", stake1.to_string_native()); - println!("val2: {val2}, {pk2}, {}", stake2.to_string_native()); - println!("val3: {val3}, {pk3}, {}", stake3.to_string_native()); - - let protocol_sk_1 = common_sk_from_simple_seed(0); - let protocol_sk_2 = common_sk_from_simple_seed(1); - - let params = test_init_genesis( - &mut s, - params, - [ - GenesisValidator { - address: val1, - tokens: stake1, - consensus_key: pk1, - protocol_key: protocol_sk_1.to_public(), - eth_hot_key: key::common::PublicKey::Secp256k1( - key::testing::gen_keypair::() - .ref_to(), - ), - eth_cold_key: key::common::PublicKey::Secp256k1( - key::testing::gen_keypair::() - .ref_to(), - ), - commission_rate: Dec::new(1, 1).expect("Dec creation failed"), - max_commission_rate_change: Dec::new(1, 1) - .expect("Dec creation failed"), - metadata: Default::default(), - }, - GenesisValidator { - address: val2.clone(), - tokens: stake2, - consensus_key: pk2, - protocol_key: protocol_sk_2.to_public(), - eth_hot_key: key::common::PublicKey::Secp256k1( - key::testing::gen_keypair::() - .ref_to(), - ), - eth_cold_key: key::common::PublicKey::Secp256k1( - key::testing::gen_keypair::() - .ref_to(), - ), - commission_rate: Dec::new(1, 1).expect("Dec creation failed"), - max_commission_rate_change: Dec::new(1, 1) - .expect("Dec creation failed"), - metadata: Default::default(), - }, - ] - .into_iter(), - epoch, - ) - .unwrap(); - - // A helper to insert a non-genesis validator - let insert_validator = |s: &mut TestWlStorage, - addr, - pk: &PublicKey, - stake: token::Amount, - epoch: Epoch| { - insert_validator_into_validator_set( - s, - ¶ms, - addr, - stake, - epoch, - params.pipeline_len, - ) - .unwrap(); - - update_validator_deltas(s, ¶ms, addr, stake.change(), epoch, None) - .unwrap(); - - // Set their consensus key (needed for - // `validator_set_update_tendermint` fn) - validator_consensus_key_handle(addr) - .set(s, pk.clone(), epoch, params.pipeline_len) - .unwrap(); - }; - - // Advance to EPOCH 1 - let epoch = advance_epoch(&mut s, ¶ms); - let pipeline_epoch = epoch + params.pipeline_len; - - // Insert another validator with 0 voting power - insert_validator(&mut s, &val3, &pk3, stake3, epoch); - - assert_eq!(stake2, stake3); - - // Add 2 bonds, one for val2 and greater one for val3 - let bonds_epoch_1 = pipeline_epoch; - let bond2 = token::Amount::from_uint(1, 0).unwrap(); - let stake2 = stake2 + bond2; - let bond3 = token::Amount::from_uint(4, 0).unwrap(); - let stake3 = stake3 + bond3; - - assert!(stake2 < stake3); - assert_eq!(into_tm_voting_power(params.tm_votes_per_token, stake2), 0); - assert_eq!(into_tm_voting_power(params.tm_votes_per_token, stake3), 0); - - update_validator_set(&mut s, ¶ms, &val2, bond2.change(), epoch, None) - .unwrap(); - update_validator_deltas( - &mut s, - ¶ms, - &val2, - bond2.change(), - epoch, - None, - ) - .unwrap(); - - update_validator_set(&mut s, ¶ms, &val3, bond3.change(), epoch, None) - .unwrap(); - update_validator_deltas( - &mut s, - ¶ms, - &val3, - bond3.change(), - epoch, - None, - ) - .unwrap(); - - // Advance to EPOCH 2 - let epoch = advance_epoch(&mut s, ¶ms); - let pipeline_epoch = epoch + params.pipeline_len; - - // Add 2 more bonds, same amount for `val2` and val3` - let bonds_epoch_2 = pipeline_epoch; - let bonds = token::Amount::native_whole(1); - let stake2 = stake2 + bonds; - let stake3 = stake3 + bonds; - assert!(stake2 < stake3); - assert_eq!( - into_tm_voting_power(params.tm_votes_per_token, stake2), - into_tm_voting_power(params.tm_votes_per_token, stake3) - ); - - update_validator_set(&mut s, ¶ms, &val2, bonds.change(), epoch, None) - .unwrap(); - update_validator_deltas( - &mut s, - ¶ms, - &val2, - bonds.change(), - epoch, - None, - ) - .unwrap(); - - update_validator_set(&mut s, ¶ms, &val3, bonds.change(), epoch, None) - .unwrap(); - update_validator_deltas( - &mut s, - ¶ms, - &val3, - bonds.change(), - epoch, - None, - ) - .unwrap(); - - // Advance to EPOCH 3 - let epoch = advance_epoch(&mut s, ¶ms); - - // Check tendermint validator set updates - assert_eq!(bonds_epoch_1, epoch); - let tm_updates = get_tendermint_set_updates(&s, ¶ms, epoch); - // `val2` must not be given to tendermint - even though it was in the - // consensus set, its voting power was 0, so it wasn't in TM set before the - // bond - assert!(tm_updates.is_empty()); - - // Advance to EPOCH 4 - let epoch = advance_epoch(&mut s, ¶ms); - - // Check tendermint validator set updates - assert_eq!(bonds_epoch_2, epoch); - let tm_updates = get_tendermint_set_updates(&s, ¶ms, epoch); - dbg!(&tm_updates); - assert_eq!(tm_updates.len(), 1); - // `val2` must not be given to tendermint as it was and still is below - // capacity - assert_eq!( - tm_updates[0], - ValidatorSetUpdate::Consensus(ConsensusValidator { - consensus_key: pk3, - bonded_stake: stake3, - }) - ); -} - -fn get_tendermint_set_updates( - s: &TestWlStorage, - params: &PosParams, - Epoch(epoch): Epoch, -) -> Vec { - // Because the `validator_set_update_tendermint` is called 2 blocks before - // the start of a new epoch, it expects to receive the epoch that is before - // the start of a new one too and so we give it the predecessor of the - // current epoch here to actually get the update for the current epoch. - let epoch = Epoch(epoch - 1); - validator_set_update_tendermint(s, params, epoch, |update| update).unwrap() -} - -/// Advance to the next epoch. Returns the new epoch. -fn advance_epoch(s: &mut TestWlStorage, params: &PosParams) -> Epoch { - s.storage.block.epoch = s.storage.block.epoch.next(); - let current_epoch = s.storage.block.epoch; - compute_and_store_total_consensus_stake(s, current_epoch).unwrap(); - copy_validator_sets_and_positions( - s, - params, - current_epoch, - current_epoch + params.pipeline_len, - ) - .unwrap(); - // purge_validator_sets_for_old_epoch(s, current_epoch).unwrap(); - // process_slashes(s, current_epoch).unwrap(); - // dbg!(current_epoch); - current_epoch -} - -fn arb_genesis_validators( - size: Range, - threshold: Option, -) -> impl Strategy> { - let threshold = threshold - .unwrap_or_else(|| PosParams::default().validator_stake_threshold); - let tokens: Vec<_> = (0..size.end) - .map(|ix| { - if ix == 0 { - // Make sure that at least one validator has at least a stake - // greater or equal to the threshold to avoid having an empty - // consensus set. - threshold.raw_amount().as_u64()..=10_000_000_u64 - } else { - 1..=10_000_000_u64 - } - .prop_map(token::Amount::from) - }) - .collect(); - (size, tokens) - .prop_map(|(size, token_amounts)| { - // use unique seeds to generate validators' address and consensus - // key - let seeds = (0_u64..).take(size); - seeds - .zip(token_amounts) - .map(|(seed, tokens)| { - let address = address_from_simple_seed(seed); - let consensus_sk = common_sk_from_simple_seed(seed); - let consensus_key = consensus_sk.to_public(); - - let protocol_sk = common_sk_from_simple_seed(seed); - let protocol_key = protocol_sk.to_public(); - - let eth_hot_key = key::common::PublicKey::Secp256k1( - key::testing::gen_keypair::( - ) - .ref_to(), - ); - let eth_cold_key = key::common::PublicKey::Secp256k1( - key::testing::gen_keypair::( - ) - .ref_to(), - ); - - let commission_rate = Dec::new(5, 2).expect("Test failed"); - let max_commission_rate_change = - Dec::new(1, 2).expect("Test failed"); - GenesisValidator { - address, - tokens, - consensus_key, - protocol_key, - eth_hot_key, - eth_cold_key, - commission_rate, - max_commission_rate_change, - metadata: Default::default(), - } - }) - .collect() - }) - .prop_filter( - "Must have at least one genesis validator with stake above the \ - provided threshold, if any.", - move |gen_vals: &Vec| { - gen_vals.iter().any(|val| val.tokens >= threshold) - }, - ) -} - -fn test_unjail_validator_aux( - params: OwnedPosParams, - mut validators: Vec, -) { - println!("\nTest inputs: {params:?}, genesis validators: {validators:#?}"); - let mut s = TestWlStorage::default(); - - // Find the validator with the most stake and 100x his stake to keep the - // cubic slash rate small - let num_vals = validators.len(); - validators.sort_by_key(|a| a.tokens); - validators[num_vals - 1].tokens = 100 * validators[num_vals - 1].tokens; - - // Get second highest stake validator tomisbehave - let val_addr = &validators[num_vals - 2].address; - let val_tokens = validators[num_vals - 2].tokens; - println!( - "Validator that will misbehave addr {val_addr}, tokens {}", - val_tokens.to_string_native() - ); - - // Genesis - let mut current_epoch = s.storage.block.epoch; - let params = test_init_genesis( - &mut s, - params, - validators.clone().into_iter(), - current_epoch, - ) - .unwrap(); - s.commit_block().unwrap(); - - current_epoch = advance_epoch(&mut s, ¶ms); - process_slashes(&mut s, current_epoch).unwrap(); - - // Discover first slash - let slash_0_evidence_epoch = current_epoch; - let evidence_block_height = BlockHeight(0); // doesn't matter for slashing logic - let slash_0_type = SlashType::DuplicateVote; - slash( - &mut s, - ¶ms, - current_epoch, - slash_0_evidence_epoch, - evidence_block_height, - slash_0_type, - val_addr, - current_epoch.next(), - ) - .unwrap(); - - assert_eq!( - validator_state_handle(val_addr) - .get(&s, current_epoch, ¶ms) - .unwrap(), - Some(ValidatorState::Consensus) - ); - - for epoch in Epoch::iter_bounds_inclusive( - current_epoch.next(), - current_epoch + params.pipeline_len, - ) { - // Check the validator state - assert_eq!( - validator_state_handle(val_addr) - .get(&s, epoch, ¶ms) - .unwrap(), - Some(ValidatorState::Jailed) - ); - // Check the validator set positions - assert!( - validator_set_positions_handle() - .at(&epoch) - .get(&s, val_addr) - .unwrap() - .is_none(), - ); - } - - // Advance past an epoch in which we can unbond - let unfreeze_epoch = - slash_0_evidence_epoch + params.slash_processing_epoch_offset(); - while current_epoch < unfreeze_epoch + 4u64 { - current_epoch = advance_epoch(&mut s, ¶ms); - process_slashes(&mut s, current_epoch).unwrap(); - } - - // Unjail the validator - unjail_validator(&mut s, val_addr, current_epoch).unwrap(); - - // Check the validator state - for epoch in - Epoch::iter_bounds_inclusive(current_epoch, current_epoch.next()) - { - assert_eq!( - validator_state_handle(val_addr) - .get(&s, epoch, ¶ms) - .unwrap(), - Some(ValidatorState::Jailed) - ); - } - - assert_eq!( - validator_state_handle(val_addr) - .get(&s, current_epoch + params.pipeline_len, ¶ms) - .unwrap(), - Some(ValidatorState::Consensus) - ); - assert!( - validator_set_positions_handle() - .at(&(current_epoch + params.pipeline_len)) - .get(&s, val_addr) - .unwrap() - .is_some(), - ); - - // Advance another epoch - current_epoch = advance_epoch(&mut s, ¶ms); - process_slashes(&mut s, current_epoch).unwrap(); - - let second_att = unjail_validator(&mut s, val_addr, current_epoch); - assert!(second_att.is_err()); -} - -/// `iterateBondsUpToAmountTest` -#[test] -fn test_find_bonds_to_remove() { - let mut storage = TestWlStorage::default(); - let gov_params = namada_core::ledger::governance::parameters::GovernanceParameters::default(); - gov_params.init_storage(&mut storage).unwrap(); - write_pos_params(&mut storage, &OwnedPosParams::default()).unwrap(); - - let source = established_address_1(); - let validator = established_address_2(); - let bond_handle = bond_handle(&source, &validator); - - let (e1, e2, e6) = (Epoch(1), Epoch(2), Epoch(6)); - - bond_handle - .set(&mut storage, token::Amount::from(5), e1, 0) - .unwrap(); - bond_handle - .set(&mut storage, token::Amount::from(3), e2, 0) - .unwrap(); - bond_handle - .set(&mut storage, token::Amount::from(8), e6, 0) - .unwrap(); - - // Test 1 - let bonds_for_removal = find_bonds_to_remove( - &storage, - &bond_handle.get_data_handler(), - token::Amount::from(8), - ) - .unwrap(); - assert_eq!( - bonds_for_removal.epochs, - vec![e6].into_iter().collect::>() - ); - assert!(bonds_for_removal.new_entry.is_none()); - - // Test 2 - let bonds_for_removal = find_bonds_to_remove( - &storage, - &bond_handle.get_data_handler(), - token::Amount::from(10), - ) - .unwrap(); - assert_eq!( - bonds_for_removal.epochs, - vec![e6].into_iter().collect::>() - ); - assert_eq!( - bonds_for_removal.new_entry, - Some((Epoch(2), token::Amount::from(1))) - ); - - // Test 3 - let bonds_for_removal = find_bonds_to_remove( - &storage, - &bond_handle.get_data_handler(), - token::Amount::from(11), - ) - .unwrap(); - assert_eq!( - bonds_for_removal.epochs, - vec![e6, e2].into_iter().collect::>() - ); - assert!(bonds_for_removal.new_entry.is_none()); - - // Test 4 - let bonds_for_removal = find_bonds_to_remove( - &storage, - &bond_handle.get_data_handler(), - token::Amount::from(12), - ) - .unwrap(); - assert_eq!( - bonds_for_removal.epochs, - vec![e6, e2].into_iter().collect::>() - ); - assert_eq!( - bonds_for_removal.new_entry, - Some((Epoch(1), token::Amount::from(4))) - ); -} - -/// `computeModifiedRedelegationTest` -#[test] -fn test_compute_modified_redelegation() { - let mut storage = TestWlStorage::default(); - let validator1 = established_address_1(); - let validator2 = established_address_2(); - let owner = established_address_3(); - let outer_epoch = Epoch(0); - - let mut alice = validator1.clone(); - let mut bob = validator2.clone(); - - // Ensure a ranking order of alice > bob - if bob > alice { - alice = validator2; - bob = validator1; - } - println!("\n\nalice = {}\nbob = {}\n", &alice, &bob); - - // Fill redelegated bonds in storage - let redelegated_bonds_map = delegator_redelegated_bonds_handle(&owner) - .at(&alice) - .at(&outer_epoch); - redelegated_bonds_map - .at(&alice) - .insert(&mut storage, Epoch(2), token::Amount::from(6)) - .unwrap(); - redelegated_bonds_map - .at(&alice) - .insert(&mut storage, Epoch(4), token::Amount::from(7)) - .unwrap(); - redelegated_bonds_map - .at(&bob) - .insert(&mut storage, Epoch(1), token::Amount::from(5)) - .unwrap(); - redelegated_bonds_map - .at(&bob) - .insert(&mut storage, Epoch(4), token::Amount::from(7)) - .unwrap(); - - // Test cases 1 and 2 - let mr1 = compute_modified_redelegation( - &storage, - &redelegated_bonds_map, - Epoch(5), - token::Amount::from(25), - ) - .unwrap(); - let mr2 = compute_modified_redelegation( - &storage, - &redelegated_bonds_map, - Epoch(5), - token::Amount::from(30), - ) - .unwrap(); - - let exp_mr = ModifiedRedelegation { - epoch: Some(Epoch(5)), - ..Default::default() - }; - - assert_eq!(mr1, exp_mr); - assert_eq!(mr2, exp_mr); - - // Test case 3 - let mr3 = compute_modified_redelegation( - &storage, - &redelegated_bonds_map, - Epoch(5), - token::Amount::from(7), - ) - .unwrap(); - - let exp_mr = ModifiedRedelegation { - epoch: Some(Epoch(5)), - validators_to_remove: BTreeSet::from_iter([bob.clone()]), - validator_to_modify: Some(bob.clone()), - epochs_to_remove: BTreeSet::from_iter([Epoch(4)]), - ..Default::default() - }; - assert_eq!(mr3, exp_mr); - - // Test case 4 - let mr4 = compute_modified_redelegation( - &storage, - &redelegated_bonds_map, - Epoch(5), - token::Amount::from(8), - ) - .unwrap(); - - let exp_mr = ModifiedRedelegation { - epoch: Some(Epoch(5)), - validators_to_remove: BTreeSet::from_iter([bob.clone()]), - validator_to_modify: Some(bob.clone()), - epochs_to_remove: BTreeSet::from_iter([Epoch(1), Epoch(4)]), - epoch_to_modify: Some(Epoch(1)), - new_amount: Some(4.into()), - }; - assert_eq!(mr4, exp_mr); - - // Test case 5 - let mr5 = compute_modified_redelegation( - &storage, - &redelegated_bonds_map, - Epoch(5), - 12.into(), - ) - .unwrap(); - - let exp_mr = ModifiedRedelegation { - epoch: Some(Epoch(5)), - validators_to_remove: BTreeSet::from_iter([bob.clone()]), - ..Default::default() - }; - assert_eq!(mr5, exp_mr); - - // Test case 6 - let mr6 = compute_modified_redelegation( - &storage, - &redelegated_bonds_map, - Epoch(5), - 14.into(), - ) - .unwrap(); - - let exp_mr = ModifiedRedelegation { - epoch: Some(Epoch(5)), - validators_to_remove: BTreeSet::from_iter([alice.clone(), bob.clone()]), - validator_to_modify: Some(alice.clone()), - epochs_to_remove: BTreeSet::from_iter([Epoch(4)]), - epoch_to_modify: Some(Epoch(4)), - new_amount: Some(5.into()), - }; - assert_eq!(mr6, exp_mr); - - // Test case 7 - let mr7 = compute_modified_redelegation( - &storage, - &redelegated_bonds_map, - Epoch(5), - 19.into(), - ) - .unwrap(); - - let exp_mr = ModifiedRedelegation { - epoch: Some(Epoch(5)), - validators_to_remove: BTreeSet::from_iter([alice.clone(), bob.clone()]), - validator_to_modify: Some(alice.clone()), - epochs_to_remove: BTreeSet::from_iter([Epoch(4)]), - ..Default::default() - }; - assert_eq!(mr7, exp_mr); - - // Test case 8 - let mr8 = compute_modified_redelegation( - &storage, - &redelegated_bonds_map, - Epoch(5), - 21.into(), - ) - .unwrap(); - - let exp_mr = ModifiedRedelegation { - epoch: Some(Epoch(5)), - validators_to_remove: BTreeSet::from_iter([alice.clone(), bob]), - validator_to_modify: Some(alice), - epochs_to_remove: BTreeSet::from_iter([Epoch(2), Epoch(4)]), - epoch_to_modify: Some(Epoch(2)), - new_amount: Some(4.into()), - }; - assert_eq!(mr8, exp_mr); -} - -/// `computeBondAtEpochTest` -#[test] -fn test_compute_bond_at_epoch() { - let mut storage = TestWlStorage::default(); - let params = OwnedPosParams { - pipeline_len: 2, - unbonding_len: 4, - cubic_slashing_window_length: 1, - ..Default::default() - }; - let alice = established_address_1(); - let bob = established_address_2(); - - // Test 1 - let res = compute_bond_at_epoch( - &storage, - ¶ms, - &bob, - 12.into(), - 3.into(), - 23.into(), - Some(&Default::default()), - ) - .unwrap(); - - pretty_assertions::assert_eq!(res, 23.into()); - - // Test 2 - validator_slashes_handle(&bob) - .push( - &mut storage, - Slash { - epoch: 4.into(), - block_height: 0, - r#type: SlashType::DuplicateVote, - rate: Dec::one(), - }, - ) - .unwrap(); - let res = compute_bond_at_epoch( - &storage, - ¶ms, - &bob, - 12.into(), - 3.into(), - 23.into(), - Some(&Default::default()), - ) - .unwrap(); - - pretty_assertions::assert_eq!(res, 0.into()); - - // Test 3 - validator_slashes_handle(&bob).pop(&mut storage).unwrap(); - let mut redel_bonds = EagerRedelegatedBondsMap::default(); - redel_bonds.insert( - alice.clone(), - BTreeMap::from_iter([(Epoch(1), token::Amount::from(5))]), - ); - let res = compute_bond_at_epoch( - &storage, - ¶ms, - &bob, - 12.into(), - 3.into(), - 23.into(), - Some(&redel_bonds), - ) - .unwrap(); - - pretty_assertions::assert_eq!(res, 23.into()); - - // Test 4 - validator_slashes_handle(&bob) - .push( - &mut storage, - Slash { - epoch: 4.into(), - block_height: 0, - r#type: SlashType::DuplicateVote, - rate: Dec::one(), - }, - ) - .unwrap(); - let res = compute_bond_at_epoch( - &storage, - ¶ms, - &bob, - 12.into(), - 3.into(), - 23.into(), - Some(&redel_bonds), - ) - .unwrap(); - - pretty_assertions::assert_eq!(res, 0.into()); - - // Test 5 - validator_slashes_handle(&bob).pop(&mut storage).unwrap(); - validator_slashes_handle(&alice) - .push( - &mut storage, - Slash { - epoch: 6.into(), - block_height: 0, - r#type: SlashType::DuplicateVote, - rate: Dec::one(), - }, - ) - .unwrap(); - let res = compute_bond_at_epoch( - &storage, - ¶ms, - &bob, - 12.into(), - 3.into(), - 23.into(), - Some(&redel_bonds), - ) - .unwrap(); - - pretty_assertions::assert_eq!(res, 23.into()); - - // Test 6 - validator_slashes_handle(&alice).pop(&mut storage).unwrap(); - validator_slashes_handle(&alice) - .push( - &mut storage, - Slash { - epoch: 4.into(), - block_height: 0, - r#type: SlashType::DuplicateVote, - rate: Dec::one(), - }, - ) - .unwrap(); - let res = compute_bond_at_epoch( - &storage, - ¶ms, - &bob, - 18.into(), - 9.into(), - 23.into(), - Some(&redel_bonds), - ) - .unwrap(); - - pretty_assertions::assert_eq!(res, 18.into()); -} - -/// `computeSlashBondAtEpochTest` -#[test] -fn test_compute_slash_bond_at_epoch() { - let mut storage = TestWlStorage::default(); - let params = OwnedPosParams { - pipeline_len: 2, - unbonding_len: 4, - cubic_slashing_window_length: 1, - ..Default::default() - }; - let alice = established_address_1(); - let bob = established_address_2(); - - let current_epoch = Epoch(20); - let infraction_epoch = - current_epoch - params.slash_processing_epoch_offset(); - - let redelegated_bond = BTreeMap::from_iter([( - alice, - BTreeMap::from_iter([(infraction_epoch - 4, token::Amount::from(10))]), - )]); - - // Test 1 - let res = compute_slash_bond_at_epoch( - &storage, - ¶ms, - &bob, - current_epoch.next(), - infraction_epoch, - infraction_epoch - 2, - 30.into(), - Some(&Default::default()), - Dec::one(), - ) - .unwrap(); - - pretty_assertions::assert_eq!(res, 30.into()); - - // Test 2 - let res = compute_slash_bond_at_epoch( - &storage, - ¶ms, - &bob, - current_epoch.next(), - infraction_epoch, - infraction_epoch - 2, - 30.into(), - Some(&redelegated_bond), - Dec::one(), - ) - .unwrap(); - - pretty_assertions::assert_eq!(res, 30.into()); - - // Test 3 - validator_slashes_handle(&bob) - .push( - &mut storage, - Slash { - epoch: infraction_epoch.prev(), - block_height: 0, - r#type: SlashType::DuplicateVote, - rate: Dec::one(), - }, - ) - .unwrap(); - let res = compute_slash_bond_at_epoch( - &storage, - ¶ms, - &bob, - current_epoch.next(), - infraction_epoch, - infraction_epoch - 2, - 30.into(), - Some(&Default::default()), - Dec::one(), - ) - .unwrap(); - - pretty_assertions::assert_eq!(res, 0.into()); - - // Test 4 - let res = compute_slash_bond_at_epoch( - &storage, - ¶ms, - &bob, - current_epoch.next(), - infraction_epoch, - infraction_epoch - 2, - 30.into(), - Some(&redelegated_bond), - Dec::one(), - ) - .unwrap(); - - pretty_assertions::assert_eq!(res, 0.into()); -} - -/// `computeNewRedelegatedUnbondsTest` -#[test] -fn test_compute_new_redelegated_unbonds() { - let mut storage = TestWlStorage::default(); - let alice = established_address_1(); - let bob = established_address_2(); - - let key = Key::parse("testing").unwrap(); - let redelegated_bonds = NestedMap::::open(key); - - // Populate the lazy and eager maps - let (ep1, ep2, ep4, ep5, ep6, ep7) = - (Epoch(1), Epoch(2), Epoch(4), Epoch(5), Epoch(6), Epoch(7)); - let keys_and_values = vec![ - (ep5, alice.clone(), ep2, 1), - (ep5, alice.clone(), ep4, 1), - (ep7, alice.clone(), ep2, 1), - (ep7, alice.clone(), ep4, 1), - (ep5, bob.clone(), ep1, 1), - (ep5, bob.clone(), ep4, 2), - (ep7, bob.clone(), ep1, 1), - (ep7, bob.clone(), ep4, 2), - ]; - let mut eager_map = BTreeMap::::new(); - for (outer_ep, address, inner_ep, amount) in keys_and_values { - redelegated_bonds - .at(&outer_ep) - .at(&address) - .insert(&mut storage, inner_ep, token::Amount::from(amount)) - .unwrap(); - eager_map - .entry(outer_ep) - .or_default() - .entry(address.clone()) - .or_default() - .insert(inner_ep, token::Amount::from(amount)); - } - - // Different ModifiedRedelegation objects for testing - let empty_mr = ModifiedRedelegation::default(); - let all_mr = ModifiedRedelegation { - epoch: Some(ep7), - validators_to_remove: BTreeSet::from_iter([alice.clone(), bob.clone()]), - validator_to_modify: None, - epochs_to_remove: Default::default(), - epoch_to_modify: None, - new_amount: None, - }; - let mod_val_mr = ModifiedRedelegation { - epoch: Some(ep7), - validators_to_remove: BTreeSet::from_iter([alice.clone()]), - validator_to_modify: None, - epochs_to_remove: Default::default(), - epoch_to_modify: None, - new_amount: None, - }; - let mod_val_partial_mr = ModifiedRedelegation { - epoch: Some(ep7), - validators_to_remove: BTreeSet::from_iter([alice.clone(), bob.clone()]), - validator_to_modify: Some(bob.clone()), - epochs_to_remove: BTreeSet::from_iter([ep1]), - epoch_to_modify: None, - new_amount: None, - }; - let mod_epoch_partial_mr = ModifiedRedelegation { - epoch: Some(ep7), - validators_to_remove: BTreeSet::from_iter([alice, bob.clone()]), - validator_to_modify: Some(bob.clone()), - epochs_to_remove: BTreeSet::from_iter([ep1, ep4]), - epoch_to_modify: Some(ep4), - new_amount: Some(token::Amount::from(1)), - }; - - // Test case 1 - let res = compute_new_redelegated_unbonds( - &storage, - &redelegated_bonds, - &Default::default(), - &empty_mr, - ) - .unwrap(); - assert_eq!(res, Default::default()); - - let set5 = BTreeSet::::from_iter([ep5]); - let set56 = BTreeSet::::from_iter([ep5, ep6]); - - // Test case 2 - let res = compute_new_redelegated_unbonds( - &storage, - &redelegated_bonds, - &set5, - &empty_mr, - ) - .unwrap(); - let mut exp_res = eager_map.clone(); - exp_res.remove(&ep7); - assert_eq!(res, exp_res); - - // Test case 3 - let res = compute_new_redelegated_unbonds( - &storage, - &redelegated_bonds, - &set56, - &empty_mr, - ) - .unwrap(); - assert_eq!(res, exp_res); - - // Test case 4 - println!("\nTEST CASE 4\n"); - let res = compute_new_redelegated_unbonds( - &storage, - &redelegated_bonds, - &set56, - &all_mr, - ) - .unwrap(); - assert_eq!(res, eager_map); - - // Test case 5 - let res = compute_new_redelegated_unbonds( - &storage, - &redelegated_bonds, - &set56, - &mod_val_mr, - ) - .unwrap(); - exp_res = eager_map.clone(); - exp_res.entry(ep7).or_default().remove(&bob); - assert_eq!(res, exp_res); - - // Test case 6 - let res = compute_new_redelegated_unbonds( - &storage, - &redelegated_bonds, - &set56, - &mod_val_partial_mr, - ) - .unwrap(); - exp_res = eager_map.clone(); - exp_res - .entry(ep7) - .or_default() - .entry(bob.clone()) - .or_default() - .remove(&ep4); - assert_eq!(res, exp_res); - - // Test case 7 - let res = compute_new_redelegated_unbonds( - &storage, - &redelegated_bonds, - &set56, - &mod_epoch_partial_mr, - ) - .unwrap(); - exp_res - .entry(ep7) - .or_default() - .entry(bob) - .or_default() - .insert(ep4, token::Amount::from(1)); - assert_eq!(res, exp_res); -} - -/// `applyListSlashesTest` -#[test] -fn test_apply_list_slashes() { - let init_epoch = Epoch(2); - let params = OwnedPosParams { - unbonding_len: 4, - ..Default::default() - }; - // let unbonding_len = 4u64; - // let cubic_offset = 1u64; - - let slash1 = Slash { - epoch: init_epoch, - block_height: Default::default(), - r#type: SlashType::DuplicateVote, - rate: Dec::one(), - }; - let slash2 = Slash { - epoch: init_epoch - + params.unbonding_len - + params.cubic_slashing_window_length - + 1u64, - block_height: Default::default(), - r#type: SlashType::DuplicateVote, - rate: Dec::one(), - }; - - let list1 = vec![slash1.clone()]; - let list2 = vec![slash1.clone(), slash2.clone()]; - let list3 = vec![slash1.clone(), slash1.clone()]; - let list4 = vec![slash1.clone(), slash1, slash2]; - - let res = apply_list_slashes(¶ms, &[], token::Amount::from(100)); - assert_eq!(res, token::Amount::from(100)); - - let res = apply_list_slashes(¶ms, &list1, token::Amount::from(100)); - assert_eq!(res, token::Amount::zero()); - - let res = apply_list_slashes(¶ms, &list2, token::Amount::from(100)); - assert_eq!(res, token::Amount::zero()); - - let res = apply_list_slashes(¶ms, &list3, token::Amount::from(100)); - assert_eq!(res, token::Amount::zero()); - - let res = apply_list_slashes(¶ms, &list4, token::Amount::from(100)); - assert_eq!(res, token::Amount::zero()); -} - -/// `computeSlashableAmountTest` -#[test] -fn test_compute_slashable_amount() { - let init_epoch = Epoch(2); - let params = OwnedPosParams { - unbonding_len: 4, - ..Default::default() - }; - - let slash1 = Slash { - epoch: init_epoch - + params.unbonding_len - + params.cubic_slashing_window_length, - block_height: Default::default(), - r#type: SlashType::DuplicateVote, - rate: Dec::one(), - }; - - let slash2 = Slash { - epoch: init_epoch - + params.unbonding_len - + params.cubic_slashing_window_length - + 1u64, - block_height: Default::default(), - r#type: SlashType::DuplicateVote, - rate: Dec::one(), - }; - - let test_map = vec![(init_epoch, token::Amount::from(50))] - .into_iter() - .collect::>(); - - let res = compute_slashable_amount( - ¶ms, - &slash1, - token::Amount::from(100), - &BTreeMap::new(), - ); - assert_eq!(res, token::Amount::from(100)); - - let res = compute_slashable_amount( - ¶ms, - &slash2, - token::Amount::from(100), - &test_map, - ); - assert_eq!(res, token::Amount::from(50)); - - let res = compute_slashable_amount( - ¶ms, - &slash1, - token::Amount::from(100), - &test_map, - ); - assert_eq!(res, token::Amount::from(100)); -} - -/// `foldAndSlashRedelegatedBondsMapTest` -#[test] -fn test_fold_and_slash_redelegated_bonds() { - let mut storage = TestWlStorage::default(); - let params = OwnedPosParams { - unbonding_len: 4, - ..Default::default() - }; - let start_epoch = Epoch(7); - - let alice = established_address_1(); - let bob = established_address_2(); - - println!("\n\nAlice: {}", alice); - println!("Bob: {}\n", bob); - - let test_slash = Slash { - epoch: Default::default(), - block_height: Default::default(), - r#type: SlashType::DuplicateVote, - rate: Dec::one(), - }; - - let test_data = vec![ - (alice.clone(), vec![(2, 1), (4, 1)]), - (bob, vec![(1, 1), (4, 2)]), - ]; - let mut eager_redel_bonds = EagerRedelegatedBondsMap::default(); - for (address, pair) in test_data { - for (epoch, amount) in pair { - eager_redel_bonds - .entry(address.clone()) - .or_default() - .insert(Epoch(epoch), token::Amount::from(amount)); - } - } - - // Test case 1 - let res = fold_and_slash_redelegated_bonds( - &storage, - ¶ms, - &eager_redel_bonds, - start_epoch, - &[], - |_| true, - ); - assert_eq!( - res, - FoldRedelegatedBondsResult { - total_redelegated: token::Amount::from(5), - total_after_slashing: token::Amount::from(5), - } - ); - - // Test case 2 - let res = fold_and_slash_redelegated_bonds( - &storage, - ¶ms, - &eager_redel_bonds, - start_epoch, - &[test_slash], - |_| true, - ); - assert_eq!( - res, - FoldRedelegatedBondsResult { - total_redelegated: token::Amount::from(5), - total_after_slashing: token::Amount::zero(), - } - ); - - // Test case 3 - let alice_slash = Slash { - epoch: Epoch(6), - block_height: Default::default(), - r#type: SlashType::DuplicateVote, - rate: Dec::one(), - }; - validator_slashes_handle(&alice) - .push(&mut storage, alice_slash) - .unwrap(); - - let res = fold_and_slash_redelegated_bonds( - &storage, - ¶ms, - &eager_redel_bonds, - start_epoch, - &[], - |_| true, - ); - assert_eq!( - res, - FoldRedelegatedBondsResult { - total_redelegated: token::Amount::from(5), - total_after_slashing: token::Amount::from(3), - } - ); -} - -/// `slashRedelegationTest` -#[test] -fn test_slash_redelegation() { - let mut storage = TestWlStorage::default(); - let params = OwnedPosParams { - unbonding_len: 4, - ..Default::default() - }; - let alice = established_address_1(); - - let total_redelegated_unbonded = - validator_total_redelegated_unbonded_handle(&alice); - total_redelegated_unbonded - .at(&Epoch(13)) - .at(&Epoch(10)) - .at(&alice) - .insert(&mut storage, Epoch(7), token::Amount::from(2)) - .unwrap(); - - let slashes = validator_slashes_handle(&alice); - - let mut slashed_amounts_map = BTreeMap::from_iter([ - (Epoch(15), token::Amount::zero()), - (Epoch(16), token::Amount::zero()), - ]); - let empty_slash_amounts = slashed_amounts_map.clone(); - - // Test case 1 - slash_redelegation( - &storage, - ¶ms, - token::Amount::from(7), - Epoch(7), - Epoch(10), - &alice, - Epoch(14), - &slashes, - &total_redelegated_unbonded, - Dec::one(), - &mut slashed_amounts_map, - ) - .unwrap(); - assert_eq!( - slashed_amounts_map, - BTreeMap::from_iter([ - (Epoch(15), token::Amount::from(5)), - (Epoch(16), token::Amount::from(5)), - ]) - ); - - // Test case 2 - slashed_amounts_map = empty_slash_amounts.clone(); - slash_redelegation( - &storage, - ¶ms, - token::Amount::from(7), - Epoch(7), - Epoch(11), - &alice, - Epoch(14), - &slashes, - &total_redelegated_unbonded, - Dec::one(), - &mut slashed_amounts_map, - ) - .unwrap(); - assert_eq!( - slashed_amounts_map, - BTreeMap::from_iter([ - (Epoch(15), token::Amount::from(7)), - (Epoch(16), token::Amount::from(7)), - ]) - ); - - // Test case 3 - slashed_amounts_map = BTreeMap::from_iter([ - (Epoch(15), token::Amount::from(2)), - (Epoch(16), token::Amount::from(3)), - ]); - slash_redelegation( - &storage, - ¶ms, - token::Amount::from(7), - Epoch(7), - Epoch(10), - &alice, - Epoch(14), - &slashes, - &total_redelegated_unbonded, - Dec::one(), - &mut slashed_amounts_map, - ) - .unwrap(); - assert_eq!( - slashed_amounts_map, - BTreeMap::from_iter([ - (Epoch(15), token::Amount::from(7)), - (Epoch(16), token::Amount::from(8)), - ]) - ); - - // Test case 4 - slashes - .push( - &mut storage, - Slash { - epoch: Epoch(8), - block_height: Default::default(), - r#type: SlashType::DuplicateVote, - rate: Dec::one(), - }, - ) - .unwrap(); - slashed_amounts_map = empty_slash_amounts.clone(); - slash_redelegation( - &storage, - ¶ms, - token::Amount::from(7), - Epoch(7), - Epoch(10), - &alice, - Epoch(14), - &slashes, - &total_redelegated_unbonded, - Dec::one(), - &mut slashed_amounts_map, - ) - .unwrap(); - assert_eq!(slashed_amounts_map, empty_slash_amounts); - - // Test case 5 - slashes.pop(&mut storage).unwrap(); - slashes - .push( - &mut storage, - Slash { - epoch: Epoch(9), - block_height: Default::default(), - r#type: SlashType::DuplicateVote, - rate: Dec::one(), - }, - ) - .unwrap(); - slash_redelegation( - &storage, - ¶ms, - token::Amount::from(7), - Epoch(7), - Epoch(10), - &alice, - Epoch(14), - &slashes, - &total_redelegated_unbonded, - Dec::one(), - &mut slashed_amounts_map, - ) - .unwrap(); - assert_eq!(slashed_amounts_map, empty_slash_amounts); - - // Test case 6 - slashes - .push( - &mut storage, - Slash { - epoch: Epoch(8), - block_height: Default::default(), - r#type: SlashType::DuplicateVote, - rate: Dec::one(), - }, - ) - .unwrap(); - slash_redelegation( - &storage, - ¶ms, - token::Amount::from(7), - Epoch(7), - Epoch(10), - &alice, - Epoch(14), - &slashes, - &total_redelegated_unbonded, - Dec::one(), - &mut slashed_amounts_map, - ) - .unwrap(); - assert_eq!(slashed_amounts_map, empty_slash_amounts); -} - -/// `slashValidatorRedelegationTest` -#[test] -fn test_slash_validator_redelegation() { - let mut storage = TestWlStorage::default(); - let params = OwnedPosParams { - unbonding_len: 4, - ..Default::default() - }; - let gov_params = namada_core::ledger::governance::parameters::GovernanceParameters::default(); - gov_params.init_storage(&mut storage).unwrap(); - write_pos_params(&mut storage, ¶ms).unwrap(); - - let alice = established_address_1(); - let bob = established_address_2(); - - let total_redelegated_unbonded = - validator_total_redelegated_unbonded_handle(&alice); - total_redelegated_unbonded - .at(&Epoch(13)) - .at(&Epoch(10)) - .at(&alice) - .insert(&mut storage, Epoch(7), token::Amount::from(2)) - .unwrap(); - - let outgoing_redelegations = - validator_outgoing_redelegations_handle(&alice).at(&bob); - - let slashes = validator_slashes_handle(&alice); - - let mut slashed_amounts_map = BTreeMap::from_iter([ - (Epoch(15), token::Amount::zero()), - (Epoch(16), token::Amount::zero()), - ]); - let empty_slash_amounts = slashed_amounts_map.clone(); - - // Test case 1 - slash_validator_redelegation( - &storage, - ¶ms, - &alice, - Epoch(14), - &outgoing_redelegations, - &slashes, - &total_redelegated_unbonded, - Dec::one(), - &mut slashed_amounts_map, - ) - .unwrap(); - assert_eq!(slashed_amounts_map, empty_slash_amounts); - - // Test case 2 - total_redelegated_unbonded - .remove_all(&mut storage, &Epoch(13)) - .unwrap(); - slash_validator_redelegation( - &storage, - ¶ms, - &alice, - Epoch(14), - &outgoing_redelegations, - &slashes, - &total_redelegated_unbonded, - Dec::one(), - &mut slashed_amounts_map, - ) - .unwrap(); - assert_eq!(slashed_amounts_map, empty_slash_amounts); - - // Test case 3 - total_redelegated_unbonded - .at(&Epoch(13)) - .at(&Epoch(10)) - .at(&alice) - .insert(&mut storage, Epoch(7), token::Amount::from(2)) - .unwrap(); - outgoing_redelegations - .at(&Epoch(6)) - .insert(&mut storage, Epoch(8), token::Amount::from(7)) - .unwrap(); - slash_validator_redelegation( - &storage, - ¶ms, - &alice, - Epoch(14), - &outgoing_redelegations, - &slashes, - &total_redelegated_unbonded, - Dec::one(), - &mut slashed_amounts_map, - ) - .unwrap(); - assert_eq!( - slashed_amounts_map, - BTreeMap::from_iter([ - (Epoch(15), token::Amount::from(7)), - (Epoch(16), token::Amount::from(7)), - ]) - ); - - // Test case 4 - slashed_amounts_map = empty_slash_amounts.clone(); - outgoing_redelegations - .remove_all(&mut storage, &Epoch(6)) - .unwrap(); - outgoing_redelegations - .at(&Epoch(7)) - .insert(&mut storage, Epoch(8), token::Amount::from(7)) - .unwrap(); - slash_validator_redelegation( - &storage, - ¶ms, - &alice, - Epoch(14), - &outgoing_redelegations, - &slashes, - &total_redelegated_unbonded, - Dec::one(), - &mut slashed_amounts_map, - ) - .unwrap(); - assert_eq!( - slashed_amounts_map, - BTreeMap::from_iter([ - (Epoch(15), token::Amount::from(5)), - (Epoch(16), token::Amount::from(5)), - ]) - ); - - // Test case 5 - slashed_amounts_map = BTreeMap::from_iter([ - (Epoch(15), token::Amount::from(2)), - (Epoch(16), token::Amount::from(3)), - ]); - slash_validator_redelegation( - &storage, - ¶ms, - &alice, - Epoch(14), - &outgoing_redelegations, - &slashes, - &total_redelegated_unbonded, - Dec::one(), - &mut slashed_amounts_map, - ) - .unwrap(); - assert_eq!( - slashed_amounts_map, - BTreeMap::from_iter([ - (Epoch(15), token::Amount::from(7)), - (Epoch(16), token::Amount::from(8)), - ]) - ); - - // Test case 6 - slashed_amounts_map = empty_slash_amounts.clone(); - slashes - .push( - &mut storage, - Slash { - epoch: Epoch(8), - block_height: Default::default(), - r#type: SlashType::DuplicateVote, - rate: Dec::one(), - }, - ) - .unwrap(); - slash_validator_redelegation( - &storage, - ¶ms, - &alice, - Epoch(14), - &outgoing_redelegations, - &slashes, - &total_redelegated_unbonded, - Dec::one(), - &mut slashed_amounts_map, - ) - .unwrap(); - assert_eq!(slashed_amounts_map, empty_slash_amounts); -} - -/// `slashValidatorTest` -#[test] -fn test_slash_validator() { - let mut storage = TestWlStorage::default(); - let params = OwnedPosParams { - unbonding_len: 4, - ..Default::default() - }; - let gov_params = namada_core::ledger::governance::parameters::GovernanceParameters::default(); - gov_params.init_storage(&mut storage).unwrap(); - write_pos_params(&mut storage, ¶ms).unwrap(); - - let alice = established_address_1(); - let bob = established_address_2(); - - let total_bonded = total_bonded_handle(&bob); - let total_unbonded = total_unbonded_handle(&bob); - let total_redelegated_bonded = - validator_total_redelegated_bonded_handle(&bob); - let total_redelegated_unbonded = - validator_total_redelegated_unbonded_handle(&bob); - - let infraction_stake = token::Amount::from(23); - - let initial_stakes = BTreeMap::from_iter([ - (Epoch(11), infraction_stake), - (Epoch(12), infraction_stake), - (Epoch(13), infraction_stake), - ]); - let mut exp_res = initial_stakes.clone(); - - let current_epoch = Epoch(10); - let infraction_epoch = - current_epoch - params.slash_processing_epoch_offset(); - let processing_epoch = current_epoch.next(); - let slash_rate = Dec::one(); - - // Test case 1 - println!("\nTEST 1:"); - - total_bonded - .set(&mut storage, 23.into(), infraction_epoch - 2, 0) - .unwrap(); - let res = slash_validator( - &storage, - ¶ms, - &bob, - slash_rate, - processing_epoch, - &Default::default(), - ) - .unwrap(); - assert_eq!(res, exp_res); - - // Test case 2 - println!("\nTEST 2:"); - total_bonded - .set(&mut storage, 17.into(), infraction_epoch - 2, 0) - .unwrap(); - total_unbonded - .at(&(current_epoch + params.pipeline_len)) - .insert(&mut storage, infraction_epoch - 2, 6.into()) - .unwrap(); - let res = slash_validator( - &storage, - ¶ms, - &bob, - slash_rate, - processing_epoch, - &Default::default(), - ) - .unwrap(); - exp_res.insert(Epoch(12), 17.into()); - exp_res.insert(Epoch(13), 17.into()); - assert_eq!(res, exp_res); - - // Test case 3 - println!("\nTEST 3:"); - total_redelegated_bonded - .at(&infraction_epoch.prev()) - .at(&alice) - .insert(&mut storage, Epoch(2), 5.into()) - .unwrap(); - total_redelegated_bonded - .at(&infraction_epoch.prev()) - .at(&alice) - .insert(&mut storage, Epoch(3), 1.into()) - .unwrap(); - - let res = slash_validator( - &storage, - ¶ms, - &bob, - slash_rate, - processing_epoch, - &Default::default(), - ) - .unwrap(); - assert_eq!(res, exp_res); - - // Test case 4 - println!("\nTEST 4:"); - total_unbonded_handle(&bob) - .at(&(current_epoch + params.pipeline_len)) - .remove(&mut storage, &(infraction_epoch - 2)) - .unwrap(); - total_unbonded_handle(&bob) - .at(&(current_epoch + params.pipeline_len)) - .insert(&mut storage, infraction_epoch - 1, 6.into()) - .unwrap(); - total_redelegated_unbonded - .at(&(current_epoch + params.pipeline_len)) - .at(&infraction_epoch.prev()) - .at(&alice) - .insert(&mut storage, Epoch(2), 5.into()) - .unwrap(); - total_redelegated_unbonded - .at(&(current_epoch + params.pipeline_len)) - .at(&infraction_epoch.prev()) - .at(&alice) - .insert(&mut storage, Epoch(3), 1.into()) - .unwrap(); - let res = slash_validator( - &storage, - ¶ms, - &bob, - slash_rate, - processing_epoch, - &Default::default(), - ) - .unwrap(); - assert_eq!(res, exp_res); - - // Test case 5 - println!("\nTEST 5:"); - total_bonded_handle(&bob) - .set(&mut storage, 19.into(), infraction_epoch - 2, 0) - .unwrap(); - total_unbonded_handle(&bob) - .at(&(current_epoch + params.pipeline_len)) - .insert(&mut storage, infraction_epoch - 1, 4.into()) - .unwrap(); - total_redelegated_bonded - .at(¤t_epoch) - .at(&alice) - .insert(&mut storage, Epoch(2), token::Amount::from(1)) - .unwrap(); - total_redelegated_unbonded - .at(&(current_epoch + params.pipeline_len)) - .at(&infraction_epoch.prev()) - .at(&alice) - .remove(&mut storage, &Epoch(3)) - .unwrap(); - total_redelegated_unbonded - .at(&(current_epoch + params.pipeline_len)) - .at(&infraction_epoch.prev()) - .at(&alice) - .insert(&mut storage, Epoch(2), 4.into()) - .unwrap(); - let res = slash_validator( - &storage, - ¶ms, - &bob, - slash_rate, - processing_epoch, - &Default::default(), - ) - .unwrap(); - exp_res.insert(Epoch(12), 19.into()); - exp_res.insert(Epoch(13), 19.into()); - assert_eq!(res, exp_res); - - // Test case 6 - println!("\nTEST 6:"); - total_unbonded_handle(&bob) - .remove_all(&mut storage, &(current_epoch + params.pipeline_len)) - .unwrap(); - total_redelegated_unbonded - .remove_all(&mut storage, &(current_epoch + params.pipeline_len)) - .unwrap(); - total_redelegated_bonded - .remove_all(&mut storage, ¤t_epoch) - .unwrap(); - total_bonded_handle(&bob) - .set(&mut storage, 23.into(), infraction_epoch - 2, 0) - .unwrap(); - total_bonded_handle(&bob) - .set(&mut storage, 6.into(), current_epoch, 0) - .unwrap(); - - let res = slash_validator( - &storage, - ¶ms, - &bob, - slash_rate, - processing_epoch, - &Default::default(), - ) - .unwrap(); - exp_res = initial_stakes; - assert_eq!(res, exp_res); - - // Test case 7 - println!("\nTEST 7:"); - total_bonded - .get_data_handler() - .remove(&mut storage, ¤t_epoch) - .unwrap(); - total_unbonded - .at(¤t_epoch.next()) - .insert(&mut storage, current_epoch, 6.into()) - .unwrap(); - let res = slash_validator( - &storage, - ¶ms, - &bob, - slash_rate, - processing_epoch, - &Default::default(), - ) - .unwrap(); - assert_eq!(res, exp_res); - - // Test case 8 - println!("\nTEST 8:"); - total_bonded - .get_data_handler() - .insert(&mut storage, current_epoch, 3.into()) - .unwrap(); - total_unbonded - .at(¤t_epoch.next()) - .insert(&mut storage, current_epoch, 3.into()) - .unwrap(); - let res = slash_validator( - &storage, - ¶ms, - &bob, - slash_rate, - processing_epoch, - &Default::default(), - ) - .unwrap(); - assert_eq!(res, exp_res); - - // Test case 9 - println!("\nTEST 9:"); - total_unbonded - .remove_all(&mut storage, ¤t_epoch.next()) - .unwrap(); - total_bonded - .set(&mut storage, 6.into(), current_epoch, 0) - .unwrap(); - total_redelegated_bonded - .at(¤t_epoch) - .at(&alice) - .insert(&mut storage, 2.into(), 5.into()) - .unwrap(); - total_redelegated_bonded - .at(¤t_epoch) - .at(&alice) - .insert(&mut storage, 3.into(), 1.into()) - .unwrap(); - let res = slash_validator( - &storage, - ¶ms, - &bob, - slash_rate, - processing_epoch, - &Default::default(), - ) - .unwrap(); - assert_eq!(res, exp_res); - - // Test case 10 - println!("\nTEST 10:"); - total_redelegated_bonded - .remove_all(&mut storage, ¤t_epoch) - .unwrap(); - total_bonded - .get_data_handler() - .remove(&mut storage, ¤t_epoch) - .unwrap(); - total_redelegated_unbonded - .at(¤t_epoch.next()) - .at(¤t_epoch) - .at(&alice) - .insert(&mut storage, 2.into(), 5.into()) - .unwrap(); - total_redelegated_unbonded - .at(¤t_epoch.next()) - .at(¤t_epoch) - .at(&alice) - .insert(&mut storage, 3.into(), 1.into()) - .unwrap(); - let res = slash_validator( - &storage, - ¶ms, - &bob, - slash_rate, - processing_epoch, - &Default::default(), - ) - .unwrap(); - assert_eq!(res, exp_res); - - // Test case 11 - println!("\nTEST 11:"); - total_bonded - .set(&mut storage, 2.into(), current_epoch, 0) - .unwrap(); - total_redelegated_unbonded - .at(¤t_epoch.next()) - .at(¤t_epoch) - .at(&alice) - .insert(&mut storage, 2.into(), 4.into()) - .unwrap(); - total_redelegated_unbonded - .at(¤t_epoch.next()) - .at(¤t_epoch) - .at(&alice) - .remove(&mut storage, &3.into()) - .unwrap(); - total_redelegated_bonded - .at(¤t_epoch) - .at(&alice) - .insert(&mut storage, 2.into(), 1.into()) - .unwrap(); - total_redelegated_bonded - .at(¤t_epoch) - .at(&alice) - .insert(&mut storage, 3.into(), 1.into()) - .unwrap(); - let res = slash_validator( - &storage, - ¶ms, - &bob, - slash_rate, - processing_epoch, - &Default::default(), - ) - .unwrap(); - assert_eq!(res, exp_res); - - // Test case 12 - println!("\nTEST 12:"); - total_bonded - .set(&mut storage, 6.into(), current_epoch, 0) - .unwrap(); - total_bonded - .set(&mut storage, 2.into(), current_epoch.next(), 0) - .unwrap(); - total_redelegated_bonded - .remove_all(&mut storage, ¤t_epoch) - .unwrap(); - total_redelegated_bonded - .at(¤t_epoch.next()) - .at(&alice) - .insert(&mut storage, 2.into(), 1.into()) - .unwrap(); - total_redelegated_bonded - .at(¤t_epoch.next()) - .at(&alice) - .insert(&mut storage, 3.into(), 1.into()) - .unwrap(); - let res = slash_validator( - &storage, - ¶ms, - &bob, - slash_rate, - processing_epoch, - &Default::default(), - ) - .unwrap(); - assert_eq!(res, exp_res); - - // Test case 13 - println!("\nTEST 13:"); - validator_slashes_handle(&bob) - .push( - &mut storage, - Slash { - epoch: infraction_epoch.prev(), - block_height: 0, - r#type: SlashType::DuplicateVote, - rate: Dec::one(), - }, - ) - .unwrap(); - total_redelegated_unbonded - .remove_all(&mut storage, ¤t_epoch.next()) - .unwrap(); - total_bonded - .get_data_handler() - .remove(&mut storage, ¤t_epoch.next()) - .unwrap(); - total_redelegated_bonded - .remove_all(&mut storage, ¤t_epoch.next()) - .unwrap(); - let res = slash_validator( - &storage, - ¶ms, - &bob, - slash_rate, - processing_epoch, - &Default::default(), - ) - .unwrap(); - exp_res.insert(Epoch(11), 0.into()); - exp_res.insert(Epoch(12), 0.into()); - exp_res.insert(Epoch(13), 0.into()); - assert_eq!(res, exp_res); -} - -/// `computeAmountAfterSlashingUnbondTest` -#[test] -fn compute_amount_after_slashing_unbond_test() { - let mut storage = TestWlStorage::default(); - let params = OwnedPosParams { - unbonding_len: 4, - ..Default::default() - }; - - // Test data - let alice = established_address_1(); - let bob = established_address_2(); - let unbonds: BTreeMap = BTreeMap::from_iter([ - ((Epoch(2)), token::Amount::from(5)), - ((Epoch(4)), token::Amount::from(6)), - ]); - let redelegated_unbonds: EagerRedelegatedUnbonds = BTreeMap::from_iter([( - Epoch(2), - BTreeMap::from_iter([( - alice.clone(), - BTreeMap::from_iter([(Epoch(1), token::Amount::from(1))]), - )]), - )]); - - // Test case 1 - let slashes = vec![]; - let result = compute_amount_after_slashing_unbond( - &storage, - ¶ms, - &unbonds, - &redelegated_unbonds, - slashes, - ) - .unwrap(); - assert_eq!(result.sum, 11.into()); - itertools::assert_equal( - result.epoch_map, - [(2.into(), 5.into()), (4.into(), 6.into())], - ); - - // Test case 2 - let bob_slash = Slash { - epoch: Epoch(5), - block_height: Default::default(), - r#type: SlashType::DuplicateVote, - rate: Dec::one(), - }; - let slashes = vec![bob_slash.clone()]; - validator_slashes_handle(&bob) - .push(&mut storage, bob_slash) - .unwrap(); - let result = compute_amount_after_slashing_unbond( - &storage, - ¶ms, - &unbonds, - &redelegated_unbonds, - slashes, - ) - .unwrap(); - assert_eq!(result.sum, 0.into()); - itertools::assert_equal( - result.epoch_map, - [(2.into(), 0.into()), (4.into(), 0.into())], - ); - - // Test case 3 - let alice_slash = Slash { - epoch: Epoch(0), - block_height: Default::default(), - r#type: SlashType::DuplicateVote, - rate: Dec::one(), - }; - let slashes = vec![alice_slash.clone()]; - validator_slashes_handle(&alice) - .push(&mut storage, alice_slash) - .unwrap(); - validator_slashes_handle(&bob).pop(&mut storage).unwrap(); - let result = compute_amount_after_slashing_unbond( - &storage, - ¶ms, - &unbonds, - &redelegated_unbonds, - slashes, - ) - .unwrap(); - assert_eq!(result.sum, 11.into()); - itertools::assert_equal( - result.epoch_map, - [(2.into(), 5.into()), (4.into(), 6.into())], - ); - - // Test case 4 - let alice_slash = Slash { - epoch: Epoch(1), - block_height: Default::default(), - r#type: SlashType::DuplicateVote, - rate: Dec::one(), - }; - let slashes = vec![alice_slash.clone()]; - validator_slashes_handle(&alice).pop(&mut storage).unwrap(); - validator_slashes_handle(&alice) - .push(&mut storage, alice_slash) - .unwrap(); - let result = compute_amount_after_slashing_unbond( - &storage, - ¶ms, - &unbonds, - &redelegated_unbonds, - slashes, - ) - .unwrap(); - assert_eq!(result.sum, 10.into()); - itertools::assert_equal( - result.epoch_map, - [(2.into(), 4.into()), (4.into(), 6.into())], - ); -} - -/// `computeAmountAfterSlashingWithdrawTest` -#[test] -fn compute_amount_after_slashing_withdraw_test() { - let mut storage = TestWlStorage::default(); - let params = OwnedPosParams { - unbonding_len: 4, - ..Default::default() - }; - - // Test data - let alice = established_address_1(); - let bob = established_address_2(); - let unbonds_and_redelegated_unbonds: BTreeMap< - (Epoch, Epoch), - (token::Amount, EagerRedelegatedBondsMap), - > = BTreeMap::from_iter([ - ( - (Epoch(2), Epoch(20)), - ( - // unbond - token::Amount::from(5), - // redelegations - BTreeMap::from_iter([( - alice.clone(), - BTreeMap::from_iter([(Epoch(1), token::Amount::from(1))]), - )]), - ), - ), - ( - (Epoch(4), Epoch(20)), - ( - // unbond - token::Amount::from(6), - // redelegations - BTreeMap::default(), - ), - ), - ]); - - // Test case 1 - let slashes = vec![]; - let result = compute_amount_after_slashing_withdraw( - &storage, - ¶ms, - &unbonds_and_redelegated_unbonds, - slashes, - ) - .unwrap(); - assert_eq!(result.sum, 11.into()); - itertools::assert_equal( - result.epoch_map, - [(2.into(), 5.into()), (4.into(), 6.into())], - ); - - // Test case 2 - let bob_slash = Slash { - epoch: Epoch(5), - block_height: Default::default(), - r#type: SlashType::DuplicateVote, - rate: Dec::one(), - }; - let slashes = vec![bob_slash.clone()]; - validator_slashes_handle(&bob) - .push(&mut storage, bob_slash) - .unwrap(); - let result = compute_amount_after_slashing_withdraw( - &storage, - ¶ms, - &unbonds_and_redelegated_unbonds, - slashes, - ) - .unwrap(); - assert_eq!(result.sum, 0.into()); - itertools::assert_equal( - result.epoch_map, - [(2.into(), 0.into()), (4.into(), 0.into())], - ); - - // Test case 3 - let alice_slash = Slash { - epoch: Epoch(0), - block_height: Default::default(), - r#type: SlashType::DuplicateVote, - rate: Dec::one(), - }; - let slashes = vec![alice_slash.clone()]; - validator_slashes_handle(&alice) - .push(&mut storage, alice_slash) - .unwrap(); - validator_slashes_handle(&bob).pop(&mut storage).unwrap(); - let result = compute_amount_after_slashing_withdraw( - &storage, - ¶ms, - &unbonds_and_redelegated_unbonds, - slashes, - ) - .unwrap(); - assert_eq!(result.sum, 11.into()); - itertools::assert_equal( - result.epoch_map, - [(2.into(), 5.into()), (4.into(), 6.into())], - ); - - // Test case 4 - let alice_slash = Slash { - epoch: Epoch(1), - block_height: Default::default(), - r#type: SlashType::DuplicateVote, - rate: Dec::one(), - }; - let slashes = vec![alice_slash.clone()]; - validator_slashes_handle(&alice).pop(&mut storage).unwrap(); - validator_slashes_handle(&alice) - .push(&mut storage, alice_slash) - .unwrap(); - let result = compute_amount_after_slashing_withdraw( - &storage, - ¶ms, - &unbonds_and_redelegated_unbonds, - slashes, - ) - .unwrap(); - assert_eq!(result.sum, 10.into()); - itertools::assert_equal( - result.epoch_map, - [(2.into(), 4.into()), (4.into(), 6.into())], - ); -} - -fn arb_redelegation_amounts( - max_delegation: u64, -) -> impl Strategy { - let arb_delegation = arb_amount_non_zero_ceiled(max_delegation); - let amounts = arb_delegation.prop_flat_map(move |amount_delegate| { - let amount_redelegate = arb_amount_non_zero_ceiled(max( - 1, - u64::try_from(amount_delegate.raw_amount()).unwrap() - 1, - )); - (Just(amount_delegate), amount_redelegate) - }); - amounts.prop_flat_map(move |(amount_delegate, amount_redelegate)| { - let amount_unbond = arb_amount_non_zero_ceiled(max( - 1, - u64::try_from(amount_redelegate.raw_amount()).unwrap() - 1, - )); - ( - Just(amount_delegate), - Just(amount_redelegate), - amount_unbond, - ) - }) -} - -fn test_simple_redelegation_aux( - mut validators: Vec, - amount_delegate: token::Amount, - amount_redelegate: token::Amount, - amount_unbond: token::Amount, -) { - validators.sort_by(|a, b| b.tokens.cmp(&a.tokens)); - - let src_validator = validators[0].address.clone(); - let dest_validator = validators[1].address.clone(); - - let mut storage = TestWlStorage::default(); - let params = OwnedPosParams { - unbonding_len: 4, - ..Default::default() - }; - - // Genesis - let mut current_epoch = storage.storage.block.epoch; - let params = test_init_genesis( - &mut storage, - params, - validators.clone().into_iter(), - current_epoch, - ) - .unwrap(); - storage.commit_block().unwrap(); - - // Get a delegator with some tokens - let staking_token = staking_token_address(&storage); - let delegator = address::testing::gen_implicit_address(); - let del_balance = token::Amount::from_uint(1_000_000, 0).unwrap(); - credit_tokens(&mut storage, &staking_token, &delegator, del_balance) - .unwrap(); - - // Ensure that we cannot redelegate with the same src and dest validator - let err = super::redelegate_tokens( - &mut storage, - &delegator, - &src_validator, - &src_validator, - current_epoch, - amount_redelegate, - ) - .unwrap_err(); - let err_str = err.to_string(); - assert_matches!( - err.downcast::().unwrap().deref(), - RedelegationError::RedelegationSrcEqDest, - "Redelegation with the same src and dest validator must be rejected, \ - got {err_str}", - ); - - for _ in 0..5 { - current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); - } - - let init_epoch = current_epoch; - - // Delegate in epoch 1 to src_validator - println!( - "\nBONDING {} TOKENS TO {}\n", - amount_delegate.to_string_native(), - &src_validator - ); - super::bond_tokens( - &mut storage, - Some(&delegator), - &src_validator, - amount_delegate, - current_epoch, - None, - ) - .unwrap(); - - println!("\nAFTER DELEGATION\n"); - let bonds = bond_handle(&delegator, &src_validator) - .get_data_handler() - .collect_map(&storage) - .unwrap(); - let bonds_dest = bond_handle(&delegator, &dest_validator) - .get_data_handler() - .collect_map(&storage) - .unwrap(); - let unbonds = unbond_handle(&delegator, &src_validator) - .collect_map(&storage) - .unwrap(); - let tot_bonds = total_bonded_handle(&src_validator) - .get_data_handler() - .collect_map(&storage) - .unwrap(); - let tot_unbonds = total_unbonded_handle(&src_validator) - .collect_map(&storage) - .unwrap(); - dbg!(&bonds, &bonds_dest, &unbonds, &tot_bonds, &tot_unbonds); - - // Advance three epochs - current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); - current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); - current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); - - // Redelegate in epoch 3 - println!( - "\nREDELEGATING {} TOKENS TO {}\n", - amount_redelegate.to_string_native(), - &dest_validator - ); - - super::redelegate_tokens( - &mut storage, - &delegator, - &src_validator, - &dest_validator, - current_epoch, - amount_redelegate, - ) - .unwrap(); - - println!("\nAFTER REDELEGATION\n"); - println!("\nDELEGATOR\n"); - let bonds_src = bond_handle(&delegator, &src_validator) - .get_data_handler() - .collect_map(&storage) - .unwrap(); - let bonds_dest = bond_handle(&delegator, &dest_validator) - .get_data_handler() - .collect_map(&storage) - .unwrap(); - let unbonds_src = unbond_handle(&delegator, &src_validator) - .collect_map(&storage) - .unwrap(); - let unbonds_dest = unbond_handle(&delegator, &dest_validator) - .collect_map(&storage) - .unwrap(); - let redel_bonds = delegator_redelegated_bonds_handle(&delegator) - .collect_map(&storage) - .unwrap(); - let redel_unbonds = delegator_redelegated_unbonds_handle(&delegator) - .collect_map(&storage) - .unwrap(); - - dbg!( - &bonds_src, - &bonds_dest, - &unbonds_src, - &unbonds_dest, - &redel_bonds, - &redel_unbonds - ); - - // Dest val - println!("\nDEST VALIDATOR\n"); - - let incoming_redels_dest = - validator_incoming_redelegations_handle(&dest_validator) - .collect_map(&storage) - .unwrap(); - let outgoing_redels_dest = - validator_outgoing_redelegations_handle(&dest_validator) - .collect_map(&storage) - .unwrap(); - let tot_bonds_dest = total_bonded_handle(&dest_validator) - .get_data_handler() - .collect_map(&storage) - .unwrap(); - let tot_unbonds_dest = total_unbonded_handle(&dest_validator) - .collect_map(&storage) - .unwrap(); - let tot_redel_bonds_dest = - validator_total_redelegated_bonded_handle(&dest_validator) - .collect_map(&storage) - .unwrap(); - let tot_redel_unbonds_dest = - validator_total_redelegated_unbonded_handle(&dest_validator) - .collect_map(&storage) - .unwrap(); - dbg!( - &incoming_redels_dest, - &outgoing_redels_dest, - &tot_bonds_dest, - &tot_unbonds_dest, - &tot_redel_bonds_dest, - &tot_redel_unbonds_dest - ); - - // Src val - println!("\nSRC VALIDATOR\n"); - - let incoming_redels_src = - validator_incoming_redelegations_handle(&src_validator) - .collect_map(&storage) - .unwrap(); - let outgoing_redels_src = - validator_outgoing_redelegations_handle(&src_validator) - .collect_map(&storage) - .unwrap(); - let tot_bonds_src = total_bonded_handle(&src_validator) - .get_data_handler() - .collect_map(&storage) - .unwrap(); - let tot_unbonds_src = total_unbonded_handle(&src_validator) - .collect_map(&storage) - .unwrap(); - let tot_redel_bonds_src = - validator_total_redelegated_bonded_handle(&src_validator) - .collect_map(&storage) - .unwrap(); - let tot_redel_unbonds_src = - validator_total_redelegated_unbonded_handle(&src_validator) - .collect_map(&storage) - .unwrap(); - dbg!( - &incoming_redels_src, - &outgoing_redels_src, - &tot_bonds_src, - &tot_unbonds_src, - &tot_redel_bonds_src, - &tot_redel_unbonds_src - ); - - // Checks - let redelegated = delegator_redelegated_bonds_handle(&delegator) - .at(&dest_validator) - .at(&(current_epoch + params.pipeline_len)) - .at(&src_validator) - .get(&storage, &(init_epoch + params.pipeline_len)) - .unwrap() - .unwrap(); - assert_eq!(redelegated, amount_redelegate); - - let redel_start_epoch = - validator_incoming_redelegations_handle(&dest_validator) - .get(&storage, &delegator) - .unwrap() - .unwrap(); - assert_eq!(redel_start_epoch, current_epoch + params.pipeline_len); - - let redelegated = validator_outgoing_redelegations_handle(&src_validator) - .at(&dest_validator) - .at(¤t_epoch.prev()) - .get(&storage, ¤t_epoch) - .unwrap() - .unwrap(); - assert_eq!(redelegated, amount_redelegate); - - // Advance three epochs - current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); - current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); - current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); - - // Unbond in epoch 5 from dest_validator - println!( - "\nUNBONDING {} TOKENS FROM {}\n", - amount_unbond.to_string_native(), - &dest_validator - ); - let _ = unbond_tokens( - &mut storage, - Some(&delegator), - &dest_validator, - amount_unbond, - current_epoch, - false, - ) - .unwrap(); - - println!("\nAFTER UNBONDING\n"); - println!("\nDELEGATOR\n"); - - let bonds_src = bond_handle(&delegator, &src_validator) - .get_data_handler() - .collect_map(&storage) - .unwrap(); - let bonds_dest = bond_handle(&delegator, &dest_validator) - .get_data_handler() - .collect_map(&storage) - .unwrap(); - let unbonds_src = unbond_handle(&delegator, &src_validator) - .collect_map(&storage) - .unwrap(); - let unbonds_dest = unbond_handle(&delegator, &dest_validator) - .collect_map(&storage) - .unwrap(); - let redel_bonds = delegator_redelegated_bonds_handle(&delegator) - .collect_map(&storage) - .unwrap(); - let redel_unbonds = delegator_redelegated_unbonds_handle(&delegator) - .collect_map(&storage) - .unwrap(); - - dbg!( - &bonds_src, - &bonds_dest, - &unbonds_src, - &unbonds_dest, - &redel_bonds, - &redel_unbonds - ); - - println!("\nDEST VALIDATOR\n"); - - let incoming_redels_dest = - validator_incoming_redelegations_handle(&dest_validator) - .collect_map(&storage) - .unwrap(); - let outgoing_redels_dest = - validator_outgoing_redelegations_handle(&dest_validator) - .collect_map(&storage) - .unwrap(); - let tot_bonds_dest = total_bonded_handle(&dest_validator) - .get_data_handler() - .collect_map(&storage) - .unwrap(); - let tot_unbonds_dest = total_unbonded_handle(&dest_validator) - .collect_map(&storage) - .unwrap(); - let tot_redel_bonds_dest = - validator_total_redelegated_bonded_handle(&dest_validator) - .collect_map(&storage) - .unwrap(); - let tot_redel_unbonds_dest = - validator_total_redelegated_unbonded_handle(&dest_validator) - .collect_map(&storage) - .unwrap(); - dbg!( - &incoming_redels_dest, - &outgoing_redels_dest, - &tot_bonds_dest, - &tot_unbonds_dest, - &tot_redel_bonds_dest, - &tot_redel_unbonds_dest - ); - - let bond_start = init_epoch + params.pipeline_len; - let redelegation_end = bond_start + params.pipeline_len + 1u64; - let unbond_end = - redelegation_end + params.withdrawable_epoch_offset() + 1u64; - let unbond_materialized = redelegation_end + params.pipeline_len + 1u64; - - // Checks - let redelegated_remaining = delegator_redelegated_bonds_handle(&delegator) - .at(&dest_validator) - .at(&redelegation_end) - .at(&src_validator) - .get(&storage, &bond_start) - .unwrap() - .unwrap_or_default(); - assert_eq!(redelegated_remaining, amount_redelegate - amount_unbond); - - let redel_unbonded = delegator_redelegated_unbonds_handle(&delegator) - .at(&dest_validator) - .at(&redelegation_end) - .at(&unbond_end) - .at(&src_validator) - .get(&storage, &bond_start) - .unwrap() - .unwrap(); - assert_eq!(redel_unbonded, amount_unbond); - - dbg!(unbond_materialized, redelegation_end, bond_start); - let total_redel_unbonded = - validator_total_redelegated_unbonded_handle(&dest_validator) - .at(&unbond_materialized) - .at(&redelegation_end) - .at(&src_validator) - .get(&storage, &bond_start) - .unwrap() - .unwrap(); - assert_eq!(total_redel_unbonded, amount_unbond); - - // Advance to withdrawal epoch - loop { - current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); - if current_epoch == unbond_end { - break; - } - } - - // Withdraw - withdraw_tokens( - &mut storage, - Some(&delegator), - &dest_validator, - current_epoch, - ) - .unwrap(); - - assert!( - delegator_redelegated_unbonds_handle(&delegator) - .at(&dest_validator) - .is_empty(&storage) - .unwrap() - ); - - let delegator_balance = storage - .read::(&token::balance_key(&staking_token, &delegator)) - .unwrap() - .unwrap_or_default(); - assert_eq!( - delegator_balance, - del_balance - amount_delegate + amount_unbond - ); -} - -fn test_redelegation_with_slashing_aux( - mut validators: Vec, - amount_delegate: token::Amount, - amount_redelegate: token::Amount, - amount_unbond: token::Amount, -) { - validators.sort_by(|a, b| b.tokens.cmp(&a.tokens)); - - let src_validator = validators[0].address.clone(); - let dest_validator = validators[1].address.clone(); - - let mut storage = TestWlStorage::default(); - let params = OwnedPosParams { - unbonding_len: 4, - // Avoid empty consensus set by removing the threshold - validator_stake_threshold: token::Amount::zero(), - ..Default::default() - }; - - // Genesis - let mut current_epoch = storage.storage.block.epoch; - let params = test_init_genesis( - &mut storage, - params, - validators.clone().into_iter(), - current_epoch, - ) - .unwrap(); - storage.commit_block().unwrap(); - - // Get a delegator with some tokens - let staking_token = staking_token_address(&storage); - let delegator = address::testing::gen_implicit_address(); - let del_balance = token::Amount::from_uint(1_000_000, 0).unwrap(); - credit_tokens(&mut storage, &staking_token, &delegator, del_balance) - .unwrap(); - - for _ in 0..5 { - current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); - } - - let init_epoch = current_epoch; - - // Delegate in epoch 5 to src_validator - println!( - "\nBONDING {} TOKENS TO {}\n", - amount_delegate.to_string_native(), - &src_validator - ); - super::bond_tokens( - &mut storage, - Some(&delegator), - &src_validator, - amount_delegate, - current_epoch, - None, - ) - .unwrap(); - - println!("\nAFTER DELEGATION\n"); - let bonds = bond_handle(&delegator, &src_validator) - .get_data_handler() - .collect_map(&storage) - .unwrap(); - let bonds_dest = bond_handle(&delegator, &dest_validator) - .get_data_handler() - .collect_map(&storage) - .unwrap(); - let unbonds = unbond_handle(&delegator, &src_validator) - .collect_map(&storage) - .unwrap(); - let tot_bonds = total_bonded_handle(&src_validator) - .get_data_handler() - .collect_map(&storage) - .unwrap(); - let tot_unbonds = total_unbonded_handle(&src_validator) - .collect_map(&storage) - .unwrap(); - dbg!(&bonds, &bonds_dest, &unbonds, &tot_bonds, &tot_unbonds); - - // Advance three epochs - current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); - current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); - current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); - - // Redelegate in epoch 8 - println!( - "\nREDELEGATING {} TOKENS TO {}\n", - amount_redelegate.to_string_native(), - &dest_validator - ); - - super::redelegate_tokens( - &mut storage, - &delegator, - &src_validator, - &dest_validator, - current_epoch, - amount_redelegate, - ) - .unwrap(); - - println!("\nAFTER REDELEGATION\n"); - println!("\nDELEGATOR\n"); - let bonds_src = bond_handle(&delegator, &src_validator) - .get_data_handler() - .collect_map(&storage) - .unwrap(); - let bonds_dest = bond_handle(&delegator, &dest_validator) - .get_data_handler() - .collect_map(&storage) - .unwrap(); - let unbonds_src = unbond_handle(&delegator, &src_validator) - .collect_map(&storage) - .unwrap(); - let unbonds_dest = unbond_handle(&delegator, &dest_validator) - .collect_map(&storage) - .unwrap(); - let redel_bonds = delegator_redelegated_bonds_handle(&delegator) - .collect_map(&storage) - .unwrap(); - let redel_unbonds = delegator_redelegated_unbonds_handle(&delegator) - .collect_map(&storage) - .unwrap(); - - dbg!( - &bonds_src, - &bonds_dest, - &unbonds_src, - &unbonds_dest, - &redel_bonds, - &redel_unbonds - ); - - // Dest val - println!("\nDEST VALIDATOR\n"); - - let incoming_redels_dest = - validator_incoming_redelegations_handle(&dest_validator) - .collect_map(&storage) - .unwrap(); - let outgoing_redels_dest = - validator_outgoing_redelegations_handle(&dest_validator) - .collect_map(&storage) - .unwrap(); - let tot_bonds_dest = total_bonded_handle(&dest_validator) - .get_data_handler() - .collect_map(&storage) - .unwrap(); - let tot_unbonds_dest = total_unbonded_handle(&dest_validator) - .collect_map(&storage) - .unwrap(); - let tot_redel_bonds_dest = - validator_total_redelegated_bonded_handle(&dest_validator) - .collect_map(&storage) - .unwrap(); - let tot_redel_unbonds_dest = - validator_total_redelegated_unbonded_handle(&dest_validator) - .collect_map(&storage) - .unwrap(); - dbg!( - &incoming_redels_dest, - &outgoing_redels_dest, - &tot_bonds_dest, - &tot_unbonds_dest, - &tot_redel_bonds_dest, - &tot_redel_unbonds_dest - ); - - // Src val - println!("\nSRC VALIDATOR\n"); - - let incoming_redels_src = - validator_incoming_redelegations_handle(&src_validator) - .collect_map(&storage) - .unwrap(); - let outgoing_redels_src = - validator_outgoing_redelegations_handle(&src_validator) - .collect_map(&storage) - .unwrap(); - let tot_bonds_src = total_bonded_handle(&src_validator) - .get_data_handler() - .collect_map(&storage) - .unwrap(); - let tot_unbonds_src = total_unbonded_handle(&src_validator) - .collect_map(&storage) - .unwrap(); - let tot_redel_bonds_src = - validator_total_redelegated_bonded_handle(&src_validator) - .collect_map(&storage) - .unwrap(); - let tot_redel_unbonds_src = - validator_total_redelegated_unbonded_handle(&src_validator) - .collect_map(&storage) - .unwrap(); - dbg!( - &incoming_redels_src, - &outgoing_redels_src, - &tot_bonds_src, - &tot_unbonds_src, - &tot_redel_bonds_src, - &tot_redel_unbonds_src - ); - - // Checks - let redelegated = delegator_redelegated_bonds_handle(&delegator) - .at(&dest_validator) - .at(&(current_epoch + params.pipeline_len)) - .at(&src_validator) - .get(&storage, &(init_epoch + params.pipeline_len)) - .unwrap() - .unwrap(); - assert_eq!(redelegated, amount_redelegate); - - let redel_start_epoch = - validator_incoming_redelegations_handle(&dest_validator) - .get(&storage, &delegator) - .unwrap() - .unwrap(); - assert_eq!(redel_start_epoch, current_epoch + params.pipeline_len); - - let redelegated = validator_outgoing_redelegations_handle(&src_validator) - .at(&dest_validator) - .at(¤t_epoch.prev()) - .get(&storage, ¤t_epoch) - .unwrap() - .unwrap(); - assert_eq!(redelegated, amount_redelegate); - - // Advance three epochs - current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); - current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); - current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); - - // Unbond in epoch 11 from dest_validator - println!( - "\nUNBONDING {} TOKENS FROM {}\n", - amount_unbond.to_string_native(), - &dest_validator - ); - let _ = unbond_tokens( - &mut storage, - Some(&delegator), - &dest_validator, - amount_unbond, - current_epoch, - false, - ) - .unwrap(); - - println!("\nAFTER UNBONDING\n"); - println!("\nDELEGATOR\n"); - - let bonds_src = bond_handle(&delegator, &src_validator) - .get_data_handler() - .collect_map(&storage) - .unwrap(); - let bonds_dest = bond_handle(&delegator, &dest_validator) - .get_data_handler() - .collect_map(&storage) - .unwrap(); - let unbonds_src = unbond_handle(&delegator, &src_validator) - .collect_map(&storage) - .unwrap(); - let unbonds_dest = unbond_handle(&delegator, &dest_validator) - .collect_map(&storage) - .unwrap(); - let redel_bonds = delegator_redelegated_bonds_handle(&delegator) - .collect_map(&storage) - .unwrap(); - let redel_unbonds = delegator_redelegated_unbonds_handle(&delegator) - .collect_map(&storage) - .unwrap(); - - dbg!( - &bonds_src, - &bonds_dest, - &unbonds_src, - &unbonds_dest, - &redel_bonds, - &redel_unbonds - ); - - println!("\nDEST VALIDATOR\n"); - - let incoming_redels_dest = - validator_incoming_redelegations_handle(&dest_validator) - .collect_map(&storage) - .unwrap(); - let outgoing_redels_dest = - validator_outgoing_redelegations_handle(&dest_validator) - .collect_map(&storage) - .unwrap(); - let tot_bonds_dest = total_bonded_handle(&dest_validator) - .get_data_handler() - .collect_map(&storage) - .unwrap(); - let tot_unbonds_dest = total_unbonded_handle(&dest_validator) - .collect_map(&storage) - .unwrap(); - let tot_redel_bonds_dest = - validator_total_redelegated_bonded_handle(&dest_validator) - .collect_map(&storage) - .unwrap(); - let tot_redel_unbonds_dest = - validator_total_redelegated_unbonded_handle(&dest_validator) - .collect_map(&storage) - .unwrap(); - dbg!( - &incoming_redels_dest, - &outgoing_redels_dest, - &tot_bonds_dest, - &tot_unbonds_dest, - &tot_redel_bonds_dest, - &tot_redel_unbonds_dest - ); - - // Advance one epoch - current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); - - // Discover evidence - slash( - &mut storage, - ¶ms, - current_epoch, - init_epoch + 2 * params.pipeline_len, - 0u64, - SlashType::DuplicateVote, - &src_validator, - current_epoch.next(), - ) - .unwrap(); - - let bond_start = init_epoch + params.pipeline_len; - let redelegation_end = bond_start + params.pipeline_len + 1u64; - let unbond_end = - redelegation_end + params.withdrawable_epoch_offset() + 1u64; - let unbond_materialized = redelegation_end + params.pipeline_len + 1u64; - - // Checks - let redelegated_remaining = delegator_redelegated_bonds_handle(&delegator) - .at(&dest_validator) - .at(&redelegation_end) - .at(&src_validator) - .get(&storage, &bond_start) - .unwrap() - .unwrap_or_default(); - assert_eq!(redelegated_remaining, amount_redelegate - amount_unbond); - - let redel_unbonded = delegator_redelegated_unbonds_handle(&delegator) - .at(&dest_validator) - .at(&redelegation_end) - .at(&unbond_end) - .at(&src_validator) - .get(&storage, &bond_start) - .unwrap() - .unwrap(); - assert_eq!(redel_unbonded, amount_unbond); - - dbg!(unbond_materialized, redelegation_end, bond_start); - let total_redel_unbonded = - validator_total_redelegated_unbonded_handle(&dest_validator) - .at(&unbond_materialized) - .at(&redelegation_end) - .at(&src_validator) - .get(&storage, &bond_start) - .unwrap() - .unwrap(); - assert_eq!(total_redel_unbonded, amount_unbond); - - // Advance to withdrawal epoch - loop { - current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); - if current_epoch == unbond_end { - break; - } - } - - // Withdraw - withdraw_tokens( - &mut storage, - Some(&delegator), - &dest_validator, - current_epoch, - ) - .unwrap(); - - assert!( - delegator_redelegated_unbonds_handle(&delegator) - .at(&dest_validator) - .is_empty(&storage) - .unwrap() - ); - - let delegator_balance = storage - .read::(&token::balance_key(&staking_token, &delegator)) - .unwrap() - .unwrap_or_default(); - assert_eq!(delegator_balance, del_balance - amount_delegate); -} - -fn test_chain_redelegations_aux(mut validators: Vec) { - validators.sort_by(|a, b| b.tokens.cmp(&a.tokens)); - - let src_validator = validators[0].address.clone(); - let _init_stake_src = validators[0].tokens; - let dest_validator = validators[1].address.clone(); - let _init_stake_dest = validators[1].tokens; - let dest_validator_2 = validators[2].address.clone(); - let _init_stake_dest_2 = validators[2].tokens; - - let mut storage = TestWlStorage::default(); - let params = OwnedPosParams { - unbonding_len: 4, - ..Default::default() - }; - - // Genesis - let mut current_epoch = storage.storage.block.epoch; - let params = test_init_genesis( - &mut storage, - params, - validators.clone().into_iter(), - current_epoch, - ) - .unwrap(); - storage.commit_block().unwrap(); - - // Get a delegator with some tokens - let staking_token = staking_token_address(&storage); - let delegator = address::testing::gen_implicit_address(); - let del_balance = token::Amount::from_uint(1_000_000, 0).unwrap(); - credit_tokens(&mut storage, &staking_token, &delegator, del_balance) - .unwrap(); - - // Delegate in epoch 0 to src_validator - let bond_amount: token::Amount = 100.into(); - super::bond_tokens( - &mut storage, - Some(&delegator), - &src_validator, - bond_amount, - current_epoch, - None, - ) - .unwrap(); - - let bond_start = current_epoch + params.pipeline_len; - - // Advance one epoch - current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); - - // Redelegate in epoch 1 to dest_validator - let redel_amount_1: token::Amount = 58.into(); - super::redelegate_tokens( - &mut storage, - &delegator, - &src_validator, - &dest_validator, - current_epoch, - redel_amount_1, - ) - .unwrap(); - - let redel_start = current_epoch; - let redel_end = current_epoch + params.pipeline_len; - - // Checks ---------------- - - // Dest validator should have an incoming redelegation - let incoming_redelegation = - validator_incoming_redelegations_handle(&dest_validator) - .get(&storage, &delegator) - .unwrap(); - assert_eq!(incoming_redelegation, Some(redel_end)); - - // Src validator should have an outoging redelegation - let outgoing_redelegation = - validator_outgoing_redelegations_handle(&src_validator) - .at(&dest_validator) - .at(&bond_start) - .get(&storage, &redel_start) - .unwrap(); - assert_eq!(outgoing_redelegation, Some(redel_amount_1)); - - // Delegator should have redelegated bonds - let del_total_redelegated_bonded = - delegator_redelegated_bonds_handle(&delegator) - .at(&dest_validator) - .at(&redel_end) - .at(&src_validator) - .get(&storage, &bond_start) - .unwrap() - .unwrap_or_default(); - assert_eq!(del_total_redelegated_bonded, redel_amount_1); - - // There should be delegator bonds for both src and dest validators - let bonded_src = bond_handle(&delegator, &src_validator); - let bonded_dest = bond_handle(&delegator, &dest_validator); - assert_eq!( - bonded_src - .get_delta_val(&storage, bond_start) - .unwrap() - .unwrap_or_default(), - bond_amount - redel_amount_1 - ); - assert_eq!( - bonded_dest - .get_delta_val(&storage, redel_end) - .unwrap() - .unwrap_or_default(), - redel_amount_1 - ); - - // The dest validator should have total redelegated bonded tokens - let dest_total_redelegated_bonded = - validator_total_redelegated_bonded_handle(&dest_validator) - .at(&redel_end) - .at(&src_validator) - .get(&storage, &bond_start) - .unwrap() - .unwrap_or_default(); - assert_eq!(dest_total_redelegated_bonded, redel_amount_1); - - // The dest validator's total bonded should have an entry for the genesis - // bond and the redelegation - let dest_total_bonded = total_bonded_handle(&dest_validator) - .get_data_handler() - .collect_map(&storage) - .unwrap(); - assert!( - dest_total_bonded.len() == 2 - && dest_total_bonded.contains_key(&Epoch::default()) - ); - assert_eq!( - dest_total_bonded - .get(&redel_end) - .cloned() - .unwrap_or_default(), - redel_amount_1 - ); - - // The src validator should have a total bonded entry for the original bond - // accounting for the redelegation - assert_eq!( - total_bonded_handle(&src_validator) - .get_delta_val(&storage, bond_start) - .unwrap() - .unwrap_or_default(), - bond_amount - redel_amount_1 - ); - - // The src validator should have a total unbonded entry due to the - // redelegation - let src_total_unbonded = total_unbonded_handle(&src_validator) - .at(&redel_end) - .get(&storage, &bond_start) - .unwrap() - .unwrap_or_default(); - assert_eq!(src_total_unbonded, redel_amount_1); - - // Attempt to redelegate in epoch 3 to dest_validator - current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); - current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); - - let redel_amount_2: token::Amount = 23.into(); - let redel_att = super::redelegate_tokens( - &mut storage, - &delegator, - &dest_validator, - &dest_validator_2, - current_epoch, - redel_amount_2, - ); - assert!(redel_att.is_err()); - - // Advance to right before the redelegation can be redelegated again - assert_eq!(redel_end, current_epoch); - let epoch_can_redel = - redel_end.prev() + params.slash_processing_epoch_offset(); - loop { - current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); - if current_epoch == epoch_can_redel.prev() { - break; - } - } - - // Attempt to redelegate in epoch before we actually are able to - let redel_att = super::redelegate_tokens( - &mut storage, - &delegator, - &dest_validator, - &dest_validator_2, - current_epoch, - redel_amount_2, - ); - assert!(redel_att.is_err()); - - // Advance one more epoch - current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); - - // Redelegate from dest_validator to dest_validator_2 now - super::redelegate_tokens( - &mut storage, - &delegator, - &dest_validator, - &dest_validator_2, - current_epoch, - redel_amount_2, - ) - .unwrap(); - - let redel_2_start = current_epoch; - let redel_2_end = current_epoch + params.pipeline_len; - - // Checks ----------------------------------- - - // Both the dest validator and dest validator 2 should have incoming - // redelegations - let incoming_redelegation_1 = - validator_incoming_redelegations_handle(&dest_validator) - .get(&storage, &delegator) - .unwrap(); - assert_eq!(incoming_redelegation_1, Some(redel_end)); - let incoming_redelegation_2 = - validator_incoming_redelegations_handle(&dest_validator_2) - .get(&storage, &delegator) - .unwrap(); - assert_eq!(incoming_redelegation_2, Some(redel_2_end)); - - // Both the src validator and dest validator should have outgoing - // redelegations - let outgoing_redelegation_1 = - validator_outgoing_redelegations_handle(&src_validator) - .at(&dest_validator) - .at(&bond_start) - .get(&storage, &redel_start) - .unwrap(); - assert_eq!(outgoing_redelegation_1, Some(redel_amount_1)); - - let outgoing_redelegation_2 = - validator_outgoing_redelegations_handle(&dest_validator) - .at(&dest_validator_2) - .at(&redel_end) - .get(&storage, &redel_2_start) - .unwrap(); - assert_eq!(outgoing_redelegation_2, Some(redel_amount_2)); - - // All three validators should have bonds - let bonded_dest2 = bond_handle(&delegator, &dest_validator_2); - assert_eq!( - bonded_src - .get_delta_val(&storage, bond_start) - .unwrap() - .unwrap_or_default(), - bond_amount - redel_amount_1 - ); - assert_eq!( - bonded_dest - .get_delta_val(&storage, redel_end) - .unwrap() - .unwrap_or_default(), - redel_amount_1 - redel_amount_2 - ); - assert_eq!( - bonded_dest2 - .get_delta_val(&storage, redel_2_end) - .unwrap() - .unwrap_or_default(), - redel_amount_2 - ); - - // There should be no unbond entries - let unbond_src = unbond_handle(&delegator, &src_validator); - let unbond_dest = unbond_handle(&delegator, &dest_validator); - assert!(unbond_src.is_empty(&storage).unwrap()); - assert!(unbond_dest.is_empty(&storage).unwrap()); - - // The dest validator should have some total unbonded due to the second - // redelegation - let dest_total_unbonded = total_unbonded_handle(&dest_validator) - .at(&redel_2_end) - .get(&storage, &redel_end) - .unwrap(); - assert_eq!(dest_total_unbonded, Some(redel_amount_2)); - - // Delegator should have redelegated bonds due to both redelegations - let del_redelegated_bonds = delegator_redelegated_bonds_handle(&delegator); - assert_eq!( - Some(redel_amount_1 - redel_amount_2), - del_redelegated_bonds - .at(&dest_validator) - .at(&redel_end) - .at(&src_validator) - .get(&storage, &bond_start) - .unwrap() - ); - assert_eq!( - Some(redel_amount_2), - del_redelegated_bonds - .at(&dest_validator_2) - .at(&redel_2_end) - .at(&dest_validator) - .get(&storage, &redel_end) - .unwrap() - ); - - // Delegator redelegated unbonds should be empty - assert!( - delegator_redelegated_unbonds_handle(&delegator) - .is_empty(&storage) - .unwrap() - ); - - // Both the dest validator and dest validator 2 should have total - // redelegated bonds - let dest_redelegated_bonded = - validator_total_redelegated_bonded_handle(&dest_validator) - .at(&redel_end) - .at(&src_validator) - .get(&storage, &bond_start) - .unwrap() - .unwrap_or_default(); - let dest2_redelegated_bonded = - validator_total_redelegated_bonded_handle(&dest_validator_2) - .at(&redel_2_end) - .at(&dest_validator) - .get(&storage, &redel_end) - .unwrap() - .unwrap_or_default(); - assert_eq!(dest_redelegated_bonded, redel_amount_1 - redel_amount_2); - assert_eq!(dest2_redelegated_bonded, redel_amount_2); - - // Total redelegated unbonded should be empty for src_validator and - // dest_validator_2 - assert!( - validator_total_redelegated_unbonded_handle(&dest_validator_2) - .is_empty(&storage) - .unwrap() - ); - assert!( - validator_total_redelegated_unbonded_handle(&src_validator) - .is_empty(&storage) - .unwrap() - ); - - // The dest_validator should have total_redelegated unbonded - let tot_redel_unbonded = - validator_total_redelegated_unbonded_handle(&dest_validator) - .at(&redel_2_end) - .at(&redel_end) - .at(&src_validator) - .get(&storage, &bond_start) - .unwrap() - .unwrap_or_default(); - assert_eq!(tot_redel_unbonded, redel_amount_2); -} - -/// SM test case 1 from Brent -#[test] -fn test_from_sm_case_1() { - use namada_core::types::address::testing::established_address_4; - - let mut storage = TestWlStorage::default(); - let gov_params = namada_core::ledger::governance::parameters::GovernanceParameters::default(); - gov_params.init_storage(&mut storage).unwrap(); - write_pos_params(&mut storage, &OwnedPosParams::default()).unwrap(); - - let validator = established_address_1(); - let redeleg_src_1 = established_address_2(); - let redeleg_src_2 = established_address_3(); - let owner = established_address_4(); - let unbond_amount = token::Amount::from(3130688); - println!( - "Owner: {owner}\nValidator: {validator}\nRedeleg src 1: \ - {redeleg_src_1}\nRedeleg src 2: {redeleg_src_2}" - ); - - // Validator's incoming redelegations - let outer_epoch_1 = Epoch(27); - // from redeleg_src_1 - let epoch_1_redeleg_1 = token::Amount::from(8516); - // from redeleg_src_2 - let epoch_1_redeleg_2 = token::Amount::from(5704386); - let outer_epoch_2 = Epoch(30); - // from redeleg_src_2 - let epoch_2_redeleg_2 = token::Amount::from(1035191); - - // Insert the data - bonds and redelegated bonds - let bonds_handle = bond_handle(&owner, &validator); - bonds_handle - .add( - &mut storage, - epoch_1_redeleg_1 + epoch_1_redeleg_2, - outer_epoch_1, - 0, - ) - .unwrap(); - bonds_handle - .add(&mut storage, epoch_2_redeleg_2, outer_epoch_2, 0) - .unwrap(); - - let redelegated_bonds_map_1 = delegator_redelegated_bonds_handle(&owner) - .at(&validator) - .at(&outer_epoch_1); - redelegated_bonds_map_1 - .at(&redeleg_src_1) - .insert(&mut storage, Epoch(14), epoch_1_redeleg_1) - .unwrap(); - redelegated_bonds_map_1 - .at(&redeleg_src_2) - .insert(&mut storage, Epoch(18), epoch_1_redeleg_2) - .unwrap(); - let redelegated_bonds_map_1 = delegator_redelegated_bonds_handle(&owner) - .at(&validator) - .at(&outer_epoch_1); - - let redelegated_bonds_map_2 = delegator_redelegated_bonds_handle(&owner) - .at(&validator) - .at(&outer_epoch_2); - redelegated_bonds_map_2 - .at(&redeleg_src_2) - .insert(&mut storage, Epoch(18), epoch_2_redeleg_2) - .unwrap(); - - // Find the modified redelegation the same way as `unbond_tokens` - let bonds_to_unbond = find_bonds_to_remove( - &storage, - &bonds_handle.get_data_handler(), - unbond_amount, - ) - .unwrap(); - dbg!(&bonds_to_unbond); - - let (new_entry_epoch, new_bond_amount) = bonds_to_unbond.new_entry.unwrap(); - assert_eq!(outer_epoch_1, new_entry_epoch); - // The modified bond should be sum of all redelegations less the unbonded - // amouunt - assert_eq!( - epoch_1_redeleg_1 + epoch_1_redeleg_2 + epoch_2_redeleg_2 - - unbond_amount, - new_bond_amount - ); - // The current bond should be sum of redelegations fom the modified epoch - let cur_bond_amount = bonds_handle - .get_delta_val(&storage, new_entry_epoch) - .unwrap() - .unwrap_or_default(); - assert_eq!(epoch_1_redeleg_1 + epoch_1_redeleg_2, cur_bond_amount); - - let mr = compute_modified_redelegation( - &storage, - &redelegated_bonds_map_1, - new_entry_epoch, - cur_bond_amount - new_bond_amount, - ) - .unwrap(); - - let exp_mr = ModifiedRedelegation { - epoch: Some(Epoch(27)), - validators_to_remove: BTreeSet::from_iter([redeleg_src_2.clone()]), - validator_to_modify: Some(redeleg_src_2), - epochs_to_remove: BTreeSet::from_iter([Epoch(18)]), - epoch_to_modify: Some(Epoch(18)), - new_amount: Some(token::Amount::from(3608889)), - }; - - pretty_assertions::assert_eq!(mr, exp_mr); -} - -/// Test precisely that we are not overslashing, as originally discovered by Tomas in this issue: https://github.com/informalsystems/partnership-heliax/issues/74 -fn test_overslashing_aux(mut validators: Vec) { - assert_eq!(validators.len(), 4); - - let params = OwnedPosParams { - unbonding_len: 4, - ..Default::default() - }; - - let offending_stake = token::Amount::native_whole(110); - let other_stake = token::Amount::native_whole(100); - - // Set stakes so we know we will get a slashing rate between 0.5 -1.0 - validators[0].tokens = offending_stake; - validators[1].tokens = other_stake; - validators[2].tokens = other_stake; - validators[3].tokens = other_stake; - - // Get the offending validator - let validator = validators[0].address.clone(); - - println!("\nTest inputs: {params:?}, genesis validators: {validators:#?}"); - let mut storage = TestWlStorage::default(); - - // Genesis - let mut current_epoch = storage.storage.block.epoch; - let params = test_init_genesis( - &mut storage, - params, - validators.clone().into_iter(), - current_epoch, - ) - .unwrap(); - storage.commit_block().unwrap(); - - // Get a delegator with some tokens - let staking_token = storage.storage.native_token.clone(); - let delegator = address::testing::gen_implicit_address(); - let amount_del = token::Amount::native_whole(5); - credit_tokens(&mut storage, &staking_token, &delegator, amount_del) - .unwrap(); - - // Delegate tokens in epoch 0 to validator - bond_tokens( - &mut storage, - Some(&delegator), - &validator, - amount_del, - current_epoch, - None, - ) - .unwrap(); - - let self_bond_epoch = current_epoch; - let delegation_epoch = current_epoch + params.pipeline_len; - - // Advance to pipeline epoch - for _ in 0..params.pipeline_len { - current_epoch = advance_epoch(&mut storage, ¶ms); - } - assert_eq!(delegation_epoch, current_epoch); - - // Find a misbehavior committed in epoch 0 - slash( - &mut storage, - ¶ms, - current_epoch, - self_bond_epoch, - 0_u64, - SlashType::DuplicateVote, - &validator, - current_epoch.next(), - ) - .unwrap(); - - // Find a misbehavior committed in current epoch - slash( - &mut storage, - ¶ms, - current_epoch, - delegation_epoch, - 0_u64, - SlashType::DuplicateVote, - &validator, - current_epoch.next(), - ) - .unwrap(); - - let processing_epoch_1 = - self_bond_epoch + params.slash_processing_epoch_offset(); - let processing_epoch_2 = - delegation_epoch + params.slash_processing_epoch_offset(); - - // Advance to processing epoch 1 - loop { - current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); - if current_epoch == processing_epoch_1 { - break; - } - } - - let total_stake_1 = offending_stake + 3 * other_stake; - let stake_frac = Dec::from(offending_stake) / Dec::from(total_stake_1); - let slash_rate_1 = Dec::from_str("9.0").unwrap() * stake_frac * stake_frac; - dbg!(&slash_rate_1); - - let exp_slashed_1 = offending_stake.mul_ceil(slash_rate_1); - - // Check that the proper amount was slashed - let epoch = current_epoch.next(); - let validator_stake = - read_validator_stake(&storage, ¶ms, &validator, epoch).unwrap(); - let exp_validator_stake = offending_stake - exp_slashed_1 + amount_del; - assert_eq!(validator_stake, exp_validator_stake); - - let total_stake = read_total_stake(&storage, ¶ms, epoch).unwrap(); - let exp_total_stake = - offending_stake - exp_slashed_1 + amount_del + 3 * other_stake; - assert_eq!(total_stake, exp_total_stake); - - let self_bond_id = BondId { - source: validator.clone(), - validator: validator.clone(), - }; - let bond_amount = - crate::bond_amount(&storage, &self_bond_id, epoch).unwrap(); - let exp_bond_amount = offending_stake - exp_slashed_1; - assert_eq!(bond_amount, exp_bond_amount); - - // Advance to processing epoch 2 - loop { - current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); - if current_epoch == processing_epoch_2 { - break; - } - } - - let total_stake_2 = offending_stake + amount_del + 3 * other_stake; - let stake_frac = - Dec::from(offending_stake + amount_del) / Dec::from(total_stake_2); - let slash_rate_2 = Dec::from_str("9.0").unwrap() * stake_frac * stake_frac; - dbg!(&slash_rate_2); - - let exp_slashed_from_delegation = amount_del.mul_ceil(slash_rate_2); - - // Check that the proper amount was slashed. We expect that all of the - // validator self-bond has been slashed and some of the delegation has been - // slashed due to the second infraction. - let epoch = current_epoch.next(); - - let validator_stake = - read_validator_stake(&storage, ¶ms, &validator, epoch).unwrap(); - let exp_validator_stake = amount_del - exp_slashed_from_delegation; - assert_eq!(validator_stake, exp_validator_stake); - - let total_stake = read_total_stake(&storage, ¶ms, epoch).unwrap(); - let exp_total_stake = - amount_del - exp_slashed_from_delegation + 3 * other_stake; - assert_eq!(total_stake, exp_total_stake); - - let delegation_id = BondId { - source: delegator.clone(), - validator: validator.clone(), - }; - let delegation_amount = - crate::bond_amount(&storage, &delegation_id, epoch).unwrap(); - let exp_del_amount = amount_del - exp_slashed_from_delegation; - assert_eq!(delegation_amount, exp_del_amount); - - let self_bond_amount = - crate::bond_amount(&storage, &self_bond_id, epoch).unwrap(); - let exp_bond_amount = token::Amount::zero(); - assert_eq!(self_bond_amount, exp_bond_amount); -} - -fn test_unslashed_bond_amount_aux(validators: Vec) { - let mut storage = TestWlStorage::default(); - let params = OwnedPosParams { - unbonding_len: 4, - ..Default::default() - }; - - // Genesis - let mut current_epoch = storage.storage.block.epoch; - let params = test_init_genesis( - &mut storage, - params, - validators.clone().into_iter(), - current_epoch, - ) - .unwrap(); - storage.commit_block().unwrap(); - - let validator1 = validators[0].address.clone(); - let validator2 = validators[1].address.clone(); - - // Get a delegator with some tokens - let staking_token = staking_token_address(&storage); - let delegator = address::testing::gen_implicit_address(); - let del_balance = token::Amount::from_uint(1_000_000, 0).unwrap(); - credit_tokens(&mut storage, &staking_token, &delegator, del_balance) - .unwrap(); - - // Bond to validator 1 - super::bond_tokens( - &mut storage, - Some(&delegator), - &validator1, - 10_000.into(), - current_epoch, - None, - ) - .unwrap(); - - // Unbond some from validator 1 - super::unbond_tokens( - &mut storage, - Some(&delegator), - &validator1, - 1_342.into(), - current_epoch, - false, - ) - .unwrap(); - - // Redelegate some from validator 1 -> 2 - super::redelegate_tokens( - &mut storage, - &delegator, - &validator1, - &validator2, - current_epoch, - 1_875.into(), - ) - .unwrap(); - - // Unbond some from validator 2 - super::unbond_tokens( - &mut storage, - Some(&delegator), - &validator2, - 584.into(), - current_epoch, - false, - ) - .unwrap(); - - // Advance an epoch - current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); - - // Bond to validator 1 - super::bond_tokens( - &mut storage, - Some(&delegator), - &validator1, - 384.into(), - current_epoch, - None, - ) - .unwrap(); - - // Unbond some from validator 1 - super::unbond_tokens( - &mut storage, - Some(&delegator), - &validator1, - 144.into(), - current_epoch, - false, - ) - .unwrap(); - - // Redelegate some from validator 1 -> 2 - super::redelegate_tokens( - &mut storage, - &delegator, - &validator1, - &validator2, - current_epoch, - 3_448.into(), - ) - .unwrap(); - - // Unbond some from validator 2 - super::unbond_tokens( - &mut storage, - Some(&delegator), - &validator2, - 699.into(), - current_epoch, - false, - ) - .unwrap(); - - // Advance an epoch - current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); - - // Bond to validator 1 - super::bond_tokens( - &mut storage, - Some(&delegator), - &validator1, - 4_384.into(), - current_epoch, - None, - ) - .unwrap(); - - // Redelegate some from validator 1 -> 2 - super::redelegate_tokens( - &mut storage, - &delegator, - &validator1, - &validator2, - current_epoch, - 1_008.into(), - ) - .unwrap(); - - // Unbond some from validator 2 - super::unbond_tokens( - &mut storage, - Some(&delegator), - &validator2, - 3_500.into(), - current_epoch, - false, - ) - .unwrap(); - - // Checks - let val1_init_stake = validators[0].tokens; - - for epoch in Epoch::iter_bounds_inclusive( - Epoch(0), - current_epoch + params.pipeline_len, - ) { - let bond_amount = crate::bond_amount( - &storage, - &BondId { - source: delegator.clone(), - validator: validator1.clone(), - }, - epoch, - ) - .unwrap_or_default(); - - let val_stake = - crate::read_validator_stake(&storage, ¶ms, &validator1, epoch) - .unwrap(); - // dbg!(&bond_amount); - assert_eq!(val_stake - val1_init_stake, bond_amount); - } -} - -fn test_log_block_rewards_aux( - validators: Vec, - params: OwnedPosParams, -) { - tracing::info!( - "New case with {} validators: {:#?}", - validators.len(), - validators - .iter() - .map(|v| (&v.address, v.tokens.to_string_native())) - .collect::>() - ); - let mut s = TestWlStorage::default(); - // Init genesis - let current_epoch = s.storage.block.epoch; - let params = test_init_genesis( - &mut s, - params, - validators.clone().into_iter(), - current_epoch, - ) - .unwrap(); - s.commit_block().unwrap(); - let total_stake = - crate::get_total_consensus_stake(&s, current_epoch, ¶ms).unwrap(); - let consensus_set = - crate::read_consensus_validator_set_addresses(&s, current_epoch) - .unwrap(); - let proposer_address = consensus_set.iter().next().unwrap().clone(); - - tracing::info!( - ?params.block_proposer_reward, - ?params.block_vote_reward, - ); - tracing::info!(?proposer_address,); - - // Rewards accumulator should be empty at first - let rewards_handle = rewards_accumulator_handle(); - assert!(rewards_handle.is_empty(&s).unwrap()); - - let mut last_rewards = BTreeMap::default(); - - let num_blocks = 100; - // Loop through `num_blocks`, log rewards & check results - for i in 0..num_blocks { - tracing::info!(""); - tracing::info!("Block {}", i + 1); - - // A helper closure to prepare minimum required votes - let prep_votes = |epoch| { - // Ceil of 2/3 of total stake - let min_required_votes = total_stake.mul_ceil(Dec::two() / 3); - - let mut total_votes = token::Amount::zero(); - let mut non_voters = HashSet::
::default(); - let mut prep_vote = |validator| { - // Add validator vote if it's in consensus set and if we don't - // yet have min required votes - if consensus_set.contains(validator) - && total_votes < min_required_votes - { - let stake = - read_validator_stake(&s, ¶ms, validator, epoch) - .unwrap(); - total_votes += stake; - let validator_vp = - into_tm_voting_power(params.tm_votes_per_token, stake) - as u64; - tracing::info!("Validator {validator} signed"); - Some(VoteInfo { - validator_address: validator.clone(), - validator_vp, - }) - } else { - non_voters.insert(validator.clone()); - None - } - }; - - let votes: Vec = validators - .iter() - .rev() - .filter_map(|validator| prep_vote(&validator.address)) - .collect(); - (votes, total_votes, non_voters) - }; - - let (votes, signing_stake, non_voters) = prep_votes(current_epoch); - log_block_rewards( - &mut s, - current_epoch, - &proposer_address, - votes.clone(), - ) - .unwrap(); - - assert!(!rewards_handle.is_empty(&s).unwrap()); - - let rewards_calculator = PosRewardsCalculator { - proposer_reward: params.block_proposer_reward, - signer_reward: params.block_vote_reward, - signing_stake, - total_stake, - }; - let coeffs = rewards_calculator.get_reward_coeffs().unwrap(); - tracing::info!(?coeffs); - - // Check proposer reward - let stake = - read_validator_stake(&s, ¶ms, &proposer_address, current_epoch) - .unwrap(); - let proposer_signing_reward = votes.iter().find_map(|vote| { - if vote.validator_address == proposer_address { - let signing_fraction = - Dec::from(stake) / Dec::from(signing_stake); - Some(coeffs.signer_coeff * signing_fraction) - } else { - None - } - }); - let expected_proposer_rewards = last_rewards.get(&proposer_address).copied().unwrap_or_default() + - // Proposer reward - coeffs.proposer_coeff - // Consensus validator reward - + (coeffs.active_val_coeff - * (Dec::from(stake) / Dec::from(total_stake))) - // Signing reward (if proposer voted) - + proposer_signing_reward - .unwrap_or_default(); - tracing::info!( - "Expected proposer rewards: {expected_proposer_rewards}. Signed \ - block: {}", - proposer_signing_reward.is_some() - ); - assert_eq!( - rewards_handle.get(&s, &proposer_address).unwrap(), - Some(expected_proposer_rewards) - ); - - // Check voters rewards - for VoteInfo { - validator_address, .. - } in votes.iter() - { - // Skip proposer, in case voted - already checked - if validator_address == &proposer_address { - continue; - } - - let stake = read_validator_stake( - &s, - ¶ms, - validator_address, - current_epoch, - ) - .unwrap(); - let signing_fraction = Dec::from(stake) / Dec::from(signing_stake); - let expected_signer_rewards = last_rewards - .get(validator_address) - .copied() - .unwrap_or_default() - + coeffs.signer_coeff * signing_fraction - + (coeffs.active_val_coeff - * (Dec::from(stake) / Dec::from(total_stake))); - tracing::info!( - "Expected signer {validator_address} rewards: \ - {expected_signer_rewards}" - ); - assert_eq!( - rewards_handle.get(&s, validator_address).unwrap(), - Some(expected_signer_rewards) - ); - } - - // Check non-voters rewards, if any - for address in non_voters { - // Skip proposer, in case it didn't vote - already checked - if address == proposer_address { - continue; - } - - if consensus_set.contains(&address) { - let stake = - read_validator_stake(&s, ¶ms, &address, current_epoch) - .unwrap(); - let expected_non_signer_rewards = - last_rewards.get(&address).copied().unwrap_or_default() - + coeffs.active_val_coeff - * (Dec::from(stake) / Dec::from(total_stake)); - tracing::info!( - "Expected non-signer {address} rewards: \ - {expected_non_signer_rewards}" - ); - assert_eq!( - rewards_handle.get(&s, &address).unwrap(), - Some(expected_non_signer_rewards) - ); - } else { - let last_reward = last_rewards.get(&address).copied(); - assert_eq!( - rewards_handle.get(&s, &address).unwrap(), - last_reward - ); - } - } - s.commit_block().unwrap(); - - last_rewards = rewards_accumulator_handle().collect_map(&s).unwrap(); - - let rewards_sum: Dec = last_rewards.values().copied().sum(); - let expected_sum = Dec::one() * (i as u64 + 1); - let err_tolerance = Dec::new(1, 9).unwrap(); - let fail_msg = format!( - "Expected rewards sum at block {} to be {expected_sum}, got \ - {rewards_sum}. Error tolerance {err_tolerance}.", - i + 1 - ); - assert!(expected_sum <= rewards_sum + err_tolerance, "{fail_msg}"); - assert!(rewards_sum <= expected_sum, "{fail_msg}"); - } -} - -fn test_update_rewards_products_aux(validators: Vec) { - tracing::info!( - "New case with {} validators: {:#?}", - validators.len(), - validators - .iter() - .map(|v| (&v.address, v.tokens.to_string_native())) - .collect::>() - ); - let mut s = TestWlStorage::default(); - // Init genesis - let current_epoch = s.storage.block.epoch; - let params = OwnedPosParams::default(); - let params = test_init_genesis( - &mut s, - params, - validators.into_iter(), - current_epoch, - ) - .unwrap(); - s.commit_block().unwrap(); - - let staking_token = staking_token_address(&s); - let consensus_set = - crate::read_consensus_validator_set_addresses(&s, current_epoch) - .unwrap(); - - // Start a new epoch - let current_epoch = advance_epoch(&mut s, ¶ms); - - // Read some data before applying rewards - let pos_balance_pre = - read_balance(&s, &staking_token, &address::POS).unwrap(); - let gov_balance_pre = - read_balance(&s, &staking_token, &address::GOV).unwrap(); - - let num_consensus_validators = consensus_set.len() as u64; - let accum_val = Dec::one() / num_consensus_validators; - let num_blocks_in_last_epoch = 1000; - - // Assign some reward accumulator values to consensus validator - for validator in &consensus_set { - rewards_accumulator_handle() - .insert( - &mut s, - validator.clone(), - accum_val * num_blocks_in_last_epoch, - ) - .unwrap(); - } - - // Distribute inflation into rewards - let last_epoch = current_epoch.prev(); - let inflation = token::Amount::native_whole(10_000_000); - update_rewards_products_and_mint_inflation( - &mut s, - ¶ms, - last_epoch, - num_blocks_in_last_epoch, - inflation, - &staking_token, - ) - .unwrap(); - - let pos_balance_post = - read_balance(&s, &staking_token, &address::POS).unwrap(); - let gov_balance_post = - read_balance(&s, &staking_token, &address::GOV).unwrap(); - - assert_eq!( - pos_balance_pre + gov_balance_pre + inflation, - pos_balance_post + gov_balance_post, - "Expected inflation to be minted to PoS and left-over amount to Gov" - ); - - let pos_credit = pos_balance_post - pos_balance_pre; - let gov_credit = gov_balance_post - gov_balance_pre; - assert!( - pos_credit > gov_credit, - "PoS must receive more tokens than Gov, but got {} in PoS and {} in \ - Gov", - pos_credit.to_string_native(), - gov_credit.to_string_native() - ); - - // Rewards accumulator must be cleared out - let rewards_handle = rewards_accumulator_handle(); - assert!(rewards_handle.is_empty(&s).unwrap()); -} - -fn test_slashed_bond_amount_aux(validators: Vec) { - let mut storage = TestWlStorage::default(); - let params = OwnedPosParams { - unbonding_len: 4, - ..Default::default() - }; - - let init_tot_stake = validators - .clone() - .into_iter() - .fold(token::Amount::zero(), |acc, v| acc + v.tokens); - let val1_init_stake = validators[0].tokens; - - let mut validators = validators; - validators[0].tokens = (init_tot_stake - val1_init_stake) / 30; - - // Genesis - let mut current_epoch = storage.storage.block.epoch; - let params = test_init_genesis( - &mut storage, - params, - validators.clone().into_iter(), - current_epoch, - ) - .unwrap(); - storage.commit_block().unwrap(); - - let validator1 = validators[0].address.clone(); - let validator2 = validators[1].address.clone(); - - // Get a delegator with some tokens - let staking_token = staking_token_address(&storage); - let delegator = address::testing::gen_implicit_address(); - let del_balance = token::Amount::from_uint(1_000_000, 0).unwrap(); - credit_tokens(&mut storage, &staking_token, &delegator, del_balance) - .unwrap(); - - // Bond to validator 1 - super::bond_tokens( - &mut storage, - Some(&delegator), - &validator1, - 10_000.into(), - current_epoch, - None, - ) - .unwrap(); - - // Unbond some from validator 1 - super::unbond_tokens( - &mut storage, - Some(&delegator), - &validator1, - 1_342.into(), - current_epoch, - false, - ) - .unwrap(); - - // Redelegate some from validator 1 -> 2 - super::redelegate_tokens( - &mut storage, - &delegator, - &validator1, - &validator2, - current_epoch, - 1_875.into(), - ) - .unwrap(); - - // Unbond some from validator 2 - super::unbond_tokens( - &mut storage, - Some(&delegator), - &validator2, - 584.into(), - current_epoch, - false, - ) - .unwrap(); - - // Advance an epoch to 1 - current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); - - // Bond to validator 1 - super::bond_tokens( - &mut storage, - Some(&delegator), - &validator1, - 384.into(), - current_epoch, - None, - ) - .unwrap(); - - // Unbond some from validator 1 - super::unbond_tokens( - &mut storage, - Some(&delegator), - &validator1, - 144.into(), - current_epoch, - false, - ) - .unwrap(); - - // Redelegate some from validator 1 -> 2 - super::redelegate_tokens( - &mut storage, - &delegator, - &validator1, - &validator2, - current_epoch, - 3_448.into(), - ) - .unwrap(); - - // Unbond some from validator 2 - super::unbond_tokens( - &mut storage, - Some(&delegator), - &validator2, - 699.into(), - current_epoch, - false, - ) - .unwrap(); - - // Advance an epoch to ep 2 - current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); - - // Bond to validator 1 - super::bond_tokens( - &mut storage, - Some(&delegator), - &validator1, - 4_384.into(), - current_epoch, - None, - ) - .unwrap(); - - // Redelegate some from validator 1 -> 2 - super::redelegate_tokens( - &mut storage, - &delegator, - &validator1, - &validator2, - current_epoch, - 1_008.into(), - ) - .unwrap(); - - // Unbond some from validator 2 - super::unbond_tokens( - &mut storage, - Some(&delegator), - &validator2, - 3_500.into(), - current_epoch, - false, - ) - .unwrap(); - - // Advance two epochs to ep 4 - for _ in 0..2 { - current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); - } - - // Find some slashes committed in various epochs - slash( - &mut storage, - ¶ms, - current_epoch, - Epoch(1), - 1_u64, - SlashType::DuplicateVote, - &validator1, - current_epoch, - ) - .unwrap(); - slash( - &mut storage, - ¶ms, - current_epoch, - Epoch(2), - 1_u64, - SlashType::DuplicateVote, - &validator1, - current_epoch, - ) - .unwrap(); - slash( - &mut storage, - ¶ms, - current_epoch, - Epoch(2), - 1_u64, - SlashType::DuplicateVote, - &validator1, - current_epoch, - ) - .unwrap(); - slash( - &mut storage, - ¶ms, - current_epoch, - Epoch(3), - 1_u64, - SlashType::DuplicateVote, - &validator1, - current_epoch, - ) - .unwrap(); - - // Advance such that these slashes are all processed - for _ in 0..params.slash_processing_epoch_offset() { - current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); - } - - let pipeline_epoch = current_epoch + params.pipeline_len; - - let del_bond_amount = crate::bond_amount( - &storage, - &BondId { - source: delegator.clone(), - validator: validator1.clone(), - }, - pipeline_epoch, - ) - .unwrap_or_default(); - - let self_bond_amount = crate::bond_amount( - &storage, - &BondId { - source: validator1.clone(), - validator: validator1.clone(), - }, - pipeline_epoch, - ) - .unwrap_or_default(); - - let val_stake = crate::read_validator_stake( - &storage, - ¶ms, - &validator1, - pipeline_epoch, - ) - .unwrap(); - // dbg!(&val_stake); - // dbg!(&del_bond_amount); - // dbg!(&self_bond_amount); - - let diff = val_stake - self_bond_amount - del_bond_amount; - assert!(diff <= 2.into()); -} - -fn test_consensus_key_change_aux(validators: Vec) { - assert_eq!(validators.len(), 1); - - let params = OwnedPosParams { - unbonding_len: 4, - ..Default::default() - }; - let validator = validators[0].address.clone(); - - println!("\nTest inputs: {params:?}, genesis validators: {validators:#?}"); - let mut storage = TestWlStorage::default(); - - // Genesis - let mut current_epoch = storage.storage.block.epoch; - let params = test_init_genesis( - &mut storage, - params, - validators.into_iter(), - current_epoch, - ) - .unwrap(); - storage.commit_block().unwrap(); - - // Check that there is one consensus key in the network - let consensus_keys = get_consensus_key_set(&storage).unwrap(); - assert_eq!(consensus_keys.len(), 1); - let ck = consensus_keys.first().cloned().unwrap(); - let og_ck = validator_consensus_key_handle(&validator) - .get(&storage, current_epoch, ¶ms) - .unwrap() - .unwrap(); - assert_eq!(ck, og_ck); - - // Attempt to change to a new secp256k1 consensus key (disallowed) - let secp_ck = gen_keypair::(); - let secp_ck = key::common::SecretKey::Secp256k1(secp_ck).ref_to(); - let res = - change_consensus_key(&mut storage, &validator, &secp_ck, current_epoch); - assert!(res.is_err()); - - // Change consensus keys - let ck_2 = common_sk_from_simple_seed(1).ref_to(); - change_consensus_key(&mut storage, &validator, &ck_2, current_epoch) - .unwrap(); - - // Check that there is a new consensus key - let consensus_keys = get_consensus_key_set(&storage).unwrap(); - assert_eq!(consensus_keys.len(), 2); - - for epoch in current_epoch.iter_range(params.pipeline_len) { - let ck = validator_consensus_key_handle(&validator) - .get(&storage, epoch, ¶ms) - .unwrap() - .unwrap(); - assert_eq!(ck, og_ck); - } - let pipeline_epoch = current_epoch + params.pipeline_len; - let ck = validator_consensus_key_handle(&validator) - .get(&storage, pipeline_epoch, ¶ms) - .unwrap() - .unwrap(); - assert_eq!(ck, ck_2); - - // Advance to the pipeline epoch - loop { - current_epoch = advance_epoch(&mut storage, ¶ms); - if current_epoch == pipeline_epoch { - break; - } - } - - // Check the consensus keys again - let consensus_keys = get_consensus_key_set(&storage).unwrap(); - assert_eq!(consensus_keys.len(), 2); - - for epoch in current_epoch.iter_range(params.pipeline_len + 1) { - let ck = validator_consensus_key_handle(&validator) - .get(&storage, epoch, ¶ms) - .unwrap() - .unwrap(); - assert_eq!(ck, ck_2); - } - - // Now change the consensus key again and bond in the same epoch - let ck_3 = common_sk_from_simple_seed(3).ref_to(); - change_consensus_key(&mut storage, &validator, &ck_3, current_epoch) - .unwrap(); - - let staking_token = storage.storage.native_token.clone(); - let amount_del = token::Amount::native_whole(5); - credit_tokens(&mut storage, &staking_token, &validator, amount_del) - .unwrap(); - bond_tokens( - &mut storage, - None, - &validator, - token::Amount::native_whole(1), - current_epoch, - None, - ) - .unwrap(); - - // Check consensus keys again - let consensus_keys = get_consensus_key_set(&storage).unwrap(); - assert_eq!(consensus_keys.len(), 3); - - for epoch in current_epoch.iter_range(params.pipeline_len) { - let ck = validator_consensus_key_handle(&validator) - .get(&storage, epoch, ¶ms) - .unwrap() - .unwrap(); - assert_eq!(ck, ck_2); - } - let pipeline_epoch = current_epoch + params.pipeline_len; - let ck = validator_consensus_key_handle(&validator) - .get(&storage, pipeline_epoch, ¶ms) - .unwrap() - .unwrap(); - assert_eq!(ck, ck_3); - - // Advance to the pipeline epoch to ensure that the validator set updates to - // tendermint will work - loop { - current_epoch = advance_epoch(&mut storage, ¶ms); - if current_epoch == pipeline_epoch { - break; - } - } - assert_eq!(current_epoch.0, 2 * params.pipeline_len); -} - -fn test_is_delegator_aux(mut validators: Vec) { - validators.sort_by(|a, b| b.tokens.cmp(&a.tokens)); - - let validator1 = validators[0].address.clone(); - let validator2 = validators[1].address.clone(); - - let mut storage = TestWlStorage::default(); - let params = OwnedPosParams { - unbonding_len: 4, - ..Default::default() - }; - - // Genesis - let mut current_epoch = storage.storage.block.epoch; - let params = test_init_genesis( - &mut storage, - params, - validators.clone().into_iter(), - current_epoch, - ) - .unwrap(); - storage.commit_block().unwrap(); - - // Get delegators with some tokens - let staking_token = staking_token_address(&storage); - let delegator1 = address::testing::gen_implicit_address(); - let delegator2 = address::testing::gen_implicit_address(); - let del_balance = token::Amount::native_whole(1000); - credit_tokens(&mut storage, &staking_token, &delegator1, del_balance) - .unwrap(); - credit_tokens(&mut storage, &staking_token, &delegator2, del_balance) - .unwrap(); - - // Advance to epoch 1 - current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); - - // Delegate in epoch 1 to validator1 - let del1_epoch = current_epoch; - super::bond_tokens( - &mut storage, - Some(&delegator1), - &validator1, - 1000.into(), - current_epoch, - None, - ) - .unwrap(); - - // Advance to epoch 2 - current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); - - // Delegate in epoch 2 to validator2 - let del2_epoch = current_epoch; - super::bond_tokens( - &mut storage, - Some(&delegator2), - &validator2, - 1000.into(), - current_epoch, - None, - ) - .unwrap(); - - // Checks - assert!(super::is_validator(&storage, &validator1).unwrap()); - assert!(super::is_validator(&storage, &validator2).unwrap()); - assert!(!super::is_delegator(&storage, &validator1, None).unwrap()); - assert!(!super::is_delegator(&storage, &validator2, None).unwrap()); - - assert!(!super::is_validator(&storage, &delegator1).unwrap()); - assert!(!super::is_validator(&storage, &delegator2).unwrap()); - assert!(super::is_delegator(&storage, &delegator1, None).unwrap()); - assert!(super::is_delegator(&storage, &delegator2, None).unwrap()); - - for epoch in Epoch::default().iter_range(del1_epoch.0 + params.pipeline_len) - { - assert!( - !super::is_delegator(&storage, &delegator1, Some(epoch)).unwrap() - ); - } - assert!( - super::is_delegator( - &storage, - &delegator1, - Some(del1_epoch + params.pipeline_len) - ) - .unwrap() - ); - for epoch in Epoch::default().iter_range(del2_epoch.0 + params.pipeline_len) - { - assert!( - !super::is_delegator(&storage, &delegator2, Some(epoch)).unwrap() - ); - } - assert!( - super::is_delegator( - &storage, - &delegator2, - Some(del2_epoch + params.pipeline_len) - ) - .unwrap() - ); -} diff --git a/proof_of_stake/src/tests/helpers.rs b/proof_of_stake/src/tests/helpers.rs new file mode 100644 index 0000000000..ddf8bba4c6 --- /dev/null +++ b/proof_of_stake/src/tests/helpers.rs @@ -0,0 +1,173 @@ +use std::cmp::max; +use std::ops::Range; + +use namada_core::ledger::storage::testing::TestWlStorage; +use namada_core::types::address::testing::address_from_simple_seed; +use namada_core::types::dec::Dec; +use namada_core::types::key::testing::common_sk_from_simple_seed; +use namada_core::types::key::{self, RefTo}; +use namada_core::types::storage::Epoch; +use namada_core::types::token; +use namada_core::types::token::testing::arb_amount_non_zero_ceiled; +use proptest::strategy::{Just, Strategy}; + +use crate::parameters::testing::arb_pos_params; +use crate::types::{GenesisValidator, ValidatorSetUpdate}; +use crate::validator_set_update::{ + copy_validator_sets_and_positions, validator_set_update_tendermint, +}; +use crate::{ + compute_and_store_total_consensus_stake, OwnedPosParams, PosParams, +}; + +pub fn arb_params_and_genesis_validators( + num_max_validator_slots: Option, + val_size: Range, +) -> impl Strategy)> { + let params = arb_pos_params(num_max_validator_slots); + params.prop_flat_map(move |params| { + let validators = arb_genesis_validators( + val_size.clone(), + Some(params.validator_stake_threshold), + ); + (Just(params), validators) + }) +} + +pub fn test_slashes_with_unbonding_params() +-> impl Strategy, u64)> { + let params = arb_pos_params(Some(5)); + params.prop_flat_map(|params| { + let unbond_delay = 0..(params.slash_processing_epoch_offset() * 2); + // Must have at least 4 validators so we can slash one and the cubic + // slash rate will be less than 100% + let validators = arb_genesis_validators(4..10, None); + (Just(params), validators, unbond_delay) + }) +} + +pub fn get_tendermint_set_updates( + s: &TestWlStorage, + params: &PosParams, + Epoch(epoch): Epoch, +) -> Vec { + // Because the `validator_set_update_tendermint` is called 2 blocks before + // the start of a new epoch, it expects to receive the epoch that is before + // the start of a new one too and so we give it the predecessor of the + // current epoch here to actually get the update for the current epoch. + let epoch = Epoch(epoch - 1); + validator_set_update_tendermint(s, params, epoch, |update| update).unwrap() +} + +/// Advance to the next epoch. Returns the new epoch. +pub fn advance_epoch(s: &mut TestWlStorage, params: &PosParams) -> Epoch { + s.storage.block.epoch = s.storage.block.epoch.next(); + let current_epoch = s.storage.block.epoch; + compute_and_store_total_consensus_stake(s, current_epoch).unwrap(); + copy_validator_sets_and_positions( + s, + params, + current_epoch, + current_epoch + params.pipeline_len, + ) + .unwrap(); + // purge_validator_sets_for_old_epoch(s, current_epoch).unwrap(); + // process_slashes(s, current_epoch).unwrap(); + // dbg!(current_epoch); + current_epoch +} + +pub fn arb_genesis_validators( + size: Range, + threshold: Option, +) -> impl Strategy> { + let threshold = threshold + .unwrap_or_else(|| PosParams::default().validator_stake_threshold); + let tokens: Vec<_> = (0..size.end) + .map(|ix| { + if ix == 0 { + // Make sure that at least one validator has at least a stake + // greater or equal to the threshold to avoid having an empty + // consensus set. + threshold.raw_amount().as_u64()..=10_000_000_u64 + } else { + 1..=10_000_000_u64 + } + .prop_map(token::Amount::from) + }) + .collect(); + (size, tokens) + .prop_map(|(size, token_amounts)| { + // use unique seeds to generate validators' address and consensus + // key + let seeds = (0_u64..).take(size); + seeds + .zip(token_amounts) + .map(|(seed, tokens)| { + let address = address_from_simple_seed(seed); + let consensus_sk = common_sk_from_simple_seed(seed); + let consensus_key = consensus_sk.to_public(); + + let protocol_sk = common_sk_from_simple_seed(seed); + let protocol_key = protocol_sk.to_public(); + + let eth_hot_key = key::common::PublicKey::Secp256k1( + key::testing::gen_keypair::( + ) + .ref_to(), + ); + let eth_cold_key = key::common::PublicKey::Secp256k1( + key::testing::gen_keypair::( + ) + .ref_to(), + ); + + let commission_rate = Dec::new(5, 2).expect("Test failed"); + let max_commission_rate_change = + Dec::new(1, 2).expect("Test failed"); + GenesisValidator { + address, + tokens, + consensus_key, + protocol_key, + eth_hot_key, + eth_cold_key, + commission_rate, + max_commission_rate_change, + metadata: Default::default(), + } + }) + .collect() + }) + .prop_filter( + "Must have at least one genesis validator with stake above the \ + provided threshold, if any.", + move |gen_vals: &Vec| { + gen_vals.iter().any(|val| val.tokens >= threshold) + }, + ) +} + +pub fn arb_redelegation_amounts( + max_delegation: u64, +) -> impl Strategy { + let arb_delegation = arb_amount_non_zero_ceiled(max_delegation); + let amounts = arb_delegation.prop_flat_map(move |amount_delegate| { + let amount_redelegate = arb_amount_non_zero_ceiled(max( + 1, + u64::try_from(amount_delegate.raw_amount()).unwrap() - 1, + )); + (Just(amount_delegate), amount_redelegate) + }); + amounts.prop_flat_map(move |(amount_delegate, amount_redelegate)| { + let amount_unbond = arb_amount_non_zero_ceiled(max( + 1, + u64::try_from(amount_redelegate.raw_amount()).unwrap() - 1, + )); + ( + Just(amount_delegate), + Just(amount_redelegate), + amount_unbond, + ) + }) +} diff --git a/proof_of_stake/src/tests/state_machine.rs b/proof_of_stake/src/tests/state_machine.rs index 395c57d7ef..1d07d6465b 100644 --- a/proof_of_stake/src/tests/state_machine.rs +++ b/proof_of_stake/src/tests/state_machine.rs @@ -36,7 +36,7 @@ use crate::storage::{ read_below_threshold_validator_set_addresses, read_consensus_validator_set_addresses_with_stake, }; -use crate::tests::arb_params_and_genesis_validators; +use crate::tests::helpers::{advance_epoch, arb_params_and_genesis_validators}; use crate::types::{ BondId, EagerRedelegatedBondsMap, GenesisValidator, ReverseOrdTokenAmount, Slash, SlashType, ValidatorState, WeightedValidator, @@ -249,11 +249,12 @@ impl StateMachineTest for ConcretePosState { match transition { Transition::NextEpoch => { tracing::debug!("\nCONCRETE Next epoch"); - super::advance_epoch(&mut state.s, ¶ms); + advance_epoch(&mut state.s, ¶ms); // Need to apply some slashing let current_epoch = state.s.storage.block.epoch; - super::process_slashes(&mut state.s, current_epoch).unwrap(); + crate::slashing::process_slashes(&mut state.s, current_epoch) + .unwrap(); let params = read_pos_params(&state.s).unwrap(); state.check_next_epoch_post_conditions(¶ms); @@ -270,9 +271,9 @@ impl StateMachineTest for ConcretePosState { tracing::debug!("\nCONCRETE Init validator"); let current_epoch = state.current_epoch(); - super::become_validator( + crate::become_validator( &mut state.s, - super::BecomeValidator { + crate::BecomeValidator { params: ¶ms, address: &address, consensus_key: &consensus_key, @@ -342,7 +343,7 @@ impl StateMachineTest for ConcretePosState { ); // Apply the bond - super::bond_tokens( + crate::bond_tokens( &mut state.s, Some(&id.source), &id.validator, @@ -409,7 +410,7 @@ impl StateMachineTest for ConcretePosState { .unwrap(); // Apply the unbond - super::unbond_tokens( + crate::unbond_tokens( &mut state.s, Some(&id.source), &id.validator, @@ -460,7 +461,7 @@ impl StateMachineTest for ConcretePosState { .unwrap(); // Apply the withdrawal - let withdrawn = super::withdraw_tokens( + let withdrawn = crate::withdraw_tokens( &mut state.s, Some(&source), &validator, @@ -714,7 +715,7 @@ impl StateMachineTest for ConcretePosState { tracing::debug!("\nCONCRETE Misbehavior"); let current_epoch = state.current_epoch(); // Record the slash evidence - super::slash( + crate::slashing::slash( &mut state.s, ¶ms, current_epoch, @@ -743,7 +744,7 @@ impl StateMachineTest for ConcretePosState { let current_epoch = state.current_epoch(); // Unjail the validator - super::unjail_validator(&mut state.s, &address, current_epoch) + crate::unjail_validator(&mut state.s, &address, current_epoch) .unwrap(); // Post-conditions @@ -853,7 +854,7 @@ impl ConcretePosState { ) { let pipeline = submit_epoch + params.pipeline_len; - let cur_stake = super::read_validator_stake( + let cur_stake = crate::read_validator_stake( &self.s, params, &id.validator, @@ -865,7 +866,7 @@ impl ConcretePosState { // change assert_eq!(cur_stake, validator_stake_before_bond_cur); - let stake_at_pipeline = super::read_validator_stake( + let stake_at_pipeline = crate::read_validator_stake( &self.s, params, &id.validator, @@ -922,7 +923,7 @@ impl ConcretePosState { ) { let pipeline = submit_epoch + params.pipeline_len; - let cur_stake = super::read_validator_stake( + let cur_stake = crate::read_validator_stake( &self.s, params, &id.validator, @@ -934,7 +935,7 @@ impl ConcretePosState { // change assert_eq!(cur_stake, validator_stake_before_unbond_cur); - let stake_at_pipeline = super::read_validator_stake( + let stake_at_pipeline = crate::read_validator_stake( &self.s, params, &id.validator, diff --git a/proof_of_stake/src/tests/state_machine_v2.rs b/proof_of_stake/src/tests/state_machine_v2.rs index 828a2eb766..c75d629995 100644 --- a/proof_of_stake/src/tests/state_machine_v2.rs +++ b/proof_of_stake/src/tests/state_machine_v2.rs @@ -29,6 +29,7 @@ use proptest_state_machine::{ use test_log::test; use yansi::Paint; +use super::helpers::advance_epoch; use super::utils::DbgPrintDiff; use crate::parameters::testing::arb_rate; use crate::parameters::PosParams; @@ -41,7 +42,7 @@ use crate::storage::{ read_below_threshold_validator_set_addresses, read_consensus_validator_set_addresses_with_stake, }; -use crate::tests::arb_params_and_genesis_validators; +use crate::tests::helpers::arb_params_and_genesis_validators; use crate::tests::utils::pause_for_enter; use crate::types::{ BondId, GenesisValidator, ReverseOrdTokenAmount, Slash, SlashType, @@ -1978,11 +1979,12 @@ impl StateMachineTest for ConcretePosState { match transition { Transition::NextEpoch => { tracing::debug!("\nCONCRETE Next epoch"); - super::advance_epoch(&mut state.s, ¶ms); + advance_epoch(&mut state.s, ¶ms); // Need to apply some slashing let current_epoch = state.s.storage.block.epoch; - super::process_slashes(&mut state.s, current_epoch).unwrap(); + crate::slashing::process_slashes(&mut state.s, current_epoch) + .unwrap(); let params = read_pos_params(&state.s).unwrap(); state.check_next_epoch_post_conditions(¶ms); @@ -1999,9 +2001,9 @@ impl StateMachineTest for ConcretePosState { tracing::debug!("\nCONCRETE Init validator"); let current_epoch = state.current_epoch(); - super::become_validator( + crate::become_validator( &mut state.s, - super::BecomeValidator { + crate::BecomeValidator { params: ¶ms, address: &address, consensus_key: &consensus_key, @@ -2071,7 +2073,7 @@ impl StateMachineTest for ConcretePosState { ); // Apply the bond - super::bond_tokens( + crate::bond_tokens( &mut state.s, Some(&id.source), &id.validator, @@ -2136,7 +2138,7 @@ impl StateMachineTest for ConcretePosState { .unwrap(); // Apply the unbond - super::unbond_tokens( + crate::unbond_tokens( &mut state.s, Some(&id.source), &id.validator, @@ -2212,7 +2214,7 @@ impl StateMachineTest for ConcretePosState { // .unwrap(); // Apply the withdrawal - let withdrawn = super::withdraw_tokens( + let withdrawn = crate::withdraw_tokens( &mut state.s, Some(&source), &validator, @@ -2668,7 +2670,7 @@ impl StateMachineTest for ConcretePosState { tracing::debug!("\nCONCRETE Misbehavior"); let current_epoch = state.current_epoch(); // Record the slash evidence - super::slash( + crate::slashing::slash( &mut state.s, ¶ms, current_epoch, @@ -2697,7 +2699,7 @@ impl StateMachineTest for ConcretePosState { let current_epoch = state.current_epoch(); // Unjail the validator - super::unjail_validator(&mut state.s, &address, current_epoch) + crate::unjail_validator(&mut state.s, &address, current_epoch) .unwrap(); // Post-conditions @@ -2807,7 +2809,7 @@ impl ConcretePosState { ) { let pipeline = submit_epoch + params.pipeline_len; - let cur_stake = super::read_validator_stake( + let cur_stake = crate::read_validator_stake( &self.s, params, &id.validator, @@ -2819,7 +2821,7 @@ impl ConcretePosState { // change assert_eq!(cur_stake, validator_stake_before_bond_cur); - let stake_at_pipeline = super::read_validator_stake( + let stake_at_pipeline = crate::read_validator_stake( &self.s, params, &id.validator, @@ -2853,7 +2855,7 @@ impl ConcretePosState { ) { let pipeline = submit_epoch + params.pipeline_len; - let cur_stake = super::read_validator_stake( + let cur_stake = crate::read_validator_stake( &self.s, params, &id.validator, @@ -2865,7 +2867,7 @@ impl ConcretePosState { // change assert_eq!(cur_stake, validator_stake_before_unbond_cur); - let stake_at_pipeline = super::read_validator_stake( + let stake_at_pipeline = crate::read_validator_stake( &self.s, params, &id.validator, diff --git a/proof_of_stake/src/tests/test_helper_fns.rs b/proof_of_stake/src/tests/test_helper_fns.rs new file mode 100644 index 0000000000..594965fe43 --- /dev/null +++ b/proof_of_stake/src/tests/test_helper_fns.rs @@ -0,0 +1,2034 @@ +use std::collections::{BTreeMap, BTreeSet}; + +use namada_core::ledger::storage::testing::TestWlStorage; +use namada_core::ledger::storage_api::collections::lazy_map::NestedMap; +use namada_core::ledger::storage_api::collections::LazyCollection; +use namada_core::types::address::testing::{ + established_address_1, established_address_2, established_address_3, +}; +use namada_core::types::dec::Dec; +use namada_core::types::storage::{Epoch, Key}; +use namada_core::types::token; + +use crate::slashing::{ + apply_list_slashes, compute_amount_after_slashing_unbond, + compute_amount_after_slashing_withdraw, compute_bond_at_epoch, + compute_slash_bond_at_epoch, compute_slashable_amount, slash_redelegation, + slash_validator, slash_validator_redelegation, +}; +use crate::storage::{ + bond_handle, delegator_redelegated_bonds_handle, total_bonded_handle, + total_unbonded_handle, validator_outgoing_redelegations_handle, + validator_slashes_handle, validator_total_redelegated_bonded_handle, + validator_total_redelegated_unbonded_handle, write_pos_params, +}; +use crate::types::{ + EagerRedelegatedBondsMap, RedelegatedTokens, Slash, SlashType, +}; +use crate::{ + compute_modified_redelegation, compute_new_redelegated_unbonds, + find_bonds_to_remove, fold_and_slash_redelegated_bonds, + EagerRedelegatedUnbonds, FoldRedelegatedBondsResult, ModifiedRedelegation, + OwnedPosParams, +}; + +/// `iterateBondsUpToAmountTest` +#[test] +fn test_find_bonds_to_remove() { + let mut storage = TestWlStorage::default(); + let gov_params = namada_core::ledger::governance::parameters::GovernanceParameters::default(); + gov_params.init_storage(&mut storage).unwrap(); + write_pos_params(&mut storage, &OwnedPosParams::default()).unwrap(); + + let source = established_address_1(); + let validator = established_address_2(); + let bond_handle = bond_handle(&source, &validator); + + let (e1, e2, e6) = (Epoch(1), Epoch(2), Epoch(6)); + + bond_handle + .set(&mut storage, token::Amount::from(5), e1, 0) + .unwrap(); + bond_handle + .set(&mut storage, token::Amount::from(3), e2, 0) + .unwrap(); + bond_handle + .set(&mut storage, token::Amount::from(8), e6, 0) + .unwrap(); + + // Test 1 + let bonds_for_removal = find_bonds_to_remove( + &storage, + &bond_handle.get_data_handler(), + token::Amount::from(8), + ) + .unwrap(); + assert_eq!( + bonds_for_removal.epochs, + vec![e6].into_iter().collect::>() + ); + assert!(bonds_for_removal.new_entry.is_none()); + + // Test 2 + let bonds_for_removal = find_bonds_to_remove( + &storage, + &bond_handle.get_data_handler(), + token::Amount::from(10), + ) + .unwrap(); + assert_eq!( + bonds_for_removal.epochs, + vec![e6].into_iter().collect::>() + ); + assert_eq!( + bonds_for_removal.new_entry, + Some((Epoch(2), token::Amount::from(1))) + ); + + // Test 3 + let bonds_for_removal = find_bonds_to_remove( + &storage, + &bond_handle.get_data_handler(), + token::Amount::from(11), + ) + .unwrap(); + assert_eq!( + bonds_for_removal.epochs, + vec![e6, e2].into_iter().collect::>() + ); + assert!(bonds_for_removal.new_entry.is_none()); + + // Test 4 + let bonds_for_removal = find_bonds_to_remove( + &storage, + &bond_handle.get_data_handler(), + token::Amount::from(12), + ) + .unwrap(); + assert_eq!( + bonds_for_removal.epochs, + vec![e6, e2].into_iter().collect::>() + ); + assert_eq!( + bonds_for_removal.new_entry, + Some((Epoch(1), token::Amount::from(4))) + ); +} + +/// `computeModifiedRedelegationTest` +#[test] +fn test_compute_modified_redelegation() { + let mut storage = TestWlStorage::default(); + let validator1 = established_address_1(); + let validator2 = established_address_2(); + let owner = established_address_3(); + let outer_epoch = Epoch(0); + + let mut alice = validator1.clone(); + let mut bob = validator2.clone(); + + // Ensure a ranking order of alice > bob + if bob > alice { + alice = validator2; + bob = validator1; + } + println!("\n\nalice = {}\nbob = {}\n", &alice, &bob); + + // Fill redelegated bonds in storage + let redelegated_bonds_map = delegator_redelegated_bonds_handle(&owner) + .at(&alice) + .at(&outer_epoch); + redelegated_bonds_map + .at(&alice) + .insert(&mut storage, Epoch(2), token::Amount::from(6)) + .unwrap(); + redelegated_bonds_map + .at(&alice) + .insert(&mut storage, Epoch(4), token::Amount::from(7)) + .unwrap(); + redelegated_bonds_map + .at(&bob) + .insert(&mut storage, Epoch(1), token::Amount::from(5)) + .unwrap(); + redelegated_bonds_map + .at(&bob) + .insert(&mut storage, Epoch(4), token::Amount::from(7)) + .unwrap(); + + // Test cases 1 and 2 + let mr1 = compute_modified_redelegation( + &storage, + &redelegated_bonds_map, + Epoch(5), + token::Amount::from(25), + ) + .unwrap(); + let mr2 = compute_modified_redelegation( + &storage, + &redelegated_bonds_map, + Epoch(5), + token::Amount::from(30), + ) + .unwrap(); + + let exp_mr = ModifiedRedelegation { + epoch: Some(Epoch(5)), + ..Default::default() + }; + + assert_eq!(mr1, exp_mr); + assert_eq!(mr2, exp_mr); + + // Test case 3 + let mr3 = compute_modified_redelegation( + &storage, + &redelegated_bonds_map, + Epoch(5), + token::Amount::from(7), + ) + .unwrap(); + + let exp_mr = ModifiedRedelegation { + epoch: Some(Epoch(5)), + validators_to_remove: BTreeSet::from_iter([bob.clone()]), + validator_to_modify: Some(bob.clone()), + epochs_to_remove: BTreeSet::from_iter([Epoch(4)]), + ..Default::default() + }; + assert_eq!(mr3, exp_mr); + + // Test case 4 + let mr4 = compute_modified_redelegation( + &storage, + &redelegated_bonds_map, + Epoch(5), + token::Amount::from(8), + ) + .unwrap(); + + let exp_mr = ModifiedRedelegation { + epoch: Some(Epoch(5)), + validators_to_remove: BTreeSet::from_iter([bob.clone()]), + validator_to_modify: Some(bob.clone()), + epochs_to_remove: BTreeSet::from_iter([Epoch(1), Epoch(4)]), + epoch_to_modify: Some(Epoch(1)), + new_amount: Some(4.into()), + }; + assert_eq!(mr4, exp_mr); + + // Test case 5 + let mr5 = compute_modified_redelegation( + &storage, + &redelegated_bonds_map, + Epoch(5), + 12.into(), + ) + .unwrap(); + + let exp_mr = ModifiedRedelegation { + epoch: Some(Epoch(5)), + validators_to_remove: BTreeSet::from_iter([bob.clone()]), + ..Default::default() + }; + assert_eq!(mr5, exp_mr); + + // Test case 6 + let mr6 = compute_modified_redelegation( + &storage, + &redelegated_bonds_map, + Epoch(5), + 14.into(), + ) + .unwrap(); + + let exp_mr = ModifiedRedelegation { + epoch: Some(Epoch(5)), + validators_to_remove: BTreeSet::from_iter([alice.clone(), bob.clone()]), + validator_to_modify: Some(alice.clone()), + epochs_to_remove: BTreeSet::from_iter([Epoch(4)]), + epoch_to_modify: Some(Epoch(4)), + new_amount: Some(5.into()), + }; + assert_eq!(mr6, exp_mr); + + // Test case 7 + let mr7 = compute_modified_redelegation( + &storage, + &redelegated_bonds_map, + Epoch(5), + 19.into(), + ) + .unwrap(); + + let exp_mr = ModifiedRedelegation { + epoch: Some(Epoch(5)), + validators_to_remove: BTreeSet::from_iter([alice.clone(), bob.clone()]), + validator_to_modify: Some(alice.clone()), + epochs_to_remove: BTreeSet::from_iter([Epoch(4)]), + ..Default::default() + }; + assert_eq!(mr7, exp_mr); + + // Test case 8 + let mr8 = compute_modified_redelegation( + &storage, + &redelegated_bonds_map, + Epoch(5), + 21.into(), + ) + .unwrap(); + + let exp_mr = ModifiedRedelegation { + epoch: Some(Epoch(5)), + validators_to_remove: BTreeSet::from_iter([alice.clone(), bob]), + validator_to_modify: Some(alice), + epochs_to_remove: BTreeSet::from_iter([Epoch(2), Epoch(4)]), + epoch_to_modify: Some(Epoch(2)), + new_amount: Some(4.into()), + }; + assert_eq!(mr8, exp_mr); +} + +/// `computeBondAtEpochTest` +#[test] +fn test_compute_bond_at_epoch() { + let mut storage = TestWlStorage::default(); + let params = OwnedPosParams { + pipeline_len: 2, + unbonding_len: 4, + cubic_slashing_window_length: 1, + ..Default::default() + }; + let alice = established_address_1(); + let bob = established_address_2(); + + // Test 1 + let res = compute_bond_at_epoch( + &storage, + ¶ms, + &bob, + 12.into(), + 3.into(), + 23.into(), + Some(&Default::default()), + ) + .unwrap(); + + pretty_assertions::assert_eq!(res, 23.into()); + + // Test 2 + validator_slashes_handle(&bob) + .push( + &mut storage, + Slash { + epoch: 4.into(), + block_height: 0, + r#type: SlashType::DuplicateVote, + rate: Dec::one(), + }, + ) + .unwrap(); + let res = compute_bond_at_epoch( + &storage, + ¶ms, + &bob, + 12.into(), + 3.into(), + 23.into(), + Some(&Default::default()), + ) + .unwrap(); + + pretty_assertions::assert_eq!(res, 0.into()); + + // Test 3 + validator_slashes_handle(&bob).pop(&mut storage).unwrap(); + let mut redel_bonds = EagerRedelegatedBondsMap::default(); + redel_bonds.insert( + alice.clone(), + BTreeMap::from_iter([(Epoch(1), token::Amount::from(5))]), + ); + let res = compute_bond_at_epoch( + &storage, + ¶ms, + &bob, + 12.into(), + 3.into(), + 23.into(), + Some(&redel_bonds), + ) + .unwrap(); + + pretty_assertions::assert_eq!(res, 23.into()); + + // Test 4 + validator_slashes_handle(&bob) + .push( + &mut storage, + Slash { + epoch: 4.into(), + block_height: 0, + r#type: SlashType::DuplicateVote, + rate: Dec::one(), + }, + ) + .unwrap(); + let res = compute_bond_at_epoch( + &storage, + ¶ms, + &bob, + 12.into(), + 3.into(), + 23.into(), + Some(&redel_bonds), + ) + .unwrap(); + + pretty_assertions::assert_eq!(res, 0.into()); + + // Test 5 + validator_slashes_handle(&bob).pop(&mut storage).unwrap(); + validator_slashes_handle(&alice) + .push( + &mut storage, + Slash { + epoch: 6.into(), + block_height: 0, + r#type: SlashType::DuplicateVote, + rate: Dec::one(), + }, + ) + .unwrap(); + let res = compute_bond_at_epoch( + &storage, + ¶ms, + &bob, + 12.into(), + 3.into(), + 23.into(), + Some(&redel_bonds), + ) + .unwrap(); + + pretty_assertions::assert_eq!(res, 23.into()); + + // Test 6 + validator_slashes_handle(&alice).pop(&mut storage).unwrap(); + validator_slashes_handle(&alice) + .push( + &mut storage, + Slash { + epoch: 4.into(), + block_height: 0, + r#type: SlashType::DuplicateVote, + rate: Dec::one(), + }, + ) + .unwrap(); + let res = compute_bond_at_epoch( + &storage, + ¶ms, + &bob, + 18.into(), + 9.into(), + 23.into(), + Some(&redel_bonds), + ) + .unwrap(); + + pretty_assertions::assert_eq!(res, 18.into()); +} + +/// `computeSlashBondAtEpochTest` +#[test] +fn test_compute_slash_bond_at_epoch() { + let mut storage = TestWlStorage::default(); + let params = OwnedPosParams { + pipeline_len: 2, + unbonding_len: 4, + cubic_slashing_window_length: 1, + ..Default::default() + }; + let alice = established_address_1(); + let bob = established_address_2(); + + let current_epoch = Epoch(20); + let infraction_epoch = + current_epoch - params.slash_processing_epoch_offset(); + + let redelegated_bond = BTreeMap::from_iter([( + alice, + BTreeMap::from_iter([(infraction_epoch - 4, token::Amount::from(10))]), + )]); + + // Test 1 + let res = compute_slash_bond_at_epoch( + &storage, + ¶ms, + &bob, + current_epoch.next(), + infraction_epoch, + infraction_epoch - 2, + 30.into(), + Some(&Default::default()), + Dec::one(), + ) + .unwrap(); + + pretty_assertions::assert_eq!(res, 30.into()); + + // Test 2 + let res = compute_slash_bond_at_epoch( + &storage, + ¶ms, + &bob, + current_epoch.next(), + infraction_epoch, + infraction_epoch - 2, + 30.into(), + Some(&redelegated_bond), + Dec::one(), + ) + .unwrap(); + + pretty_assertions::assert_eq!(res, 30.into()); + + // Test 3 + validator_slashes_handle(&bob) + .push( + &mut storage, + Slash { + epoch: infraction_epoch.prev(), + block_height: 0, + r#type: SlashType::DuplicateVote, + rate: Dec::one(), + }, + ) + .unwrap(); + let res = compute_slash_bond_at_epoch( + &storage, + ¶ms, + &bob, + current_epoch.next(), + infraction_epoch, + infraction_epoch - 2, + 30.into(), + Some(&Default::default()), + Dec::one(), + ) + .unwrap(); + + pretty_assertions::assert_eq!(res, 0.into()); + + // Test 4 + let res = compute_slash_bond_at_epoch( + &storage, + ¶ms, + &bob, + current_epoch.next(), + infraction_epoch, + infraction_epoch - 2, + 30.into(), + Some(&redelegated_bond), + Dec::one(), + ) + .unwrap(); + + pretty_assertions::assert_eq!(res, 0.into()); +} + +/// `computeNewRedelegatedUnbondsTest` +#[test] +fn test_compute_new_redelegated_unbonds() { + let mut storage = TestWlStorage::default(); + let alice = established_address_1(); + let bob = established_address_2(); + + let key = Key::parse("testing").unwrap(); + let redelegated_bonds = NestedMap::::open(key); + + // Populate the lazy and eager maps + let (ep1, ep2, ep4, ep5, ep6, ep7) = + (Epoch(1), Epoch(2), Epoch(4), Epoch(5), Epoch(6), Epoch(7)); + let keys_and_values = vec![ + (ep5, alice.clone(), ep2, 1), + (ep5, alice.clone(), ep4, 1), + (ep7, alice.clone(), ep2, 1), + (ep7, alice.clone(), ep4, 1), + (ep5, bob.clone(), ep1, 1), + (ep5, bob.clone(), ep4, 2), + (ep7, bob.clone(), ep1, 1), + (ep7, bob.clone(), ep4, 2), + ]; + let mut eager_map = BTreeMap::::new(); + for (outer_ep, address, inner_ep, amount) in keys_and_values { + redelegated_bonds + .at(&outer_ep) + .at(&address) + .insert(&mut storage, inner_ep, token::Amount::from(amount)) + .unwrap(); + eager_map + .entry(outer_ep) + .or_default() + .entry(address.clone()) + .or_default() + .insert(inner_ep, token::Amount::from(amount)); + } + + // Different ModifiedRedelegation objects for testing + let empty_mr = ModifiedRedelegation::default(); + let all_mr = ModifiedRedelegation { + epoch: Some(ep7), + validators_to_remove: BTreeSet::from_iter([alice.clone(), bob.clone()]), + validator_to_modify: None, + epochs_to_remove: Default::default(), + epoch_to_modify: None, + new_amount: None, + }; + let mod_val_mr = ModifiedRedelegation { + epoch: Some(ep7), + validators_to_remove: BTreeSet::from_iter([alice.clone()]), + validator_to_modify: None, + epochs_to_remove: Default::default(), + epoch_to_modify: None, + new_amount: None, + }; + let mod_val_partial_mr = ModifiedRedelegation { + epoch: Some(ep7), + validators_to_remove: BTreeSet::from_iter([alice.clone(), bob.clone()]), + validator_to_modify: Some(bob.clone()), + epochs_to_remove: BTreeSet::from_iter([ep1]), + epoch_to_modify: None, + new_amount: None, + }; + let mod_epoch_partial_mr = ModifiedRedelegation { + epoch: Some(ep7), + validators_to_remove: BTreeSet::from_iter([alice, bob.clone()]), + validator_to_modify: Some(bob.clone()), + epochs_to_remove: BTreeSet::from_iter([ep1, ep4]), + epoch_to_modify: Some(ep4), + new_amount: Some(token::Amount::from(1)), + }; + + // Test case 1 + let res = compute_new_redelegated_unbonds( + &storage, + &redelegated_bonds, + &Default::default(), + &empty_mr, + ) + .unwrap(); + assert_eq!(res, Default::default()); + + let set5 = BTreeSet::::from_iter([ep5]); + let set56 = BTreeSet::::from_iter([ep5, ep6]); + + // Test case 2 + let res = compute_new_redelegated_unbonds( + &storage, + &redelegated_bonds, + &set5, + &empty_mr, + ) + .unwrap(); + let mut exp_res = eager_map.clone(); + exp_res.remove(&ep7); + assert_eq!(res, exp_res); + + // Test case 3 + let res = compute_new_redelegated_unbonds( + &storage, + &redelegated_bonds, + &set56, + &empty_mr, + ) + .unwrap(); + assert_eq!(res, exp_res); + + // Test case 4 + println!("\nTEST CASE 4\n"); + let res = compute_new_redelegated_unbonds( + &storage, + &redelegated_bonds, + &set56, + &all_mr, + ) + .unwrap(); + assert_eq!(res, eager_map); + + // Test case 5 + let res = compute_new_redelegated_unbonds( + &storage, + &redelegated_bonds, + &set56, + &mod_val_mr, + ) + .unwrap(); + exp_res = eager_map.clone(); + exp_res.entry(ep7).or_default().remove(&bob); + assert_eq!(res, exp_res); + + // Test case 6 + let res = compute_new_redelegated_unbonds( + &storage, + &redelegated_bonds, + &set56, + &mod_val_partial_mr, + ) + .unwrap(); + exp_res = eager_map.clone(); + exp_res + .entry(ep7) + .or_default() + .entry(bob.clone()) + .or_default() + .remove(&ep4); + assert_eq!(res, exp_res); + + // Test case 7 + let res = compute_new_redelegated_unbonds( + &storage, + &redelegated_bonds, + &set56, + &mod_epoch_partial_mr, + ) + .unwrap(); + exp_res + .entry(ep7) + .or_default() + .entry(bob) + .or_default() + .insert(ep4, token::Amount::from(1)); + assert_eq!(res, exp_res); +} + +/// `applyListSlashesTest` +#[test] +fn test_apply_list_slashes() { + let init_epoch = Epoch(2); + let params = OwnedPosParams { + unbonding_len: 4, + ..Default::default() + }; + // let unbonding_len = 4u64; + // let cubic_offset = 1u64; + + let slash1 = Slash { + epoch: init_epoch, + block_height: Default::default(), + r#type: SlashType::DuplicateVote, + rate: Dec::one(), + }; + let slash2 = Slash { + epoch: init_epoch + + params.unbonding_len + + params.cubic_slashing_window_length + + 1u64, + block_height: Default::default(), + r#type: SlashType::DuplicateVote, + rate: Dec::one(), + }; + + let list1 = vec![slash1.clone()]; + let list2 = vec![slash1.clone(), slash2.clone()]; + let list3 = vec![slash1.clone(), slash1.clone()]; + let list4 = vec![slash1.clone(), slash1, slash2]; + + let res = apply_list_slashes(¶ms, &[], token::Amount::from(100)); + assert_eq!(res, token::Amount::from(100)); + + let res = apply_list_slashes(¶ms, &list1, token::Amount::from(100)); + assert_eq!(res, token::Amount::zero()); + + let res = apply_list_slashes(¶ms, &list2, token::Amount::from(100)); + assert_eq!(res, token::Amount::zero()); + + let res = apply_list_slashes(¶ms, &list3, token::Amount::from(100)); + assert_eq!(res, token::Amount::zero()); + + let res = apply_list_slashes(¶ms, &list4, token::Amount::from(100)); + assert_eq!(res, token::Amount::zero()); +} + +/// `computeSlashableAmountTest` +#[test] +fn test_compute_slashable_amount() { + let init_epoch = Epoch(2); + let params = OwnedPosParams { + unbonding_len: 4, + ..Default::default() + }; + + let slash1 = Slash { + epoch: init_epoch + + params.unbonding_len + + params.cubic_slashing_window_length, + block_height: Default::default(), + r#type: SlashType::DuplicateVote, + rate: Dec::one(), + }; + + let slash2 = Slash { + epoch: init_epoch + + params.unbonding_len + + params.cubic_slashing_window_length + + 1u64, + block_height: Default::default(), + r#type: SlashType::DuplicateVote, + rate: Dec::one(), + }; + + let test_map = vec![(init_epoch, token::Amount::from(50))] + .into_iter() + .collect::>(); + + let res = compute_slashable_amount( + ¶ms, + &slash1, + token::Amount::from(100), + &BTreeMap::new(), + ); + assert_eq!(res, token::Amount::from(100)); + + let res = compute_slashable_amount( + ¶ms, + &slash2, + token::Amount::from(100), + &test_map, + ); + assert_eq!(res, token::Amount::from(50)); + + let res = compute_slashable_amount( + ¶ms, + &slash1, + token::Amount::from(100), + &test_map, + ); + assert_eq!(res, token::Amount::from(100)); +} + +/// `foldAndSlashRedelegatedBondsMapTest` +#[test] +fn test_fold_and_slash_redelegated_bonds() { + let mut storage = TestWlStorage::default(); + let params = OwnedPosParams { + unbonding_len: 4, + ..Default::default() + }; + let start_epoch = Epoch(7); + + let alice = established_address_1(); + let bob = established_address_2(); + + println!("\n\nAlice: {}", alice); + println!("Bob: {}\n", bob); + + let test_slash = Slash { + epoch: Default::default(), + block_height: Default::default(), + r#type: SlashType::DuplicateVote, + rate: Dec::one(), + }; + + let test_data = vec![ + (alice.clone(), vec![(2, 1), (4, 1)]), + (bob, vec![(1, 1), (4, 2)]), + ]; + let mut eager_redel_bonds = EagerRedelegatedBondsMap::default(); + for (address, pair) in test_data { + for (epoch, amount) in pair { + eager_redel_bonds + .entry(address.clone()) + .or_default() + .insert(Epoch(epoch), token::Amount::from(amount)); + } + } + + // Test case 1 + let res = fold_and_slash_redelegated_bonds( + &storage, + ¶ms, + &eager_redel_bonds, + start_epoch, + &[], + |_| true, + ); + assert_eq!( + res, + FoldRedelegatedBondsResult { + total_redelegated: token::Amount::from(5), + total_after_slashing: token::Amount::from(5), + } + ); + + // Test case 2 + let res = fold_and_slash_redelegated_bonds( + &storage, + ¶ms, + &eager_redel_bonds, + start_epoch, + &[test_slash], + |_| true, + ); + assert_eq!( + res, + FoldRedelegatedBondsResult { + total_redelegated: token::Amount::from(5), + total_after_slashing: token::Amount::zero(), + } + ); + + // Test case 3 + let alice_slash = Slash { + epoch: Epoch(6), + block_height: Default::default(), + r#type: SlashType::DuplicateVote, + rate: Dec::one(), + }; + validator_slashes_handle(&alice) + .push(&mut storage, alice_slash) + .unwrap(); + + let res = fold_and_slash_redelegated_bonds( + &storage, + ¶ms, + &eager_redel_bonds, + start_epoch, + &[], + |_| true, + ); + assert_eq!( + res, + FoldRedelegatedBondsResult { + total_redelegated: token::Amount::from(5), + total_after_slashing: token::Amount::from(3), + } + ); +} + +/// `slashRedelegationTest` +#[test] +fn test_slash_redelegation() { + let mut storage = TestWlStorage::default(); + let params = OwnedPosParams { + unbonding_len: 4, + ..Default::default() + }; + let alice = established_address_1(); + + let total_redelegated_unbonded = + validator_total_redelegated_unbonded_handle(&alice); + total_redelegated_unbonded + .at(&Epoch(13)) + .at(&Epoch(10)) + .at(&alice) + .insert(&mut storage, Epoch(7), token::Amount::from(2)) + .unwrap(); + + let slashes = validator_slashes_handle(&alice); + + let mut slashed_amounts_map = BTreeMap::from_iter([ + (Epoch(15), token::Amount::zero()), + (Epoch(16), token::Amount::zero()), + ]); + let empty_slash_amounts = slashed_amounts_map.clone(); + + // Test case 1 + slash_redelegation( + &storage, + ¶ms, + token::Amount::from(7), + Epoch(7), + Epoch(10), + &alice, + Epoch(14), + &slashes, + &total_redelegated_unbonded, + Dec::one(), + &mut slashed_amounts_map, + ) + .unwrap(); + assert_eq!( + slashed_amounts_map, + BTreeMap::from_iter([ + (Epoch(15), token::Amount::from(5)), + (Epoch(16), token::Amount::from(5)), + ]) + ); + + // Test case 2 + slashed_amounts_map = empty_slash_amounts.clone(); + slash_redelegation( + &storage, + ¶ms, + token::Amount::from(7), + Epoch(7), + Epoch(11), + &alice, + Epoch(14), + &slashes, + &total_redelegated_unbonded, + Dec::one(), + &mut slashed_amounts_map, + ) + .unwrap(); + assert_eq!( + slashed_amounts_map, + BTreeMap::from_iter([ + (Epoch(15), token::Amount::from(7)), + (Epoch(16), token::Amount::from(7)), + ]) + ); + + // Test case 3 + slashed_amounts_map = BTreeMap::from_iter([ + (Epoch(15), token::Amount::from(2)), + (Epoch(16), token::Amount::from(3)), + ]); + slash_redelegation( + &storage, + ¶ms, + token::Amount::from(7), + Epoch(7), + Epoch(10), + &alice, + Epoch(14), + &slashes, + &total_redelegated_unbonded, + Dec::one(), + &mut slashed_amounts_map, + ) + .unwrap(); + assert_eq!( + slashed_amounts_map, + BTreeMap::from_iter([ + (Epoch(15), token::Amount::from(7)), + (Epoch(16), token::Amount::from(8)), + ]) + ); + + // Test case 4 + slashes + .push( + &mut storage, + Slash { + epoch: Epoch(8), + block_height: Default::default(), + r#type: SlashType::DuplicateVote, + rate: Dec::one(), + }, + ) + .unwrap(); + slashed_amounts_map = empty_slash_amounts.clone(); + slash_redelegation( + &storage, + ¶ms, + token::Amount::from(7), + Epoch(7), + Epoch(10), + &alice, + Epoch(14), + &slashes, + &total_redelegated_unbonded, + Dec::one(), + &mut slashed_amounts_map, + ) + .unwrap(); + assert_eq!(slashed_amounts_map, empty_slash_amounts); + + // Test case 5 + slashes.pop(&mut storage).unwrap(); + slashes + .push( + &mut storage, + Slash { + epoch: Epoch(9), + block_height: Default::default(), + r#type: SlashType::DuplicateVote, + rate: Dec::one(), + }, + ) + .unwrap(); + slash_redelegation( + &storage, + ¶ms, + token::Amount::from(7), + Epoch(7), + Epoch(10), + &alice, + Epoch(14), + &slashes, + &total_redelegated_unbonded, + Dec::one(), + &mut slashed_amounts_map, + ) + .unwrap(); + assert_eq!(slashed_amounts_map, empty_slash_amounts); + + // Test case 6 + slashes + .push( + &mut storage, + Slash { + epoch: Epoch(8), + block_height: Default::default(), + r#type: SlashType::DuplicateVote, + rate: Dec::one(), + }, + ) + .unwrap(); + slash_redelegation( + &storage, + ¶ms, + token::Amount::from(7), + Epoch(7), + Epoch(10), + &alice, + Epoch(14), + &slashes, + &total_redelegated_unbonded, + Dec::one(), + &mut slashed_amounts_map, + ) + .unwrap(); + assert_eq!(slashed_amounts_map, empty_slash_amounts); +} + +/// `slashValidatorRedelegationTest` +#[test] +fn test_slash_validator_redelegation() { + let mut storage = TestWlStorage::default(); + let params = OwnedPosParams { + unbonding_len: 4, + ..Default::default() + }; + let gov_params = namada_core::ledger::governance::parameters::GovernanceParameters::default(); + gov_params.init_storage(&mut storage).unwrap(); + write_pos_params(&mut storage, ¶ms).unwrap(); + + let alice = established_address_1(); + let bob = established_address_2(); + + let total_redelegated_unbonded = + validator_total_redelegated_unbonded_handle(&alice); + total_redelegated_unbonded + .at(&Epoch(13)) + .at(&Epoch(10)) + .at(&alice) + .insert(&mut storage, Epoch(7), token::Amount::from(2)) + .unwrap(); + + let outgoing_redelegations = + validator_outgoing_redelegations_handle(&alice).at(&bob); + + let slashes = validator_slashes_handle(&alice); + + let mut slashed_amounts_map = BTreeMap::from_iter([ + (Epoch(15), token::Amount::zero()), + (Epoch(16), token::Amount::zero()), + ]); + let empty_slash_amounts = slashed_amounts_map.clone(); + + // Test case 1 + slash_validator_redelegation( + &storage, + ¶ms, + &alice, + Epoch(14), + &outgoing_redelegations, + &slashes, + &total_redelegated_unbonded, + Dec::one(), + &mut slashed_amounts_map, + ) + .unwrap(); + assert_eq!(slashed_amounts_map, empty_slash_amounts); + + // Test case 2 + total_redelegated_unbonded + .remove_all(&mut storage, &Epoch(13)) + .unwrap(); + slash_validator_redelegation( + &storage, + ¶ms, + &alice, + Epoch(14), + &outgoing_redelegations, + &slashes, + &total_redelegated_unbonded, + Dec::one(), + &mut slashed_amounts_map, + ) + .unwrap(); + assert_eq!(slashed_amounts_map, empty_slash_amounts); + + // Test case 3 + total_redelegated_unbonded + .at(&Epoch(13)) + .at(&Epoch(10)) + .at(&alice) + .insert(&mut storage, Epoch(7), token::Amount::from(2)) + .unwrap(); + outgoing_redelegations + .at(&Epoch(6)) + .insert(&mut storage, Epoch(8), token::Amount::from(7)) + .unwrap(); + slash_validator_redelegation( + &storage, + ¶ms, + &alice, + Epoch(14), + &outgoing_redelegations, + &slashes, + &total_redelegated_unbonded, + Dec::one(), + &mut slashed_amounts_map, + ) + .unwrap(); + assert_eq!( + slashed_amounts_map, + BTreeMap::from_iter([ + (Epoch(15), token::Amount::from(7)), + (Epoch(16), token::Amount::from(7)), + ]) + ); + + // Test case 4 + slashed_amounts_map = empty_slash_amounts.clone(); + outgoing_redelegations + .remove_all(&mut storage, &Epoch(6)) + .unwrap(); + outgoing_redelegations + .at(&Epoch(7)) + .insert(&mut storage, Epoch(8), token::Amount::from(7)) + .unwrap(); + slash_validator_redelegation( + &storage, + ¶ms, + &alice, + Epoch(14), + &outgoing_redelegations, + &slashes, + &total_redelegated_unbonded, + Dec::one(), + &mut slashed_amounts_map, + ) + .unwrap(); + assert_eq!( + slashed_amounts_map, + BTreeMap::from_iter([ + (Epoch(15), token::Amount::from(5)), + (Epoch(16), token::Amount::from(5)), + ]) + ); + + // Test case 5 + slashed_amounts_map = BTreeMap::from_iter([ + (Epoch(15), token::Amount::from(2)), + (Epoch(16), token::Amount::from(3)), + ]); + slash_validator_redelegation( + &storage, + ¶ms, + &alice, + Epoch(14), + &outgoing_redelegations, + &slashes, + &total_redelegated_unbonded, + Dec::one(), + &mut slashed_amounts_map, + ) + .unwrap(); + assert_eq!( + slashed_amounts_map, + BTreeMap::from_iter([ + (Epoch(15), token::Amount::from(7)), + (Epoch(16), token::Amount::from(8)), + ]) + ); + + // Test case 6 + slashed_amounts_map = empty_slash_amounts.clone(); + slashes + .push( + &mut storage, + Slash { + epoch: Epoch(8), + block_height: Default::default(), + r#type: SlashType::DuplicateVote, + rate: Dec::one(), + }, + ) + .unwrap(); + slash_validator_redelegation( + &storage, + ¶ms, + &alice, + Epoch(14), + &outgoing_redelegations, + &slashes, + &total_redelegated_unbonded, + Dec::one(), + &mut slashed_amounts_map, + ) + .unwrap(); + assert_eq!(slashed_amounts_map, empty_slash_amounts); +} + +/// `slashValidatorTest` +#[test] +fn test_slash_validator() { + let mut storage = TestWlStorage::default(); + let params = OwnedPosParams { + unbonding_len: 4, + ..Default::default() + }; + let gov_params = namada_core::ledger::governance::parameters::GovernanceParameters::default(); + gov_params.init_storage(&mut storage).unwrap(); + write_pos_params(&mut storage, ¶ms).unwrap(); + + let alice = established_address_1(); + let bob = established_address_2(); + + let total_bonded = total_bonded_handle(&bob); + let total_unbonded = total_unbonded_handle(&bob); + let total_redelegated_bonded = + validator_total_redelegated_bonded_handle(&bob); + let total_redelegated_unbonded = + validator_total_redelegated_unbonded_handle(&bob); + + let infraction_stake = token::Amount::from(23); + + let initial_stakes = BTreeMap::from_iter([ + (Epoch(11), infraction_stake), + (Epoch(12), infraction_stake), + (Epoch(13), infraction_stake), + ]); + let mut exp_res = initial_stakes.clone(); + + let current_epoch = Epoch(10); + let infraction_epoch = + current_epoch - params.slash_processing_epoch_offset(); + let processing_epoch = current_epoch.next(); + let slash_rate = Dec::one(); + + // Test case 1 + total_bonded + .set(&mut storage, 23.into(), infraction_epoch - 2, 0) + .unwrap(); + let res = slash_validator( + &storage, + ¶ms, + &bob, + slash_rate, + processing_epoch, + &Default::default(), + ) + .unwrap(); + assert_eq!(res, exp_res); + + // Test case 2 + total_bonded + .set(&mut storage, 17.into(), infraction_epoch - 2, 0) + .unwrap(); + total_unbonded + .at(&(current_epoch + params.pipeline_len)) + .insert(&mut storage, infraction_epoch - 2, 6.into()) + .unwrap(); + let res = slash_validator( + &storage, + ¶ms, + &bob, + slash_rate, + processing_epoch, + &Default::default(), + ) + .unwrap(); + exp_res.insert(Epoch(12), 17.into()); + exp_res.insert(Epoch(13), 17.into()); + assert_eq!(res, exp_res); + + // Test case 3 + total_redelegated_bonded + .at(&infraction_epoch.prev()) + .at(&alice) + .insert(&mut storage, Epoch(2), 5.into()) + .unwrap(); + total_redelegated_bonded + .at(&infraction_epoch.prev()) + .at(&alice) + .insert(&mut storage, Epoch(3), 1.into()) + .unwrap(); + + let res = slash_validator( + &storage, + ¶ms, + &bob, + slash_rate, + processing_epoch, + &Default::default(), + ) + .unwrap(); + assert_eq!(res, exp_res); + + // Test case 4 + total_unbonded_handle(&bob) + .at(&(current_epoch + params.pipeline_len)) + .remove(&mut storage, &(infraction_epoch - 2)) + .unwrap(); + total_unbonded_handle(&bob) + .at(&(current_epoch + params.pipeline_len)) + .insert(&mut storage, infraction_epoch - 1, 6.into()) + .unwrap(); + total_redelegated_unbonded + .at(&(current_epoch + params.pipeline_len)) + .at(&infraction_epoch.prev()) + .at(&alice) + .insert(&mut storage, Epoch(2), 5.into()) + .unwrap(); + total_redelegated_unbonded + .at(&(current_epoch + params.pipeline_len)) + .at(&infraction_epoch.prev()) + .at(&alice) + .insert(&mut storage, Epoch(3), 1.into()) + .unwrap(); + let res = slash_validator( + &storage, + ¶ms, + &bob, + slash_rate, + processing_epoch, + &Default::default(), + ) + .unwrap(); + assert_eq!(res, exp_res); + + // Test case 5 + total_bonded_handle(&bob) + .set(&mut storage, 19.into(), infraction_epoch - 2, 0) + .unwrap(); + total_unbonded_handle(&bob) + .at(&(current_epoch + params.pipeline_len)) + .insert(&mut storage, infraction_epoch - 1, 4.into()) + .unwrap(); + total_redelegated_bonded + .at(¤t_epoch) + .at(&alice) + .insert(&mut storage, Epoch(2), token::Amount::from(1)) + .unwrap(); + total_redelegated_unbonded + .at(&(current_epoch + params.pipeline_len)) + .at(&infraction_epoch.prev()) + .at(&alice) + .remove(&mut storage, &Epoch(3)) + .unwrap(); + total_redelegated_unbonded + .at(&(current_epoch + params.pipeline_len)) + .at(&infraction_epoch.prev()) + .at(&alice) + .insert(&mut storage, Epoch(2), 4.into()) + .unwrap(); + let res = slash_validator( + &storage, + ¶ms, + &bob, + slash_rate, + processing_epoch, + &Default::default(), + ) + .unwrap(); + exp_res.insert(Epoch(12), 19.into()); + exp_res.insert(Epoch(13), 19.into()); + assert_eq!(res, exp_res); + + // Test case 6 + total_unbonded_handle(&bob) + .remove_all(&mut storage, &(current_epoch + params.pipeline_len)) + .unwrap(); + total_redelegated_unbonded + .remove_all(&mut storage, &(current_epoch + params.pipeline_len)) + .unwrap(); + total_redelegated_bonded + .remove_all(&mut storage, ¤t_epoch) + .unwrap(); + total_bonded_handle(&bob) + .set(&mut storage, 23.into(), infraction_epoch - 2, 0) + .unwrap(); + total_bonded_handle(&bob) + .set(&mut storage, 6.into(), current_epoch, 0) + .unwrap(); + + let res = slash_validator( + &storage, + ¶ms, + &bob, + slash_rate, + processing_epoch, + &Default::default(), + ) + .unwrap(); + exp_res = initial_stakes; + assert_eq!(res, exp_res); + + // Test case 7 + total_bonded + .get_data_handler() + .remove(&mut storage, ¤t_epoch) + .unwrap(); + total_unbonded + .at(¤t_epoch.next()) + .insert(&mut storage, current_epoch, 6.into()) + .unwrap(); + let res = slash_validator( + &storage, + ¶ms, + &bob, + slash_rate, + processing_epoch, + &Default::default(), + ) + .unwrap(); + assert_eq!(res, exp_res); + + // Test case 8 + total_bonded + .get_data_handler() + .insert(&mut storage, current_epoch, 3.into()) + .unwrap(); + total_unbonded + .at(¤t_epoch.next()) + .insert(&mut storage, current_epoch, 3.into()) + .unwrap(); + let res = slash_validator( + &storage, + ¶ms, + &bob, + slash_rate, + processing_epoch, + &Default::default(), + ) + .unwrap(); + assert_eq!(res, exp_res); + + // Test case 9 + total_unbonded + .remove_all(&mut storage, ¤t_epoch.next()) + .unwrap(); + total_bonded + .set(&mut storage, 6.into(), current_epoch, 0) + .unwrap(); + total_redelegated_bonded + .at(¤t_epoch) + .at(&alice) + .insert(&mut storage, 2.into(), 5.into()) + .unwrap(); + total_redelegated_bonded + .at(¤t_epoch) + .at(&alice) + .insert(&mut storage, 3.into(), 1.into()) + .unwrap(); + let res = slash_validator( + &storage, + ¶ms, + &bob, + slash_rate, + processing_epoch, + &Default::default(), + ) + .unwrap(); + assert_eq!(res, exp_res); + + // Test case 10 + total_redelegated_bonded + .remove_all(&mut storage, ¤t_epoch) + .unwrap(); + total_bonded + .get_data_handler() + .remove(&mut storage, ¤t_epoch) + .unwrap(); + total_redelegated_unbonded + .at(¤t_epoch.next()) + .at(¤t_epoch) + .at(&alice) + .insert(&mut storage, 2.into(), 5.into()) + .unwrap(); + total_redelegated_unbonded + .at(¤t_epoch.next()) + .at(¤t_epoch) + .at(&alice) + .insert(&mut storage, 3.into(), 1.into()) + .unwrap(); + let res = slash_validator( + &storage, + ¶ms, + &bob, + slash_rate, + processing_epoch, + &Default::default(), + ) + .unwrap(); + assert_eq!(res, exp_res); + + // Test case 11 + total_bonded + .set(&mut storage, 2.into(), current_epoch, 0) + .unwrap(); + total_redelegated_unbonded + .at(¤t_epoch.next()) + .at(¤t_epoch) + .at(&alice) + .insert(&mut storage, 2.into(), 4.into()) + .unwrap(); + total_redelegated_unbonded + .at(¤t_epoch.next()) + .at(¤t_epoch) + .at(&alice) + .remove(&mut storage, &3.into()) + .unwrap(); + total_redelegated_bonded + .at(¤t_epoch) + .at(&alice) + .insert(&mut storage, 2.into(), 1.into()) + .unwrap(); + total_redelegated_bonded + .at(¤t_epoch) + .at(&alice) + .insert(&mut storage, 3.into(), 1.into()) + .unwrap(); + let res = slash_validator( + &storage, + ¶ms, + &bob, + slash_rate, + processing_epoch, + &Default::default(), + ) + .unwrap(); + assert_eq!(res, exp_res); + + // Test case 12 + total_bonded + .set(&mut storage, 6.into(), current_epoch, 0) + .unwrap(); + total_bonded + .set(&mut storage, 2.into(), current_epoch.next(), 0) + .unwrap(); + total_redelegated_bonded + .remove_all(&mut storage, ¤t_epoch) + .unwrap(); + total_redelegated_bonded + .at(¤t_epoch.next()) + .at(&alice) + .insert(&mut storage, 2.into(), 1.into()) + .unwrap(); + total_redelegated_bonded + .at(¤t_epoch.next()) + .at(&alice) + .insert(&mut storage, 3.into(), 1.into()) + .unwrap(); + let res = slash_validator( + &storage, + ¶ms, + &bob, + slash_rate, + processing_epoch, + &Default::default(), + ) + .unwrap(); + assert_eq!(res, exp_res); + + // Test case 13 + validator_slashes_handle(&bob) + .push( + &mut storage, + Slash { + epoch: infraction_epoch.prev(), + block_height: 0, + r#type: SlashType::DuplicateVote, + rate: Dec::one(), + }, + ) + .unwrap(); + total_redelegated_unbonded + .remove_all(&mut storage, ¤t_epoch.next()) + .unwrap(); + total_bonded + .get_data_handler() + .remove(&mut storage, ¤t_epoch.next()) + .unwrap(); + total_redelegated_bonded + .remove_all(&mut storage, ¤t_epoch.next()) + .unwrap(); + let res = slash_validator( + &storage, + ¶ms, + &bob, + slash_rate, + processing_epoch, + &Default::default(), + ) + .unwrap(); + exp_res.insert(Epoch(11), 0.into()); + exp_res.insert(Epoch(12), 0.into()); + exp_res.insert(Epoch(13), 0.into()); + assert_eq!(res, exp_res); +} + +/// `computeAmountAfterSlashingUnbondTest` +#[test] +fn test_compute_amount_after_slashing_unbond() { + let mut storage = TestWlStorage::default(); + let params = OwnedPosParams { + unbonding_len: 4, + ..Default::default() + }; + + // Test data + let alice = established_address_1(); + let bob = established_address_2(); + let unbonds: BTreeMap = BTreeMap::from_iter([ + ((Epoch(2)), token::Amount::from(5)), + ((Epoch(4)), token::Amount::from(6)), + ]); + let redelegated_unbonds: EagerRedelegatedUnbonds = BTreeMap::from_iter([( + Epoch(2), + BTreeMap::from_iter([( + alice.clone(), + BTreeMap::from_iter([(Epoch(1), token::Amount::from(1))]), + )]), + )]); + + // Test case 1 + let slashes = vec![]; + let result = compute_amount_after_slashing_unbond( + &storage, + ¶ms, + &unbonds, + &redelegated_unbonds, + slashes, + ) + .unwrap(); + assert_eq!(result.sum, 11.into()); + itertools::assert_equal( + result.epoch_map, + [(2.into(), 5.into()), (4.into(), 6.into())], + ); + + // Test case 2 + let bob_slash = Slash { + epoch: Epoch(5), + block_height: Default::default(), + r#type: SlashType::DuplicateVote, + rate: Dec::one(), + }; + let slashes = vec![bob_slash.clone()]; + validator_slashes_handle(&bob) + .push(&mut storage, bob_slash) + .unwrap(); + let result = compute_amount_after_slashing_unbond( + &storage, + ¶ms, + &unbonds, + &redelegated_unbonds, + slashes, + ) + .unwrap(); + assert_eq!(result.sum, 0.into()); + itertools::assert_equal( + result.epoch_map, + [(2.into(), 0.into()), (4.into(), 0.into())], + ); + + // Test case 3 + let alice_slash = Slash { + epoch: Epoch(0), + block_height: Default::default(), + r#type: SlashType::DuplicateVote, + rate: Dec::one(), + }; + let slashes = vec![alice_slash.clone()]; + validator_slashes_handle(&alice) + .push(&mut storage, alice_slash) + .unwrap(); + validator_slashes_handle(&bob).pop(&mut storage).unwrap(); + let result = compute_amount_after_slashing_unbond( + &storage, + ¶ms, + &unbonds, + &redelegated_unbonds, + slashes, + ) + .unwrap(); + assert_eq!(result.sum, 11.into()); + itertools::assert_equal( + result.epoch_map, + [(2.into(), 5.into()), (4.into(), 6.into())], + ); + + // Test case 4 + let alice_slash = Slash { + epoch: Epoch(1), + block_height: Default::default(), + r#type: SlashType::DuplicateVote, + rate: Dec::one(), + }; + let slashes = vec![alice_slash.clone()]; + validator_slashes_handle(&alice).pop(&mut storage).unwrap(); + validator_slashes_handle(&alice) + .push(&mut storage, alice_slash) + .unwrap(); + let result = compute_amount_after_slashing_unbond( + &storage, + ¶ms, + &unbonds, + &redelegated_unbonds, + slashes, + ) + .unwrap(); + assert_eq!(result.sum, 10.into()); + itertools::assert_equal( + result.epoch_map, + [(2.into(), 4.into()), (4.into(), 6.into())], + ); +} + +/// `computeAmountAfterSlashingWithdrawTest` +#[test] +fn test_compute_amount_after_slashing_withdraw() { + let mut storage = TestWlStorage::default(); + let params = OwnedPosParams { + unbonding_len: 4, + ..Default::default() + }; + + // Test data + let alice = established_address_1(); + let bob = established_address_2(); + let unbonds_and_redelegated_unbonds: BTreeMap< + (Epoch, Epoch), + (token::Amount, EagerRedelegatedBondsMap), + > = BTreeMap::from_iter([ + ( + (Epoch(2), Epoch(20)), + ( + // unbond + token::Amount::from(5), + // redelegations + BTreeMap::from_iter([( + alice.clone(), + BTreeMap::from_iter([(Epoch(1), token::Amount::from(1))]), + )]), + ), + ), + ( + (Epoch(4), Epoch(20)), + ( + // unbond + token::Amount::from(6), + // redelegations + BTreeMap::default(), + ), + ), + ]); + + // Test case 1 + let slashes = vec![]; + let result = compute_amount_after_slashing_withdraw( + &storage, + ¶ms, + &unbonds_and_redelegated_unbonds, + slashes, + ) + .unwrap(); + assert_eq!(result.sum, 11.into()); + itertools::assert_equal( + result.epoch_map, + [(2.into(), 5.into()), (4.into(), 6.into())], + ); + + // Test case 2 + let bob_slash = Slash { + epoch: Epoch(5), + block_height: Default::default(), + r#type: SlashType::DuplicateVote, + rate: Dec::one(), + }; + let slashes = vec![bob_slash.clone()]; + validator_slashes_handle(&bob) + .push(&mut storage, bob_slash) + .unwrap(); + let result = compute_amount_after_slashing_withdraw( + &storage, + ¶ms, + &unbonds_and_redelegated_unbonds, + slashes, + ) + .unwrap(); + assert_eq!(result.sum, 0.into()); + itertools::assert_equal( + result.epoch_map, + [(2.into(), 0.into()), (4.into(), 0.into())], + ); + + // Test case 3 + let alice_slash = Slash { + epoch: Epoch(0), + block_height: Default::default(), + r#type: SlashType::DuplicateVote, + rate: Dec::one(), + }; + let slashes = vec![alice_slash.clone()]; + validator_slashes_handle(&alice) + .push(&mut storage, alice_slash) + .unwrap(); + validator_slashes_handle(&bob).pop(&mut storage).unwrap(); + let result = compute_amount_after_slashing_withdraw( + &storage, + ¶ms, + &unbonds_and_redelegated_unbonds, + slashes, + ) + .unwrap(); + assert_eq!(result.sum, 11.into()); + itertools::assert_equal( + result.epoch_map, + [(2.into(), 5.into()), (4.into(), 6.into())], + ); + + // Test case 4 + let alice_slash = Slash { + epoch: Epoch(1), + block_height: Default::default(), + r#type: SlashType::DuplicateVote, + rate: Dec::one(), + }; + let slashes = vec![alice_slash.clone()]; + validator_slashes_handle(&alice).pop(&mut storage).unwrap(); + validator_slashes_handle(&alice) + .push(&mut storage, alice_slash) + .unwrap(); + let result = compute_amount_after_slashing_withdraw( + &storage, + ¶ms, + &unbonds_and_redelegated_unbonds, + slashes, + ) + .unwrap(); + assert_eq!(result.sum, 10.into()); + itertools::assert_equal( + result.epoch_map, + [(2.into(), 4.into()), (4.into(), 6.into())], + ); +} + +/// SM test case 1 from Brent +#[test] +fn test_from_sm_case_1() { + use namada_core::types::address::testing::established_address_4; + + let mut storage = TestWlStorage::default(); + let gov_params = namada_core::ledger::governance::parameters::GovernanceParameters::default(); + gov_params.init_storage(&mut storage).unwrap(); + write_pos_params(&mut storage, &OwnedPosParams::default()).unwrap(); + + let validator = established_address_1(); + let redeleg_src_1 = established_address_2(); + let redeleg_src_2 = established_address_3(); + let owner = established_address_4(); + let unbond_amount = token::Amount::from(3130688); + println!( + "Owner: {owner}\nValidator: {validator}\nRedeleg src 1: \ + {redeleg_src_1}\nRedeleg src 2: {redeleg_src_2}" + ); + + // Validator's incoming redelegations + let outer_epoch_1 = Epoch(27); + // from redeleg_src_1 + let epoch_1_redeleg_1 = token::Amount::from(8516); + // from redeleg_src_2 + let epoch_1_redeleg_2 = token::Amount::from(5704386); + let outer_epoch_2 = Epoch(30); + // from redeleg_src_2 + let epoch_2_redeleg_2 = token::Amount::from(1035191); + + // Insert the data - bonds and redelegated bonds + let bonds_handle = bond_handle(&owner, &validator); + bonds_handle + .add( + &mut storage, + epoch_1_redeleg_1 + epoch_1_redeleg_2, + outer_epoch_1, + 0, + ) + .unwrap(); + bonds_handle + .add(&mut storage, epoch_2_redeleg_2, outer_epoch_2, 0) + .unwrap(); + + let redelegated_bonds_map_1 = delegator_redelegated_bonds_handle(&owner) + .at(&validator) + .at(&outer_epoch_1); + redelegated_bonds_map_1 + .at(&redeleg_src_1) + .insert(&mut storage, Epoch(14), epoch_1_redeleg_1) + .unwrap(); + redelegated_bonds_map_1 + .at(&redeleg_src_2) + .insert(&mut storage, Epoch(18), epoch_1_redeleg_2) + .unwrap(); + let redelegated_bonds_map_1 = delegator_redelegated_bonds_handle(&owner) + .at(&validator) + .at(&outer_epoch_1); + + let redelegated_bonds_map_2 = delegator_redelegated_bonds_handle(&owner) + .at(&validator) + .at(&outer_epoch_2); + redelegated_bonds_map_2 + .at(&redeleg_src_2) + .insert(&mut storage, Epoch(18), epoch_2_redeleg_2) + .unwrap(); + + // Find the modified redelegation the same way as `unbond_tokens` + let bonds_to_unbond = find_bonds_to_remove( + &storage, + &bonds_handle.get_data_handler(), + unbond_amount, + ) + .unwrap(); + dbg!(&bonds_to_unbond); + + let (new_entry_epoch, new_bond_amount) = bonds_to_unbond.new_entry.unwrap(); + assert_eq!(outer_epoch_1, new_entry_epoch); + // The modified bond should be sum of all redelegations less the unbonded + // amouunt + assert_eq!( + epoch_1_redeleg_1 + epoch_1_redeleg_2 + epoch_2_redeleg_2 + - unbond_amount, + new_bond_amount + ); + // The current bond should be sum of redelegations fom the modified epoch + let cur_bond_amount = bonds_handle + .get_delta_val(&storage, new_entry_epoch) + .unwrap() + .unwrap_or_default(); + assert_eq!(epoch_1_redeleg_1 + epoch_1_redeleg_2, cur_bond_amount); + + let mr = compute_modified_redelegation( + &storage, + &redelegated_bonds_map_1, + new_entry_epoch, + cur_bond_amount - new_bond_amount, + ) + .unwrap(); + + let exp_mr = ModifiedRedelegation { + epoch: Some(Epoch(27)), + validators_to_remove: BTreeSet::from_iter([redeleg_src_2.clone()]), + validator_to_modify: Some(redeleg_src_2), + epochs_to_remove: BTreeSet::from_iter([Epoch(18)]), + epoch_to_modify: Some(Epoch(18)), + new_amount: Some(token::Amount::from(3608889)), + }; + + pretty_assertions::assert_eq!(mr, exp_mr); +} diff --git a/proof_of_stake/src/tests/test_pos.rs b/proof_of_stake/src/tests/test_pos.rs new file mode 100644 index 0000000000..a37268a1a6 --- /dev/null +++ b/proof_of_stake/src/tests/test_pos.rs @@ -0,0 +1,1653 @@ +//! PoS system tests + +use std::collections::{BTreeMap, HashSet}; + +use namada_core::ledger::storage::testing::TestWlStorage; +use namada_core::ledger::storage_api::collections::lazy_map::Collectable; +use namada_core::ledger::storage_api::token::{credit_tokens, read_balance}; +use namada_core::ledger::storage_api::StorageRead; +use namada_core::types::address::Address; +use namada_core::types::dec::Dec; +use namada_core::types::key::testing::{ + common_sk_from_simple_seed, gen_keypair, +}; +use namada_core::types::key::RefTo; +use namada_core::types::storage::{BlockHeight, Epoch}; +use namada_core::types::{address, key, token}; +use proptest::prelude::*; +use proptest::test_runner::Config; +// Use `RUST_LOG=info` (or another tracing level) and `--nocapture` to see +// `tracing` logs from tests +use test_log::test; + +use crate::parameters::testing::arb_pos_params; +use crate::parameters::OwnedPosParams; +use crate::queries::bonds_and_unbonds; +use crate::rewards::{ + log_block_rewards, update_rewards_products_and_mint_inflation, + PosRewardsCalculator, +}; +use crate::slashing::{process_slashes, slash}; +use crate::storage::{ + get_consensus_key_set, read_below_threshold_validator_set_addresses, + read_consensus_validator_set_addresses_with_stake, read_total_stake, + read_validator_deltas_value, rewards_accumulator_handle, + total_deltas_handle, +}; +use crate::test_utils::test_init_genesis; +use crate::tests::helpers::{ + advance_epoch, arb_genesis_validators, arb_params_and_genesis_validators, +}; +use crate::types::{ + into_tm_voting_power, BondDetails, BondId, BondsAndUnbondsDetails, + GenesisValidator, SlashType, UnbondDetails, ValidatorState, VoteInfo, + WeightedValidator, +}; +use crate::{ + below_capacity_validator_set_handle, bond_handle, bond_tokens, + change_consensus_key, consensus_validator_set_handle, is_delegator, + is_validator, read_validator_stake, redelegate_tokens, + staking_token_address, unbond_handle, unbond_tokens, unjail_validator, + validator_consensus_key_handle, validator_set_positions_handle, + validator_state_handle, withdraw_tokens, +}; + +proptest! { + // Generate arb valid input for `test_test_init_genesis_aux` + #![proptest_config(Config { + cases: 100, + .. Config::default() + })] + #[test] + fn test_test_init_genesis( + + (pos_params, genesis_validators) in arb_params_and_genesis_validators(Some(5), 1..10), + start_epoch in (0_u64..1000).prop_map(Epoch), + + ) { + test_test_init_genesis_aux(pos_params, start_epoch, genesis_validators) + } +} + +proptest! { + // Generate arb valid input for `test_bonds_aux` + #![proptest_config(Config { + cases: 100, + .. Config::default() + })] + #[test] + fn test_bonds( + + (pos_params, genesis_validators) in arb_params_and_genesis_validators(Some(5), 1..3), + + ) { + test_bonds_aux(pos_params, genesis_validators) + } +} + +proptest! { + // Generate arb valid input for `test_unjail_validator_aux` + #![proptest_config(Config { + cases: 100, + .. Config::default() + })] + #[test] + fn test_unjail_validator( + (pos_params, genesis_validators) + in arb_params_and_genesis_validators(Some(4),6..9) + ) { + test_unjail_validator_aux(pos_params, + genesis_validators) + } +} + +proptest! { + // Generate arb valid input for `test_unslashed_bond_amount_aux` + #![proptest_config(Config { + cases: 1, + .. Config::default() + })] + #[test] + fn test_unslashed_bond_amount( + + genesis_validators in arb_genesis_validators(4..5, None), + + ) { + test_unslashed_bond_amount_aux(genesis_validators) + } +} + +proptest! { + // Generate arb valid input for `test_log_block_rewards_aux` + #![proptest_config(Config { + cases: 1, + .. Config::default() + })] + #[test] + fn test_log_block_rewards( + genesis_validators in arb_genesis_validators(4..10, None), + params in arb_pos_params(Some(5)) + + ) { + test_log_block_rewards_aux(genesis_validators, params) + } +} + +proptest! { + // Generate arb valid input for `test_update_rewards_products_aux` + #![proptest_config(Config { + cases: 1, + .. Config::default() + })] + #[test] + fn test_update_rewards_products( + genesis_validators in arb_genesis_validators(4..10, None), + + ) { + test_update_rewards_products_aux(genesis_validators) + } +} + +proptest! { + // Generate arb valid input for `test_consensus_key_change` + #![proptest_config(Config { + cases: 1, + .. Config::default() + })] + #[test] + fn test_consensus_key_change( + + genesis_validators in arb_genesis_validators(1..2, None), + + ) { + test_consensus_key_change_aux(genesis_validators) + } +} + +proptest! { + // Generate arb valid input for `test_is_delegator` + #![proptest_config(Config { + cases: 100, + .. Config::default() + })] + #[test] + fn test_is_delegator( + + genesis_validators in arb_genesis_validators(2..3, None), + + ) { + test_is_delegator_aux(genesis_validators) + } +} + +/// Test genesis initialization +fn test_test_init_genesis_aux( + params: OwnedPosParams, + start_epoch: Epoch, + mut validators: Vec, +) { + // println!( + // "Test inputs: {params:?}, {start_epoch}, genesis validators: \ + // {validators:#?}" + // ); + let mut s = TestWlStorage::default(); + s.storage.block.epoch = start_epoch; + + validators.sort_by(|a, b| b.tokens.cmp(&a.tokens)); + let params = test_init_genesis( + &mut s, + params, + validators.clone().into_iter(), + start_epoch, + ) + .unwrap(); + + let mut bond_details = bonds_and_unbonds(&s, None, None).unwrap(); + assert!(bond_details.iter().all(|(_id, details)| { + details.unbonds.is_empty() && details.slashes.is_empty() + })); + + for (i, validator) in validators.into_iter().enumerate() { + let addr = &validator.address; + let self_bonds = bond_details + .remove(&BondId { + source: addr.clone(), + validator: addr.clone(), + }) + .unwrap(); + assert_eq!(self_bonds.bonds.len(), 1); + assert_eq!( + self_bonds.bonds[0], + BondDetails { + start: start_epoch, + amount: validator.tokens, + slashed_amount: None, + } + ); + + let state = validator_state_handle(&validator.address) + .get(&s, start_epoch, ¶ms) + .unwrap(); + if (i as u64) < params.max_validator_slots + && validator.tokens >= params.validator_stake_threshold + { + // should be in consensus set + let handle = consensus_validator_set_handle().at(&start_epoch); + assert!(handle.at(&validator.tokens).iter(&s).unwrap().any( + |result| { + let (_pos, addr) = result.unwrap(); + addr == validator.address + } + )); + assert_eq!(state, Some(ValidatorState::Consensus)); + } else if validator.tokens >= params.validator_stake_threshold { + // Should be in below-capacity set if its tokens are greater than + // `validator_stake_threshold` + let handle = below_capacity_validator_set_handle().at(&start_epoch); + assert!(handle.at(&validator.tokens.into()).iter(&s).unwrap().any( + |result| { + let (_pos, addr) = result.unwrap(); + addr == validator.address + } + )); + assert_eq!(state, Some(ValidatorState::BelowCapacity)); + } else { + // Should be in below-threshold + let bt_addresses = + read_below_threshold_validator_set_addresses(&s, start_epoch) + .unwrap(); + assert!( + bt_addresses + .into_iter() + .any(|addr| { addr == validator.address }) + ); + assert_eq!(state, Some(ValidatorState::BelowThreshold)); + } + } +} + +/// Test bonding +/// NOTE: copy validator sets each time we advance the epoch +fn test_bonds_aux(params: OwnedPosParams, validators: Vec) { + // This can be useful for debugging: + // params.pipeline_len = 2; + // params.unbonding_len = 4; + // println!("\nTest inputs: {params:?}, genesis validators: + // {validators:#?}"); + let mut s = TestWlStorage::default(); + + // Genesis + let start_epoch = s.storage.block.epoch; + let mut current_epoch = s.storage.block.epoch; + let params = test_init_genesis( + &mut s, + params, + validators.clone().into_iter(), + current_epoch, + ) + .unwrap(); + s.commit_block().unwrap(); + + // Advance to epoch 1 + current_epoch = advance_epoch(&mut s, ¶ms); + let self_bond_epoch = current_epoch; + + let validator = validators.first().unwrap(); + + // Read some data before submitting bond + let pipeline_epoch = current_epoch + params.pipeline_len; + let staking_token = staking_token_address(&s); + let pos_balance_pre = s + .read::(&token::balance_key( + &staking_token, + &crate::ADDRESS, + )) + .unwrap() + .unwrap_or_default(); + let total_stake_before = + read_total_stake(&s, ¶ms, pipeline_epoch).unwrap(); + + // Self-bond + let amount_self_bond = token::Amount::from_uint(100_500_000, 0).unwrap(); + credit_tokens(&mut s, &staking_token, &validator.address, amount_self_bond) + .unwrap(); + bond_tokens( + &mut s, + None, + &validator.address, + amount_self_bond, + current_epoch, + None, + ) + .unwrap(); + + // Check the bond delta + let self_bond = bond_handle(&validator.address, &validator.address); + let delta = self_bond.get_delta_val(&s, pipeline_epoch).unwrap(); + assert_eq!(delta, Some(amount_self_bond)); + + // Check the validator in the validator set + let set = + read_consensus_validator_set_addresses_with_stake(&s, pipeline_epoch) + .unwrap(); + assert!(set.into_iter().any( + |WeightedValidator { + bonded_stake, + address, + }| { + address == validator.address + && bonded_stake == validator.tokens + amount_self_bond + } + )); + + let val_deltas = + read_validator_deltas_value(&s, &validator.address, &pipeline_epoch) + .unwrap(); + assert_eq!(val_deltas, Some(amount_self_bond.change())); + + let total_deltas_handle = total_deltas_handle(); + assert_eq!( + current_epoch, + total_deltas_handle.get_last_update(&s).unwrap().unwrap() + ); + let total_stake_after = + read_total_stake(&s, ¶ms, pipeline_epoch).unwrap(); + assert_eq!(total_stake_before + amount_self_bond, total_stake_after); + + // Check bond details after self-bond + let self_bond_id = BondId { + source: validator.address.clone(), + validator: validator.address.clone(), + }; + let check_bond_details = |ix, bond_details: BondsAndUnbondsDetails| { + println!("Check index {ix}"); + let details = bond_details.get(&self_bond_id).unwrap(); + assert_eq!( + details.bonds.len(), + 2, + "Contains genesis and newly added self-bond" + ); + // dbg!(&details.bonds); + assert_eq!( + details.bonds[0], + BondDetails { + start: start_epoch, + amount: validator.tokens, + slashed_amount: None + }, + ); + assert_eq!( + details.bonds[1], + BondDetails { + start: pipeline_epoch, + amount: amount_self_bond, + slashed_amount: None + }, + ); + }; + // Try to call it with different combinations of owner/validator args + check_bond_details(0, bonds_and_unbonds(&s, None, None).unwrap()); + check_bond_details( + 1, + bonds_and_unbonds(&s, Some(validator.address.clone()), None).unwrap(), + ); + check_bond_details( + 2, + bonds_and_unbonds(&s, None, Some(validator.address.clone())).unwrap(), + ); + check_bond_details( + 3, + bonds_and_unbonds( + &s, + Some(validator.address.clone()), + Some(validator.address.clone()), + ) + .unwrap(), + ); + + // Get a non-validating account with tokens + let delegator = address::testing::gen_implicit_address(); + let amount_del = token::Amount::from_uint(201_000_000, 0).unwrap(); + credit_tokens(&mut s, &staking_token, &delegator, amount_del).unwrap(); + let balance_key = token::balance_key(&staking_token, &delegator); + let balance = s + .read::(&balance_key) + .unwrap() + .unwrap_or_default(); + assert_eq!(balance, amount_del); + + // Advance to epoch 3 + advance_epoch(&mut s, ¶ms); + current_epoch = advance_epoch(&mut s, ¶ms); + let pipeline_epoch = current_epoch + params.pipeline_len; + + // Delegation + let delegation_epoch = current_epoch; + bond_tokens( + &mut s, + Some(&delegator), + &validator.address, + amount_del, + current_epoch, + None, + ) + .unwrap(); + let val_stake_pre = read_validator_stake( + &s, + ¶ms, + &validator.address, + pipeline_epoch.prev(), + ) + .unwrap(); + let val_stake_post = + read_validator_stake(&s, ¶ms, &validator.address, pipeline_epoch) + .unwrap(); + assert_eq!(validator.tokens + amount_self_bond, val_stake_pre); + assert_eq!( + validator.tokens + amount_self_bond + amount_del, + val_stake_post + ); + let delegation = bond_handle(&delegator, &validator.address); + assert_eq!( + delegation + .get_sum(&s, pipeline_epoch.prev(), ¶ms) + .unwrap() + .unwrap_or_default(), + token::Amount::zero() + ); + assert_eq!( + delegation + .get_sum(&s, pipeline_epoch, ¶ms) + .unwrap() + .unwrap_or_default(), + amount_del + ); + + // Check delegation bonds details after delegation + let delegation_bond_id = BondId { + source: delegator.clone(), + validator: validator.address.clone(), + }; + let check_bond_details = |ix, bond_details: BondsAndUnbondsDetails| { + println!("Check index {ix}"); + assert_eq!(bond_details.len(), 1); + let details = bond_details.get(&delegation_bond_id).unwrap(); + assert_eq!(details.bonds.len(), 1,); + // dbg!(&details.bonds); + assert_eq!( + details.bonds[0], + BondDetails { + start: pipeline_epoch, + amount: amount_del, + slashed_amount: None + }, + ); + }; + // Try to call it with different combinations of owner/validator args + check_bond_details( + 0, + bonds_and_unbonds(&s, Some(delegator.clone()), None).unwrap(), + ); + check_bond_details( + 1, + bonds_and_unbonds( + &s, + Some(delegator.clone()), + Some(validator.address.clone()), + ) + .unwrap(), + ); + + // Check all bond details (self-bonds and delegation) + let check_bond_details = |ix, bond_details: BondsAndUnbondsDetails| { + println!("Check index {ix}"); + let self_bond_details = bond_details.get(&self_bond_id).unwrap(); + let delegation_details = bond_details.get(&delegation_bond_id).unwrap(); + assert_eq!( + self_bond_details.bonds.len(), + 2, + "Contains genesis and newly added self-bond" + ); + assert_eq!( + self_bond_details.bonds[0], + BondDetails { + start: start_epoch, + amount: validator.tokens, + slashed_amount: None + }, + ); + assert_eq!(self_bond_details.bonds[1].amount, amount_self_bond); + assert_eq!( + delegation_details.bonds[0], + BondDetails { + start: pipeline_epoch, + amount: amount_del, + slashed_amount: None + }, + ); + }; + // Try to call it with different combinations of owner/validator args + check_bond_details(0, bonds_and_unbonds(&s, None, None).unwrap()); + check_bond_details( + 1, + bonds_and_unbonds(&s, None, Some(validator.address.clone())).unwrap(), + ); + + // Advance to epoch 5 + for _ in 0..2 { + current_epoch = advance_epoch(&mut s, ¶ms); + } + let pipeline_epoch = current_epoch + params.pipeline_len; + + // Unbond the self-bond with an amount that will remove all of the self-bond + // executed after genesis and some of the genesis bond + let amount_self_unbond: token::Amount = + amount_self_bond + (validator.tokens / 2); + // When the difference is 0, only the non-genesis self-bond is unbonded + let unbonded_genesis_self_bond = + amount_self_unbond - amount_self_bond != token::Amount::zero(); + + let self_unbond_epoch = s.storage.block.epoch; + + unbond_tokens( + &mut s, + None, + &validator.address, + amount_self_unbond, + current_epoch, + false, + ) + .unwrap(); + + let val_stake_pre = read_validator_stake( + &s, + ¶ms, + &validator.address, + pipeline_epoch.prev(), + ) + .unwrap(); + + let val_stake_post = + read_validator_stake(&s, ¶ms, &validator.address, pipeline_epoch) + .unwrap(); + + let val_delta = + read_validator_deltas_value(&s, &validator.address, &pipeline_epoch) + .unwrap(); + let unbond = unbond_handle(&validator.address, &validator.address); + + assert_eq!(val_delta, Some(-amount_self_unbond.change())); + assert_eq!( + unbond + .at(&Epoch::default()) + .get( + &s, + &(pipeline_epoch + + params.unbonding_len + + params.cubic_slashing_window_length) + ) + .unwrap(), + if unbonded_genesis_self_bond { + Some(amount_self_unbond - amount_self_bond) + } else { + None + } + ); + assert_eq!( + unbond + .at(&(self_bond_epoch + params.pipeline_len)) + .get( + &s, + &(pipeline_epoch + + params.unbonding_len + + params.cubic_slashing_window_length) + ) + .unwrap(), + Some(amount_self_bond) + ); + assert_eq!( + val_stake_pre, + validator.tokens + amount_self_bond + amount_del + ); + assert_eq!( + val_stake_post, + validator.tokens + amount_self_bond + amount_del - amount_self_unbond + ); + + // Check all bond and unbond details (self-bonds and delegation) + let check_bond_details = |ix, bond_details: BondsAndUnbondsDetails| { + println!("Check index {ix}"); + // dbg!(&bond_details); + assert_eq!(bond_details.len(), 2); + let self_bond_details = bond_details.get(&self_bond_id).unwrap(); + let delegation_details = bond_details.get(&delegation_bond_id).unwrap(); + assert_eq!( + self_bond_details.bonds.len(), + 1, + "Contains only part of the genesis bond now" + ); + assert_eq!( + self_bond_details.bonds[0], + BondDetails { + start: start_epoch, + amount: validator.tokens + amount_self_bond + - amount_self_unbond, + slashed_amount: None + }, + ); + assert_eq!( + delegation_details.bonds[0], + BondDetails { + start: delegation_epoch + params.pipeline_len, + amount: amount_del, + slashed_amount: None + }, + ); + assert_eq!( + self_bond_details.unbonds.len(), + if unbonded_genesis_self_bond { 2 } else { 1 }, + "Contains a full unbond of the last self-bond and an unbond from \ + the genesis bond" + ); + if unbonded_genesis_self_bond { + assert_eq!( + self_bond_details.unbonds[0], + UnbondDetails { + start: start_epoch, + withdraw: self_unbond_epoch + + params.pipeline_len + + params.unbonding_len + + params.cubic_slashing_window_length, + amount: amount_self_unbond - amount_self_bond, + slashed_amount: None + } + ); + } + assert_eq!( + self_bond_details.unbonds[usize::from(unbonded_genesis_self_bond)], + UnbondDetails { + start: self_bond_epoch + params.pipeline_len, + withdraw: self_unbond_epoch + + params.pipeline_len + + params.unbonding_len + + params.cubic_slashing_window_length, + amount: amount_self_bond, + slashed_amount: None + } + ); + }; + check_bond_details( + 0, + bonds_and_unbonds(&s, None, Some(validator.address.clone())).unwrap(), + ); + + // Unbond delegation + let amount_undel = token::Amount::from_uint(1_000_000, 0).unwrap(); + unbond_tokens( + &mut s, + Some(&delegator), + &validator.address, + amount_undel, + current_epoch, + false, + ) + .unwrap(); + + let val_stake_pre = read_validator_stake( + &s, + ¶ms, + &validator.address, + pipeline_epoch.prev(), + ) + .unwrap(); + let val_stake_post = + read_validator_stake(&s, ¶ms, &validator.address, pipeline_epoch) + .unwrap(); + let val_delta = + read_validator_deltas_value(&s, &validator.address, &pipeline_epoch) + .unwrap(); + let unbond = unbond_handle(&delegator, &validator.address); + + assert_eq!( + val_delta, + Some(-(amount_self_unbond + amount_undel).change()) + ); + assert_eq!( + unbond + .at(&(delegation_epoch + params.pipeline_len)) + .get( + &s, + &(pipeline_epoch + + params.unbonding_len + + params.cubic_slashing_window_length) + ) + .unwrap(), + Some(amount_undel) + ); + assert_eq!( + val_stake_pre, + validator.tokens + amount_self_bond + amount_del + ); + assert_eq!( + val_stake_post, + validator.tokens + amount_self_bond - amount_self_unbond + amount_del + - amount_undel + ); + + let withdrawable_offset = params.unbonding_len + + params.pipeline_len + + params.cubic_slashing_window_length; + + // Advance to withdrawable epoch + for _ in 0..withdrawable_offset { + current_epoch = advance_epoch(&mut s, ¶ms); + } + + let pos_balance = s + .read::(&token::balance_key( + &staking_token, + &crate::ADDRESS, + )) + .unwrap(); + + assert_eq!( + Some(pos_balance_pre + amount_self_bond + amount_del), + pos_balance + ); + + // Withdraw the self-unbond + withdraw_tokens(&mut s, None, &validator.address, current_epoch).unwrap(); + let unbond = unbond_handle(&validator.address, &validator.address); + let unbond_iter = unbond.iter(&s).unwrap().next(); + assert!(unbond_iter.is_none()); + + let pos_balance = s + .read::(&token::balance_key( + &staking_token, + &crate::ADDRESS, + )) + .unwrap(); + assert_eq!( + Some( + pos_balance_pre + amount_self_bond - amount_self_unbond + + amount_del + ), + pos_balance + ); + + // Withdraw the delegation unbond + withdraw_tokens( + &mut s, + Some(&delegator), + &validator.address, + current_epoch, + ) + .unwrap(); + let unbond = unbond_handle(&delegator, &validator.address); + let unbond_iter = unbond.iter(&s).unwrap().next(); + assert!(unbond_iter.is_none()); + + let pos_balance = s + .read::(&token::balance_key( + &staking_token, + &crate::ADDRESS, + )) + .unwrap(); + assert_eq!( + Some( + pos_balance_pre + amount_self_bond - amount_self_unbond + + amount_del + - amount_undel + ), + pos_balance + ); +} + +fn test_unjail_validator_aux( + params: OwnedPosParams, + mut validators: Vec, +) { + // println!("\nTest inputs: {params:?}, genesis validators: + // {validators:#?}"); + let mut s = TestWlStorage::default(); + + // Find the validator with the most stake and 100x his stake to keep the + // cubic slash rate small + let num_vals = validators.len(); + validators.sort_by_key(|a| a.tokens); + validators[num_vals - 1].tokens = 100 * validators[num_vals - 1].tokens; + + // Get second highest stake validator to misbehave + let val_addr = &validators[num_vals - 2].address; + // let val_tokens = validators[num_vals - 2].tokens; + + // Genesis + let mut current_epoch = s.storage.block.epoch; + let params = test_init_genesis( + &mut s, + params, + validators.clone().into_iter(), + current_epoch, + ) + .unwrap(); + s.commit_block().unwrap(); + + current_epoch = advance_epoch(&mut s, ¶ms); + process_slashes(&mut s, current_epoch).unwrap(); + + // Discover first slash + let slash_0_evidence_epoch = current_epoch; + let evidence_block_height = BlockHeight(0); // doesn't matter for slashing logic + let slash_0_type = SlashType::DuplicateVote; + slash( + &mut s, + ¶ms, + current_epoch, + slash_0_evidence_epoch, + evidence_block_height, + slash_0_type, + val_addr, + current_epoch.next(), + ) + .unwrap(); + + assert_eq!( + validator_state_handle(val_addr) + .get(&s, current_epoch, ¶ms) + .unwrap(), + Some(ValidatorState::Consensus) + ); + + for epoch in Epoch::iter_bounds_inclusive( + current_epoch.next(), + current_epoch + params.pipeline_len, + ) { + // Check the validator state + assert_eq!( + validator_state_handle(val_addr) + .get(&s, epoch, ¶ms) + .unwrap(), + Some(ValidatorState::Jailed) + ); + // Check the validator set positions + assert!( + validator_set_positions_handle() + .at(&epoch) + .get(&s, val_addr) + .unwrap() + .is_none(), + ); + } + + // Advance past an epoch in which we can unbond + let unfreeze_epoch = + slash_0_evidence_epoch + params.slash_processing_epoch_offset(); + while current_epoch < unfreeze_epoch + 4u64 { + current_epoch = advance_epoch(&mut s, ¶ms); + process_slashes(&mut s, current_epoch).unwrap(); + } + + // Unjail the validator + unjail_validator(&mut s, val_addr, current_epoch).unwrap(); + + // Check the validator state + for epoch in + Epoch::iter_bounds_inclusive(current_epoch, current_epoch.next()) + { + assert_eq!( + validator_state_handle(val_addr) + .get(&s, epoch, ¶ms) + .unwrap(), + Some(ValidatorState::Jailed) + ); + } + + assert_eq!( + validator_state_handle(val_addr) + .get(&s, current_epoch + params.pipeline_len, ¶ms) + .unwrap(), + Some(ValidatorState::Consensus) + ); + assert!( + validator_set_positions_handle() + .at(&(current_epoch + params.pipeline_len)) + .get(&s, val_addr) + .unwrap() + .is_some(), + ); + + // Advance another epoch + current_epoch = advance_epoch(&mut s, ¶ms); + process_slashes(&mut s, current_epoch).unwrap(); + + let second_att = unjail_validator(&mut s, val_addr, current_epoch); + assert!(second_att.is_err()); +} + +fn test_unslashed_bond_amount_aux(validators: Vec) { + let mut storage = TestWlStorage::default(); + let params = OwnedPosParams { + unbonding_len: 4, + ..Default::default() + }; + + // Genesis + let mut current_epoch = storage.storage.block.epoch; + let params = test_init_genesis( + &mut storage, + params, + validators.clone().into_iter(), + current_epoch, + ) + .unwrap(); + storage.commit_block().unwrap(); + + let validator1 = validators[0].address.clone(); + let validator2 = validators[1].address.clone(); + + // Get a delegator with some tokens + let staking_token = staking_token_address(&storage); + let delegator = address::testing::gen_implicit_address(); + let del_balance = token::Amount::from_uint(1_000_000, 0).unwrap(); + credit_tokens(&mut storage, &staking_token, &delegator, del_balance) + .unwrap(); + + // Bond to validator 1 + bond_tokens( + &mut storage, + Some(&delegator), + &validator1, + 10_000.into(), + current_epoch, + None, + ) + .unwrap(); + + // Unbond some from validator 1 + unbond_tokens( + &mut storage, + Some(&delegator), + &validator1, + 1_342.into(), + current_epoch, + false, + ) + .unwrap(); + + // Redelegate some from validator 1 -> 2 + redelegate_tokens( + &mut storage, + &delegator, + &validator1, + &validator2, + current_epoch, + 1_875.into(), + ) + .unwrap(); + + // Unbond some from validator 2 + unbond_tokens( + &mut storage, + Some(&delegator), + &validator2, + 584.into(), + current_epoch, + false, + ) + .unwrap(); + + // Advance an epoch + current_epoch = advance_epoch(&mut storage, ¶ms); + process_slashes(&mut storage, current_epoch).unwrap(); + + // Bond to validator 1 + bond_tokens( + &mut storage, + Some(&delegator), + &validator1, + 384.into(), + current_epoch, + None, + ) + .unwrap(); + + // Unbond some from validator 1 + unbond_tokens( + &mut storage, + Some(&delegator), + &validator1, + 144.into(), + current_epoch, + false, + ) + .unwrap(); + + // Redelegate some from validator 1 -> 2 + redelegate_tokens( + &mut storage, + &delegator, + &validator1, + &validator2, + current_epoch, + 3_448.into(), + ) + .unwrap(); + + // Unbond some from validator 2 + unbond_tokens( + &mut storage, + Some(&delegator), + &validator2, + 699.into(), + current_epoch, + false, + ) + .unwrap(); + + // Advance an epoch + current_epoch = advance_epoch(&mut storage, ¶ms); + process_slashes(&mut storage, current_epoch).unwrap(); + + // Bond to validator 1 + bond_tokens( + &mut storage, + Some(&delegator), + &validator1, + 4_384.into(), + current_epoch, + None, + ) + .unwrap(); + + // Redelegate some from validator 1 -> 2 + redelegate_tokens( + &mut storage, + &delegator, + &validator1, + &validator2, + current_epoch, + 1_008.into(), + ) + .unwrap(); + + // Unbond some from validator 2 + unbond_tokens( + &mut storage, + Some(&delegator), + &validator2, + 3_500.into(), + current_epoch, + false, + ) + .unwrap(); + + // Checks + let val1_init_stake = validators[0].tokens; + + for epoch in Epoch::iter_bounds_inclusive( + Epoch(0), + current_epoch + params.pipeline_len, + ) { + let bond_amount = crate::bond_amount( + &storage, + &BondId { + source: delegator.clone(), + validator: validator1.clone(), + }, + epoch, + ) + .unwrap_or_default(); + + let val_stake = + crate::read_validator_stake(&storage, ¶ms, &validator1, epoch) + .unwrap(); + // dbg!(&bond_amount); + assert_eq!(val_stake - val1_init_stake, bond_amount); + } +} + +fn test_log_block_rewards_aux( + validators: Vec, + params: OwnedPosParams, +) { + tracing::info!( + "New case with {} validators: {:#?}", + validators.len(), + validators + .iter() + .map(|v| (&v.address, v.tokens.to_string_native())) + .collect::>() + ); + let mut s = TestWlStorage::default(); + // Init genesis + let current_epoch = s.storage.block.epoch; + let params = test_init_genesis( + &mut s, + params, + validators.clone().into_iter(), + current_epoch, + ) + .unwrap(); + s.commit_block().unwrap(); + let total_stake = + crate::get_total_consensus_stake(&s, current_epoch, ¶ms).unwrap(); + let consensus_set = + crate::read_consensus_validator_set_addresses(&s, current_epoch) + .unwrap(); + let proposer_address = consensus_set.iter().next().unwrap().clone(); + + tracing::info!( + ?params.block_proposer_reward, + ?params.block_vote_reward, + ); + tracing::info!(?proposer_address,); + + // Rewards accumulator should be empty at first + let rewards_handle = rewards_accumulator_handle(); + assert!(rewards_handle.is_empty(&s).unwrap()); + + let mut last_rewards = BTreeMap::default(); + + let num_blocks = 100; + // Loop through `num_blocks`, log rewards & check results + for i in 0..num_blocks { + tracing::info!(""); + tracing::info!("Block {}", i + 1); + + // A helper closure to prepare minimum required votes + let prep_votes = |epoch| { + // Ceil of 2/3 of total stake + let min_required_votes = total_stake.mul_ceil(Dec::two() / 3); + + let mut total_votes = token::Amount::zero(); + let mut non_voters = HashSet::
::default(); + let mut prep_vote = |validator| { + // Add validator vote if it's in consensus set and if we don't + // yet have min required votes + if consensus_set.contains(validator) + && total_votes < min_required_votes + { + let stake = + read_validator_stake(&s, ¶ms, validator, epoch) + .unwrap(); + total_votes += stake; + let validator_vp = + into_tm_voting_power(params.tm_votes_per_token, stake) + as u64; + tracing::info!("Validator {validator} signed"); + Some(VoteInfo { + validator_address: validator.clone(), + validator_vp, + }) + } else { + non_voters.insert(validator.clone()); + None + } + }; + + let votes: Vec = validators + .iter() + .rev() + .filter_map(|validator| prep_vote(&validator.address)) + .collect(); + (votes, total_votes, non_voters) + }; + + let (votes, signing_stake, non_voters) = prep_votes(current_epoch); + log_block_rewards( + &mut s, + current_epoch, + &proposer_address, + votes.clone(), + ) + .unwrap(); + + assert!(!rewards_handle.is_empty(&s).unwrap()); + + let rewards_calculator = PosRewardsCalculator { + proposer_reward: params.block_proposer_reward, + signer_reward: params.block_vote_reward, + signing_stake, + total_stake, + }; + let coeffs = rewards_calculator.get_reward_coeffs().unwrap(); + tracing::info!(?coeffs); + + // Check proposer reward + let stake = + read_validator_stake(&s, ¶ms, &proposer_address, current_epoch) + .unwrap(); + let proposer_signing_reward = votes.iter().find_map(|vote| { + if vote.validator_address == proposer_address { + let signing_fraction = + Dec::from(stake) / Dec::from(signing_stake); + Some(coeffs.signer_coeff * signing_fraction) + } else { + None + } + }); + let expected_proposer_rewards = last_rewards.get(&proposer_address).copied().unwrap_or_default() + + // Proposer reward + coeffs.proposer_coeff + // Consensus validator reward + + (coeffs.active_val_coeff + * (Dec::from(stake) / Dec::from(total_stake))) + // Signing reward (if proposer voted) + + proposer_signing_reward + .unwrap_or_default(); + tracing::info!( + "Expected proposer rewards: {expected_proposer_rewards}. Signed \ + block: {}", + proposer_signing_reward.is_some() + ); + assert_eq!( + rewards_handle.get(&s, &proposer_address).unwrap(), + Some(expected_proposer_rewards) + ); + + // Check voters rewards + for VoteInfo { + validator_address, .. + } in votes.iter() + { + // Skip proposer, in case voted - already checked + if validator_address == &proposer_address { + continue; + } + + let stake = read_validator_stake( + &s, + ¶ms, + validator_address, + current_epoch, + ) + .unwrap(); + let signing_fraction = Dec::from(stake) / Dec::from(signing_stake); + let expected_signer_rewards = last_rewards + .get(validator_address) + .copied() + .unwrap_or_default() + + coeffs.signer_coeff * signing_fraction + + (coeffs.active_val_coeff + * (Dec::from(stake) / Dec::from(total_stake))); + tracing::info!( + "Expected signer {validator_address} rewards: \ + {expected_signer_rewards}" + ); + assert_eq!( + rewards_handle.get(&s, validator_address).unwrap(), + Some(expected_signer_rewards) + ); + } + + // Check non-voters rewards, if any + for address in non_voters { + // Skip proposer, in case it didn't vote - already checked + if address == proposer_address { + continue; + } + + if consensus_set.contains(&address) { + let stake = + read_validator_stake(&s, ¶ms, &address, current_epoch) + .unwrap(); + let expected_non_signer_rewards = + last_rewards.get(&address).copied().unwrap_or_default() + + coeffs.active_val_coeff + * (Dec::from(stake) / Dec::from(total_stake)); + tracing::info!( + "Expected non-signer {address} rewards: \ + {expected_non_signer_rewards}" + ); + assert_eq!( + rewards_handle.get(&s, &address).unwrap(), + Some(expected_non_signer_rewards) + ); + } else { + let last_reward = last_rewards.get(&address).copied(); + assert_eq!( + rewards_handle.get(&s, &address).unwrap(), + last_reward + ); + } + } + s.commit_block().unwrap(); + + last_rewards = rewards_accumulator_handle().collect_map(&s).unwrap(); + + let rewards_sum: Dec = last_rewards.values().copied().sum(); + let expected_sum = Dec::one() * (i as u64 + 1); + let err_tolerance = Dec::new(1, 9).unwrap(); + let fail_msg = format!( + "Expected rewards sum at block {} to be {expected_sum}, got \ + {rewards_sum}. Error tolerance {err_tolerance}.", + i + 1 + ); + assert!(expected_sum <= rewards_sum + err_tolerance, "{fail_msg}"); + assert!(rewards_sum <= expected_sum, "{fail_msg}"); + } +} + +fn test_update_rewards_products_aux(validators: Vec) { + tracing::info!( + "New case with {} validators: {:#?}", + validators.len(), + validators + .iter() + .map(|v| (&v.address, v.tokens.to_string_native())) + .collect::>() + ); + let mut s = TestWlStorage::default(); + // Init genesis + let current_epoch = s.storage.block.epoch; + let params = OwnedPosParams::default(); + let params = test_init_genesis( + &mut s, + params, + validators.into_iter(), + current_epoch, + ) + .unwrap(); + s.commit_block().unwrap(); + + let staking_token = staking_token_address(&s); + let consensus_set = + crate::read_consensus_validator_set_addresses(&s, current_epoch) + .unwrap(); + + // Start a new epoch + let current_epoch = advance_epoch(&mut s, ¶ms); + + // Read some data before applying rewards + let pos_balance_pre = + read_balance(&s, &staking_token, &address::POS).unwrap(); + let gov_balance_pre = + read_balance(&s, &staking_token, &address::GOV).unwrap(); + + let num_consensus_validators = consensus_set.len() as u64; + let accum_val = Dec::one() / num_consensus_validators; + let num_blocks_in_last_epoch = 1000; + + // Assign some reward accumulator values to consensus validator + for validator in &consensus_set { + rewards_accumulator_handle() + .insert( + &mut s, + validator.clone(), + accum_val * num_blocks_in_last_epoch, + ) + .unwrap(); + } + + // Distribute inflation into rewards + let last_epoch = current_epoch.prev(); + let inflation = token::Amount::native_whole(10_000_000); + update_rewards_products_and_mint_inflation( + &mut s, + ¶ms, + last_epoch, + num_blocks_in_last_epoch, + inflation, + &staking_token, + ) + .unwrap(); + + let pos_balance_post = + read_balance(&s, &staking_token, &address::POS).unwrap(); + let gov_balance_post = + read_balance(&s, &staking_token, &address::GOV).unwrap(); + + assert_eq!( + pos_balance_pre + gov_balance_pre + inflation, + pos_balance_post + gov_balance_post, + "Expected inflation to be minted to PoS and left-over amount to Gov" + ); + + let pos_credit = pos_balance_post - pos_balance_pre; + let gov_credit = gov_balance_post - gov_balance_pre; + assert!( + pos_credit > gov_credit, + "PoS must receive more tokens than Gov, but got {} in PoS and {} in \ + Gov", + pos_credit.to_string_native(), + gov_credit.to_string_native() + ); + + // Rewards accumulator must be cleared out + let rewards_handle = rewards_accumulator_handle(); + assert!(rewards_handle.is_empty(&s).unwrap()); +} + +fn test_consensus_key_change_aux(validators: Vec) { + assert_eq!(validators.len(), 1); + + let params = OwnedPosParams { + unbonding_len: 4, + ..Default::default() + }; + let validator = validators[0].address.clone(); + + // println!("\nTest inputs: {params:?}, genesis validators: + // {validators:#?}"); + let mut storage = TestWlStorage::default(); + + // Genesis + let mut current_epoch = storage.storage.block.epoch; + let params = test_init_genesis( + &mut storage, + params, + validators.into_iter(), + current_epoch, + ) + .unwrap(); + storage.commit_block().unwrap(); + + // Check that there is one consensus key in the network + let consensus_keys = get_consensus_key_set(&storage).unwrap(); + assert_eq!(consensus_keys.len(), 1); + let ck = consensus_keys.first().cloned().unwrap(); + let og_ck = validator_consensus_key_handle(&validator) + .get(&storage, current_epoch, ¶ms) + .unwrap() + .unwrap(); + assert_eq!(ck, og_ck); + + // Attempt to change to a new secp256k1 consensus key (disallowed) + let secp_ck = gen_keypair::(); + let secp_ck = key::common::SecretKey::Secp256k1(secp_ck).ref_to(); + let res = + change_consensus_key(&mut storage, &validator, &secp_ck, current_epoch); + assert!(res.is_err()); + + // Change consensus keys + let ck_2 = common_sk_from_simple_seed(1).ref_to(); + change_consensus_key(&mut storage, &validator, &ck_2, current_epoch) + .unwrap(); + + // Check that there is a new consensus key + let consensus_keys = get_consensus_key_set(&storage).unwrap(); + assert_eq!(consensus_keys.len(), 2); + + for epoch in current_epoch.iter_range(params.pipeline_len) { + let ck = validator_consensus_key_handle(&validator) + .get(&storage, epoch, ¶ms) + .unwrap() + .unwrap(); + assert_eq!(ck, og_ck); + } + let pipeline_epoch = current_epoch + params.pipeline_len; + let ck = validator_consensus_key_handle(&validator) + .get(&storage, pipeline_epoch, ¶ms) + .unwrap() + .unwrap(); + assert_eq!(ck, ck_2); + + // Advance to the pipeline epoch + loop { + current_epoch = advance_epoch(&mut storage, ¶ms); + if current_epoch == pipeline_epoch { + break; + } + } + + // Check the consensus keys again + let consensus_keys = get_consensus_key_set(&storage).unwrap(); + assert_eq!(consensus_keys.len(), 2); + + for epoch in current_epoch.iter_range(params.pipeline_len + 1) { + let ck = validator_consensus_key_handle(&validator) + .get(&storage, epoch, ¶ms) + .unwrap() + .unwrap(); + assert_eq!(ck, ck_2); + } + + // Now change the consensus key again and bond in the same epoch + let ck_3 = common_sk_from_simple_seed(3).ref_to(); + change_consensus_key(&mut storage, &validator, &ck_3, current_epoch) + .unwrap(); + + let staking_token = storage.storage.native_token.clone(); + let amount_del = token::Amount::native_whole(5); + credit_tokens(&mut storage, &staking_token, &validator, amount_del) + .unwrap(); + bond_tokens( + &mut storage, + None, + &validator, + token::Amount::native_whole(1), + current_epoch, + None, + ) + .unwrap(); + + // Check consensus keys again + let consensus_keys = get_consensus_key_set(&storage).unwrap(); + assert_eq!(consensus_keys.len(), 3); + + for epoch in current_epoch.iter_range(params.pipeline_len) { + let ck = validator_consensus_key_handle(&validator) + .get(&storage, epoch, ¶ms) + .unwrap() + .unwrap(); + assert_eq!(ck, ck_2); + } + let pipeline_epoch = current_epoch + params.pipeline_len; + let ck = validator_consensus_key_handle(&validator) + .get(&storage, pipeline_epoch, ¶ms) + .unwrap() + .unwrap(); + assert_eq!(ck, ck_3); + + // Advance to the pipeline epoch to ensure that the validator set updates to + // tendermint will work + loop { + current_epoch = advance_epoch(&mut storage, ¶ms); + if current_epoch == pipeline_epoch { + break; + } + } + assert_eq!(current_epoch.0, 2 * params.pipeline_len); +} + +fn test_is_delegator_aux(mut validators: Vec) { + validators.sort_by(|a, b| b.tokens.cmp(&a.tokens)); + + let validator1 = validators[0].address.clone(); + let validator2 = validators[1].address.clone(); + + let mut storage = TestWlStorage::default(); + let params = OwnedPosParams { + unbonding_len: 4, + ..Default::default() + }; + + // Genesis + let mut current_epoch = storage.storage.block.epoch; + let params = test_init_genesis( + &mut storage, + params, + validators.clone().into_iter(), + current_epoch, + ) + .unwrap(); + storage.commit_block().unwrap(); + + // Get delegators with some tokens + let staking_token = staking_token_address(&storage); + let delegator1 = address::testing::gen_implicit_address(); + let delegator2 = address::testing::gen_implicit_address(); + let del_balance = token::Amount::native_whole(1000); + credit_tokens(&mut storage, &staking_token, &delegator1, del_balance) + .unwrap(); + credit_tokens(&mut storage, &staking_token, &delegator2, del_balance) + .unwrap(); + + // Advance to epoch 1 + current_epoch = advance_epoch(&mut storage, ¶ms); + process_slashes(&mut storage, current_epoch).unwrap(); + + // Delegate in epoch 1 to validator1 + let del1_epoch = current_epoch; + bond_tokens( + &mut storage, + Some(&delegator1), + &validator1, + 1000.into(), + current_epoch, + None, + ) + .unwrap(); + + // Advance to epoch 2 + current_epoch = advance_epoch(&mut storage, ¶ms); + process_slashes(&mut storage, current_epoch).unwrap(); + + // Delegate in epoch 2 to validator2 + let del2_epoch = current_epoch; + bond_tokens( + &mut storage, + Some(&delegator2), + &validator2, + 1000.into(), + current_epoch, + None, + ) + .unwrap(); + + // Checks + assert!(is_validator(&storage, &validator1).unwrap()); + assert!(is_validator(&storage, &validator2).unwrap()); + assert!(!is_delegator(&storage, &validator1, None).unwrap()); + assert!(!is_delegator(&storage, &validator2, None).unwrap()); + + assert!(!is_validator(&storage, &delegator1).unwrap()); + assert!(!is_validator(&storage, &delegator2).unwrap()); + assert!(is_delegator(&storage, &delegator1, None).unwrap()); + assert!(is_delegator(&storage, &delegator2, None).unwrap()); + + for epoch in Epoch::default().iter_range(del1_epoch.0 + params.pipeline_len) + { + assert!(!is_delegator(&storage, &delegator1, Some(epoch)).unwrap()); + } + assert!( + is_delegator( + &storage, + &delegator1, + Some(del1_epoch + params.pipeline_len) + ) + .unwrap() + ); + for epoch in Epoch::default().iter_range(del2_epoch.0 + params.pipeline_len) + { + assert!(!is_delegator(&storage, &delegator2, Some(epoch)).unwrap()); + } + assert!( + is_delegator( + &storage, + &delegator2, + Some(del2_epoch + params.pipeline_len) + ) + .unwrap() + ); +} diff --git a/proof_of_stake/src/tests/test_slash_and_redel.rs b/proof_of_stake/src/tests/test_slash_and_redel.rs new file mode 100644 index 0000000000..17b73494b6 --- /dev/null +++ b/proof_of_stake/src/tests/test_slash_and_redel.rs @@ -0,0 +1,1495 @@ +use std::ops::Deref; +use std::str::FromStr; + +use assert_matches::assert_matches; +use namada_core::ledger::storage::testing::TestWlStorage; +use namada_core::ledger::storage_api::collections::lazy_map::Collectable; +use namada_core::ledger::storage_api::token::{credit_tokens, read_balance}; +use namada_core::ledger::storage_api::StorageRead; +use namada_core::types::dec::Dec; +use namada_core::types::storage::{BlockHeight, Epoch}; +use namada_core::types::token::NATIVE_MAX_DECIMAL_PLACES; +use namada_core::types::{address, token}; +use proptest::prelude::*; +use proptest::test_runner::Config; +// Use `RUST_LOG=info` (or another tracing level) and `--nocapture` to see +// `tracing` logs from tests +use test_log::test; + +use crate::queries::bonds_and_unbonds; +use crate::slashing::{process_slashes, slash}; +use crate::storage::{ + bond_handle, delegator_redelegated_bonds_handle, + delegator_redelegated_unbonds_handle, read_total_stake, + read_validator_stake, total_bonded_handle, total_unbonded_handle, + unbond_handle, validator_incoming_redelegations_handle, + validator_outgoing_redelegations_handle, validator_slashes_handle, + validator_total_redelegated_bonded_handle, + validator_total_redelegated_unbonded_handle, +}; +use crate::test_utils::test_init_genesis; +use crate::tests::helpers::{ + advance_epoch, arb_genesis_validators, arb_redelegation_amounts, + test_slashes_with_unbonding_params, +}; +use crate::types::{BondId, GenesisValidator, SlashType}; +use crate::{ + bond_tokens, redelegate_tokens, staking_token_address, unbond_tokens, + withdraw_tokens, OwnedPosParams, RedelegationError, +}; + +proptest! { + // Generate arb valid input for `test_simple_redelegation_aux` + #![proptest_config(Config { + cases: 100, + .. Config::default() + })] + #[test] + fn test_simple_redelegation( + + genesis_validators in arb_genesis_validators(2..4, None), + (amount_delegate, amount_redelegate, amount_unbond) in arb_redelegation_amounts(20) + + ) { + test_simple_redelegation_aux(genesis_validators, amount_delegate, amount_redelegate, amount_unbond) + } +} + +fn test_simple_redelegation_aux( + mut validators: Vec, + amount_delegate: token::Amount, + amount_redelegate: token::Amount, + amount_unbond: token::Amount, +) { + validators.sort_by(|a, b| b.tokens.cmp(&a.tokens)); + + let src_validator = validators[0].address.clone(); + let dest_validator = validators[1].address.clone(); + + let mut storage = TestWlStorage::default(); + let params = OwnedPosParams { + unbonding_len: 4, + ..Default::default() + }; + + // Genesis + let mut current_epoch = storage.storage.block.epoch; + let params = test_init_genesis( + &mut storage, + params, + validators.clone().into_iter(), + current_epoch, + ) + .unwrap(); + storage.commit_block().unwrap(); + + // Get a delegator with some tokens + let staking_token = staking_token_address(&storage); + let delegator = address::testing::gen_implicit_address(); + let del_balance = token::Amount::from_uint(1_000_000, 0).unwrap(); + credit_tokens(&mut storage, &staking_token, &delegator, del_balance) + .unwrap(); + + // Ensure that we cannot redelegate with the same src and dest validator + let err = redelegate_tokens( + &mut storage, + &delegator, + &src_validator, + &src_validator, + current_epoch, + amount_redelegate, + ) + .unwrap_err(); + let err_str = err.to_string(); + assert_matches!( + err.downcast::().unwrap().deref(), + RedelegationError::RedelegationSrcEqDest, + "Redelegation with the same src and dest validator must be rejected, \ + got {err_str}", + ); + + for _ in 0..5 { + current_epoch = advance_epoch(&mut storage, ¶ms); + process_slashes(&mut storage, current_epoch).unwrap(); + } + + let init_epoch = current_epoch; + + // Delegate in epoch 1 to src_validator + bond_tokens( + &mut storage, + Some(&delegator), + &src_validator, + amount_delegate, + current_epoch, + None, + ) + .unwrap(); + + // Advance three epochs + current_epoch = advance_epoch(&mut storage, ¶ms); + process_slashes(&mut storage, current_epoch).unwrap(); + current_epoch = advance_epoch(&mut storage, ¶ms); + process_slashes(&mut storage, current_epoch).unwrap(); + current_epoch = advance_epoch(&mut storage, ¶ms); + process_slashes(&mut storage, current_epoch).unwrap(); + + // Redelegate in epoch 3 + redelegate_tokens( + &mut storage, + &delegator, + &src_validator, + &dest_validator, + current_epoch, + amount_redelegate, + ) + .unwrap(); + + // Dest val + + // Src val + + // Checks + let redelegated = delegator_redelegated_bonds_handle(&delegator) + .at(&dest_validator) + .at(&(current_epoch + params.pipeline_len)) + .at(&src_validator) + .get(&storage, &(init_epoch + params.pipeline_len)) + .unwrap() + .unwrap(); + assert_eq!(redelegated, amount_redelegate); + + let redel_start_epoch = + validator_incoming_redelegations_handle(&dest_validator) + .get(&storage, &delegator) + .unwrap() + .unwrap(); + assert_eq!(redel_start_epoch, current_epoch + params.pipeline_len); + + let redelegated = validator_outgoing_redelegations_handle(&src_validator) + .at(&dest_validator) + .at(¤t_epoch.prev()) + .get(&storage, ¤t_epoch) + .unwrap() + .unwrap(); + assert_eq!(redelegated, amount_redelegate); + + // Advance three epochs + current_epoch = advance_epoch(&mut storage, ¶ms); + process_slashes(&mut storage, current_epoch).unwrap(); + current_epoch = advance_epoch(&mut storage, ¶ms); + process_slashes(&mut storage, current_epoch).unwrap(); + current_epoch = advance_epoch(&mut storage, ¶ms); + process_slashes(&mut storage, current_epoch).unwrap(); + + // Unbond in epoch 5 from dest_validator + let _ = unbond_tokens( + &mut storage, + Some(&delegator), + &dest_validator, + amount_unbond, + current_epoch, + false, + ) + .unwrap(); + + let bond_start = init_epoch + params.pipeline_len; + let redelegation_end = bond_start + params.pipeline_len + 1u64; + let unbond_end = + redelegation_end + params.withdrawable_epoch_offset() + 1u64; + let unbond_materialized = redelegation_end + params.pipeline_len + 1u64; + + // Checks + let redelegated_remaining = delegator_redelegated_bonds_handle(&delegator) + .at(&dest_validator) + .at(&redelegation_end) + .at(&src_validator) + .get(&storage, &bond_start) + .unwrap() + .unwrap_or_default(); + assert_eq!(redelegated_remaining, amount_redelegate - amount_unbond); + + let redel_unbonded = delegator_redelegated_unbonds_handle(&delegator) + .at(&dest_validator) + .at(&redelegation_end) + .at(&unbond_end) + .at(&src_validator) + .get(&storage, &bond_start) + .unwrap() + .unwrap(); + assert_eq!(redel_unbonded, amount_unbond); + + let total_redel_unbonded = + validator_total_redelegated_unbonded_handle(&dest_validator) + .at(&unbond_materialized) + .at(&redelegation_end) + .at(&src_validator) + .get(&storage, &bond_start) + .unwrap() + .unwrap(); + assert_eq!(total_redel_unbonded, amount_unbond); + + // Advance to withdrawal epoch + loop { + current_epoch = advance_epoch(&mut storage, ¶ms); + process_slashes(&mut storage, current_epoch).unwrap(); + if current_epoch == unbond_end { + break; + } + } + + // Withdraw + withdraw_tokens( + &mut storage, + Some(&delegator), + &dest_validator, + current_epoch, + ) + .unwrap(); + + assert!( + delegator_redelegated_unbonds_handle(&delegator) + .at(&dest_validator) + .is_empty(&storage) + .unwrap() + ); + + let delegator_balance = storage + .read::(&token::balance_key(&staking_token, &delegator)) + .unwrap() + .unwrap_or_default(); + assert_eq!( + delegator_balance, + del_balance - amount_delegate + amount_unbond + ); +} + +proptest! { + // Generate arb valid input for `test_slashes_with_unbonding_aux` + #![proptest_config(Config { + cases: 100, + .. Config::default() + })] + #[test] + fn test_slashes_with_unbonding( + (params, genesis_validators, unbond_delay) + in test_slashes_with_unbonding_params() + ) { + test_slashes_with_unbonding_aux( + params, genesis_validators, unbond_delay) + } +} + +fn test_slashes_with_unbonding_aux( + mut params: OwnedPosParams, + validators: Vec, + unbond_delay: u64, +) { + // This can be useful for debugging: + params.pipeline_len = 2; + params.unbonding_len = 4; + // println!("\nTest inputs: {params:?}, genesis validators: + // {validators:#?}"); + let mut s = TestWlStorage::default(); + + // Find the validator with the least stake to avoid the cubic slash rate + // going to 100% + let validator = + itertools::Itertools::sorted_by_key(validators.iter(), |v| v.tokens) + .next() + .unwrap(); + let val_addr = &validator.address; + let val_tokens = validator.tokens; + + // Genesis + // let start_epoch = s.storage.block.epoch; + let mut current_epoch = s.storage.block.epoch; + let params = test_init_genesis( + &mut s, + params, + validators.clone().into_iter(), + current_epoch, + ) + .unwrap(); + s.commit_block().unwrap(); + + current_epoch = advance_epoch(&mut s, ¶ms); + process_slashes(&mut s, current_epoch).unwrap(); + + // Discover first slash + let slash_0_evidence_epoch = current_epoch; + // let slash_0_processing_epoch = + // slash_0_evidence_epoch + params.slash_processing_epoch_offset(); + let evidence_block_height = BlockHeight(0); // doesn't matter for slashing logic + let slash_0_type = SlashType::DuplicateVote; + slash( + &mut s, + ¶ms, + current_epoch, + slash_0_evidence_epoch, + evidence_block_height, + slash_0_type, + val_addr, + current_epoch.next(), + ) + .unwrap(); + + // Advance to an epoch in which we can unbond + let unfreeze_epoch = + slash_0_evidence_epoch + params.slash_processing_epoch_offset(); + while current_epoch < unfreeze_epoch { + current_epoch = advance_epoch(&mut s, ¶ms); + process_slashes(&mut s, current_epoch).unwrap(); + } + + // Advance more epochs randomly from the generated delay + for _ in 0..unbond_delay { + current_epoch = advance_epoch(&mut s, ¶ms); + } + + // Unbond half of the tokens + let unbond_amount = Dec::new(5, 1).unwrap() * val_tokens; + let unbond_epoch = current_epoch; + unbond_tokens(&mut s, None, val_addr, unbond_amount, unbond_epoch, false) + .unwrap(); + + // Discover second slash + let slash_1_evidence_epoch = current_epoch; + // Ensure that both slashes happen before `unbond_epoch + pipeline` + let _slash_1_processing_epoch = + slash_1_evidence_epoch + params.slash_processing_epoch_offset(); + let evidence_block_height = BlockHeight(0); // doesn't matter for slashing logic + let slash_1_type = SlashType::DuplicateVote; + slash( + &mut s, + ¶ms, + current_epoch, + slash_1_evidence_epoch, + evidence_block_height, + slash_1_type, + val_addr, + current_epoch.next(), + ) + .unwrap(); + + // Advance to an epoch in which we can withdraw + let withdraw_epoch = unbond_epoch + params.withdrawable_epoch_offset(); + while current_epoch < withdraw_epoch { + current_epoch = advance_epoch(&mut s, ¶ms); + process_slashes(&mut s, current_epoch).unwrap(); + } + let token = staking_token_address(&s); + let val_balance_pre = read_balance(&s, &token, val_addr).unwrap(); + + let bond_id = BondId { + source: val_addr.clone(), + validator: val_addr.clone(), + }; + let binding = bonds_and_unbonds(&s, None, Some(val_addr.clone())).unwrap(); + let details = binding.get(&bond_id).unwrap(); + let exp_withdraw_from_details = details.unbonds[0].amount + - details.unbonds[0].slashed_amount.unwrap_or_default(); + + withdraw_tokens(&mut s, None, val_addr, current_epoch).unwrap(); + + let val_balance_post = read_balance(&s, &token, val_addr).unwrap(); + let withdrawn_tokens = val_balance_post - val_balance_pre; + + assert_eq!(exp_withdraw_from_details, withdrawn_tokens); + + let slash_rate_0 = validator_slashes_handle(val_addr) + .get(&s, 0) + .unwrap() + .unwrap() + .rate; + let slash_rate_1 = validator_slashes_handle(val_addr) + .get(&s, 1) + .unwrap() + .unwrap() + .rate; + + let expected_withdrawn_amount = Dec::from( + (Dec::one() - slash_rate_1) + * (Dec::one() - slash_rate_0) + * unbond_amount, + ); + // Allow some rounding error, 1 NAMNAM per each slash + let rounding_error_tolerance = + Dec::new(2, NATIVE_MAX_DECIMAL_PLACES).unwrap(); + assert!( + expected_withdrawn_amount.abs_diff(&Dec::from(withdrawn_tokens)) + <= rounding_error_tolerance + ); + + // TODO: finish once implemented + // let slash_0 = decimal_mult_amount(slash_rate_0, val_tokens); + // let slash_1 = decimal_mult_amount(slash_rate_1, val_tokens - slash_0); + // let expected_slash_pool = slash_0 + slash_1; + // let slash_pool_balance = + // read_balance(&s, &token, &SLASH_POOL_ADDRESS).unwrap(); + // assert_eq!(expected_slash_pool, slash_pool_balance); +} + +proptest! { + // Generate arb valid input for `test_simple_redelegation_aux` + #![proptest_config(Config { + cases: 100, + .. Config::default() + })] + #[test] + fn test_redelegation_with_slashing( + + genesis_validators in arb_genesis_validators(2..4, None), + (amount_delegate, amount_redelegate, amount_unbond) in arb_redelegation_amounts(20) + + ) { + test_redelegation_with_slashing_aux(genesis_validators, amount_delegate, amount_redelegate, amount_unbond) + } +} + +fn test_redelegation_with_slashing_aux( + mut validators: Vec, + amount_delegate: token::Amount, + amount_redelegate: token::Amount, + amount_unbond: token::Amount, +) { + validators.sort_by(|a, b| b.tokens.cmp(&a.tokens)); + + let src_validator = validators[0].address.clone(); + let dest_validator = validators[1].address.clone(); + + let mut storage = TestWlStorage::default(); + let params = OwnedPosParams { + unbonding_len: 4, + // Avoid empty consensus set by removing the threshold + validator_stake_threshold: token::Amount::zero(), + ..Default::default() + }; + + // Genesis + let mut current_epoch = storage.storage.block.epoch; + let params = test_init_genesis( + &mut storage, + params, + validators.clone().into_iter(), + current_epoch, + ) + .unwrap(); + storage.commit_block().unwrap(); + + // Get a delegator with some tokens + let staking_token = staking_token_address(&storage); + let delegator = address::testing::gen_implicit_address(); + let del_balance = token::Amount::from_uint(1_000_000, 0).unwrap(); + credit_tokens(&mut storage, &staking_token, &delegator, del_balance) + .unwrap(); + + for _ in 0..5 { + current_epoch = advance_epoch(&mut storage, ¶ms); + process_slashes(&mut storage, current_epoch).unwrap(); + } + + let init_epoch = current_epoch; + + // Delegate in epoch 5 to src_validator + bond_tokens( + &mut storage, + Some(&delegator), + &src_validator, + amount_delegate, + current_epoch, + None, + ) + .unwrap(); + + // Advance three epochs + current_epoch = advance_epoch(&mut storage, ¶ms); + process_slashes(&mut storage, current_epoch).unwrap(); + current_epoch = advance_epoch(&mut storage, ¶ms); + process_slashes(&mut storage, current_epoch).unwrap(); + current_epoch = advance_epoch(&mut storage, ¶ms); + process_slashes(&mut storage, current_epoch).unwrap(); + + // Redelegate in epoch 8 + redelegate_tokens( + &mut storage, + &delegator, + &src_validator, + &dest_validator, + current_epoch, + amount_redelegate, + ) + .unwrap(); + + // Checks + let redelegated = delegator_redelegated_bonds_handle(&delegator) + .at(&dest_validator) + .at(&(current_epoch + params.pipeline_len)) + .at(&src_validator) + .get(&storage, &(init_epoch + params.pipeline_len)) + .unwrap() + .unwrap(); + assert_eq!(redelegated, amount_redelegate); + + let redel_start_epoch = + validator_incoming_redelegations_handle(&dest_validator) + .get(&storage, &delegator) + .unwrap() + .unwrap(); + assert_eq!(redel_start_epoch, current_epoch + params.pipeline_len); + + let redelegated = validator_outgoing_redelegations_handle(&src_validator) + .at(&dest_validator) + .at(¤t_epoch.prev()) + .get(&storage, ¤t_epoch) + .unwrap() + .unwrap(); + assert_eq!(redelegated, amount_redelegate); + + // Advance three epochs + current_epoch = advance_epoch(&mut storage, ¶ms); + process_slashes(&mut storage, current_epoch).unwrap(); + current_epoch = advance_epoch(&mut storage, ¶ms); + process_slashes(&mut storage, current_epoch).unwrap(); + current_epoch = advance_epoch(&mut storage, ¶ms); + process_slashes(&mut storage, current_epoch).unwrap(); + + // Unbond in epoch 11 from dest_validator + let _ = unbond_tokens( + &mut storage, + Some(&delegator), + &dest_validator, + amount_unbond, + current_epoch, + false, + ) + .unwrap(); + + // Advance one epoch + current_epoch = advance_epoch(&mut storage, ¶ms); + process_slashes(&mut storage, current_epoch).unwrap(); + + // Discover evidence + slash( + &mut storage, + ¶ms, + current_epoch, + init_epoch + 2 * params.pipeline_len, + 0u64, + SlashType::DuplicateVote, + &src_validator, + current_epoch.next(), + ) + .unwrap(); + + let bond_start = init_epoch + params.pipeline_len; + let redelegation_end = bond_start + params.pipeline_len + 1u64; + let unbond_end = + redelegation_end + params.withdrawable_epoch_offset() + 1u64; + let unbond_materialized = redelegation_end + params.pipeline_len + 1u64; + + // Checks + let redelegated_remaining = delegator_redelegated_bonds_handle(&delegator) + .at(&dest_validator) + .at(&redelegation_end) + .at(&src_validator) + .get(&storage, &bond_start) + .unwrap() + .unwrap_or_default(); + assert_eq!(redelegated_remaining, amount_redelegate - amount_unbond); + + let redel_unbonded = delegator_redelegated_unbonds_handle(&delegator) + .at(&dest_validator) + .at(&redelegation_end) + .at(&unbond_end) + .at(&src_validator) + .get(&storage, &bond_start) + .unwrap() + .unwrap(); + assert_eq!(redel_unbonded, amount_unbond); + + let total_redel_unbonded = + validator_total_redelegated_unbonded_handle(&dest_validator) + .at(&unbond_materialized) + .at(&redelegation_end) + .at(&src_validator) + .get(&storage, &bond_start) + .unwrap() + .unwrap(); + assert_eq!(total_redel_unbonded, amount_unbond); + + // Advance to withdrawal epoch + loop { + current_epoch = advance_epoch(&mut storage, ¶ms); + process_slashes(&mut storage, current_epoch).unwrap(); + if current_epoch == unbond_end { + break; + } + } + + // Withdraw + withdraw_tokens( + &mut storage, + Some(&delegator), + &dest_validator, + current_epoch, + ) + .unwrap(); + + assert!( + delegator_redelegated_unbonds_handle(&delegator) + .at(&dest_validator) + .is_empty(&storage) + .unwrap() + ); + + let delegator_balance = storage + .read::(&token::balance_key(&staking_token, &delegator)) + .unwrap() + .unwrap_or_default(); + assert_eq!(delegator_balance, del_balance - amount_delegate); +} + +proptest! { + // Generate arb valid input for `test_chain_redelegations_aux` + #![proptest_config(Config { + cases: 100, + .. Config::default() + })] + #[test] + fn test_chain_redelegations( + + genesis_validators in arb_genesis_validators(3..4, None), + + ) { + test_chain_redelegations_aux(genesis_validators) + } +} + +fn test_chain_redelegations_aux(mut validators: Vec) { + validators.sort_by(|a, b| b.tokens.cmp(&a.tokens)); + + let src_validator = validators[0].address.clone(); + let _init_stake_src = validators[0].tokens; + let dest_validator = validators[1].address.clone(); + let _init_stake_dest = validators[1].tokens; + let dest_validator_2 = validators[2].address.clone(); + let _init_stake_dest_2 = validators[2].tokens; + + let mut storage = TestWlStorage::default(); + let params = OwnedPosParams { + unbonding_len: 4, + ..Default::default() + }; + + // Genesis + let mut current_epoch = storage.storage.block.epoch; + let params = test_init_genesis( + &mut storage, + params, + validators.clone().into_iter(), + current_epoch, + ) + .unwrap(); + storage.commit_block().unwrap(); + + // Get a delegator with some tokens + let staking_token = staking_token_address(&storage); + let delegator = address::testing::gen_implicit_address(); + let del_balance = token::Amount::from_uint(1_000_000, 0).unwrap(); + credit_tokens(&mut storage, &staking_token, &delegator, del_balance) + .unwrap(); + + // Delegate in epoch 0 to src_validator + let bond_amount: token::Amount = 100.into(); + bond_tokens( + &mut storage, + Some(&delegator), + &src_validator, + bond_amount, + current_epoch, + None, + ) + .unwrap(); + + let bond_start = current_epoch + params.pipeline_len; + + // Advance one epoch + current_epoch = advance_epoch(&mut storage, ¶ms); + process_slashes(&mut storage, current_epoch).unwrap(); + + // Redelegate in epoch 1 to dest_validator + let redel_amount_1: token::Amount = 58.into(); + redelegate_tokens( + &mut storage, + &delegator, + &src_validator, + &dest_validator, + current_epoch, + redel_amount_1, + ) + .unwrap(); + + let redel_start = current_epoch; + let redel_end = current_epoch + params.pipeline_len; + + // Checks ---------------- + + // Dest validator should have an incoming redelegation + let incoming_redelegation = + validator_incoming_redelegations_handle(&dest_validator) + .get(&storage, &delegator) + .unwrap(); + assert_eq!(incoming_redelegation, Some(redel_end)); + + // Src validator should have an outoging redelegation + let outgoing_redelegation = + validator_outgoing_redelegations_handle(&src_validator) + .at(&dest_validator) + .at(&bond_start) + .get(&storage, &redel_start) + .unwrap(); + assert_eq!(outgoing_redelegation, Some(redel_amount_1)); + + // Delegator should have redelegated bonds + let del_total_redelegated_bonded = + delegator_redelegated_bonds_handle(&delegator) + .at(&dest_validator) + .at(&redel_end) + .at(&src_validator) + .get(&storage, &bond_start) + .unwrap() + .unwrap_or_default(); + assert_eq!(del_total_redelegated_bonded, redel_amount_1); + + // There should be delegator bonds for both src and dest validators + let bonded_src = bond_handle(&delegator, &src_validator); + let bonded_dest = bond_handle(&delegator, &dest_validator); + assert_eq!( + bonded_src + .get_delta_val(&storage, bond_start) + .unwrap() + .unwrap_or_default(), + bond_amount - redel_amount_1 + ); + assert_eq!( + bonded_dest + .get_delta_val(&storage, redel_end) + .unwrap() + .unwrap_or_default(), + redel_amount_1 + ); + + // The dest validator should have total redelegated bonded tokens + let dest_total_redelegated_bonded = + validator_total_redelegated_bonded_handle(&dest_validator) + .at(&redel_end) + .at(&src_validator) + .get(&storage, &bond_start) + .unwrap() + .unwrap_or_default(); + assert_eq!(dest_total_redelegated_bonded, redel_amount_1); + + // The dest validator's total bonded should have an entry for the genesis + // bond and the redelegation + let dest_total_bonded = total_bonded_handle(&dest_validator) + .get_data_handler() + .collect_map(&storage) + .unwrap(); + assert!( + dest_total_bonded.len() == 2 + && dest_total_bonded.contains_key(&Epoch::default()) + ); + assert_eq!( + dest_total_bonded + .get(&redel_end) + .cloned() + .unwrap_or_default(), + redel_amount_1 + ); + + // The src validator should have a total bonded entry for the original bond + // accounting for the redelegation + assert_eq!( + total_bonded_handle(&src_validator) + .get_delta_val(&storage, bond_start) + .unwrap() + .unwrap_or_default(), + bond_amount - redel_amount_1 + ); + + // The src validator should have a total unbonded entry due to the + // redelegation + let src_total_unbonded = total_unbonded_handle(&src_validator) + .at(&redel_end) + .get(&storage, &bond_start) + .unwrap() + .unwrap_or_default(); + assert_eq!(src_total_unbonded, redel_amount_1); + + // Attempt to redelegate in epoch 3 to dest_validator + current_epoch = advance_epoch(&mut storage, ¶ms); + process_slashes(&mut storage, current_epoch).unwrap(); + current_epoch = advance_epoch(&mut storage, ¶ms); + process_slashes(&mut storage, current_epoch).unwrap(); + + let redel_amount_2: token::Amount = 23.into(); + let redel_att = redelegate_tokens( + &mut storage, + &delegator, + &dest_validator, + &dest_validator_2, + current_epoch, + redel_amount_2, + ); + assert!(redel_att.is_err()); + + // Advance to right before the redelegation can be redelegated again + assert_eq!(redel_end, current_epoch); + let epoch_can_redel = + redel_end.prev() + params.slash_processing_epoch_offset(); + loop { + current_epoch = advance_epoch(&mut storage, ¶ms); + process_slashes(&mut storage, current_epoch).unwrap(); + if current_epoch == epoch_can_redel.prev() { + break; + } + } + + // Attempt to redelegate in epoch before we actually are able to + let redel_att = redelegate_tokens( + &mut storage, + &delegator, + &dest_validator, + &dest_validator_2, + current_epoch, + redel_amount_2, + ); + assert!(redel_att.is_err()); + + // Advance one more epoch + current_epoch = advance_epoch(&mut storage, ¶ms); + process_slashes(&mut storage, current_epoch).unwrap(); + + // Redelegate from dest_validator to dest_validator_2 now + redelegate_tokens( + &mut storage, + &delegator, + &dest_validator, + &dest_validator_2, + current_epoch, + redel_amount_2, + ) + .unwrap(); + + let redel_2_start = current_epoch; + let redel_2_end = current_epoch + params.pipeline_len; + + // Checks ----------------------------------- + + // Both the dest validator and dest validator 2 should have incoming + // redelegations + let incoming_redelegation_1 = + validator_incoming_redelegations_handle(&dest_validator) + .get(&storage, &delegator) + .unwrap(); + assert_eq!(incoming_redelegation_1, Some(redel_end)); + let incoming_redelegation_2 = + validator_incoming_redelegations_handle(&dest_validator_2) + .get(&storage, &delegator) + .unwrap(); + assert_eq!(incoming_redelegation_2, Some(redel_2_end)); + + // Both the src validator and dest validator should have outgoing + // redelegations + let outgoing_redelegation_1 = + validator_outgoing_redelegations_handle(&src_validator) + .at(&dest_validator) + .at(&bond_start) + .get(&storage, &redel_start) + .unwrap(); + assert_eq!(outgoing_redelegation_1, Some(redel_amount_1)); + + let outgoing_redelegation_2 = + validator_outgoing_redelegations_handle(&dest_validator) + .at(&dest_validator_2) + .at(&redel_end) + .get(&storage, &redel_2_start) + .unwrap(); + assert_eq!(outgoing_redelegation_2, Some(redel_amount_2)); + + // All three validators should have bonds + let bonded_dest2 = bond_handle(&delegator, &dest_validator_2); + assert_eq!( + bonded_src + .get_delta_val(&storage, bond_start) + .unwrap() + .unwrap_or_default(), + bond_amount - redel_amount_1 + ); + assert_eq!( + bonded_dest + .get_delta_val(&storage, redel_end) + .unwrap() + .unwrap_or_default(), + redel_amount_1 - redel_amount_2 + ); + assert_eq!( + bonded_dest2 + .get_delta_val(&storage, redel_2_end) + .unwrap() + .unwrap_or_default(), + redel_amount_2 + ); + + // There should be no unbond entries + let unbond_src = unbond_handle(&delegator, &src_validator); + let unbond_dest = unbond_handle(&delegator, &dest_validator); + assert!(unbond_src.is_empty(&storage).unwrap()); + assert!(unbond_dest.is_empty(&storage).unwrap()); + + // The dest validator should have some total unbonded due to the second + // redelegation + let dest_total_unbonded = total_unbonded_handle(&dest_validator) + .at(&redel_2_end) + .get(&storage, &redel_end) + .unwrap(); + assert_eq!(dest_total_unbonded, Some(redel_amount_2)); + + // Delegator should have redelegated bonds due to both redelegations + let del_redelegated_bonds = delegator_redelegated_bonds_handle(&delegator); + assert_eq!( + Some(redel_amount_1 - redel_amount_2), + del_redelegated_bonds + .at(&dest_validator) + .at(&redel_end) + .at(&src_validator) + .get(&storage, &bond_start) + .unwrap() + ); + assert_eq!( + Some(redel_amount_2), + del_redelegated_bonds + .at(&dest_validator_2) + .at(&redel_2_end) + .at(&dest_validator) + .get(&storage, &redel_end) + .unwrap() + ); + + // Delegator redelegated unbonds should be empty + assert!( + delegator_redelegated_unbonds_handle(&delegator) + .is_empty(&storage) + .unwrap() + ); + + // Both the dest validator and dest validator 2 should have total + // redelegated bonds + let dest_redelegated_bonded = + validator_total_redelegated_bonded_handle(&dest_validator) + .at(&redel_end) + .at(&src_validator) + .get(&storage, &bond_start) + .unwrap() + .unwrap_or_default(); + let dest2_redelegated_bonded = + validator_total_redelegated_bonded_handle(&dest_validator_2) + .at(&redel_2_end) + .at(&dest_validator) + .get(&storage, &redel_end) + .unwrap() + .unwrap_or_default(); + assert_eq!(dest_redelegated_bonded, redel_amount_1 - redel_amount_2); + assert_eq!(dest2_redelegated_bonded, redel_amount_2); + + // Total redelegated unbonded should be empty for src_validator and + // dest_validator_2 + assert!( + validator_total_redelegated_unbonded_handle(&dest_validator_2) + .is_empty(&storage) + .unwrap() + ); + assert!( + validator_total_redelegated_unbonded_handle(&src_validator) + .is_empty(&storage) + .unwrap() + ); + + // The dest_validator should have total_redelegated unbonded + let tot_redel_unbonded = + validator_total_redelegated_unbonded_handle(&dest_validator) + .at(&redel_2_end) + .at(&redel_end) + .at(&src_validator) + .get(&storage, &bond_start) + .unwrap() + .unwrap_or_default(); + assert_eq!(tot_redel_unbonded, redel_amount_2); +} + +proptest! { + // Generate arb valid input for `test_overslashing_aux` + #![proptest_config(Config { + cases: 1, + .. Config::default() + })] + #[test] + fn test_overslashing( + + genesis_validators in arb_genesis_validators(4..5, None), + + ) { + test_overslashing_aux(genesis_validators) + } +} + +/// Test precisely that we are not overslashing, as originally discovered by Tomas in this issue: https://github.com/informalsystems/partnership-heliax/issues/74 +fn test_overslashing_aux(mut validators: Vec) { + assert_eq!(validators.len(), 4); + + let params = OwnedPosParams { + unbonding_len: 4, + ..Default::default() + }; + + let offending_stake = token::Amount::native_whole(110); + let other_stake = token::Amount::native_whole(100); + + // Set stakes so we know we will get a slashing rate between 0.5 -1.0 + validators[0].tokens = offending_stake; + validators[1].tokens = other_stake; + validators[2].tokens = other_stake; + validators[3].tokens = other_stake; + + // Get the offending validator + let validator = validators[0].address.clone(); + + // println!("\nTest inputs: {params:?}, genesis validators: + // {validators:#?}"); + let mut storage = TestWlStorage::default(); + + // Genesis + let mut current_epoch = storage.storage.block.epoch; + let params = test_init_genesis( + &mut storage, + params, + validators.clone().into_iter(), + current_epoch, + ) + .unwrap(); + storage.commit_block().unwrap(); + + // Get a delegator with some tokens + let staking_token = storage.storage.native_token.clone(); + let delegator = address::testing::gen_implicit_address(); + let amount_del = token::Amount::native_whole(5); + credit_tokens(&mut storage, &staking_token, &delegator, amount_del) + .unwrap(); + + // Delegate tokens in epoch 0 to validator + bond_tokens( + &mut storage, + Some(&delegator), + &validator, + amount_del, + current_epoch, + None, + ) + .unwrap(); + + let self_bond_epoch = current_epoch; + let delegation_epoch = current_epoch + params.pipeline_len; + + // Advance to pipeline epoch + for _ in 0..params.pipeline_len { + current_epoch = advance_epoch(&mut storage, ¶ms); + } + assert_eq!(delegation_epoch, current_epoch); + + // Find a misbehavior committed in epoch 0 + slash( + &mut storage, + ¶ms, + current_epoch, + self_bond_epoch, + 0_u64, + SlashType::DuplicateVote, + &validator, + current_epoch.next(), + ) + .unwrap(); + + // Find a misbehavior committed in current epoch + slash( + &mut storage, + ¶ms, + current_epoch, + delegation_epoch, + 0_u64, + SlashType::DuplicateVote, + &validator, + current_epoch.next(), + ) + .unwrap(); + + let processing_epoch_1 = + self_bond_epoch + params.slash_processing_epoch_offset(); + let processing_epoch_2 = + delegation_epoch + params.slash_processing_epoch_offset(); + + // Advance to processing epoch 1 + loop { + current_epoch = advance_epoch(&mut storage, ¶ms); + process_slashes(&mut storage, current_epoch).unwrap(); + if current_epoch == processing_epoch_1 { + break; + } + } + + let total_stake_1 = offending_stake + 3 * other_stake; + let stake_frac = Dec::from(offending_stake) / Dec::from(total_stake_1); + let slash_rate_1 = Dec::from_str("9.0").unwrap() * stake_frac * stake_frac; + + let exp_slashed_1 = offending_stake.mul_ceil(slash_rate_1); + + // Check that the proper amount was slashed + let epoch = current_epoch.next(); + let validator_stake = + read_validator_stake(&storage, ¶ms, &validator, epoch).unwrap(); + let exp_validator_stake = offending_stake - exp_slashed_1 + amount_del; + assert_eq!(validator_stake, exp_validator_stake); + + let total_stake = read_total_stake(&storage, ¶ms, epoch).unwrap(); + let exp_total_stake = + offending_stake - exp_slashed_1 + amount_del + 3 * other_stake; + assert_eq!(total_stake, exp_total_stake); + + let self_bond_id = BondId { + source: validator.clone(), + validator: validator.clone(), + }; + let bond_amount = + crate::bond_amount(&storage, &self_bond_id, epoch).unwrap(); + let exp_bond_amount = offending_stake - exp_slashed_1; + assert_eq!(bond_amount, exp_bond_amount); + + // Advance to processing epoch 2 + loop { + current_epoch = advance_epoch(&mut storage, ¶ms); + process_slashes(&mut storage, current_epoch).unwrap(); + if current_epoch == processing_epoch_2 { + break; + } + } + + let total_stake_2 = offending_stake + amount_del + 3 * other_stake; + let stake_frac = + Dec::from(offending_stake + amount_del) / Dec::from(total_stake_2); + let slash_rate_2 = Dec::from_str("9.0").unwrap() * stake_frac * stake_frac; + + let exp_slashed_from_delegation = amount_del.mul_ceil(slash_rate_2); + + // Check that the proper amount was slashed. We expect that all of the + // validator self-bond has been slashed and some of the delegation has been + // slashed due to the second infraction. + let epoch = current_epoch.next(); + + let validator_stake = + read_validator_stake(&storage, ¶ms, &validator, epoch).unwrap(); + let exp_validator_stake = amount_del - exp_slashed_from_delegation; + assert_eq!(validator_stake, exp_validator_stake); + + let total_stake = read_total_stake(&storage, ¶ms, epoch).unwrap(); + let exp_total_stake = + amount_del - exp_slashed_from_delegation + 3 * other_stake; + assert_eq!(total_stake, exp_total_stake); + + let delegation_id = BondId { + source: delegator.clone(), + validator: validator.clone(), + }; + let delegation_amount = + crate::bond_amount(&storage, &delegation_id, epoch).unwrap(); + let exp_del_amount = amount_del - exp_slashed_from_delegation; + assert_eq!(delegation_amount, exp_del_amount); + + let self_bond_amount = + crate::bond_amount(&storage, &self_bond_id, epoch).unwrap(); + let exp_bond_amount = token::Amount::zero(); + assert_eq!(self_bond_amount, exp_bond_amount); +} + +proptest! { + // Generate arb valid input for `test_slashed_bond_amount_aux` + #![proptest_config(Config { + cases: 1, + .. Config::default() + })] + #[test] + fn test_slashed_bond_amount( + + genesis_validators in arb_genesis_validators(4..5, None), + + ) { + test_slashed_bond_amount_aux(genesis_validators) + } +} + +fn test_slashed_bond_amount_aux(validators: Vec) { + let mut storage = TestWlStorage::default(); + let params = OwnedPosParams { + unbonding_len: 4, + ..Default::default() + }; + + let init_tot_stake = validators + .clone() + .into_iter() + .fold(token::Amount::zero(), |acc, v| acc + v.tokens); + let val1_init_stake = validators[0].tokens; + + let mut validators = validators; + validators[0].tokens = (init_tot_stake - val1_init_stake) / 30; + + // Genesis + let mut current_epoch = storage.storage.block.epoch; + let params = test_init_genesis( + &mut storage, + params, + validators.clone().into_iter(), + current_epoch, + ) + .unwrap(); + storage.commit_block().unwrap(); + + let validator1 = validators[0].address.clone(); + let validator2 = validators[1].address.clone(); + + // Get a delegator with some tokens + let staking_token = staking_token_address(&storage); + let delegator = address::testing::gen_implicit_address(); + let del_balance = token::Amount::from_uint(1_000_000, 0).unwrap(); + credit_tokens(&mut storage, &staking_token, &delegator, del_balance) + .unwrap(); + + // Bond to validator 1 + bond_tokens( + &mut storage, + Some(&delegator), + &validator1, + 10_000.into(), + current_epoch, + None, + ) + .unwrap(); + + // Unbond some from validator 1 + unbond_tokens( + &mut storage, + Some(&delegator), + &validator1, + 1_342.into(), + current_epoch, + false, + ) + .unwrap(); + + // Redelegate some from validator 1 -> 2 + redelegate_tokens( + &mut storage, + &delegator, + &validator1, + &validator2, + current_epoch, + 1_875.into(), + ) + .unwrap(); + + // Unbond some from validator 2 + unbond_tokens( + &mut storage, + Some(&delegator), + &validator2, + 584.into(), + current_epoch, + false, + ) + .unwrap(); + + // Advance an epoch to 1 + current_epoch = advance_epoch(&mut storage, ¶ms); + process_slashes(&mut storage, current_epoch).unwrap(); + + // Bond to validator 1 + bond_tokens( + &mut storage, + Some(&delegator), + &validator1, + 384.into(), + current_epoch, + None, + ) + .unwrap(); + + // Unbond some from validator 1 + unbond_tokens( + &mut storage, + Some(&delegator), + &validator1, + 144.into(), + current_epoch, + false, + ) + .unwrap(); + + // Redelegate some from validator 1 -> 2 + redelegate_tokens( + &mut storage, + &delegator, + &validator1, + &validator2, + current_epoch, + 3_448.into(), + ) + .unwrap(); + + // Unbond some from validator 2 + unbond_tokens( + &mut storage, + Some(&delegator), + &validator2, + 699.into(), + current_epoch, + false, + ) + .unwrap(); + + // Advance an epoch to ep 2 + current_epoch = advance_epoch(&mut storage, ¶ms); + process_slashes(&mut storage, current_epoch).unwrap(); + + // Bond to validator 1 + bond_tokens( + &mut storage, + Some(&delegator), + &validator1, + 4_384.into(), + current_epoch, + None, + ) + .unwrap(); + + // Redelegate some from validator 1 -> 2 + redelegate_tokens( + &mut storage, + &delegator, + &validator1, + &validator2, + current_epoch, + 1_008.into(), + ) + .unwrap(); + + // Unbond some from validator 2 + unbond_tokens( + &mut storage, + Some(&delegator), + &validator2, + 3_500.into(), + current_epoch, + false, + ) + .unwrap(); + + // Advance two epochs to ep 4 + for _ in 0..2 { + current_epoch = advance_epoch(&mut storage, ¶ms); + process_slashes(&mut storage, current_epoch).unwrap(); + } + + // Find some slashes committed in various epochs + slash( + &mut storage, + ¶ms, + current_epoch, + Epoch(1), + 1_u64, + SlashType::DuplicateVote, + &validator1, + current_epoch, + ) + .unwrap(); + slash( + &mut storage, + ¶ms, + current_epoch, + Epoch(2), + 1_u64, + SlashType::DuplicateVote, + &validator1, + current_epoch, + ) + .unwrap(); + slash( + &mut storage, + ¶ms, + current_epoch, + Epoch(2), + 1_u64, + SlashType::DuplicateVote, + &validator1, + current_epoch, + ) + .unwrap(); + slash( + &mut storage, + ¶ms, + current_epoch, + Epoch(3), + 1_u64, + SlashType::DuplicateVote, + &validator1, + current_epoch, + ) + .unwrap(); + + // Advance such that these slashes are all processed + for _ in 0..params.slash_processing_epoch_offset() { + current_epoch = advance_epoch(&mut storage, ¶ms); + process_slashes(&mut storage, current_epoch).unwrap(); + } + + let pipeline_epoch = current_epoch + params.pipeline_len; + + let del_bond_amount = crate::bond_amount( + &storage, + &BondId { + source: delegator.clone(), + validator: validator1.clone(), + }, + pipeline_epoch, + ) + .unwrap_or_default(); + + let self_bond_amount = crate::bond_amount( + &storage, + &BondId { + source: validator1.clone(), + validator: validator1.clone(), + }, + pipeline_epoch, + ) + .unwrap_or_default(); + + let val_stake = crate::read_validator_stake( + &storage, + ¶ms, + &validator1, + pipeline_epoch, + ) + .unwrap(); + + let diff = val_stake - self_bond_amount - del_bond_amount; + assert!(diff <= 2.into()); +} diff --git a/proof_of_stake/src/tests/test_validator.rs b/proof_of_stake/src/tests/test_validator.rs new file mode 100644 index 0000000000..dc40c1e512 --- /dev/null +++ b/proof_of_stake/src/tests/test_validator.rs @@ -0,0 +1,1191 @@ +use std::cmp::min; + +use namada_core::ledger::storage::testing::TestWlStorage; +use namada_core::ledger::storage_api::collections::lazy_map; +use namada_core::ledger::storage_api::token::credit_tokens; +use namada_core::types::address::testing::arb_established_address; +use namada_core::types::address::{self, Address, EstablishedAddressGen}; +use namada_core::types::dec::Dec; +use namada_core::types::key::testing::{ + arb_common_keypair, common_sk_from_simple_seed, +}; +use namada_core::types::key::{self, common, RefTo, SecretKey}; +use namada_core::types::storage::Epoch; +use namada_core::types::token; +use proptest::prelude::*; +use proptest::test_runner::Config; +// Use `RUST_LOG=info` (or another tracing level) and `--nocapture` to see +// `tracing` logs from tests +use test_log::test; + +use crate::epoched::DEFAULT_NUM_PAST_EPOCHS; +use crate::storage::{ + below_capacity_validator_set_handle, bond_handle, + consensus_validator_set_handle, find_validator_by_raw_hash, + get_num_consensus_validators, + read_below_capacity_validator_set_addresses_with_stake, + read_below_threshold_validator_set_addresses, + read_consensus_validator_set_addresses_with_stake, update_validator_deltas, + validator_consensus_key_handle, write_validator_address_raw_hash, +}; +use crate::test_utils::test_init_genesis; +use crate::tests::helpers::{ + advance_epoch, arb_params_and_genesis_validators, + get_tendermint_set_updates, +}; +use crate::types::{ + into_tm_voting_power, ConsensusValidator, GenesisValidator, Position, + ReverseOrdTokenAmount, ValidatorSetUpdate, WeightedValidator, +}; +use crate::validator_set_update::{ + insert_validator_into_validator_set, update_validator_set, +}; +use crate::{ + become_validator, bond_tokens, is_validator, staking_token_address, + unbond_tokens, withdraw_tokens, BecomeValidator, OwnedPosParams, +}; + +proptest! { + // Generate arb valid input for `test_become_validator_aux` + #![proptest_config(Config { + cases: 100, + .. Config::default() + })] + #[test] + fn test_become_validator( + + (pos_params, genesis_validators) in arb_params_and_genesis_validators(Some(5), 1..3), + new_validator in arb_established_address().prop_map(Address::Established), + new_validator_consensus_key in arb_common_keypair(), + + ) { + test_become_validator_aux(pos_params, new_validator, + new_validator_consensus_key, genesis_validators) + } +} + +/// Test validator initialization. +fn test_become_validator_aux( + params: OwnedPosParams, + new_validator: Address, + new_validator_consensus_key: common::SecretKey, + validators: Vec, +) { + // println!( + // "Test inputs: {params:?}, new validator: {new_validator}, genesis \ + // validators: {validators:#?}" + // ); + + let mut s = TestWlStorage::default(); + + // Genesis + let mut current_epoch = s.storage.block.epoch; + let params = test_init_genesis( + &mut s, + params, + validators.clone().into_iter(), + current_epoch, + ) + .unwrap(); + s.commit_block().unwrap(); + + // Advance to epoch 1 + current_epoch = advance_epoch(&mut s, ¶ms); + + let num_consensus_before = + get_num_consensus_validators(&s, current_epoch + params.pipeline_len) + .unwrap(); + let num_validators_over_thresh = validators + .iter() + .filter(|validator| { + validator.tokens >= params.validator_stake_threshold + }) + .count(); + + assert_eq!( + min( + num_validators_over_thresh as u64, + params.max_validator_slots + ), + num_consensus_before + ); + assert!(!is_validator(&s, &new_validator).unwrap()); + + // Credit the `new_validator` account + let staking_token = staking_token_address(&s); + let amount = token::Amount::from_uint(100_500_000, 0).unwrap(); + // Credit twice the amount as we're gonna bond it in delegation first, then + // self-bond + credit_tokens(&mut s, &staking_token, &new_validator, amount * 2).unwrap(); + + // Add a delegation from `new_validator` to `genesis_validator` + let genesis_validator = &validators.first().unwrap().address; + bond_tokens( + &mut s, + Some(&new_validator), + genesis_validator, + amount, + current_epoch, + None, + ) + .unwrap(); + + let consensus_key = new_validator_consensus_key.to_public(); + let protocol_sk = common_sk_from_simple_seed(0); + let protocol_key = protocol_sk.to_public(); + let eth_hot_key = key::common::PublicKey::Secp256k1( + key::testing::gen_keypair::().ref_to(), + ); + let eth_cold_key = key::common::PublicKey::Secp256k1( + key::testing::gen_keypair::().ref_to(), + ); + + // Try to become a validator - it should fail as there is a delegation + let result = become_validator( + &mut s, + BecomeValidator { + params: ¶ms, + address: &new_validator, + consensus_key: &consensus_key, + protocol_key: &protocol_key, + eth_cold_key: ð_cold_key, + eth_hot_key: ð_hot_key, + current_epoch, + commission_rate: Dec::new(5, 2).expect("Dec creation failed"), + max_commission_rate_change: Dec::new(5, 2) + .expect("Dec creation failed"), + metadata: Default::default(), + offset_opt: None, + }, + ); + assert!(result.is_err()); + assert!(!is_validator(&s, &new_validator).unwrap()); + + // Unbond the delegation + unbond_tokens( + &mut s, + Some(&new_validator), + genesis_validator, + amount, + current_epoch, + false, + ) + .unwrap(); + + // Try to become a validator account again - it should pass now + become_validator( + &mut s, + BecomeValidator { + params: ¶ms, + address: &new_validator, + consensus_key: &consensus_key, + protocol_key: &protocol_key, + eth_cold_key: ð_cold_key, + eth_hot_key: ð_hot_key, + current_epoch, + commission_rate: Dec::new(5, 2).expect("Dec creation failed"), + max_commission_rate_change: Dec::new(5, 2) + .expect("Dec creation failed"), + metadata: Default::default(), + offset_opt: None, + }, + ) + .unwrap(); + assert!(is_validator(&s, &new_validator).unwrap()); + + let num_consensus_after = + get_num_consensus_validators(&s, current_epoch + params.pipeline_len) + .unwrap(); + // The new validator is initialized with no stake and thus is in the + // below-threshold set + assert_eq!(num_consensus_before, num_consensus_after); + + // Advance to epoch 2 + current_epoch = advance_epoch(&mut s, ¶ms); + + // Self-bond to the new validator + bond_tokens(&mut s, None, &new_validator, amount, current_epoch, None) + .unwrap(); + + // Check the bond delta + let bond_handle = bond_handle(&new_validator, &new_validator); + let pipeline_epoch = current_epoch + params.pipeline_len; + let delta = bond_handle.get_delta_val(&s, pipeline_epoch).unwrap(); + assert_eq!(delta, Some(amount)); + + // Check the validator in the validator set - + // If the consensus validator slots are full and all the genesis validators + // have stake GTE the new validator's self-bond amount, the validator should + // be added to the below-capacity set, or the consensus otherwise + if params.max_validator_slots <= validators.len() as u64 + && validators + .iter() + .all(|validator| validator.tokens >= amount) + { + let set = read_below_capacity_validator_set_addresses_with_stake( + &s, + pipeline_epoch, + ) + .unwrap(); + assert!(set.into_iter().any( + |WeightedValidator { + bonded_stake, + address, + }| { + address == new_validator && bonded_stake == amount + } + )); + } else { + let set = read_consensus_validator_set_addresses_with_stake( + &s, + pipeline_epoch, + ) + .unwrap(); + assert!(set.into_iter().any( + |WeightedValidator { + bonded_stake, + address, + }| { + address == new_validator && bonded_stake == amount + } + )); + } + + // Advance to epoch 3 + current_epoch = advance_epoch(&mut s, ¶ms); + + // Unbond the self-bond + unbond_tokens(&mut s, None, &new_validator, amount, current_epoch, false) + .unwrap(); + + let withdrawable_offset = params.unbonding_len + params.pipeline_len; + + // Advance to withdrawable epoch + for _ in 0..withdrawable_offset { + current_epoch = advance_epoch(&mut s, ¶ms); + } + + // Withdraw the self-bond + withdraw_tokens(&mut s, None, &new_validator, current_epoch).unwrap(); +} + +#[test] +fn test_validator_raw_hash() { + let mut storage = TestWlStorage::default(); + let address = address::testing::established_address_1(); + let consensus_sk = key::testing::keypair_1(); + let consensus_pk = consensus_sk.to_public(); + let expected_raw_hash = key::tm_consensus_key_raw_hash(&consensus_pk); + + assert!( + find_validator_by_raw_hash(&storage, &expected_raw_hash) + .unwrap() + .is_none() + ); + write_validator_address_raw_hash(&mut storage, &address, &consensus_pk) + .unwrap(); + let found = + find_validator_by_raw_hash(&storage, &expected_raw_hash).unwrap(); + assert_eq!(found, Some(address)); +} + +#[test] +fn test_validator_sets() { + let mut s = TestWlStorage::default(); + // Only 3 consensus validator slots + let params = OwnedPosParams { + max_validator_slots: 3, + ..Default::default() + }; + let addr_seed = "seed"; + let mut address_gen = EstablishedAddressGen::new(addr_seed); + let mut sk_seed = 0; + let mut gen_validator = || { + let res = ( + address_gen.generate_address(addr_seed), + key::testing::common_sk_from_simple_seed(sk_seed).to_public(), + ); + // bump the sk seed + sk_seed += 1; + res + }; + + // Create genesis validators + let ((val1, pk1), stake1) = + (gen_validator(), token::Amount::native_whole(1)); + let ((val2, pk2), stake2) = + (gen_validator(), token::Amount::native_whole(1)); + let ((val3, pk3), stake3) = + (gen_validator(), token::Amount::native_whole(10)); + let ((val4, pk4), stake4) = + (gen_validator(), token::Amount::native_whole(1)); + let ((val5, pk5), stake5) = + (gen_validator(), token::Amount::native_whole(100)); + let ((val6, pk6), stake6) = + (gen_validator(), token::Amount::native_whole(1)); + let ((val7, pk7), stake7) = + (gen_validator(), token::Amount::native_whole(1)); + // println!("\nval1: {val1}, {pk1}, {}", stake1.to_string_native()); + // println!("val2: {val2}, {pk2}, {}", stake2.to_string_native()); + // println!("val3: {val3}, {pk3}, {}", stake3.to_string_native()); + // println!("val4: {val4}, {pk4}, {}", stake4.to_string_native()); + // println!("val5: {val5}, {pk5}, {}", stake5.to_string_native()); + // println!("val6: {val6}, {pk6}, {}", stake6.to_string_native()); + // println!("val7: {val7}, {pk7}, {}", stake7.to_string_native()); + + let start_epoch = Epoch::default(); + let epoch = start_epoch; + + let protocol_sk_1 = common_sk_from_simple_seed(0); + let protocol_sk_2 = common_sk_from_simple_seed(1); + + let params = test_init_genesis( + &mut s, + params, + [ + GenesisValidator { + address: val1.clone(), + tokens: stake1, + consensus_key: pk1.clone(), + protocol_key: protocol_sk_1.to_public(), + eth_hot_key: key::common::PublicKey::Secp256k1( + key::testing::gen_keypair::() + .ref_to(), + ), + eth_cold_key: key::common::PublicKey::Secp256k1( + key::testing::gen_keypair::() + .ref_to(), + ), + commission_rate: Dec::new(1, 1).expect("Dec creation failed"), + max_commission_rate_change: Dec::new(1, 1) + .expect("Dec creation failed"), + metadata: Default::default(), + }, + GenesisValidator { + address: val2.clone(), + tokens: stake2, + consensus_key: pk2.clone(), + protocol_key: protocol_sk_2.to_public(), + eth_hot_key: key::common::PublicKey::Secp256k1( + key::testing::gen_keypair::() + .ref_to(), + ), + eth_cold_key: key::common::PublicKey::Secp256k1( + key::testing::gen_keypair::() + .ref_to(), + ), + commission_rate: Dec::new(1, 1).expect("Dec creation failed"), + max_commission_rate_change: Dec::new(1, 1) + .expect("Dec creation failed"), + metadata: Default::default(), + }, + ] + .into_iter(), + epoch, + ) + .unwrap(); + + // A helper to insert a non-genesis validator + let insert_validator = |s: &mut TestWlStorage, + addr, + pk: &common::PublicKey, + stake: token::Amount, + epoch: Epoch| { + insert_validator_into_validator_set( + s, + ¶ms, + addr, + stake, + epoch, + params.pipeline_len, + ) + .unwrap(); + + update_validator_deltas(s, ¶ms, addr, stake.change(), epoch, None) + .unwrap(); + + // Set their consensus key (needed for + // `validator_set_update_tendermint` fn) + validator_consensus_key_handle(addr) + .set(s, pk.clone(), epoch, params.pipeline_len) + .unwrap(); + }; + + // Advance to EPOCH 1 + // + // We cannot call `get_tendermint_set_updates` for the genesis state as + // `validator_set_update_tendermint` is only called 2 blocks before the + // start of an epoch and so we need to give it a predecessor epoch (see + // `get_tendermint_set_updates`), which we cannot have on the first + // epoch. In any way, the initial validator set is given to Tendermint + // from InitChain, so `validator_set_update_tendermint` is + // not being used for it. + let epoch = advance_epoch(&mut s, ¶ms); + let pipeline_epoch = epoch + params.pipeline_len; + + // Insert another validator with the greater stake 10 NAM + insert_validator(&mut s, &val3, &pk3, stake3, epoch); + // Insert validator with stake 1 NAM + insert_validator(&mut s, &val4, &pk4, stake4, epoch); + + // Validator `val3` and `val4` will be added at pipeline offset (2) - epoch + // 3 + let val3_and_4_epoch = pipeline_epoch; + + let consensus_vals: Vec<_> = consensus_validator_set_handle() + .at(&pipeline_epoch) + .iter(&s) + .unwrap() + .map(Result::unwrap) + .collect(); + + assert_eq!(consensus_vals.len(), 3); + assert!(matches!( + &consensus_vals[0], + (lazy_map::NestedSubKey::Data { + key: stake, + nested_sub_key: lazy_map::SubKey::Data(position), + }, address) + if address == &val1 && stake == &stake1 && *position == Position(0) + )); + assert!(matches!( + &consensus_vals[1], + (lazy_map::NestedSubKey::Data { + key: stake, + nested_sub_key: lazy_map::SubKey::Data(position), + }, address) + if address == &val2 && stake == &stake2 && *position == Position(1) + )); + assert!(matches!( + &consensus_vals[2], + (lazy_map::NestedSubKey::Data { + key: stake, + nested_sub_key: lazy_map::SubKey::Data(position), + }, address) + if address == &val3 && stake == &stake3 && *position == Position(0) + )); + + // Check tendermint validator set updates - there should be none + let tm_updates = get_tendermint_set_updates(&s, ¶ms, epoch); + assert!(tm_updates.is_empty()); + + // Advance to EPOCH 2 + let epoch = advance_epoch(&mut s, ¶ms); + let pipeline_epoch = epoch + params.pipeline_len; + + // Insert another validator with a greater stake still 1000 NAM. It should + // replace 2nd consensus validator with stake 1, which should become + // below-capacity + insert_validator(&mut s, &val5, &pk5, stake5, epoch); + // Validator `val5` will be added at pipeline offset (2) - epoch 4 + let val5_epoch = pipeline_epoch; + + let consensus_vals: Vec<_> = consensus_validator_set_handle() + .at(&pipeline_epoch) + .iter(&s) + .unwrap() + .map(Result::unwrap) + .collect(); + + assert_eq!(consensus_vals.len(), 3); + assert!(matches!( + &consensus_vals[0], + (lazy_map::NestedSubKey::Data { + key: stake, + nested_sub_key: lazy_map::SubKey::Data(position), + }, address) + if address == &val1 && stake == &stake1 && *position == Position(0) + )); + assert!(matches!( + &consensus_vals[1], + (lazy_map::NestedSubKey::Data { + key: stake, + nested_sub_key: lazy_map::SubKey::Data(position), + }, address) + if address == &val3 && stake == &stake3 && *position == Position(0) + )); + assert!(matches!( + &consensus_vals[2], + (lazy_map::NestedSubKey::Data { + key: stake, + nested_sub_key: lazy_map::SubKey::Data(position), + }, address) + if address == &val5 && stake == &stake5 && *position == Position(0) + )); + + let below_capacity_vals: Vec<_> = below_capacity_validator_set_handle() + .at(&pipeline_epoch) + .iter(&s) + .unwrap() + .map(Result::unwrap) + .collect(); + + assert_eq!(below_capacity_vals.len(), 2); + assert!(matches!( + &below_capacity_vals[0], + (lazy_map::NestedSubKey::Data { + key: ReverseOrdTokenAmount(stake), + nested_sub_key: lazy_map::SubKey::Data(position), + }, address) + if address == &val4 && stake == &stake4 && *position == Position(0) + )); + assert!(matches!( + &below_capacity_vals[1], + (lazy_map::NestedSubKey::Data { + key: ReverseOrdTokenAmount(stake), + nested_sub_key: lazy_map::SubKey::Data(position), + }, address) + if address == &val2 && stake == &stake2 && *position == Position(1) + )); + + // Advance to EPOCH 3 + let epoch = advance_epoch(&mut s, ¶ms); + let pipeline_epoch = epoch + params.pipeline_len; + + // Check tendermint validator set updates + assert_eq!( + val3_and_4_epoch, epoch, + "val3 and val4 are in the validator sets now" + ); + let tm_updates = get_tendermint_set_updates(&s, ¶ms, epoch); + // `val4` is newly added below-capacity, must be skipped in updated in TM + assert_eq!(tm_updates.len(), 1); + assert_eq!( + tm_updates[0], + ValidatorSetUpdate::Consensus(ConsensusValidator { + consensus_key: pk3, + bonded_stake: stake3, + }) + ); + + // Insert another validator with a stake 1 NAM. It should be added to the + // below-capacity set + insert_validator(&mut s, &val6, &pk6, stake6, epoch); + // Validator `val6` will be added at pipeline offset (2) - epoch 5 + let val6_epoch = pipeline_epoch; + + let below_capacity_vals: Vec<_> = below_capacity_validator_set_handle() + .at(&pipeline_epoch) + .iter(&s) + .unwrap() + .map(Result::unwrap) + .collect(); + + assert_eq!(below_capacity_vals.len(), 3); + assert!(matches!( + &below_capacity_vals[0], + (lazy_map::NestedSubKey::Data { + key: ReverseOrdTokenAmount(stake), + nested_sub_key: lazy_map::SubKey::Data(position), + }, address) + if address == &val4 && stake == &stake4 && *position == Position(0) + )); + assert!(matches!( + &below_capacity_vals[1], + (lazy_map::NestedSubKey::Data { + key: ReverseOrdTokenAmount(stake), + nested_sub_key: lazy_map::SubKey::Data(position), + }, address) + if address == &val2 && stake == &stake2 && *position == Position(1) + )); + assert!(matches!( + &below_capacity_vals[2], + (lazy_map::NestedSubKey::Data { + key: ReverseOrdTokenAmount(stake), + nested_sub_key: lazy_map::SubKey::Data(position), + }, address) + if address == &val6 && stake == &stake6 && *position == Position(2) + )); + + // Advance to EPOCH 4 + let epoch = advance_epoch(&mut s, ¶ms); + let pipeline_epoch = epoch + params.pipeline_len; + + // Check tendermint validator set updates + assert_eq!(val5_epoch, epoch, "val5 is in the validator sets now"); + let tm_updates = get_tendermint_set_updates(&s, ¶ms, epoch); + assert_eq!(tm_updates.len(), 2); + assert_eq!( + tm_updates[0], + ValidatorSetUpdate::Consensus(ConsensusValidator { + consensus_key: pk5, + bonded_stake: stake5, + }) + ); + assert_eq!(tm_updates[1], ValidatorSetUpdate::Deactivated(pk2)); + + // Unbond some stake from val1, it should be be swapped with the greatest + // below-capacity validator val2 into the below-capacity set. The stake of + // val1 will go below 1 NAM, which is the validator_stake_threshold, so it + // will enter the below-threshold validator set. + let unbond = token::Amount::from_uint(500_000, 0).unwrap(); + let stake1 = stake1 - unbond; + + // Because `update_validator_set` and `update_validator_deltas` are + // effective from pipeline offset, we use pipeline epoch for the rest of the + // checks + update_validator_set(&mut s, ¶ms, &val1, -unbond.change(), epoch, None) + .unwrap(); + update_validator_deltas( + &mut s, + ¶ms, + &val1, + -unbond.change(), + epoch, + None, + ) + .unwrap(); + // Epoch 6 + let val1_unbond_epoch = pipeline_epoch; + + let consensus_vals: Vec<_> = consensus_validator_set_handle() + .at(&pipeline_epoch) + .iter(&s) + .unwrap() + .map(Result::unwrap) + .collect(); + + assert_eq!(consensus_vals.len(), 3); + assert!(matches!( + &consensus_vals[0], + (lazy_map::NestedSubKey::Data { + key: stake, + nested_sub_key: lazy_map::SubKey::Data(position), + }, address) + if address == &val4 && stake == &stake4 && *position == Position(0) + )); + assert!(matches!( + &consensus_vals[1], + (lazy_map::NestedSubKey::Data { + key: stake, + nested_sub_key: lazy_map::SubKey::Data(position), + }, address) + if address == &val3 && stake == &stake3 && *position == Position(0) + )); + assert!(matches!( + &consensus_vals[2], + (lazy_map::NestedSubKey::Data { + key: stake, + nested_sub_key: lazy_map::SubKey::Data(position), + }, address) + if address == &val5 && stake == &stake5 && *position == Position(0) + )); + + let below_capacity_vals: Vec<_> = below_capacity_validator_set_handle() + .at(&pipeline_epoch) + .iter(&s) + .unwrap() + .map(Result::unwrap) + .collect(); + + assert_eq!(below_capacity_vals.len(), 2); + assert!(matches!( + &below_capacity_vals[0], + (lazy_map::NestedSubKey::Data { + key: ReverseOrdTokenAmount(stake), + nested_sub_key: lazy_map::SubKey::Data(position), + }, address) + if address == &val2 && stake == &stake2 && *position == Position(1) + )); + assert!(matches!( + &below_capacity_vals[1], + (lazy_map::NestedSubKey::Data { + key: ReverseOrdTokenAmount(stake), + nested_sub_key: lazy_map::SubKey::Data(position), + }, address) + if address == &val6 && stake == &stake6 && *position == Position(2) + )); + + let below_threshold_vals = + read_below_threshold_validator_set_addresses(&s, pipeline_epoch) + .unwrap() + .into_iter() + .collect::>(); + + assert_eq!(below_threshold_vals.len(), 1); + assert_eq!(&below_threshold_vals[0], &val1); + + // Advance to EPOCH 5 + let epoch = advance_epoch(&mut s, ¶ms); + let pipeline_epoch = epoch + params.pipeline_len; + + // Check tendermint validator set updates + assert_eq!(val6_epoch, epoch, "val6 is in the validator sets now"); + let tm_updates = get_tendermint_set_updates(&s, ¶ms, epoch); + assert!(tm_updates.is_empty()); + + // Insert another validator with stake 1 - it should be added to below + // capacity set + insert_validator(&mut s, &val7, &pk7, stake7, epoch); + // Epoch 7 + let val7_epoch = pipeline_epoch; + + let consensus_vals: Vec<_> = consensus_validator_set_handle() + .at(&pipeline_epoch) + .iter(&s) + .unwrap() + .map(Result::unwrap) + .collect(); + + assert_eq!(consensus_vals.len(), 3); + assert!(matches!( + &consensus_vals[0], + (lazy_map::NestedSubKey::Data { + key: stake, + nested_sub_key: lazy_map::SubKey::Data(position), + }, address) + if address == &val4 && stake == &stake4 && *position == Position(0) + )); + assert!(matches!( + &consensus_vals[1], + (lazy_map::NestedSubKey::Data { + key: stake, + nested_sub_key: lazy_map::SubKey::Data(position), + }, address) + if address == &val3 && stake == &stake3 && *position == Position(0) + )); + assert!(matches!( + &consensus_vals[2], + (lazy_map::NestedSubKey::Data { + key: stake, + nested_sub_key: lazy_map::SubKey::Data(position), + }, address) + if address == &val5 && stake == &stake5 && *position == Position(0) + )); + + let below_capacity_vals: Vec<_> = below_capacity_validator_set_handle() + .at(&pipeline_epoch) + .iter(&s) + .unwrap() + .map(Result::unwrap) + .collect(); + + assert_eq!(below_capacity_vals.len(), 3); + assert!(matches!( + &below_capacity_vals[0], + (lazy_map::NestedSubKey::Data { + key: ReverseOrdTokenAmount(stake), + nested_sub_key: lazy_map::SubKey::Data(position), + }, address) + if address == &val2 && stake == &stake2 && *position == Position(1) + )); + assert!(matches!( + &below_capacity_vals[1], + (lazy_map::NestedSubKey::Data { + key: ReverseOrdTokenAmount(stake), + nested_sub_key: lazy_map::SubKey::Data(position), + }, address) + if address == &val6 && stake == &stake6 && *position == Position(2) + )); + assert!(matches!( + &below_capacity_vals[2], + ( + lazy_map::NestedSubKey::Data { + key: ReverseOrdTokenAmount(stake), + nested_sub_key: lazy_map::SubKey::Data(position), + }, + address + ) + if address == &val7 && stake == &stake7 && *position == Position(3) + )); + + let below_threshold_vals = + read_below_threshold_validator_set_addresses(&s, pipeline_epoch) + .unwrap() + .into_iter() + .collect::>(); + + assert_eq!(below_threshold_vals.len(), 1); + assert_eq!(&below_threshold_vals[0], &val1); + + // Advance to EPOCH 6 + let epoch = advance_epoch(&mut s, ¶ms); + let pipeline_epoch = epoch + params.pipeline_len; + + // Check tendermint validator set updates + assert_eq!(val1_unbond_epoch, epoch, "val1's unbond is applied now"); + let tm_updates = get_tendermint_set_updates(&s, ¶ms, epoch); + assert_eq!(tm_updates.len(), 2); + assert_eq!( + tm_updates[0], + ValidatorSetUpdate::Consensus(ConsensusValidator { + consensus_key: pk4.clone(), + bonded_stake: stake4, + }) + ); + assert_eq!(tm_updates[1], ValidatorSetUpdate::Deactivated(pk1)); + + // Bond some stake to val6, it should be be swapped with the lowest + // consensus validator val2 into the consensus set + let bond = token::Amount::from_uint(500_000, 0).unwrap(); + let stake6 = stake6 + bond; + + update_validator_set(&mut s, ¶ms, &val6, bond.change(), epoch, None) + .unwrap(); + update_validator_deltas(&mut s, ¶ms, &val6, bond.change(), epoch, None) + .unwrap(); + let val6_bond_epoch = pipeline_epoch; + + let consensus_vals: Vec<_> = consensus_validator_set_handle() + .at(&pipeline_epoch) + .iter(&s) + .unwrap() + .map(Result::unwrap) + .collect(); + + assert_eq!(consensus_vals.len(), 3); + assert!(matches!( + &consensus_vals[0], + (lazy_map::NestedSubKey::Data { + key: stake, + nested_sub_key: lazy_map::SubKey::Data(position), + }, address) + if address == &val6 && stake == &stake6 && *position == Position(0) + )); + assert!(matches!( + &consensus_vals[1], + (lazy_map::NestedSubKey::Data { + key: stake, + nested_sub_key: lazy_map::SubKey::Data(position), + }, address) + if address == &val3 && stake == &stake3 && *position == Position(0) + )); + assert!(matches!( + &consensus_vals[2], + (lazy_map::NestedSubKey::Data { + key: stake, + nested_sub_key: lazy_map::SubKey::Data(position), + }, address) + if address == &val5 && stake == &stake5 && *position == Position(0) + )); + + let below_capacity_vals: Vec<_> = below_capacity_validator_set_handle() + .at(&pipeline_epoch) + .iter(&s) + .unwrap() + .map(Result::unwrap) + .collect(); + + assert_eq!(below_capacity_vals.len(), 3); + + assert!(matches!( + &below_capacity_vals[0], + (lazy_map::NestedSubKey::Data { + key: ReverseOrdTokenAmount(stake), + nested_sub_key: lazy_map::SubKey::Data(position), + }, address) + if address == &val2 && stake == &stake2 && *position == Position(1) + )); + assert!(matches!( + &below_capacity_vals[1], + (lazy_map::NestedSubKey::Data { + key: ReverseOrdTokenAmount(stake), + nested_sub_key: lazy_map::SubKey::Data(position), + }, address) + if address == &val7 && stake == &stake7 && *position == Position(3) + )); + assert!(matches!( + &below_capacity_vals[2], + ( + lazy_map::NestedSubKey::Data { + key: ReverseOrdTokenAmount(stake), + nested_sub_key: lazy_map::SubKey::Data(position), + }, + address + ) + if address == &val4 && stake == &stake4 && *position == Position(4) + )); + + let below_threshold_vals = + read_below_threshold_validator_set_addresses(&s, pipeline_epoch) + .unwrap() + .into_iter() + .collect::>(); + + assert_eq!(below_threshold_vals.len(), 1); + assert_eq!(&below_threshold_vals[0], &val1); + + // Advance to EPOCH 7 + let epoch = advance_epoch(&mut s, ¶ms); + assert_eq!(val7_epoch, epoch, "val6 is in the validator sets now"); + + // Check tendermint validator set updates + let tm_updates = get_tendermint_set_updates(&s, ¶ms, epoch); + assert!(tm_updates.is_empty()); + + // Advance to EPOCH 8 + let epoch = advance_epoch(&mut s, ¶ms); + + // Check tendermint validator set updates + assert_eq!(val6_bond_epoch, epoch, "val5's bond is applied now"); + let tm_updates = get_tendermint_set_updates(&s, ¶ms, epoch); + // dbg!(&tm_updates); + assert_eq!(tm_updates.len(), 2); + assert_eq!( + tm_updates[0], + ValidatorSetUpdate::Consensus(ConsensusValidator { + consensus_key: pk6, + bonded_stake: stake6, + }) + ); + assert_eq!(tm_updates[1], ValidatorSetUpdate::Deactivated(pk4)); + + // Check that the below-capacity validator set was purged for the old epochs + // but that the consensus_validator_set was not + let last_epoch = epoch; + for e in Epoch::iter_bounds_inclusive( + start_epoch, + last_epoch + .sub_or_default(Epoch(DEFAULT_NUM_PAST_EPOCHS)) + .sub_or_default(Epoch(1)), + ) { + assert!( + !consensus_validator_set_handle() + .at(&e) + .is_empty(&s) + .unwrap() + ); + assert!( + below_capacity_validator_set_handle() + .at(&e) + .is_empty(&s) + .unwrap() + ); + } +} + +/// When a consensus set validator with 0 voting power adds a bond in the same +/// epoch as another below-capacity set validator with 0 power, but who adds +/// more bonds than the validator who is in the consensus set, they get swapped +/// in the sets. But if both of their new voting powers are still 0 after +/// bonding, the newly below-capacity validator must not be given to tendermint +/// with 0 voting power, because it wasn't it its set before +#[test] +fn test_validator_sets_swap() { + let mut s = TestWlStorage::default(); + // Only 2 consensus validator slots + let params = OwnedPosParams { + max_validator_slots: 2, + // Set the stake threshold to 0 so no validators are in the + // below-threshold set + validator_stake_threshold: token::Amount::zero(), + // Set 0.1 votes per token + tm_votes_per_token: Dec::new(1, 1).expect("Dec creation failed"), + ..Default::default() + }; + + let addr_seed = "seed"; + let mut address_gen = EstablishedAddressGen::new(addr_seed); + let mut sk_seed = 0; + let mut gen_validator = || { + let res = ( + address_gen.generate_address(addr_seed), + key::testing::common_sk_from_simple_seed(sk_seed).to_public(), + ); + // bump the sk seed + sk_seed += 1; + res + }; + + // Start with two genesis validators, one with 1 voting power and other 0 + let epoch = Epoch::default(); + // 1M voting power + let ((val1, pk1), stake1) = + (gen_validator(), token::Amount::native_whole(10)); + // 0 voting power + let ((val2, pk2), stake2) = + (gen_validator(), token::Amount::from_uint(5, 0).unwrap()); + // 0 voting power + let ((val3, pk3), stake3) = + (gen_validator(), token::Amount::from_uint(5, 0).unwrap()); + // println!("val1: {val1}, {pk1}, {}", stake1.to_string_native()); + // println!("val2: {val2}, {pk2}, {}", stake2.to_string_native()); + // println!("val3: {val3}, {pk3}, {}", stake3.to_string_native()); + + let protocol_sk_1 = common_sk_from_simple_seed(0); + let protocol_sk_2 = common_sk_from_simple_seed(1); + + let params = test_init_genesis( + &mut s, + params, + [ + GenesisValidator { + address: val1, + tokens: stake1, + consensus_key: pk1, + protocol_key: protocol_sk_1.to_public(), + eth_hot_key: key::common::PublicKey::Secp256k1( + key::testing::gen_keypair::() + .ref_to(), + ), + eth_cold_key: key::common::PublicKey::Secp256k1( + key::testing::gen_keypair::() + .ref_to(), + ), + commission_rate: Dec::new(1, 1).expect("Dec creation failed"), + max_commission_rate_change: Dec::new(1, 1) + .expect("Dec creation failed"), + metadata: Default::default(), + }, + GenesisValidator { + address: val2.clone(), + tokens: stake2, + consensus_key: pk2, + protocol_key: protocol_sk_2.to_public(), + eth_hot_key: key::common::PublicKey::Secp256k1( + key::testing::gen_keypair::() + .ref_to(), + ), + eth_cold_key: key::common::PublicKey::Secp256k1( + key::testing::gen_keypair::() + .ref_to(), + ), + commission_rate: Dec::new(1, 1).expect("Dec creation failed"), + max_commission_rate_change: Dec::new(1, 1) + .expect("Dec creation failed"), + metadata: Default::default(), + }, + ] + .into_iter(), + epoch, + ) + .unwrap(); + + // A helper to insert a non-genesis validator + let insert_validator = |s: &mut TestWlStorage, + addr, + pk: &common::PublicKey, + stake: token::Amount, + epoch: Epoch| { + insert_validator_into_validator_set( + s, + ¶ms, + addr, + stake, + epoch, + params.pipeline_len, + ) + .unwrap(); + + update_validator_deltas(s, ¶ms, addr, stake.change(), epoch, None) + .unwrap(); + + // Set their consensus key (needed for + // `validator_set_update_tendermint` fn) + validator_consensus_key_handle(addr) + .set(s, pk.clone(), epoch, params.pipeline_len) + .unwrap(); + }; + + // Advance to EPOCH 1 + let epoch = advance_epoch(&mut s, ¶ms); + let pipeline_epoch = epoch + params.pipeline_len; + + // Insert another validator with 0 voting power + insert_validator(&mut s, &val3, &pk3, stake3, epoch); + + assert_eq!(stake2, stake3); + + // Add 2 bonds, one for val2 and greater one for val3 + let bonds_epoch_1 = pipeline_epoch; + let bond2 = token::Amount::from_uint(1, 0).unwrap(); + let stake2 = stake2 + bond2; + let bond3 = token::Amount::from_uint(4, 0).unwrap(); + let stake3 = stake3 + bond3; + + assert!(stake2 < stake3); + assert_eq!(into_tm_voting_power(params.tm_votes_per_token, stake2), 0); + assert_eq!(into_tm_voting_power(params.tm_votes_per_token, stake3), 0); + + update_validator_set(&mut s, ¶ms, &val2, bond2.change(), epoch, None) + .unwrap(); + update_validator_deltas( + &mut s, + ¶ms, + &val2, + bond2.change(), + epoch, + None, + ) + .unwrap(); + + update_validator_set(&mut s, ¶ms, &val3, bond3.change(), epoch, None) + .unwrap(); + update_validator_deltas( + &mut s, + ¶ms, + &val3, + bond3.change(), + epoch, + None, + ) + .unwrap(); + + // Advance to EPOCH 2 + let epoch = advance_epoch(&mut s, ¶ms); + let pipeline_epoch = epoch + params.pipeline_len; + + // Add 2 more bonds, same amount for `val2` and val3` + let bonds_epoch_2 = pipeline_epoch; + let bonds = token::Amount::native_whole(1); + let stake2 = stake2 + bonds; + let stake3 = stake3 + bonds; + assert!(stake2 < stake3); + assert_eq!( + into_tm_voting_power(params.tm_votes_per_token, stake2), + into_tm_voting_power(params.tm_votes_per_token, stake3) + ); + + update_validator_set(&mut s, ¶ms, &val2, bonds.change(), epoch, None) + .unwrap(); + update_validator_deltas( + &mut s, + ¶ms, + &val2, + bonds.change(), + epoch, + None, + ) + .unwrap(); + + update_validator_set(&mut s, ¶ms, &val3, bonds.change(), epoch, None) + .unwrap(); + update_validator_deltas( + &mut s, + ¶ms, + &val3, + bonds.change(), + epoch, + None, + ) + .unwrap(); + + // Advance to EPOCH 3 + let epoch = advance_epoch(&mut s, ¶ms); + + // Check tendermint validator set updates + assert_eq!(bonds_epoch_1, epoch); + let tm_updates = get_tendermint_set_updates(&s, ¶ms, epoch); + // `val2` must not be given to tendermint - even though it was in the + // consensus set, its voting power was 0, so it wasn't in TM set before the + // bond + assert!(tm_updates.is_empty()); + + // Advance to EPOCH 4 + let epoch = advance_epoch(&mut s, ¶ms); + + // Check tendermint validator set updates + assert_eq!(bonds_epoch_2, epoch); + let tm_updates = get_tendermint_set_updates(&s, ¶ms, epoch); + // dbg!(&tm_updates); + assert_eq!(tm_updates.len(), 1); + // `val2` must not be given to tendermint as it was and still is below + // capacity + assert_eq!( + tm_updates[0], + ValidatorSetUpdate::Consensus(ConsensusValidator { + consensus_key: pk3, + bonded_stake: stake3, + }) + ); +} From 4461ab4f2a7daa9f34ce332a30b4b5ec3711ca9c Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 8 Dec 2023 11:22:09 -0500 Subject: [PATCH 128/216] resolve clippy --- proof_of_stake/src/slashing.rs | 10 ++- proof_of_stake/src/tests/test_validator.rs | 4 +- proof_of_stake/src/validator_set_update.rs | 3 + wasm/wasm_source/src/tx_bond.rs | 4 +- .../src/tx_change_validator_commission.rs | 2 +- wasm/wasm_source/src/tx_redelegate.rs | 4 +- wasm/wasm_source/src/tx_unbond.rs | 4 +- wasm/wasm_source/src/tx_withdraw.rs | 2 +- wasm/wasm_source/src/vp_implicit.rs | 6 +- wasm/wasm_source/src/vp_user.rs | 80 +++++++++---------- 10 files changed, 61 insertions(+), 58 deletions(-) diff --git a/proof_of_stake/src/slashing.rs b/proof_of_stake/src/slashing.rs index 9e07cca0c7..2b8831cf69 100644 --- a/proof_of_stake/src/slashing.rs +++ b/proof_of_stake/src/slashing.rs @@ -467,6 +467,10 @@ where Ok(()) } +/// Computes how many tokens will be slashed from a redelegated bond, +/// considering that the bond may have been completely or partially unbonded and +/// that the source validator may have misbehaved within the redelegation +/// slashing window. #[allow(clippy::too_many_arguments)] pub fn slash_redelegation( storage: &S, @@ -1004,6 +1008,8 @@ pub fn get_slashed_amount( Ok(final_amount) } +/// Compute the total amount of tokens from a set of unbonds, both redelegated +/// and not, after applying slashes. Used in `unbond_tokens`. // `def computeAmountAfterSlashingUnbond` pub fn compute_amount_after_slashing_unbond( storage: &S, @@ -1057,8 +1063,8 @@ where Ok(result_slashing) } -/// Compute from a set of unbonds (both redelegated and not) how much is left -/// after applying all relevant slashes. +/// Compute the total amount of tokens from a set of unbonds, both redelegated +/// and not, after applying slashes. Used in `withdraw_tokens`. // `def computeAmountAfterSlashingWithdraw` pub fn compute_amount_after_slashing_withdraw( storage: &S, diff --git a/proof_of_stake/src/tests/test_validator.rs b/proof_of_stake/src/tests/test_validator.rs index dc40c1e512..cb37c876bf 100644 --- a/proof_of_stake/src/tests/test_validator.rs +++ b/proof_of_stake/src/tests/test_validator.rs @@ -9,7 +9,7 @@ use namada_core::types::dec::Dec; use namada_core::types::key::testing::{ arb_common_keypair, common_sk_from_simple_seed, }; -use namada_core::types::key::{self, common, RefTo, SecretKey}; +use namada_core::types::key::{self, common, RefTo}; use namada_core::types::storage::Epoch; use namada_core::types::token; use proptest::prelude::*; @@ -619,7 +619,7 @@ fn test_validator_sets() { // val1 will go below 1 NAM, which is the validator_stake_threshold, so it // will enter the below-threshold validator set. let unbond = token::Amount::from_uint(500_000, 0).unwrap(); - let stake1 = stake1 - unbond; + // let stake1 = stake1 - unbond; // Because `update_validator_set` and `update_validator_deltas` are // effective from pipeline offset, we use pipeline epoch for the rest of the diff --git a/proof_of_stake/src/validator_set_update.rs b/proof_of_stake/src/validator_set_update.rs index b33a3f7927..2819380250 100644 --- a/proof_of_stake/src/validator_set_update.rs +++ b/proof_of_stake/src/validator_set_update.rs @@ -603,6 +603,9 @@ where })) } +/// Inserts a validator into the provided `handle` within some validator set at +/// the next position. Also updates the validator set position for the +/// validator. pub fn insert_validator_into_set( handle: &ValidatorPositionAddresses, storage: &mut S, diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index 419c1e00a0..733e021c59 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -21,11 +21,11 @@ mod tests { use std::collections::BTreeSet; use namada::ledger::pos::{OwnedPosParams, PosVP}; - use namada::proof_of_stake::types::{GenesisValidator, WeightedValidator}; - use namada::proof_of_stake::{ + use namada::proof_of_stake::storage::{ bond_handle, read_consensus_validator_set_addresses_with_stake, read_total_stake, read_validator_stake, }; + use namada::proof_of_stake::types::{GenesisValidator, WeightedValidator}; use namada::types::dec::Dec; use namada::types::storage::Epoch; use namada_tests::log::test; diff --git a/wasm/wasm_source/src/tx_change_validator_commission.rs b/wasm/wasm_source/src/tx_change_validator_commission.rs index 33433b59b3..4569c4d603 100644 --- a/wasm/wasm_source/src/tx_change_validator_commission.rs +++ b/wasm/wasm_source/src/tx_change_validator_commission.rs @@ -23,8 +23,8 @@ mod tests { use std::cmp; use namada::ledger::pos::{OwnedPosParams, PosVP}; + use namada::proof_of_stake::storage::validator_commission_rate_handle; use namada::proof_of_stake::types::GenesisValidator; - use namada::proof_of_stake::validator_commission_rate_handle; use namada::types::dec::{Dec, POS_DECIMAL_PRECISION}; use namada::types::storage::Epoch; use namada_tests::log::test; diff --git a/wasm/wasm_source/src/tx_redelegate.rs b/wasm/wasm_source/src/tx_redelegate.rs index 82f63cd9e4..a02970a0c1 100644 --- a/wasm/wasm_source/src/tx_redelegate.rs +++ b/wasm/wasm_source/src/tx_redelegate.rs @@ -25,11 +25,11 @@ mod tests { use std::collections::BTreeSet; use namada::ledger::pos::{OwnedPosParams, PosVP}; - use namada::proof_of_stake::types::{GenesisValidator, WeightedValidator}; - use namada::proof_of_stake::{ + use namada::proof_of_stake::storage::{ bond_handle, read_consensus_validator_set_addresses_with_stake, read_total_stake, read_validator_stake, unbond_handle, }; + use namada::proof_of_stake::types::{GenesisValidator, WeightedValidator}; use namada::types::dec::Dec; use namada::types::storage::Epoch; use namada_tests::log::test; diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index 3747f91d33..f7f11b14bc 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -28,11 +28,11 @@ mod tests { use std::collections::BTreeSet; use namada::ledger::pos::{OwnedPosParams, PosVP}; - use namada::proof_of_stake::types::{GenesisValidator, WeightedValidator}; - use namada::proof_of_stake::{ + use namada::proof_of_stake::storage::{ bond_handle, read_consensus_validator_set_addresses_with_stake, read_total_stake, read_validator_stake, unbond_handle, }; + use namada::proof_of_stake::types::{GenesisValidator, WeightedValidator}; use namada::types::dec::Dec; use namada::types::storage::Epoch; use namada_tests::log::test; diff --git a/wasm/wasm_source/src/tx_withdraw.rs b/wasm/wasm_source/src/tx_withdraw.rs index 1fb10dc588..f8984b1bef 100644 --- a/wasm/wasm_source/src/tx_withdraw.rs +++ b/wasm/wasm_source/src/tx_withdraw.rs @@ -24,8 +24,8 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { #[cfg(test)] mod tests { use namada::ledger::pos::{OwnedPosParams, PosVP}; + use namada::proof_of_stake::storage::unbond_handle; use namada::proof_of_stake::types::GenesisValidator; - use namada::proof_of_stake::unbond_handle; use namada::types::dec::Dec; use namada::types::storage::Epoch; use namada_tests::log::test; diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index 95d1401c54..e52b75c26c 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -32,7 +32,7 @@ impl<'a> From<&'a storage::Key> for KeyType<'a> { Self::Pk(address) } else if let Some([_, owner]) = token::is_any_token_balance_key(key) { Self::Token { owner } - } else if proof_of_stake::storage::is_pos_key(key) { + } else if proof_of_stake::storage_key::is_pos_key(key) { Self::PoS } else if gov_storage::keys::is_vote_key(key) { let voter_address = gov_storage::keys::get_voter_address(key); @@ -132,10 +132,10 @@ fn validate_tx( } KeyType::PoS => { // Allow the account to be used in PoS - let bond_id = proof_of_stake::storage::is_bond_key(key) + let bond_id = proof_of_stake::storage_key::is_bond_key(key) .map(|(bond_id, _)| bond_id) .or_else(|| { - proof_of_stake::storage::is_unbond_key(key) + proof_of_stake::storage_key::is_unbond_key(key) .map(|(bond_id, _, _)| bond_id) }); let valid = match bond_id { diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index 390bc389fb..cee41afc17 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -14,6 +14,11 @@ use namada_vp_prelude::storage::KeySeg; use namada_vp_prelude::*; use once_cell::unsync::Lazy; +use proof_of_stake::storage::{read_pos_params, validator_state_handle}; +use proof_of_stake::storage_key::{ + is_bond_key, is_pos_key, is_unbond_key, is_validator_commission_rate_key, + is_validator_metadata_key, is_validator_state_key, +}; use proof_of_stake::types::ValidatorState; enum KeyType<'a> { @@ -30,7 +35,7 @@ impl<'a> From<&'a storage::Key> for KeyType<'a> { fn from(key: &'a storage::Key) -> KeyType<'a> { if let Some([_, owner]) = token::is_any_token_balance_key(key) { Self::Token { owner } - } else if proof_of_stake::storage::is_pos_key(key) { + } else if is_pos_key(key) { Self::PoS } else if gov_storage::keys::is_vote_key(key) { let voter_address = gov_storage::keys::get_voter_address(key); @@ -109,12 +114,10 @@ fn validate_tx( } KeyType::PoS => { // Bond or unbond - let bond_id = proof_of_stake::storage::is_bond_key(key) - .map(|(bond_id, _)| bond_id) - .or_else(|| { - proof_of_stake::storage::is_unbond_key(key) - .map(|(bond_id, _, _)| bond_id) - }); + let bond_id = + is_bond_key(key).map(|(bond_id, _)| bond_id).or_else( + || is_unbond_key(key).map(|(bond_id, _, _)| bond_id), + ); let valid_bond_or_unbond_change = match bond_id { Some(bond_id) => { // Bonds and unbonds changes for this address @@ -127,10 +130,7 @@ fn validate_tx( } }; // Commission rate changes must be signed by the validator - let comm = - proof_of_stake::storage::is_validator_commission_rate_key( - key, - ); + let comm = is_validator_commission_rate_key(key); let valid_commission_rate_change = match comm { Some((validator, _epoch)) => { *validator == addr && *valid_sig @@ -139,8 +139,7 @@ fn validate_tx( }; // Metadata changes must be signed by the validator whose // metadata is manipulated - let metadata = - proof_of_stake::storage::is_validator_metadata_key(key); + let metadata = is_validator_metadata_key(key); let valid_metadata_change = match metadata { Some(address) => *address == addr && *valid_sig, None => true, @@ -148,27 +147,23 @@ fn validate_tx( // Changes due to unjailing, deactivating, and reactivating are // marked by changes in validator state - let state_change = - proof_of_stake::storage::is_validator_state_key(key); - let valid_state_change = match state_change { - Some((address, epoch)) => { - let params_pre = - proof_of_stake::read_pos_params(&ctx.pre())?; - let state_pre = - proof_of_stake::validator_state_handle(address) + let state_change = is_validator_state_key(key); + let valid_state_change = + match state_change { + Some((address, epoch)) => { + let params_pre = read_pos_params(&ctx.pre())?; + let state_pre = validator_state_handle(address) .get(&ctx.pre(), epoch, ¶ms_pre)?; - let params_post = - proof_of_stake::read_pos_params(&ctx.post())?; - let state_post = - proof_of_stake::validator_state_handle(address) + let params_post = read_pos_params(&ctx.post())?; + let state_post = validator_state_handle(address) .get(&ctx.post(), epoch, ¶ms_post)?; - match (state_pre, state_post) { - (Some(pre), Some(post)) => { - if - // Deactivation case - (matches!( + match (state_pre, state_post) { + (Some(pre), Some(post)) => { + if + // Deactivation case + (matches!( pre, ValidatorState::Consensus | ValidatorState::BelowCapacity @@ -184,22 +179,21 @@ fn validate_tx( ValidatorState::Consensus | ValidatorState::BelowCapacity | ValidatorState::BelowThreshold - ) - { - *address == addr && *valid_sig - } else { - true + ) { + *address == addr && *valid_sig + } else { + true + } } + (None, Some(_post)) => { + // Becoming a validator must be authorized + *valid_sig + } + _ => true, } - (None, Some(_post)) => { - // Becoming a validator must be authorized - *valid_sig - } - _ => true, } - } - None => true, - }; + None => true, + }; valid_bond_or_unbond_change && valid_commission_rate_change From ec838a6e691f921147dd9d80df85c07ffe83358a Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 8 Dec 2023 17:18:38 -0500 Subject: [PATCH 129/216] compactify code that modifies validator sets --- proof_of_stake/src/lib.rs | 183 ++++----------------- proof_of_stake/src/validator_set_update.rs | 101 ++++++++++++ 2 files changed, 133 insertions(+), 151 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 089cf8e424..fcaaa324c6 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -81,9 +81,10 @@ use crate::types::{ ValidatorState, VoteInfo, }; use crate::validator_set_update::{ - copy_validator_sets_and_positions, find_first_position, - get_max_below_capacity_validator_amount, insert_validator_into_set, - insert_validator_into_validator_set, update_validator_set, + copy_validator_sets_and_positions, insert_validator_into_validator_set, + promote_next_below_capacity_validator_to_consensus, + remove_below_capacity_validator, remove_consensus_validator, + update_validator_set, }; /// Address of the PoS account implemented as a native VP @@ -2160,35 +2161,32 @@ where } }; - let pipeline_stake = - read_validator_stake(storage, ¶ms, validator, pipeline_epoch)?; - // Remove the validator from the validator set. If it is in the consensus // set, promote the next validator. match pipeline_state { - ValidatorState::Consensus => deactivate_consensus_validator( - storage, - validator, - pipeline_epoch, - pipeline_stake, - )?, + ValidatorState::Consensus => { + // Remove from the consensus set first + remove_consensus_validator( + storage, + ¶ms, + pipeline_epoch, + validator, + )?; + + // Promote the next below-capacity validator to consensus + promote_next_below_capacity_validator_to_consensus( + storage, + pipeline_epoch, + )?; + } ValidatorState::BelowCapacity => { - let below_capacity_set = below_capacity_validator_set_handle() - .at(&pipeline_epoch) - .at(&pipeline_stake.into()); - // TODO: handle the unwrap better here - let val_position = validator_set_positions_handle() - .at(&pipeline_epoch) - .get(storage, validator)? - .unwrap(); - let removed = below_capacity_set.remove(storage, &val_position)?; - debug_assert_eq!(removed, Some(validator.clone())); - - // Remove position - validator_set_positions_handle() - .at(&pipeline_epoch) - .remove(storage, validator)?; + remove_below_capacity_validator( + storage, + ¶ms, + pipeline_epoch, + validator, + )?; } ValidatorState::BelowThreshold => {} ValidatorState::Inactive => { @@ -2218,64 +2216,6 @@ where Ok(()) } -fn deactivate_consensus_validator( - storage: &mut S, - validator: &Address, - target_epoch: Epoch, - stake: token::Amount, -) -> storage_api::Result<()> -where - S: StorageRead + StorageWrite, -{ - let consensus_set = consensus_validator_set_handle() - .at(&target_epoch) - .at(&stake); - // TODO: handle the unwrap better here - let val_position = validator_set_positions_handle() - .at(&target_epoch) - .get(storage, validator)? - .unwrap(); - let removed = consensus_set.remove(storage, &val_position)?; - debug_assert_eq!(removed, Some(validator.clone())); - - // Remove position - validator_set_positions_handle() - .at(&target_epoch) - .remove(storage, validator)?; - - // Now promote the next below-capacity validator to the consensus - // set - let below_cap_set = below_capacity_validator_set_handle().at(&target_epoch); - let max_below_capacity_validator_amount = - get_max_below_capacity_validator_amount(&below_cap_set, storage)?; - - if let Some(max_bc_amount) = max_below_capacity_validator_amount { - let below_cap_vals_max = below_cap_set.at(&max_bc_amount.into()); - let lowest_position = - find_first_position(&below_cap_vals_max, storage)?.unwrap(); - let removed_max_below_capacity = below_cap_vals_max - .remove(storage, &lowest_position)? - .expect("Must have been removed"); - - insert_validator_into_set( - &consensus_validator_set_handle() - .at(&target_epoch) - .at(&max_bc_amount), - storage, - &target_epoch, - &removed_max_below_capacity, - )?; - validator_state_handle(&removed_max_below_capacity).set( - storage, - ValidatorState::Consensus, - target_epoch, - 0, - )?; - } - - Ok(()) -} - /// Re-activate an inactive validator pub fn reactivate_validator( storage: &mut S, @@ -2741,61 +2681,15 @@ where "Removing validator from the consensus set in epoch {}", epoch ); - let amount_pre = - read_validator_stake(storage, params, validator, epoch)?; - let val_position = validator_set_positions_handle() - .at(&epoch) - .get(storage, validator)? - .expect("Could not find validator's position in storage."); - let _ = consensus_validator_set_handle() - .at(&epoch) - .at(&amount_pre) - .remove(storage, &val_position)?; - validator_set_positions_handle() - .at(&epoch) - .remove(storage, validator)?; + remove_consensus_validator(storage, params, epoch, validator)?; // For the pipeline epoch only: // promote the next max inactive validator to the active // validator set at the pipeline offset if epoch == pipeline_epoch { - let below_capacity_handle = - below_capacity_validator_set_handle().at(&epoch); - let max_below_capacity_amount = - get_max_below_capacity_validator_amount( - &below_capacity_handle, - storage, - )?; - if let Some(max_below_capacity_amount) = - max_below_capacity_amount - { - let position_to_promote = find_first_position( - &below_capacity_handle - .at(&max_below_capacity_amount.into()), - storage, - )? - .expect("Should return a position."); - let max_bc_validator = below_capacity_handle - .at(&max_below_capacity_amount.into()) - .remove(storage, &position_to_promote)? - .expect( - "Should have returned a removed validator.", - ); - insert_validator_into_set( - &consensus_validator_set_handle() - .at(&epoch) - .at(&max_below_capacity_amount), - storage, - &epoch, - &max_bc_validator, - )?; - validator_state_handle(&max_bc_validator).set( - storage, - ValidatorState::Consensus, - current_epoch, - params.pipeline_len, - )?; - } + promote_next_below_capacity_validator_to_consensus( + storage, epoch, + )?; } } ValidatorState::BelowCapacity => { @@ -2804,22 +2698,9 @@ where {}", epoch ); - - let amount_pre = validator_deltas_handle(validator) - .get_sum(storage, epoch, params)? - .unwrap_or_default(); - debug_assert!(amount_pre.non_negative()); - let val_position = validator_set_positions_handle() - .at(&epoch) - .get(storage, validator)? - .expect("Could not find validator's position in storage."); - let _ = below_capacity_validator_set_handle() - .at(&epoch) - .at(&token::Amount::from_change(amount_pre).into()) - .remove(storage, &val_position)?; - validator_set_positions_handle() - .at(&epoch) - .remove(storage, validator)?; + remove_below_capacity_validator( + storage, params, epoch, validator, + )?; } ValidatorState::BelowThreshold => { tracing::debug!( diff --git a/proof_of_stake/src/validator_set_update.rs b/proof_of_stake/src/validator_set_update.rs index 2819380250..4bc26922e1 100644 --- a/proof_of_stake/src/validator_set_update.rs +++ b/proof_of_stake/src/validator_set_update.rs @@ -631,6 +631,107 @@ where Ok(()) } +/// Remove a validator from the consensus validator set +pub fn remove_consensus_validator( + storage: &mut S, + params: &PosParams, + epoch: Epoch, + validator: &Address, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let stake = read_validator_stake(storage, params, validator, epoch)?; + let consensus_set = consensus_validator_set_handle().at(&epoch).at(&stake); + let val_position = validator_set_positions_handle() + .at(&epoch) + .get(storage, validator)? + .expect("Could not find validator's position in storage."); + + // Removal + let removed = consensus_set.remove(storage, &val_position)?; + debug_assert_eq!(removed, Some(validator.clone())); + + validator_set_positions_handle() + .at(&epoch) + .remove(storage, validator)?; + + Ok(()) +} + +/// Remove a validator from the below-capacity set +pub fn remove_below_capacity_validator( + storage: &mut S, + params: &PosParams, + epoch: Epoch, + validator: &Address, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let stake = read_validator_stake(storage, params, validator, epoch)?; + let below_cap_set = below_capacity_validator_set_handle() + .at(&epoch) + .at(&stake.into()); + let val_position = validator_set_positions_handle() + .at(&epoch) + .get(storage, validator)? + .expect("Could not find validator's position in storage."); + + // Removal + let removed = below_cap_set.remove(storage, &val_position)?; + debug_assert_eq!(removed, Some(validator.clone())); + + validator_set_positions_handle() + .at(&epoch) + .remove(storage, validator)?; + + Ok(()) +} + +/// Promote the next below-capacity validator to the consensus validator set, +/// determined as the validator in the below-capacity set with the largest stake +/// and the lowest `Position`. Assumes that there is adequate space within the +/// consensus set already. +pub fn promote_next_below_capacity_validator_to_consensus( + storage: &mut S, + epoch: Epoch, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let below_cap_set = below_capacity_validator_set_handle().at(&epoch); + let max_below_capacity_amount = + get_max_below_capacity_validator_amount(&below_cap_set, storage)?; + + if let Some(max_below_capacity_amount) = max_below_capacity_amount { + let max_bc_vals = below_cap_set.at(&max_below_capacity_amount.into()); + let position_to_promote = find_first_position(&max_bc_vals, storage)? + .expect("Should be at least one below-capacity validator"); + + let promoted_validator = max_bc_vals + .remove(storage, &position_to_promote)? + .expect("Should have returned a removed validator."); + + insert_validator_into_set( + &consensus_validator_set_handle() + .at(&epoch) + .at(&max_below_capacity_amount), + storage, + &epoch, + &promoted_validator, + )?; + validator_state_handle(&promoted_validator).set( + storage, + ValidatorState::Consensus, + epoch, + 0, + )?; + } + + Ok(()) +} + /// Communicate imminent validator set updates to Tendermint. This function is /// called two blocks before the start of a new epoch because Tendermint /// validator updates become active two blocks after the updates are submitted. From 51902e4fc4d58644c4cfa8bb8af634c92e0bdd99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 12 Dec 2023 19:34:47 +0000 Subject: [PATCH 130/216] proof_of_stake: move mod files with other sub-modules --- proof_of_stake/src/{tests.rs => tests/mod.rs} | 0 proof_of_stake/src/{types.rs => types/mod.rs} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename proof_of_stake/src/{tests.rs => tests/mod.rs} (100%) rename proof_of_stake/src/{types.rs => types/mod.rs} (100%) diff --git a/proof_of_stake/src/tests.rs b/proof_of_stake/src/tests/mod.rs similarity index 100% rename from proof_of_stake/src/tests.rs rename to proof_of_stake/src/tests/mod.rs diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types/mod.rs similarity index 100% rename from proof_of_stake/src/types.rs rename to proof_of_stake/src/types/mod.rs From ccc7f459b695a7436541ac1c1e8bd033cc86f14e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 12 Dec 2023 19:35:58 +0000 Subject: [PATCH 131/216] proof_of_stake: re-order and make some fns private --- proof_of_stake/src/slashing.rs | 290 ++++++++--------- proof_of_stake/src/validator_set_update.rs | 346 ++++++++++----------- 2 files changed, 318 insertions(+), 318 deletions(-) diff --git a/proof_of_stake/src/slashing.rs b/proof_of_stake/src/slashing.rs index 2b8831cf69..4b91a84527 100644 --- a/proof_of_stake/src/slashing.rs +++ b/proof_of_stake/src/slashing.rs @@ -34,63 +34,6 @@ use crate::{ FoldRedelegatedBondsResult, OwnedPosParams, PosParams, }; -/// Calculate the cubic slashing rate using all slashes within a window around -/// the given infraction epoch. There is no cap on the rate applied within this -/// function. -pub fn compute_cubic_slash_rate( - storage: &S, - params: &PosParams, - infraction_epoch: Epoch, -) -> storage_api::Result -where - S: StorageRead, -{ - tracing::debug!( - "Computing the cubic slash rate for infraction epoch \ - {infraction_epoch}." - ); - let mut sum_vp_fraction = Dec::zero(); - let (start_epoch, end_epoch) = - params.cubic_slash_epoch_window(infraction_epoch); - - for epoch in Epoch::iter_bounds_inclusive(start_epoch, end_epoch) { - let consensus_stake = - Dec::from(get_total_consensus_stake(storage, epoch, params)?); - tracing::debug!( - "Total consensus stake in epoch {}: {}", - epoch, - consensus_stake - ); - let processing_epoch = epoch + params.slash_processing_epoch_offset(); - let slashes = enqueued_slashes_handle().at(&processing_epoch); - let infracting_stake = slashes.iter(storage)?.fold( - Ok(Dec::zero()), - |acc: storage_api::Result, res| { - let acc = acc?; - let ( - NestedSubKey::Data { - key: validator, - nested_sub_key: _, - }, - _slash, - ) = res?; - - let validator_stake = - read_validator_stake(storage, params, &validator, epoch)?; - // tracing::debug!("Val {} stake: {}", &validator, - // validator_stake); - - Ok(acc + Dec::from(validator_stake)) - }, - )?; - sum_vp_fraction += infracting_stake / consensus_stake; - } - let cubic_rate = - Dec::new(9, 0).unwrap() * sum_vp_fraction * sum_vp_fraction; - tracing::debug!("Cubic slash rate: {}", cubic_rate); - Ok(cubic_rate) -} - /// Record a slash for a misbehavior that has been received from Tendermint and /// then jail the validator, removing it from the validator set. The slash rate /// will be computed at a later epoch. @@ -303,94 +246,6 @@ where Ok(()) } -/// Process a slash by (i) slashing the misbehaving validator; and (ii) any -/// validator to which it has redelegated some tokens and the slash misbehaving -/// epoch is wihtin the redelegation slashing window. -/// -/// `validator` - the misbehaving validator. -/// `slash_rate` - the slash rate. -/// `slashed_amounts_map` - a map from validator address to a map from epoch to -/// already processed slash amounts. -/// -/// Adds any newly processed slash amount of any involved validator to -/// `slashed_amounts_map`. -// Quint `processSlash` -fn process_validator_slash( - storage: &mut S, - params: &PosParams, - validator: &Address, - slash_rate: Dec, - current_epoch: Epoch, - slashed_amount_map: &mut EagerRedelegatedBondsMap, -) -> storage_api::Result<()> -where - S: StorageRead + StorageWrite, -{ - // `resultSlashValidator - let result_slash = slash_validator( - storage, - params, - validator, - slash_rate, - current_epoch, - &slashed_amount_map - .get(validator) - .cloned() - .unwrap_or_default(), - )?; - - // `updatedSlashedAmountMap` - let validator_slashes = - slashed_amount_map.entry(validator.clone()).or_default(); - *validator_slashes = result_slash; - - // `outgoingRedelegation` - let outgoing_redelegations = - validator_outgoing_redelegations_handle(validator); - - // Final loop in `processSlash` - let dest_validators = outgoing_redelegations - .iter(storage)? - .map(|res| { - let ( - NestedSubKey::Data { - key: dest_validator, - nested_sub_key: _, - }, - _redelegation, - ) = res?; - Ok(dest_validator) - }) - .collect::>>()?; - - for dest_validator in dest_validators { - let to_modify = slashed_amount_map - .entry(dest_validator.clone()) - .or_default(); - - tracing::debug!( - "Slashing {} redelegation to {}", - validator, - &dest_validator - ); - - // `slashValidatorRedelegation` - slash_validator_redelegation( - storage, - params, - validator, - current_epoch, - &outgoing_redelegations.at(&dest_validator), - &validator_slashes_handle(validator), - &validator_total_redelegated_unbonded_handle(&dest_validator), - slash_rate, - to_modify, - )?; - } - - Ok(()) -} - /// In the context of a redelegation, the function computes how much a validator /// (the destination validator of the redelegation) should be slashed due to the /// misbehaving of a second validator (the source validator of the @@ -1128,3 +983,148 @@ where Ok(result_slashing) } + +/// Process a slash by (i) slashing the misbehaving validator; and (ii) any +/// validator to which it has redelegated some tokens and the slash misbehaving +/// epoch is wihtin the redelegation slashing window. +/// +/// `validator` - the misbehaving validator. +/// `slash_rate` - the slash rate. +/// `slashed_amounts_map` - a map from validator address to a map from epoch to +/// already processed slash amounts. +/// +/// Adds any newly processed slash amount of any involved validator to +/// `slashed_amounts_map`. +// Quint `processSlash` +fn process_validator_slash( + storage: &mut S, + params: &PosParams, + validator: &Address, + slash_rate: Dec, + current_epoch: Epoch, + slashed_amount_map: &mut EagerRedelegatedBondsMap, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + // `resultSlashValidator + let result_slash = slash_validator( + storage, + params, + validator, + slash_rate, + current_epoch, + &slashed_amount_map + .get(validator) + .cloned() + .unwrap_or_default(), + )?; + + // `updatedSlashedAmountMap` + let validator_slashes = + slashed_amount_map.entry(validator.clone()).or_default(); + *validator_slashes = result_slash; + + // `outgoingRedelegation` + let outgoing_redelegations = + validator_outgoing_redelegations_handle(validator); + + // Final loop in `processSlash` + let dest_validators = outgoing_redelegations + .iter(storage)? + .map(|res| { + let ( + NestedSubKey::Data { + key: dest_validator, + nested_sub_key: _, + }, + _redelegation, + ) = res?; + Ok(dest_validator) + }) + .collect::>>()?; + + for dest_validator in dest_validators { + let to_modify = slashed_amount_map + .entry(dest_validator.clone()) + .or_default(); + + tracing::debug!( + "Slashing {} redelegation to {}", + validator, + &dest_validator + ); + + // `slashValidatorRedelegation` + slash_validator_redelegation( + storage, + params, + validator, + current_epoch, + &outgoing_redelegations.at(&dest_validator), + &validator_slashes_handle(validator), + &validator_total_redelegated_unbonded_handle(&dest_validator), + slash_rate, + to_modify, + )?; + } + + Ok(()) +} + +/// Calculate the cubic slashing rate using all slashes within a window around +/// the given infraction epoch. There is no cap on the rate applied within this +/// function. +fn compute_cubic_slash_rate( + storage: &S, + params: &PosParams, + infraction_epoch: Epoch, +) -> storage_api::Result +where + S: StorageRead, +{ + tracing::debug!( + "Computing the cubic slash rate for infraction epoch \ + {infraction_epoch}." + ); + let mut sum_vp_fraction = Dec::zero(); + let (start_epoch, end_epoch) = + params.cubic_slash_epoch_window(infraction_epoch); + + for epoch in Epoch::iter_bounds_inclusive(start_epoch, end_epoch) { + let consensus_stake = + Dec::from(get_total_consensus_stake(storage, epoch, params)?); + tracing::debug!( + "Total consensus stake in epoch {}: {}", + epoch, + consensus_stake + ); + let processing_epoch = epoch + params.slash_processing_epoch_offset(); + let slashes = enqueued_slashes_handle().at(&processing_epoch); + let infracting_stake = slashes.iter(storage)?.fold( + Ok(Dec::zero()), + |acc: storage_api::Result, res| { + let acc = acc?; + let ( + NestedSubKey::Data { + key: validator, + nested_sub_key: _, + }, + _slash, + ) = res?; + + let validator_stake = + read_validator_stake(storage, params, &validator, epoch)?; + // tracing::debug!("Val {} stake: {}", &validator, + // validator_stake); + + Ok(acc + Dec::from(validator_stake)) + }, + )?; + sum_vp_fraction += infracting_stake / consensus_stake; + } + let cubic_rate = + Dec::new(9, 0).unwrap() * sum_vp_fraction * sum_vp_fraction; + tracing::debug!("Cubic slash rate: {}", cubic_rate); + Ok(cubic_rate) +} diff --git a/proof_of_stake/src/validator_set_update.rs b/proof_of_stake/src/validator_set_update.rs index 4bc26922e1..9ad53c20b1 100644 --- a/proof_of_stake/src/validator_set_update.rs +++ b/proof_of_stake/src/validator_set_update.rs @@ -458,179 +458,6 @@ where Ok(()) } -#[allow(clippy::too_many_arguments)] -fn insert_into_consensus_and_demote_to_below_cap( - storage: &mut S, - validator: &Address, - tokens_post: token::Amount, - min_consensus_amount: token::Amount, - current_epoch: Epoch, - offset: u64, - consensus_set: &ConsensusValidatorSet, - below_capacity_set: &BelowCapacityValidatorSet, -) -> storage_api::Result<()> -where - S: StorageRead + StorageWrite, -{ - // First, remove the last position min consensus validator - let consensus_vals_min = consensus_set.at(&min_consensus_amount); - let last_position_of_min_consensus_vals = - find_last_position(&consensus_vals_min, storage)? - .expect("There must be always be at least 1 consensus validator"); - let removed_min_consensus = consensus_vals_min - .remove(storage, &last_position_of_min_consensus_vals)? - .expect("There must be always be at least 1 consensus validator"); - - let offset_epoch = current_epoch + offset; - - // Insert the min consensus validator into the below-capacity - // set - insert_validator_into_set( - &below_capacity_set.at(&min_consensus_amount.into()), - storage, - &offset_epoch, - &removed_min_consensus, - )?; - validator_state_handle(&removed_min_consensus).set( - storage, - ValidatorState::BelowCapacity, - current_epoch, - offset, - )?; - - // Insert the current validator into the consensus set - insert_validator_into_set( - &consensus_set.at(&tokens_post), - storage, - &offset_epoch, - validator, - )?; - validator_state_handle(validator).set( - storage, - ValidatorState::Consensus, - current_epoch, - offset, - )?; - Ok(()) -} - -/// Find the first (lowest) position in a validator set if it is not empty -pub fn find_first_position( - handle: &ValidatorPositionAddresses, - storage: &S, -) -> storage_api::Result> -where - S: StorageRead, -{ - let lowest_position = handle - .iter(storage)? - .next() - .transpose()? - .map(|(position, _addr)| position); - Ok(lowest_position) -} - -/// Find the last (greatest) position in a validator set if it is not empty -fn find_last_position( - handle: &ValidatorPositionAddresses, - storage: &S, -) -> storage_api::Result> -where - S: StorageRead, -{ - let position = handle - .iter(storage)? - .last() - .transpose()? - .map(|(position, _addr)| position); - Ok(position) -} - -/// Find next position in a validator set or 0 if empty -fn find_next_position( - handle: &ValidatorPositionAddresses, - storage: &S, -) -> storage_api::Result -where - S: StorageRead, -{ - let position_iter = handle.iter(storage)?; - let next = position_iter - .last() - .transpose()? - .map(|(position, _address)| position.next()) - .unwrap_or_default(); - Ok(next) -} - -fn get_min_consensus_validator_amount( - handle: &ConsensusValidatorSet, - storage: &S, -) -> storage_api::Result -where - S: StorageRead, -{ - Ok(handle - .iter(storage)? - .next() - .transpose()? - .map(|(subkey, _address)| match subkey { - NestedSubKey::Data { - key, - nested_sub_key: _, - } => key, - }) - .unwrap_or_default()) -} - -/// Returns `Ok(None)` when the below capacity set is empty. -pub fn get_max_below_capacity_validator_amount( - handle: &BelowCapacityValidatorSet, - storage: &S, -) -> storage_api::Result> -where - S: StorageRead, -{ - Ok(handle - .iter(storage)? - .next() - .transpose()? - .map(|(subkey, _address)| match subkey { - NestedSubKey::Data { - key, - nested_sub_key: _, - } => token::Amount::from(key), - })) -} - -/// Inserts a validator into the provided `handle` within some validator set at -/// the next position. Also updates the validator set position for the -/// validator. -pub fn insert_validator_into_set( - handle: &ValidatorPositionAddresses, - storage: &mut S, - epoch: &Epoch, - address: &Address, -) -> storage_api::Result<()> -where - S: StorageRead + StorageWrite, -{ - let next_position = find_next_position(handle, storage)?; - tracing::debug!( - "Inserting validator {} into position {:?} at epoch {}", - address.clone(), - next_position.clone(), - epoch.clone() - ); - handle.insert(storage, next_position, address.clone())?; - validator_set_positions_handle().at(epoch).insert( - storage, - address.clone(), - next_position, - )?; - Ok(()) -} - /// Remove a validator from the consensus validator set pub fn remove_consensus_validator( storage: &mut S, @@ -1052,6 +879,179 @@ where Ok(()) } +#[allow(clippy::too_many_arguments)] +fn insert_into_consensus_and_demote_to_below_cap( + storage: &mut S, + validator: &Address, + tokens_post: token::Amount, + min_consensus_amount: token::Amount, + current_epoch: Epoch, + offset: u64, + consensus_set: &ConsensusValidatorSet, + below_capacity_set: &BelowCapacityValidatorSet, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + // First, remove the last position min consensus validator + let consensus_vals_min = consensus_set.at(&min_consensus_amount); + let last_position_of_min_consensus_vals = + find_last_position(&consensus_vals_min, storage)? + .expect("There must be always be at least 1 consensus validator"); + let removed_min_consensus = consensus_vals_min + .remove(storage, &last_position_of_min_consensus_vals)? + .expect("There must be always be at least 1 consensus validator"); + + let offset_epoch = current_epoch + offset; + + // Insert the min consensus validator into the below-capacity + // set + insert_validator_into_set( + &below_capacity_set.at(&min_consensus_amount.into()), + storage, + &offset_epoch, + &removed_min_consensus, + )?; + validator_state_handle(&removed_min_consensus).set( + storage, + ValidatorState::BelowCapacity, + current_epoch, + offset, + )?; + + // Insert the current validator into the consensus set + insert_validator_into_set( + &consensus_set.at(&tokens_post), + storage, + &offset_epoch, + validator, + )?; + validator_state_handle(validator).set( + storage, + ValidatorState::Consensus, + current_epoch, + offset, + )?; + Ok(()) +} + +/// Find the first (lowest) position in a validator set if it is not empty +fn find_first_position( + handle: &ValidatorPositionAddresses, + storage: &S, +) -> storage_api::Result> +where + S: StorageRead, +{ + let lowest_position = handle + .iter(storage)? + .next() + .transpose()? + .map(|(position, _addr)| position); + Ok(lowest_position) +} + +/// Find the last (greatest) position in a validator set if it is not empty +fn find_last_position( + handle: &ValidatorPositionAddresses, + storage: &S, +) -> storage_api::Result> +where + S: StorageRead, +{ + let position = handle + .iter(storage)? + .last() + .transpose()? + .map(|(position, _addr)| position); + Ok(position) +} + +/// Find next position in a validator set or 0 if empty +fn find_next_position( + handle: &ValidatorPositionAddresses, + storage: &S, +) -> storage_api::Result +where + S: StorageRead, +{ + let position_iter = handle.iter(storage)?; + let next = position_iter + .last() + .transpose()? + .map(|(position, _address)| position.next()) + .unwrap_or_default(); + Ok(next) +} + +fn get_min_consensus_validator_amount( + handle: &ConsensusValidatorSet, + storage: &S, +) -> storage_api::Result +where + S: StorageRead, +{ + Ok(handle + .iter(storage)? + .next() + .transpose()? + .map(|(subkey, _address)| match subkey { + NestedSubKey::Data { + key, + nested_sub_key: _, + } => key, + }) + .unwrap_or_default()) +} + +/// Returns `Ok(None)` when the below capacity set is empty. +fn get_max_below_capacity_validator_amount( + handle: &BelowCapacityValidatorSet, + storage: &S, +) -> storage_api::Result> +where + S: StorageRead, +{ + Ok(handle + .iter(storage)? + .next() + .transpose()? + .map(|(subkey, _address)| match subkey { + NestedSubKey::Data { + key, + nested_sub_key: _, + } => token::Amount::from(key), + })) +} + +/// Inserts a validator into the provided `handle` within some validator set at +/// the next position. Also updates the validator set position for the +/// validator. +fn insert_validator_into_set( + handle: &ValidatorPositionAddresses, + storage: &mut S, + epoch: &Epoch, + address: &Address, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let next_position = find_next_position(handle, storage)?; + tracing::debug!( + "Inserting validator {} into position {:?} at epoch {}", + address.clone(), + next_position.clone(), + epoch.clone() + ); + handle.insert(storage, next_position, address.clone())?; + validator_set_positions_handle().at(epoch).insert( + storage, + address.clone(), + next_position, + )?; + Ok(()) +} + /// Read the position of the validator in the subset of validators that have the /// same bonded stake. This information is held in its own epoched structure in /// addition to being inside the validator sets. From a569bdf4c4435f2881ba888d49774908090bcf65 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 15 Dec 2023 14:31:52 -0500 Subject: [PATCH 132/216] changelog: add #2253 --- .changelog/unreleased/improvements/2253-pos-crate-refactor.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/2253-pos-crate-refactor.md diff --git a/.changelog/unreleased/improvements/2253-pos-crate-refactor.md b/.changelog/unreleased/improvements/2253-pos-crate-refactor.md new file mode 100644 index 0000000000..716e2166a5 --- /dev/null +++ b/.changelog/unreleased/improvements/2253-pos-crate-refactor.md @@ -0,0 +1,2 @@ +- Refactor the PoS crate by breaking up the lib and tests code into smaller + files. ([\#2253](https://github.com/anoma/namada/pull/2253)) \ No newline at end of file From 100f882d7e387be8f50a9d2302800556add71a82 Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 27 Nov 2023 22:39:11 -0500 Subject: [PATCH 133/216] WIP --- apps/src/lib/client/tx.rs | 6 +- tests/src/e2e/ledger_tests.rs | 83 +++++++++++++++++++ .../src/tx_change_consensus_key.rs | 10 +++ 3 files changed, 97 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 74ef2bab4c..dfb3370abb 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -425,7 +425,7 @@ pub async fn submit_change_consensus_key( let data = ConsensusKeyChange { validator: validator.clone(), - consensus_key: new_ck, + consensus_key: new_ck.clone(), }; tx.add_code_from_hash( @@ -433,7 +433,9 @@ pub async fn submit_change_consensus_key( Some(args::TX_CHANGE_CONSENSUS_KEY_WASM.to_string()), ) .add_data(data); - let signing_data = aux_signing_data(namada, &tx_args, None, None).await?; + + let signing_data = + init_validator_signing_data(namada, &tx_args, vec![new_ck]).await?; tx::prepare_tx( namada, diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index e11317d4fe..1eb21d10c5 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -3529,3 +3529,86 @@ fn test_invalid_validator_txs() -> Result<()> { Ok(()) } + +/// Consensus key change +/// +/// 1. +#[test] +fn change_consensus_key() -> Result<()> { + let test = setup::network( + |mut genesis, base_dir| { + genesis.parameters.parameters.min_num_of_blocks = 6; + genesis.parameters.parameters.max_expected_time_per_block = 1; + genesis.parameters.parameters.epochs_per_year = 31_536_000; + genesis.parameters.pos_params.pipeline_len = 2; + genesis.parameters.pos_params.unbonding_len = 4; + setup::set_validators(2, genesis, base_dir, default_port_offset) + }, + None, + )?; + + for i in 0..2 { + set_ethereum_bridge_mode( + &test, + &test.net.chain_id, + &Who::Validator(i), + ethereum_bridge::ledger::Mode::Off, + None, + ); + } + + // 1. Run 4 genesis validator ledger nodes + let _bg_validator_0 = + start_namada_ledger_node_wait_wasm(&test, Some(0), Some(40))? + .background(); + + let _bg_validator_1 = + start_namada_ledger_node_wait_wasm(&test, Some(1), Some(40))? + .background(); + + // let _bg_validator_2 = + // start_namada_ledger_node_wait_wasm(&test, Some(2), Some(40))? + // .background(); + + // let _bg_validator_3 = + // start_namada_ledger_node_wait_wasm(&test, Some(3), Some(40))? + // .background(); + + let validator_0_rpc = get_actor_rpc(&test, &Who::Validator(0)); + + // Put money in the validator account from its balance account so that it + // can pay gas fees + let tx_args = vec![ + "transfer", + "--source", + "validator-0-balance-key", + "--target", + "validator-0-validator-key", + "--amount", + "100.0", + "--token", + "NAM", + "--node", + &validator_0_rpc, + ]; + let mut client = + run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction applied with result")?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + + // Change consensus key + let tx_args = vec![ + "change-consensus-key", + "--validator", + "validator-0", + "--node", + &validator_0_rpc, + ]; + let mut client = + run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + + Ok(()) +} diff --git a/wasm/wasm_source/src/tx_change_consensus_key.rs b/wasm/wasm_source/src/tx_change_consensus_key.rs index 9f06745c9f..9d82f4d855 100644 --- a/wasm/wasm_source/src/tx_change_consensus_key.rs +++ b/wasm/wasm_source/src/tx_change_consensus_key.rs @@ -12,5 +12,15 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { consensus_key, } = transaction::pos::ConsensusKeyChange::try_from_slice(&data[..]) .wrap_err("failed to decode Dec value")?; + + // Check that the tx has been signed with the new consensus key + if !matches!( + verify_signatures_of_pks(ctx, &signed, vec![consensus_key.clone()]), + Ok(true) + ) { + debug_log!("Consensus key ownership signature verification failed"); + panic!() + } + ctx.change_validator_consensus_key(&validator, &consensus_key) } From ae8963f64cb5846ab544d2dc2fc0f20f8de834ae Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 1 Dec 2023 17:48:21 -0500 Subject: [PATCH 134/216] fix bug --- apps/src/lib/client/tx.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index dfb3370abb..8688ee1374 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -374,6 +374,7 @@ pub async fn submit_change_consensus_key( consensus_key_alias = format!("{base_consensus_key_alias}-{key_counter}"); } + drop(wallet); let mut wallet = namada.wallet_mut().await; let consensus_key = consensus_key From 4cc20c2ecbd608ad900a0fa441b773a07292af51 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 1 Dec 2023 18:48:42 -0500 Subject: [PATCH 135/216] need `unsafe-dont-encrypt` arg for testing --- apps/src/lib/cli.rs | 7 +++++++ apps/src/lib/client/tx.rs | 4 +++- sdk/src/args.rs | 2 ++ sdk/src/lib.rs | 1 + tests/src/e2e/ledger_tests.rs | 3 +++ 5 files changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index c04f5a531b..482244fb93 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -5412,6 +5412,7 @@ pub mod args { tx, validator: chain_ctx.get(&self.validator), consensus_key: self.consensus_key.map(|x| chain_ctx.get(&x)), + unsafe_dont_encrypt: self.unsafe_dont_encrypt, tx_code_path: self.tx_code_path.to_path_buf(), } } @@ -5422,11 +5423,13 @@ pub mod args { let tx = Tx::parse(matches); let validator = VALIDATOR.parse(matches); let consensus_key = VALIDATOR_CONSENSUS_KEY.parse(matches); + let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); let tx_code_path = PathBuf::from(TX_CHANGE_CONSENSUS_KEY_WASM); Self { tx, validator, consensus_key, + unsafe_dont_encrypt, tx_code_path, } } @@ -5440,6 +5443,10 @@ pub mod args { "The desired new consensus key. A new one will be \ generated if none given. Note this key must be ed25519.", )) + .arg(UNSAFE_DONT_ENCRYPT.def().help( + "UNSAFE: Do not encrypt the generated keypairs. Do not \ + use this for keys used in a live network.", + )) } } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 8688ee1374..5f166198c6 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -343,6 +343,7 @@ pub async fn submit_change_consensus_key( tx: tx_args, validator, consensus_key, + unsafe_dont_encrypt, tx_code_path: _, }: args::ConsensusKeyChange, ) -> Result<(), error::Error> { @@ -390,7 +391,8 @@ pub async fn submit_change_consensus_key( }) .unwrap_or_else(|| { display_line!(namada.io(), "Generating new consensus key..."); - let password = read_and_confirm_encryption_password(false); + let password = + read_and_confirm_encryption_password(unsafe_dont_encrypt); wallet .gen_store_secret_key( // Note that TM only allows ed25519 for consensus key diff --git a/sdk/src/args.rs b/sdk/src/args.rs index 6599b0e9fb..49c2175e33 100644 --- a/sdk/src/args.rs +++ b/sdk/src/args.rs @@ -1400,6 +1400,8 @@ pub struct ConsensusKeyChange { pub validator: C::Address, /// New consensus key pub consensus_key: Option, + /// Don't encrypt the keypair + pub unsafe_dont_encrypt: bool, /// Path to the TX WASM code file pub tx_code_path: PathBuf, } diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 29aab3041a..7c475fddcd 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -332,6 +332,7 @@ pub trait Namada: Sized + MaybeSync + MaybeSend { validator, consensus_key: None, tx_code_path: PathBuf::from(TX_CHANGE_CONSENSUS_KEY_WASM), + unsafe_dont_encrypt: false, tx: self.tx_builder(), } } diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 1eb21d10c5..443d10fc70 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -3602,8 +3602,11 @@ fn change_consensus_key() -> Result<()> { "change-consensus-key", "--validator", "validator-0", + "--signing-keys", + "validator-0-validator-key", "--node", &validator_0_rpc, + "--unsafe-dont-encrypt", ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; From ae77eff5a4f8d752748dcea860eb3472a2b857b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 8 Dec 2023 12:23:14 +0000 Subject: [PATCH 136/216] test/e2e/ledger: finish consensus key change test --- tests/src/e2e/helpers.rs | 4 +- tests/src/e2e/ledger_tests.rs | 113 +++++++++++++++++++++++----------- tests/src/e2e/setup.rs | 9 +++ 3 files changed, 89 insertions(+), 37 deletions(-) diff --git a/tests/src/e2e/helpers.rs b/tests/src/e2e/helpers.rs index 9fe5194da6..38b77b083a 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -381,7 +381,9 @@ pub fn wait_for_block_height( break Ok(()); } if Instant::now().duration_since(start) > loop_timeout { - panic!("Timed out waiting for height {height}, current {current}"); + return Err(eyre!( + "Timed out waiting for height {height}, current {current}" + )); } sleep(1); } diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 443d10fc70..7e74b4cc4f 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -18,6 +18,7 @@ use std::time::{Duration, Instant}; use borsh_ext::BorshSerializeExt; use color_eyre::eyre::Result; +use color_eyre::owo_colors::OwoColorize; use data_encoding::HEXLOWER; use namada::types::address::Address; use namada::types::storage::Epoch; @@ -3530,17 +3531,25 @@ fn test_invalid_validator_txs() -> Result<()> { Ok(()) } -/// Consensus key change +/// Test change of consensus key of a validator from consensus set. /// -/// 1. +/// 1. Run 2 genesis validator nodes. +/// 2. Change consensus key of validator-0 +/// 3. Check that no new blocks are being created - chain halted because +/// validator-0 consensus change took effect and it cannot sign with the old +/// key anymore +/// 4. Configure validator-0 node with the new key +/// 5. Resume the chain and check that blocks are being created #[test] fn change_consensus_key() -> Result<()> { + let min_num_of_blocks = 6; + let pipeline_len = 2; let test = setup::network( |mut genesis, base_dir| { - genesis.parameters.parameters.min_num_of_blocks = 6; + genesis.parameters.parameters.min_num_of_blocks = min_num_of_blocks; genesis.parameters.parameters.max_expected_time_per_block = 1; genesis.parameters.parameters.epochs_per_year = 31_536_000; - genesis.parameters.pos_params.pipeline_len = 2; + genesis.parameters.pos_params.pipeline_len = pipeline_len; genesis.parameters.pos_params.unbonding_len = 4; setup::set_validators(2, genesis, base_dir, default_port_offset) }, @@ -3557,8 +3566,10 @@ fn change_consensus_key() -> Result<()> { ); } - // 1. Run 4 genesis validator ledger nodes - let _bg_validator_0 = + // ========================================================================= + // 1. Run 2 genesis validator ledger nodes + + let bg_validator_0 = start_namada_ledger_node_wait_wasm(&test, Some(0), Some(40))? .background(); @@ -3566,44 +3577,17 @@ fn change_consensus_key() -> Result<()> { start_namada_ledger_node_wait_wasm(&test, Some(1), Some(40))? .background(); - // let _bg_validator_2 = - // start_namada_ledger_node_wait_wasm(&test, Some(2), Some(40))? - // .background(); - - // let _bg_validator_3 = - // start_namada_ledger_node_wait_wasm(&test, Some(3), Some(40))? - // .background(); - let validator_0_rpc = get_actor_rpc(&test, &Who::Validator(0)); - // Put money in the validator account from its balance account so that it - // can pay gas fees - let tx_args = vec![ - "transfer", - "--source", - "validator-0-balance-key", - "--target", - "validator-0-validator-key", - "--amount", - "100.0", - "--token", - "NAM", - "--node", - &validator_0_rpc, - ]; - let mut client = - run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction applied with result")?; - client.exp_string("Transaction is valid.")?; - client.assert_success(); + // ========================================================================= + // 2. Change consensus key of validator-0 - // Change consensus key let tx_args = vec![ "change-consensus-key", "--validator", "validator-0", "--signing-keys", - "validator-0-validator-key", + "validator-0-balance-key", "--node", &validator_0_rpc, "--unsafe-dont-encrypt", @@ -3613,5 +3597,62 @@ fn change_consensus_key() -> Result<()> { client.exp_string("Transaction is valid.")?; client.assert_success(); + // ========================================================================= + // 3. Check that no new blocks are being created - chain halted because + // validator-0 consensus change took effect and it cannot sign with the old + // key anymore + + // Wait for the next epoch + let validator_0_rpc = get_actor_rpc(&test, Who::Validator(0)); + let _epoch = epoch_sleep(&test, &validator_0_rpc, 30)?; + + // The chain should halt before the following (pipeline) epoch + let _err_report = epoch_sleep(&test, &validator_0_rpc, 30) + .expect_err("Chain should halt"); + + // Load validator-0 wallet + println!( + "{}", + "Setting up the new validator consensus key in CometBFT...".blue() + ); + let chain_dir = test.get_chain_dir(&Who::Validator(0)); + let mut wallet = namada_apps::wallet::load(&chain_dir).unwrap(); + + // ========================================================================= + // 4. Configure validator-0 node with the new key + + // Get the new consensus SK + let new_key_alias = "validator-0-consensus-key-1"; + let new_sk = wallet.find_secret_key(new_key_alias, None).unwrap(); + // Write the key to CometBFT dir + let cometbft_dir = test.get_cometbft_home(&Who::Validator(0)); + namada_apps::node::ledger::tendermint_node::write_validator_key( + cometbft_dir, + &new_sk, + ); + println!( + "{}", + "Done setting up the new validator consensus key in CometBFT.".blue() + ); + + // ========================================================================= + // 5. Resume the chain and check that blocks are being created + + // Restart validator-0 node + let mut validator_0 = bg_validator_0.foreground(); + validator_0.interrupt().unwrap(); + // Wait for the node to stop running + validator_0.exp_string("Namada ledger node has shut down.")?; + validator_0.exp_eof()?; + drop(validator_0); + + let mut validator_0 = start_namada_ledger_node(&test, Some(0), Some(40))?; + // Wait to commit a block + validator_0.exp_regex(r"Committed block hash.*, height: [0-9]+")?; + let _bg_validator_0 = validator_0.background(); + + // Continue to make blocks for another epoch + let _epoch = epoch_sleep(&test, &validator_0_rpc, 40)?; + Ok(()) } diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 6c041b67a0..3d711046c4 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -772,6 +772,15 @@ impl Test { } } + pub fn get_chain_dir(&self, who: &Who) -> PathBuf { + self.get_base_dir(who).join(self.net.chain_id.as_str()) + } + + pub fn get_cometbft_home(&self, who: &Who) -> PathBuf { + self.get_chain_dir(who) + .join(namada_apps::config::COMETBFT_DIR) + } + /// Get an async runtime. pub fn async_runtime(&self) -> &tokio::runtime::Runtime { Lazy::force(&self.async_runtime.0) From fdf9008238e898fd816034afa2f70ec984763352 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 8 Dec 2023 12:35:21 +0000 Subject: [PATCH 137/216] test/e2e: make `Who` copy-able --- tests/src/e2e/eth_bridge_tests.rs | 70 ++++++------ tests/src/e2e/eth_bridge_tests/helpers.rs | 6 +- tests/src/e2e/helpers.rs | 8 +- tests/src/e2e/ibc_tests.rs | 44 ++++---- tests/src/e2e/ledger_tests.rs | 123 +++++++++++----------- tests/src/e2e/setup.rs | 16 +-- 6 files changed, 133 insertions(+), 134 deletions(-) diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index 9f6c14fb1f..42b3c3cf3f 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -70,7 +70,7 @@ fn run_ledger_with_ethereum_events_endpoint() -> Result<()> { set_ethereum_bridge_mode( &test, &test.net.chain_id, - &Who::Validator(0), + Who::Validator(0), ethereum_bridge::ledger::Mode::SelfHostedEndpoint, Some(DEFAULT_ETHEREUM_EVENTS_LISTEN_ADDR), ); @@ -318,7 +318,7 @@ async fn test_bridge_pool_e2e() { set_ethereum_bridge_mode( &test, &test.net.chain_id, - &Who::Validator(0), + Who::Validator(0), ethereum_bridge::ledger::Mode::SelfHostedEndpoint, Some(DEFAULT_ETHEREUM_EVENTS_LISTEN_ADDR), ); @@ -533,7 +533,7 @@ async fn test_wnam_transfer() -> Result<()> { set_ethereum_bridge_mode( &test, &test.net.chain_id, - &Who::Validator(0), + Who::Validator(0), ethereum_bridge::ledger::Mode::SelfHostedEndpoint, Some(DEFAULT_ETHEREUM_EVENTS_LISTEN_ADDR), ); @@ -568,7 +568,7 @@ async fn test_wnam_transfer() -> Result<()> { // check NAM balance of receiver and bridge let receiver_balance = find_balance( &test, - &Who::Validator(0), + Who::Validator(0), &native_token_address, &wnam_transfer.receiver, )?; @@ -576,7 +576,7 @@ async fn test_wnam_transfer() -> Result<()> { let bridge_balance = find_balance( &test, - &Who::Validator(0), + Who::Validator(0), &native_token_address, &BRIDGE_ADDRESS, )?; @@ -629,7 +629,7 @@ fn test_configure_oracle_from_storage() -> Result<()> { set_ethereum_bridge_mode( &test, &test.net.chain_id, - &Who::Validator(0), + Who::Validator(0), ethereum_bridge::ledger::Mode::RemoteEndpoint, None, ); @@ -676,7 +676,7 @@ async fn test_dai_transfer_implicit() -> Result<()> { let albert_wdai_balance = find_wrapped_erc20_balance( &test, - &Who::Validator(0), + Who::Validator(0), &DAI_ERC20_ETH_ADDRESS, &albert_addr, )?; @@ -692,7 +692,7 @@ async fn test_dai_transfer_established() -> Result<()> { // create an established account that Albert controls let established_alias = "albert-established"; - let rpc_address = get_actor_rpc(&test, &Who::Validator(0)); + let rpc_address = get_actor_rpc(&test, Who::Validator(0)); init_established_account( &test, &rpc_address, @@ -715,7 +715,7 @@ async fn test_dai_transfer_established() -> Result<()> { let established_wdai_balance = find_wrapped_erc20_balance( &test, - &Who::Validator(0), + Who::Validator(0), &DAI_ERC20_ETH_ADDRESS, &established_addr, )?; @@ -745,7 +745,7 @@ async fn test_wdai_transfer_implicit_unauthorized() -> Result<()> { let albert_wdai_balance = find_wrapped_erc20_balance( &test, - &Who::Validator(0), + Who::Validator(0), &DAI_ERC20_ETH_ADDRESS, &albert_addr, )?; @@ -757,7 +757,7 @@ async fn test_wdai_transfer_implicit_unauthorized() -> Result<()> { // signed with Albert's key let mut cmd = attempt_wrapped_erc20_transfer( &test, - &Who::Validator(0), + Who::Validator(0), &DAI_ERC20_ETH_ADDRESS, &albert_addr.to_string(), &bertha_addr.to_string(), @@ -774,7 +774,7 @@ async fn test_wdai_transfer_implicit_unauthorized() -> Result<()> { // check balances are unchanged after an unsuccessful transfer let albert_wdai_balance = find_wrapped_erc20_balance( &test, - &Who::Validator(0), + Who::Validator(0), &DAI_ERC20_ETH_ADDRESS, &albert_addr, )?; @@ -793,7 +793,7 @@ async fn test_wdai_transfer_established_unauthorized() -> Result<()> { let initial_transfer_amount = token::Amount::from(10_000_000); // create an established account that Albert controls let albert_established_alias = "albert-established"; - let rpc_address = get_actor_rpc(&test, &Who::Validator(0)); + let rpc_address = get_actor_rpc(&test, Who::Validator(0)); init_established_account( &test, &rpc_address, @@ -815,7 +815,7 @@ async fn test_wdai_transfer_established_unauthorized() -> Result<()> { let albert_wdai_balance = find_wrapped_erc20_balance( &test, - &Who::Validator(0), + Who::Validator(0), &DAI_ERC20_ETH_ADDRESS, &albert_established_addr, )?; @@ -827,7 +827,7 @@ async fn test_wdai_transfer_established_unauthorized() -> Result<()> { // signed with Albert's key let mut cmd = attempt_wrapped_erc20_transfer( &test, - &Who::Validator(0), + Who::Validator(0), &DAI_ERC20_ETH_ADDRESS, &albert_established_addr.to_string(), &bertha_addr.to_string(), @@ -844,7 +844,7 @@ async fn test_wdai_transfer_established_unauthorized() -> Result<()> { // check balances are unchanged after an unsuccessful transfer let albert_wdai_balance = find_wrapped_erc20_balance( &test, - &Who::Validator(0), + Who::Validator(0), &DAI_ERC20_ETH_ADDRESS, &albert_established_addr, )?; @@ -873,7 +873,7 @@ async fn test_wdai_transfer_implicit_to_implicit() -> Result<()> { let albert_wdai_balance = find_wrapped_erc20_balance( &test, - &Who::Validator(0), + Who::Validator(0), &DAI_ERC20_ETH_ADDRESS, &albert_addr, )?; @@ -888,7 +888,7 @@ async fn test_wdai_transfer_implicit_to_implicit() -> Result<()> { }; let mut cmd = attempt_wrapped_erc20_transfer( &test, - &Who::Validator(0), + Who::Validator(0), &DAI_ERC20_ETH_ADDRESS, &albert_addr.to_string(), &bertha_addr.to_string(), @@ -901,7 +901,7 @@ async fn test_wdai_transfer_implicit_to_implicit() -> Result<()> { let albert_wdai_balance = find_wrapped_erc20_balance( &test, - &Who::Validator(0), + Who::Validator(0), &DAI_ERC20_ETH_ADDRESS, &albert_addr, )?; @@ -912,7 +912,7 @@ async fn test_wdai_transfer_implicit_to_implicit() -> Result<()> { let bertha_wdai_balance = find_wrapped_erc20_balance( &test, - &Who::Validator(0), + Who::Validator(0), &DAI_ERC20_ETH_ADDRESS, &bertha_addr, )?; @@ -939,7 +939,7 @@ async fn test_wdai_transfer_implicit_to_established() -> Result<()> { let albert_wdai_balance = find_wrapped_erc20_balance( &test, - &Who::Validator(0), + Who::Validator(0), &DAI_ERC20_ETH_ADDRESS, &albert_addr, )?; @@ -947,7 +947,7 @@ async fn test_wdai_transfer_implicit_to_established() -> Result<()> { // create an established account that Bertha controls let bertha_established_alias = "bertha-established"; - let rpc_address = get_actor_rpc(&test, &Who::Validator(0)); + let rpc_address = get_actor_rpc(&test, Who::Validator(0)); init_established_account( &test, &rpc_address, @@ -966,7 +966,7 @@ async fn test_wdai_transfer_implicit_to_established() -> Result<()> { }; let mut cmd = attempt_wrapped_erc20_transfer( &test, - &Who::Validator(0), + Who::Validator(0), &DAI_ERC20_ETH_ADDRESS, &albert_addr.to_string(), &bertha_established_addr.to_string(), @@ -979,7 +979,7 @@ async fn test_wdai_transfer_implicit_to_established() -> Result<()> { let albert_wdai_balance = find_wrapped_erc20_balance( &test, - &Who::Validator(0), + Who::Validator(0), &DAI_ERC20_ETH_ADDRESS, &albert_addr, )?; @@ -990,7 +990,7 @@ async fn test_wdai_transfer_implicit_to_established() -> Result<()> { let bertha_established_wdai_balance = find_wrapped_erc20_balance( &test, - &Who::Validator(0), + Who::Validator(0), &DAI_ERC20_ETH_ADDRESS, &bertha_established_addr, )?; @@ -1008,7 +1008,7 @@ async fn test_wdai_transfer_established_to_implicit() -> Result<()> { // create an established account that Albert controls let albert_established_alias = "albert-established"; - let rpc_address = get_actor_rpc(&test, &Who::Validator(0)); + let rpc_address = get_actor_rpc(&test, Who::Validator(0)); init_established_account( &test, &rpc_address, @@ -1031,7 +1031,7 @@ async fn test_wdai_transfer_established_to_implicit() -> Result<()> { let albert_established_wdai_balance = find_wrapped_erc20_balance( &test, - &Who::Validator(0), + Who::Validator(0), &DAI_ERC20_ETH_ADDRESS, &albert_established_addr, )?; @@ -1047,7 +1047,7 @@ async fn test_wdai_transfer_established_to_implicit() -> Result<()> { }; let mut cmd = attempt_wrapped_erc20_transfer( &test, - &Who::Validator(0), + Who::Validator(0), &DAI_ERC20_ETH_ADDRESS, &albert_established_addr.to_string(), &bertha_addr.to_string(), @@ -1060,7 +1060,7 @@ async fn test_wdai_transfer_established_to_implicit() -> Result<()> { let albert_established_wdai_balance = find_wrapped_erc20_balance( &test, - &Who::Validator(0), + Who::Validator(0), &DAI_ERC20_ETH_ADDRESS, &albert_established_addr, )?; @@ -1071,7 +1071,7 @@ async fn test_wdai_transfer_established_to_implicit() -> Result<()> { let bertha_wdai_balance = find_wrapped_erc20_balance( &test, - &Who::Validator(0), + Who::Validator(0), &DAI_ERC20_ETH_ADDRESS, &bertha_addr, )?; @@ -1088,7 +1088,7 @@ async fn test_wdai_transfer_established_to_established() -> Result<()> { // create an established account that Albert controls let albert_established_alias = "albert-established"; - let rpc_address = get_actor_rpc(&test, &Who::Validator(0)); + let rpc_address = get_actor_rpc(&test, Who::Validator(0)); init_established_account( &test, &rpc_address, @@ -1111,7 +1111,7 @@ async fn test_wdai_transfer_established_to_established() -> Result<()> { let albert_established_wdai_balance = find_wrapped_erc20_balance( &test, - &Who::Validator(0), + Who::Validator(0), &DAI_ERC20_ETH_ADDRESS, &albert_established_addr, )?; @@ -1137,7 +1137,7 @@ async fn test_wdai_transfer_established_to_established() -> Result<()> { }; let mut cmd = attempt_wrapped_erc20_transfer( &test, - &Who::Validator(0), + Who::Validator(0), &DAI_ERC20_ETH_ADDRESS, &albert_established_addr.to_string(), &bertha_established_addr.to_string(), @@ -1150,7 +1150,7 @@ async fn test_wdai_transfer_established_to_established() -> Result<()> { let albert_established_wdai_balance = find_wrapped_erc20_balance( &test, - &Who::Validator(0), + Who::Validator(0), &DAI_ERC20_ETH_ADDRESS, &albert_established_addr, )?; @@ -1161,7 +1161,7 @@ async fn test_wdai_transfer_established_to_established() -> Result<()> { let bertha_established_wdai_balance = find_wrapped_erc20_balance( &test, - &Who::Validator(0), + Who::Validator(0), &DAI_ERC20_ETH_ADDRESS, &bertha_established_addr, )?; diff --git a/tests/src/e2e/eth_bridge_tests/helpers.rs b/tests/src/e2e/eth_bridge_tests/helpers.rs index 9fbc467443..3c7241cdce 100644 --- a/tests/src/e2e/eth_bridge_tests/helpers.rs +++ b/tests/src/e2e/eth_bridge_tests/helpers.rs @@ -114,7 +114,7 @@ pub fn setup_single_validator_test() -> Result<(Test, NamadaBgCmd)> { set_ethereum_bridge_mode( &test, &test.net.chain_id, - &Who::Validator(0), + Who::Validator(0), ethereum_bridge::ledger::Mode::SelfHostedEndpoint, Some(DEFAULT_ETHEREUM_EVENTS_LISTEN_ADDR), ); @@ -163,7 +163,7 @@ pub async fn send_transfer_to_namada_event( /// This will fail if the keys for `signer` are not in the local wallet. pub fn attempt_wrapped_erc20_transfer( test: &Test, - node: &Who, + node: Who, asset: &EthAddress, from: &str, to: &str, @@ -198,7 +198,7 @@ pub fn attempt_wrapped_erc20_transfer( /// been involved in a wrapped ERC20 transfer of any kind). pub fn find_wrapped_erc20_balance( test: &Test, - node: &Who, + node: Who, asset: &EthAddress, owner: &Address, ) -> Result { diff --git a/tests/src/e2e/helpers.rs b/tests/src/e2e/helpers.rs index 38b77b083a..81488cba94 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -130,7 +130,7 @@ pub fn find_address(test: &Test, alias: impl AsRef) -> Result
{ #[allow(dead_code)] pub fn find_balance( test: &Test, - node: &Who, + node: Who, token: &Address, owner: &Address, ) -> Result { @@ -162,7 +162,7 @@ pub fn find_balance( } /// Find the address of the node's RPC endpoint. -pub fn get_actor_rpc(test: &Test, who: &Who) -> String { +pub fn get_actor_rpc(test: &Test, who: Who) -> String { let base_dir = test.get_base_dir(who); let tendermint_mode = match who { Who::NonValidator => TendermintMode::Full, @@ -176,7 +176,7 @@ pub fn get_actor_rpc(test: &Test, who: &Who) -> String { } /// Get some nodes's wallet. -pub fn get_node_wallet(test: &Test, who: &Who) -> Wallet { +pub fn get_node_wallet(test: &Test, who: Who) -> Wallet { let wallet_store_dir = test.get_base_dir(who).join(test.net.chain_id.as_str()); let mut wallet = FsWalletUtils::new(wallet_store_dir); @@ -185,7 +185,7 @@ pub fn get_node_wallet(test: &Test, who: &Who) -> Wallet { } /// Get the public key of the validator -pub fn get_validator_pk(test: &Test, who: &Who) -> Option { +pub fn get_validator_pk(test: &Test, who: Who) -> Option { let index = match who { Who::NonValidator => return None, Who::Validator(i) => i, diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index d69d3f1844..9aeaf59475 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -96,14 +96,14 @@ fn run_ledger_ibc() -> Result<()> { set_ethereum_bridge_mode( &test_a, &test_a.net.chain_id, - &Who::Validator(0), + Who::Validator(0), ethereum_bridge::ledger::Mode::Off, None, ); set_ethereum_bridge_mode( &test_b, &test_b.net.chain_id, - &Who::Validator(0), + Who::Validator(0), ethereum_bridge::ledger::Mode::Off, None, ); @@ -265,7 +265,7 @@ fn setup_two_single_node_nets() -> Result<(Test, Test)> { .map_err(|_| eyre!("Could not read genesis files from test b"))?; // chain b's validator needs to listen on a different port than chain a's // validator - let validator_pk = get_validator_pk(&test_b, &Who::Validator(0)).unwrap(); + let validator_pk = get_validator_pk(&test_b, Who::Validator(0)).unwrap(); let validator_addr = genesis_b .transactions .established_account @@ -349,7 +349,7 @@ fn create_client(test_a: &Test, test_b: &Test) -> Result<(ClientId, ClientId)> { } fn make_client_state(test: &Test, height: Height) -> TmClientState { - let rpc = get_actor_rpc(test, &Who::Validator(0)); + let rpc = get_actor_rpc(test, Who::Validator(0)); let ledger_address = TendermintAddress::from_str(&rpc).unwrap(); let client = HttpClient::new(ledger_address).unwrap(); @@ -473,12 +473,12 @@ fn update_client( } fn make_light_client_io(test: &Test) -> TmLightClientIo { - let addr = format!("http://{}", get_actor_rpc(test, &Who::Validator(0))); + let addr = format!("http://{}", get_actor_rpc(test, Who::Validator(0))); let rpc_addr = Url::from_str(&addr).unwrap(); let rpc_client = HttpClient::new(rpc_addr).unwrap(); let rpc_timeout = Duration::new(10, 0); - let pk = get_validator_pk(test, &Who::Validator(0)).unwrap(); + let pk = get_validator_pk(test, Who::Validator(0)).unwrap(); let peer_id = id_from_pk(&PublicKey::try_from_pk(&pk).unwrap()); TmLightClientIo::new(peer_id, rpc_client, Some(rpc_timeout)) @@ -867,7 +867,7 @@ fn transfer_received_token( channel_id: &ChannelId, test: &Test, ) -> Result<()> { - let rpc = get_actor_rpc(test, &Who::Validator(0)); + let rpc = get_actor_rpc(test, Who::Validator(0)); let ibc_denom = format!("{port_id}/{channel_id}/nam"); let amount = Amount::native_whole(50000).to_string_native(); let tx_args = [ @@ -1022,7 +1022,7 @@ fn shielded_transfer( ) -> Result<()> { // Get masp proof for the following IBC transfer from the destination chain // It will send 10 BTC from Chain A to PA(B) on Chain B - let rpc_b = get_actor_rpc(test_b, &Who::Validator(0)); + let rpc_b = get_actor_rpc(test_b, Who::Validator(0)); let output_folder = test_b.test_dir.path().to_string_lossy(); let amount = Amount::native_whole(10).to_string_native(); let args = [ @@ -1178,7 +1178,7 @@ fn submit_ibc_tx( std::fs::write(&data_path, data).expect("writing data failed"); let data_path = data_path.to_string_lossy(); - let rpc = get_actor_rpc(test, &Who::Validator(0)); + let rpc = get_actor_rpc(test, Who::Validator(0)); let mut client = run!( test, Bin::Client, @@ -1221,7 +1221,7 @@ fn transfer( expected_err: Option<&str>, wait_reveal_pk: bool, ) -> Result { - let rpc = get_actor_rpc(test, &Who::Validator(0)); + let rpc = get_actor_rpc(test, Who::Validator(0)); let channel_id = channel_id.to_string(); let port_id = port_id.to_string(); @@ -1311,7 +1311,7 @@ fn make_ibc_data(message: impl Msg) -> Vec { } fn query_height(test: &Test) -> Result { - let rpc = get_actor_rpc(test, &Who::Validator(0)); + let rpc = get_actor_rpc(test, Who::Validator(0)); let ledger_address = TendermintAddress::from_str(&rpc).unwrap(); let client = HttpClient::new(ledger_address).unwrap(); @@ -1324,7 +1324,7 @@ fn query_height(test: &Test) -> Result { } fn query_header(test: &Test, height: Height) -> Result { - let rpc = get_actor_rpc(test, &Who::Validator(0)); + let rpc = get_actor_rpc(test, Who::Validator(0)); let ledger_address = TendermintAddress::from_str(&rpc).unwrap(); let client = HttpClient::new(ledger_address).unwrap(); let height = height.revision_height() as u32; @@ -1345,7 +1345,7 @@ fn check_ibc_update_query( client_id: &ClientId, consensus_height: BlockHeight, ) -> Result<()> { - let rpc = get_actor_rpc(test, &Who::Validator(0)); + let rpc = get_actor_rpc(test, Who::Validator(0)); let ledger_address = TendermintAddress::from_str(&rpc).unwrap(); let client = HttpClient::new(ledger_address).unwrap(); match test.async_runtime().block_on(RPC.shell().ibc_client_update( @@ -1364,7 +1364,7 @@ fn check_ibc_packet_query( event_type: &EventType, packet: &Packet, ) -> Result<()> { - let rpc = get_actor_rpc(test, &Who::Validator(0)); + let rpc = get_actor_rpc(test, Who::Validator(0)); let ledger_address = TendermintAddress::from_str(&rpc).unwrap(); let client = HttpClient::new(ledger_address).unwrap(); match test.async_runtime().block_on(RPC.shell().ibc_packet( @@ -1387,7 +1387,7 @@ fn query_value_with_proof( key: &Key, height: Option, ) -> Result<(Option>, TmProof)> { - let rpc = get_actor_rpc(test, &Who::Validator(0)); + let rpc = get_actor_rpc(test, Who::Validator(0)); let ledger_address = TendermintAddress::from_str(&rpc).unwrap(); let client = HttpClient::new(ledger_address).unwrap(); let result = test.async_runtime().block_on(query_storage_value_bytes( @@ -1425,7 +1425,7 @@ fn check_balances( test_b: &Test, ) -> Result<()> { // Check the balances on Chain A - let rpc_a = get_actor_rpc(test_a, &Who::Validator(0)); + let rpc_a = get_actor_rpc(test_a, Who::Validator(0)); // Check the escrowed balance let escrow = Address::Internal(InternalAddress::Ibc).to_string(); let query_args = vec![ @@ -1444,7 +1444,7 @@ fn check_balances( // Check the balance on Chain B let trace_path = format!("{}/{}", &dest_port_id, &dest_channel_id); - let rpc_b = get_actor_rpc(test_b, &Who::Validator(0)); + let rpc_b = get_actor_rpc(test_b, Who::Validator(0)); let query_args = vec![ "balance", "--owner", BERTHA, "--token", NAM, "--node", &rpc_b, ]; @@ -1465,7 +1465,7 @@ fn check_balances_after_non_ibc( let trace_path = format!("{}/{}", port_id, channel_id); // Check the source - let rpc = get_actor_rpc(test, &Who::Validator(0)); + let rpc = get_actor_rpc(test, Who::Validator(0)); let query_args = vec!["balance", "--owner", BERTHA, "--token", NAM, "--node", &rpc]; let expected = format!("{}/nam: 50000", trace_path); @@ -1492,7 +1492,7 @@ fn check_balances_after_back( test_b: &Test, ) -> Result<()> { // Check the balances on Chain A - let rpc_a = get_actor_rpc(test_a, &Who::Validator(0)); + let rpc_a = get_actor_rpc(test_a, Who::Validator(0)); // Check the escrowed balance let escrow = Address::Internal(InternalAddress::Ibc).to_string(); let query_args = vec![ @@ -1511,7 +1511,7 @@ fn check_balances_after_back( // Check the balance on Chain B let trace_path = format!("{}/{}", dest_port_id, dest_channel_id); - let rpc_b = get_actor_rpc(test_b, &Who::Validator(0)); + let rpc_b = get_actor_rpc(test_b, Who::Validator(0)); let query_args = vec![ "balance", "--owner", BERTHA, "--token", NAM, "--node", &rpc_b, ]; @@ -1529,7 +1529,7 @@ fn check_shielded_balances( test_b: &Test, ) -> Result<()> { // Check the balance on Chain B - let rpc_b = get_actor_rpc(test_b, &Who::Validator(0)); + let rpc_b = get_actor_rpc(test_b, Who::Validator(0)); let query_args = vec![ "balance", "--owner", @@ -1640,7 +1640,7 @@ fn get_attributes_from_event(event: &AbciEvent) -> HashMap { } fn get_events(test: &Test, height: u32) -> Result> { - let rpc = get_actor_rpc(test, &Who::Validator(0)); + let rpc = get_actor_rpc(test, Who::Validator(0)); let ledger_address = TendermintAddress::from_str(&rpc).unwrap(); let client = HttpClient::new(ledger_address).unwrap(); diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 7e74b4cc4f..76b7b63345 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -62,8 +62,7 @@ fn start_namada_ledger_node( Some(idx) => Who::Validator(idx), _ => Who::NonValidator, }; - let mut node = - run_as!(test, who.clone(), Bin::Node, &["ledger"], timeout_sec)?; + let mut node = run_as!(test, who, Bin::Node, &["ledger"], timeout_sec)?; node.exp_string("Namada ledger node started")?; if let Who::Validator(_) = who { node.exp_string("This node is a validator")?; @@ -93,7 +92,7 @@ fn run_ledger() -> Result<()> { set_ethereum_bridge_mode( &test, &test.net.chain_id, - &Who::Validator(0), + Who::Validator(0), ethereum_bridge::ledger::Mode::Off, None, ); @@ -134,20 +133,20 @@ fn test_node_connectivity_and_consensus() -> Result<()> { None, )?; - allow_duplicate_ips(&test, &test.net.chain_id, &Who::Validator(0)); - allow_duplicate_ips(&test, &test.net.chain_id, &Who::Validator(1)); + allow_duplicate_ips(&test, &test.net.chain_id, Who::Validator(0)); + allow_duplicate_ips(&test, &test.net.chain_id, Who::Validator(1)); set_ethereum_bridge_mode( &test, &test.net.chain_id, - &Who::Validator(0), + Who::Validator(0), ethereum_bridge::ledger::Mode::Off, None, ); set_ethereum_bridge_mode( &test, &test.net.chain_id, - &Who::Validator(1), + Who::Validator(1), ethereum_bridge::ledger::Mode::Off, None, ); @@ -163,7 +162,7 @@ fn test_node_connectivity_and_consensus() -> Result<()> { start_namada_ledger_node_wait_wasm(&test, None, Some(40))?.background(); // 2. Cross over epoch to check for consensus with multiple nodes - let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let validator_one_rpc = get_actor_rpc(&test, Who::Validator(0)); let _ = epoch_sleep(&test, &validator_one_rpc, 720)?; // 3. Submit a valid token transfer tx @@ -200,9 +199,9 @@ fn test_node_connectivity_and_consensus() -> Result<()> { let _bg_validator_0 = validator_0.background(); let _bg_validator_1 = validator_1.background(); - let validator_0_rpc = get_actor_rpc(&test, &Who::Validator(0)); - let validator_1_rpc = get_actor_rpc(&test, &Who::Validator(1)); - let non_validator_rpc = get_actor_rpc(&test, &Who::NonValidator); + let validator_0_rpc = get_actor_rpc(&test, Who::Validator(0)); + let validator_1_rpc = get_actor_rpc(&test, Who::Validator(1)); + let non_validator_rpc = get_actor_rpc(&test, Who::NonValidator); // Find the block height on the validator let after_tx_height = get_height(&test, &validator_0_rpc)?; @@ -237,7 +236,7 @@ fn test_namada_shuts_down_if_tendermint_dies() -> Result<()> { set_ethereum_bridge_mode( &test, &test.net.chain_id, - &Who::Validator(0), + Who::Validator(0), ethereum_bridge::ledger::Mode::Off, None, ); @@ -279,7 +278,7 @@ fn run_ledger_load_state_and_reset() -> Result<()> { set_ethereum_bridge_mode( &test, &test.net.chain_id, - &Who::Validator(0), + Who::Validator(0), ethereum_bridge::ledger::Mode::Off, None, ); @@ -293,7 +292,7 @@ fn run_ledger_load_state_and_reset() -> Result<()> { ledger.exp_regex(r"Committed block hash.*, height: [0-9]+")?; let bg_ledger = ledger.background(); // Wait for a new epoch - let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let validator_one_rpc = get_actor_rpc(&test, Who::Validator(0)); epoch_sleep(&test, &validator_one_rpc, 30)?; // 2. Shut it down @@ -362,7 +361,7 @@ fn suspend_ledger() -> Result<()> { let bg_ledger = ledger.background(); // 2. Query the ledger - let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let validator_one_rpc = get_actor_rpc(&test, Who::Validator(0)); let mut client = run!( test, Bin::Client, @@ -421,7 +420,7 @@ fn ledger_txs_and_queries() -> Result<()> { set_ethereum_bridge_mode( &test, &test.net.chain_id, - &Who::Validator(0), + Who::Validator(0), ethereum_bridge::ledger::Mode::Off, None, ); @@ -448,7 +447,7 @@ fn ledger_txs_and_queries() -> Result<()> { std::fs::write(&tx_data_path, transfer).unwrap(); let tx_data_path = tx_data_path.to_string_lossy(); - let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let validator_one_rpc = get_actor_rpc(&test, Who::Validator(0)); let multisig_account = format!("{},{},{}", BERTHA_KEY, ALBERT_KEY, CHRISTEL_KEY); @@ -711,7 +710,7 @@ fn wrapper_disposable_signer() -> Result<()> { start_namada_ledger_node_wait_wasm(&test, Some(0), Some(40))? .background(); - let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let validator_one_rpc = get_actor_rpc(&test, Who::Validator(0)); let _ep1 = epoch_sleep(&test, &validator_one_rpc, 720)?; @@ -795,7 +794,7 @@ fn invalid_transactions() -> Result<()> { set_ethereum_bridge_mode( &test, &test.net.chain_id, - &Who::Validator(0), + Who::Validator(0), ethereum_bridge::ledger::Mode::Off, None, ); @@ -807,7 +806,7 @@ fn invalid_transactions() -> Result<()> { // 2. Submit a an invalid transaction (trying to transfer tokens should fail // in the user's VP due to the wrong signer) - let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let validator_one_rpc = get_actor_rpc(&test, Who::Validator(0)); let tx_args = vec![ "transfer", @@ -936,12 +935,12 @@ fn pos_bonds() -> Result<()> { }, None, )?; - allow_duplicate_ips(&test, &test.net.chain_id, &Who::Validator(0)); - allow_duplicate_ips(&test, &test.net.chain_id, &Who::Validator(1)); + allow_duplicate_ips(&test, &test.net.chain_id, Who::Validator(0)); + allow_duplicate_ips(&test, &test.net.chain_id, Who::Validator(1)); set_ethereum_bridge_mode( &test, &test.net.chain_id, - &Who::Validator(0), + Who::Validator(0), ethereum_bridge::ledger::Mode::Off, None, ); @@ -951,7 +950,7 @@ fn pos_bonds() -> Result<()> { start_namada_ledger_node_wait_wasm(&test, Some(0), Some(40))? .background(); - let validator_0_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let validator_0_rpc = get_actor_rpc(&test, Who::Validator(0)); // 2. Submit a self-bond for the first genesis validator let tx_args = vec![ @@ -1170,7 +1169,7 @@ fn pos_rewards() -> Result<()> { set_ethereum_bridge_mode( &test, &test.net.chain_id, - &Who::Validator(i), + Who::Validator(i), ethereum_bridge::ledger::Mode::Off, None, ); @@ -1181,7 +1180,7 @@ fn pos_rewards() -> Result<()> { start_namada_ledger_node_wait_wasm(&test, Some(0), Some(40))? .background(); - let validator_0_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let validator_0_rpc = get_actor_rpc(&test, Who::Validator(0)); // Query the current rewards for the validator self-bond let tx_args = vec![ @@ -1344,7 +1343,7 @@ fn test_bond_queries() -> Result<()> { start_namada_ledger_node_wait_wasm(&test, Some(0), Some(40))? .background(); - let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let validator_one_rpc = get_actor_rpc(&test, Who::Validator(0)); let validator_alias = "validator-0"; // 2. Submit a delegation to the genesis validator @@ -1524,7 +1523,7 @@ fn pos_init_validator() -> Result<()> { non_validator.exp_string("Committed block hash")?; let bg_non_validator = non_validator.background(); - let non_validator_rpc = get_actor_rpc(&test, &Who::NonValidator); + let non_validator_rpc = get_actor_rpc(&test, Who::NonValidator); // 2. Initialize a new validator account with the non-validator node let new_validator = "new-validator"; @@ -1639,7 +1638,7 @@ fn pos_init_validator() -> Result<()> { } let loc = format!("{}:{}", std::file!(), std::line!()); - let validator_1_base_dir = test.get_base_dir(&Who::NonValidator); + let validator_1_base_dir = test.get_base_dir(Who::NonValidator); let mut validator_1 = setup::run_cmd( Bin::Node, ["ledger"], @@ -1703,7 +1702,7 @@ fn ledger_many_txs_in_a_block() -> Result<()> { set_ethereum_bridge_mode( &test, &test.net.chain_id, - &Who::Validator(0), + Who::Validator(0), ethereum_bridge::ledger::Mode::Off, None, ); @@ -1713,7 +1712,7 @@ fn ledger_many_txs_in_a_block() -> Result<()> { start_namada_ledger_node_wait_wasm(&test, Some(0), Some(40))? .background(); - let validator_one_rpc = Arc::new(get_actor_rpc(&test, &Who::Validator(0))); + let validator_one_rpc = Arc::new(get_actor_rpc(&test, Who::Validator(0))); // A token transfer tx args let tx_args = Arc::new(vec![ @@ -1789,7 +1788,7 @@ fn proposal_submission() -> Result<()> { set_ethereum_bridge_mode( &test, &test.net.chain_id, - &Who::Validator(0), + Who::Validator(0), ethereum_bridge::ledger::Mode::Off, None, ); @@ -1805,7 +1804,7 @@ fn proposal_submission() -> Result<()> { start_namada_ledger_node_wait_wasm(&test, Some(0), Some(40))? .background(); - let validator_0_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let validator_0_rpc = get_actor_rpc(&test, Who::Validator(0)); // 1.1 Delegate some token let tx_args = vec![ @@ -1831,7 +1830,7 @@ fn proposal_submission() -> Result<()> { TestWasms::TxProposalCode.read_bytes(), 12, ); - let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let validator_one_rpc = get_actor_rpc(&test, Who::Validator(0)); let submit_proposal_args = vec![ "init-proposal", @@ -2114,7 +2113,7 @@ fn pgf_governance_proposal() -> Result<()> { set_ethereum_bridge_mode( &test, &test.net.chain_id, - &Who::Validator(0), + Who::Validator(0), ethereum_bridge::ledger::Mode::Off, None, ); @@ -2130,7 +2129,7 @@ fn pgf_governance_proposal() -> Result<()> { start_namada_ledger_node_wait_wasm(&test, Some(0), Some(40))? .background(); - let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let validator_one_rpc = get_actor_rpc(&test, Who::Validator(0)); // Delegate some token let tx_args = vec![ @@ -2158,7 +2157,7 @@ fn pgf_governance_proposal() -> Result<()> { let valid_proposal_json_path = prepare_proposal_data(&test, albert, pgf_stewards, 12); - let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let validator_one_rpc = get_actor_rpc(&test, Who::Validator(0)); let submit_proposal_args = vec![ "init-proposal", @@ -2350,7 +2349,7 @@ fn pgf_governance_proposal() -> Result<()> { let valid_proposal_json_path = prepare_proposal_data(&test, albert, pgf_funding, 36); - let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let validator_one_rpc = get_actor_rpc(&test, Who::Validator(0)); let submit_proposal_args = vec![ "init-proposal", @@ -2428,7 +2427,7 @@ fn proposal_offline() -> Result<()> { set_ethereum_bridge_mode( &test, &test.net.chain_id, - &Who::Validator(0), + Who::Validator(0), ethereum_bridge::ledger::Mode::Off, None, ); @@ -2438,7 +2437,7 @@ fn proposal_offline() -> Result<()> { start_namada_ledger_node_wait_wasm(&test, Some(0), Some(40))? .background(); - let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let validator_one_rpc = get_actor_rpc(&test, Who::Validator(0)); // 1.1 Delegate some token let tx_args = vec![ @@ -2489,7 +2488,7 @@ fn proposal_offline() -> Result<()> { epoch = get_epoch(&test, &validator_one_rpc).unwrap(); } - let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let validator_one_rpc = get_actor_rpc(&test, Who::Validator(0)); let offline_proposal_args = vec![ "init-proposal", @@ -2608,20 +2607,20 @@ fn double_signing_gets_slashed() -> Result<()> { None, )?; - allow_duplicate_ips(&test, &test.net.chain_id, &Who::Validator(0)); - allow_duplicate_ips(&test, &test.net.chain_id, &Who::Validator(1)); + allow_duplicate_ips(&test, &test.net.chain_id, Who::Validator(0)); + allow_duplicate_ips(&test, &test.net.chain_id, Who::Validator(1)); set_ethereum_bridge_mode( &test, &test.net.chain_id, - &Who::Validator(0), + Who::Validator(0), ethereum_bridge::ledger::Mode::Off, None, ); set_ethereum_bridge_mode( &test, &test.net.chain_id, - &Who::Validator(1), + Who::Validator(1), ethereum_bridge::ledger::Mode::Off, None, ); @@ -2648,7 +2647,7 @@ fn double_signing_gets_slashed() -> Result<()> { let _bg_validator_3 = validator_3.background(); // 2. Copy the first genesis validator base-dir - let validator_0_base_dir = test.get_base_dir(&Who::Validator(0)); + let validator_0_base_dir = test.get_base_dir(Who::Validator(0)); let validator_0_base_dir_copy = test .test_dir .path() @@ -2740,7 +2739,7 @@ fn double_signing_gets_slashed() -> Result<()> { let _bg_validator_0_copy = validator_0_copy.background(); // 5. Submit a valid token transfer tx to validator 0 - let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let validator_one_rpc = get_actor_rpc(&test, Who::Validator(0)); let tx_args = [ "transfer", "--source", @@ -2900,7 +2899,7 @@ fn implicit_account_reveal_pk() -> Result<()> { .background(); // 2. Some transactions that need signature authorization: - let validator_0_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let validator_0_rpc = get_actor_rpc(&test, Who::Validator(0)); let txs_args: Vec Vec>> = vec![ // A token transfer tx Box::new(|source| { @@ -3039,7 +3038,7 @@ fn test_epoch_sleep() -> Result<()> { let _bg_ledger = ledger.background(); - let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let validator_one_rpc = get_actor_rpc(&test, Who::Validator(0)); // 2. Query the current epoch let start_epoch = get_epoch(&test, &validator_one_rpc).unwrap(); @@ -3129,12 +3128,12 @@ fn deactivate_and_reactivate_validator() -> Result<()> { }, None, )?; - allow_duplicate_ips(&test, &test.net.chain_id, &Who::Validator(0)); - allow_duplicate_ips(&test, &test.net.chain_id, &Who::Validator(1)); + allow_duplicate_ips(&test, &test.net.chain_id, Who::Validator(0)); + allow_duplicate_ips(&test, &test.net.chain_id, Who::Validator(1)); set_ethereum_bridge_mode( &test, &test.net.chain_id, - &Who::Validator(0), + Who::Validator(0), ethereum_bridge::ledger::Mode::Off, None, ); @@ -3148,7 +3147,7 @@ fn deactivate_and_reactivate_validator() -> Result<()> { start_namada_ledger_node_wait_wasm(&test, Some(1), Some(40))? .background(); - let validator_1_rpc = get_actor_rpc(&test, &Who::Validator(1)); + let validator_1_rpc = get_actor_rpc(&test, Who::Validator(1)); // Check the state of validator-1 let tx_args = vec![ @@ -3261,7 +3260,7 @@ fn change_validator_metadata() -> Result<()> { set_ethereum_bridge_mode( &test, &test.net.chain_id, - &Who::Validator(0), + Who::Validator(0), ethereum_bridge::ledger::Mode::Off, None, ); @@ -3271,7 +3270,7 @@ fn change_validator_metadata() -> Result<()> { start_namada_ledger_node_wait_wasm(&test, Some(0), Some(40))? .background(); - let validator_0_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let validator_0_rpc = get_actor_rpc(&test, Who::Validator(0)); // 2. Query the validator metadata loaded from genesis let metadata_query_args = vec![ @@ -3399,7 +3398,7 @@ fn test_invalid_validator_txs() -> Result<()> { set_ethereum_bridge_mode( &test, &test.net.chain_id, - &Who::Validator(0), + Who::Validator(0), ethereum_bridge::ledger::Mode::Off, None, ); @@ -3413,8 +3412,8 @@ fn test_invalid_validator_txs() -> Result<()> { start_namada_ledger_node_wait_wasm(&test, Some(1), Some(40))? .background(); - let validator_0_rpc = get_actor_rpc(&test, &Who::Validator(0)); - let validator_1_rpc = get_actor_rpc(&test, &Who::Validator(1)); + let validator_0_rpc = get_actor_rpc(&test, Who::Validator(0)); + let validator_1_rpc = get_actor_rpc(&test, Who::Validator(1)); // Try to change validator-1 commission rate as validator-0 let tx_args = vec![ @@ -3560,7 +3559,7 @@ fn change_consensus_key() -> Result<()> { set_ethereum_bridge_mode( &test, &test.net.chain_id, - &Who::Validator(i), + Who::Validator(i), ethereum_bridge::ledger::Mode::Off, None, ); @@ -3577,7 +3576,7 @@ fn change_consensus_key() -> Result<()> { start_namada_ledger_node_wait_wasm(&test, Some(1), Some(40))? .background(); - let validator_0_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let validator_0_rpc = get_actor_rpc(&test, Who::Validator(0)); // ========================================================================= // 2. Change consensus key of validator-0 @@ -3615,7 +3614,7 @@ fn change_consensus_key() -> Result<()> { "{}", "Setting up the new validator consensus key in CometBFT...".blue() ); - let chain_dir = test.get_chain_dir(&Who::Validator(0)); + let chain_dir = test.get_chain_dir(Who::Validator(0)); let mut wallet = namada_apps::wallet::load(&chain_dir).unwrap(); // ========================================================================= @@ -3625,7 +3624,7 @@ fn change_consensus_key() -> Result<()> { let new_key_alias = "validator-0-consensus-key-1"; let new_sk = wallet.find_secret_key(new_key_alias, None).unwrap(); // Write the key to CometBFT dir - let cometbft_dir = test.get_cometbft_home(&Who::Validator(0)); + let cometbft_dir = test.get_cometbft_home(Who::Validator(0)); namada_apps::node::ledger::tendermint_node::write_validator_key( cometbft_dir, &new_sk, diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 3d711046c4..f0c9df5d5e 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -86,7 +86,7 @@ pub fn default_port_offset(ix: u8) -> u16 { pub fn update_actor_config( test: &Test, chain_id: &ChainId, - who: &Who, + who: Who, update: F, ) where F: FnOnce(&mut Config), @@ -101,7 +101,7 @@ pub fn update_actor_config( } /// Configure validator p2p settings to allow duplicat ips -pub fn allow_duplicate_ips(test: &Test, chain_id: &ChainId, who: &Who) { +pub fn allow_duplicate_ips(test: &Test, chain_id: &ChainId, who: Who) { update_actor_config(test, chain_id, who, |config| { config.ledger.cometbft.p2p.allow_duplicate_ip = true; }); @@ -112,7 +112,7 @@ pub fn allow_duplicate_ips(test: &Test, chain_id: &ChainId, who: &Who) { pub fn set_ethereum_bridge_mode( test: &Test, chain_id: &ChainId, - who: &Who, + who: Who, mode: ethereum_bridge::ledger::Mode, rpc_endpoint: Option<&str>, ) { @@ -710,7 +710,7 @@ mod macros { } } -#[derive(Clone)] +#[derive(Clone, Copy, Debug)] pub enum Who { // A non-validator NonValidator, @@ -757,11 +757,11 @@ impl Test { I: IntoIterator, S: AsRef, { - let base_dir = self.get_base_dir(&who); + let base_dir = self.get_base_dir(who); run_cmd(bin, args, timeout_sec, &self.working_dir, base_dir, loc) } - pub fn get_base_dir(&self, who: &Who) -> PathBuf { + pub fn get_base_dir(&self, who: Who) -> PathBuf { match who { Who::NonValidator => self.test_dir.path().to_owned(), Who::Validator(index) => self @@ -772,11 +772,11 @@ impl Test { } } - pub fn get_chain_dir(&self, who: &Who) -> PathBuf { + pub fn get_chain_dir(&self, who: Who) -> PathBuf { self.get_base_dir(who).join(self.net.chain_id.as_str()) } - pub fn get_cometbft_home(&self, who: &Who) -> PathBuf { + pub fn get_cometbft_home(&self, who: Who) -> PathBuf { self.get_chain_dir(who) .join(namada_apps::config::COMETBFT_DIR) } From bbde7eaa1cdd1f5c80ab60e37a6723338a83e555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 8 Dec 2023 13:18:31 +0000 Subject: [PATCH 138/216] github/workflows: add e2e test --- .github/workflows/scripts/e2e.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/scripts/e2e.json b/.github/workflows/scripts/e2e.json index 6e736d16f1..8817c9b6d7 100644 --- a/.github/workflows/scripts/e2e.json +++ b/.github/workflows/scripts/e2e.json @@ -27,6 +27,7 @@ "e2e::ledger_tests::test_bond_queries": 95, "e2e::ledger_tests::suspend_ledger": 30, "e2e::ledger_tests::stop_ledger_at_height": 18, + "e2e::ledger_tests::change_consensus_key": 91, "e2e::wallet_tests::wallet_address_cmds": 1, "e2e::wallet_tests::wallet_encrypted_key_cmds": 1, "e2e::wallet_tests::wallet_encrypted_key_cmds_env_var": 1, From 6d94864a79fe3aeedc1022f04df8f3475177a14a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 8 Dec 2023 14:57:00 +0000 Subject: [PATCH 139/216] client/tx: tidy up change-consensus-key --- apps/src/lib/client/tx.rs | 38 +++++++++++++++----------------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 5f166198c6..8f5c61c001 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -13,6 +13,7 @@ use namada::core::ledger::governance::cli::offline::{ use namada::core::ledger::governance::cli::onchain::{ DefaultProposal, PgfFundingProposal, PgfStewardProposal, ProposalVote, }; +use namada::core::ledger::storage::EPOCH_SWITCH_BLOCKS_DELAY; use namada::ibc::apps::transfer::types::Memo; use namada::proto::{CompressedSignature, Section, Signer, Tx}; use namada::types::address::{Address, ImplicitAddress}; @@ -355,12 +356,8 @@ pub async fn submit_change_consensus_key( ..tx_args.clone() }; - // TODO: do I need to get the validator alias from somewhere, if it exists? - // // Don't think I should generate a new one... Should get the alias - // for the consensus key though... - - let wallet = namada.wallet().await; - + // Determine the alias for the new key + let mut wallet = namada.wallet_mut().await; let alias = wallet.find_alias(&validator).cloned(); let base_consensus_key_alias = alias .map(|al| validator_consensus_key(&al)) @@ -375,10 +372,9 @@ pub async fn submit_change_consensus_key( consensus_key_alias = format!("{base_consensus_key_alias}-{key_counter}"); } - drop(wallet); - let mut wallet = namada.wallet_mut().await; - let consensus_key = consensus_key + // Check the given key or generate a new one + let new_key = consensus_key .map(|key| match key { common::PublicKey::Ed25519(_) => key, common::PublicKey::Secp256k1(_) => { @@ -412,9 +408,8 @@ pub async fn submit_change_consensus_key( // Check that the new consensus key is unique let consensus_keys = rpc::query_consensus_keys(namada.client()).await; - let new_ck = consensus_key; - if consensus_keys.contains(&new_ck) { - edisplay_line!(namada.io(), "Consensus key can only be ed25519"); + if consensus_keys.contains(&new_key) { + edisplay_line!(namada.io(), "The consensus key is already being used."); safe_exit(1) } @@ -428,7 +423,7 @@ pub async fn submit_change_consensus_key( let data = ConsensusKeyChange { validator: validator.clone(), - consensus_key: new_ck.clone(), + consensus_key: new_key.clone(), }; tx.add_code_from_hash( @@ -438,7 +433,7 @@ pub async fn submit_change_consensus_key( .add_data(data); let signing_data = - init_validator_signing_data(namada, &tx_args, vec![new_ck]).await?; + init_validator_signing_data(namada, &tx_args, vec![new_key]).await?; tx::prepare_tx( namada, @@ -464,17 +459,14 @@ pub async fn submit_change_consensus_key( .save() .unwrap_or_else(|err| edisplay_line!(namada.io(), "{}", err)); - // let tendermint_home = config.ledger.cometbft_dir(); - // tendermint_node::write_validator_key( - // &tendermint_home, - // &consensus_key, - // ); - // tendermint_node::write_validator_state(tendermint_home); - display_line!( namada.io(), - " Consensus key \"{}\"", - consensus_key_alias + "New consensus key stored with alias \ + \"{consensus_key_alias}\". It will become active \ + {EPOCH_SWITCH_BLOCKS_DELAY} blocks before pipeline offset \ + from the current epoch, at which point you'll need to give \ + the new key to CometBFT in order to be able to sign with it \ + in consensus.", ); } else { display_line!( From de2e1761bf04fc9193035087c90101a90d9e60b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 8 Dec 2023 15:03:15 +0000 Subject: [PATCH 140/216] changelog: add #2218 --- .../unreleased/testing/2218-update-consensus-key-change.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/testing/2218-update-consensus-key-change.md diff --git a/.changelog/unreleased/testing/2218-update-consensus-key-change.md b/.changelog/unreleased/testing/2218-update-consensus-key-change.md new file mode 100644 index 0000000000..6c6daba7b8 --- /dev/null +++ b/.changelog/unreleased/testing/2218-update-consensus-key-change.md @@ -0,0 +1,2 @@ +- Added e2e test for change-consensus-key command. + ([\#2218](https://github.com/anoma/namada/pull/2218)) \ No newline at end of file From dd28da14cefd6806ebfe71eb6a8ba8c894d975cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 14 Dec 2023 10:42:19 +0000 Subject: [PATCH 141/216] benches/tx: fix change_consensus_key by adding the extra req sig --- benches/txs.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/benches/txs.rs b/benches/txs.rs index 454b50f401..e19c79de4d 100644 --- a/benches/txs.rs +++ b/benches/txs.rs @@ -686,21 +686,21 @@ fn change_validator_commission(c: &mut Criterion) { fn change_consensus_key(c: &mut Criterion) { let mut csprng = rand::rngs::OsRng {}; - let consensus_key = ed25519::SigScheme::generate(&mut csprng) + let consensus_sk = ed25519::SigScheme::generate(&mut csprng) .try_to_sk::() - .unwrap() - .to_public(); + .unwrap(); + let consensus_pk = consensus_sk.to_public(); let shell = BenchShell::default(); let signed_tx = shell.generate_tx( TX_CHANGE_CONSENSUS_KEY_WASM, ConsensusKeyChange { validator: defaults::validator_address(), - consensus_key, + consensus_key: consensus_pk, }, None, None, - vec![&defaults::validator_keypair()], + vec![&defaults::validator_keypair(), &consensus_sk], ); c.bench_function("change_consensus_key", |b| { From 936a95db517515ed664755909594711b1d60d873 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 11 Dec 2023 17:11:44 +0000 Subject: [PATCH 142/216] sdk/tx: remove unused `ProcessTxResponse` case --- sdk/src/tx.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/sdk/src/tx.rs b/sdk/src/tx.rs index d99a9c6f44..a3c36fc116 100644 --- a/sdk/src/tx.rs +++ b/sdk/src/tx.rs @@ -129,8 +129,6 @@ pub enum ProcessTxResponse { Broadcast(Response), /// Result of dry running transaction DryRun, - /// Dump transaction to disk - Dump, } impl ProcessTxResponse { From e18787cf322cdea324a96661e5acbf319454b078 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 11 Dec 2023 17:13:00 +0000 Subject: [PATCH 143/216] refactor tx response and result --- apps/src/lib/client/rpc.rs | 41 ++--- .../lib/node/ledger/shell/finalize_block.rs | 3 +- core/src/ledger/gas.rs | 12 +- core/src/types/ibc.rs | 10 +- core/src/types/transaction/mod.rs | 57 +++++-- sdk/src/rpc.rs | 55 ++++++- sdk/src/tx.rs | 143 +++++++++++++----- 7 files changed, 230 insertions(+), 91 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 43acdc9302..b71703e627 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -53,6 +53,7 @@ use namada_sdk::proof_of_stake::types::ValidatorMetaData; use namada_sdk::rpc::{ self, enriched_bonds_and_unbonds, query_epoch, TxResponse, }; +use namada_sdk::tx::{display_inner_resp, display_wrapper_resp_and_get_result}; use namada_sdk::wallet::AddressVpType; use namada_sdk::{display, display_line, edisplay_line, error, prompt, Namada}; use tokio::time::Instant; @@ -2276,22 +2277,6 @@ pub async fn query_find_validator( } } -/// Dry run a transaction -pub async fn dry_run_tx( - context: &N, - tx_bytes: Vec, -) -> Result<(), error::Error> -where - ::Error: std::fmt::Display, -{ - display_line!( - context.io(), - "Dry-run result: {}", - rpc::dry_run_tx(context, tx_bytes).await? - ); - Ok(()) -} - /// Get account's public key stored in its storage sub-space pub async fn get_public_key( client: &C, @@ -2511,32 +2496,26 @@ pub async fn query_tx_response( /// blockchain. pub async fn query_result(context: &impl Namada, args: args::QueryResult) { // First try looking up application event pertaining to given hash. - let tx_response = query_tx_response( + let inner_resp = query_tx_response( context.client(), namada_sdk::rpc::TxEventQuery::Applied(&args.tx_hash), ) .await; - match tx_response { - Ok(result) => { - display_line!( - context.io(), - "Transaction was applied with result: {}", - serde_json::to_string_pretty(&result).unwrap() - ) + match inner_resp { + Ok(resp) => { + display_inner_resp(context, &resp); } Err(err1) => { // If this fails then instead look for an acceptance event. - let tx_response = query_tx_response( + let wrapper_resp = query_tx_response( context.client(), namada_sdk::rpc::TxEventQuery::Accepted(&args.tx_hash), ) .await; - match tx_response { - Ok(result) => display_line!( - context.io(), - "Transaction was accepted with result: {}", - serde_json::to_string_pretty(&result).unwrap() - ), + match wrapper_resp { + Ok(resp) => { + display_wrapper_resp_and_get_result(context, &resp); + } Err(err2) => { // Print the errors that caused the lookups to fail edisplay_line!(context.io(), "{}\n{}", err1, err2); diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 239b7b35da..3547c8c9b3 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -491,7 +491,8 @@ where tx_event["code"] = ErrorCodes::InvalidTx.into(); } tx_event["gas_used"] = result.gas_used.to_string(); - tx_event["info"] = result.to_string(); + tx_event["info"] = "Check inner_tx for result.".to_string(); + tx_event["inner_tx"] = result.to_string(); } Err(msg) => { tracing::info!( diff --git a/core/src/ledger/gas.rs b/core/src/ledger/gas.rs index fc09636766..4f4d2a39e1 100644 --- a/core/src/ledger/gas.rs +++ b/core/src/ledger/gas.rs @@ -5,6 +5,7 @@ use std::fmt::Display; use std::ops::Div; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use serde::{Deserialize, Serialize}; use thiserror::Error; use super::parameters; @@ -89,6 +90,8 @@ pub fn get_max_block_gas( BorshDeserialize, BorshSerialize, BorshSchema, + Serialize, + Deserialize, )] pub struct Gas { sub: u64, @@ -223,7 +226,14 @@ pub struct VpGasMeter { /// Gas meter for VPs parallel runs #[derive( - Clone, Debug, Default, BorshSerialize, BorshDeserialize, BorshSchema, + Clone, + Debug, + Default, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, )] pub struct VpsGas { max: Gas, diff --git a/core/src/types/ibc.rs b/core/src/types/ibc.rs index 2f04e11709..2bda4c3d65 100644 --- a/core/src/types/ibc.rs +++ b/core/src/types/ibc.rs @@ -60,7 +60,15 @@ impl FromStr for IbcTokenHash { /// Wrapped IbcEvent #[derive( - Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema, PartialEq, Eq, + Debug, + Clone, + BorshSerialize, + BorshDeserialize, + BorshSchema, + PartialEq, + Eq, + Serialize, + Deserialize, )] pub struct IbcEvent { /// The IBC event type diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index 1ab89ba61f..0688cea6b2 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -18,6 +18,7 @@ pub mod wrapper; use std::collections::BTreeSet; use std::fmt; +use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use borsh_ext::BorshSerializeExt; @@ -41,7 +42,15 @@ pub fn hash_tx(tx_bytes: &[u8]) -> Hash { /// Transaction application result // TODO derive BorshSchema after -#[derive(Clone, Debug, Default, BorshSerialize, BorshDeserialize)] +#[derive( + Clone, + Debug, + Default, + BorshSerialize, + BorshDeserialize, + Serialize, + Deserialize, +)] pub struct TxResult { /// Total gas used by the transaction (includes the gas used by VPs) pub gas_used: Gas, @@ -64,7 +73,15 @@ impl TxResult { /// Result of checking a transaction with validity predicates // TODO derive BorshSchema after -#[derive(Clone, Debug, Default, BorshSerialize, BorshDeserialize)] +#[derive( + Clone, + Debug, + Default, + BorshSerialize, + BorshDeserialize, + Serialize, + Deserialize, +)] pub struct VpsResult { /// The addresses whose VPs accepted the transaction pub accepted_vps: BTreeSet
, @@ -80,18 +97,30 @@ pub struct VpsResult { impl fmt::Display for TxResult { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "Transaction is {}. Gas used: {};{} VPs result: {}", - if self.is_accepted() { - "valid" - } else { - "invalid" - }, - self.gas_used, - iterable_to_string("Changed keys", self.changed_keys.iter()), - self.vps_result, - ) + if f.alternate() { + write!( + f, + "Transaction is {}. Gas used: {};{} VPs result: {}", + if self.is_accepted() { + "valid" + } else { + "invalid" + }, + self.gas_used, + iterable_to_string("Changed keys", self.changed_keys.iter()), + self.vps_result, + ) + } else { + write!(f, "{}", serde_json::to_string(self).unwrap()) + } + } +} + +impl FromStr for TxResult { + type Err = serde_json::Error; + + fn from_str(s: &str) -> Result { + serde_json::from_str(s) } } diff --git a/sdk/src/rpc.rs b/sdk/src/rpc.rs index 6dc6850193..7210d06d03 100644 --- a/sdk/src/rpc.rs +++ b/sdk/src/rpc.rs @@ -3,6 +3,7 @@ use std::cell::Cell; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::ops::ControlFlow; +use std::str::FromStr; use borsh::BorshDeserialize; use masp_primitives::asset_type::AssetType; @@ -25,6 +26,7 @@ use namada_core::types::storage::{ use namada_core::types::token::{ Amount, DenominatedAmount, Denomination, MaspDenom, }; +use namada_core::types::transaction::TxResult; use namada_core::types::{storage, token}; use namada_proof_of_stake::parameters::PosParams; use namada_proof_of_stake::types::{ @@ -483,7 +485,20 @@ pub async fn dry_run_tx( .await, )? .data; - display_line!(context.io(), "Dry-run result: {}", result); + let result_str = if result.is_accepted() { + format!( + "Transaction was successfully applied. Used {} gas.", + result.gas_used + ) + } else { + format!( + "Transaction was rejected by VPs: {}.\nChanged key: {}", + serde_json::to_string_pretty(&result.vps_result.rejected_vps) + .unwrap(), + serde_json::to_string_pretty(&result.changed_keys).unwrap(), + ) + }; + display_line!(context.io(), "Dry-run result: {result_str}"); Ok(result) } @@ -511,7 +526,9 @@ pub enum TxBroadcastData { /// A parsed event from tendermint relating to a transaction #[derive(Debug, Serialize)] pub struct TxResponse { - /// Response information + /// Result of inner tx (wasm), if any + pub inner_tx: Option, + /// Response additional information pub info: String, /// Response log pub log: String, @@ -527,6 +544,16 @@ pub struct TxResponse { pub initialized_accounts: Vec
, } +/// Determines a result of an inner tx from [`TxResponse::inner_tx_result`]. +pub enum InnerTxResult<'a> { + /// Tx is applied and accepted by all VPs + Success(&'a TxResult), + /// Some VPs rejected the tx + VpsRejected(&'a TxResult), + /// Transaction failed in some other way + OtherFailure, +} + impl TryFrom for TxResponse { type Error = String; @@ -535,6 +562,9 @@ impl TryFrom for TxResponse { format!("Field \"{field}\" not present in event") } + let inner_tx = event + .get("inner_tx") + .map(|s| TxResult::from_str(s).unwrap()); let hash = event .get("hash") .ok_or_else(|| missing_field_err("hash"))? @@ -570,8 +600,9 @@ impl TryFrom for TxResponse { })?; Ok(TxResponse { - hash, + inner_tx, info, + hash, log, height, code, @@ -588,6 +619,20 @@ impl TxResponse { panic!("Error fetching TxResponse: {err}"); }) } + + /// Check the result of the inner tx. This should not be used with wrapper + /// txs. + pub fn inner_tx_result(&self) -> InnerTxResult<'_> { + if let Some(tx) = self.inner_tx.as_ref() { + if tx.is_accepted() { + InnerTxResult::Success(tx) + } else { + InnerTxResult::VpsRejected(tx) + } + } else { + InnerTxResult::OtherFailure + } + } } /// Lookup the full response accompanying the specified transaction event @@ -646,7 +691,11 @@ pub async fn query_tx_response( .map(|tag| (tag.key.as_ref(), tag.value.as_ref())) .collect(); // Summarize the transaction results that we were searching for + let inner_tx = event_map + .get("inner_tx") + .map(|s| TxResult::from_str(s).unwrap()); let result = TxResponse { + inner_tx, info: event_map["info"].to_string(), log: event_map["log"].to_string(), height: event_map["height"].to_string(), diff --git a/sdk/src/tx.rs b/sdk/src/tx.rs index a3c36fc116..ae5d7a1d47 100644 --- a/sdk/src/tx.rs +++ b/sdk/src/tx.rs @@ -45,7 +45,7 @@ use namada_core::types::transaction::governance::{ InitProposalData, VoteProposalData, }; use namada_core::types::transaction::pgf::UpdateStewardCommission; -use namada_core::types::transaction::pos; +use namada_core::types::transaction::{pos, TxResult}; use namada_core::types::{storage, token}; use namada_proof_of_stake::parameters::PosParams; use namada_proof_of_stake::types::{CommissionPair, ValidatorState}; @@ -59,7 +59,8 @@ use crate::masp::{make_asset_type, ShieldedContext, ShieldedTransfer}; use crate::proto::{MaspBuilder, Tx}; use crate::queries::Client; use crate::rpc::{ - self, query_wasm_code_hash, validate_amount, TxBroadcastData, TxResponse, + self, query_wasm_code_hash, validate_amount, InnerTxResult, + TxBroadcastData, TxResponse, }; use crate::signing::{self, SigningTxData, TxSourcePostBalance}; use crate::tendermint_rpc::endpoint::broadcast::tx_sync::Response; @@ -128,7 +129,7 @@ pub enum ProcessTxResponse { /// Result of submitting a transaction to the mempool Broadcast(Response), /// Result of dry running transaction - DryRun, + DryRun(TxResult), } impl ProcessTxResponse { @@ -139,6 +140,17 @@ impl ProcessTxResponse { _ => vec![], } } + + // Is the transaction applied and was it accepted by all VPs? Note that this + // always returns false for dry-run transactions. + pub fn is_applied_and_valid(&self) -> bool { + match self { + ProcessTxResponse::Applied(resp) => resp.code == 0.to_string(), + ProcessTxResponse::DryRun(_) | ProcessTxResponse::Broadcast(_) => { + false + } + } + } } /// Build and dump a transaction either to file or to screen @@ -313,23 +325,18 @@ pub async fn broadcast_tx( )?; if response.code == 0.into() { - display_line!( - context.io(), - "Transaction added to mempool: {:?}", - response - ); + display_line!(context.io(), "Transaction added to mempool."); + tracing::debug!("Transaction mempool response: {:?}", response); // Print the transaction identifiers to enable the extraction of // acceptance/application results later { display_line!( context.io(), - "Wrapper transaction hash: {:?}", - wrapper_tx_hash + "Wrapper transaction hash: {wrapper_tx_hash}", ); display_line!( context.io(), - "Inner transaction hash: {:?}", - decrypted_tx_hash + "Inner transaction hash: {decrypted_tx_hash}", ); } Ok(response) @@ -377,24 +384,19 @@ pub async fn submit_tx( "Awaiting transaction approval", ); - let parsed = { + let response = { let wrapper_query = rpc::TxEventQuery::Accepted(wrapper_hash.as_str()); let event = rpc::query_tx_status(context, wrapper_query, deadline).await?; - let parsed = TxResponse::from_event(event); - let tx_to_str = |parsed| { - serde_json::to_string_pretty(parsed).map_err(|err| { - Error::from(EncodingError::Serde(err.to_string())) - }) - }; - display_line!( - context.io(), - "Transaction accepted with result: {}", - tx_to_str(&parsed)? - ); - // The transaction is now on chain. We wait for it to be decrypted - // and applied - if parsed.code == 0.to_string() { + let wrapper_resp = TxResponse::from_event(event); + + if display_wrapper_resp_and_get_result(context, &wrapper_resp) { + display_line!( + context.io(), + "Waiting for inner transaction result..." + ); + // The transaction is now on chain. We wait for it to be decrypted + // and applied // We also listen to the event emitted when the encrypted // payload makes its way onto the blockchain let decrypted_query = @@ -402,24 +404,85 @@ pub async fn submit_tx( let event = rpc::query_tx_status(context, decrypted_query, deadline) .await?; - let parsed = TxResponse::from_event(event); - display_line!( - context.io(), - "Transaction applied with result: {}", - tx_to_str(&parsed)? - ); - Ok(parsed) + let inner_resp = TxResponse::from_event(event); + + display_inner_resp(context, &inner_resp); + Ok(inner_resp) } else { - Ok(parsed) + Ok(wrapper_resp) } }; + response +} + +/// Display a result of a wrapper tx. +/// Returns true if the wrapper tx was successful. +pub fn display_wrapper_resp_and_get_result( + context: &impl Namada, + resp: &TxResponse, +) -> bool { + let result = if resp.code != 0.to_string() { + display_line!( + context.io(), + "Wrapper transaction failed with error code {}. Used {} gas.", + resp.code, + resp.gas_used, + ); + false + } else { + display_line!( + context.io(), + "Wrapper transaction accepted. Used {} gas.", + resp.gas_used, + ); + true + }; + tracing::debug!( - transaction = ?to_broadcast, - "Transaction approved", + "Full wrapper result: {}", + serde_json::to_string_pretty(resp).unwrap() ); + result +} - parsed +/// Display a result of an inner tx. +pub fn display_inner_resp(context: &impl Namada, resp: &TxResponse) { + match resp.inner_tx_result() { + InnerTxResult::Success(inner) => { + display_line!( + context.io(), + "Transaction was successfully applied. Used {} gas.", + inner.gas_used, + ); + } + InnerTxResult::VpsRejected(inner) => { + let changed_keys: Vec<_> = inner + .changed_keys + .iter() + .map(storage::Key::to_string) + .collect(); + edisplay_line!( + context.io(), + "Transaction was rejected by VPs: {}.\nChanged keys: {}", + serde_json::to_string_pretty(&inner.vps_result.rejected_vps) + .unwrap(), + serde_json::to_string_pretty(&changed_keys).unwrap(), + ); + } + InnerTxResult::OtherFailure => { + edisplay_line!( + context.io(), + "Transaction failed.\nDetails: {}", + serde_json::to_string_pretty(&resp).unwrap() + ); + } + } + + tracing::debug!( + "Full result: {}", + serde_json::to_string_pretty(&resp).unwrap() + ); } /// decode components of a masp note @@ -2650,8 +2713,8 @@ async fn expect_dry_broadcast( ) -> Result { match to_broadcast { TxBroadcastData::DryRun(tx) => { - rpc::dry_run_tx(context, tx.to_bytes()).await?; - Ok(ProcessTxResponse::DryRun) + let result = rpc::dry_run_tx(context, tx.to_bytes()).await?; + Ok(ProcessTxResponse::DryRun(result)) } TxBroadcastData::Live { tx, From 7676a861cda650d8f72e947231c4f6f42768c4d4 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 15 Dec 2023 15:56:53 -0500 Subject: [PATCH 144/216] move pos gain params to PosParams --- apps/src/lib/config/genesis.rs | 4 -- apps/src/lib/config/genesis/chain.rs | 8 +-- apps/src/lib/config/genesis/templates.rs | 12 ++--- .../lib/node/ledger/shell/finalize_block.rs | 9 ++-- apps/src/lib/node/ledger/shell/mod.rs | 2 - apps/src/lib/node/ledger/storage/mod.rs | 2 - core/src/ledger/masp_conversions.rs | 2 - core/src/ledger/parameters/mod.rs | 54 ------------------- core/src/ledger/parameters/storage.rs | 22 -------- core/src/ledger/storage/mod.rs | 2 - genesis/localnet/parameters.toml | 8 +-- genesis/starter/parameters.toml | 8 +-- proof_of_stake/src/parameters.rs | 8 +++ 13 files changed, 27 insertions(+), 114 deletions(-) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index f7da635257..4c9f013b4f 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -287,10 +287,6 @@ pub struct Parameters { pub epochs_per_year: u64, /// Maximum amount of signatures per transaction pub max_signatures_per_transaction: u8, - /// PoS gain p (read only) - pub pos_gain_p: Dec, - /// PoS gain d (read only) - pub pos_gain_d: Dec, /// PoS staked ratio (read + write for every epoch) pub staked_ratio: Dec, /// PoS inflation amount from the last epoch (read + write for every epoch) diff --git a/apps/src/lib/config/genesis/chain.rs b/apps/src/lib/config/genesis/chain.rs index d8e57f11f6..6f04281cdb 100644 --- a/apps/src/lib/config/genesis/chain.rs +++ b/apps/src/lib/config/genesis/chain.rs @@ -267,8 +267,6 @@ impl Finalized { tx_whitelist, implicit_vp, epochs_per_year, - pos_gain_p, - pos_gain_d, max_signatures_per_transaction, fee_unshielding_gas_limit, fee_unshielding_descriptions_limit, @@ -311,8 +309,6 @@ impl Finalized { tx_whitelist, implicit_vp_code_hash, epochs_per_year, - pos_gain_p, - pos_gain_d, staked_ratio, pos_inflation_amount: Amount::native_whole(pos_inflation_amount), max_proposal_bytes, @@ -350,6 +346,8 @@ impl Finalized { validator_stake_threshold, liveness_window_check, liveness_threshold, + rewards_gain_p, + rewards_gain_d, } = self.parameters.pos_params.clone(); namada::proof_of_stake::parameters::PosParams { @@ -368,6 +366,8 @@ impl Finalized { validator_stake_threshold, liveness_window_check, liveness_threshold, + rewards_gain_p, + rewards_gain_d, }, max_proposal_period: self.parameters.gov_params.max_proposal_period, } diff --git a/apps/src/lib/config/genesis/templates.rs b/apps/src/lib/config/genesis/templates.rs index 62209dd24d..e5d09da1e7 100644 --- a/apps/src/lib/config/genesis/templates.rs +++ b/apps/src/lib/config/genesis/templates.rs @@ -276,10 +276,6 @@ pub struct ChainParams { pub implicit_vp: String, /// Expected number of epochs per year pub epochs_per_year: u64, - /// PoS gain p - pub pos_gain_p: Dec, - /// PoS gain d - pub pos_gain_d: Dec, /// Maximum number of signature per transaction pub max_signatures_per_transaction: u8, /// Max gas for block @@ -307,8 +303,6 @@ impl ChainParams { tx_whitelist, implicit_vp, epochs_per_year, - pos_gain_p, - pos_gain_d, max_signatures_per_transaction, max_block_gas, fee_unshielding_gas_limit, @@ -354,8 +348,6 @@ impl ChainParams { tx_whitelist, implicit_vp, epochs_per_year, - pos_gain_p, - pos_gain_d, max_signatures_per_transaction, max_block_gas, fee_unshielding_gas_limit, @@ -410,6 +402,10 @@ pub struct PosParams { /// The minimum required activity of consensus validators, in percentage, /// over the `liveness_window_check` pub liveness_threshold: Dec, + /// PoS gain p (read only) + pub rewards_gain_p: Dec, + /// PoS gain d (read only) + pub rewards_gain_d: Dec, } #[derive( diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 239b7b35da..702262bf7d 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -645,12 +645,6 @@ where let epochs_per_year: u64 = self .read_storage_key(¶ms_storage::get_epochs_per_year_key()) .expect("Epochs per year should exist in storage"); - let pos_p_gain_nom: Dec = self - .read_storage_key(¶ms_storage::get_pos_gain_p_key()) - .expect("PoS P-gain factor should exist in storage"); - let pos_d_gain_nom: Dec = self - .read_storage_key(¶ms_storage::get_pos_gain_d_key()) - .expect("PoS D-gain factor should exist in storage"); let pos_last_staked_ratio: Dec = self .read_storage_key(¶ms_storage::get_staked_ratio_key()) @@ -658,6 +652,7 @@ where let pos_last_inflation_amount: token::Amount = self .read_storage_key(¶ms_storage::get_pos_inflation_amount_key()) .expect("PoS inflation amount should exist in storage"); + // Read from PoS storage let total_tokens: token::Amount = self .read_storage_key(&token::minted_balance_key( @@ -668,6 +663,8 @@ where read_total_stake(&self.wl_storage, ¶ms, last_epoch)?; let pos_locked_ratio_target = params.target_staked_ratio; let pos_max_inflation_rate = params.max_inflation_rate; + let pos_p_gain_nom = params.rewards_gain_p; + let pos_d_gain_nom = params.rewards_gain_d; // Run rewards PD controller let pos_controller = inflation::RewardsController { diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 8e33bf87bb..00b70bedc3 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -2157,8 +2157,6 @@ mod test_utils { implicit_vp_code_hash: Default::default(), epochs_per_year: 365, max_signatures_per_transaction: 10, - pos_gain_p: Default::default(), - pos_gain_d: Default::default(), staked_ratio: Default::default(), pos_inflation_amount: Default::default(), fee_unshielding_gas_limit: 0, diff --git a/apps/src/lib/node/ledger/storage/mod.rs b/apps/src/lib/node/ledger/storage/mod.rs index 4a02b2bcf9..58ae90ce88 100644 --- a/apps/src/lib/node/ledger/storage/mod.rs +++ b/apps/src/lib/node/ledger/storage/mod.rs @@ -162,8 +162,6 @@ mod tests { implicit_vp_code_hash: Default::default(), epochs_per_year: 365, max_signatures_per_transaction: 10, - pos_gain_p: Default::default(), - pos_gain_d: Default::default(), staked_ratio: Default::default(), pos_inflation_amount: Default::default(), fee_unshielding_gas_limit: 0, diff --git a/core/src/ledger/masp_conversions.rs b/core/src/ledger/masp_conversions.rs index ab915c22e2..f7b7c2568a 100644 --- a/core/src/ledger/masp_conversions.rs +++ b/core/src/ledger/masp_conversions.rs @@ -550,8 +550,6 @@ mod tests { implicit_vp_code_hash: Default::default(), epochs_per_year: 365, max_signatures_per_transaction: 10, - pos_gain_p: Default::default(), - pos_gain_d: Default::default(), staked_ratio: Default::default(), pos_inflation_amount: Default::default(), fee_unshielding_gas_limit: 0, diff --git a/core/src/ledger/parameters/mod.rs b/core/src/ledger/parameters/mod.rs index f8f33d9768..9b768f6d69 100644 --- a/core/src/ledger/parameters/mod.rs +++ b/core/src/ledger/parameters/mod.rs @@ -55,10 +55,6 @@ pub struct Parameters { pub epochs_per_year: u64, /// Maximum number of signature per transaction pub max_signatures_per_transaction: u8, - /// PoS gain p (read only) - pub pos_gain_p: Dec, - /// PoS gain d (read only) - pub pos_gain_d: Dec, /// PoS staked ratio (read + write for every epoch) pub staked_ratio: Dec, /// PoS inflation amount from the last epoch (read + write for every epoch) @@ -129,8 +125,6 @@ impl Parameters { implicit_vp_code_hash, epochs_per_year, max_signatures_per_transaction, - pos_gain_p, - pos_gain_d, staked_ratio, pos_inflation_amount, minimum_gas_price, @@ -208,12 +202,6 @@ impl Parameters { max_signatures_per_transaction, )?; - let pos_gain_p_key = storage::get_pos_gain_p_key(); - storage.write(&pos_gain_p_key, pos_gain_p)?; - - let pos_gain_d_key = storage::get_pos_gain_d_key(); - storage.write(&pos_gain_d_key, pos_gain_d)?; - let staked_ratio_key = storage::get_staked_ratio_key(); storage.write(&staked_ratio_key, staked_ratio)?; @@ -315,32 +303,6 @@ where storage.write(&key, value) } -/// Update the PoS P-gain parameter in storage. Returns the parameters and gas -/// cost. -pub fn update_pos_gain_p_parameter( - storage: &mut S, - value: &Dec, -) -> storage_api::Result<()> -where - S: StorageRead + StorageWrite, -{ - let key = storage::get_pos_gain_p_key(); - storage.write(&key, value) -} - -/// Update the PoS D-gain parameter in storage. Returns the parameters and gas -/// cost. -pub fn update_pos_gain_d_parameter( - storage: &mut S, - value: &Dec, -) -> storage_api::Result<()> -where - S: StorageRead + StorageWrite, -{ - let key = storage::get_pos_gain_d_key(); - storage.write(&key, value) -} - /// Update the PoS staked ratio parameter in storage. Returns the parameters and /// gas cost. pub fn update_staked_ratio_parameter( @@ -514,20 +476,6 @@ where .ok_or(ReadError::ParametersMissing) .into_storage_result()?; - // read PoS gain P - let pos_gain_p_key = storage::get_pos_gain_p_key(); - let value = storage.read(&pos_gain_p_key)?; - let pos_gain_p = value - .ok_or(ReadError::ParametersMissing) - .into_storage_result()?; - - // read PoS gain D - let pos_gain_d_key = storage::get_pos_gain_d_key(); - let value = storage.read(&pos_gain_d_key)?; - let pos_gain_d = value - .ok_or(ReadError::ParametersMissing) - .into_storage_result()?; - // read staked ratio let staked_ratio_key = storage::get_staked_ratio_key(); let value = storage.read(&staked_ratio_key)?; @@ -567,8 +515,6 @@ where implicit_vp_code_hash, epochs_per_year, max_signatures_per_transaction, - pos_gain_p, - pos_gain_d, staked_ratio, pos_inflation_amount, minimum_gas_price, diff --git a/core/src/ledger/parameters/storage.rs b/core/src/ledger/parameters/storage.rs index fd7f4fabad..c8c72ed8c9 100644 --- a/core/src/ledger/parameters/storage.rs +++ b/core/src/ledger/parameters/storage.rs @@ -26,8 +26,6 @@ struct Keys { // ======================================== // PoS parameters // ======================================== - pos_gain_d: &'static str, - pos_gain_p: &'static str, pos_inflation_amount: &'static str, staked_ratio: &'static str, // ======================================== @@ -96,16 +94,6 @@ pub fn is_epochs_per_year_key(key: &Key) -> bool { is_epochs_per_year_key_at_addr(key, &ADDRESS) } -/// Returns if the key is the pos_gain_p key. -pub fn is_pos_gain_p_key(key: &Key) -> bool { - is_pos_gain_p_key_at_addr(key, &ADDRESS) -} - -/// Returns if the key is the pos_gain_d key. -pub fn is_pos_gain_d_key(key: &Key) -> bool { - is_pos_gain_d_key_at_addr(key, &ADDRESS) -} - /// Returns if the key is the staked ratio key. pub fn is_staked_ratio_key(key: &Key) -> bool { is_staked_ratio_key_at_addr(key, &ADDRESS) @@ -166,16 +154,6 @@ pub fn get_epochs_per_year_key() -> Key { get_epochs_per_year_key_at_addr(ADDRESS) } -/// Storage key used for pos_gain_p parameter. -pub fn get_pos_gain_p_key() -> Key { - get_pos_gain_p_key_at_addr(ADDRESS) -} - -/// Storage key used for pos_gain_d parameter. -pub fn get_pos_gain_d_key() -> Key { - get_pos_gain_d_key_at_addr(ADDRESS) -} - /// Storage key used for staked ratio parameter. pub fn get_staked_ratio_key() -> Key { get_staked_ratio_key_at_addr(ADDRESS) diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index 6a51b207a8..a89496c396 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -1460,8 +1460,6 @@ mod tests { implicit_vp_code_hash: Hash::zero(), epochs_per_year: 100, max_signatures_per_transaction: 15, - pos_gain_p: Dec::new(1,1).expect("Cannot fail"), - pos_gain_d: Dec::new(1,1).expect("Cannot fail"), staked_ratio: Dec::new(1,1).expect("Cannot fail"), pos_inflation_amount: token::Amount::zero(), fee_unshielding_gas_limit: 20_000, diff --git a/genesis/localnet/parameters.toml b/genesis/localnet/parameters.toml index 266fe803fa..7a99fa34b5 100644 --- a/genesis/localnet/parameters.toml +++ b/genesis/localnet/parameters.toml @@ -17,10 +17,6 @@ tx_whitelist = [] implicit_vp = "vp_implicit" # Expected number of epochs per year (also sets the min duration of an epoch in seconds) epochs_per_year = 31_536_000 -# The P gain factor in the Proof of Stake rewards controller -pos_gain_p = "0.1" -# The D gain factor in the Proof of Stake rewards controller -pos_gain_d = "0.1" # Maximum number of signature per transaction max_signatures_per_transaction = 15 # Max gas for block @@ -72,6 +68,10 @@ liveness_window_check = 100 # The minimum required activity of consensus validators, in percentage, over # the `liveness_window_check` liveness_threshold = "0.9" +# The P gain factor in the Proof of Stake rewards controller +rewards_gain_p = "0.25" +# The D gain factor in the Proof of Stake rewards controller +rewards_gain_d = "0.25" # Governance parameters. [gov_params] diff --git a/genesis/starter/parameters.toml b/genesis/starter/parameters.toml index 543f7dbc13..926e564755 100644 --- a/genesis/starter/parameters.toml +++ b/genesis/starter/parameters.toml @@ -17,10 +17,6 @@ tx_whitelist = [] implicit_vp = "vp_implicit" # Expected number of epochs per year (also sets the min duration of an epoch in seconds) epochs_per_year = 31_536_000 -# The P gain factor in the Proof of Stake rewards controller -pos_gain_p = "0.1" -# The D gain factor in the Proof of Stake rewards controller -pos_gain_d = "0.1" # Maximum number of signature per transaction max_signatures_per_transaction = 15 # Max gas for block @@ -72,6 +68,10 @@ liveness_window_check = 10_000 # The minimum required activity of consensus validators, in percentage, over # the `liveness_window_check` liveness_threshold = "0.9" +# The P gain factor in the Proof of Stake rewards controller +rewards_gain_p = "0.25" +# The D gain factor in the Proof of Stake rewards controller +rewards_gain_d = "0.25" # Governance parameters. [gov_params] diff --git a/proof_of_stake/src/parameters.rs b/proof_of_stake/src/parameters.rs index ecacdde206..cb963d9e36 100644 --- a/proof_of_stake/src/parameters.rs +++ b/proof_of_stake/src/parameters.rs @@ -1,5 +1,7 @@ //! Proof-of-Stake system parameters +use std::str::FromStr; + use borsh::{BorshDeserialize, BorshSerialize}; use namada_core::ledger::governance::parameters::GovernanceParameters; use namada_core::types::dec::Dec; @@ -64,6 +66,10 @@ pub struct OwnedPosParams { /// The minimum required activity of consesus validators, in percentage, /// over the `liveness_window_check` pub liveness_threshold: Dec, + /// PoS gain p (read only) + pub rewards_gain_p: Dec, + /// PoS gain d (read only) + pub rewards_gain_d: Dec, } impl Default for PosParams { @@ -101,6 +107,8 @@ impl Default for OwnedPosParams { validator_stake_threshold: token::Amount::native_whole(1_u64), liveness_window_check: 10_000, liveness_threshold: Dec::new(9, 1).expect("Test failed"), + rewards_gain_p: Dec::from_str("0.25").expect("Test failed"), + rewards_gain_d: Dec::from_str("0.25").expect("Test failed"), } } } From 8f57cc70ea540c17b7f8fb1da9482b0f9bab8777 Mon Sep 17 00:00:00 2001 From: brentstone Date: Sat, 16 Dec 2023 18:38:05 -0500 Subject: [PATCH 145/216] changelog: add #2294 --- .../unreleased/improvements/2294-move-gain-params-to-pos.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/2294-move-gain-params-to-pos.md diff --git a/.changelog/unreleased/improvements/2294-move-gain-params-to-pos.md b/.changelog/unreleased/improvements/2294-move-gain-params-to-pos.md new file mode 100644 index 0000000000..54203f8ab4 --- /dev/null +++ b/.changelog/unreleased/improvements/2294-move-gain-params-to-pos.md @@ -0,0 +1,2 @@ +- Move the pos inflation gain parameters to the PosParams. + ([\#2294](https://github.com/anoma/namada/pull/2294)) \ No newline at end of file From 3c40cac21f5ab15c79aeb148aac899824557b12f Mon Sep 17 00:00:00 2001 From: brentstone Date: Sat, 16 Dec 2023 19:34:29 -0500 Subject: [PATCH 146/216] move pos and pgf inflation into respective mods --- .../lib/node/ledger/shell/finalize_block.rs | 190 ++---------------- core/src/ledger/pgf/inflation.rs | 104 ++++++++++ core/src/ledger/pgf/mod.rs | 2 + proof_of_stake/src/lib.rs | 80 ++++++++ 4 files changed, 203 insertions(+), 173 deletions(-) create mode 100644 core/src/ledger/pgf/inflation.rs diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 239b7b35da..4746e88c9b 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -1,23 +1,19 @@ //! Implementation of the `FinalizeBlock` ABCI++ method for the Shell use data_encoding::HEXUPPER; -use namada::core::ledger::inflation; use namada::core::ledger::masp_conversions::update_allowed_conversions; -use namada::core::ledger::pgf::ADDRESS as pgf_address; +use namada::core::ledger::pgf::inflation as pgf_inflation; use namada::ledger::events::EventType; use namada::ledger::gas::{GasMetering, TxGasMeter}; -use namada::ledger::parameters::storage as params_storage; -use namada::ledger::pos::{namada_proof_of_stake, staking_token_address}; +use namada::ledger::pos::namada_proof_of_stake; use namada::ledger::protocol; use namada::ledger::storage::wl_storage::WriteLogAndStorage; use namada::ledger::storage::EPOCH_SWITCH_BLOCKS_DELAY; -use namada::ledger::storage_api::token::credit_tokens; -use namada::ledger::storage_api::{pgf, StorageRead, StorageWrite}; +use namada::ledger::storage_api::StorageRead; use namada::proof_of_stake::{ find_validator_by_raw_hash, read_last_block_proposer_address, - read_pos_params, read_total_stake, write_last_block_proposer_address, + write_last_block_proposer_address, }; -use namada::types::dec::Dec; use namada::types::key::tm_raw_hash_to_string; use namada::types::storage::{BlockHash, BlockResults, Epoch, Header}; use namada::types::transaction::protocol::{ @@ -636,58 +632,6 @@ where /// with respect to the previous epoch. fn apply_inflation(&mut self, current_epoch: Epoch) -> Result<()> { let last_epoch = current_epoch.prev(); - // Get input values needed for the PD controller for PoS. - // Run the PD controllers to calculate new rates. - - let params = read_pos_params(&self.wl_storage)?; - - // Read from Parameters storage - let epochs_per_year: u64 = self - .read_storage_key(¶ms_storage::get_epochs_per_year_key()) - .expect("Epochs per year should exist in storage"); - let pos_p_gain_nom: Dec = self - .read_storage_key(¶ms_storage::get_pos_gain_p_key()) - .expect("PoS P-gain factor should exist in storage"); - let pos_d_gain_nom: Dec = self - .read_storage_key(¶ms_storage::get_pos_gain_d_key()) - .expect("PoS D-gain factor should exist in storage"); - - let pos_last_staked_ratio: Dec = self - .read_storage_key(¶ms_storage::get_staked_ratio_key()) - .expect("PoS staked ratio should exist in storage"); - let pos_last_inflation_amount: token::Amount = self - .read_storage_key(¶ms_storage::get_pos_inflation_amount_key()) - .expect("PoS inflation amount should exist in storage"); - // Read from PoS storage - let total_tokens: token::Amount = self - .read_storage_key(&token::minted_balance_key( - &staking_token_address(&self.wl_storage), - )) - .expect("Total NAM balance should exist in storage"); - let pos_locked_supply = - read_total_stake(&self.wl_storage, ¶ms, last_epoch)?; - let pos_locked_ratio_target = params.target_staked_ratio; - let pos_max_inflation_rate = params.max_inflation_rate; - - // Run rewards PD controller - let pos_controller = inflation::RewardsController { - locked_tokens: pos_locked_supply.raw_amount(), - total_tokens: total_tokens.raw_amount(), - total_native_tokens: total_tokens.raw_amount(), - locked_ratio_target: pos_locked_ratio_target, - locked_ratio_last: pos_last_staked_ratio, - max_reward_rate: pos_max_inflation_rate, - last_inflation_amount: pos_last_inflation_amount.raw_amount(), - p_gain_nom: pos_p_gain_nom, - d_gain_nom: pos_d_gain_nom, - epochs_per_year, - }; - - // Run the rewards controllers - let inflation::ValsToUpdate { - locked_ratio, - inflation, - } = pos_controller.run(); // Get the number of blocks in the last epoch let first_block_of_last_epoch = self @@ -700,116 +644,15 @@ where let num_blocks_in_last_epoch = self.wl_storage.storage.block.height.0 - first_block_of_last_epoch; - let staking_token = staking_token_address(&self.wl_storage); - - let inflation = token::Amount::from_uint(inflation, 0) - .expect("Should not fail Uint -> Amount conversion"); - namada_proof_of_stake::update_rewards_products_and_mint_inflation( + // PoS inflation + namada_proof_of_stake::apply_inflation( &mut self.wl_storage, - ¶ms, last_epoch, num_blocks_in_last_epoch, - inflation, - &staking_token, - ) - .expect( - "Must be able to update PoS rewards products and mint inflation", - ); - - // Write new rewards parameters that will be used for the inflation of - // the current new epoch - self.wl_storage - .write(¶ms_storage::get_pos_inflation_amount_key(), inflation) - .expect("unable to write new reward rate"); - self.wl_storage - .write(¶ms_storage::get_staked_ratio_key(), locked_ratio) - .expect("unable to write new locked ratio"); - - // Pgf inflation - let pgf_parameters = pgf::get_parameters(&self.wl_storage)?; - - let pgf_pd_rate = - pgf_parameters.pgf_inflation_rate / Dec::from(epochs_per_year); - let pgf_inflation = Dec::from(total_tokens) * pgf_pd_rate; - let pgf_inflation_amount = token::Amount::from(pgf_inflation); - - credit_tokens( - &mut self.wl_storage, - &staking_token, - &pgf_address, - pgf_inflation_amount, )?; - tracing::info!( - "Minting {} tokens for PGF rewards distribution into the PGF \ - account.", - pgf_inflation_amount.to_string_native() - ); - - let mut pgf_fundings = pgf::get_payments(&self.wl_storage)?; - // we want to pay first the oldest fundings - pgf_fundings.sort_by(|a, b| a.id.cmp(&b.id)); - - for funding in pgf_fundings { - if storage_api::token::transfer( - &mut self.wl_storage, - &staking_token, - &pgf_address, - &funding.detail.target, - funding.detail.amount, - ) - .is_ok() - { - tracing::info!( - "Paying {} tokens for {} project.", - funding.detail.amount.to_string_native(), - &funding.detail.target, - ); - } else { - tracing::warn!( - "Failed to pay {} tokens for {} project.", - funding.detail.amount.to_string_native(), - &funding.detail.target, - ); - } - } - - // Pgf steward inflation - let stewards = pgf::get_stewards(&self.wl_storage)?; - let pgf_stewards_pd_rate = - pgf_parameters.stewards_inflation_rate / Dec::from(epochs_per_year); - let pgf_steward_inflation = - Dec::from(total_tokens) * pgf_stewards_pd_rate; - - for steward in stewards { - for (address, percentage) in steward.reward_distribution { - let pgf_steward_reward = pgf_steward_inflation - .checked_mul(&percentage) - .unwrap_or_default(); - let reward_amount = token::Amount::from(pgf_steward_reward); - - if credit_tokens( - &mut self.wl_storage, - &staking_token, - &address, - reward_amount, - ) - .is_ok() - { - tracing::info!( - "Minting {} tokens for steward {}.", - reward_amount.to_string_native(), - address, - ); - } else { - tracing::warn!( - "Failed minting {} tokens for steward {}.", - reward_amount.to_string_native(), - address, - ); - } - } - } + // Pgf inflation + pgf_inflation::apply_inflation(&mut self.wl_storage)?; Ok(()) } @@ -969,14 +812,14 @@ mod test_finalize_block { }; use namada::proof_of_stake::{ enqueued_slashes_handle, get_num_consensus_validators, - read_consensus_validator_set_addresses_with_stake, + read_consensus_validator_set_addresses_with_stake, read_total_stake, read_validator_stake, rewards_accumulator_handle, unjail_validator, validator_consensus_key_handle, validator_rewards_products_handle, validator_slashes_handle, validator_state_handle, write_pos_params, ADDRESS as pos_address, }; use namada::proto::{Code, Data, Section, Signature}; - use namada::types::dec::POS_DECIMAL_PRECISION; + use namada::types::dec::{Dec, POS_DECIMAL_PRECISION}; use namada::types::ethereum_events::{EthAddress, Uint as ethUint}; use namada::types::hash::Hash; use namada::types::keccak::KeccakHash; @@ -2504,7 +2347,7 @@ mod test_finalize_block { let delegator = address::testing::gen_implicit_address(); let del_amount = init_stake; let staking_token = shell.wl_storage.storage.native_token.clone(); - credit_tokens( + storage_api::token::credit_tokens( &mut shell.wl_storage, &staking_token, &delegator, @@ -2629,21 +2472,21 @@ mod test_finalize_block { // Give the validators some tokens for txs let staking_token = shell.wl_storage.storage.native_token.clone(); - credit_tokens( + storage_api::token::credit_tokens( &mut shell.wl_storage, &staking_token, &validator1.address, init_stake, ) .unwrap(); - credit_tokens( + storage_api::token::credit_tokens( &mut shell.wl_storage, &staking_token, &validator2.address, init_stake, ) .unwrap(); - credit_tokens( + storage_api::token::credit_tokens( &mut shell.wl_storage, &staking_token, &validator3.address, @@ -4001,7 +3844,7 @@ mod test_finalize_block { let delegator = address::testing::gen_implicit_address(); let del_1_amount = token::Amount::native_whole(67_231); let staking_token = shell.wl_storage.storage.native_token.clone(); - credit_tokens( + storage_api::token::credit_tokens( &mut shell.wl_storage, &staking_token, &delegator, @@ -5270,7 +5113,8 @@ mod test_finalize_block { misbehaviors: Option>, ) -> (Epoch, token::Amount) { let current_epoch = shell.wl_storage.storage.block.epoch; - let staking_token = staking_token_address(&shell.wl_storage); + let staking_token = + namada_proof_of_stake::staking_token_address(&shell.wl_storage); // NOTE: assumed that the only change in pos address balance by // advancing to the next epoch is minted inflation - no change occurs diff --git a/core/src/ledger/pgf/inflation.rs b/core/src/ledger/pgf/inflation.rs new file mode 100644 index 0000000000..30dd4191fd --- /dev/null +++ b/core/src/ledger/pgf/inflation.rs @@ -0,0 +1,104 @@ +//! PGF lib code. + +use crate::ledger::parameters::storage as params_storage; +use crate::ledger::storage_api::pgf::{ + get_parameters, get_payments, get_stewards, +}; +use crate::ledger::storage_api::token::credit_tokens; +use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; +use crate::types::dec::Dec; +use crate::types::token; + +/// Apply the PGF inflation. +pub fn apply_inflation(storage: &mut S) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let pgf_parameters = get_parameters(storage)?; + let staking_token = storage.get_native_token()?; + + let epochs_per_year: u64 = storage + .read(¶ms_storage::get_epochs_per_year_key())? + .expect("Epochs per year should exist in storage"); + let total_tokens: token::Amount = storage + .read(&token::minted_balance_key(&staking_token))? + .expect("Total NAM balance should exist in storage"); + + let pgf_pd_rate = + pgf_parameters.pgf_inflation_rate / Dec::from(epochs_per_year); + let pgf_inflation = Dec::from(total_tokens) * pgf_pd_rate; + let pgf_inflation_amount = token::Amount::from(pgf_inflation); + + credit_tokens( + storage, + &staking_token, + &super::ADDRESS, + pgf_inflation_amount, + )?; + + tracing::info!( + "Minting {} tokens for PGF rewards distribution into the PGF account.", + pgf_inflation_amount.to_string_native() + ); + + let mut pgf_fundings = get_payments(storage)?; + // we want to pay first the oldest fundings + pgf_fundings.sort_by(|a, b| a.id.cmp(&b.id)); + + for funding in pgf_fundings { + if storage_api::token::transfer( + storage, + &staking_token, + &super::ADDRESS, + &funding.detail.target, + funding.detail.amount, + ) + .is_ok() + { + tracing::info!( + "Paying {} tokens for {} project.", + funding.detail.amount.to_string_native(), + &funding.detail.target, + ); + } else { + tracing::warn!( + "Failed to pay {} tokens for {} project.", + funding.detail.amount.to_string_native(), + &funding.detail.target, + ); + } + } + + // Pgf steward inflation + let stewards = get_stewards(storage)?; + let pgf_stewards_pd_rate = + pgf_parameters.stewards_inflation_rate / Dec::from(epochs_per_year); + let pgf_steward_inflation = Dec::from(total_tokens) * pgf_stewards_pd_rate; + + for steward in stewards { + for (address, percentage) in steward.reward_distribution { + let pgf_steward_reward = pgf_steward_inflation + .checked_mul(&percentage) + .unwrap_or_default(); + let reward_amount = token::Amount::from(pgf_steward_reward); + + if credit_tokens(storage, &staking_token, &address, reward_amount) + .is_ok() + { + tracing::info!( + "Minting {} tokens for steward {}.", + reward_amount.to_string_native(), + address, + ); + } else { + tracing::warn!( + "Failed minting {} tokens for steward {}.", + reward_amount.to_string_native(), + address, + ); + } + } + } + + Ok(()) +} diff --git a/core/src/ledger/pgf/mod.rs b/core/src/ledger/pgf/mod.rs index b7f8e3cecb..5d7d7eef24 100644 --- a/core/src/ledger/pgf/mod.rs +++ b/core/src/ledger/pgf/mod.rs @@ -4,6 +4,8 @@ use crate::types::address::{Address, InternalAddress}; /// Pgf CLI pub mod cli; +/// Pgf inflation code +pub mod inflation; /// Pgf parameters pub mod parameters; /// Pgf storage diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 3d79a345e9..3aa632b4f2 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -24,6 +24,8 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use borsh::BorshDeserialize; pub use error::*; +use namada_core::ledger::inflation; +use namada_core::ledger::parameters::storage as params_storage; use namada_core::ledger::storage_api::collections::lazy_map::{ Collectable, LazyMap, NestedMap, NestedSubKey, SubKey, }; @@ -6466,3 +6468,81 @@ where } Ok(()) } + +/// Apply inflation to the Proof of Stake system. +pub fn apply_inflation( + storage: &mut S, + last_epoch: Epoch, + num_blocks_in_last_epoch: u64, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + // Read from Parameters storage + let epochs_per_year: u64 = storage + .read(¶ms_storage::get_epochs_per_year_key())? + .expect("Epochs per year should exist in storage"); + let pos_p_gain_nom: Dec = storage + .read(¶ms_storage::get_pos_gain_p_key())? + .expect("PoS P-gain factor should exist in storage"); + let pos_d_gain_nom: Dec = storage + .read(¶ms_storage::get_pos_gain_d_key())? + .expect("PoS D-gain factor should exist in storage"); + + let pos_last_staked_ratio: Dec = storage + .read(¶ms_storage::get_staked_ratio_key())? + .expect("PoS staked ratio should exist in storage"); + let pos_last_inflation_amount: token::Amount = storage + .read(¶ms_storage::get_pos_inflation_amount_key())? + .expect("PoS inflation amount should exist in storage"); + + // Read from PoS storage + let params = read_pos_params(storage)?; + let staking_token = staking_token_address(storage); + + let total_tokens: token::Amount = storage + .read(&token::minted_balance_key(&staking_token))? + .expect("Total NAM balance should exist in storage"); + let pos_locked_supply = read_total_stake(storage, ¶ms, last_epoch)?; + let pos_locked_ratio_target = params.target_staked_ratio; + let pos_max_inflation_rate = params.max_inflation_rate; + + // Run rewards PD controller + let pos_controller = inflation::RewardsController { + locked_tokens: pos_locked_supply.raw_amount(), + total_tokens: total_tokens.raw_amount(), + total_native_tokens: total_tokens.raw_amount(), + locked_ratio_target: pos_locked_ratio_target, + locked_ratio_last: pos_last_staked_ratio, + max_reward_rate: pos_max_inflation_rate, + last_inflation_amount: pos_last_inflation_amount.raw_amount(), + p_gain_nom: pos_p_gain_nom, + d_gain_nom: pos_d_gain_nom, + epochs_per_year, + }; + // Run the rewards controllers + let inflation::ValsToUpdate { + locked_ratio, + inflation, + } = pos_controller.run(); + + let inflation = + token::Amount::from_uint(inflation, 0).into_storage_result()?; + + update_rewards_products_and_mint_inflation( + storage, + ¶ms, + last_epoch, + num_blocks_in_last_epoch, + inflation, + &staking_token, + )?; + + // Write new rewards parameters that will be used for the inflation of + // the current new epoch + storage + .write(¶ms_storage::get_pos_inflation_amount_key(), inflation)?; + storage.write(¶ms_storage::get_staked_ratio_key(), locked_ratio)?; + + Ok(()) +} From ccf9a86c103ac218f5954328c2d87395a03f6b1f Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 18 Dec 2023 10:56:07 +0100 Subject: [PATCH 147/216] [feat]: Added an rpc endpoint to query which tokens can earn MASP rewards while shielded --- apps/src/lib/cli.rs | 27 +++++++++++++++++++++++++++ apps/src/lib/cli/client.rs | 10 ++++++++++ apps/src/lib/client/rpc.rs | 11 +++++++++++ sdk/src/queries/shell.rs | 15 +++++++++++++++ sdk/src/rpc.rs | 7 +++++++ 5 files changed, 70 insertions(+) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index c04f5a531b..68257787d1 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -246,6 +246,7 @@ pub mod cmds { .subcommand(QueryAccount::def().display_order(5)) .subcommand(QueryTransfers::def().display_order(5)) .subcommand(QueryConversions::def().display_order(5)) + .subcommand(QueryMaspRewardTokens::def().display_order(5)) .subcommand(QueryBlock::def().display_order(5)) .subcommand(QueryBalance::def().display_order(5)) .subcommand(QueryBonds::def().display_order(5)) @@ -313,6 +314,8 @@ pub mod cmds { let query_transfers = Self::parse_with_ctx(matches, QueryTransfers); let query_conversions = Self::parse_with_ctx(matches, QueryConversions); + let query_masp_reward_tokens = + Self::parse_with_ctx(matches, QueryMaspRewardTokens); let query_block = Self::parse_with_ctx(matches, QueryBlock); let query_balance = Self::parse_with_ctx(matches, QueryBalance); let query_bonds = Self::parse_with_ctx(matches, QueryBonds); @@ -370,6 +373,7 @@ pub mod cmds { .or(query_epoch) .or(query_transfers) .or(query_conversions) + .or(query_masp_reward_tokens) .or(query_block) .or(query_balance) .or(query_bonds) @@ -456,6 +460,7 @@ pub mod cmds { QueryAccount(QueryAccount), QueryTransfers(QueryTransfers), QueryConversions(QueryConversions), + QueryMaspRewardTokens(QueryMaspRewardTokens), QueryBlock(QueryBlock), QueryBalance(QueryBalance), QueryBonds(QueryBonds), @@ -1670,6 +1675,28 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct QueryMaspRewardTokens(pub args::Query); + + impl SubCmd for QueryMaspRewardTokens { + const CMD: &'static str = "masp-reward-tokens"; + + fn parse(matches: &ArgMatches) -> Option { + matches.subcommand_matches(Self::CMD).map(|matches| { + QueryMaspRewardTokens(args::Query::parse(matches)) + }) + } + + fn def() -> App { + App::new(Self::CMD) + .about( + "Query the tokens which can earn MASP rewards while \ + shielded.", + ) + .add_args::>() + } + } + #[derive(Clone, Debug)] pub struct QueryBlock(pub args::Query); diff --git a/apps/src/lib/cli/client.rs b/apps/src/lib/cli/client.rs index 9602f80cdd..73728898c8 100644 --- a/apps/src/lib/cli/client.rs +++ b/apps/src/lib/cli/client.rs @@ -416,6 +416,16 @@ impl CliApi { let namada = ctx.to_sdk(client, io); rpc::query_conversions(&namada, args).await; } + Sub::QueryMaspRewardTokens(QueryMaspRewardTokens( + mut args, + )) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address(&mut args.ledger_address) + }); + client.wait_until_node_is_synced(&io).await?; + let namada = ctx.to_sdk(client, io); + rpc::query_masp_reward_tokens(&namada).await; + } Sub::QueryBlock(QueryBlock(mut args)) => { let client = client.unwrap_or_else(|| { C::from_tendermint_address(&mut args.ledger_address) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 43acdc9302..343a8c9bf6 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -2428,6 +2428,17 @@ pub async fn query_conversion( namada_sdk::rpc::query_conversion(client, asset_type).await } +/// Query to read the tokens that earn masp rewards. +pub async fn query_masp_reward_tokens(context: &impl Namada) { + let tokens = namada_sdk::rpc::query_masp_reward_tokens(context.client()) + .await + .expect("The tokens that may earn MASP rewards should be defined"); + display_line!(context.io(), "The following tokens may ear MASP rewards:"); + for (alias, address) in tokens { + display_line!(context.io(), "{}: {}", alias, address); + } +} + /// Query a wasm code hash pub async fn query_wasm_code_hash( context: &impl Namada, diff --git a/sdk/src/queries/shell.rs b/sdk/src/queries/shell.rs index 23f7441cae..813348dacb 100644 --- a/sdk/src/queries/shell.rs +++ b/sdk/src/queries/shell.rs @@ -84,6 +84,10 @@ router! {SHELL, // Conversion state access - read conversion ( "conversions" ) -> BTreeMap = read_conversions, + + // Conversion state access - read conversion + ( "masp_reward_tokens" ) -> BTreeMap = masp_reward_tokens, + // Block results access - read bit-vec ( "results" ) -> Vec = read_results, @@ -210,6 +214,17 @@ where } } +/// Query to read the tokens that earn masp rewards. +fn masp_reward_tokens( + ctx: RequestCtx<'_, D, H, V, T>, +) -> storage_api::Result> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + Ok(ctx.wl_storage.storage.conversion_state.tokens.clone()) +} + fn epoch( ctx: RequestCtx<'_, D, H, V, T>, ) -> storage_api::Result diff --git a/sdk/src/rpc.rs b/sdk/src/rpc.rs index 6dc6850193..fd46ac2642 100644 --- a/sdk/src/rpc.rs +++ b/sdk/src/rpc.rs @@ -283,6 +283,13 @@ pub async fn query_conversions( convert_response::(RPC.shell().read_conversions(client).await) } +/// Query to read the tokens that earn masp rewards. +pub async fn query_masp_reward_tokens( + client: &C, +) -> Result, Error> { + convert_response::(RPC.shell().masp_reward_tokens(client).await) +} + /// Query a wasm code hash pub async fn query_wasm_code_hash( context: &impl Namada, From 124cffdf3c8c8fbb09badc66c08c2549a15aedab Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 18 Dec 2023 12:22:53 -0500 Subject: [PATCH 148/216] changelog: add #2295 --- .../unreleased/improvements/2295-refactor-apply-inflation.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/2295-refactor-apply-inflation.md diff --git a/.changelog/unreleased/improvements/2295-refactor-apply-inflation.md b/.changelog/unreleased/improvements/2295-refactor-apply-inflation.md new file mode 100644 index 0000000000..95990a60a2 --- /dev/null +++ b/.changelog/unreleased/improvements/2295-refactor-apply-inflation.md @@ -0,0 +1,2 @@ +- Move the inflation code for PoS and PGF into their own native modules. + ([\#2295](https://github.com/anoma/namada/pull/2295)) \ No newline at end of file From d0e26643816c7da680dac1ed54827faf2f79f958 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 19 Dec 2023 11:42:23 +0100 Subject: [PATCH 149/216] [fix]: Make the ledger wait for genesis before starting up any processes --- apps/src/lib/node/ledger/mod.rs | 34 +++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 11bd75aba5..d747ed1a7d 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -17,6 +17,8 @@ use futures::future::TryFutureExt; use namada::core::ledger::governance::storage::keys as governance_storage; use namada::eth_bridge::ethers::providers::{Http, Provider}; use namada::types::storage::Key; +use namada::types::time::{DateTimeUtc, Utc}; +use namada_sdk::control_flow::install_shutdown_signal; use namada_sdk::tendermint::abci::request::CheckTxKind; use once_cell::unsync::Lazy; use sysinfo::{RefreshKind, System, SystemExt}; @@ -249,6 +251,10 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { // Start Tendermint node let tendermint_node = start_tendermint(&mut spawner, &config); + // wait for genesis time + let genesis_time = DateTimeUtc::try_from(config.genesis_time.clone()) + .expect("Should be able to parse genesis time"); + sleep_until(genesis_time).await; // Start oracle if necessary let (eth_oracle_channels, eth_oracle) = @@ -748,3 +754,31 @@ async fn maybe_start_ethereum_oracle( fn spawn_dummy_task(ready: T) -> task::JoinHandle { tokio::spawn(async { std::future::ready(ready).await }) } + +/// Sleep until the genesis time if necessary. +async fn sleep_until(time: DateTimeUtc) { + let shutdown_signal = install_shutdown_signal(); + // Sleep until start time if needed + let sleep = async { + if let Ok(sleep_time) = + time.0.signed_duration_since(Utc::now()).to_std() + { + if !sleep_time.is_zero() { + tracing::info!( + "Waiting for ledger genesis time: {:?}, time left: {:?}", + time, + sleep_time + ); + tokio::time::sleep(sleep_time).await + } + } + }; + tokio::select! { + _ = shutdown_signal => { + tracing::info!("Shutdown signal receiving, shutting down"); + } + _ = sleep => { + tracing::info!("Genesis time reached, starting ledger"); + } + }; +} From b658053cd1e57cd49b2a77bd1c63fd934b00fbf1 Mon Sep 17 00:00:00 2001 From: yito88 Date: Tue, 19 Dec 2023 11:44:23 +0100 Subject: [PATCH 150/216] non-namada token to ibc-gen-shielded --- apps/src/lib/cli.rs | 5 +++-- sdk/src/args.rs | 4 ++-- sdk/src/rpc.rs | 31 ++++++++++++++++++++++++++----- sdk/src/tx.rs | 3 ++- 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index c04f5a531b..fc38f4655b 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -3164,6 +3164,7 @@ pub mod args { pub const TM_ADDRESS: Arg = arg("tm-address"); pub const TOKEN_OPT: ArgOpt = TOKEN.opt(); pub const TOKEN: Arg = arg("token"); + pub const TOKEN_STR: Arg = arg("token"); pub const TRANSFER_SOURCE: Arg = arg("source"); pub const TRANSFER_TARGET: Arg = arg("target"); pub const TX_HASH: Arg = arg("tx-hash"); @@ -5654,7 +5655,7 @@ pub mod args { query, output_folder: self.output_folder, target: chain_ctx.get(&self.target), - token: chain_ctx.get(&self.token), + token: self.token, amount: self.amount, port_id: self.port_id, channel_id: self.channel_id, @@ -5667,7 +5668,7 @@ pub mod args { let query = Query::parse(matches); let output_folder = OUTPUT_FOLDER_PATH.parse(matches); let target = TRANSFER_TARGET.parse(matches); - let token = TOKEN.parse(matches); + let token = TOKEN_STR.parse(matches); let amount = InputAmount::Unvalidated(AMOUNT.parse(matches)); let port_id = PORT_ID.parse(matches); let channel_id = CHANNEL_ID.parse(matches); diff --git a/sdk/src/args.rs b/sdk/src/args.rs index 6599b0e9fb..919741db5d 100644 --- a/sdk/src/args.rs +++ b/sdk/src/args.rs @@ -2418,8 +2418,8 @@ pub struct GenIbcShieldedTransafer { pub output_folder: Option, /// The target address pub target: C::TransferTarget, - /// The token address - pub token: C::Address, + /// The token address which could be a non-namada address + pub token: String, /// Transferred token amount pub amount: InputAmount, /// Port ID via which the token is received diff --git a/sdk/src/rpc.rs b/sdk/src/rpc.rs index 6dc6850193..c09f892138 100644 --- a/sdk/src/rpc.rs +++ b/sdk/src/rpc.rs @@ -1163,12 +1163,33 @@ pub async fn format_denominated_amount( /// Look up the IBC denomination from a IbcToken. pub async fn query_ibc_denom( context: &N, - token: &Address, + token: impl AsRef, owner: Option<&Address>, ) -> String { - let hash = match token { - Address::Internal(InternalAddress::IbcToken(hash)) => hash.to_string(), - _ => return token.to_string(), + let hash = match Address::decode(token.as_ref()) { + Ok(Address::Internal(InternalAddress::IbcToken(hash))) => { + hash.to_string() + } + Ok(_) => return token.as_ref().to_string(), + Err(_) => match namada_core::types::ibc::is_ibc_denom(token.as_ref()) { + Some((trace_path, base_denom)) => { + let base_token = context + .wallet() + .await + .find_address(&base_denom) + .map(|addr| addr.to_string()) + .unwrap_or(base_denom); + return format!("{trace_path}/{base_token}"); + } + None => { + return context + .wallet() + .await + .find_address(token.as_ref()) + .map(|addr| addr.to_string()) + .unwrap_or(token.as_ref().to_string()); + } + }, }; if let Some(owner) = owner { @@ -1195,5 +1216,5 @@ pub async fn query_ibc_denom( } } - token.to_string() + token.as_ref().to_string() } diff --git a/sdk/src/tx.rs b/sdk/src/tx.rs index d99a9c6f44..13372bee13 100644 --- a/sdk/src/tx.rs +++ b/sdk/src/tx.rs @@ -1974,7 +1974,8 @@ pub async fn build_ibc_transfer( .map_err(|e| Error::from(QueryError::Wasm(e.to_string())))?; let ibc_denom = - rpc::query_ibc_denom(context, &args.token, Some(&source)).await; + rpc::query_ibc_denom(context, &args.token.to_string(), Some(&source)) + .await; let token = PrefixedCoin { denom: ibc_denom.parse().expect("Invalid IBC denom"), // Set the IBC amount as an integer From 02db71413c298ce231f92c90a6b2c9906500faac Mon Sep 17 00:00:00 2001 From: yito88 Date: Tue, 19 Dec 2023 11:46:25 +0100 Subject: [PATCH 151/216] add changelog --- .changelog/unreleased/bug-fixes/2308-fix-ibc-gen-shielded.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/2308-fix-ibc-gen-shielded.md diff --git a/.changelog/unreleased/bug-fixes/2308-fix-ibc-gen-shielded.md b/.changelog/unreleased/bug-fixes/2308-fix-ibc-gen-shielded.md new file mode 100644 index 0000000000..9386818074 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/2308-fix-ibc-gen-shielded.md @@ -0,0 +1,2 @@ +- Non-Namada token can be given to ibc-gen-shielded + ([\#2308](https://github.com/anoma/namada/issues/2308)) \ No newline at end of file From df1ad885546db9b198b9c43a9373601868849dc3 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 19 Dec 2023 11:46:59 +0100 Subject: [PATCH 152/216] changelog --- .changelog/unreleased/bug-fixes/2310-wait-for-genesis-time.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 .changelog/unreleased/bug-fixes/2310-wait-for-genesis-time.md diff --git a/.changelog/unreleased/bug-fixes/2310-wait-for-genesis-time.md b/.changelog/unreleased/bug-fixes/2310-wait-for-genesis-time.md new file mode 100644 index 0000000000..0d8c8ab34f --- /dev/null +++ b/.changelog/unreleased/bug-fixes/2310-wait-for-genesis-time.md @@ -0,0 +1 @@ + - Make the ledger wait for genesis before starting up any processes ([\#2310](https://github.com/anoma/namada/pull/2310)) \ No newline at end of file From 6779c618935d7dd5b9804292e33a1ee1a080e1ad Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 19 Dec 2023 11:47:58 +0100 Subject: [PATCH 153/216] [fix]: Fixed english --- apps/src/lib/node/ledger/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index d747ed1a7d..fe7c0704f0 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -775,7 +775,7 @@ async fn sleep_until(time: DateTimeUtc) { }; tokio::select! { _ = shutdown_signal => { - tracing::info!("Shutdown signal receiving, shutting down"); + tracing::info!("Shutdown signal received, shutting down"); } _ = sleep => { tracing::info!("Genesis time reached, starting ledger"); From 8e5bc9bb183ee49fc75661714c9bc4f604826c1d Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 19 Dec 2023 15:27:57 +0100 Subject: [PATCH 154/216] Addressing review comments --- apps/src/lib/node/ledger/mod.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index fe7c0704f0..f48b1964b4 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -249,12 +249,14 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { // from Tendermint let mut spawner = AbortableSpawner::new(); - // Start Tendermint node - let tendermint_node = start_tendermint(&mut spawner, &config); // wait for genesis time let genesis_time = DateTimeUtc::try_from(config.genesis_time.clone()) .expect("Should be able to parse genesis time"); - sleep_until(genesis_time).await; + if let std::ops::ControlFlow::Break(_) = sleep_until(genesis_time).await { + return; + } + // Start Tendermint node + let tendermint_node = start_tendermint(&mut spawner, &config); // Start oracle if necessary let (eth_oracle_channels, eth_oracle) = @@ -756,7 +758,7 @@ fn spawn_dummy_task(ready: T) -> task::JoinHandle { } /// Sleep until the genesis time if necessary. -async fn sleep_until(time: DateTimeUtc) { +async fn sleep_until(time: DateTimeUtc) -> std::ops::ControlFlow<()> { let shutdown_signal = install_shutdown_signal(); // Sleep until start time if needed let sleep = async { @@ -776,9 +778,11 @@ async fn sleep_until(time: DateTimeUtc) { tokio::select! { _ = shutdown_signal => { tracing::info!("Shutdown signal received, shutting down"); + std::ops::ControlFlow::Break(()) } _ = sleep => { tracing::info!("Genesis time reached, starting ledger"); + std::ops::ControlFlow::Continue(()) } - }; + } } From 79317bb7752681664299d61aae184a489ac297f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 13 Dec 2023 17:41:13 +0000 Subject: [PATCH 155/216] tests: update expected tx result strings --- tests/src/e2e/eth_bridge_tests.rs | 37 +++--- tests/src/e2e/helpers.rs | 9 +- tests/src/e2e/ibc_tests.rs | 31 ++--- tests/src/e2e/ledger_tests.rs | 139 ++++++++-------------- tests/src/e2e/multitoken_tests/helpers.rs | 17 ++- tests/src/integration/masp.rs | 16 +-- 6 files changed, 103 insertions(+), 146 deletions(-) diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index 42b3c3cf3f..90f786a64c 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -175,9 +175,8 @@ async fn test_roundtrip_eth_transfer() -> Result<()> { tx_args, Some(CLIENT_COMMAND_TIMEOUT_SECONDS) )?; - namadac_tx.exp_string("Transaction accepted")?; - namadac_tx.exp_string("Transaction applied")?; - namadac_tx.exp_string("Transaction is valid")?; + namadac_tx.exp_string("Wrapper transaction accepted")?; + namadac_tx.exp_string("Transaction was successfully applied")?; drop(namadac_tx); let mut namadar = run!( @@ -367,9 +366,12 @@ async fn test_bridge_pool_e2e() { Some(CLIENT_COMMAND_TIMEOUT_SECONDS) ) .unwrap(); - namadac_tx.exp_string("Transaction accepted").unwrap(); - namadac_tx.exp_string("Transaction applied").unwrap(); - namadac_tx.exp_string("Transaction is valid").unwrap(); + namadac_tx + .exp_string("Wrapper transaction accepted") + .unwrap(); + namadac_tx + .exp_string("Transaction was successfully applied") + .unwrap(); drop(namadac_tx); let mut namadar = run!( @@ -767,8 +769,7 @@ async fn test_wdai_transfer_implicit_unauthorized() -> Result<()> { denom: 0u8.into(), }, )?; - cmd.exp_string("Transaction is valid.")?; - cmd.exp_string("Transaction is invalid.")?; + cmd.exp_string("Transaction was rejected by VPs.")?; cmd.assert_success(); // check balances are unchanged after an unsuccessful transfer @@ -837,8 +838,8 @@ async fn test_wdai_transfer_established_unauthorized() -> Result<()> { denom: 0u8.into(), }, )?; - cmd.exp_string("Transaction is valid.")?; - cmd.exp_string("Transaction is invalid.")?; + cmd.exp_string("Wrapper transaction accepted")?; + cmd.exp_string("Transaction was rejected by VPs")?; cmd.assert_success(); // check balances are unchanged after an unsuccessful transfer @@ -895,8 +896,8 @@ async fn test_wdai_transfer_implicit_to_implicit() -> Result<()> { &albert_addr.to_string(), second_transfer_amount, )?; - cmd.exp_string("Transaction is valid.")?; - cmd.exp_string("Transaction is valid.")?; + cmd.exp_string("Wrapper transaction accepted")?; + cmd.exp_string("Transaction was successfully applied")?; cmd.assert_success(); let albert_wdai_balance = find_wrapped_erc20_balance( @@ -973,8 +974,8 @@ async fn test_wdai_transfer_implicit_to_established() -> Result<()> { &albert_addr.to_string(), second_transfer_amount, )?; - cmd.exp_string("Transaction is valid.")?; - cmd.exp_string("Transaction is valid.")?; + cmd.exp_string("Wrapper transaction accepted")?; + cmd.exp_string("Transaction was successfully applied")?; cmd.assert_success(); let albert_wdai_balance = find_wrapped_erc20_balance( @@ -1054,8 +1055,8 @@ async fn test_wdai_transfer_established_to_implicit() -> Result<()> { &albert_established_addr.to_string(), second_transfer_amount, )?; - cmd.exp_string("Transaction is valid.")?; - cmd.exp_string("Transaction is valid.")?; + cmd.exp_string("Wrapper transaction accepted")?; + cmd.exp_string("Transaction was successfully applied")?; cmd.assert_success(); let albert_established_wdai_balance = find_wrapped_erc20_balance( @@ -1144,8 +1145,8 @@ async fn test_wdai_transfer_established_to_established() -> Result<()> { &albert_established_addr.to_string(), second_transfer_amount, )?; - cmd.exp_string("Transaction is valid.")?; - cmd.exp_string("Transaction is valid.")?; + cmd.exp_string("Wrapper transaction accepted")?; + cmd.exp_string("Transaction was successfully applied")?; cmd.assert_success(); let albert_established_wdai_balance = find_wrapped_erc20_balance( diff --git a/tests/src/e2e/helpers.rs b/tests/src/e2e/helpers.rs index 81488cba94..f3d2f40858 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -94,11 +94,10 @@ pub fn init_established_account( "--ledger-address", rpc_addr, ]; - let mut client_init_account = - run!(test, Bin::Client, init_account_args, Some(40))?; - client_init_account.exp_string("Transaction is valid.")?; - client_init_account.exp_string("Transaction applied")?; - client_init_account.assert_success(); + let mut cmd = run!(test, Bin::Client, init_account_args, Some(40))?; + cmd.exp_string("Wrapper transaction accepted")?; + cmd.exp_string("Transaction was successfully applied")?; + cmd.assert_success(); Ok(()) } diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index 9aeaf59475..bfd6a25686 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -886,7 +886,8 @@ fn transfer_received_token( &rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Wrapper transaction accepted")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); Ok(()) @@ -1199,9 +1200,9 @@ fn submit_ibc_tx( ], Some(40) )?; - client.exp_string("Transaction applied")?; + client.exp_string("Transaction was successfully applied")?; if wait_reveal_pk { - client.exp_string("Transaction applied")?; + client.exp_string("Transaction was successfully applied")?; } check_tx_height(test, &mut client) } @@ -1264,9 +1265,9 @@ fn transfer( Ok(0) } None => { - client.exp_string("Transaction applied")?; + client.exp_string("Transaction was successfully applied")?; if wait_reveal_pk { - client.exp_string("Transaction applied")?; + client.exp_string("Transaction was successfully applied")?; } check_tx_height(test, &mut client) } @@ -1274,25 +1275,17 @@ fn transfer( } fn check_tx_height(test: &Test, client: &mut NamadaCmd) -> Result { - let (unread, matched) = client.exp_regex("\"height\": .*,")?; + let (_unread, matched) = client.exp_regex(r"height .*")?; + // Expecting e.g. "height 1337." let height_str = matched .trim() - .rsplit_once(' ') + .split_once(' ') .unwrap() .1 - .replace(['"', ','], ""); - let height = height_str.parse().unwrap(); - - let (_unread, matched) = client.exp_regex("\"code\": .*,")?; - let code = matched - .trim() - .rsplit_once(' ') + .split_once('.') .unwrap() - .1 - .replace(['"', ','], ""); - if code != "0" { - return Err(eyre!("The IBC transaction failed: unread {}", unread)); - } + .0; + let height: u32 = height_str.parse().unwrap(); // wait for the next block to use the app hash while height as u64 + 1 > query_height(test)?.revision_height() { diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 76b7b63345..c723fee0cf 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -184,8 +184,7 @@ fn test_node_connectivity_and_consensus() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction applied with result:")?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); // 4. Check that all the nodes processed the tx with the same result @@ -573,10 +572,9 @@ fn ledger_txs_and_queries() -> Result<()> { let mut client = run!(test, Bin::Client, tx_args, Some(40))?; if !dry_run { - client.exp_string("Transaction accepted")?; - client.exp_string("Transaction applied")?; + client.exp_string("Wrapper transaction accepted")?; } - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); } } @@ -729,9 +727,8 @@ fn wrapper_disposable_signer() -> Result<()> { ]; let mut client = run!(test, Bin::Client, tx_args, Some(720))?; - client.exp_string("Transaction accepted")?; - client.exp_string("Transaction applied")?; - client.exp_string("Transaction is valid")?; + client.exp_string("Wrapper transaction accepted")?; + client.exp_string("Transaction was successfully applied")?; let _ep1 = epoch_sleep(&test, &validator_one_rpc, 720)?; let tx_args = vec![ @@ -752,9 +749,8 @@ fn wrapper_disposable_signer() -> Result<()> { ]; let mut client = run!(test, Bin::Client, tx_args, Some(720))?; - client.exp_string("Transaction accepted")?; - client.exp_string("Transaction applied")?; - client.exp_string("Transaction is valid")?; + client.exp_string("Wrapper transaction accepted")?; + client.exp_string("Transaction was successfully applied")?; let _ep1 = epoch_sleep(&test, &validator_one_rpc, 720)?; let tx_args = vec![ "transfer", @@ -826,10 +822,8 @@ fn invalid_transactions() -> Result<()> { ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction accepted")?; - client.exp_string("Transaction applied")?; - client.exp_string("Transaction is invalid")?; - client.exp_string(r#""code": "2"#)?; + client.exp_string("Wrapper transaction accepted")?; + client.exp_string("Transaction was rejected by VPs")?; client.assert_success(); let mut ledger = bg_ledger.foreground(); @@ -880,13 +874,8 @@ fn invalid_transactions() -> Result<()> { ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction accepted")?; - client.exp_string("Transaction applied")?; - - client.exp_string("Error trying to apply a transaction")?; - - client.exp_string(r#""code": "1"#)?; - + client.exp_string("Wrapper transaction accepted")?; + client.exp_string("Transaction failed.")?; client.assert_success(); Ok(()) } @@ -966,8 +955,7 @@ fn pos_bonds() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction applied with result:")?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); // 3. Submit a delegation to the first genesis validator @@ -985,8 +973,7 @@ fn pos_bonds() -> Result<()> { &validator_0_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction applied with result:")?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); // 4. Submit a re-delegation from the first to the second genesis validator @@ -1006,8 +993,7 @@ fn pos_bonds() -> Result<()> { &validator_0_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction applied with result:")?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); // 5. Submit an unbond of the self-bond @@ -1103,8 +1089,7 @@ fn pos_bonds() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction applied with result:")?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); // 10. Submit a withdrawal of the delegation @@ -1120,8 +1105,7 @@ fn pos_bonds() -> Result<()> { &validator_0_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction applied with result:")?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); // 11. Submit an withdrawal of the re-delegation @@ -1137,8 +1121,7 @@ fn pos_bonds() -> Result<()> { &validator_0_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction applied with result:")?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); Ok(()) @@ -1284,8 +1267,7 @@ fn pos_rewards() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction applied with result:")?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); // Query the validator balance again and check that the balance has grown @@ -1358,7 +1340,7 @@ fn test_bond_queries() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); // 3. Submit a delegation to the genesis validator @@ -1376,8 +1358,7 @@ fn test_bond_queries() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction applied with result:")?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); // 3. Wait for epoch 4 @@ -1408,8 +1389,7 @@ fn test_bond_queries() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction applied with result:")?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); // 5. Submit an unbond of the delegation @@ -1427,8 +1407,7 @@ fn test_bond_queries() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction applied with result:")?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; let (_, res) = client .exp_regex(r"withdrawable starting from epoch [0-9]+") .unwrap(); @@ -1547,7 +1526,7 @@ fn pos_init_validator() -> Result<()> { "--unsafe-dont-encrypt", ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); // 3. Submit a delegation to the new validator @@ -1568,7 +1547,7 @@ fn pos_init_validator() -> Result<()> { &non_validator_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); // Then self-bond the tokens: let delegation = 5_u64; @@ -1587,7 +1566,7 @@ fn pos_init_validator() -> Result<()> { &non_validator_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); // 4. Transfer some NAM to the new validator @@ -1608,7 +1587,7 @@ fn pos_init_validator() -> Result<()> { &non_validator_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); // 5. Submit a self-bond for the new validator @@ -1622,7 +1601,7 @@ fn pos_init_validator() -> Result<()> { &non_validator_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); // Stop the non-validator node and run it as the new validator @@ -1742,9 +1721,8 @@ fn ledger_many_txs_in_a_block() -> Result<()> { let mut args = (*tx_args).clone(); args.push(&*validator_one_rpc); let mut client = run!(*test, Bin::Client, args, Some(80))?; - client.exp_string("Transaction accepted")?; - client.exp_string("Transaction applied")?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Wrapper transaction accepted")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); let res: Result<()> = Ok(()); res @@ -1819,7 +1797,7 @@ fn proposal_submission() -> Result<()> { &validator_0_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); // 2. Submit valid proposal @@ -1842,8 +1820,7 @@ fn proposal_submission() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; - client.exp_string("Transaction applied")?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); // Wait for the proposal to be committed @@ -1972,7 +1949,7 @@ fn proposal_submission() -> Result<()> { submit_proposal_vote, Some(15) )?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); let submit_proposal_vote_delagator = vec![ @@ -1989,7 +1966,7 @@ fn proposal_submission() -> Result<()> { let mut client = run!(test, Bin::Client, submit_proposal_vote_delagator, Some(40))?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); // 10. Send a yay vote from a non-validator/non-delegator user @@ -2008,7 +1985,7 @@ fn proposal_submission() -> Result<()> { // this is valid because the client filter ALBERT delegation and there are // none let mut client = run!(test, Bin::Client, submit_proposal_vote, Some(15))?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); // 11. Query the proposal and check the result @@ -2144,8 +2121,7 @@ fn pgf_governance_proposal() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction applied with result:")?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); // 1 - Submit proposal @@ -2168,8 +2144,7 @@ fn pgf_governance_proposal() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; - client.exp_string("Transaction applied with result:")?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); // 2 - Query the proposal @@ -2242,8 +2217,7 @@ fn pgf_governance_proposal() -> Result<()> { submit_proposal_vote, Some(15) )?; - client.exp_string("Transaction applied with result:")?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); // Send different yay vote from delegator to check majority on 1/3 @@ -2261,8 +2235,7 @@ fn pgf_governance_proposal() -> Result<()> { let mut client = run!(test, Bin::Client, submit_proposal_vote_delagator, Some(40))?; - client.exp_string("Transaction applied with result:")?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); // 4 - Query the proposal and check the result is the one voted by the @@ -2360,8 +2333,7 @@ fn pgf_governance_proposal() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; - client.exp_string("Transaction applied with result:")?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); // 2 - Query the funding proposal @@ -2452,8 +2424,7 @@ fn proposal_offline() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction applied with result:")?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); // 2. Create an offline proposal @@ -2837,8 +2808,7 @@ fn double_signing_gets_slashed() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction applied with result:")?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); // Wait until pipeline epoch to see if the validator is back in consensus @@ -3173,8 +3143,7 @@ fn deactivate_and_reactivate_validator() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(1), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction applied with result:")?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); let deactivate_epoch = get_epoch(&test, &validator_1_rpc)?; @@ -3217,8 +3186,7 @@ fn deactivate_and_reactivate_validator() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(1), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction applied with result:")?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); let reactivate_epoch = get_epoch(&test, &validator_1_rpc)?; @@ -3311,7 +3279,7 @@ fn change_validator_metadata() -> Result<()> { metadata_change_args, Some(40) )?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); // 4. Query the metadata after the change @@ -3344,7 +3312,7 @@ fn change_validator_metadata() -> Result<()> { metadata_change_args, Some(40) )?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); // 6. Query the metadata to see that the validator website is removed @@ -3429,8 +3397,7 @@ fn test_invalid_validator_txs() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction applied with result:")?; - client.exp_string("Transaction is invalid.")?; + client.exp_string("Transaction was rejected by VPs")?; client.assert_success(); // Try to deactivate validator-1 as validator-0 @@ -3445,8 +3412,7 @@ fn test_invalid_validator_txs() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction applied with result:")?; - client.exp_string("Transaction is invalid.")?; + client.exp_string("Transaction was rejected by VPs")?; client.assert_success(); // Try to change the validator-1 website as validator-0 @@ -3463,8 +3429,7 @@ fn test_invalid_validator_txs() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction applied with result:")?; - client.exp_string("Transaction is invalid.")?; + client.exp_string("Transaction was rejected by VPs")?; client.assert_success(); // Deactivate validator-1 @@ -3479,8 +3444,7 @@ fn test_invalid_validator_txs() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(1), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction applied with result:")?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); let deactivate_epoch = get_epoch(&test, &validator_1_rpc)?; @@ -3523,8 +3487,7 @@ fn test_invalid_validator_txs() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction applied with result:")?; - client.exp_string("Transaction is invalid.")?; + client.exp_string("Transaction was rejected by VPs")?; client.assert_success(); Ok(()) @@ -3593,7 +3556,7 @@ fn change_consensus_key() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction was successfully applied")?; client.assert_success(); // ========================================================================= diff --git a/tests/src/e2e/multitoken_tests/helpers.rs b/tests/src/e2e/multitoken_tests/helpers.rs index 0856691dd5..41887265b8 100644 --- a/tests/src/e2e/multitoken_tests/helpers.rs +++ b/tests/src/e2e/multitoken_tests/helpers.rs @@ -53,11 +53,10 @@ pub fn init_multitoken_vp(test: &Test, rpc_addr: &str) -> Result { "--ledger-address", rpc_addr, ]; - let mut client_init_account = - run!(test, Bin::Client, init_account_args, Some(40))?; - client_init_account.exp_string("Transaction is valid.")?; - client_init_account.exp_string("Transaction applied")?; - client_init_account.assert_success(); + let mut cmd = run!(test, Bin::Client, init_account_args, Some(40))?; + cmd.exp_string("Wrapper transaction accepted")?; + cmd.exp_string("Transaction was successfully applied")?; + cmd.assert_success(); Ok(multitoken_alias.to_string()) } @@ -120,10 +119,10 @@ pub fn mint_red_tokens( "--ledger-address", rpc_addr, ]; - let mut client_tx = run!(test, Bin::Client, tx_args, Some(40))?; - client_tx.exp_string("Transaction is valid.")?; - client_tx.exp_string("Transaction applied")?; - client_tx.assert_success(); + let mut cmd = run!(test, Bin::Client, tx_args, Some(40))?; + cmd.exp_string("Wrapper transaction accepted")?; + cmd.exp_string("Transaction was successfully applied")?; + cmd.assert_success(); Ok(()) } diff --git a/tests/src/integration/masp.rs b/tests/src/integration/masp.rs index bb7ca903b6..0dbdf1daa7 100644 --- a/tests/src/integration/masp.rs +++ b/tests/src/integration/masp.rs @@ -919,7 +919,7 @@ fn masp_txs_and_queries() -> Result<()> { "--node", validator_one_rpc, ], - Response::Ok("Transaction is valid"), + Response::Ok("Transaction was successfully applied"), ), // 3. Attempt to spend 10 ETH at SK(A) to PA(B) ( @@ -957,7 +957,7 @@ fn masp_txs_and_queries() -> Result<()> { "--node", validator_one_rpc, ], - Response::Ok("Transaction is valid"), + Response::Ok("Transaction was successfully applied"), ), // 5. Spend 7 BTC at SK(A) to PA(B) ( @@ -976,7 +976,7 @@ fn masp_txs_and_queries() -> Result<()> { "--node", validator_one_rpc, ], - Response::Ok("Transaction is valid"), + Response::Ok("Transaction was successfully applied"), ), // 6. Attempt to spend 7 BTC at SK(A) to PA(B) ( @@ -1014,7 +1014,7 @@ fn masp_txs_and_queries() -> Result<()> { "--node", validator_one_rpc, ], - Response::Ok("Transaction is valid"), + Response::Ok("Transaction was successfully applied"), ), // 8. Assert BTC balance at VK(A) is 0 ( @@ -1070,7 +1070,7 @@ fn masp_txs_and_queries() -> Result<()> { "--node", validator_one_rpc, ], - Response::Ok("Transaction is valid"), + Response::Ok("Transaction was successfully applied"), ), ]; @@ -1093,7 +1093,7 @@ fn masp_txs_and_queries() -> Result<()> { let captured = CapturedOutput::of(|| run(&node, Bin::Client, tx_args.clone())); match tx_result { - Response::Ok("Transaction is valid") => { + Response::Ok("Transaction was successfully applied") => { assert!( captured.result.is_ok(), "{:?} failed with result {:?}.\n Unread output: {}", @@ -1105,7 +1105,9 @@ fn masp_txs_and_queries() -> Result<()> { node.assert_success(); } else { assert!( - captured.contains("Transaction is valid"), + captured.contains( + "Transaction was successfully applied" + ), "{:?} failed to contain needle 'Transaction is \ valid',\nGot output '{}'", tx_args, From 238bd817ef3f699679a0a5875204565ea13df4de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 13 Dec 2023 18:01:16 +0000 Subject: [PATCH 156/216] sdk: remove `initialized_accounts` from `TxResponse` (it's in inner_tx) --- apps/src/lib/client/tx.rs | 12 ++++---- .../lib/node/ledger/shell/finalize_block.rs | 15 ---------- sdk/src/rpc.rs | 18 +----------- sdk/src/tx.rs | 28 ++++++++----------- 4 files changed, 20 insertions(+), 53 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 8f5c61c001..7cb979de7f 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -21,7 +21,7 @@ use namada::types::dec::Dec; use namada::types::io::Io; use namada::types::key::{self, *}; use namada::types::transaction::pos::{BecomeValidator, ConsensusKeyChange}; -use namada_sdk::rpc::{TxBroadcastData, TxResponse}; +use namada_sdk::rpc::{InnerTxResult, TxBroadcastData, TxResponse}; use namada_sdk::wallet::alias::validator_consensus_key; use namada_sdk::wallet::{Wallet, WalletIo}; use namada_sdk::{display_line, edisplay_line, error, signing, tx, Namada}; @@ -328,9 +328,11 @@ where signing::generate_test_vector(namada, &tx).await?; - let result = namada.submit(tx, &args.tx).await?; - if let ProcessTxResponse::Applied(response) = result { - return Ok(response.initialized_accounts.first().cloned()); + let response = namada.submit(tx, &args.tx).await?; + if let ProcessTxResponse::Applied(tx_resp) = response { + if let InnerTxResult::Success(result) = tx_resp.inner_tx_result() { + return Ok(result.initialized_accounts.first().cloned()); + } } } @@ -779,7 +781,7 @@ pub async fn submit_become_validator( signing::generate_test_vector(namada, &tx).await?; - namada.submit(tx, &tx_args).await?.initialized_accounts(); + namada.submit(tx, &tx_args).await?; if !tx_args.dry_run { // add validator address and keys to the wallet diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 3547c8c9b3..6fa9d50b03 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -454,21 +454,6 @@ where event["height"] = height.to_string(); response.events.push(event); } - match serde_json::to_string( - &result.initialized_accounts, - ) { - Ok(initialized_accounts) => { - tx_event["initialized_accounts"] = - initialized_accounts; - } - Err(err) => { - tracing::error!( - "Failed to serialize the initialized \ - accounts: {}", - err - ); - } - } } else { tracing::trace!( "some VPs rejected transaction {} storage \ diff --git a/sdk/src/rpc.rs b/sdk/src/rpc.rs index 7210d06d03..6fa013c521 100644 --- a/sdk/src/rpc.rs +++ b/sdk/src/rpc.rs @@ -538,10 +538,8 @@ pub struct TxResponse { pub hash: String, /// Response code pub code: String, - /// Gas used + /// Gas used. If there's an `inner_tx`, its gas is equal to this value. pub gas_used: String, - /// Initialized accounts - pub initialized_accounts: Vec
, } /// Determines a result of an inner tx from [`TxResponse::inner_tx_result`]. @@ -589,15 +587,6 @@ impl TryFrom for TxResponse { .get("gas_used") .ok_or_else(|| missing_field_err("gas_used"))? .clone(); - let initialized_accounts = event - .get("initialized_accounts") - .map(String::as_str) - // TODO: fix finalize block, to return initialized accounts, - // even when we reject a tx? - .map_or(Ok(vec![]), |initialized_accounts| { - serde_json::from_str(initialized_accounts) - .map_err(|err| format!("JSON decode error: {err}")) - })?; Ok(TxResponse { inner_tx, @@ -607,7 +596,6 @@ impl TryFrom for TxResponse { height, code, gas_used, - initialized_accounts, }) } } @@ -702,10 +690,6 @@ pub async fn query_tx_response( hash: event_map["hash"].to_string(), code: event_map["code"].to_string(), gas_used: event_map["gas_used"].to_string(), - initialized_accounts: serde_json::from_str( - event_map["initialized_accounts"], - ) - .unwrap_or_default(), }; Ok(result) } diff --git a/sdk/src/tx.rs b/sdk/src/tx.rs index ae5d7a1d47..3285d59c66 100644 --- a/sdk/src/tx.rs +++ b/sdk/src/tx.rs @@ -133,14 +133,6 @@ pub enum ProcessTxResponse { } impl ProcessTxResponse { - /// Get the the accounts that were reported to be initialized - pub fn initialized_accounts(&self) -> Vec
{ - match self { - Self::Applied(result) => result.initialized_accounts.clone(), - _ => vec![], - } - } - // Is the transaction applied and was it accepted by all VPs? Note that this // always returns false for dry-run transactions. pub fn is_applied_and_valid(&self) -> bool { @@ -236,14 +228,18 @@ pub async fn process_tx( .map(ProcessTxResponse::Broadcast) } else { match submit_tx(context, to_broadcast).await { - Ok(x) => { - save_initialized_accounts( - context, - args, - x.initialized_accounts.clone(), - ) - .await; - Ok(ProcessTxResponse::Applied(x)) + Ok(resp) => { + if let InnerTxResult::Success(result) = + resp.inner_tx_result() + { + save_initialized_accounts( + context, + args, + result.initialized_accounts.clone(), + ) + .await; + } + Ok(ProcessTxResponse::Applied(resp)) } Err(x) => Err(x), } From 30cb4894d3b7c591c301289df33181dac17812d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 13 Dec 2023 18:30:25 +0000 Subject: [PATCH 157/216] move tx ErrorCodes type to core --- Cargo.lock | 2 +- apps/Cargo.toml | 1 - apps/src/lib/node/ledger/shell/mod.rs | 60 +------------- .../src/lib/node/ledger/shell/testing/node.rs | 4 +- core/Cargo.toml | 1 + core/src/types/transaction/mod.rs | 83 +++++++++++++++++++ wasm/Cargo.lock | 1 + wasm_for_tests/wasm_source/Cargo.lock | 1 + 8 files changed, 90 insertions(+), 63 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8c5c490885..9848a9a6e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4236,7 +4236,6 @@ dependencies = [ "namada", "namada_sdk", "namada_test_utils", - "num-derive", "num-rational 0.4.1", "num-traits 0.2.17", "num256", @@ -4325,6 +4324,7 @@ dependencies = [ "k256", "masp_primitives", "namada_macros", + "num-derive", "num-integer", "num-rational 0.4.1", "num-traits 0.2.17", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index b0d098665f..fb2aa72b16 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -102,7 +102,6 @@ masp_primitives = { workspace = true, features = ["transparent-inputs"] } masp_proofs = { workspace = true, features = ["bundled-prover", "download-params"] } num_cpus.workspace = true num256.workspace = true -num-derive.workspace = true num-rational.workspace = true num-traits.workspace = true once_cell.workspace = true diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 8e33bf87bb..7f5f8dbb86 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -31,6 +31,7 @@ use borsh_ext::BorshSerializeExt; use masp_primitives::transaction::Transaction; use namada::core::hints; use namada::core::ledger::eth_bridge; +pub use namada::core::types::transaction::ErrorCodes; use namada::ledger::events::log::EventLog; use namada::ledger::events::Event; use namada::ledger::gas::{Gas, TxGasMeter}; @@ -67,8 +68,6 @@ use namada::vm::wasm::{TxCache, VpCache}; use namada::vm::{WasmCacheAccess, WasmCacheRwAccess}; use namada_sdk::eth_bridge::{EthBridgeQueries, EthereumOracleConfig}; use namada_sdk::tendermint::AppHash; -use num_derive::{FromPrimitive, ToPrimitive}; -use num_traits::{FromPrimitive, ToPrimitive}; use thiserror::Error; use tokio::sync::mpsc::{Receiver, UnboundedSender}; @@ -136,63 +135,6 @@ impl From for TxResult { } } -/// The different error codes that the ledger may -/// send back to a client indicating the status -/// of their submitted tx -#[derive(Debug, Copy, Clone, FromPrimitive, ToPrimitive, PartialEq, Eq)] -pub enum ErrorCodes { - Ok = 0, - WasmRuntimeError = 1, - InvalidTx = 2, - InvalidSig = 3, - InvalidOrder = 4, - ExtraTxs = 5, - Undecryptable = 6, - AllocationError = 7, - ReplayTx = 8, - InvalidChainId = 9, - ExpiredTx = 10, - TxGasLimit = 11, - FeeError = 12, - InvalidVoteExtension = 13, - TooLarge = 14, -} - -impl ErrorCodes { - /// Checks if the given [`ErrorCodes`] value is a protocol level error, - /// that can be recovered from at the finalize block stage. - pub const fn is_recoverable(&self) -> bool { - use ErrorCodes::*; - // NOTE: pattern match on all `ErrorCodes` variants, in order - // to catch potential bugs when adding new codes - match self { - Ok | WasmRuntimeError => true, - InvalidTx | InvalidSig | InvalidOrder | ExtraTxs - | Undecryptable | AllocationError | ReplayTx | InvalidChainId - | ExpiredTx | TxGasLimit | FeeError | InvalidVoteExtension - | TooLarge => false, - } - } -} - -impl From for u32 { - fn from(code: ErrorCodes) -> u32 { - code.to_u32().unwrap() - } -} - -impl From for String { - fn from(code: ErrorCodes) -> String { - u32::from(code).to_string() - } -} - -impl From for crate::facade::tendermint::abci::Code { - fn from(value: ErrorCodes) -> Self { - Self::from(u32::from(value)) - } -} - pub type Result = std::result::Result; pub fn reset(config: config::Ledger) -> Result<()> { diff --git a/apps/src/lib/node/ledger/shell/testing/node.rs b/apps/src/lib/node/ledger/shell/testing/node.rs index 4f8fa13342..47cf64d990 100644 --- a/apps/src/lib/node/ledger/shell/testing/node.rs +++ b/apps/src/lib/node/ledger/shell/testing/node.rs @@ -10,6 +10,7 @@ use data_encoding::HEXUPPER; use itertools::Either; use lazy_static::lazy_static; use namada::core::types::ethereum_structs; +use namada::core::types::transaction::ErrorCodes; use namada::eth_bridge::oracle::config::Config as OracleConfig; use namada::ledger::dry_run_tx; use namada::ledger::events::log::dumb_queries; @@ -35,7 +36,6 @@ use namada::types::key::tm_consensus_key_raw_hash; use namada::types::storage::{BlockHash, BlockHeight, Epoch, Header}; use namada::types::time::DateTimeUtc; use namada_sdk::queries::Client; -use num_traits::cast::FromPrimitive; use regex::Regex; use tokio::sync::mpsc; @@ -51,7 +51,7 @@ use crate::node::ledger::ethereum_oracle::{ control, last_processed_block, try_process_eth_events, }; use crate::node::ledger::shell::testing::utils::TestDir; -use crate::node::ledger::shell::{ErrorCodes, EthereumOracleChannels, Shell}; +use crate::node::ledger::shell::{EthereumOracleChannels, Shell}; use crate::node::ledger::shims::abcipp_shim_types::shim::request::{ FinalizeBlock, ProcessedTx, }; diff --git a/core/Cargo.toml b/core/Cargo.toml index df25f86120..5e88789554 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -52,6 +52,7 @@ itertools.workspace = true k256.workspace = true masp_primitives.workspace = true num256.workspace = true +num-derive.workspace = true num_enum = "0.7.0" num-integer = "0.1.45" num-rational.workspace = true diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index 0688cea6b2..071e295e67 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -23,6 +23,8 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use borsh_ext::BorshSerializeExt; pub use decrypted::*; +use num_derive::{FromPrimitive, ToPrimitive}; +use num_traits::{FromPrimitive, ToPrimitive}; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; pub use wrapper::*; @@ -34,6 +36,87 @@ use crate::types::ibc::IbcEvent; use crate::types::storage; use crate::types::transaction::protocol::ProtocolTx; +/// The different error codes that the ledger may send back to a client +/// indicating the status of their submitted tx +#[derive(Debug, Copy, Clone, FromPrimitive, ToPrimitive, PartialEq, Eq)] +pub enum ErrorCodes { + /// Success + Ok = 0, + /// Error in WASM tx execution + WasmRuntimeError = 1, + /// Invalid tx + InvalidTx = 2, + /// Invalid signature + InvalidSig = 3, + /// Tx is in invalid order + InvalidOrder = 4, + /// Tx wasn't expected + ExtraTxs = 5, + /// Undecryptable + Undecryptable = 6, + /// The block is full + AllocationError = 7, + /// Replayed tx + ReplayTx = 8, + /// Invalid chain ID + InvalidChainId = 9, + /// Expired tx + ExpiredTx = 10, + /// Exceeded gas limit + TxGasLimit = 11, + /// Error in paying tx fee + FeeError = 12, + /// Invalid vote extension + InvalidVoteExtension = 13, + /// Tx is too large + TooLarge = 14, +} + +impl ErrorCodes { + /// Checks if the given [`ErrorCodes`] value is a protocol level error, + /// that can be recovered from at the finalize block stage. + pub const fn is_recoverable(&self) -> bool { + use ErrorCodes::*; + // NOTE: pattern match on all `ErrorCodes` variants, in order + // to catch potential bugs when adding new codes + match self { + Ok | WasmRuntimeError => true, + InvalidTx | InvalidSig | InvalidOrder | ExtraTxs + | Undecryptable | AllocationError | ReplayTx | InvalidChainId + | ExpiredTx | TxGasLimit | FeeError | InvalidVoteExtension + | TooLarge => false, + } + } + + /// Convert to `u32`. + pub fn to_u32(&self) -> u32 { + ToPrimitive::to_u32(self).unwrap() + } + + /// Convert from `u32`. + pub fn from_u32(raw: u32) -> Option { + FromPrimitive::from_u32(raw) + } +} + +impl From for u32 { + fn from(code: ErrorCodes) -> u32 { + code.to_u32() + } +} + +impl From for String { + fn from(code: ErrorCodes) -> String { + code.to_u32().to_string() + } +} + +impl From for crate::tendermint::abci::Code { + fn from(value: ErrorCodes) -> Self { + Self::from(value.to_u32()) + } +} + /// Get the hash of a transaction pub fn hash_tx(tx_bytes: &[u8]) -> Hash { let digest = Sha256::digest(tx_bytes); diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index e89c96c0fd..0410574298 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -3390,6 +3390,7 @@ dependencies = [ "k256", "masp_primitives", "namada_macros", + "num-derive", "num-integer", "num-rational 0.4.1", "num-traits", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 885bd23415..9bbd15d9e6 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -3390,6 +3390,7 @@ dependencies = [ "k256", "masp_primitives", "namada_macros", + "num-derive", "num-integer", "num-rational 0.4.1", "num-traits", From e19e65e3634bd3a359967d40401641e08b7db62b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 13 Dec 2023 18:44:27 +0000 Subject: [PATCH 158/216] sdk: use typed `ErrorCodes` for `TxResponse` --- apps/src/lib/client/tx.rs | 2 +- core/src/types/transaction/mod.rs | 42 +++++++++++++++++++++++++++---- sdk/src/rpc.rs | 15 +++++------ sdk/src/tx.rs | 6 ++--- 4 files changed, 49 insertions(+), 16 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 7cb979de7f..c8df8afd85 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -960,7 +960,7 @@ pub async fn submit_transfer( // If a transaction is shielded tx_epoch.is_some() && // And it is rejected by a VP - resp.code == 1.to_string() && + matches!(resp.inner_tx_result(), InnerTxResult::VpsRejected(_)) && // And its submission epoch doesn't match construction epoch tx_epoch.unwrap() != submission_epoch => { diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index 071e295e67..35114e4e15 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -17,7 +17,7 @@ pub mod protocol; pub mod wrapper; use std::collections::BTreeSet; -use std::fmt; +use std::fmt::{self, Display}; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; @@ -38,7 +38,17 @@ use crate::types::transaction::protocol::ProtocolTx; /// The different error codes that the ledger may send back to a client /// indicating the status of their submitted tx -#[derive(Debug, Copy, Clone, FromPrimitive, ToPrimitive, PartialEq, Eq)] +#[derive( + Debug, + Copy, + Clone, + FromPrimitive, + ToPrimitive, + PartialEq, + Eq, + Serialize, + Deserialize, +)] pub enum ErrorCodes { /// Success Ok = 0, @@ -99,15 +109,37 @@ impl ErrorCodes { } } +impl From for String { + fn from(code: ErrorCodes) -> String { + code.to_string() + } +} + impl From for u32 { fn from(code: ErrorCodes) -> u32 { code.to_u32() } } -impl From for String { - fn from(code: ErrorCodes) -> String { - code.to_u32().to_string() +impl Display for ErrorCodes { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.to_u32()) + } +} + +impl FromStr for ErrorCodes { + type Err = std::io::Error; + + fn from_str(s: &str) -> Result { + let raw = u32::from_str(s).map_err(|e| { + std::io::Error::new(std::io::ErrorKind::InvalidData, e) + })?; + Self::from_u32(raw).ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Unexpected error code", + ) + }) } } diff --git a/sdk/src/rpc.rs b/sdk/src/rpc.rs index 6fa013c521..d81ed46290 100644 --- a/sdk/src/rpc.rs +++ b/sdk/src/rpc.rs @@ -26,7 +26,7 @@ use namada_core::types::storage::{ use namada_core::types::token::{ Amount, DenominatedAmount, Denomination, MaspDenom, }; -use namada_core::types::transaction::TxResult; +use namada_core::types::transaction::{ErrorCodes, TxResult}; use namada_core::types::{storage, token}; use namada_proof_of_stake::parameters::PosParams; use namada_proof_of_stake::types::{ @@ -537,7 +537,7 @@ pub struct TxResponse { /// Transaction height pub hash: String, /// Response code - pub code: String, + pub code: ErrorCodes, /// Gas used. If there's an `inner_tx`, its gas is equal to this value. pub gas_used: String, } @@ -579,10 +579,10 @@ impl TryFrom for TxResponse { .get("height") .ok_or_else(|| missing_field_err("height"))? .clone(); - let code = event - .get("code") - .ok_or_else(|| missing_field_err("code"))? - .clone(); + let code = ErrorCodes::from_str( + event.get("code").ok_or_else(|| missing_field_err("code"))?, + ) + .map_err(|e| e.to_string())?; let gas_used = event .get("gas_used") .ok_or_else(|| missing_field_err("gas_used"))? @@ -682,13 +682,14 @@ pub async fn query_tx_response( let inner_tx = event_map .get("inner_tx") .map(|s| TxResult::from_str(s).unwrap()); + let code = ErrorCodes::from_str(event_map["code"]).unwrap(); let result = TxResponse { inner_tx, info: event_map["info"].to_string(), log: event_map["log"].to_string(), height: event_map["height"].to_string(), hash: event_map["hash"].to_string(), - code: event_map["code"].to_string(), + code, gas_used: event_map["gas_used"].to_string(), }; Ok(result) diff --git a/sdk/src/tx.rs b/sdk/src/tx.rs index 3285d59c66..62acb2e2ac 100644 --- a/sdk/src/tx.rs +++ b/sdk/src/tx.rs @@ -45,7 +45,7 @@ use namada_core::types::transaction::governance::{ InitProposalData, VoteProposalData, }; use namada_core::types::transaction::pgf::UpdateStewardCommission; -use namada_core::types::transaction::{pos, TxResult}; +use namada_core::types::transaction::{pos, ErrorCodes, TxResult}; use namada_core::types::{storage, token}; use namada_proof_of_stake::parameters::PosParams; use namada_proof_of_stake::types::{CommissionPair, ValidatorState}; @@ -137,7 +137,7 @@ impl ProcessTxResponse { // always returns false for dry-run transactions. pub fn is_applied_and_valid(&self) -> bool { match self { - ProcessTxResponse::Applied(resp) => resp.code == 0.to_string(), + ProcessTxResponse::Applied(resp) => resp.code == ErrorCodes::Ok, ProcessTxResponse::DryRun(_) | ProcessTxResponse::Broadcast(_) => { false } @@ -418,7 +418,7 @@ pub fn display_wrapper_resp_and_get_result( context: &impl Namada, resp: &TxResponse, ) -> bool { - let result = if resp.code != 0.to_string() { + let result = if resp.code != ErrorCodes::Ok { display_line!( context.io(), "Wrapper transaction failed with error code {}. Used {} gas.", From eb909db081bab3bb233e2d728a3a6f2073d421ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 13 Dec 2023 21:28:07 +0000 Subject: [PATCH 159/216] Rename ErrorCodes to ResultCode and make TxReponse.height typed --- .../lib/node/ledger/shell/finalize_block.rs | 80 ++++++------ apps/src/lib/node/ledger/shell/mod.rs | 84 ++++++------ .../lib/node/ledger/shell/process_proposal.rs | 122 +++++++++--------- .../src/lib/node/ledger/shell/testing/node.rs | 8 +- core/src/types/transaction/mod.rs | 28 ++-- sdk/src/rpc.rs | 32 +++-- sdk/src/tx.rs | 19 ++- 7 files changed, 196 insertions(+), 177 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 6fa9d50b03..7ab0f85921 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -212,8 +212,8 @@ where }; // If [`process_proposal`] rejected a Tx due to invalid signature, // emit an event here and move on to next tx. - if ErrorCodes::from_u32(processed_tx.result.code).unwrap() - == ErrorCodes::InvalidSig + if ResultCode::from_u32(processed_tx.result.code).unwrap() + == ResultCode::InvalidSig { let mut tx_event = match tx.header().tx_type { TxType::Wrapper(_) | TxType::Protocol(_) => { @@ -247,8 +247,8 @@ where let tx_header = tx.header(); // If [`process_proposal`] rejected a Tx, emit an event here and // move on to next tx - if ErrorCodes::from_u32(processed_tx.result.code).unwrap() - != ErrorCodes::Ok + if ResultCode::from_u32(processed_tx.result.code).unwrap() + != ResultCode::Ok { let mut tx_event = Event::new_tx_event(&tx, height.0); tx_event["code"] = processed_tx.result.code.to_string(); @@ -309,7 +309,7 @@ where decrypted." .into(); event["code"] = - ErrorCodes::Undecryptable.into(); + ResultCode::Undecryptable.into(); response.events.push(event); continue; } @@ -440,7 +440,7 @@ where } self.wl_storage.commit_tx(); if !tx_event.contains_key("code") { - tx_event["code"] = ErrorCodes::Ok.into(); + tx_event["code"] = ResultCode::Ok.into(); self.wl_storage .storage .block @@ -473,7 +473,7 @@ where stats.increment_rejected_txs(); self.wl_storage.drop_tx(); - tx_event["code"] = ErrorCodes::InvalidTx.into(); + tx_event["code"] = ResultCode::InvalidTx.into(); } tx_event["gas_used"] = result.gas_used.to_string(); tx_event["info"] = "Check inner_tx for result.".to_string(); @@ -525,9 +525,9 @@ where tx_event["info"] = msg.to_string(); if let EventType::Accepted = tx_event.event_type { // If wrapper, invalid tx error code - tx_event["code"] = ErrorCodes::InvalidTx.into(); + tx_event["code"] = ResultCode::InvalidTx.into(); } else { - tx_event["code"] = ErrorCodes::WasmRuntimeError.into(); + tx_event["code"] = ResultCode::WasmRuntimeError.into(); } } } @@ -1032,7 +1032,7 @@ mod test_finalize_block { ProcessedTx { tx: tx.into(), result: TxResult { - code: ErrorCodes::Ok.into(), + code: ResultCode::Ok.into(), info: "".into(), }, }, @@ -1071,7 +1071,7 @@ mod test_finalize_block { ProcessedTx { tx: outer_tx.to_bytes().into(), result: TxResult { - code: ErrorCodes::Ok.into(), + code: ResultCode::Ok.into(), info: "".into(), }, } @@ -1187,7 +1187,7 @@ mod test_finalize_block { let processed_tx = ProcessedTx { tx: outer_tx.to_bytes().into(), result: TxResult { - code: ErrorCodes::InvalidTx.into(), + code: ResultCode::InvalidTx.into(), info: "".into(), }, }; @@ -1202,7 +1202,7 @@ mod test_finalize_block { { assert_eq!(event.event_type.to_string(), String::from("applied")); let code = event.attributes.get("code").expect("Test failed"); - assert_eq!(code, &String::from(ErrorCodes::InvalidTx)); + assert_eq!(code, &String::from(ResultCode::InvalidTx)); } // check that the corresponding wrapper tx was removed from the queue assert!(shell.wl_storage.storage.tx_queue.is_empty()); @@ -1231,7 +1231,7 @@ mod test_finalize_block { .to_bytes() .into(), result: TxResult { - code: ErrorCodes::Ok.into(), + code: ResultCode::Ok.into(), info: "".into(), }, }; @@ -1252,7 +1252,7 @@ mod test_finalize_block { { assert_eq!(event.event_type.to_string(), String::from("applied")); let code = event.attributes.get("code").expect("Test failed"); - assert_eq!(code, &String::from(ErrorCodes::Undecryptable)); + assert_eq!(code, &String::from(ResultCode::Undecryptable)); let log = event.attributes.get("log").expect("Test failed"); assert!(log.contains("Transaction could not be decrypted.")) } @@ -1311,7 +1311,7 @@ mod test_finalize_block { ); let code = event.attributes.get("code").expect("Test failed").as_str(); - assert_eq!(code, String::from(ErrorCodes::Ok).as_str()); + assert_eq!(code, String::from(ResultCode::Ok).as_str()); } else { // these should be accepted decrypted txs assert_eq!( @@ -1320,7 +1320,7 @@ mod test_finalize_block { ); let code = event.attributes.get("code").expect("Test failed").as_str(); - assert_eq!(code, String::from(ErrorCodes::Ok).as_str()); + assert_eq!(code, String::from(ResultCode::Ok).as_str()); } } @@ -1358,7 +1358,7 @@ mod test_finalize_block { txs: vec![ProcessedTx { tx: tx.into(), result: TxResult { - code: ErrorCodes::InvalidTx.into(), + code: ResultCode::InvalidTx.into(), info: Default::default(), }, }], @@ -1369,7 +1369,7 @@ mod test_finalize_block { let event = resp.remove(0); assert_eq!(event.event_type.to_string(), String::from("applied")); let code = event.attributes.get("code").expect("Test failed"); - assert_eq!(code, &String::from(ErrorCodes::InvalidTx)); + assert_eq!(code, &String::from(ResultCode::InvalidTx)); } /// Test that once a validator's vote for an Ethereum event lands @@ -1428,7 +1428,7 @@ mod test_finalize_block { .to_bytes() .into(), result: TxResult { - code: ErrorCodes::Ok.into(), + code: ResultCode::Ok.into(), info: "".into(), }, } @@ -1445,7 +1445,7 @@ mod test_finalize_block { .expect("Test failed"); assert_eq!(result.event_type.to_string(), String::from("applied")); let code = result.attributes.get("code").expect("Test failed").as_str(); - assert_eq!(code, String::from(ErrorCodes::Ok).as_str()); + assert_eq!(code, String::from(ResultCode::Ok).as_str()); // --- The event is removed from the queue assert!(shell.new_ethereum_events().is_empty()); @@ -1488,7 +1488,7 @@ mod test_finalize_block { .to_bytes() .into(), result: TxResult { - code: ErrorCodes::Ok.into(), + code: ResultCode::Ok.into(), info: "".into(), }, }; @@ -1504,7 +1504,7 @@ mod test_finalize_block { .expect("Test failed"); assert_eq!(result.event_type.to_string(), String::from("applied")); let code = result.attributes.get("code").expect("Test failed").as_str(); - assert_eq!(code, String::from(ErrorCodes::Ok).as_str()); + assert_eq!(code, String::from(ResultCode::Ok).as_str()); // --- The event is removed from the queue assert!(shell.new_ethereum_events().is_empty()); @@ -1547,7 +1547,7 @@ mod test_finalize_block { let processed_tx = ProcessedTx { tx: tx.to_bytes().into(), result: TxResult { - code: ErrorCodes::Ok.into(), + code: ResultCode::Ok.into(), info: "".into(), }, }; @@ -2950,7 +2950,7 @@ mod test_finalize_block { failed", ) .as_str(); - assert_eq!(code, String::from(ErrorCodes::Ok).as_str()); + assert_eq!(code, String::from(ResultCode::Ok).as_str()); // the merkle tree root should not change after finalize_block let root_post = shell.shell.wl_storage.storage.block.tree.root(); @@ -3053,7 +3053,7 @@ mod test_finalize_block { processed_txs.push(ProcessedTx { tx: inner.to_bytes().into(), result: TxResult { - code: ErrorCodes::Ok.into(), + code: ResultCode::Ok.into(), info: "".into(), }, }) @@ -3077,10 +3077,10 @@ mod test_finalize_block { assert_eq!(event[0].event_type.to_string(), String::from("applied")); let code = event[0].attributes.get("code").unwrap().as_str(); - assert_eq!(code, String::from(ErrorCodes::Ok).as_str()); + assert_eq!(code, String::from(ResultCode::Ok).as_str()); assert_eq!(event[1].event_type.to_string(), String::from("applied")); let code = event[1].attributes.get("code").unwrap().as_str(); - assert_eq!(code, String::from(ErrorCodes::WasmRuntimeError).as_str()); + assert_eq!(code, String::from(ResultCode::WasmRuntimeError).as_str()); for (inner, wrapper) in [(inner, wrapper), (new_inner, new_wrapper)] { assert!( @@ -3200,7 +3200,7 @@ mod test_finalize_block { processed_txs.push(ProcessedTx { tx: inner.to_bytes().into(), result: TxResult { - code: ErrorCodes::Ok.into(), + code: ResultCode::Ok.into(), info: "".into(), }, }) @@ -3233,19 +3233,19 @@ mod test_finalize_block { assert_eq!(event[0].event_type.to_string(), String::from("applied")); let code = event[0].attributes.get("code").unwrap().as_str(); - assert_eq!(code, String::from(ErrorCodes::WasmRuntimeError).as_str()); + assert_eq!(code, String::from(ResultCode::WasmRuntimeError).as_str()); assert_eq!(event[1].event_type.to_string(), String::from("applied")); let code = event[1].attributes.get("code").unwrap().as_str(); - assert_eq!(code, String::from(ErrorCodes::Undecryptable).as_str()); + assert_eq!(code, String::from(ResultCode::Undecryptable).as_str()); assert_eq!(event[2].event_type.to_string(), String::from("applied")); let code = event[2].attributes.get("code").unwrap().as_str(); - assert_eq!(code, String::from(ErrorCodes::InvalidTx).as_str()); + assert_eq!(code, String::from(ResultCode::InvalidTx).as_str()); assert_eq!(event[3].event_type.to_string(), String::from("applied")); let code = event[3].attributes.get("code").unwrap().as_str(); - assert_eq!(code, String::from(ErrorCodes::WasmRuntimeError).as_str()); + assert_eq!(code, String::from(ResultCode::WasmRuntimeError).as_str()); assert_eq!(event[4].event_type.to_string(), String::from("applied")); let code = event[4].attributes.get("code").unwrap().as_str(); - assert_eq!(code, String::from(ErrorCodes::WasmRuntimeError).as_str()); + assert_eq!(code, String::from(ResultCode::WasmRuntimeError).as_str()); for (invalid_inner, valid_wrapper) in [ (out_of_gas_inner, out_of_gas_wrapper), @@ -3323,7 +3323,7 @@ mod test_finalize_block { let processed_txs = vec![ProcessedTx { tx: wrapper.to_bytes().into(), result: TxResult { - code: ErrorCodes::Ok.into(), + code: ResultCode::Ok.into(), info: "".into(), }, }]; @@ -3347,7 +3347,7 @@ mod test_finalize_block { .get("code") .expect("Test failed") .as_str(); - assert_eq!(code, String::from(ErrorCodes::InvalidTx).as_str()); + assert_eq!(code, String::from(ResultCode::InvalidTx).as_str()); assert!( shell @@ -3398,7 +3398,7 @@ mod test_finalize_block { let processed_tx = ProcessedTx { tx: wrapper.to_bytes().into(), result: TxResult { - code: ErrorCodes::Ok.into(), + code: ResultCode::Ok.into(), info: "".into(), }, }; @@ -3413,7 +3413,7 @@ mod test_finalize_block { // Check balance of fee payer is 0 assert_eq!(event.event_type.to_string(), String::from("accepted")); let code = event.attributes.get("code").expect("Testfailed").as_str(); - assert_eq!(code, String::from(ErrorCodes::InvalidTx).as_str()); + assert_eq!(code, String::from(ResultCode::InvalidTx).as_str()); let balance_key = namada::core::types::token::balance_key( &shell.wl_storage.storage.native_token, &Address::from(&keypair.to_public()), @@ -3492,7 +3492,7 @@ mod test_finalize_block { let processed_tx = ProcessedTx { tx: wrapper.to_bytes().into(), result: TxResult { - code: ErrorCodes::Ok.into(), + code: ResultCode::Ok.into(), info: "".into(), }, }; @@ -3508,7 +3508,7 @@ mod test_finalize_block { // Check fee payment assert_eq!(event.event_type.to_string(), String::from("accepted")); let code = event.attributes.get("code").expect("Test failed").as_str(); - assert_eq!(code, String::from(ErrorCodes::Ok).as_str()); + assert_eq!(code, String::from(ResultCode::Ok).as_str()); let new_proposer_balance = storage_api::token::read_balance( &shell.wl_storage, diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 7f5f8dbb86..f9eb26fb00 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -31,7 +31,7 @@ use borsh_ext::BorshSerializeExt; use masp_primitives::transaction::Transaction; use namada::core::hints; use namada::core::ledger::eth_bridge; -pub use namada::core::types::transaction::ErrorCodes; +pub use namada::core::types::transaction::ResultCode; use namada::ledger::events::log::EventLog; use namada::ledger::events::Event; use namada::ledger::gas::{Gas, TxGasMeter}; @@ -1064,7 +1064,7 @@ where if !validate_tx_bytes(&self.wl_storage, tx_bytes.len()) .expect("Failed to get max tx bytes param from storage") { - response.code = ErrorCodes::TooLarge.into(); + response.code = ResultCode::TooLarge.into(); response.log = format!("{INVALID_MSG}: Tx too large"); return response; } @@ -1073,7 +1073,7 @@ where let tx = match Tx::try_from(tx_bytes).map_err(Error::TxDecoding) { Ok(t) => t, Err(msg) => { - response.code = ErrorCodes::InvalidTx.into(); + response.code = ResultCode::InvalidTx.into(); response.log = format!("{INVALID_MSG}: {msg}"); return response; } @@ -1081,7 +1081,7 @@ where // Tx chain id if tx.header.chain_id != self.chain_id { - response.code = ErrorCodes::InvalidChainId.into(); + response.code = ResultCode::InvalidChainId.into(); response.log = format!( "{INVALID_MSG}: Tx carries a wrong chain id: expected {}, \ found {}", @@ -1095,7 +1095,7 @@ where let last_block_timestamp = self.get_block_timestamp(None); if last_block_timestamp > exp { - response.code = ErrorCodes::ExpiredTx.into(); + response.code = ResultCode::ExpiredTx.into(); response.log = format!( "{INVALID_MSG}: Tx expired at {exp:#?}, last committed \ block time: {last_block_timestamp:#?}", @@ -1108,7 +1108,7 @@ where let tx_type = match tx.validate_tx() { Ok(_) => tx.header(), Err(msg) => { - response.code = ErrorCodes::InvalidSig.into(); + response.code = ResultCode::InvalidSig.into(); response.log = format!("{INVALID_MSG}: {msg}"); return response; } @@ -1121,7 +1121,7 @@ where match $result { Ok(ext) => ext, Err(err) => { - $rsp.code = ErrorCodes::InvalidVoteExtension.into(); + $rsp.code = ResultCode::InvalidVoteExtension.into(); $rsp.log = format!( "{INVALID_MSG}: Invalid {} vote extension: {err}", $kind, @@ -1146,7 +1146,7 @@ where self.wl_storage.storage.get_last_block_height(), ) { - response.code = ErrorCodes::InvalidVoteExtension.into(); + response.code = ResultCode::InvalidVoteExtension.into(); response.log = format!( "{INVALID_MSG}: Invalid Ethereum events vote \ extension: {err}", @@ -1169,7 +1169,7 @@ where self.wl_storage.storage.get_last_block_height(), ) { - response.code = ErrorCodes::InvalidVoteExtension.into(); + response.code = ResultCode::InvalidVoteExtension.into(); response.log = format!( "{INVALID_MSG}: Invalid Brige pool roots vote \ extension: {err}", @@ -1200,7 +1200,7 @@ where self.wl_storage.storage.last_epoch, ) { - response.code = ErrorCodes::InvalidVoteExtension.into(); + response.code = ResultCode::InvalidVoteExtension.into(); response.log = format!( "{INVALID_MSG}: Invalid validator set update vote \ extension: {err}", @@ -1213,7 +1213,7 @@ where } } _ => { - response.code = ErrorCodes::InvalidTx.into(); + response.code = ResultCode::InvalidTx.into(); response.log = format!( "{INVALID_MSG}: The given protocol tx cannot be added \ to the mempool" @@ -1224,7 +1224,7 @@ where // Tx gas limit let mut gas_meter = TxGasMeter::new(wrapper.gas_limit); if gas_meter.add_wrapper_gas(tx_bytes).is_err() { - response.code = ErrorCodes::TxGasLimit.into(); + response.code = ResultCode::TxGasLimit.into(); response.log = "{INVALID_MSG}: Wrapper transactions \ exceeds its gas limit" .to_string(); @@ -1239,7 +1239,7 @@ where .unwrap(), ); if gas_meter.tx_gas_limit > block_gas_limit { - response.code = ErrorCodes::AllocationError.into(); + response.code = ResultCode::AllocationError.into(); response.log = "{INVALID_MSG}: Wrapper transaction \ exceeds the maximum block gas limit" .to_string(); @@ -1254,7 +1254,7 @@ where .has_replay_protection_entry(&tx.raw_header_hash()) .expect("Error while checking inner tx hash key in storage") { - response.code = ErrorCodes::ReplayTx.into(); + response.code = ResultCode::ReplayTx.into(); response.log = format!( "{INVALID_MSG}: Inner transaction hash {} already in \ storage, replay attempt", @@ -1274,7 +1274,7 @@ where "Error while checking wrapper tx hash key in storage", ) { - response.code = ErrorCodes::ReplayTx.into(); + response.code = ResultCode::ReplayTx.into(); response.log = format!( "{INVALID_MSG}: Wrapper transaction hash {} already \ in storage, replay attempt", @@ -1293,27 +1293,27 @@ where None, false, ) { - response.code = ErrorCodes::FeeError.into(); + response.code = ResultCode::FeeError.into(); response.log = format!("{INVALID_MSG}: {e}"); return response; } } TxType::Raw => { - response.code = ErrorCodes::InvalidTx.into(); + response.code = ResultCode::InvalidTx.into(); response.log = format!( "{INVALID_MSG}: Raw transactions cannot be accepted into \ the mempool" ); } TxType::Decrypted(_) => { - response.code = ErrorCodes::InvalidTx.into(); + response.code = ResultCode::InvalidTx.into(); response.log = format!( "{INVALID_MSG}: Decrypted txs cannot be sent by clients" ); } } - if response.code == ErrorCodes::Ok.into() { + if response.code == ResultCode::Ok.into() { response.log = VALID_MSG.into(); } response @@ -2375,7 +2375,7 @@ mod shell_tests { .to_bytes(); let rsp = shell.mempool_validate(&tx, Default::default()); assert!( - rsp.code != ErrorCodes::Ok.into(), + rsp.code != ResultCode::Ok.into(), "Validation should have failed" ); } @@ -2405,7 +2405,7 @@ mod shell_tests { .to_bytes(); let rsp = shell.mempool_validate(&tx, Default::default()); assert!( - rsp.code == ErrorCodes::Ok.into(), + rsp.code == ResultCode::Ok.into(), "Validation should have passed" ); } @@ -2459,7 +2459,7 @@ mod shell_tests { for (tx_bytes, err_msg) in txs_to_validate { let rsp = shell.mempool_validate(&tx_bytes, Default::default()); assert!( - rsp.code == ErrorCodes::InvalidVoteExtension.into(), + rsp.code == ResultCode::InvalidVoteExtension.into(), "{err_msg}" ); } @@ -2541,7 +2541,7 @@ mod shell_tests { } .to_bytes(); let rsp = shell.mempool_validate(&tx, Default::default()); - assert_eq!(rsp.code, ErrorCodes::InvalidVoteExtension.into()); + assert_eq!(rsp.code, ResultCode::InvalidVoteExtension.into()); } /// Mempool validation must reject unsigned wrappers @@ -2573,12 +2573,12 @@ mod shell_tests { unsigned_wrapper.to_bytes().as_ref(), MempoolTxType::NewTransaction, ); - assert_eq!(result.code, ErrorCodes::InvalidSig.into()); + assert_eq!(result.code, ResultCode::InvalidSig.into()); result = shell.mempool_validate( unsigned_wrapper.to_bytes().as_ref(), MempoolTxType::RecheckTransaction, ); - assert_eq!(result.code, ErrorCodes::InvalidSig.into()); + assert_eq!(result.code, ResultCode::InvalidSig.into()); } /// Mempool validation must reject wrappers with an invalid signature @@ -2621,12 +2621,12 @@ mod shell_tests { invalid_wrapper.to_bytes().as_ref(), MempoolTxType::NewTransaction, ); - assert_eq!(result.code, ErrorCodes::InvalidSig.into()); + assert_eq!(result.code, ResultCode::InvalidSig.into()); result = shell.mempool_validate( invalid_wrapper.to_bytes().as_ref(), MempoolTxType::RecheckTransaction, ); - assert_eq!(result.code, ErrorCodes::InvalidSig.into()); + assert_eq!(result.code, ResultCode::InvalidSig.into()); } /// Mempool validation must reject non-wrapper txs @@ -2641,7 +2641,7 @@ mod shell_tests { tx.to_bytes().as_ref(), MempoolTxType::NewTransaction, ); - assert_eq!(result.code, ErrorCodes::InvalidTx.into()); + assert_eq!(result.code, ResultCode::InvalidTx.into()); assert_eq!( result.log, "Mempool validation failed: Raw transactions cannot be accepted \ @@ -2695,7 +2695,7 @@ mod shell_tests { wrapper.to_bytes().as_ref(), MempoolTxType::NewTransaction, ); - assert_eq!(result.code, ErrorCodes::ReplayTx.into()); + assert_eq!(result.code, ResultCode::ReplayTx.into()); assert_eq!( result.log, format!( @@ -2709,7 +2709,7 @@ mod shell_tests { wrapper.to_bytes().as_ref(), MempoolTxType::RecheckTransaction, ); - assert_eq!(result.code, ErrorCodes::ReplayTx.into()); + assert_eq!(result.code, ResultCode::ReplayTx.into()); assert_eq!( result.log, format!( @@ -2736,7 +2736,7 @@ mod shell_tests { wrapper.to_bytes().as_ref(), MempoolTxType::NewTransaction, ); - assert_eq!(result.code, ErrorCodes::ReplayTx.into()); + assert_eq!(result.code, ResultCode::ReplayTx.into()); assert_eq!( result.log, format!( @@ -2750,7 +2750,7 @@ mod shell_tests { wrapper.to_bytes().as_ref(), MempoolTxType::RecheckTransaction, ); - assert_eq!(result.code, ErrorCodes::ReplayTx.into()); + assert_eq!(result.code, ResultCode::ReplayTx.into()); assert_eq!( result.log, format!( @@ -2778,7 +2778,7 @@ mod shell_tests { tx.to_bytes().as_ref(), MempoolTxType::NewTransaction, ); - assert_eq!(result.code, ErrorCodes::InvalidChainId.into()); + assert_eq!(result.code, ResultCode::InvalidChainId.into()); assert_eq!( result.log, format!( @@ -2806,7 +2806,7 @@ mod shell_tests { tx.to_bytes().as_ref(), MempoolTxType::NewTransaction, ); - assert_eq!(result.code, ErrorCodes::ExpiredTx.into()); + assert_eq!(result.code, ResultCode::ExpiredTx.into()); } /// Check that a tx requiring more gas than the block limit gets rejected @@ -2843,7 +2843,7 @@ mod shell_tests { wrapper.to_bytes().as_ref(), MempoolTxType::NewTransaction, ); - assert_eq!(result.code, ErrorCodes::AllocationError.into()); + assert_eq!(result.code, ResultCode::AllocationError.into()); } // Check that a tx requiring more gas than its limit gets rejected @@ -2876,7 +2876,7 @@ mod shell_tests { wrapper.to_bytes().as_ref(), MempoolTxType::NewTransaction, ); - assert_eq!(result.code, ErrorCodes::TxGasLimit.into()); + assert_eq!(result.code, ResultCode::TxGasLimit.into()); } // Check that a wrapper using a non-whitelisted token for fee payment is @@ -2911,7 +2911,7 @@ mod shell_tests { wrapper.to_bytes().as_ref(), MempoolTxType::NewTransaction, ); - assert_eq!(result.code, ErrorCodes::FeeError.into()); + assert_eq!(result.code, ResultCode::FeeError.into()); } // Check that a wrapper setting a fee amount lower than the minimum required @@ -2946,7 +2946,7 @@ mod shell_tests { wrapper.to_bytes().as_ref(), MempoolTxType::NewTransaction, ); - assert_eq!(result.code, ErrorCodes::FeeError.into()); + assert_eq!(result.code, ResultCode::FeeError.into()); } // Check that a wrapper transactions whose fees cannot be paid is rejected @@ -2980,7 +2980,7 @@ mod shell_tests { wrapper.to_bytes().as_ref(), MempoolTxType::NewTransaction, ); - assert_eq!(result.code, ErrorCodes::FeeError.into()); + assert_eq!(result.code, ResultCode::FeeError.into()); } // Check that a fee overflow in the wrapper transaction is rejected @@ -3014,7 +3014,7 @@ mod shell_tests { wrapper.to_bytes().as_ref(), MempoolTxType::NewTransaction, ); - assert_eq!(result.code, ErrorCodes::FeeError.into()); + assert_eq!(result.code, ResultCode::FeeError.into()); } /// Test max tx bytes parameter in CheckTx @@ -3061,13 +3061,13 @@ mod shell_tests { new_tx(50).to_bytes().as_ref(), MempoolTxType::NewTransaction, ); - assert!(result.code != ErrorCodes::TooLarge.into()); + assert!(result.code != ResultCode::TooLarge.into()); // max tx bytes + 1, on the other hand, is not let result = shell.mempool_validate( new_tx(max_tx_bytes + 1).to_bytes().as_ref(), MempoolTxType::NewTransaction, ); - assert_eq!(result.code, ErrorCodes::TooLarge.into()); + assert_eq!(result.code, ResultCode::TooLarge.into()); } } diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 7fad385eda..0fe554386b 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -113,7 +113,7 @@ where // deserialize properly, that have invalid signatures // and that have invalid wasm code to reach FinalizeBlock. let invalid_txs = tx_results.iter().any(|res| { - let error = ErrorCodes::from_u32(res.code).expect( + let error = ResultCode::from_u32(res.code).expect( "All error codes returned from process_single_tx are valid", ); !error.is_recoverable() @@ -182,8 +182,8 @@ where &mut tx_wasm_cache, block_proposer, ); - let error_code = ErrorCodes::from_u32(result.code).unwrap(); - if let ErrorCodes::Ok = error_code { + let error_code = ResultCode::from_u32(result.code).unwrap(); + if let ResultCode::Ok = error_code { temp_wl_storage.write_log.commit_tx(); } else { tracing::info!( @@ -217,12 +217,12 @@ where { if vote_extensions.all(|maybe_ext| maybe_ext.is_some()) { TxResult { - code: ErrorCodes::Ok.into(), + code: ResultCode::Ok.into(), info: "Process proposal accepted this transaction".into(), } } else { TxResult { - code: ErrorCodes::InvalidVoteExtension.into(), + code: ResultCode::InvalidVoteExtension.into(), info: "Process proposal rejected this proposal because at \ least one of the vote extensions included was invalid." .into(), @@ -275,7 +275,7 @@ where .expect("Failed to get max tx bytes param from storage") { return TxResult { - code: ErrorCodes::TooLarge.into(), + code: ResultCode::TooLarge.into(), info: "Tx too large".into(), }; } @@ -283,7 +283,7 @@ where // try to allocate space for this tx if let Err(e) = metadata.txs_bin.try_dump(tx_bytes) { return TxResult { - code: ErrorCodes::AllocationError.into(), + code: ResultCode::AllocationError.into(), info: match e { AllocFailure::Rejected { .. } => { "No more space left in the block" @@ -305,7 +305,7 @@ where PrepareProposal" ); Err(TxResult { - code: ErrorCodes::InvalidTx.into(), + code: ResultCode::InvalidTx.into(), info: "The submitted transaction was not deserializable" .into(), }) @@ -317,7 +317,7 @@ where // This occurs if the wrapper / protocol tx signature is // invalid return Err(TxResult { - code: ErrorCodes::InvalidSig.into(), + code: ResultCode::InvalidSig.into(), info: err.to_string(), }); } @@ -331,14 +331,14 @@ where if let Err(err) = tx.validate_tx() { return TxResult { - code: ErrorCodes::InvalidSig.into(), + code: ResultCode::InvalidSig.into(), info: err.to_string(), }; } match tx.header().tx_type { // If it is a raw transaction, we do no further validation TxType::Raw => TxResult { - code: ErrorCodes::InvalidTx.into(), + code: ResultCode::InvalidTx.into(), info: "Transaction rejected: Non-encrypted transactions are \ not supported" .into(), @@ -347,7 +347,7 @@ where // Tx chain id if tx_chain_id != self.chain_id { return TxResult { - code: ErrorCodes::InvalidChainId.into(), + code: ResultCode::InvalidChainId.into(), info: format!( "Tx carries a wrong chain id: expected {}, found \ {}", @@ -360,7 +360,7 @@ where if let Some(exp) = tx_expiration { if block_time > exp { return TxResult { - code: ErrorCodes::ExpiredTx.into(), + code: ResultCode::ExpiredTx.into(), info: format!( "Tx expired at {:#?}, block time: {:#?}", exp, block_time @@ -380,7 +380,7 @@ where .get_last_block_height(), ) .map(|_| TxResult { - code: ErrorCodes::Ok.into(), + code: ResultCode::Ok.into(), info: "Process Proposal accepted this \ transaction" .into(), @@ -388,7 +388,7 @@ where .map_err(|err| err.to_string()) }) .unwrap_or_else(|err| TxResult { - code: ErrorCodes::InvalidVoteExtension.into(), + code: ResultCode::InvalidVoteExtension.into(), info: format!( "Process proposal rejected this proposal \ because one of the included Ethereum \ @@ -407,7 +407,7 @@ where .get_last_block_height(), ) .map(|_| TxResult { - code: ErrorCodes::Ok.into(), + code: ResultCode::Ok.into(), info: "Process Proposal accepted this \ transaction" .into(), @@ -415,7 +415,7 @@ where .map_err(|err| err.to_string()) }) .unwrap_or_else(|err| TxResult { - code: ErrorCodes::InvalidVoteExtension.into(), + code: ResultCode::InvalidVoteExtension.into(), info: format!( "Process proposal rejected this proposal \ because one of the included Bridge pool \ @@ -439,7 +439,7 @@ where self.wl_storage.storage.get_current_epoch().0, ) .map(|_| TxResult { - code: ErrorCodes::Ok.into(), + code: ResultCode::Ok.into(), info: "Process Proposal accepted this \ transaction" .into(), @@ -448,7 +448,7 @@ where }) .unwrap_or_else(|err| { TxResult { - code: ErrorCodes::InvalidVoteExtension.into(), + code: ResultCode::InvalidVoteExtension.into(), info: format!( "Process proposal rejected this proposal \ because one of the included validator \ @@ -500,7 +500,7 @@ where .must_send_valset_upd(SendValsetUpd::AtPrevHeight) { return TxResult { - code: ErrorCodes::InvalidVoteExtension.into(), + code: ResultCode::InvalidVoteExtension.into(), info: "Process proposal rejected a validator \ set update vote extension issued at an \ invalid block height" @@ -528,7 +528,7 @@ where if wrapper.tx.raw_header_hash() != tx.raw_header_hash() { TxResult { - code: ErrorCodes::InvalidOrder.into(), + code: ResultCode::InvalidOrder.into(), info: "Process proposal rejected a decrypted \ transaction that violated the tx order \ determined in the previous block" @@ -540,14 +540,14 @@ where ) { // DKG is disabled, txs are not actually encrypted TxResult { - code: ErrorCodes::InvalidTx.into(), + code: ResultCode::InvalidTx.into(), info: "The encrypted payload of tx was \ incorrectly marked as un-decryptable" .into(), } } else { TxResult { - code: ErrorCodes::Ok.into(), + code: ResultCode::Ok.into(), info: "Process Proposal accepted this \ tranasction" .into(), @@ -555,7 +555,7 @@ where } } None => TxResult { - code: ErrorCodes::ExtraTxs.into(), + code: ResultCode::ExtraTxs.into(), info: "Received more decrypted txs than expected" .into(), }, @@ -576,7 +576,7 @@ where .try_dump(tx_bytes, u64::from(wrapper.gas_limit)); return TxResult { - code: ErrorCodes::TxGasLimit.into(), + code: ResultCode::TxGasLimit.into(), info: "Wrapper transactions exceeds its gas limit" .to_string(), }; @@ -588,14 +588,14 @@ where .try_dump(tx_bytes, u64::from(wrapper.gas_limit)) { return TxResult { - code: ErrorCodes::AllocationError.into(), + code: ResultCode::AllocationError.into(), info: e, }; } // decrypted txs shouldn't show up before wrapper txs if metadata.has_decrypted_txs { return TxResult { - code: ErrorCodes::InvalidTx.into(), + code: ResultCode::InvalidTx.into(), info: "Decrypted txs should not be proposed before \ wrapper txs" .into(), @@ -603,7 +603,7 @@ where } if hints::unlikely(self.encrypted_txs_not_allowed()) { return TxResult { - code: ErrorCodes::AllocationError.into(), + code: ResultCode::AllocationError.into(), info: "Wrapper txs not allowed at the current block \ height" .into(), @@ -613,7 +613,7 @@ where // ChainId check if tx_chain_id != self.chain_id { return TxResult { - code: ErrorCodes::InvalidChainId.into(), + code: ResultCode::InvalidChainId.into(), info: format!( "Tx carries a wrong chain id: expected {}, found \ {}", @@ -626,7 +626,7 @@ where if let Some(exp) = tx_expiration { if block_time > exp { return TxResult { - code: ErrorCodes::ExpiredTx.into(), + code: ResultCode::ExpiredTx.into(), info: format!( "Tx expired at {:#?}, block time: {:#?}", exp, block_time @@ -640,7 +640,7 @@ where self.replay_protection_checks(&tx, temp_wl_storage) { return TxResult { - code: ErrorCodes::ReplayTx.into(), + code: ResultCode::ReplayTx.into(), info: e.to_string(), }; } @@ -656,12 +656,12 @@ where false, ) { Ok(()) => TxResult { - code: ErrorCodes::Ok.into(), + code: ResultCode::Ok.into(), info: "Process proposal accepted this transaction" .into(), }, Err(e) => TxResult { - code: ErrorCodes::FeeError.into(), + code: ResultCode::FeeError.into(), info: e.to_string(), }, } @@ -742,7 +742,7 @@ mod test_process_proposal { .expect("Test failed") .try_into() .expect("Test failed"); - assert_eq!(resp.result.code, u32::from(ErrorCodes::Ok)); + assert_eq!(resp.result.code, u32::from(ResultCode::Ok)); deactivate_bridge(&mut shell); let response = if let Err(TestError::RejectProposal(resp)) = shell.process_proposal(request) @@ -757,7 +757,7 @@ mod test_process_proposal { }; assert_eq!( response.result.code, - u32::from(ErrorCodes::InvalidVoteExtension) + u32::from(ResultCode::InvalidVoteExtension) ); } @@ -794,7 +794,7 @@ mod test_process_proposal { .try_into() .expect("Test failed"); - assert_eq!(resp.result.code, u32::from(ErrorCodes::Ok)); + assert_eq!(resp.result.code, u32::from(ResultCode::Ok)); deactivate_bridge(&mut shell); let response = if let Err(TestError::RejectProposal(resp)) = shell.process_proposal(request) @@ -809,7 +809,7 @@ mod test_process_proposal { }; assert_eq!( response.result.code, - u32::from(ErrorCodes::InvalidVoteExtension) + u32::from(ResultCode::InvalidVoteExtension) ); } @@ -835,7 +835,7 @@ mod test_process_proposal { }; assert_eq!( response.result.code, - u32::from(ErrorCodes::InvalidVoteExtension) + u32::from(ResultCode::InvalidVoteExtension) ); } @@ -966,7 +966,7 @@ mod test_process_proposal { println!("{}", response.result.info); - assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidSig)); + assert_eq!(response.result.code, u32::from(ResultCode::InvalidSig)); assert_eq!( response.result.info, String::from( @@ -1026,7 +1026,7 @@ mod test_process_proposal { invalid."; assert_eq!( response.result.code, - u32::from(ErrorCodes::InvalidSig) + u32::from(ResultCode::InvalidSig) ); assert!( response.result.info.contains(expected_error), @@ -1091,7 +1091,7 @@ mod test_process_proposal { panic!("Test failed") } }; - assert_eq!(response.result.code, u32::from(ErrorCodes::FeeError)); + assert_eq!(response.result.code, u32::from(ResultCode::FeeError)); assert_eq!( response.result.info, String::from( @@ -1157,7 +1157,7 @@ mod test_process_proposal { panic!("Test failed") } }; - assert_eq!(response.result.code, u32::from(ErrorCodes::FeeError)); + assert_eq!(response.result.code, u32::from(ResultCode::FeeError)); assert_eq!( response.result.info, String::from( @@ -1220,7 +1220,7 @@ mod test_process_proposal { panic!("Test failed") } }; - assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidOrder)); + assert_eq!(response.result.code, u32::from(ResultCode::InvalidOrder)); assert_eq!( response.result.info, String::from( @@ -1273,7 +1273,7 @@ mod test_process_proposal { panic!("Test failed") } }; - assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidTx)); + assert_eq!(response.result.code, u32::from(ResultCode::InvalidTx)); assert_eq!( response.result.info, String::from( @@ -1340,7 +1340,7 @@ mod test_process_proposal { } else { panic!("Test failed") }; - assert_eq!(response.result.code, u32::from(ErrorCodes::ExtraTxs)); + assert_eq!(response.result.code, u32::from(ResultCode::ExtraTxs)); assert_eq!( response.result.info, String::from("Received more decrypted txs than expected"), @@ -1375,7 +1375,7 @@ mod test_process_proposal { panic!("Test failed") } }; - assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidTx)); + assert_eq!(response.result.code, u32::from(ResultCode::InvalidTx)); assert_eq!( response.result.info, String::from( @@ -1436,7 +1436,7 @@ mod test_process_proposal { Err(TestError::RejectProposal(response)) => { assert_eq!( response[0].result.code, - u32::from(ErrorCodes::ReplayTx) + u32::from(ResultCode::ReplayTx) ); assert_eq!( response[0].result.info, @@ -1495,10 +1495,10 @@ mod test_process_proposal { match shell.process_proposal(request) { Ok(_) => panic!("Test failed"), Err(TestError::RejectProposal(response)) => { - assert_eq!(response[0].result.code, u32::from(ErrorCodes::Ok)); + assert_eq!(response[0].result.code, u32::from(ResultCode::Ok)); assert_eq!( response[1].result.code, - u32::from(ErrorCodes::ReplayTx) + u32::from(ResultCode::ReplayTx) ); assert_eq!( response[1].result.info, @@ -1561,7 +1561,7 @@ mod test_process_proposal { Err(TestError::RejectProposal(response)) => { assert_eq!( response[0].result.code, - u32::from(ErrorCodes::ReplayTx) + u32::from(ResultCode::ReplayTx) ); assert_eq!( response[0].result.info, @@ -1678,7 +1678,7 @@ mod test_process_proposal { for res in response { assert_eq!( res.result.code, - u32::from(ErrorCodes::InvalidChainId) + u32::from(ResultCode::InvalidChainId) ); assert_eq!( res.result.info, @@ -1729,7 +1729,7 @@ mod test_process_proposal { Err(TestError::RejectProposal(response)) => { assert_eq!( response[0].result.code, - u32::from(ErrorCodes::ExpiredTx) + u32::from(ResultCode::ExpiredTx) ); } } @@ -1775,7 +1775,7 @@ mod test_process_proposal { Err(TestError::RejectProposal(response)) => { assert_eq!( response[0].result.code, - u32::from(ErrorCodes::AllocationError) + u32::from(ResultCode::AllocationError) ); } } @@ -1817,7 +1817,7 @@ mod test_process_proposal { Err(TestError::RejectProposal(response)) => { assert_eq!( response[0].result.code, - u32::from(ErrorCodes::TxGasLimit) + u32::from(ResultCode::TxGasLimit) ); } } @@ -1860,7 +1860,7 @@ mod test_process_proposal { Err(TestError::RejectProposal(response)) => { assert_eq!( response[0].result.code, - u32::from(ErrorCodes::FeeError) + u32::from(ResultCode::FeeError) ); } } @@ -1903,7 +1903,7 @@ mod test_process_proposal { Err(TestError::RejectProposal(response)) => { assert_eq!( response[0].result.code, - u32::from(ErrorCodes::FeeError) + u32::from(ResultCode::FeeError) ); } } @@ -1946,7 +1946,7 @@ mod test_process_proposal { Err(TestError::RejectProposal(response)) => { assert_eq!( response[0].result.code, - u32::from(ErrorCodes::FeeError) + u32::from(ResultCode::FeeError) ); } } @@ -1989,7 +1989,7 @@ mod test_process_proposal { Err(TestError::RejectProposal(response)) => { assert_eq!( response[0].result.code, - u32::from(ErrorCodes::FeeError) + u32::from(ResultCode::FeeError) ); } } @@ -2043,7 +2043,7 @@ mod test_process_proposal { }; assert_eq!( response.result.code, - u32::from(ErrorCodes::AllocationError) + u32::from(ResultCode::AllocationError) ); assert_eq!( response.result.info, @@ -2102,7 +2102,7 @@ mod test_process_proposal { Err(TestError::RejectProposal(response)) => { assert_eq!( response[0].result.code, - u32::from(ErrorCodes::TooLarge) + u32::from(ResultCode::TooLarge) ); } } @@ -2114,7 +2114,7 @@ mod test_process_proposal { Ok(_) => panic!("Test failed"), Err(TestError::RejectProposal(response)) => { assert!( - response[0].result.code != u32::from(ErrorCodes::TooLarge) + response[0].result.code != u32::from(ResultCode::TooLarge) ); } } diff --git a/apps/src/lib/node/ledger/shell/testing/node.rs b/apps/src/lib/node/ledger/shell/testing/node.rs index 47cf64d990..fcdd0367e0 100644 --- a/apps/src/lib/node/ledger/shell/testing/node.rs +++ b/apps/src/lib/node/ledger/shell/testing/node.rs @@ -10,7 +10,7 @@ use data_encoding::HEXUPPER; use itertools::Either; use lazy_static::lazy_static; use namada::core::types::ethereum_structs; -use namada::core::types::transaction::ErrorCodes; +use namada::core::types::transaction::ResultCode; use namada::eth_bridge::oracle::config::Config as OracleConfig; use namada::ledger::dry_run_tx; use namada::ledger::events::log::dumb_queries; @@ -239,7 +239,7 @@ pub enum NodeResults { /// Rejected by Process Proposal Rejected(TxResult), /// Failure in application in Finalize Block - Failed(ErrorCodes), + Failed(ResultCode), } pub struct MockNode { @@ -509,14 +509,14 @@ impl MockNode { .events .into_iter() .map(|e| { - let code = ErrorCodes::from_u32( + let code = ResultCode::from_u32( e.attributes .get("code") .map(|e| u32::from_str(e).unwrap()) .unwrap_or_default(), ) .unwrap(); - if code == ErrorCodes::Ok { + if code == ResultCode::Ok { NodeResults::Ok } else { NodeResults::Failed(code) diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index 35114e4e15..86ceb15116 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -36,7 +36,7 @@ use crate::types::ibc::IbcEvent; use crate::types::storage; use crate::types::transaction::protocol::ProtocolTx; -/// The different error codes that the ledger may send back to a client +/// The different result codes that the ledger may send back to a client /// indicating the status of their submitted tx #[derive( Debug, @@ -49,7 +49,7 @@ use crate::types::transaction::protocol::ProtocolTx; Serialize, Deserialize, )] -pub enum ErrorCodes { +pub enum ResultCode { /// Success Ok = 0, /// Error in WASM tx execution @@ -82,12 +82,12 @@ pub enum ErrorCodes { TooLarge = 14, } -impl ErrorCodes { - /// Checks if the given [`ErrorCodes`] value is a protocol level error, +impl ResultCode { + /// Checks if the given [`ResultCode`] value is a protocol level error, /// that can be recovered from at the finalize block stage. pub const fn is_recoverable(&self) -> bool { - use ErrorCodes::*; - // NOTE: pattern match on all `ErrorCodes` variants, in order + use ResultCode::*; + // NOTE: pattern match on all `ResultCode` variants, in order // to catch potential bugs when adding new codes match self { Ok | WasmRuntimeError => true, @@ -109,25 +109,25 @@ impl ErrorCodes { } } -impl From for String { - fn from(code: ErrorCodes) -> String { +impl From for String { + fn from(code: ResultCode) -> String { code.to_string() } } -impl From for u32 { - fn from(code: ErrorCodes) -> u32 { +impl From for u32 { + fn from(code: ResultCode) -> u32 { code.to_u32() } } -impl Display for ErrorCodes { +impl Display for ResultCode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.to_u32()) } } -impl FromStr for ErrorCodes { +impl FromStr for ResultCode { type Err = std::io::Error; fn from_str(s: &str) -> Result { @@ -143,8 +143,8 @@ impl FromStr for ErrorCodes { } } -impl From for crate::tendermint::abci::Code { - fn from(value: ErrorCodes) -> Self { +impl From for crate::tendermint::abci::Code { + fn from(value: ResultCode) -> Self { Self::from(value.to_u32()) } } diff --git a/sdk/src/rpc.rs b/sdk/src/rpc.rs index d81ed46290..41ed33a87d 100644 --- a/sdk/src/rpc.rs +++ b/sdk/src/rpc.rs @@ -26,7 +26,7 @@ use namada_core::types::storage::{ use namada_core::types::token::{ Amount, DenominatedAmount, Denomination, MaspDenom, }; -use namada_core::types::transaction::{ErrorCodes, TxResult}; +use namada_core::types::transaction::{ResultCode, TxResult}; use namada_core::types::{storage, token}; use namada_proof_of_stake::parameters::PosParams; use namada_proof_of_stake::types::{ @@ -533,11 +533,11 @@ pub struct TxResponse { /// Response log pub log: String, /// Block height - pub height: String, + pub height: BlockHeight, /// Transaction height pub hash: String, /// Response code - pub code: ErrorCodes, + pub code: ResultCode, /// Gas used. If there's an `inner_tx`, its gas is equal to this value. pub gas_used: String, } @@ -575,11 +575,13 @@ impl TryFrom for TxResponse { .get("log") .ok_or_else(|| missing_field_err("log"))? .clone(); - let height = event - .get("height") - .ok_or_else(|| missing_field_err("height"))? - .clone(); - let code = ErrorCodes::from_str( + let height = BlockHeight::from_str( + event + .get("height") + .ok_or_else(|| missing_field_err("height"))?, + ) + .map_err(|e| e.to_string())?; + let code = ResultCode::from_str( event.get("code").ok_or_else(|| missing_field_err("code"))?, ) .map_err(|e| e.to_string())?; @@ -681,13 +683,21 @@ pub async fn query_tx_response( // Summarize the transaction results that we were searching for let inner_tx = event_map .get("inner_tx") - .map(|s| TxResult::from_str(s).unwrap()); - let code = ErrorCodes::from_str(event_map["code"]).unwrap(); + .map(|s| { + TxResult::from_str(s).map_err(|_| { + TError::parse("Error parsing TxResult".to_string()) + }) + }) + .transpose()?; + let code = ResultCode::from_str(event_map["code"]) + .map_err(|_| TError::parse("Error parsing ResultCode".to_string()))?; + let height = BlockHeight::from_str(event_map["height"]) + .map_err(|_| TError::parse("Error parsing BlockHeight".to_string()))?; let result = TxResponse { inner_tx, info: event_map["info"].to_string(), log: event_map["log"].to_string(), - height: event_map["height"].to_string(), + height, hash: event_map["hash"].to_string(), code, gas_used: event_map["gas_used"].to_string(), diff --git a/sdk/src/tx.rs b/sdk/src/tx.rs index 62acb2e2ac..0a44734f8f 100644 --- a/sdk/src/tx.rs +++ b/sdk/src/tx.rs @@ -45,7 +45,7 @@ use namada_core::types::transaction::governance::{ InitProposalData, VoteProposalData, }; use namada_core::types::transaction::pgf::UpdateStewardCommission; -use namada_core::types::transaction::{pos, ErrorCodes, TxResult}; +use namada_core::types::transaction::{pos, ResultCode, TxResult}; use namada_core::types::{storage, token}; use namada_proof_of_stake::parameters::PosParams; use namada_proof_of_stake::types::{CommissionPair, ValidatorState}; @@ -137,7 +137,13 @@ impl ProcessTxResponse { // always returns false for dry-run transactions. pub fn is_applied_and_valid(&self) -> bool { match self { - ProcessTxResponse::Applied(resp) => resp.code == ErrorCodes::Ok, + ProcessTxResponse::Applied(resp) => { + resp.code == ResultCode::Ok + && matches!( + resp.inner_tx_result(), + InnerTxResult::Success(_) + ) + } ProcessTxResponse::DryRun(_) | ProcessTxResponse::Broadcast(_) => { false } @@ -418,7 +424,7 @@ pub fn display_wrapper_resp_and_get_result( context: &impl Namada, resp: &TxResponse, ) -> bool { - let result = if resp.code != ErrorCodes::Ok { + let result = if resp.code != ResultCode::Ok { display_line!( context.io(), "Wrapper transaction failed with error code {}. Used {} gas.", @@ -429,7 +435,8 @@ pub fn display_wrapper_resp_and_get_result( } else { display_line!( context.io(), - "Wrapper transaction accepted. Used {} gas.", + "Wrapper transaction accepted at height {}. Used {} gas.", + resp.height, resp.gas_used, ); true @@ -448,7 +455,9 @@ pub fn display_inner_resp(context: &impl Namada, resp: &TxResponse) { InnerTxResult::Success(inner) => { display_line!( context.io(), - "Transaction was successfully applied. Used {} gas.", + "Transaction was successfully applied at height {}. Used {} \ + gas.", + resp.height, inner.gas_used, ); } From f167f2b5b8396acf148fcc9cf992daf89f3b48a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 14 Dec 2023 10:58:30 +0000 Subject: [PATCH 160/216] changelog: add #2276 --- .changelog/unreleased/SDK/2276-nicer-client-tx-result.md | 2 ++ .../unreleased/improvements/2276-nicer-client-tx-result.md | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 .changelog/unreleased/SDK/2276-nicer-client-tx-result.md create mode 100644 .changelog/unreleased/improvements/2276-nicer-client-tx-result.md diff --git a/.changelog/unreleased/SDK/2276-nicer-client-tx-result.md b/.changelog/unreleased/SDK/2276-nicer-client-tx-result.md new file mode 100644 index 0000000000..0d2d22586c --- /dev/null +++ b/.changelog/unreleased/SDK/2276-nicer-client-tx-result.md @@ -0,0 +1,2 @@ +- Improved the TxResponse type. + ([\#2276](https://github.com/anoma/namada/pull/2276)) \ No newline at end of file diff --git a/.changelog/unreleased/improvements/2276-nicer-client-tx-result.md b/.changelog/unreleased/improvements/2276-nicer-client-tx-result.md new file mode 100644 index 0000000000..f6b555e4fc --- /dev/null +++ b/.changelog/unreleased/improvements/2276-nicer-client-tx-result.md @@ -0,0 +1,2 @@ +- Client: Improved output of transaction results. + ([\#2276](https://github.com/anoma/namada/pull/2276)) \ No newline at end of file From 68db8f650fda3a04b7bbc830f34eb1b3bcccf67d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 19 Dec 2023 16:55:08 +0000 Subject: [PATCH 161/216] tests: replace expected tx strings with consts --- tests/src/e2e/eth_bridge_tests.rs | 15 ++--- tests/src/e2e/helpers.rs | 3 +- tests/src/e2e/ibc_tests.rs | 11 ++-- tests/src/e2e/ledger_tests.rs | 79 ++++++++++++----------- tests/src/e2e/multitoken_tests/helpers.rs | 5 +- tests/src/integration/masp.rs | 17 +++-- tests/src/lib.rs | 2 + tests/src/strings.rs | 4 ++ 8 files changed, 72 insertions(+), 64 deletions(-) create mode 100644 tests/src/strings.rs diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index 90f786a64c..2264d9cc3b 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -46,6 +46,7 @@ use crate::e2e::setup::constants::{ ALBERT, ALBERT_KEY, BERTHA, BERTHA_KEY, NAM, }; use crate::e2e::setup::{Bin, Who}; +use crate::strings::TX_APPLIED_SUCCESS; use crate::{run, run_as}; /// # Examples @@ -176,7 +177,7 @@ async fn test_roundtrip_eth_transfer() -> Result<()> { Some(CLIENT_COMMAND_TIMEOUT_SECONDS) )?; namadac_tx.exp_string("Wrapper transaction accepted")?; - namadac_tx.exp_string("Transaction was successfully applied")?; + namadac_tx.exp_string(TX_APPLIED_SUCCESS)?; drop(namadac_tx); let mut namadar = run!( @@ -369,9 +370,7 @@ async fn test_bridge_pool_e2e() { namadac_tx .exp_string("Wrapper transaction accepted") .unwrap(); - namadac_tx - .exp_string("Transaction was successfully applied") - .unwrap(); + namadac_tx.exp_string(TX_APPLIED_SUCCESS).unwrap(); drop(namadac_tx); let mut namadar = run!( @@ -897,7 +896,7 @@ async fn test_wdai_transfer_implicit_to_implicit() -> Result<()> { second_transfer_amount, )?; cmd.exp_string("Wrapper transaction accepted")?; - cmd.exp_string("Transaction was successfully applied")?; + cmd.exp_string(TX_APPLIED_SUCCESS)?; cmd.assert_success(); let albert_wdai_balance = find_wrapped_erc20_balance( @@ -975,7 +974,7 @@ async fn test_wdai_transfer_implicit_to_established() -> Result<()> { second_transfer_amount, )?; cmd.exp_string("Wrapper transaction accepted")?; - cmd.exp_string("Transaction was successfully applied")?; + cmd.exp_string(TX_APPLIED_SUCCESS)?; cmd.assert_success(); let albert_wdai_balance = find_wrapped_erc20_balance( @@ -1056,7 +1055,7 @@ async fn test_wdai_transfer_established_to_implicit() -> Result<()> { second_transfer_amount, )?; cmd.exp_string("Wrapper transaction accepted")?; - cmd.exp_string("Transaction was successfully applied")?; + cmd.exp_string(TX_APPLIED_SUCCESS)?; cmd.assert_success(); let albert_established_wdai_balance = find_wrapped_erc20_balance( @@ -1146,7 +1145,7 @@ async fn test_wdai_transfer_established_to_established() -> Result<()> { second_transfer_amount, )?; cmd.exp_string("Wrapper transaction accepted")?; - cmd.exp_string("Transaction was successfully applied")?; + cmd.exp_string(TX_APPLIED_SUCCESS)?; cmd.assert_success(); let albert_established_wdai_balance = find_wrapped_erc20_balance( diff --git a/tests/src/e2e/helpers.rs b/tests/src/e2e/helpers.rs index f3d2f40858..ea1beb959f 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -32,6 +32,7 @@ use super::setup::{ ENV_VAR_USE_PREBUILT_BINARIES, }; use crate::e2e::setup::{Bin, Who, APPS_PACKAGE}; +use crate::strings::TX_APPLIED_SUCCESS; use crate::{run, run_as}; /// Instantiate a new [`HttpClient`] to perform RPC requests with. @@ -96,7 +97,7 @@ pub fn init_established_account( ]; let mut cmd = run!(test, Bin::Client, init_account_args, Some(40))?; cmd.exp_string("Wrapper transaction accepted")?; - cmd.exp_string("Transaction was successfully applied")?; + cmd.exp_string(TX_APPLIED_SUCCESS)?; cmd.assert_success(); Ok(()) } diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index bfd6a25686..660ccfce10 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -88,6 +88,7 @@ use crate::e2e::helpers::{ use crate::e2e::setup::{ self, sleep, working_dir, Bin, NamadaCmd, Test, TestDir, Who, }; +use crate::strings::TX_APPLIED_SUCCESS; use crate::{run, run_as}; #[test] @@ -887,7 +888,7 @@ fn transfer_received_token( ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; client.exp_string("Wrapper transaction accepted")?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); Ok(()) @@ -1200,9 +1201,9 @@ fn submit_ibc_tx( ], Some(40) )?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; if wait_reveal_pk { - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; } check_tx_height(test, &mut client) } @@ -1265,9 +1266,9 @@ fn transfer( Ok(0) } None => { - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; if wait_reveal_pk { - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; } check_tx_height(test, &mut client) } diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index c723fee0cf..400a6b50be 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -51,6 +51,7 @@ use crate::e2e::setup::{ self, allow_duplicate_ips, default_port_offset, set_validators, sleep, Bin, Who, }; +use crate::strings::TX_APPLIED_SUCCESS; use crate::{run, run_as}; fn start_namada_ledger_node( @@ -184,7 +185,7 @@ fn test_node_connectivity_and_consensus() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); // 4. Check that all the nodes processed the tx with the same result @@ -574,7 +575,7 @@ fn ledger_txs_and_queries() -> Result<()> { if !dry_run { client.exp_string("Wrapper transaction accepted")?; } - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); } } @@ -728,7 +729,7 @@ fn wrapper_disposable_signer() -> Result<()> { let mut client = run!(test, Bin::Client, tx_args, Some(720))?; client.exp_string("Wrapper transaction accepted")?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; let _ep1 = epoch_sleep(&test, &validator_one_rpc, 720)?; let tx_args = vec![ @@ -750,7 +751,7 @@ fn wrapper_disposable_signer() -> Result<()> { let mut client = run!(test, Bin::Client, tx_args, Some(720))?; client.exp_string("Wrapper transaction accepted")?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; let _ep1 = epoch_sleep(&test, &validator_one_rpc, 720)?; let tx_args = vec![ "transfer", @@ -955,7 +956,7 @@ fn pos_bonds() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); // 3. Submit a delegation to the first genesis validator @@ -973,7 +974,7 @@ fn pos_bonds() -> Result<()> { &validator_0_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); // 4. Submit a re-delegation from the first to the second genesis validator @@ -993,7 +994,7 @@ fn pos_bonds() -> Result<()> { &validator_0_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); // 5. Submit an unbond of the self-bond @@ -1089,7 +1090,7 @@ fn pos_bonds() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); // 10. Submit a withdrawal of the delegation @@ -1105,7 +1106,7 @@ fn pos_bonds() -> Result<()> { &validator_0_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); // 11. Submit an withdrawal of the re-delegation @@ -1121,7 +1122,7 @@ fn pos_bonds() -> Result<()> { &validator_0_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); Ok(()) @@ -1267,7 +1268,7 @@ fn pos_rewards() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); // Query the validator balance again and check that the balance has grown @@ -1340,7 +1341,7 @@ fn test_bond_queries() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); // 3. Submit a delegation to the genesis validator @@ -1358,7 +1359,7 @@ fn test_bond_queries() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); // 3. Wait for epoch 4 @@ -1389,7 +1390,7 @@ fn test_bond_queries() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); // 5. Submit an unbond of the delegation @@ -1407,7 +1408,7 @@ fn test_bond_queries() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; let (_, res) = client .exp_regex(r"withdrawable starting from epoch [0-9]+") .unwrap(); @@ -1526,7 +1527,7 @@ fn pos_init_validator() -> Result<()> { "--unsafe-dont-encrypt", ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); // 3. Submit a delegation to the new validator @@ -1547,7 +1548,7 @@ fn pos_init_validator() -> Result<()> { &non_validator_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); // Then self-bond the tokens: let delegation = 5_u64; @@ -1566,7 +1567,7 @@ fn pos_init_validator() -> Result<()> { &non_validator_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); // 4. Transfer some NAM to the new validator @@ -1587,7 +1588,7 @@ fn pos_init_validator() -> Result<()> { &non_validator_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); // 5. Submit a self-bond for the new validator @@ -1601,7 +1602,7 @@ fn pos_init_validator() -> Result<()> { &non_validator_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); // Stop the non-validator node and run it as the new validator @@ -1722,7 +1723,7 @@ fn ledger_many_txs_in_a_block() -> Result<()> { args.push(&*validator_one_rpc); let mut client = run!(*test, Bin::Client, args, Some(80))?; client.exp_string("Wrapper transaction accepted")?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); let res: Result<()> = Ok(()); res @@ -1797,7 +1798,7 @@ fn proposal_submission() -> Result<()> { &validator_0_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); // 2. Submit valid proposal @@ -1820,7 +1821,7 @@ fn proposal_submission() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); // Wait for the proposal to be committed @@ -1949,7 +1950,7 @@ fn proposal_submission() -> Result<()> { submit_proposal_vote, Some(15) )?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); let submit_proposal_vote_delagator = vec![ @@ -1966,7 +1967,7 @@ fn proposal_submission() -> Result<()> { let mut client = run!(test, Bin::Client, submit_proposal_vote_delagator, Some(40))?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); // 10. Send a yay vote from a non-validator/non-delegator user @@ -1985,7 +1986,7 @@ fn proposal_submission() -> Result<()> { // this is valid because the client filter ALBERT delegation and there are // none let mut client = run!(test, Bin::Client, submit_proposal_vote, Some(15))?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); // 11. Query the proposal and check the result @@ -2121,7 +2122,7 @@ fn pgf_governance_proposal() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); // 1 - Submit proposal @@ -2144,7 +2145,7 @@ fn pgf_governance_proposal() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); // 2 - Query the proposal @@ -2217,7 +2218,7 @@ fn pgf_governance_proposal() -> Result<()> { submit_proposal_vote, Some(15) )?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); // Send different yay vote from delegator to check majority on 1/3 @@ -2235,7 +2236,7 @@ fn pgf_governance_proposal() -> Result<()> { let mut client = run!(test, Bin::Client, submit_proposal_vote_delagator, Some(40))?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); // 4 - Query the proposal and check the result is the one voted by the @@ -2333,7 +2334,7 @@ fn pgf_governance_proposal() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); // 2 - Query the funding proposal @@ -2424,7 +2425,7 @@ fn proposal_offline() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); // 2. Create an offline proposal @@ -2808,7 +2809,7 @@ fn double_signing_gets_slashed() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); // Wait until pipeline epoch to see if the validator is back in consensus @@ -3143,7 +3144,7 @@ fn deactivate_and_reactivate_validator() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(1), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); let deactivate_epoch = get_epoch(&test, &validator_1_rpc)?; @@ -3186,7 +3187,7 @@ fn deactivate_and_reactivate_validator() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(1), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); let reactivate_epoch = get_epoch(&test, &validator_1_rpc)?; @@ -3279,7 +3280,7 @@ fn change_validator_metadata() -> Result<()> { metadata_change_args, Some(40) )?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); // 4. Query the metadata after the change @@ -3312,7 +3313,7 @@ fn change_validator_metadata() -> Result<()> { metadata_change_args, Some(40) )?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); // 6. Query the metadata to see that the validator website is removed @@ -3444,7 +3445,7 @@ fn test_invalid_validator_txs() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(1), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); let deactivate_epoch = get_epoch(&test, &validator_1_rpc)?; @@ -3556,7 +3557,7 @@ fn change_consensus_key() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction was successfully applied")?; + client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); // ========================================================================= diff --git a/tests/src/e2e/multitoken_tests/helpers.rs b/tests/src/e2e/multitoken_tests/helpers.rs index 41887265b8..746a7dc746 100644 --- a/tests/src/e2e/multitoken_tests/helpers.rs +++ b/tests/src/e2e/multitoken_tests/helpers.rs @@ -17,6 +17,7 @@ use super::setup::constants::NAM; use super::setup::{Bin, NamadaCmd, Test}; use crate::e2e::setup::constants::ALBERT; use crate::run; +use crate::strings::TX_APPLIED_SUCCESS; const MULTITOKEN_KEY_SEGMENT: &str = "tokens"; const BALANCE_KEY_SEGMENT: &str = "balance"; @@ -55,7 +56,7 @@ pub fn init_multitoken_vp(test: &Test, rpc_addr: &str) -> Result { ]; let mut cmd = run!(test, Bin::Client, init_account_args, Some(40))?; cmd.exp_string("Wrapper transaction accepted")?; - cmd.exp_string("Transaction was successfully applied")?; + cmd.exp_string(TX_APPLIED_SUCCESS)?; cmd.assert_success(); Ok(multitoken_alias.to_string()) } @@ -121,7 +122,7 @@ pub fn mint_red_tokens( ]; let mut cmd = run!(test, Bin::Client, tx_args, Some(40))?; cmd.exp_string("Wrapper transaction accepted")?; - cmd.exp_string("Transaction was successfully applied")?; + cmd.exp_string(TX_APPLIED_SUCCESS)?; cmd.assert_success(); Ok(()) } diff --git a/tests/src/integration/masp.rs b/tests/src/integration/masp.rs index 0dbdf1daa7..8847d5cd3c 100644 --- a/tests/src/integration/masp.rs +++ b/tests/src/integration/masp.rs @@ -14,6 +14,7 @@ use crate::e2e::setup::constants::{ BB_PAYMENT_ADDRESS, BERTHA, BERTHA_KEY, BTC, B_SPENDING_KEY, CHRISTEL, CHRISTEL_KEY, ETH, MASP, NAM, }; +use crate::strings::TX_APPLIED_SUCCESS; /// In this test we verify that users of the MASP receive the correct rewards /// for leaving their assets in the pool for varying periods of time. @@ -919,7 +920,7 @@ fn masp_txs_and_queries() -> Result<()> { "--node", validator_one_rpc, ], - Response::Ok("Transaction was successfully applied"), + Response::Ok(TX_APPLIED_SUCCESS), ), // 3. Attempt to spend 10 ETH at SK(A) to PA(B) ( @@ -957,7 +958,7 @@ fn masp_txs_and_queries() -> Result<()> { "--node", validator_one_rpc, ], - Response::Ok("Transaction was successfully applied"), + Response::Ok(TX_APPLIED_SUCCESS), ), // 5. Spend 7 BTC at SK(A) to PA(B) ( @@ -976,7 +977,7 @@ fn masp_txs_and_queries() -> Result<()> { "--node", validator_one_rpc, ], - Response::Ok("Transaction was successfully applied"), + Response::Ok(TX_APPLIED_SUCCESS), ), // 6. Attempt to spend 7 BTC at SK(A) to PA(B) ( @@ -1014,7 +1015,7 @@ fn masp_txs_and_queries() -> Result<()> { "--node", validator_one_rpc, ], - Response::Ok("Transaction was successfully applied"), + Response::Ok(TX_APPLIED_SUCCESS), ), // 8. Assert BTC balance at VK(A) is 0 ( @@ -1070,7 +1071,7 @@ fn masp_txs_and_queries() -> Result<()> { "--node", validator_one_rpc, ], - Response::Ok("Transaction was successfully applied"), + Response::Ok(TX_APPLIED_SUCCESS), ), ]; @@ -1093,7 +1094,7 @@ fn masp_txs_and_queries() -> Result<()> { let captured = CapturedOutput::of(|| run(&node, Bin::Client, tx_args.clone())); match tx_result { - Response::Ok("Transaction was successfully applied") => { + Response::Ok(TX_APPLIED_SUCCESS) => { assert!( captured.result.is_ok(), "{:?} failed with result {:?}.\n Unread output: {}", @@ -1105,9 +1106,7 @@ fn masp_txs_and_queries() -> Result<()> { node.assert_success(); } else { assert!( - captured.contains( - "Transaction was successfully applied" - ), + captured.contains(TX_APPLIED_SUCCESS), "{:?} failed to contain needle 'Transaction is \ valid',\nGot output '{}'", tx_args, diff --git a/tests/src/lib.rs b/tests/src/lib.rs index 1d9958a30a..ca55eba207 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -17,6 +17,8 @@ pub mod native_vp; pub mod storage; #[cfg(test)] mod storage_api; +#[cfg(test)] +pub mod strings; /// Using this import requires `tracing` and `tracing-subscriber` dependencies. /// Set env var `RUST_LOG=info` to see the logs from a test run (and diff --git a/tests/src/strings.rs b/tests/src/strings.rs new file mode 100644 index 0000000000..628b14e420 --- /dev/null +++ b/tests/src/strings.rs @@ -0,0 +1,4 @@ +//! Expected strings for integration and e2e tests. + +/// Inner tx applied and accepted by VPs. +pub const TX_APPLIED_SUCCESS: &str = "Transaction was successfully applied"; From fe0d4033816d93fe5c42019d1b74909cc2690f37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 19 Dec 2023 17:15:42 +0000 Subject: [PATCH 162/216] tests: replace expected tx failure string with a const --- tests/src/e2e/eth_bridge_tests.rs | 22 +++++----- tests/src/e2e/helpers.rs | 4 +- tests/src/e2e/ibc_tests.rs | 50 +++++++++-------------- tests/src/e2e/ledger_tests.rs | 26 ++++++------ tests/src/e2e/multitoken_tests/helpers.rs | 6 +-- tests/src/strings.rs | 9 ++++ 6 files changed, 57 insertions(+), 60 deletions(-) diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index 2264d9cc3b..d823247690 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -46,7 +46,7 @@ use crate::e2e::setup::constants::{ ALBERT, ALBERT_KEY, BERTHA, BERTHA_KEY, NAM, }; use crate::e2e::setup::{Bin, Who}; -use crate::strings::TX_APPLIED_SUCCESS; +use crate::strings::{TX_ACCEPTED, TX_APPLIED_SUCCESS}; use crate::{run, run_as}; /// # Examples @@ -176,7 +176,7 @@ async fn test_roundtrip_eth_transfer() -> Result<()> { tx_args, Some(CLIENT_COMMAND_TIMEOUT_SECONDS) )?; - namadac_tx.exp_string("Wrapper transaction accepted")?; + namadac_tx.exp_string(TX_ACCEPTED)?; namadac_tx.exp_string(TX_APPLIED_SUCCESS)?; drop(namadac_tx); @@ -367,9 +367,7 @@ async fn test_bridge_pool_e2e() { Some(CLIENT_COMMAND_TIMEOUT_SECONDS) ) .unwrap(); - namadac_tx - .exp_string("Wrapper transaction accepted") - .unwrap(); + namadac_tx.exp_string(TX_ACCEPTED).unwrap(); namadac_tx.exp_string(TX_APPLIED_SUCCESS).unwrap(); drop(namadac_tx); @@ -768,7 +766,7 @@ async fn test_wdai_transfer_implicit_unauthorized() -> Result<()> { denom: 0u8.into(), }, )?; - cmd.exp_string("Transaction was rejected by VPs.")?; + cmd.exp_string(TX_REJECTED)?; cmd.assert_success(); // check balances are unchanged after an unsuccessful transfer @@ -837,8 +835,8 @@ async fn test_wdai_transfer_established_unauthorized() -> Result<()> { denom: 0u8.into(), }, )?; - cmd.exp_string("Wrapper transaction accepted")?; - cmd.exp_string("Transaction was rejected by VPs")?; + cmd.exp_string(TX_ACCEPTED)?; + cmd.exp_string(TX_REJECTED)?; cmd.assert_success(); // check balances are unchanged after an unsuccessful transfer @@ -895,7 +893,7 @@ async fn test_wdai_transfer_implicit_to_implicit() -> Result<()> { &albert_addr.to_string(), second_transfer_amount, )?; - cmd.exp_string("Wrapper transaction accepted")?; + cmd.exp_string(TX_ACCEPTED)?; cmd.exp_string(TX_APPLIED_SUCCESS)?; cmd.assert_success(); @@ -973,7 +971,7 @@ async fn test_wdai_transfer_implicit_to_established() -> Result<()> { &albert_addr.to_string(), second_transfer_amount, )?; - cmd.exp_string("Wrapper transaction accepted")?; + cmd.exp_string(TX_ACCEPTED)?; cmd.exp_string(TX_APPLIED_SUCCESS)?; cmd.assert_success(); @@ -1054,7 +1052,7 @@ async fn test_wdai_transfer_established_to_implicit() -> Result<()> { &albert_established_addr.to_string(), second_transfer_amount, )?; - cmd.exp_string("Wrapper transaction accepted")?; + cmd.exp_string(TX_ACCEPTED)?; cmd.exp_string(TX_APPLIED_SUCCESS)?; cmd.assert_success(); @@ -1144,7 +1142,7 @@ async fn test_wdai_transfer_established_to_established() -> Result<()> { &albert_established_addr.to_string(), second_transfer_amount, )?; - cmd.exp_string("Wrapper transaction accepted")?; + cmd.exp_string(TX_ACCEPTED)?; cmd.exp_string(TX_APPLIED_SUCCESS)?; cmd.assert_success(); diff --git a/tests/src/e2e/helpers.rs b/tests/src/e2e/helpers.rs index ea1beb959f..fbd9f8d117 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -32,7 +32,7 @@ use super::setup::{ ENV_VAR_USE_PREBUILT_BINARIES, }; use crate::e2e::setup::{Bin, Who, APPS_PACKAGE}; -use crate::strings::TX_APPLIED_SUCCESS; +use crate::strings::{TX_ACCEPTED, TX_APPLIED_SUCCESS}; use crate::{run, run_as}; /// Instantiate a new [`HttpClient`] to perform RPC requests with. @@ -96,7 +96,7 @@ pub fn init_established_account( rpc_addr, ]; let mut cmd = run!(test, Bin::Client, init_account_args, Some(40))?; - cmd.exp_string("Wrapper transaction accepted")?; + cmd.exp_string(TX_ACCEPTED)?; cmd.exp_string(TX_APPLIED_SUCCESS)?; cmd.assert_success(); Ok(()) diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index 660ccfce10..4b9cc96cb9 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -88,7 +88,7 @@ use crate::e2e::helpers::{ use crate::e2e::setup::{ self, sleep, working_dir, Bin, NamadaCmd, Test, TestDir, Who, }; -use crate::strings::TX_APPLIED_SUCCESS; +use crate::strings::{TX_ACCEPTED, TX_APPLIED_SUCCESS, TX_FAILED}; use crate::{run, run_as}; #[test] @@ -339,11 +339,11 @@ fn create_client(test_a: &Test, test_b: &Test) -> Result<(ClientId, ClientId)> { let height_b = submit_ibc_tx(test_b, message, ALBERT, ALBERT_KEY, false)?; let events = get_events(test_a, height_a)?; - let client_id_a = get_client_id_from_events(&events) - .ok_or(eyre!("Transaction failed"))?; + let client_id_a = + get_client_id_from_events(&events).ok_or(eyre!(TX_FAILED))?; let events = get_events(test_b, height_b)?; - let client_id_b = get_client_id_from_events(&events) - .ok_or(eyre!("Transaction failed"))?; + let client_id_b = + get_client_id_from_events(&events).ok_or(eyre!(TX_FAILED))?; // `client_id_a` represents the ID of the B's client on Chain A Ok((client_id_a, client_id_b)) @@ -618,8 +618,8 @@ fn channel_handshake( }; let height = submit_ibc_tx(test_a, msg, ALBERT, ALBERT_KEY, false)?; let events = get_events(test_a, height)?; - let channel_id_a = get_channel_id_from_events(&events) - .ok_or(eyre!("Transaction failed"))?; + let channel_id_a = + get_channel_id_from_events(&events).ok_or(eyre!(TX_FAILED))?; // get the proofs from Chain A let height_a = query_height(test_a)?; @@ -645,8 +645,8 @@ fn channel_handshake( // OpenTryChannel on Chain B let height = submit_ibc_tx(test_b, msg, ALBERT, ALBERT_KEY, false)?; let events = get_events(test_b, height)?; - let channel_id_b = get_channel_id_from_events(&events) - .ok_or(eyre!("Transaction failed"))?; + let channel_id_b = + get_channel_id_from_events(&events).ok_or(eyre!(TX_FAILED))?; // get the A's proofs on Chain B let height_b = query_height(test_b)?; @@ -758,8 +758,7 @@ fn transfer_token( false, )?; let events = get_events(test_a, height)?; - let packet = - get_packet_from_events(&events).ok_or(eyre!("Transaction failed"))?; + let packet = get_packet_from_events(&events).ok_or(eyre!(TX_FAILED))?; check_ibc_packet_query(test_a, &"send_packet".parse().unwrap(), &packet)?; let height_a = query_height(test_a)?; @@ -776,10 +775,8 @@ fn transfer_token( // Receive the token on Chain B let height = submit_ibc_tx(test_b, msg, ALBERT, ALBERT_KEY, false)?; let events = get_events(test_b, height)?; - let packet = - get_packet_from_events(&events).ok_or(eyre!("Transaction failed"))?; - let ack = - get_ack_from_events(&events).ok_or(eyre!("Transaction failed"))?; + let packet = get_packet_from_events(&events).ok_or(eyre!(TX_FAILED))?; + let ack = get_ack_from_events(&events).ok_or(eyre!(TX_FAILED))?; check_ibc_packet_query( test_b, &"write_acknowledgement".parse().unwrap(), @@ -887,7 +884,7 @@ fn transfer_received_token( &rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Wrapper transaction accepted")?; + client.exp_string(TX_ACCEPTED)?; client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); @@ -923,8 +920,7 @@ fn transfer_back( false, )?; let events = get_events(test_b, height)?; - let packet = - get_packet_from_events(&events).ok_or(eyre!("Transaction failed"))?; + let packet = get_packet_from_events(&events).ok_or(eyre!(TX_FAILED))?; let height_b = query_height(test_b)?; let proof = get_commitment_proof(test_b, &packet, height_b)?; @@ -939,10 +935,8 @@ fn transfer_back( // Receive the token on Chain A let height = submit_ibc_tx(test_a, msg, ALBERT, ALBERT_KEY, false)?; let events = get_events(test_a, height)?; - let packet = - get_packet_from_events(&events).ok_or(eyre!("Transaction failed"))?; - let ack = - get_ack_from_events(&events).ok_or(eyre!("Transaction failed"))?; + let packet = get_packet_from_events(&events).ok_or(eyre!(TX_FAILED))?; + let ack = get_ack_from_events(&events).ok_or(eyre!(TX_FAILED))?; // get the proof on Chain A let height_a = query_height(test_a)?; @@ -987,8 +981,7 @@ fn transfer_timeout( false, )?; let events = get_events(test_a, height)?; - let packet = - get_packet_from_events(&events).ok_or(eyre!("Transaction failed"))?; + let packet = get_packet_from_events(&events).ok_or(eyre!(TX_FAILED))?; // wait for the timeout sleep(5); @@ -1065,8 +1058,7 @@ fn shielded_transfer( false, )?; let events = get_events(test_a, height)?; - let packet = - get_packet_from_events(&events).ok_or(eyre!("Transaction failed"))?; + let packet = get_packet_from_events(&events).ok_or(eyre!(TX_FAILED))?; check_ibc_packet_query(test_a, &"send_packet".parse().unwrap(), &packet)?; let height_a = query_height(test_a)?; @@ -1083,10 +1075,8 @@ fn shielded_transfer( // Receive the token on Chain B let height = submit_ibc_tx(test_b, msg, ALBERT, ALBERT_KEY, false)?; let events = get_events(test_b, height)?; - let packet = - get_packet_from_events(&events).ok_or(eyre!("Transaction failed"))?; - let ack = - get_ack_from_events(&events).ok_or(eyre!("Transaction failed"))?; + let packet = get_packet_from_events(&events).ok_or(eyre!(TX_FAILED))?; + let ack = get_ack_from_events(&events).ok_or(eyre!(TX_FAILED))?; check_ibc_packet_query( test_b, &"write_acknowledgement".parse().unwrap(), diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 400a6b50be..91a8739b38 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -51,7 +51,7 @@ use crate::e2e::setup::{ self, allow_duplicate_ips, default_port_offset, set_validators, sleep, Bin, Who, }; -use crate::strings::TX_APPLIED_SUCCESS; +use crate::strings::{TX_ACCEPTED, TX_APPLIED_SUCCESS, TX_FAILED, TX_REJECTED}; use crate::{run, run_as}; fn start_namada_ledger_node( @@ -573,7 +573,7 @@ fn ledger_txs_and_queries() -> Result<()> { let mut client = run!(test, Bin::Client, tx_args, Some(40))?; if !dry_run { - client.exp_string("Wrapper transaction accepted")?; + client.exp_string(TX_ACCEPTED)?; } client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); @@ -728,7 +728,7 @@ fn wrapper_disposable_signer() -> Result<()> { ]; let mut client = run!(test, Bin::Client, tx_args, Some(720))?; - client.exp_string("Wrapper transaction accepted")?; + client.exp_string(TX_ACCEPTED)?; client.exp_string(TX_APPLIED_SUCCESS)?; let _ep1 = epoch_sleep(&test, &validator_one_rpc, 720)?; @@ -750,7 +750,7 @@ fn wrapper_disposable_signer() -> Result<()> { ]; let mut client = run!(test, Bin::Client, tx_args, Some(720))?; - client.exp_string("Wrapper transaction accepted")?; + client.exp_string(TX_ACCEPTED)?; client.exp_string(TX_APPLIED_SUCCESS)?; let _ep1 = epoch_sleep(&test, &validator_one_rpc, 720)?; let tx_args = vec![ @@ -823,8 +823,8 @@ fn invalid_transactions() -> Result<()> { ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Wrapper transaction accepted")?; - client.exp_string("Transaction was rejected by VPs")?; + client.exp_string(TX_ACCEPTED)?; + client.exp_string(TX_REJECTED)?; client.assert_success(); let mut ledger = bg_ledger.foreground(); @@ -875,8 +875,8 @@ fn invalid_transactions() -> Result<()> { ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Wrapper transaction accepted")?; - client.exp_string("Transaction failed.")?; + client.exp_string(TX_ACCEPTED)?; + client.exp_string(TX_FAILED)?; client.assert_success(); Ok(()) } @@ -1722,7 +1722,7 @@ fn ledger_many_txs_in_a_block() -> Result<()> { let mut args = (*tx_args).clone(); args.push(&*validator_one_rpc); let mut client = run!(*test, Bin::Client, args, Some(80))?; - client.exp_string("Wrapper transaction accepted")?; + client.exp_string(TX_ACCEPTED)?; client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); let res: Result<()> = Ok(()); @@ -3398,7 +3398,7 @@ fn test_invalid_validator_txs() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction was rejected by VPs")?; + client.exp_string(TX_REJECTED)?; client.assert_success(); // Try to deactivate validator-1 as validator-0 @@ -3413,7 +3413,7 @@ fn test_invalid_validator_txs() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction was rejected by VPs")?; + client.exp_string(TX_REJECTED)?; client.assert_success(); // Try to change the validator-1 website as validator-0 @@ -3430,7 +3430,7 @@ fn test_invalid_validator_txs() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction was rejected by VPs")?; + client.exp_string(TX_REJECTED)?; client.assert_success(); // Deactivate validator-1 @@ -3488,7 +3488,7 @@ fn test_invalid_validator_txs() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction was rejected by VPs")?; + client.exp_string(TX_REJECTED)?; client.assert_success(); Ok(()) diff --git a/tests/src/e2e/multitoken_tests/helpers.rs b/tests/src/e2e/multitoken_tests/helpers.rs index 746a7dc746..caddb88f41 100644 --- a/tests/src/e2e/multitoken_tests/helpers.rs +++ b/tests/src/e2e/multitoken_tests/helpers.rs @@ -17,7 +17,7 @@ use super::setup::constants::NAM; use super::setup::{Bin, NamadaCmd, Test}; use crate::e2e::setup::constants::ALBERT; use crate::run; -use crate::strings::TX_APPLIED_SUCCESS; +use crate::strings::{TX_ACCEPTED, TX_APPLIED_SUCCESS}; const MULTITOKEN_KEY_SEGMENT: &str = "tokens"; const BALANCE_KEY_SEGMENT: &str = "balance"; @@ -55,7 +55,7 @@ pub fn init_multitoken_vp(test: &Test, rpc_addr: &str) -> Result { rpc_addr, ]; let mut cmd = run!(test, Bin::Client, init_account_args, Some(40))?; - cmd.exp_string("Wrapper transaction accepted")?; + cmd.exp_string(TX_ACCEPTED)?; cmd.exp_string(TX_APPLIED_SUCCESS)?; cmd.assert_success(); Ok(multitoken_alias.to_string()) @@ -121,7 +121,7 @@ pub fn mint_red_tokens( rpc_addr, ]; let mut cmd = run!(test, Bin::Client, tx_args, Some(40))?; - cmd.exp_string("Wrapper transaction accepted")?; + cmd.exp_string(TX_ACCEPTED)?; cmd.exp_string(TX_APPLIED_SUCCESS)?; cmd.assert_success(); Ok(()) diff --git a/tests/src/strings.rs b/tests/src/strings.rs index 628b14e420..6360394339 100644 --- a/tests/src/strings.rs +++ b/tests/src/strings.rs @@ -2,3 +2,12 @@ /// Inner tx applied and accepted by VPs. pub const TX_APPLIED_SUCCESS: &str = "Transaction was successfully applied"; + +/// Inner transaction rejected by VP(s). +pub const TX_REJECTED: &str = "Transaction was rejected by VPs"; + +/// Inner transaction failed in execution (no VPs ran). +pub const TX_FAILED: &str = "Transaction failed"; + +/// Wrapper transaction accepted. +pub const TX_ACCEPTED: &str = "Wrapper transaction accepted"; From d09bc55133bfb8dddbadb9b9f5aa6ae2739718d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 19 Dec 2023 17:07:50 +0000 Subject: [PATCH 163/216] tests: add consts for ledger startup exp strings --- tests/src/e2e/eth_bridge_tests.rs | 14 ++++--- tests/src/e2e/eth_bridge_tests/helpers.rs | 5 ++- tests/src/e2e/helpers.rs | 4 +- tests/src/e2e/ibc_tests.rs | 12 +++--- tests/src/e2e/ledger_tests.rs | 51 ++++++++++++----------- tests/src/strings.rs | 12 ++++++ 6 files changed, 59 insertions(+), 39 deletions(-) diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index d823247690..658b888eb1 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -46,7 +46,9 @@ use crate::e2e::setup::constants::{ ALBERT, ALBERT_KEY, BERTHA, BERTHA_KEY, NAM, }; use crate::e2e::setup::{Bin, Who}; -use crate::strings::{TX_ACCEPTED, TX_APPLIED_SUCCESS}; +use crate::strings::{ + LEDGER_STARTED, TX_ACCEPTED, TX_APPLIED_SUCCESS, VALIDATOR_NODE, +}; use crate::{run, run_as}; /// # Examples @@ -82,7 +84,7 @@ fn run_ledger_with_ethereum_events_endpoint() -> Result<()> { ledger.exp_string( "Starting to listen for Borsh-serialized Ethereum events", )?; - ledger.exp_string("Namada ledger node started")?; + ledger.exp_string(LEDGER_STARTED)?; ledger.send_control(ControlCode::EndOfText)?; ledger.exp_string( @@ -539,8 +541,8 @@ async fn test_wnam_transfer() -> Result<()> { let mut ledger = run_as!(test, Who::Validator(0), Bin::Node, vec!["ledger"], Some(40))?; - ledger.exp_string("Namada ledger node started")?; - ledger.exp_string("This node is a validator")?; + ledger.exp_string(LEDGER_STARTED)?; + ledger.exp_string(VALIDATOR_NODE)?; ledger.exp_regex(r"Committed block hash.*, height: [0-9]+")?; let bg_ledger = ledger.background(); @@ -635,8 +637,8 @@ fn test_configure_oracle_from_storage() -> Result<()> { let mut ledger = run_as!(test, Who::Validator(0), Bin::Node, vec!["ledger"], Some(40))?; - ledger.exp_string("Namada ledger node started")?; - ledger.exp_string("This node is a validator")?; + ledger.exp_string(LEDGER_STARTED)?; + ledger.exp_string(VALIDATOR_NODE)?; ledger.exp_regex(r"Committed block hash.*, height: [0-9]+")?; // check that the oracle has been configured with the values from storage let initial_config = oracle::config::Config { diff --git a/tests/src/e2e/eth_bridge_tests/helpers.rs b/tests/src/e2e/eth_bridge_tests/helpers.rs index 3c7241cdce..a7ba34a7ff 100644 --- a/tests/src/e2e/eth_bridge_tests/helpers.rs +++ b/tests/src/e2e/eth_bridge_tests/helpers.rs @@ -23,6 +23,7 @@ use crate::e2e::helpers::{ use crate::e2e::setup::{ self, set_ethereum_bridge_mode, Bin, NamadaBgCmd, NamadaCmd, Test, Who, }; +use crate::strings::{LEDGER_STARTED, VALIDATOR_NODE}; use crate::{run, run_as}; /// The default listen address for a self-hosted events endpoint. @@ -121,8 +122,8 @@ pub fn setup_single_validator_test() -> Result<(Test, NamadaBgCmd)> { let mut ledger = run_as!(test, Who::Validator(0), Bin::Node, vec!["ledger"], Some(40))?; - ledger.exp_string("Namada ledger node started")?; - ledger.exp_string("This node is a validator")?; + ledger.exp_string(LEDGER_STARTED)?; + ledger.exp_string(VALIDATOR_NODE)?; ledger.exp_regex(r"Committed block hash.*, height: [0-9]+")?; let bg_ledger = ledger.background(); diff --git a/tests/src/e2e/helpers.rs b/tests/src/e2e/helpers.rs index fbd9f8d117..7e6599c893 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -32,7 +32,7 @@ use super::setup::{ ENV_VAR_USE_PREBUILT_BINARIES, }; use crate::e2e::setup::{Bin, Who, APPS_PACKAGE}; -use crate::strings::{TX_ACCEPTED, TX_APPLIED_SUCCESS}; +use crate::strings::{LEDGER_STARTED, TX_ACCEPTED, TX_APPLIED_SUCCESS}; use crate::{run, run_as}; /// Instantiate a new [`HttpClient`] to perform RPC requests with. @@ -67,7 +67,7 @@ pub fn setup_single_node_test() -> Result<(Test, NamadaBgCmd)> { pub fn run_single_node_test_from(test: Test) -> Result<(Test, NamadaBgCmd)> { let mut ledger = run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; - ledger.exp_string("Namada ledger node started")?; + ledger.exp_string(LEDGER_STARTED)?; // TODO(namada#867): we only need to wait until the RPC server is available, // not necessarily for a block to be committed // ledger.exp_string("Starting RPC HTTP server on")?; diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index 4b9cc96cb9..16abb225d4 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -88,7 +88,9 @@ use crate::e2e::helpers::{ use crate::e2e::setup::{ self, sleep, working_dir, Bin, NamadaCmd, Test, TestDir, Who, }; -use crate::strings::{TX_ACCEPTED, TX_APPLIED_SUCCESS, TX_FAILED}; +use crate::strings::{ + LEDGER_STARTED, TX_ACCEPTED, TX_APPLIED_SUCCESS, TX_FAILED, VALIDATOR_NODE, +}; use crate::{run, run_as}; #[test] @@ -117,7 +119,7 @@ fn run_ledger_ibc() -> Result<()> { &["ledger", "run"], Some(40) )?; - ledger_a.exp_string("Namada ledger node started")?; + ledger_a.exp_string(LEDGER_STARTED)?; // Run Chain B let mut ledger_b = run_as!( test_b, @@ -126,9 +128,9 @@ fn run_ledger_ibc() -> Result<()> { &["ledger", "run"], Some(40) )?; - ledger_b.exp_string("Namada ledger node started")?; - ledger_a.exp_string("This node is a validator")?; - ledger_b.exp_string("This node is a validator")?; + ledger_b.exp_string(LEDGER_STARTED)?; + ledger_a.exp_string(VALIDATOR_NODE)?; + ledger_b.exp_string(VALIDATOR_NODE)?; wait_for_wasm_pre_compile(&mut ledger_a)?; wait_for_wasm_pre_compile(&mut ledger_b)?; diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 91a8739b38..a163695f1a 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -51,7 +51,10 @@ use crate::e2e::setup::{ self, allow_duplicate_ips, default_port_offset, set_validators, sleep, Bin, Who, }; -use crate::strings::{TX_ACCEPTED, TX_APPLIED_SUCCESS, TX_FAILED, TX_REJECTED}; +use crate::strings::{ + LEDGER_SHUTDOWN, LEDGER_STARTED, NON_VALIDATOR_NODE, TX_ACCEPTED, + TX_APPLIED_SUCCESS, TX_FAILED, TX_REJECTED, VALIDATOR_NODE, +}; use crate::{run, run_as}; fn start_namada_ledger_node( @@ -64,11 +67,11 @@ fn start_namada_ledger_node( _ => Who::NonValidator, }; let mut node = run_as!(test, who, Bin::Node, &["ledger"], timeout_sec)?; - node.exp_string("Namada ledger node started")?; + node.exp_string(LEDGER_STARTED)?; if let Who::Validator(_) = who { - node.exp_string("This node is a validator")?; + node.exp_string(VALIDATOR_NODE)?; } else { - node.exp_string("This node is not a validator")?; + node.exp_string(NON_VALIDATOR_NODE)?; } Ok(node) } @@ -104,16 +107,16 @@ fn run_ledger() -> Result<()> { for args in &cmd_combinations { let mut ledger = run_as!(test, Who::Validator(0), Bin::Node, args, Some(40))?; - ledger.exp_string("Namada ledger node started")?; - ledger.exp_string("This node is a validator")?; + ledger.exp_string(LEDGER_STARTED)?; + ledger.exp_string(VALIDATOR_NODE)?; } // Start the ledger as a non-validator for args in &cmd_combinations { let mut ledger = run_as!(test, Who::NonValidator, Bin::Node, args, Some(40))?; - ledger.exp_string("Namada ledger node started")?; - ledger.exp_string("This node is not a validator")?; + ledger.exp_string(LEDGER_STARTED)?; + ledger.exp_string(NON_VALIDATOR_NODE)?; } Ok(()) @@ -258,7 +261,7 @@ fn test_namada_shuts_down_if_tendermint_dies() -> Result<()> { ledger.exp_string("Tendermint node is no longer running.")?; // 4. Check that the ledger node shuts down - ledger.exp_string("Namada ledger node has shut down.")?; + ledger.exp_string(LEDGER_SHUTDOWN)?; ledger.exp_eof()?; Ok(()) @@ -300,7 +303,7 @@ fn run_ledger_load_state_and_reset() -> Result<()> { ledger.interrupt()?; // Wait for the node to stop running to finish writing the state and tx // queue - ledger.exp_string("Namada ledger node has shut down.")?; + ledger.exp_string(LEDGER_SHUTDOWN)?; ledger.exp_eof()?; drop(ledger); @@ -352,7 +355,7 @@ fn suspend_ledger() -> Result<()> { Some(40) )?; - ledger.exp_string("Namada ledger node started")?; + ledger.exp_string(LEDGER_STARTED)?; // There should be no previous state ledger.exp_string("No state could be found")?; // Wait to commit a block @@ -375,7 +378,7 @@ fn suspend_ledger() -> Result<()> { ledger.interrupt()?; // Wait for the node to stop running to finish writing the state and tx // queue - ledger.exp_string("Namada ledger node has shut down.")?; + ledger.exp_string(LEDGER_SHUTDOWN)?; ledger.exp_eof()?; Ok(()) } @@ -394,7 +397,7 @@ fn stop_ledger_at_height() -> Result<()> { Some(40) )?; - ledger.exp_string("Namada ledger node started")?; + ledger.exp_string(LEDGER_STARTED)?; // There should be no previous state ledger.exp_string("No state could be found")?; // Wait to commit a block @@ -837,7 +840,7 @@ fn invalid_transactions() -> Result<()> { ledger.interrupt()?; // Wait for the node to stop running to finish writing the state and tx // queue - ledger.exp_string("Namada ledger node has shut down.")?; + ledger.exp_string(LEDGER_SHUTDOWN)?; ledger.exp_eof()?; drop(ledger); @@ -1628,8 +1631,8 @@ fn pos_init_validator() -> Result<()> { loc, )?; - validator_1.exp_string("Namada ledger node started")?; - validator_1.exp_string("This node is a validator")?; + validator_1.exp_string(LEDGER_STARTED)?; + validator_1.exp_string(VALIDATOR_NODE)?; validator_1.exp_string("Committed block hash")?; let _bg_validator_1 = validator_1.background(); @@ -2608,14 +2611,14 @@ fn double_signing_gets_slashed() -> Result<()> { let mut validator_2 = run_as!(test, Who::Validator(2), Bin::Node, &["ledger"], Some(40))?; - validator_2.exp_string("Namada ledger node started")?; - validator_2.exp_string("This node is a validator")?; + validator_2.exp_string(LEDGER_STARTED)?; + validator_2.exp_string(VALIDATOR_NODE)?; let _bg_validator_2 = validator_2.background(); let mut validator_3 = run_as!(test, Who::Validator(3), Bin::Node, &["ledger"], Some(40))?; - validator_3.exp_string("Namada ledger node started")?; - validator_3.exp_string("This node is a validator")?; + validator_3.exp_string(LEDGER_STARTED)?; + validator_3.exp_string(VALIDATOR_NODE)?; let _bg_validator_3 = validator_3.background(); // 2. Copy the first genesis validator base-dir @@ -2706,8 +2709,8 @@ fn double_signing_gets_slashed() -> Result<()> { validator_0_base_dir_copy, loc, )?; - validator_0_copy.exp_string("Namada ledger node started")?; - validator_0_copy.exp_string("This node is a validator")?; + validator_0_copy.exp_string(LEDGER_STARTED)?; + validator_0_copy.exp_string(VALIDATOR_NODE)?; let _bg_validator_0_copy = validator_0_copy.background(); // 5. Submit a valid token transfer tx to validator 0 @@ -2845,7 +2848,7 @@ fn double_signing_gets_slashed() -> Result<()> { validator_1.interrupt()?; // Wait for the node to stop running to finish writing the state and tx // queue - validator_1.exp_string("Namada ledger node has shut down.")?; + validator_1.exp_string(LEDGER_SHUTDOWN)?; validator_1.assert_success(); Ok(()) @@ -3605,7 +3608,7 @@ fn change_consensus_key() -> Result<()> { let mut validator_0 = bg_validator_0.foreground(); validator_0.interrupt().unwrap(); // Wait for the node to stop running - validator_0.exp_string("Namada ledger node has shut down.")?; + validator_0.exp_string(LEDGER_SHUTDOWN)?; validator_0.exp_eof()?; drop(validator_0); diff --git a/tests/src/strings.rs b/tests/src/strings.rs index 6360394339..bea5ce536d 100644 --- a/tests/src/strings.rs +++ b/tests/src/strings.rs @@ -1,5 +1,17 @@ //! Expected strings for integration and e2e tests. +/// Namada ledger started +pub const LEDGER_STARTED: &str = "Namada ledger node started"; + +/// Namada ledger has shut down +pub const LEDGER_SHUTDOWN: &str = "Namada ledger node has shut down"; + +/// Ledger is running as a validator +pub const VALIDATOR_NODE: &str = "This node is a validator"; + +/// Ledger is not running as a validator +pub const NON_VALIDATOR_NODE: &str = "This node is not a validator"; + /// Inner tx applied and accepted by VPs. pub const TX_APPLIED_SUCCESS: &str = "Transaction was successfully applied"; From 211bfc55c1b960b57e288d19b02d14697a215e75 Mon Sep 17 00:00:00 2001 From: Tomas Zemanovic Date: Tue, 19 Dec 2023 18:32:11 +0100 Subject: [PATCH 164/216] Update sdk/src/tx.rs Co-authored-by: Tiago Carvalho --- sdk/src/tx.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/tx.rs b/sdk/src/tx.rs index 0a44734f8f..dc26affd8c 100644 --- a/sdk/src/tx.rs +++ b/sdk/src/tx.rs @@ -328,7 +328,7 @@ pub async fn broadcast_tx( if response.code == 0.into() { display_line!(context.io(), "Transaction added to mempool."); - tracing::debug!("Transaction mempool response: {:?}", response); + tracing::debug!("Transaction mempool response: {response:#?}"); // Print the transaction identifiers to enable the extraction of // acceptance/application results later { From c0fc84bd272cb0b4904d4440444018f543ab36c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 19 Dec 2023 17:36:31 +0000 Subject: [PATCH 165/216] core: add a note on transaction ResultCode --- core/src/types/transaction/mod.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index 86ceb15116..d7d2ba3635 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -37,7 +37,8 @@ use crate::types::storage; use crate::types::transaction::protocol::ProtocolTx; /// The different result codes that the ledger may send back to a client -/// indicating the status of their submitted tx +/// indicating the status of their submitted tx. +/// The codes must not change with versions, only need ones may be added. #[derive( Debug, Copy, @@ -50,6 +51,8 @@ use crate::types::transaction::protocol::ProtocolTx; Deserialize, )] pub enum ResultCode { + // WARN: These codes shouldn't be changed between version! + // ========================================================================= /// Success Ok = 0, /// Error in WASM tx execution @@ -80,6 +83,8 @@ pub enum ResultCode { InvalidVoteExtension = 13, /// Tx is too large TooLarge = 14, + // ========================================================================= + // WARN: These codes shouldn't be changed between version! } impl ResultCode { From 42f05af34bba7edcdc196e4e63bdccb36c497609 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 19 Dec 2023 17:43:43 +0000 Subject: [PATCH 166/216] refactor and use ProcessTxResponse::is_applied_and_valid --- apps/src/lib/client/tx.rs | 6 ++---- sdk/src/tx.rs | 21 ++++++++++++--------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index c8df8afd85..098bfd58a7 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -329,10 +329,8 @@ where signing::generate_test_vector(namada, &tx).await?; let response = namada.submit(tx, &args.tx).await?; - if let ProcessTxResponse::Applied(tx_resp) = response { - if let InnerTxResult::Success(result) = tx_resp.inner_tx_result() { - return Ok(result.initialized_accounts.first().cloned()); - } + if let Some(result) = response.is_applied_and_valid() { + return Ok(result.initialized_accounts.first().cloned()); } } diff --git a/sdk/src/tx.rs b/sdk/src/tx.rs index dc26affd8c..d8e44f74ba 100644 --- a/sdk/src/tx.rs +++ b/sdk/src/tx.rs @@ -133,19 +133,22 @@ pub enum ProcessTxResponse { } impl ProcessTxResponse { - // Is the transaction applied and was it accepted by all VPs? Note that this - // always returns false for dry-run transactions. - pub fn is_applied_and_valid(&self) -> bool { + // Returns a `TxResult` if the transaction applied and was it accepted by + // all VPs. Note that this always returns false for dry-run transactions. + pub fn is_applied_and_valid(&self) -> Option<&TxResult> { match self { ProcessTxResponse::Applied(resp) => { - resp.code == ResultCode::Ok - && matches!( - resp.inner_tx_result(), - InnerTxResult::Success(_) - ) + if resp.code == ResultCode::Ok { + if let InnerTxResult::Success(result) = + resp.inner_tx_result() + { + return Some(result); + } + } + None } ProcessTxResponse::DryRun(_) | ProcessTxResponse::Broadcast(_) => { - false + None } } } From db6121e90501cc2a1340aa74325f1237f3c078f4 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 18 Dec 2023 21:21:54 +0100 Subject: [PATCH 167/216] Validates tx expiration again for decrypted tx --- apps/src/lib/node/ledger/shell/mod.rs | 3 +- .../lib/node/ledger/shell/process_proposal.rs | 74 +++++++++++++++++-- 2 files changed, 71 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 8e33bf87bb..78752858da 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -156,6 +156,7 @@ pub enum ErrorCodes { FeeError = 12, InvalidVoteExtension = 13, TooLarge = 14, + ExpiredDecryptedTx = 15, } impl ErrorCodes { @@ -166,7 +167,7 @@ impl ErrorCodes { // NOTE: pattern match on all `ErrorCodes` variants, in order // to catch potential bugs when adding new codes match self { - Ok | WasmRuntimeError => true, + Ok | WasmRuntimeError | ExpiredDecryptedTx => true, InvalidTx | InvalidSig | InvalidOrder | ExtraTxs | Undecryptable | AllocationError | ReplayTx | InvalidChainId | ExpiredTx | TxGasLimit | FeeError | InvalidVoteExtension diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 7fad385eda..11241ff02f 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -546,11 +546,26 @@ where .into(), } } else { - TxResult { - code: ErrorCodes::Ok.into(), - info: "Process Proposal accepted this \ - tranasction" - .into(), + match tx.header().expiration { + Some(tx_expiration) + if block_time > tx_expiration => + { + TxResult { + code: ErrorCodes::ExpiredDecryptedTx + .into(), + info: format!( + "Tx expired at {:#?}, block time: \ + {:#?}", + tx_expiration, block_time + ), + } + } + _ => TxResult { + code: ErrorCodes::Ok.into(), + info: "Process Proposal accepted this \ + tranasction" + .into(), + }, } } } @@ -1735,6 +1750,55 @@ mod test_process_proposal { } } + /// Test that an expired decrypted transaction is marked as rejected but + /// still allows the block to be accepted + #[test] + fn test_expired_decrypted() { + let (mut shell, _recv, _, _) = test_utils::setup(); + let keypair = crate::wallet::defaults::daewon_keypair(); + + let mut wrapper = + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount_per_gas_unit: 1.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + keypair.ref_to(), + Epoch(0), + GAS_LIMIT_MULTIPLIER.into(), + None, + )))); + wrapper.header.chain_id = shell.chain_id.clone(); + wrapper.header.expiration = Some(DateTimeUtc::default()); + wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); + wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); + wrapper.add_section(Section::Signature(Signature::new( + wrapper.sechashes(), + [(0, keypair)].into_iter().collect(), + None, + ))); + + shell.enqueue_tx(wrapper.clone(), GAS_LIMIT_MULTIPLIER.into()); + + let decrypted = + wrapper.update_header(TxType::Decrypted(DecryptedTx::Decrypted)); + + // Run validation + let request = ProcessProposal { + txs: vec![decrypted.to_bytes()], + }; + match shell.process_proposal(request) { + Ok(txs) => { + assert_eq!(txs.len(), 1); + assert_eq!( + txs[0].result.code, + u32::from(ErrorCodes::ExpiredDecryptedTx) + ); + } + Err(_) => panic!("Test failed"), + } + } + /// Check that a tx requiring more gas than the block limit causes a block /// rejection #[test] From d8781279f63b324536d27cc3eb77688ab501270b Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 19 Dec 2023 15:35:02 +0100 Subject: [PATCH 168/216] Updates TODO for disposable signer --- sdk/src/wallet/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sdk/src/wallet/mod.rs b/sdk/src/wallet/mod.rs index c5fe54aea9..670bebddbb 100644 --- a/sdk/src/wallet/mod.rs +++ b/sdk/src/wallet/mod.rs @@ -656,8 +656,9 @@ impl Wallet { alias = format!("disposable_{ctr}"); } // Generate a disposable keypair to sign the wrapper if requested - // TODO: once the wrapper transaction has been accepted, this key can be - // deleted from wallet + // TODO: once the wrapper transaction has been applied, this key can be + // deleted from wallet (the transaction being accepted is not enough + // cause we could end up doing a rollback) let (alias, disposable_keypair) = self .gen_store_secret_key( SchemeType::Ed25519, From 23ce09fc926a900f9e15e9c1e3e42dfbdbf7f163 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 19 Dec 2023 16:15:46 +0100 Subject: [PATCH 169/216] MASP vp checks transaction's expiration --- shared/src/ledger/native_vp/masp.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/shared/src/ledger/native_vp/masp.rs b/shared/src/ledger/native_vp/masp.rs index 54048f6759..ca4fe3bb13 100644 --- a/shared/src/ledger/native_vp/masp.rs +++ b/shared/src/ledger/native_vp/masp.rs @@ -124,6 +124,15 @@ where ) -> Result { let epoch = self.ctx.get_block_epoch()?; let (transfer, shielded_tx) = self.ctx.get_shielded_action(tx_data)?; + + let expiry_height: u64 = shielded_tx.expiry_height().into(); + if expiry_height != 0 + && u64::from(self.ctx.get_block_height()?) > expiry_height + { + tracing::debug!("MASP transaction is expired"); + return Ok(false); + } + let mut transparent_tx_pool = I128Sum::zero(); // The Sapling value balance adds to the transparent tx pool transparent_tx_pool += shielded_tx.sapling_value_balance(); From 883925b35744724b85f54173a3d97cd5bf5583d7 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 19 Dec 2023 19:03:37 +0100 Subject: [PATCH 170/216] MASP vp expiration always required --- shared/src/ledger/native_vp/masp.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/shared/src/ledger/native_vp/masp.rs b/shared/src/ledger/native_vp/masp.rs index ca4fe3bb13..4a9aad9d63 100644 --- a/shared/src/ledger/native_vp/masp.rs +++ b/shared/src/ledger/native_vp/masp.rs @@ -125,9 +125,8 @@ where let epoch = self.ctx.get_block_epoch()?; let (transfer, shielded_tx) = self.ctx.get_shielded_action(tx_data)?; - let expiry_height: u64 = shielded_tx.expiry_height().into(); - if expiry_height != 0 - && u64::from(self.ctx.get_block_height()?) > expiry_height + if u64::from(self.ctx.get_block_height()?) + > u64::from(shielded_tx.expiry_height()) { tracing::debug!("MASP transaction is expired"); return Ok(false); From ab329455f17e57d8feca254f1ffadecaea1704b9 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Tue, 19 Dec 2023 12:16:57 +0100 Subject: [PATCH 171/216] Merge remote-tracking branch 'origin/main' * origin/main: ci: update mold ci: split integration and unit --- .github/workflows/build-and-test.yml | 109 ++++++++++++++++++++++++--- .github/workflows/triggerable.yml | 2 +- Makefile | 7 ++ 3 files changed, 107 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 45a6e4dd6a..dfcc4ddf2c 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -43,7 +43,7 @@ jobs: matrix: os: [ubuntu-latest] wasm_cache_version: ["v2"] - mold_version: [2.3.2] + mold_version: [2.4.0] steps: - name: Checkout repo @@ -102,7 +102,7 @@ jobs: os: [ubuntu-latest] wasm_cache_version: ["v2"] nightly_version: [nightly-2023-06-01] - mold_version: [2.3.2] + mold_version: [2.4.0] steps: - name: Checkout repo @@ -165,7 +165,7 @@ jobs: os: [ubuntu-latest] wasm_cache_version: ["v2"] nightly_version: [nightly-2023-06-01] - mold_version: [2.3.2] + mold_version: [2.4.0] steps: - name: Download wasm artifacts @@ -185,7 +185,7 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.MINIO_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.MINIO_SECRET_KEY }} - unit-and-integration-tests: + test-unit: runs-on: group: namada-runners timeout-minutes: 30 @@ -195,7 +195,7 @@ jobs: matrix: os: [ubuntu-latest] nightly_version: [nightly-2023-06-01] - mold_version: [2.3.2] + mold_version: [2.4.0] make: - name: ABCI @@ -261,8 +261,8 @@ jobs: - uses: taiki-e/install-action@cargo-llvm-cov - name: Check crates build with default features run: make check-crates - - name: Run unit and integration tests - run: make test-coverage + - name: Run unit tests + run: make test-unit-with-coverage env: NAMADA_MASP_PARAMS_DIR: /home/runner/.masp-params RUSTFLAGS: "-C linker=clang -C link-arg=-fuse-ld=/usr/local/bin/mold" @@ -283,6 +283,95 @@ jobs: cargo install cargo-cache --no-default-features --features ci-autoclean cargo-cache cargo-cache + test-integration: + runs-on: + group: namada-runners + timeout-minutes: 30 + needs: [build-wasm] + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + nightly_version: [nightly-2023-06-01] + mold_version: [2.4.0] + make: + - name: ABCI + + env: + RUSTC_WRAPPER: sccache + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + if: ${{ github.event_name != 'pull_request_target' }} + - name: Checkout PR + uses: actions/checkout@v4 + if: ${{ github.event_name == 'pull_request_target' }} + # See comment in build-and-test.yml + with: + ref: ${{ github.event.pull_request.head.sha }} + - name: Install libudev + run: sudo apt-get update && sudo apt-get -y install libudev-dev + - name: Install Protoc + uses: heliaxdev/setup-protoc@v2 + with: + version: "25.0" + repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: Run sccache-cache + uses: mozilla-actions/sccache-action@v0.0.3 + - name: Setup rust toolchain + uses: oxidecomputer/actions-rs_toolchain@ad3f86084a8a5acf2c09cb691421b31cf8af7a36 + with: + profile: default + override: true + - name: Setup rust nightly + uses: oxidecomputer/actions-rs_toolchain@ad3f86084a8a5acf2c09cb691421b31cf8af7a36 + with: + toolchain: ${{ matrix.nightly_version }} + profile: default + - name: Cache cargo registry + uses: actions/cache@v3 + continue-on-error: false + with: + path: | + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + key: ${{ runner.os }}-${{ github.job }}-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: ${{ runner.os }}-cargo- + - name: Start sccache server + run: sccache --start-server + - name: Install mold linker + run: | + wget -q -O- https://github.com/rui314/mold/releases/download/v${{ matrix.mold_version }}/mold-${{ matrix.mold_version }}-x86_64-linux.tar.gz | tar -xz + mv mold-${{ matrix.mold_version }}-x86_64-linux/bin/mold /usr/local/bin + - name: Download MASP parameters + run: | + mkdir -p /home/runner/.masp-params + curl -o /home/runner/.masp-params/masp-spend.params -L https://github.com/anoma/masp-mpc/releases/download/namada-trusted-setup/masp-spend.params\?raw\=true + curl -o /home/runner/.masp-params/masp-output.params -L https://github.com/anoma/masp-mpc/releases/download/namada-trusted-setup/masp-output.params?raw=true + curl -o /home/runner/.masp-params/masp-convert.params -L https://github.com/anoma/masp-mpc/releases/download/namada-trusted-setup/masp-convert.params?raw=true + - name: Download wasm artifacts + uses: actions/download-artifact@v3 + with: + name: wasm-${{ github.event.pull_request.head.sha|| github.sha }} + path: ./wasm + - name: Run integration tests + run: make test-integration + env: + NAMADA_MASP_PARAMS_DIR: /home/runner/.masp-params + RUSTFLAGS: "-C linker=clang -C link-arg=-fuse-ld=/usr/local/bin/mold" + - name: Print sccache stats + if: always() + run: sccache --show-stats || true + - name: Stop sccache server + if: always() + run: sccache --stop-server || true + - name: Clean cargo cache + run: | + cargo install cargo-cache --no-default-features --features ci-autoclean cargo-cache + cargo-cache + run-benchmarks: runs-on: group: namada-runners @@ -293,7 +382,7 @@ jobs: matrix: os: [ubuntu-latest] nightly_version: [nightly-2023-06-01] - mold_version: [2.3.2] + mold_version: [2.4.0] make: - name: ABCI @@ -379,7 +468,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - mold_version: [2.3.2] + mold_version: [2.4.0] make: - name: ABCI Release build @@ -481,7 +570,7 @@ jobs: matrix: os: [ubuntu-latest] nightly_version: [nightly-2023-06-01] - mold_version: [2.3.2] + mold_version: [2.4.0] comet_bft: [0.37.2] hermes: [1.6.0-namada-beta1] make: diff --git a/.github/workflows/triggerable.yml b/.github/workflows/triggerable.yml index bcc9cbddb5..99551f6f9f 100644 --- a/.github/workflows/triggerable.yml +++ b/.github/workflows/triggerable.yml @@ -33,7 +33,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - mold_version: [2.3.2] + mold_version: [2.4.0] name: ["Run PoS state-machine tests"] command: ["make test-pos-sm"] timeout: [360] diff --git a/Makefile b/Makefile index 6b7441b01a..f9e016911e 100644 --- a/Makefile +++ b/Makefile @@ -185,6 +185,13 @@ test-unit: -- --skip e2e --skip integration --skip pos_state_machine_test \ -Z unstable-options --report-time +test-unit-with-coverage: + $(cargo) +$(nightly) llvm-cov --output-dir target \ + --features namada/testing \ + --html \ + -- --skip e2e --skip pos_state_machine_test --skip integration \ + -Z unstable-options --report-time + test-unit-mainnet: $(cargo) +$(nightly) test \ --features "mainnet" \ From a610c492017fc818bfdaa3a4e3faf80a90aa32f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 13 Dec 2023 21:28:07 +0000 Subject: [PATCH 172/216] Rename ErrorCodes to ResultCode and make TxReponse.height typed --- apps/src/lib/client/tx.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 098bfd58a7..a7ec85b626 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -951,23 +951,23 @@ pub async fn submit_transfer( let result = namada.submit(tx, &args.tx).await?; - let submission_epoch = rpc::query_and_print_epoch(namada).await; - match result { ProcessTxResponse::Applied(resp) if - // If a transaction is shielded + // If a transaction is shielded tx_epoch.is_some() && - // And it is rejected by a VP - matches!(resp.inner_tx_result(), InnerTxResult::VpsRejected(_)) && - // And its submission epoch doesn't match construction epoch - tx_epoch.unwrap() != submission_epoch => + // And it is rejected by a VP + matches!(resp.inner_tx_result(), InnerTxResult::VpsRejected(_)) => { - // Then we probably straddled an epoch boundary. Let's retry... - edisplay_line!(namada.io(), - "MASP transaction rejected and this may be due to the \ - epoch changing. Attempting to resubmit transaction.", - ); - continue; + let submission_epoch = rpc::query_and_print_epoch(namada).await; + // And its submission epoch doesn't match construction epoch + if tx_epoch.unwrap() != submission_epoch { + // Then we probably straddled an epoch boundary. Let's retry... + edisplay_line!(namada.io(), + "MASP transaction rejected and this may be due to the \ + epoch changing. Attempting to resubmit transaction.", + ); + continue; + } }, // Otherwise either the transaction was successful or it will not // benefit from resubmission From b71beb157254d709c9f1b1e81129560e0e324aaa Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 19 Dec 2023 19:05:51 +0100 Subject: [PATCH 173/216] Updates masp tx generation to include expiration --- sdk/src/masp.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/sdk/src/masp.rs b/sdk/src/masp.rs index 68467e2673..f8d3617270 100644 --- a/sdk/src/masp.rs +++ b/sdk/src/masp.rs @@ -55,6 +55,7 @@ use namada_core::types::masp::{ TransferTarget, }; use namada_core::types::storage::{BlockHeight, Epoch, Key, KeySeg, TxIndex}; +use namada_core::types::time::{DateTimeUtc, DurationSecs}; use namada_core::types::token; use namada_core::types::token::{ Change, MaspDenom, Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, TX_KEY_PREFIX, @@ -1590,8 +1591,50 @@ impl ShieldedContext { }; // Now we build up the transaction within this object - let mut builder = - Builder::::new_with_rng(NETWORK, 1.into(), rng); + let expiration_height: u32 = match context.tx_builder().expiration { + Some(expiration) => { + // Try to match a DateTime expiration with a plausible + // corresponding block height + let last_block_height: u64 = + crate::rpc::query_block(context.client()) + .await? + .map_or_else(|| 1, |block| u64::from(block.height)); + let current_time = DateTimeUtc::now(); + let delta_time = + expiration.0.signed_duration_since(current_time.0); + + let max_expected_time_per_block_key = + namada_core::ledger::parameters::storage::get_max_expected_time_per_block_key(); + let max_block_time = + crate::rpc::query_storage_value::<_, DurationSecs>( + context.client(), + &max_expected_time_per_block_key, + ) + .await?; + + let delta_blocks = u32::try_from( + delta_time.num_seconds() / max_block_time.0 as i64, + ) + .map_err(|e| Error::Other(e.to_string()))?; + u32::try_from(last_block_height) + .map_err(|e| Error::Other(e.to_string()))? + + delta_blocks + } + None => { + // NOTE: The masp library doesn't support optional expiration so + // we set the max to mimic a never-expiring tx. We also need to + // remove 20 which is going to be added back by the builder + u32::MAX - 20 + } + }; + let mut builder = Builder::::new_with_rng( + NETWORK, + // NOTE: this is going to add 20 more blocks to the actual + // expiration but there's no other exposed function that we could + // use from the masp crate to specify the expiration better + expiration_height.into(), + rng, + ); // Convert transaction amount into MASP types let (asset_types, masp_amount) = From bb9d0131155e39c9df8633a1ca398559f36432fe Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 19 Dec 2023 19:11:34 +0100 Subject: [PATCH 174/216] Changelog #2315 --- .changelog/unreleased/SDK/2315-tx-expiration-update.md | 2 ++ .changelog/unreleased/improvements/2315-tx-expiration-update.md | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 .changelog/unreleased/SDK/2315-tx-expiration-update.md create mode 100644 .changelog/unreleased/improvements/2315-tx-expiration-update.md diff --git a/.changelog/unreleased/SDK/2315-tx-expiration-update.md b/.changelog/unreleased/SDK/2315-tx-expiration-update.md new file mode 100644 index 0000000000..058aa005f9 --- /dev/null +++ b/.changelog/unreleased/SDK/2315-tx-expiration-update.md @@ -0,0 +1,2 @@ +- Updated `gen_shielded_transfer` to attach a sensible expiration to a MASP + `Transaction`. ([\#2315](https://github.com/anoma/namada/pull/2315)) \ No newline at end of file diff --git a/.changelog/unreleased/improvements/2315-tx-expiration-update.md b/.changelog/unreleased/improvements/2315-tx-expiration-update.md new file mode 100644 index 0000000000..3259838811 --- /dev/null +++ b/.changelog/unreleased/improvements/2315-tx-expiration-update.md @@ -0,0 +1,2 @@ +- Improved validation on transaction's expiration. Added an expiration for MASP + transfers. ([\#2315](https://github.com/anoma/namada/pull/2315)) \ No newline at end of file From 5245b9a47a13592f3c973ddac60cce95ac516ffb Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 19 Dec 2023 21:09:42 +0100 Subject: [PATCH 175/216] Updates masp fixtures --- ...FC7D83CE95D11AC98C8B9343E5EED170119A9.bin} | Bin 9941 -> 9941 bytes ...733C557AA7C18902CB851BB4DB93A834ED187.bin} | Bin 7448 -> 7448 bytes ...6B0C21274416797B6508AE6C759537F947AC8.bin} | Bin 7448 -> 7448 bytes ...AE37AFDAA28B0701ED5D313EA859FC8153343.bin} | Bin 7448 -> 7448 bytes ...C1886347E49C18C8E0F35D25956CA06390B17.bin} | Bin 7448 -> 7448 bytes ...9BB39683C7B8AAB924B267369B3143A3FBF89.bin} | Bin 10382 -> 10382 bytes ...E860D9B5DF041F4645BFC329BE9A03AABFE47.bin} | Bin 9941 -> 9941 bytes ...F934075535EBBEC567084302C3D9B469B83FA.bin} | Bin 9649 -> 9649 bytes ...F5FEC42C100CEB0B19DC6C47DBFE88D42FFFC.bin} | Bin 17018 -> 17018 bytes ...5FC957F905749C47816C307B4B8D580DAE5D9.bin} | Bin 24494 -> 24494 bytes ...288D9729C50CA4AE4D1635C6D82007461517F.bin} | Bin 7448 -> 7448 bytes ...7FA3E6D650FE0B00B7DF477B409EEADFE64C8.bin} | Bin 15597 -> 15597 bytes ...1842690C36EA86369DC2145AED8B139748042.bin} | Bin 15257 -> 15257 bytes 13 files changed, 0 insertions(+), 0 deletions(-) rename test_fixtures/masp_proofs/{30E148B3F9E8D21A41ABB09756027024E9AC79985302DD15C96CB57743A74CC3.bin => 28A7EA5FE79BA929443DE88963FFC7D83CE95D11AC98C8B9343E5EED170119A9.bin} (76%) rename test_fixtures/masp_proofs/{8BA2DA741BF1FE1CDEC5295AE3ECBE1A9EAF3496A30D69069DE2FBD293EEC978.bin => 2E68959DFE3412D892C1EB6A83C733C557AA7C18902CB851BB4DB93A834ED187.bin} (80%) rename test_fixtures/masp_proofs/{553C507BF748CC2353DCE98EB91B5464810F6974083CC2FEE2756ED1E97B8143.bin => 375F008787D3797A051D288892F6B0C21274416797B6508AE6C759537F947AC8.bin} (80%) rename test_fixtures/masp_proofs/{F0471ECBD3AF04B4A373D2966781D5DEF92B9A9BB3A159947560514682CC3877.bin => 68DE980FCC7CC858B090D50340BAE37AFDAA28B0701ED5D313EA859FC8153343.bin} (79%) rename test_fixtures/masp_proofs/{4B412E2EA5AC98758E696AB36327E2CFFC5F8055CE2E2FAFB4DE8E7C2216D5F8.bin => 852AFF2FF8758999DA709605017C1886347E49C18C8E0F35D25956CA06390B17.bin} (77%) rename test_fixtures/masp_proofs/{C506FA6C2EFB37B06CD5A473AF5CC6F78791F8954A0219C07B15344AEE0D2E0F.bin => 9267939E4DFBF958BF98337A9099BB39683C7B8AAB924B267369B3143A3FBF89.bin} (73%) rename test_fixtures/masp_proofs/{BB9AA71A4227C9E62948CBA8DB4A5C2232840271F032F756953233DB3E53E757.bin => AA4BBAF45B9610AD4D2BCBDDF61E860D9B5DF041F4645BFC329BE9A03AABFE47.bin} (74%) rename test_fixtures/masp_proofs/{473EDF0B2908F047110AC52C7F7ECD8CFD237A237C63AA565FC44893414EE7FC.bin => D32DFDE8713AB8AAD01125856B8F934075535EBBEC567084302C3D9B469B83FA.bin} (76%) rename test_fixtures/masp_proofs/{DEDF664AD06184041515677A72699E65D4B40A4E1BC9E7A32D63CF28466A2F20.bin => DA1D4780CBA612D7CBA0004D16FF5FEC42C100CEB0B19DC6C47DBFE88D42FFFC.bin} (70%) rename test_fixtures/masp_proofs/{978C35E058808D61F0E265D72DE8DD6A8E6226394EA6E3DFE1CFC10F69C0ACE0.bin => DC5FEEE0E4971DF2083A9D17D645FC957F905749C47816C307B4B8D580DAE5D9.bin} (78%) rename test_fixtures/masp_proofs/{4CCB9ADD3188CD893508CBB4FCB62D08DAA52F4DE496209BDAEFD36E75EAE98D.bin => E3409A9853B0ECDBE4147AD52CF288D9729C50CA4AE4D1635C6D82007461517F.bin} (71%) rename test_fixtures/masp_proofs/{AE4CEC9192B52E8CE5AB0C25936D2AEEF55A6F202D8EB4564017DB5BEF872107.bin => E93FF3062E6FCF83381BEE364347FA3E6D650FE0B00B7DF477B409EEADFE64C8.bin} (75%) rename test_fixtures/masp_proofs/{D007D1734AD42D34174F04566899481AB5F7C8F57502C5EAB2BF7594EA6CED8F.bin => ECBEE7807835F2DF39FA2924DC51842690C36EA86369DC2145AED8B139748042.bin} (75%) diff --git a/test_fixtures/masp_proofs/30E148B3F9E8D21A41ABB09756027024E9AC79985302DD15C96CB57743A74CC3.bin b/test_fixtures/masp_proofs/28A7EA5FE79BA929443DE88963FFC7D83CE95D11AC98C8B9343E5EED170119A9.bin similarity index 76% rename from test_fixtures/masp_proofs/30E148B3F9E8D21A41ABB09756027024E9AC79985302DD15C96CB57743A74CC3.bin rename to test_fixtures/masp_proofs/28A7EA5FE79BA929443DE88963FFC7D83CE95D11AC98C8B9343E5EED170119A9.bin index 95cd7edf2ec4da5262f5e58d251327e33b84a76c..caa09a7e7d2df6a5ca4eff2cbae8f5dba96fc1fa 100644 GIT binary patch delta 1777 zcmV;M1%|NsC00FiDllPMV#kxU^3>;M1%v2ltR1poj4|C9e1KOooz z>mOaJ2If5opc@9Q(`CReA`gYWCS%RzvzaSX-|~}Q89*RRBwRLM#RAT$X@9z*m1D9g z|54LWiv-QO9fX6XsZI)$z8OFu?!wZyP`~q9+sm4E{RoQ^{G9_4Du9gzW0FUcE%l_Z zlO7sDK_TYgW~gT)VaG^l~k;KVJi@(EZ2?5CWW{e0$&8?M^>4|E3gn z0ujOU2B1OE7XHqoW0atZ(do0G8uS7nXjf1FPxwK1wKTlWiz8iyVnc0Xb&TR(5VogY z^)gSiv$P$g0#L;aPJi6vXgy>cAJ3i{fCs z1|+g?FdV2syc=`X<19a!CLCiZ=TF$M<2cTCk*`;t06uG7me-OQDUqEBli46Ze~)j4 zPetY~vboJrdcdURKPkTX^OaZ@9Bu&KK_~iKT4OK*pJBV)Y}7C^R9$eQgPANDXZe^E zHoHCHql1mPXZj@@_vu_J!C|Q;MC6h0a+_|ALc7rnxdl8FrwbN)7YM(|w+}AH{xtLm zmPlN2+mS1^oGjCz5fkQX3xeg^f3nvNg>SaH)ng6IV7i@Y?d>^WqG5|z^&89Xf+tyMN&unHr(h6Qei!}o#YI;15D&uByzSB=3<%I-qs z8vGL=reVJU>g|ZY!@EoIt3-Rln$Imom$W~zmUFY7+)h3+8~36L#MwByf6ef)!U?a4 zFj!)2r+wb7pvG>-^Io=Ux6w78^Xl{r=Ro-qQM2l09xUDEDB!Z9Dt>h4WA;x_PE?5M zg6Aw#cM=RhP_UXvy%Q`=Y<8LFG$oFTyb1NRlnyi*Z~uEt5knZ)^si z_&xfSIaHP7kF3m;F7vszgqDUxI6OO1$W-N+Dh%%b~fAC@=)87^LWf~)} z)N&BWdd`4zMyP~;B=DJlLamiQj3lHXt~d&jL%WG}UzvwVvTzI3I!7q%qm zlY8Re+2oe~mA38%r|Btr0=9C2y*?(6EUY0zF##YoW4UI(e}@rD&Nl*<-9LM}RwK7W zHCVEyT~C&%Q>bb*S7YG$uBi*50IV)agidLQ=hA+ub@8AK+@DOS@=0QFkP{zTdBe`( zLH3{}yT>W@CDs>Rs$w3?i!+ro#m1)Lz_-Q4-E0bp)Y~HqNjrG+0+<<|){kM1&zXTA zPYM5=Z1ZeIdm+ER-7rXXY)gP`Q#WZx27}G=- zx36>fJonGG`58wFC~SgjymgyX28nNOy~l?1xHeWtOXbRZH)Q3ZC@Mijf()PZft>$j zev~fi=hJ!ucNd7FE`42;f9n6O>UsMdsi1A_e;yxtf26XP2JAO_6!M|8SgKo-2tS(=iW72-fNWIwnPTDH2Q(t(@+Fkc8Q-h2K6 zAItOKA=Qbyi6YW!VoP(m9;?+#GrJiIlP7T>=Ztse&%+J)X14)*oa#x=U4 ztZhP+I#RY-h$y%bKBM`X8J5U>#LJGi4lV@<1x+uWVZs>7$6C*H>yY|Cy6uZ#7Wly? zCCeQPr+^Y&xg>ccQ5^z-d;Ssv1BG~*&{ErEe;g9b%exeRn>TeJiT1Bed+Dcl1!AZy z^}&&^n=mGq90r0Bhi9NIO+z?9-W}uiRUqaaUyanV^?I5GgV?iQicz=Yn`ZY@8^FKy zLSeyga~x57hA^%##6@UV0B4~pbR1B; T3!amTNq{6#=130+lbI$wwrOH5 delta 1780 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 OfeL4rl#B+GFCr0 z9DE@sK-$dxtu2_7qH=TFRa*ql#l;@8R}l09AfyZq@q_=-`(GF9Xp|`eEPPKMyC8GRXCD>96<^6MrF(FTNehV~)J?FI8UtygCqOaW zWu{QGNRRO6ZDmpl3E`{%ksDYW!Z7nje=k9#Kt+Rj4t#Zvfvq|*I`NOF4h4;Z4|z~X z0*3=j=if%Ak$pTcVm)x3+6U=y%&IyF9d^L5zf8PJP~}|*H0G?C^fB>YM-?Nj8%ATX z#YDV4`b=()AIyRM>d!Dmkbq&LlzoCF-PdzHPwJAsf~TVvpHZ(Ud=guC4I$q z)E1@p6CI;()b&l)Y$rjMvCod7g)wbbXSMv=jgF~nwo1x?)r$PE+5QgXXv$1CRK8py#tIJ@QTKe~1J!=nriL z#m%@*$;4R*iB&2rF_}RA_mo$jVO6^y!QKtACyxf47hc2X*Y-g8wV|)E=3{3JHr0i> zK%D+N)pnUqlZd1fZnR8WeGUpY_(IV(Y9il@DmqVG`Z3k$VYmXtrqcSmL7c?7V?wWYlTxB<&oe(f9w`bt7YtKk&0Y5DGekIaIWmK7niP?Rt^d6XO{FQ02gW=UGxx<7EHLX_+8oY{zD>}r3$(0)(mzbB3r0C zL6xFgsCsW7e}hc-;w*nw`1u-|-WJO57-1&|InmPS-5UaA7#DEUMiCIa3M6PK3{prY z#~dQdu1dctLX1}cF3+cFQvqxfax@-|<#++5m6jaPHL?3K-2!&WSj)C2imnpQ!_%V~ Orr4e-%aI6^;M1%|NsC01ONa4lP?VO3IG7>|Ns910001!FAO20Dgg@w|NsC0 zlP(PPAYDL{Scoxe#}{6jPE?rKIU}OBn_3c)2iu5WJEa0&sIx;2^a3CcsV7d&s6h!^ zZ3I4~`MY9l%XSRH`o%sJ)oKUt;ytaiR}l09Ak2dF3k@pTa-CU)?xMVPfu550F$LtY z@7}>;1gEsLn6qmW^a3Em4Q)e1Y}1Uy@v$^rNfn>GF-39{jHLxf-c|99WIMpKe-kAYVBkr*DKH38ax;P9&B-{fbLC&-=KotuSt9Rn*jO9nw-#gFI0SUXNLv5q z`~(Te9?dZwQ==~39TE|3}czg&_Ei&ROI_V zNwm)QE%6sN2}X{N-7Sq zS;=LMnZ5|xR;FBB4YAGv1CFcpf1+F}g0i0A+zO5QbhkjX$s)l6v?ji5M+L78koenxS$j!z7npJY;} zpgP`!$(Z_Io5DE!=IZWV3WmatWA=iJU0uT59Z~jQc`)e;a$|yyDMH z4|RmA9kAoJ40@Rn;;x9(sMP_;W^r3vF$A}#yveiMr~2uPd^`_!;?2$cx3EM;5Tyvd zUUOr&B32KhwT7ix*(bUz$G1mSh)J@++OljM`L?$L4x4H#+m5u1Ocw+FihN%`%g?RS zhxx2_&a{Ut`0yPX`kX9Ff72W%gTLZk6AA}s2pBLNQd?wnYKrMKIW^9kiPwD`Fbe?R z(^?jU(N_SR=oInkEQxJ+ZSMUMlnteB=En!B*Q>b*RSFa}Xx}_pvnP_*Du*5>IWVv! z|BnVPdZZr+8w{}Icb|m}ei65Ca(uyRC3!1;^vujaKGj78eQNOh-FqH&2 OhT;b<`3VJ+ delta 1044 zcmV+v1nc{lI+!{P0RR916#xJL1ONa4lP?VO3IG5B000010001!FAO20Dgg@w6#xJL zlP(PPAYf>&>Mdg{s(Ir)k)oS$JSZQj>fh~vU@q@V7vXwz6|+MP^a3C<{BQUhy{xJA zQ~{86?m(hVOP|r7w#4}M+>^h3V}9K5 z-U;0sR|`bqJcuk|JJpg4fzLVCZ%XW%YU5ZJ^^eqMJBYXEi+gy|C8abSR%vR%e;2Aw zdy1;nE)bQIK-G*t?^GCD>5*0381ap?*=DY2_DA6udgOkP^Sp4Jqw-5fz9=H(hYwbO zpTNb?yE1Q4!*HM!%(t4;&M8L{i+S&W=_*0E?&6ZRfYblNBbW!NURf!(oedy2#y9H! zc>|mCc{2UgbkGX{1zU0roNxDye_TS>ms6eQ+r3&gDCpK^yJ4YuN=f+l4=_#i@cs@7 zd|!N^E~N}iM*)kO#8#^5encI+inxBAM#6tDw(JPIg;U~5)!QrfikjQ6h?!!fh4 z<5ZyT#Q6O6;&j@PBgX{VV2 z@k0=kCC1=KhdElbEtiqimG}NzZ+wjLvPQ#3#I3G?Cy$GaV#oxoA)a2Ox3fFf96FA7 zYsLkPF+{$6pti=)WAgJzfH+TJKKd=2kBVS#d-a=a9WjCUN7FKT&xUkKadT_3M$dvV z6(;A4IkvwYI$7XuDe`OEe~CMCY+t_iD`R=tq+A~+@@{%r+uH-X3AaRgoYe0EBXVuR z=BN)$T3)wi9HPsj8!1d;rHkZ>!-NHq?f&g~yR} zfyjWFJoltD$1rsp!-rsBOQ*ptfg)(Hw_t*SI&M#Umw79u8?E^gMp4=;Pa<}ce581dINRp@Qp3} OgxoC6ipva>kI+!{P>;M1%|NsC01ONa4lP?VO3IG7>|Ns910001!FAO5HD*^us1poj4 z|C25Z^&m%Qk}RZ5-ap=t>>m0t{F>YR;c5fAG12%iEBXP}aUHWm4fFyauICe)ktWMJ z^&0pkyKaZv>VPiSpvmcMOX}uqxQwl~vsV!G0w51+D=z$x@hSHnzuiP-G=+W{j^nTV zIF9}59{Q^*W>&Ln6Z8Tg^O6M8huxExXpKuN!f4Lwzd9(>vwXt(98Nnje_x2pvws#P z1AnouuZcTJZ>OxP&${@JGUV_c{U?vfRwe|gOZk65!DeSZX_B~-d9YRdwck0(c_NJ- zS?f72cT3zJB+cSJ59ZY(QYp8TX39fPG@16Dc7|$F#UMd!zxMER@pRn$=NSnoWhw4^ zeA#r-WHax4FilqnafM=AwEKT9i2Ta1D1UeH*-p4`8faZN2iwgB96OtYW}ifLRZakw zm2IC}-#W!{LY%QK1Q;IU-dMyR5u5v&DcvC>DzdXKh-el|(Hv!y+ed)zZ%>&4bn_Fp zD~ukq4XRFex08qH#yl!iACmW)E>gVC(gyK2@+^M7fEsy`xK8+OjpGW-$w2vj^M8zW z7bvJ-E)4uF|K_jS7Pn)P#%U+FhA312XtTyAG#>ZC7^>)^OxRAT3Y3C#joqX$z3AWG zg*+uwNi+G>wY24Y@^M7UFx47tr7z2o95^7F8Vw| zj`5~`^6qyYco1eO4+bJl?igA{JAYY2R+mAeO#pl7W7$Maoshj!Mi|1VpVd9>NeG)R zQi*!<@}0bd4pNfNwan3sNQ2}-7mk5h-)XMQI$u#i)|TzXmgXo~qmNLCu;Y?lofHN@ z-l35b2ESYCxO7QfN!pkSCK&S7fo~l#bOvv-VTuw(WE|%0mPbirENxWc1TkSA=#-Y!?NDSh0i8;(9?xsFt4j&-6C`J?Gc3Y*P2hIb* zA_VE?erz~RK{Ya5$X^ec3Gq+DE$V^B`t&$ai*;o`dL-AV&+8fr4(;g69jy|>vPDZp Pixg2JE$tl&ljIyUFZvC| delta 1045 zcmV+w1nT>kI+!{P0RR916#xJL1ONa4lP?VO3IG5B000010001!FAO5HD*^us1Qh@P z0Fy2Z^&o24#dE+dLviw`GN!2hjY?B*W8T&mnx460u1l%^TL7~|4fFyakmN|V=OXxr zO|%lwG-#RSttiAeAe~OQba$*_4Z>$evsV!G0w4t*nm`jrZc4bqCvGv$7CCyDwi;o- z@bmQHY~V)D;}o-N6Z8TgvYghTtEgl|VJR{C-ACw!t+)CuEj{U*HzTmm91s20#0JLfixRWO>4};ElnSP_!Zaw>f^ukectT|YruWlmVyBdQ)qL6k6vH_OW z=^?Cbk$U^FQ4~ zYkm{mbX(dv+_3*1^rong$;}Z9{!b(g_qcw78a$R4sP4%Vg*KkC=T05lbSjv#e5$a| z;${d^t5X1Z;>OhMbt-oa{tNrDz5fRD>fel|A%AnPv5w=G|2(N6PxhhLI(c@8x_`xK z2T9ygL=5}O=B;DpFYX_aik!-T;wnDpEh6Cjbkaq96(R~3qpttD(M`_4hT-2@K>=hg zVh~g!d@)>o^6(wTw(p`bq8)6JKW*=;m_F<>t;d0w*|&=1pEX*LOGT&LkQGzX#Rc;c z`Dr7}?#xs@lXG}|OnMH#XK!sP?|=56gp?d^qPCR4#2`i}cAFuk8i+m&5xkW=QF55$ zKSaKnGT@;Pzy75z2%)s)r%Iz98kH~gRWf31O=p*K1Qdaq4oe7+Oc>t)AypXW4k?WOMHgz5I+p(eP=jvhQ`@(tD&d8lH zs}j_#s`CGf0b{V*F3CG6CF4o8{YTRiy*Qr!3+3B(QLvu$x{`N&wi5)eBc`JizArAG zBKLG31h9qnCpF~aq#{)ku#~ctsgD4)(O<_jPqdI`%%Z{N3*1yV!XmM29-Cl@P|E}UfY{f+q`?O z4b)sgNRq!T`48#vI)!t-U%0Ylq3wF;M1%|NsC01ONa4lP?VO3IG7>|Ns910001!FAO20Dgg@w|NsC0 zlP(PPATz~TzqOkvPE%{kO>ewKt?R@yE-}=>@t3@~cV8Q@@3TV<^a3CqQNBe%_KK%M zk52|HXzd2Iybuj# z;|n=1b)NWW#v?*hGZm{_;UtyWzxyIHA#g$gh{9spFvt&^H! zNL!)Jy?T6`x=~$KGkshWo#^J37&vqM--x1-(=r*zh}QfBD4+AA-J2w=R|RCIo?%hl z+l)QnlXcPPAWb*f_okWu-d%PSVrNNHf1;3=&~{n%uN+sixibcNH^PFN{YF?*?Yv)0 zXH9?l`Y?>zmVIuup8J8HHl|Is&nZ&6ZhY>RP;UGGzrS4FyQ-3OqgMmm0$H_^2x%x+ zTqyQ^C~FM{KH#fJ+PmzOD#RYgxC#jQ92B*LVDdWHyOx(cH~ClZSRu%ZF9%R2e=G_d zqbyt>ts(V*{trf$@eR1^F%+wGtFjQty1LsnrXLm&&}hSVicuo?_}U$eQz>Q-tzqI! z?-}J+-+}}FbYX1;J@IM-~*`~*^kCxbWxdAwDj5s0Mb7nr6geEe7iE>(Zn#&f6jWvpd zHe{b;0XYR={|%uqx@QF-2012lY=UZxKBJdF*8jQ*Eu#MJ{)o`3riZDae^IX-mK^5X z3BAO~OtCXbp8D>rd!;+!l>EpPD>i$OyRK}j#%F-#&OFbYbXyp*v2l7v*s8Az7ldQ8 z9ps=57NsIk)R>d?q9_``3klQp;3zyz@=kXN7Ro1*h!V8Os>geJ6GKPvA;!~O zdo3HjsaS#jo9vXRIbEX2z8=0hjQwZF{@X});A&GV(2*V&XpMj>~xXsYB&U>a0Y z>?{~XT5AWaRPk_l<3}#Y*JNW5zbZY<#*cxAdB}nGCM~F}R2RT5oq=L=o$w^|kB~Od OG+1XBkA?=5$63=YQD7z4i)#RPxzwh~VCZN8HH5j4}D%{?O3k8;RY5+JXy`af?Wbxp#@ypYgs z6MkJ)!?OSW&Xx&=AWS@_^lRjdnZ#y9#B@G=>`Q z5iQ>O|9&OylaR!b3dS^92=6<)L?&!re~z0Y2@I=Of(sxf#$)wYFDi*?BO-e4pkz-O zseiSoeKFS>tsUgDX|p9(L`tC=RNi)Jqo@Wvc{Z@W@`ho7+JcfPdmd$)aY&DB&bj+` zH|Uh`Tk|}s7(cAPMCssSY!UF`FsDn-n;2E)0xL|c(k%ihTP54YDf2NH8 zw7MQI$4{KALYa6|vsb-tMkwN$ou?5P7)@{Tsg)uL`b;J4N=y35p^kcr3Y;2aL%$I2 zOud=ER69kps2I$&zcC_5ZKFp@x;6f;2j^*vb0L}F?Y`uB@G$XE(p`%{wkuLbs;5Kz z9ch=`>5KU8iMeTRl7-xb;V#dce?W(^<8j)Rd_>52+xA6;xosYfA7Y64yCvYqIsvnC4nuTcfq!j-?dh7=F(Pr3 zc3tA8!iCW{v<4PWkCOmJVX*8^Vdk$gXyz8*-?>LVsFbuq2t5j2hv?8!IzrgFD7ldlYk! OE5YRQ=nM;!XQI1gw>;M1%|NsC00FiDllUf@TkxU^3>;M1%v2nW^1poj4|C2BqKOktr zk9sV|h!D8x4bzzUTQs(qyT(0Dmd>;e?6c|DE6u9}y z9>ElnR6+x1G(VJ`n8;s~?;Am3b)5oIM$A%k(S*eQk&1rLEGma62xUqk&QX%y6D@p` z4Ae%BhO}dCO7|W#yy?lP@>lG}3Gu{&@G*C0l^e?`0vtBb!=KJPCz5ycy76)Z*mLU$ z=Z7MLP<`3Q?kd~pzO#`Wqym2s`0=I)on96P$Pb4vOb!NvJqD>uMeNi4M5V}tD=d-F zmw1zkwyHM%pfp9N3EVVYVsBob|2nn(QqQZpM*;LLoqD7@wxmOnsdI$nabn}qeajlU zc6&>aYi@8vL?44dRZ?ndS_(<}GsWAxYnxd*sTtVxSocq!_~l~b?4Yx>AC&@7_GiCf zcV9n^wUGBZW_HK{Nn3!t{Qmz4Zx?jzZQ{mPj{thY^L4BhprfuBi@a7^3zB9`vu)NK zj2gi~mM;ohotTP_xjJhVmVzqS3ZlpvlQA19k(~&W$|692jhH04qpOLh%9=)E$gen% z-(oX>G<9@Gej~KxXR%V6VZ%6}dhLyRercB6aJ9YDn2LgT;F=2%k0dXoxvbUd*>4`B zNRQ!U!ks&R$b4Yv-0}K=kVj_Ewx9r{;t=+`x^*!siGm?nWXvuCL=;Mbnn+gpwWt? z35u`ccL(pJ9QV%Gpe-w|x~(8Dq=)WNf9N3hdd#zJ4}Df9@v~9yz;E7|S$TDEBeH`iJv|MA_Yo^8Rd} zZ_(bq{;JG0%!`y(d&Uurs%xbG-j6J#&bcU^-Z7Jgv zlP8dWINKwU0UJ|&2KQ2SoU5K6q%Vl!C>|rLd0ufie5xKVi39m?B;> zIN|aLce|<81zB3=;Ht34xO4{MAS@2xo60>CCqv(@R5mXh+lw$&<{a> zh7uzt3hd_xj3;3|mEC%G95umcG%V=PFWa?UbZ~XrW5nT9%8_0 zGggwX69G8)Y>{j7C3o&C*PLT!H&RZzD4s>t!i~jYiH9cdDd?Ilk6rq1ib(31sR$tK zczTgd0xS76!^^f1-O?qg|3s^ILRQI~5L4%suwi0}OpQuendxeKvA^ZfE^Uho_M?m-UAn$akPw>;)I7o5raTx%0#|kxwni53YEcn!^bz zTfrr~x4)b-Z;p4?L>uM-Q#xCI`HZA=b2?e?h$4Jr3KEM@Qwx(`C@7O&C=>_=00000 G0002D%!o_? delta 1819 zcmV+$2juvUQI1gz0RR916#xJL00000k#Q`OS{oFROd$jT00027al0A>6#xJLlQ0`U zAkDwe(@3a+3@!l!f>r_ZjG*}-IxhbO!%?xiK-g!fo|BIoKp^AkFfKrp8$-6y&9?JZ zS!rUpH`2+mZzi1l zRB(=C$)C ztUttX&9)B#)xU%DB~bvJhD>HamG<{m!x~CUa)9~%!2v2XWR1qtAdr+)<$BDblTryz zv>R#-ip&}Df}xo}Xl8g;Xu$V=vvNb3GV>jiF&in7od}c4B0ztpdr*CmdE5g|F)pt! z%;SW7k$0{EehDM-O?=9(mAtYN?lox5u-`$^3A0*Y#$On*Em$~&$R&+$^aFtc&(Q0~ zxWz&zxFNHxk&prG6_JcGzceC@=@40P%iob?x>3Rfw=z`XirVixFIADJ>Kv;8+GP1Vk4~zm>)JHS9 zKILNZ?_i8@CNM~dheMZkkDE=88 zq$?yYJ57HzP6_V`uM+gdc~w1<%7R^?(ft<@rXzWjoc+qjHZ9U8dd?x|#Iso<_bl*}QKV)?pm5m{}tERcq6<=-hyUq~Tvr+`E>p;PBSQ?rY!(o>Ed7?Za!@tV#mgZ#Az^3T|G zEx!#6M5f)jrT4%Xg6T#L6*Pgeg7go%rlh%Bt|nklN<_(24$1;GVQWFjU(0W*{px#8T>`DTn2}MZCG-r8%+YcAKoz$10wavVVGbwR8O#R^ zzRNyVxz{*2oPZdcz_ZJCm20zAinJVZ}#S?#U zI`#`*VhgqvcB%;Y=?3h>fxe@4m_hd!!^bn%YkBS@)J}!{8IvOi9}CM*+HH?Mar?SJs|BO{<|GO!P^sS z7b@!AN)N>efY9P5kSDM8La4XPK=Xflr^f^88aP^oom~D|T(ES%J)@R9cLsxzSRc1) z_;`h4YFXj1@xf~r{%O}UD~ZRd2ihYyBzbr+e@v)33F5G@B4^nY9v*eMK|yOB#XbVY zjkEJk50PV+g9t5TlC?YQS(q}}=Qx}^^IZN*&_QUC5ShDxbIbILk{^6zzngz+U5sWx z^2F_rCX=qLKRRT{IkxB&xk!c+Zpxq@-Z1aT+X&t!WgGv5=ouiL$* zVIh@ItUM9nbQ2m(!Q6>D>2ykZh!2(V3A*6mbf4mz4;EWbM14kx z6(8=yTwy+Yuad_etATf@2u(ENv+5^7ItVPuFS04i_OC7_Z~;U?0+U`SD3e|&6bJ?Y J00000008_>X2Jjf diff --git a/test_fixtures/masp_proofs/BB9AA71A4227C9E62948CBA8DB4A5C2232840271F032F756953233DB3E53E757.bin b/test_fixtures/masp_proofs/AA4BBAF45B9610AD4D2BCBDDF61E860D9B5DF041F4645BFC329BE9A03AABFE47.bin similarity index 74% rename from test_fixtures/masp_proofs/BB9AA71A4227C9E62948CBA8DB4A5C2232840271F032F756953233DB3E53E757.bin rename to test_fixtures/masp_proofs/AA4BBAF45B9610AD4D2BCBDDF61E860D9B5DF041F4645BFC329BE9A03AABFE47.bin index 4764bda55dbf61be71b3a9185159639f7e8c8341..898275796083faf2f0f41ac233dece8c11391be4 100644 GIT binary patch delta 1777 zcmV;M1%|NsC00FiDllPMV#kxU^3>;M1%v2ltR1poj4|C9e1KOl>B zvhS3uDF+Hp;;>t(y`trbj#rHE<`VZ6hqH~Wo6VD689*RQw^#E&cq}^|3b8DZ{lNAe z+jq|Rum;{69hzG8sJTj$z8OFulRsMg3Q2^mB`5kXw1R5lyf2s zW}DBK6N19z_i@7SLiIiTL20v~8uS7n2x^*U@TvUA3$V!_=%veWUlv$H+UcyS}B>4@-E0RgK=NLn1yTw$Bq_!Lw86$t0dU z`+p}LF3olu1`@8xBBu4OTHp^e#5On2ToxL)wY>S>zh-t{bL5dbAP8S?+ZnjI&4{*4 zxroxioU2qhI@!;e7L|aMxXD;rf60Nh*-<+FOIND;G7ZNLn_WxGIIpX^%c`2f-vY#ntJ!!kPxQ*uNIGe_% zY!nHPg(ZFDj_(BH>z8ipW)o5t<{87rXv%3yTB?k})@Cc#Ockg$ko22ke|A^ky)FYJ z()udiQ=a7+wlHdOB7B>aOf@393(tA>&Z2<>0v_1>kxo@gLCOQIPO#?Xr@ol?DWN!} z$D)lAL)oYoGf>Sam%p4q)KGX`*Po@)mLo zmZUrDFQX1_8Nq7U)hvmun?n3YE5IuA%s{6!3Ij>k0Hg#Ec?#UWEytzE$glZov0hZLO&rGD=t{RpT7ts% z$-ob2-+uc5Y?h|?YV;f{7g-*43>tO}Qi``yb88Uba_PBW4UkD=0!;rpf$ zf1Ux!z`2Vle_0$b4?qChQq8`nj2`fkhF+^;LeabElxl31!<}<7;Q!R` zYi@nDf1eXpzms69@=SwI0K?CBL&0<_%p^xiw<3J92wH8bIoI!3w4>*C%3kQ043x-+ zmYLqUuqj(N_F0yt&h3SP;A(xC&(0(*EU$&fa)Ug$FG3)eH`a`V#er zj*BO@R#g;F z-2o-5EVwDQIATs({`rLYsU;DJA1k|XWAcSu(-Fchz3T-e?nlQzEuGe6UB@$v3S=cA T3w};E7^0=W;%4dulbI$wwXJ^{ delta 1780 zcmVUv?gNRsVpXj_ z$VL4Uki(-sZEwq7LTgMvyDS0@7qg%m^a3D`^hr%omN^P4d3SB>^>(?}l;ZsZu?`W9 zoTKNlx9*s;v>l`ZP;d+{8xzqnirqQOqog?;7R{YcJ{#CwLK9bJsWpkwN$p9Fu0aAP zM)wtPd+rKo#ETf_YC<_{#TR@@vRo;m8s}HC+c!HZG1M|eg4Z!FMv@sRk(~&W*&slF zsdtwC%xNalMzSuL-;hki(6*}F8qp={wL>L9``glBdL-9abY5nL;M1n^87d8iAjw#O*PKs@|Q?826RUoMdb z1g0n-+iNi^%1l4GN4re88YVg_uu8b!8?jK&QA`KC{$!oA)*>Cm!zuq4`5?pJ{07%wClo>hzOgjTshu zr)K5p1vmkr$Ll$WX^9C<5kZ@Of?_SE+V!|6qfju~*5EMces1x1EcQSjr69VU5FMR6 zmI7MO?vRIY7(JGw1B@CDftu%%aD1O*HQ*pNFdF5xb0x^x_lHw!mb1Oa%JKd9MuMXZ zI=9=->bne||dJXF)PwJ8g%`a_lKM>Z=As%iTvrPYK}AJ#}d2$}c6uh(VT&zaLLz?6HE z84P=TrB^gfR}g0=hf6B3oZgmt2G7u=zKE6m7?Dyba+4l?;geF%ifqU1nRN6DHl#;a z{INhZXYlmE3x97Uxw&9}E1+!v1ys9$Y`WNO7gLjV0K1OQu~S=Gp_z;jA#IkumxgK5 zJyXT?$=d$dp?dG9DdNQikF%ECopN?i@doet=U{7;1rhA*mMo6Ydvn=_+i@(4RTh1* z!n7&DWb`8(U8bY(!FIXrGzQDiaRinj0|OyP$<4fJOJ+)@ICdg`2=nX-BLIrhN19Q= z_}8m}9sE{-m$>C|A>RI;H|EunIlW6t3xU{{mAuYE7fthTFv`EkTB)MNkQ^HqNi6m%D3M$u#VjEX|7nPKxLRmUpUqD*zfCA_e? zMY4}V`Me&2s`73&i!wxU=@MO5il_@bLZ9isKvYqtHiX%~y3mXEec++dZjrYR+*3Bb zr$>lM>W9ie}YA6#vx(li+9W zYm?L?;24}2JE}$x=_mm#bGla&h_d49ryt$4AuH-(DIojDc)`F!o{aOFhcK(p9caB) zMc1EGIz~8u;~2k#;7Q^g$bln7o9nniQbs4qwSZcTASUPlB&s>+%|tmo)IS{bI$%d~ z1$SJxj>`e3r<;sOPNFd*Us!yggG|Q`)0xhy53nsjq?GiX;ODLR*r3A9fb>9u=;k=Q z-r)_xmWO;iX%uud{EL8|(nBj?n3Dta?`3Bau6$a52b4<(TCVAn@dCI2IBkQTmx7$+t2pI%Vy W=;u!h2co!P`<=KxueA%4nI=3IYiXGP diff --git a/test_fixtures/masp_proofs/473EDF0B2908F047110AC52C7F7ECD8CFD237A237C63AA565FC44893414EE7FC.bin b/test_fixtures/masp_proofs/D32DFDE8713AB8AAD01125856B8F934075535EBBEC567084302C3D9B469B83FA.bin similarity index 76% rename from test_fixtures/masp_proofs/473EDF0B2908F047110AC52C7F7ECD8CFD237A237C63AA565FC44893414EE7FC.bin rename to test_fixtures/masp_proofs/D32DFDE8713AB8AAD01125856B8F934075535EBBEC567084302C3D9B469B83FA.bin index 2b6a1976a213269ab230b641d7365eabe7a92c5a..f6344ef81229392739f440ceb416268968ec3204 100644 GIT binary patch delta 1709 zcmV;e22%O4OR-A{>;M1%|NsC00FiDlkvKI3>;M1%v2o@X2LJ#6{{R7z5Ii9QeKcd9 zF8kT@6ni6~y^rW{T=}`0i=!rfo98X@A5Eoele!o{AVlR@=vtlwWNQW^=M9 zTJrZYU4!+T3`QQacp{$DIwbWU?||SisRC%i>%0Q;wgv?(rD0WGOM>eU0}d#+(Ye4} zc^o_V3m2vQ1qYzBycwhde|Q-`!YKefqg$DF1*7K-3Z!#~Nr32WhYrZeRbvGlm)WO| z-WA)UcGCf`5+ve`dgpUe`N?aQ!9!><#r5_+;reKcE412B-4pp4=R%ABzlE#uW_6c7 z>eWP?04aZsoz}ybyZ*WhK4<-ieKDK51@$61AmDA&^VIX z1xhWN2v8KJao2OSWdhqjtZxe<;gz|41B)x`TV+Jj4oDahZ*g18W^svs7n*ciwR+NA zXy}j$l>q*5P+JUEiq=9Q$ed5&jX&Bw3eH9EY!NpU$Y$c{(yj?h#Uzf9mQEa9y=lm~r4)U*eu;QWd5(Hwh_erMeK5^47^qQcnFMarw5y7;!}+&1*Upo)d1@lG@6GUVi}6f@ zi{7eTT#uZWu6AoPw6zZTat}q%8$0~?B;qW&-Asrt0v-lTNPc@ZSbxuEJYHV#JjL~? zuHVh}%}fgj_WWzTR2J-%rOHWKVXmH@5WXK1*jbcyAfGMCY>UrvH#j0_rDfFMYVLG@ z0XM{Kb5KM5}87u(6E8BxW}Sk8PPx0j7eu_BSzM`%Rpd#1R%#XxCi? zFci%!P~r94)W^kO^nW!qb2^NS>8nhdvcCv$zd_6yDN6sA5-FoN7+plpR1YpJi%E{6 zG9S;WcgD_vN;!BhVSu&3q#O5I=9iP?ieMmF0K1P?k>cwyo7QKTiAgC3Q=V`8n33io z%s?QeB1y;@(+IX?`*m_2I3lkUVFvw7l`C73@^`5Y;6I?SJb%WnK?IGq0AvA5i^D>N zWHaHm#jkv4NSBS-o5?l)yuvqXa4KZds5&5NVHttg~L$6fq9T#|eH4&pGVAC6Wdm;W6~0ys_#lHi%Blahhhh z-WCPn6QBw6FMkh2w6d(r`m2&L23|HwdW&qoxe1aN=^y&P_Tr!T?64K-AEpdLxs09D2~bDPtZBlw;o_{0 zVkevjO@)^#Mz*ZV@f(Z^7n*U!3HdBBDb#9ZoB{-*!z#PqEYGm^|H2=Mtm3~>u9*%P zZee7AmLXvN*Bl`(sMaz zU#)`Sn<-=&7y0j!#F|e!Bc_4 zEau~ApGSbFvxr#6&~dMb+NEeV;>#6;&yiudS^}OGNr%j-uxj%l^D0eN6&b81?vvws z_J1951#9a@nE&F7o7S0;Wxg&lo(w!Ylc4N^2JvG|&=66z^|WTJ1=ZvL9Tu78s@Plz z3~;GRdcn^lQU=o@y>R`y0mbA>7wRjqTSMtDvqG{xlW@K6eVi@M08cV9LVuZA#>4LsdreNx?NCmL&F6&ryT%{1JtXTw+dJ zIVu@ugqKYBkL$5F#k`o~`0t9l$hMaZL1q68$D?=}xbw((M$QSh`s1k&T&Xz;lXWFL Dwx~|x delta 1712 zcmV;h22c61OR-A~0RR916#xJL00000k#Q`MI5h+T00027apo5W6#xJL00EH@JRt(P zV+}N^=g*?yzUzl<2KV{YfKM-k3pvt;3M2{l>+ceix)?wpvCl+{pJpk5B2z`$xF_tR zISDoFjyBu_V41SLIavn)lNuR8VSl^a1Ll66L|s2a*435&wxqAh<2A{s@O-HVWTaiO zDqZB{K#TTbOQ;`YAY=Y+s0%f#N5$7*`OIkA}0n8KeS#MqTfYk)ep%jM=Pc+jMRcrLT4#HgNl*)pO@3r-EJ#eq$csDai3t@?;B8N2-K1Pm+0VOmumC4sP;ae_5;Atow3X zm0>h$k$Pe-;Vk;=z>-zPyUXHxGifwU*}=OMc1~gOJRHdNV|2?9)55x+|M-OS7q@`O zW}AQBu|fECk>BX|$Et8~d0!*(}hPAi&9b`Y7qd+b9tR^k^ zB`_kYHg(XldYDHrL7^w?m40g>$F>9J&1 zkNhiw@|$IjaQGhD@+luDg{fV%AXqPG_jf_|cLHYw=v^w3#o(bTJeFs#5z`e27)TqQ zJh7bI`^ye&T$ePdpVL?r0Lbg=3Z*#FsbXnHC2L|}L%N;)mfvU_e|&!13a#R&x?BXSO%!2ZKp zrr{edT}=^>Bt1}-s&G??uvSPP4hap&+@#|)6#=Rn43`fumWOgPZ>%zgOzW$rPERC{ zR6Q+nlX5d+&g(?G)E4hdE&wJYe{gJ(5p1K}5tBn@2bD=mf80G_{tQ-v$Oczse?k=1=HXN^tky0bHb&%4LBFrP3homM~x@odYhw!kBf(k~E4zqhZafSev9?fVAWs(bZM{ky~p31XL z>K`7n^PX450)`!2yv=sqWOY80-7tTy29b8;aURwCf5t0S&23MQ=6Y*)8bx7b2G_&M zoA(Ti&$#%dTq4j`gL>X~Bqo~?H`~XcuD6SY{;EN%44>ZS_geC<9h2Dgz|mQe(;>}c zNVoJ{s18o7$*oAUDe2Q0{`CPWtnBvoGp{w|cr{tH%Z~dha;G7{V(NXHrvY4t7_Y2T zm1EHve_c6;Q~=2gp;cN5ALIF?w|<;s_UmhDOFfrzW)Km045kBrp9VP4NGb@%IJLqU z6siA!$(d4i{JN{Em;I|wn;uJM!2}byUAp*rsu@C3-|FiP@&_PGel08E$oukoSoqw)O;* GbtOD_KRrAE diff --git a/test_fixtures/masp_proofs/DEDF664AD06184041515677A72699E65D4B40A4E1BC9E7A32D63CF28466A2F20.bin b/test_fixtures/masp_proofs/DA1D4780CBA612D7CBA0004D16FF5FEC42C100CEB0B19DC6C47DBFE88D42FFFC.bin similarity index 70% rename from test_fixtures/masp_proofs/DEDF664AD06184041515677A72699E65D4B40A4E1BC9E7A32D63CF28466A2F20.bin rename to test_fixtures/masp_proofs/DA1D4780CBA612D7CBA0004D16FF5FEC42C100CEB0B19DC6C47DBFE88D42FFFC.bin index 456bc93bb45167a7c842df5ab01057b96f38b569..7ac831cc950577636f40f3d6b2fd1722575b72bc 100644 GIT binary patch delta 3098 zcmV+#4CV9sgaP`50SN2=|NsC0|NoH)DFo~P|NpTy-z6vi|Ns9000Qvk$Rk&V68w5$ z{^HD_LWugA;P>k;{46yb5sa zuMtDw!gimNn6sSul$PDChP zaK6s6Wc>CL&A>U%kOmk+mFRmQwhdMqiYi@qE?lK}qDn=KJat=XlQC6-;P3*$-ZqW+ zr5VmF7UH}X^g;6nj8rNfdl2ch$UJ(~snLDvr zr)Q_?iq&F=xp$ogk^eh`Z${u3cTiR{#Sy#BP<1bacMJNy+XgV!`w=1K88Wm(xZh^H z07!?;=9|;*^G=|%bS;$ve}$2~DdJ+jSdPK#Xx1rwLK6Q_ADLg0Vq~D6`#7@xbE%fo z=zvxqR20)M-xGAPPDP*^|5q*oUR2R*bifj&Q>E}jQ z=?^qps7ayYJa!yCe|9OPM#~a;+X%B?tbjg2idu&;Q(pS$%bZdwRL~sRE$8bGIPaD5 zUZ|Lw(!lu!Q?AD<>_{@l@w1#Wl>&cnjn5>kPb4cbgvqicaD^MAP%74G&YEPzh&1|< zyM~1voBkQ~xN^HzglseQtZ3^r1gBEckG^P$mD*GzY_{_yJkDMN7hPJ|jOhNW2V6%k zun}}e#?f6F+Pq(^#A%X6wUoIO(Lr|F0c75sqw*p+DC+VhKbV01HMdw~vpBP`HkATV zv%pv7b*LoT-XT_fjXr9yK^t$ZoY8k1oz~7asWoSbw|`=)|*1#E`6E0Fe;7Ge`4kpoj9uN~!o(bh@V)k->0#eQib@kppgh!ql z$gVf!-BPnUfB1L4g`mMh+xiW%W!GBUcqfUqEA~lMjbvTr8hgS&kS7?=L7h9_2!6t) z^0y8eaQn7BebBo^Vm|l08Xf-&TuiXIqv{vYG7OEWXHqaBfBEn<-_$w@M{X6O@14NU{_3Gn3zC=v>yW%F)GZ2nN@ zy6nJBe`e{2th|u?(ZZf%F%}aYn?{f7ud;H|M+=gtd-%&-IvJ`ZnYvR4R1G)-J9zK4 z4DrkZrL?Ig-m!#xvns5oOQ{bHKsr>nXZwNk5KqqElAyl>GdZ$=LRC*E2t8bx%o2;F zVrp(UGc=B|lgX{MnYFQBQ>h8p5(ZNtAJGT#t2JR{_l9G+jbG&5lGY&6@hK9}14;7MT ze=VG~o&l1uai;2k3gcmknu9egVUURmR~fJ#&jD&y>FWF%OR3|ffng*BZQFgO9qyZf zD6%Mco6Gk}-P}y9dCJ8I=xK~(64~c}fe&R@37YRaAgc7b%Ow*9<9O~<_t^D1Rny#7Cn8-m}jr|~rY`$jVRd0CbzNeF92A-9vnQk4Yp$SESu zjv1=RPTXCIfx56Q6~}7QOXqm*r4OH3N1o$aB6}El#&oEmPo>ZRw?urE@p<=-f262h zyjZfCu{*bi0nM#N))1Q6wNv_X*F)!?jepc|ehjc2DlDi(J;fs2JDRG(t;e(Ta2J?S zk5lz5{WgY`y&&|itq(e(AvgBN?5BH~cT>7HlG|hTzv3B4Y~ZTpgy0tO`e#D@2!#Bii@nT^+vO_^s5JjHn2Nc~b?~Y((4wpQOY_XSdc0LZtx4 z3fzooj`<4$-0VoT78CtRJ$-nEnPui316*oMr3bKYCpjhe(qjLCf?KL~hKKE3SEzS- z)2b7gkT;^Lc=eA{tpI>irf03^9f;-{FLISB9}$ zA^xH5cu`zdPWR@>Hv4dR$+$=won-Qf{_PwiD6fI(aVNpwvo^4YS{J}4ZWts^A zdPdkLFb5zm3gM^TggosdFu#vVJiC{-x-xN=)6poNF@bLV2<5jEQOktU4+-DXIh)PQ zTdf?k@OQb*9H~t}RD=Q3So;x}L1OHLct8jjJIJ{r$Jw!Ge}Q7?Q_qSNLH15zATF?= z!i|`ad9PyKYN}Fp&(ku}`C+anYh_*xqOxvPM0#&SDV^&;&uH|Hjy2V58QiAw=7u!w z-biq&$_@y>!j()c8M1Joz@23%eGg7@2q7}{TR1>-CQ~E)b3qW-l{yqv?XdM1kEZkt z7rsLzH8D85e-aNXW;dx)ZztxRC4`V_`d!p2%!jO?ZC<(4Ra&UF&d%G|k+aTkGG*_j zt`c7LBk&qZ1x&u}j8clW>WRu@%CS8;8yP25EltV@*gDR$dnmZ~(LEJcA|=&qgDRHH z(aW;~U=ej`la7#1e;?q&0b?9a2&UP$o-MYmDn-7lfBn4r^QF6cH zoiTl)q25qKlz6WP3dVeK@ZKr5eaclekj>Hw^Qfk_%30|mp_6XL3uBEOr{EKUPEcf0m@F(XpFA#qj)ON! zx`83+P*Fl>;14T6j)qr$^#obEa6UdndqHK@!*98<-zW{E5|Z~$7^i^+tI5J39h0Zf ovQHnJbt1&V3B8w;YYE1VVR#|4gSQFRNA-}y`@Q^K0h1O&WcP{XSO5S3 delta 3098 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 diff --git a/test_fixtures/masp_proofs/978C35E058808D61F0E265D72DE8DD6A8E6226394EA6E3DFE1CFC10F69C0ACE0.bin b/test_fixtures/masp_proofs/DC5FEEE0E4971DF2083A9D17D645FC957F905749C47816C307B4B8D580DAE5D9.bin similarity index 78% rename from test_fixtures/masp_proofs/978C35E058808D61F0E265D72DE8DD6A8E6226394EA6E3DFE1CFC10F69C0ACE0.bin rename to test_fixtures/masp_proofs/DC5FEEE0E4971DF2083A9D17D645FC957F905749C47816C307B4B8D580DAE5D9.bin index aa8cdbab4d60086141eddf273fb89a0e3e3cb3cb..a8ef2782352ea711a83288d5f20e265a65ea35e3 100644 GIT binary patch delta 4206 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 Ed>ec&&;S4c delta 4209 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 delta 2781 zcmV<33L^EHcbRtx0RR916#xJLkq9XX0RR91000000h66GAhBAtA|@38000000@6Nd zq}S3l!bQ-Ly*-@l3=O0E#lfd6MPML(`uWnb{v?%)YPWw{uhq3hp5NO`3Z$lFCNwSFntTCBsvs)*X0)JwqDZ(pm zU$~NUIZ|`dF9$&)jjk?E+~_F}A8Zr}FqV@DEpYppAG?>;zf2){QV1O(3z3Z2e(03y z45SvPqVpu%sb(Zm;c$pLutgoQst9)x=uN$7{vax!Xm|n{VkS3v&c947ivtt=_O5NB7ulQ8J20rn(xK5HFA=tROM! zBqJt!tR%~=S3LM`f?)8&HnzmUc>@0swCkK$z3BbZw)Hn!F1~VAH}b4(O*MYoTM!9j zH5gOV=R>C!nZ&CTgzehlgAI*nGXk#c@Lb@0qGMPPG~;)J8|`&1#^0T=>RYpeEtLX) zGGW93PC{84#S5|hO2?}$x;$H3DnpauYgKG6#JDmD)aGykDd)^__OsPd+rcH)rCL3V?8lj?r#UkJinT;1rf z`yn`z=}gAU9nvDdGuL~=yy*DEGXR-rm6IbtE|t)YQY5?a7%AB)T_ae5E9Gmr)IFLB zMrRAWRIdrK_kR?VnRs+K#)AQ*`Y?VM)i6NZ`rp-5w220*T#k&Aqty^a5*mCdi1DnTec@e?G1!nxnS^+Y8!1^#-osBS=4x z7*8FbTz?i>j17t&(#BqErcU2rT%01pp~^??Xi12~I7 z0e{M9omqb-+49sp(`mq-`}z~eR^B9gA_!`F{X|S$XhHCuUN;k66ciD=YeNNtnk&ED zP>g`QgtX!5A_f`)hf3O~|G`;x{VJx4-7$Kz)SqjS#+TXKu!sNtHVzNY20|&>vxlpu z)=K7&R!iA<7|K&_!#JLGz;hOJ&%E*OS%2v>TWyb5exZu0nIlrAHD8GW#Up>UKJp;v zV4qi~N$`Q+$7YX#CSF#KoiV2?f>>+8h_W8L38mkIgZ{^MAN_xVb)hKlifd*={B|mO zZD%)=)tEx;7*7y~QBe?Yx!lv;Ouf(!Qw`m{p2vVW!tlsp=|(hY`B*SO>ME5Y6@O{Y zol$dzrWGdv@aU`PXTgNaf=!OjOH!#XCV^gdkESjVQLdkJAXy5EOjPYjkb2%4oxL|{ zI>S-Jya>!Z#n@Vp#AIAek=lxJ+aTku3_f*M=7a5PbiP&UIT&!EuzmW-#_xy~ebl|5 zPg3me<(l!aSoY;Dzq^~6jZ<}?mnbe=m&_xyM z1a}^~gtN@G+^;(&@)k_k&k1;upLSRy+sbFL7sDLd{+vA2t3hpNLvm~A>wl=W^7WIq ze=4E{vk|Vxqz`!R&mC*SpW53oGRmmtO>^D>(mQWKefWNfR8ajy0?DWqljeQ}r8ZbD zs_3zRkGC;}4xK)}%I;@*>t4%M_&mQ0p@S0k!^+otO2ED`x3>=Hf8>6fDuzOx11F&C z*B=!!?1G=`$w)jX!`fH~GJnar2UZ;CByoQt0T1jeW*|%~sbzR{sw1Id;g0nZFP&zk zwsA%$DVo0&F4F2uBg$#6ODg&Tg8rQ1gWEz5Qe0S4jRFIS4lR47ln$|2UGxUYx4}v zpt&4aP=@q=NaO9zu7AG(j5Hf|=!ni9k8+8kC$+#^?7lkt8yZmT9BI!wx03j%|7Vb^ zKOY0%J!J!vIJl0`=RZx7h4wX2>Cv8;aT5|uPoQTK_J%Z;!){0f5B_o^`G^2k`SFzS z2|21RPY>=4$c!7?_demg`;vVEFoN)p*}hA_ibBvU{;4_AntvzvVN!QnN&Mq Date: Thu, 14 Dec 2023 11:07:34 +0000 Subject: [PATCH 176/216] client: handle tx err in change-consensus-key, unbond, become-validator --- apps/src/lib/client/tx.rs | 155 ++++++++++++++++++++------------------ 1 file changed, 81 insertions(+), 74 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index a7ec85b626..d89ae06f8d 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -450,24 +450,24 @@ pub async fn submit_change_consensus_key( tx::dump_tx(namada.io(), &tx_args, tx); } else { sign(namada, &mut tx, &tx_args, signing_data).await?; - namada.submit(tx, &tx_args).await?; + let resp = namada.submit(tx, &tx_args).await?; if !tx_args.dry_run { - namada - .wallet_mut() - .await - .save() - .unwrap_or_else(|err| edisplay_line!(namada.io(), "{}", err)); + if resp.is_applied_and_valid().is_some() { + namada.wallet_mut().await.save().unwrap_or_else(|err| { + edisplay_line!(namada.io(), "{}", err) + }); - display_line!( - namada.io(), - "New consensus key stored with alias \ - \"{consensus_key_alias}\". It will become active \ - {EPOCH_SWITCH_BLOCKS_DELAY} blocks before pipeline offset \ - from the current epoch, at which point you'll need to give \ - the new key to CometBFT in order to be able to sign with it \ - in consensus.", - ); + display_line!( + namada.io(), + "New consensus key stored with alias \ + \"{consensus_key_alias}\". It will become active \ + {EPOCH_SWITCH_BLOCKS_DELAY} blocks before pipeline \ + offset from the current epoch, at which point you'll \ + need to give the new key to CometBFT in order to be able \ + to sign with it in consensus.", + ); + } } else { display_line!( namada.io(), @@ -779,70 +779,74 @@ pub async fn submit_become_validator( signing::generate_test_vector(namada, &tx).await?; - namada.submit(tx, &tx_args).await?; + let resp = namada.submit(tx, &tx_args).await?; if !tx_args.dry_run { - // add validator address and keys to the wallet - let mut wallet = namada.wallet_mut().await; - wallet.add_validator_data(address.clone(), validator_keys); - wallet - .save() - .unwrap_or_else(|err| edisplay_line!(namada.io(), "{}", err)); - - let tendermint_home = config.ledger.cometbft_dir(); - tendermint_node::write_validator_key( - &tendermint_home, - &wallet - .find_key_by_pk(&consensus_key, None) - .expect("unable to find consensus key pair in the wallet"), - ); - // To avoid wallet deadlocks in following operations - drop(wallet); - tendermint_node::write_validator_state(tendermint_home); - - // Write Namada config stuff or figure out how to do the above - // tendermint_node things two epochs in the future!!! - config.ledger.shell.tendermint_mode = TendermintMode::Validator; - config - .write( - &config.ledger.shell.base_dir, - &config.ledger.chain_id, - true, - ) - .unwrap(); + if resp.is_applied_and_valid().is_some() { + // add validator address and keys to the wallet + let mut wallet = namada.wallet_mut().await; + wallet.add_validator_data(address.clone(), validator_keys); + wallet.save().unwrap_or_else(|err| { + edisplay_line!(namada.io(), "{}", err) + }); + + let tendermint_home = config.ledger.cometbft_dir(); + tendermint_node::write_validator_key( + &tendermint_home, + &wallet.find_key_by_pk(&consensus_key, None).expect( + "unable to find consensus key pair in the wallet", + ), + ); + // To avoid wallet deadlocks in following operations + drop(wallet); + tendermint_node::write_validator_state(tendermint_home); + + // Write Namada config stuff or figure out how to do the above + // tendermint_node things two epochs in the future!!! + config.ledger.shell.tendermint_mode = TendermintMode::Validator; + config + .write( + &config.ledger.shell.base_dir, + &config.ledger.chain_id, + true, + ) + .unwrap(); - let pos_params = rpc::query_pos_parameters(namada.client()).await; + let pos_params = + rpc::query_pos_parameters(namada.client()).await; - display_line!(namada.io(), ""); - display_line!( - namada.io(), - "The keys for validator \"{alias}\" were stored in the wallet:" - ); - display_line!( - namada.io(), - " Validator account key \"{}\"", - validator_key_alias - ); - display_line!( - namada.io(), - " Consensus key \"{}\"", - consensus_key_alias - ); - display_line!( - namada.io(), - "The ledger node has been setup to use this validator's \ - address and consensus key." - ); - display_line!( - namada.io(), - "Your validator will be active in {} epochs. Be sure to \ - restart your node for the changes to take effect!", - pos_params.pipeline_len - ); + display_line!(namada.io(), ""); + display_line!( + namada.io(), + "The keys for validator \"{alias}\" were stored in the \ + wallet:" + ); + display_line!( + namada.io(), + " Validator account key \"{}\"", + validator_key_alias + ); + display_line!( + namada.io(), + " Consensus key \"{}\"", + consensus_key_alias + ); + display_line!( + namada.io(), + "The ledger node has been setup to use this validator's \ + address and consensus key." + ); + display_line!( + namada.io(), + "Your validator will be active in {} epochs. Be sure to \ + restart your node for the changes to take effect!", + pos_params.pipeline_len + ); + } } else { display_line!( namada.io(), - "Transaction dry run. No addresses have been saved." + "Transaction dry run. No key or addresses have been saved." ); } } @@ -1362,9 +1366,12 @@ where signing::generate_test_vector(namada, &tx).await?; - namada.submit(tx, &args.tx).await?; + let resp = namada.submit(tx, &args.tx).await?; - tx::query_unbonds(namada, args.clone(), latest_withdrawal_pre).await?; + if !args.tx.dry_run && resp.is_applied_and_valid().is_some() { + tx::query_unbonds(namada, args.clone(), latest_withdrawal_pre) + .await?; + } } Ok(()) From 3c49719177ae72b491981db3a3c1f6c60a3058fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 14 Dec 2023 11:17:21 +0000 Subject: [PATCH 177/216] client/tx: rm commented out code --- apps/src/lib/client/tx.rs | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index d89ae06f8d..08a4be23a7 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1496,28 +1496,6 @@ where Ok(()) } -// pub async fn submit_change_consensus_key( -// namada: &N, -// args: args::ConsensusKeyChange, -// ) -> Result<(), error::Error> -// where -// ::Error: std::fmt::Display, -// { -// let (mut tx, signing_data, _fee_unshield_epoch) = -// args.build(namada).await?; -// signing::generate_test_vector(namada, &tx).await?; - -// if args.tx.dump_tx { -// tx::dump_tx(namada.io(), &args.tx, tx); -// } else { -// namada.sign(&mut tx, &args.tx, signing_data).await?; - -// namada.submit(tx, &args.tx).await?; -// } - -// Ok(()) -// } - pub async fn submit_unjail_validator( namada: &N, args: args::TxUnjailValidator, From a2c2e014591140e1ba2c3d7663a514768c5538de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 14 Dec 2023 11:23:56 +0000 Subject: [PATCH 178/216] changelog: add #2279 --- .changelog/unreleased/bug-fixes/2279-fix-tx-result-handle.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/2279-fix-tx-result-handle.md diff --git a/.changelog/unreleased/bug-fixes/2279-fix-tx-result-handle.md b/.changelog/unreleased/bug-fixes/2279-fix-tx-result-handle.md new file mode 100644 index 0000000000..8d63c81eab --- /dev/null +++ b/.changelog/unreleased/bug-fixes/2279-fix-tx-result-handle.md @@ -0,0 +1,2 @@ +- Client: Check that transaction is successful before taking further actions. + ([\#2279](https://github.com/anoma/namada/pull/2279)) \ No newline at end of file From f882dd3f34faca255c53e668bf63b20bac1f4fb3 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 20 Dec 2023 10:36:19 +0100 Subject: [PATCH 179/216] [fix]: Don't spawn another async task to shutdown while waiting for genesis. --- apps/src/lib/node/ledger/mod.rs | 20 +++++++++++--------- sdk/src/control_flow/mod.rs | 4 ++-- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index f48b1964b4..c5188f5c7b 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -18,7 +18,6 @@ use namada::core::ledger::governance::storage::keys as governance_storage; use namada::eth_bridge::ethers::providers::{Http, Provider}; use namada::types::storage::Key; use namada::types::time::{DateTimeUtc, Utc}; -use namada_sdk::control_flow::install_shutdown_signal; use namada_sdk::tendermint::abci::request::CheckTxKind; use once_cell::unsync::Lazy; use sysinfo::{RefreshKind, System, SystemExt}; @@ -243,18 +242,18 @@ pub fn rollback(config: config::Ledger) -> Result<(), shell::Error> { /// /// All must be alive for correct functioning. async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { - let setup_data = run_aux_setup(&config, &wasm_dir).await; - - // Create an `AbortableSpawner` for signalling shut down from the shell or - // from Tendermint - let mut spawner = AbortableSpawner::new(); - // wait for genesis time let genesis_time = DateTimeUtc::try_from(config.genesis_time.clone()) .expect("Should be able to parse genesis time"); if let std::ops::ControlFlow::Break(_) = sleep_until(genesis_time).await { return; } + let setup_data = run_aux_setup(&config, &wasm_dir).await; + + // Create an `AbortableSpawner` for signalling shut down from the shell or + // from Tendermint + let mut spawner = AbortableSpawner::new(); + // Start Tendermint node let tendermint_node = start_tendermint(&mut spawner, &config); @@ -759,7 +758,6 @@ fn spawn_dummy_task(ready: T) -> task::JoinHandle { /// Sleep until the genesis time if necessary. async fn sleep_until(time: DateTimeUtc) -> std::ops::ControlFlow<()> { - let shutdown_signal = install_shutdown_signal(); // Sleep until start time if needed let sleep = async { if let Ok(sleep_time) = @@ -775,9 +773,13 @@ async fn sleep_until(time: DateTimeUtc) -> std::ops::ControlFlow<()> { } } }; + let shutdown_signal = async { + let (tx, rx) = tokio::sync::oneshot::channel(); + namada_sdk::control_flow::shutdown_send(tx).await; + rx.await + }; tokio::select! { _ = shutdown_signal => { - tracing::info!("Shutdown signal received, shutting down"); std::ops::ControlFlow::Break(()) } _ = sleep => { diff --git a/sdk/src/control_flow/mod.rs b/sdk/src/control_flow/mod.rs index 9b75b6e921..42294d5191 100644 --- a/sdk/src/control_flow/mod.rs +++ b/sdk/src/control_flow/mod.rs @@ -66,7 +66,7 @@ pub fn install_shutdown_signal() -> ShutdownSignal { } #[cfg(unix)] -async fn shutdown_send(tx: oneshot::Sender<()>) { +pub async fn shutdown_send(tx: oneshot::Sender<()>) { use tokio::signal::unix::{signal, SignalKind}; let mut sigterm = signal(SignalKind::terminate()).unwrap(); let mut sighup = signal(SignalKind::hangup()).unwrap(); @@ -107,7 +107,7 @@ async fn shutdown_send(tx: oneshot::Sender<()>) { } #[cfg(windows)] -async fn shutdown_send(tx: oneshot::Sender<()>) { +pub async fn shutdown_send(tx: oneshot::Sender<()>) { let mut sigbreak = tokio::signal::windows::ctrl_break().unwrap(); tokio::select! { signal = tokio::signal::ctrl_c() => { From b11789ca43ef6e3bcd137ab87aa57201267a1dfb Mon Sep 17 00:00:00 2001 From: Adrian Brink Date: Tue, 28 Nov 2023 11:05:43 +0100 Subject: [PATCH 180/216] First attempt at overview of light-sdk --- Cargo.lock | 7 ++ Cargo.toml | 1 + light_sdk/Cargo.toml | 17 ++++ light_sdk/src/lib.rs | 197 ++++++++++++++++++++++++++++++++++++++++++ light_sdk/src/main.rs | 4 + 5 files changed, 226 insertions(+) create mode 100644 light_sdk/Cargo.toml create mode 100644 light_sdk/src/lib.rs create mode 100644 light_sdk/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 8c5c490885..81553a3c6a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3820,6 +3820,13 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "light_sdk" +version = "0.27.0" +dependencies = [ + "namada_core", +] + [[package]] name = "linked-hash-map" version = "0.5.6" diff --git a/Cargo.toml b/Cargo.toml index a3513a0018..aba1b48fda 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ members = [ "vp_prelude", "encoding_spec", "sdk", + "light_sdk" ] # wasm packages have to be built separately diff --git a/light_sdk/Cargo.toml b/light_sdk/Cargo.toml new file mode 100644 index 0000000000..5a9a98ff28 --- /dev/null +++ b/light_sdk/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "light_sdk" +resolver = "2" +authors.workspace = true +edition.workspace = true +documentation.workspace = true +homepage.workspace = true +keywords.workspace = true +license.workspace = true +readme.workspace = true +repository.workspace = true +version.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +namada_core = {path = "../core", default-features = false} diff --git a/light_sdk/src/lib.rs b/light_sdk/src/lib.rs new file mode 100644 index 0000000000..5d4199eb3c --- /dev/null +++ b/light_sdk/src/lib.rs @@ -0,0 +1,197 @@ + + +/* + SDK for building transactions without having to pass in an entire Namada chain + + Requirements: + * Don't try to refactor the existing sdk - this shouldn't cause any merge conflicts with anything + * Minimal dependencies - no filesystem, no multi-threading, no IO, no wasm decoding + * core with default features turned off seems fine + * No lifetimes or abstract types + * it should be dump-ass simple to use this from other languages or contexts + * Callers should never have to worry about TxType::Raw or TxType::Wrapper - this should be hidden in the implementation + * No usage of async + * None of this signing code requires any async behavior - it's crazy to force all callers into async + + Proposed Flow: + * the crate should expose 1 struct and an implementation for that struct for every transaction type + * every struct (ie Bond) should have these functions + * new() - to create a new type of Bond - it takes all parameters, like chain_id (this will lead to duplication, which is desired for a simple API (if desired the caller can build their own TxFactory)) + * sign_bytes() - the bytes that need to be signed by the signer + * the crate should expose 1 struct and an implementation for a wrapper transaction + * new() + * should take a correctly signed inner tx (this is not enforced but rather documented, you can use this API to create garbage) + * takes the inner tx & the inner signature + * should take all the fields that are required by the wrapper + * sign_bytes() - the bytes that need to be signed by the signer + + Future Development: + * !!! AVOID ABSTRACT TYPES (that a caller needs to pass in) or LIFETIMES (in the caller api) LIKE THE PLAGUE !!! + * around this core light_sdk we can build more complex features + * get data via helper functions from a connected node + * can have an query endpoint that just takes a Tendermint RPC node and allows me to query it + * verify signatures + * support multi-sig signers + * dry-run these transactions + * ALWAYS EXPOSE SYNC AND ASYNC FUNCTIONALITY + * never force callers into using async - we must always support an API that synchronous + * none of this extra stuff should leak into the core + * need to be able to import the core to iOS or other languages without complex dependencies + * key backends + * file based key backends that callers can use + * libudev based key backends that call out to HSMs + + Questions: + * Can a single wrapper contain more than 1 inner transaction? + * Can the signer of the inner tx be different than of the wrapper transaction? + * Is the signature of the outer transaction dependent on the signature of the inner one? + * How do the tags work again? Can I only sign over the tags and not the wasm_hash? + * If we need wasm_hashes, those should be saved as constants in the binary and a caller can decide to pass in their own wasm hashes or load the constants. + + MAINNET_BOND_WASM_HASH: &str = "mainnet_wasm_hash"; + Bond::new("wasm_hash"); + Bond::new(MAINNET_BOND_WASM_HASH) + */ + +/* + * need a function sign_bytes() that just returns me the bytes to sign + * need a function that takes a transaction and some sign_bytes and forms a submittable tx from them + * this is roughly the API that we want + ```rust + let keypair = gen_keypair(); + + let mut wrapper = + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount_per_gas_unit: 0.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + keypair.ref_to(), + Epoch(0), + 0.into(), + None, + )))); + wrapper.header.chain_id = shell.chain_id.clone(); + wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); + wrapper.set_data(Data::new( + "Encrypted transaction data".as_bytes().to_owned(), + )); + wrapper.add_section(Section::Signature(Signature::new( + wrapper.sechashes(), + [(0, keypair)].into_iter().collect(), + None, + ))); + ``` + */ + +use namada_core::proto::Tx; +use namada_core::types::chain::ChainId; +use namada_core::types::transaction::{TxType, WrapperTx}; + +/* + * takes a single publickey and builds an inner reveal_pk transaction + * expose decode and encode functions for Borsh in this sdk so that users don't have to pass an abstract parameter to `add_data` + * maybe don't expose this and just accept parameter like PKs and encode them under the hood + */ +pub fn build_reveal_pk() -> () { + /* + * create a tx with TxType::Raw and set all the data on that one (including a correct chain id and some expiration time) + */ + let chain_id = ""; + let tx_code_hash = ""; // this needs to be the actual hash + let tx_tag = ""; // this needs + + // this should all be hidden in a constructor that just takes the parameters to the RevealPk transaction and does all the rest + + // Tx::new creates a TxType::Raw + let mut inner_tx = Tx::new(ChainId(chain_id.to_owned()), None); + inner_tx.add_code_from_hash(namada_core::types::hash::Hash::from_str(tx_code_hash).unwrap(), Some(tx_tag.to_owned())); + inner_tx.add_serialized_data(); // takes the borsh encoded data + + // call - inner_tx.add_wrapper() + // Does this call really just mutate the inner_tx.header.tx_type away from TxType::Raw and replace it with TxType::Wrapper? + let outer_tx = WrapperTx::new(fee, fee_payer, epoch, gas_limit, None); + inner_tx.header.tx_type = TxType::Wrapper(Box::new(outer_tx)); + + // the entire tx now exists and can be signed; expose signing functionality by having to call sign_bytes() on the tx object and then passing this to some signing oracle + /* + ```rust + // The inner tx signer signs the Decrypted version of the Header + let hashes = vec![self.raw_header_hash()]; + self.protocol_filter(); + + self.add_section(Section::Signature(Signature::new( + hashes, + account_public_keys_map.index_secret_keys(keypairs), + signer, + ))); + ``` + */ + // set the tx.header.tx_type to TxType::Raw and then turn it into a section hash with Section::Header(raw_header).get_hash() + // then sign over that hash and add it into a section + + // now try to sign the outer header + /* + ```rust + pub fn sign_wrapper(&mut self, keypair: common::SecretKey) -> &mut Self { + self.protocol_filter(); + self.add_section(Section::Signature(Signature::new( + self.sechashes(), + [(0, keypair)].into_iter().collect(), + None, + ))); + self + } + ``` + */ + // signs over all the sections + + // now the bytes should be submittable to cometbft `broadcast_tx` - just call tx.to_bytes() and submit + + println!("{:?}", inner_tx); +} + +/* + * takes a reveal pk transaction and wraps it in a wrapper transaction + */ +pub fn wrap_reveal_pk() -> () { + +} + +/* + * takes a wrapped reveal_pk transaction and signs it + */ +pub fn sign_reveal_pk() -> () { + +} + +/* + * takes any kind of inner tx and gives me back my sign bytes + */ +pub fn get_inner_sign_bytes() -> () { + +} + +/* + * takes any kind of outer tx and gives me back my sign bytes + */ +pub fn get_outer_sign_bytes() -> () { + +} + + + +pub fn add(left: usize, right: usize) -> usize { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} diff --git a/light_sdk/src/main.rs b/light_sdk/src/main.rs new file mode 100644 index 0000000000..4b793e2f97 --- /dev/null +++ b/light_sdk/src/main.rs @@ -0,0 +1,4 @@ +fn main() { + light_sdk::build_reveal_pk(); + println!("{:?}", light_sdk::add(1,2)); +} \ No newline at end of file From 91b2774058087b3551a39f51405acf1430fc210e Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Tue, 28 Nov 2023 16:31:21 +0200 Subject: [PATCH 181/216] Created light SDK builders. --- Cargo.lock | 1 + light_sdk/Cargo.toml | 1 + light_sdk/src/lib.rs | 877 ++++++++++++++++++++++++++++++++++++++---- light_sdk/src/main.rs | 4 +- 4 files changed, 801 insertions(+), 82 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 81553a3c6a..b6bf8d3dcd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3824,6 +3824,7 @@ dependencies = [ name = "light_sdk" version = "0.27.0" dependencies = [ + "borsh-ext", "namada_core", ] diff --git a/light_sdk/Cargo.toml b/light_sdk/Cargo.toml index 5a9a98ff28..2aa82b7c73 100644 --- a/light_sdk/Cargo.toml +++ b/light_sdk/Cargo.toml @@ -14,4 +14,5 @@ version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +borsh-ext.workspace = true namada_core = {path = "../core", default-features = false} diff --git a/light_sdk/src/lib.rs b/light_sdk/src/lib.rs index 5d4199eb3c..78bce14728 100644 --- a/light_sdk/src/lib.rs +++ b/light_sdk/src/lib.rs @@ -84,114 +84,833 @@ ``` */ -use namada_core::proto::Tx; +use namada_core::proto::{Tx, Signature}; use namada_core::types::chain::ChainId; -use namada_core::types::transaction::{TxType, WrapperTx}; +use namada_core::types::key::{common, secp256k1}; +use namada_core::types::time::DateTimeUtc; +use namada_core::types::hash::Hash; +use namada_core::types::transaction::GasLimit; +use namada_core::types::storage::Epoch; +use namada_core::types::transaction::Fee; +use namada_core::proto::Signer; +use std::collections::BTreeMap; +use namada_core::proto::Section; +use std::str::FromStr; +use namada_core::proto::TxError; +use namada_core::types::address::Address; +use namada_core::types::token; +use borsh_ext::BorshSerializeExt; +use namada_core::types::dec::Dec; +use namada_core::ledger::governance::storage::proposal::ProposalType; +use namada_core::types::token::{Amount, DenominatedAmount, MaspDenom}; + +/// Initialize account transaction WASM +pub const TX_INIT_ACCOUNT_WASM: &str = "tx_init_account.wasm"; +/// Initialize validator transaction WASM path +pub const TX_INIT_VALIDATOR_WASM: &str = "tx_init_validator.wasm"; +/// Unjail validator transaction WASM path +pub const TX_UNJAIL_VALIDATOR_WASM: &str = "tx_unjail_validator.wasm"; +/// Deactivate validator transaction WASM path +pub const TX_DEACTIVATE_VALIDATOR_WASM: &str = "tx_deactivate_validator.wasm"; +/// Reactivate validator transaction WASM path +pub const TX_REACTIVATE_VALIDATOR_WASM: &str = "tx_reactivate_validator.wasm"; +/// Initialize proposal transaction WASM path +pub const TX_INIT_PROPOSAL_WASM: &str = "tx_init_proposal.wasm"; +/// Vote transaction WASM path +pub const TX_VOTE_PROPOSAL: &str = "tx_vote_proposal.wasm"; +/// Reveal public key transaction WASM path +pub const TX_REVEAL_PK_WASM: &str = "tx_reveal_pk.wasm"; +/// Update validity predicate WASM path +pub const TX_UPDATE_ACCOUNT_WASM: &str = "tx_update_account.wasm"; +/// Transfer transaction WASM path +pub const TX_TRANSFER_WASM: &str = "tx_transfer.wasm"; +/// IBC transaction WASM path +pub const TX_IBC_WASM: &str = "tx_ibc.wasm"; +/// User validity predicate WASM path +pub const VP_USER_WASM: &str = "vp_user.wasm"; +/// Validator validity predicate WASM path +pub const VP_VALIDATOR_WASM: &str = "vp_validator.wasm"; +/// Bond WASM path +pub const TX_BOND_WASM: &str = "tx_bond.wasm"; +/// Unbond WASM path +pub const TX_UNBOND_WASM: &str = "tx_unbond.wasm"; +/// Withdraw WASM path +pub const TX_WITHDRAW_WASM: &str = "tx_withdraw.wasm"; +/// Claim-rewards WASM path +pub const TX_CLAIM_REWARDS_WASM: &str = "tx_claim_rewards.wasm"; +/// Bridge pool WASM path +pub const TX_BRIDGE_POOL_WASM: &str = "tx_bridge_pool.wasm"; +/// Change commission WASM path +pub const TX_CHANGE_COMMISSION_WASM: &str = + "tx_change_validator_commission.wasm"; +/// Change consensus key WASM path +pub const TX_CHANGE_CONSENSUS_KEY_WASM: &str = "tx_change_consensus_key.wasm"; +/// Change validator metadata WASM path +pub const TX_CHANGE_METADATA_WASM: &str = "tx_change_validator_metadata.wasm"; +/// Resign steward WASM path +pub const TX_RESIGN_STEWARD: &str = "tx_resign_steward.wasm"; +/// Update steward commission WASM path +pub const TX_UPDATE_STEWARD_COMMISSION: &str = + "tx_update_steward_commission.wasm"; +/// Redelegate transaction WASM path +pub const TX_REDELEGATE_WASM: &str = "tx_redelegate.wasm"; +/// Target chain ID +pub const CHAIN_ID: &str = "localnet.3e837878d84b54a40f-0"; +/// Reveal public key transaction code hash +pub const TX_REVEAL_PK_HASH: &str = "924d926119e24a16d2eb50752acedd9ffc506f5131bbfc866cdc1c0c20d2de77"; + +#[derive(Debug)] +pub enum Error { ParseWasmHashErr, ParseChainIdErr } + +fn build_tx( + data: Vec, + timestamp: DateTimeUtc, + expiration: Option, + code_hash: &str, + code_tag: &str, + chain_id: &str, +) -> Result { + // Provide default values for chain ID and code hash + let chain_id = ChainId(chain_id.to_owned()); + let code_hash = Hash::from_str(code_hash).map_err(|_| Error::ParseWasmHashErr)?; + // Construct a raw transaction + let mut inner_tx = Tx::new(chain_id, expiration); + inner_tx.header.timestamp = timestamp; + inner_tx.add_code_from_hash(code_hash, Some(code_tag.to_owned())); + inner_tx.add_serialized_data(data); // takes the borsh encoded data + Ok(inner_tx) +} -/* - * takes a single publickey and builds an inner reveal_pk transaction - * expose decode and encode functions for Borsh in this sdk so that users don't have to pass an abstract parameter to `add_data` - * maybe don't expose this and just accept parameter like PKs and encode them under the hood - */ -pub fn build_reveal_pk() -> () { - /* - * create a tx with TxType::Raw and set all the data on that one (including a correct chain id and some expiration time) - */ - let chain_id = ""; - let tx_code_hash = ""; // this needs to be the actual hash - let tx_tag = ""; // this needs - - // this should all be hidden in a constructor that just takes the parameters to the RevealPk transaction and does all the rest - - // Tx::new creates a TxType::Raw - let mut inner_tx = Tx::new(ChainId(chain_id.to_owned()), None); - inner_tx.add_code_from_hash(namada_core::types::hash::Hash::from_str(tx_code_hash).unwrap(), Some(tx_tag.to_owned())); - inner_tx.add_serialized_data(); // takes the borsh encoded data - - // call - inner_tx.add_wrapper() - // Does this call really just mutate the inner_tx.header.tx_type away from TxType::Raw and replace it with TxType::Wrapper? - let outer_tx = WrapperTx::new(fee, fee_payer, epoch, gas_limit, None); - inner_tx.header.tx_type = TxType::Wrapper(Box::new(outer_tx)); - - // the entire tx now exists and can be signed; expose signing functionality by having to call sign_bytes() on the tx object and then passing this to some signing oracle - /* - ```rust - // The inner tx signer signs the Decrypted version of the Header - let hashes = vec![self.raw_header_hash()]; - self.protocol_filter(); - - self.add_section(Section::Signature(Signature::new( - hashes, - account_public_keys_map.index_secret_keys(keypairs), - signer, - ))); - ``` - */ - // set the tx.header.tx_type to TxType::Raw and then turn it into a section hash with Section::Header(raw_header).get_hash() - // then sign over that hash and add it into a section +fn sign_bytes(tx: &Tx) -> Hash { + let mut tx = tx.clone(); + tx.protocol_filter(); + Signature { + targets: vec![tx.raw_header_hash()], + signer: Signer::PubKeys(vec![]), + signatures: BTreeMap::new(), + }.get_raw_hash() +} - // now try to sign the outer header - /* - ```rust - pub fn sign_wrapper(&mut self, keypair: common::SecretKey) -> &mut Self { - self.protocol_filter(); - self.add_section(Section::Signature(Signature::new( - self.sechashes(), - [(0, keypair)].into_iter().collect(), - None, - ))); - self +fn attach_inner_signatures( + tx: &Tx, + signer: Signer, + signatures: BTreeMap, +) -> Tx { + let mut tx = tx.clone(); + tx.add_section(Section::Signature(Signature { + targets: vec![tx.raw_header_hash()], + signer, + signatures, + })); + tx +} + +pub struct RevealPk(Tx); + +impl RevealPk { + /// Build a raw Reveal Public Key transaction from the given parameters + pub fn new( + public_key: common::PublicKey, + timestamp: DateTimeUtc, + expiration: Option, + code_hash: &str, + chain_id: &str, + ) -> Result { + build_tx(public_key.serialize_to_vec(), timestamp, expiration, code_hash, TX_REVEAL_PK_WASM, chain_id) + .map(Self) } - ``` - */ - // signs over all the sections - // now the bytes should be submittable to cometbft `broadcast_tx` - just call tx.to_bytes() and submit + /// Takes any kind of inner tx and gives me back my sign bytes + pub fn sign_bytes(&self) -> Hash { + sign_bytes(&self.0) + } - println!("{:?}", inner_tx); + /// Attach the given inner signatures to the transaction + pub fn attach_signatures( + &self, + signer: Signer, + signatures: BTreeMap, + ) -> Self { + Self(attach_inner_signatures(&self.0, signer, signatures)) + } } -/* - * takes a reveal pk transaction and wraps it in a wrapper transaction - */ -pub fn wrap_reveal_pk() -> () { +pub struct Bond(Tx); + +impl Bond { + /// Build a raw Bond transaction from the given parameters + pub fn new( + validator: Address, + amount: token::Amount, + source: Option
, + timestamp: DateTimeUtc, + expiration: Option, + code_hash: &str, + chain_id: &str, + ) -> Result { + let bond = namada_core::types::transaction::pos::Bond { + validator, + amount, + source, + }; + + build_tx(bond.serialize_to_vec(), timestamp, expiration, code_hash, TX_BOND_WASM, chain_id) + .map(Self) + } + + /// Takes any kind of inner tx and gives me back my sign bytes + pub fn sign_bytes(&self) -> Hash { + sign_bytes(&self.0) + } + /// Attach the given inner signatures to the transaction + pub fn attach_signatures( + &self, + signer: Signer, + signatures: BTreeMap, + ) -> Self { + Self(attach_inner_signatures(&self.0, signer, signatures)) + } } -/* - * takes a wrapped reveal_pk transaction and signs it - */ -pub fn sign_reveal_pk() -> () { +pub struct Unbond(Tx); + +impl Unbond { + /// Build a raw Bond transaction from the given parameters + pub fn new( + validator: Address, + amount: token::Amount, + source: Option
, + timestamp: DateTimeUtc, + expiration: Option, + code_hash: &str, + chain_id: &str, + ) -> Result { + let bond = namada_core::types::transaction::pos::Unbond { + validator, + amount, + source, + }; + + build_tx(bond.serialize_to_vec(), timestamp, expiration, code_hash, TX_UNBOND_WASM, chain_id) + .map(Self) + } + + /// Takes any kind of inner tx and gives me back my sign bytes + pub fn sign_bytes(&self) -> Hash { + sign_bytes(&self.0) + } + /// Attach the given inner signatures to the transaction + pub fn attach_signatures( + &self, + signer: Signer, + signatures: BTreeMap, + ) -> Self { + Self(attach_inner_signatures(&self.0, signer, signatures)) + } } -/* - * takes any kind of inner tx and gives me back my sign bytes - */ -pub fn get_inner_sign_bytes() -> () { +pub struct InitAccount(Tx); + +impl InitAccount { + /// Build a raw Bond transaction from the given parameters + pub fn new( + public_keys: Vec, + vp_code_hash: Hash, + threshold: u8, + timestamp: DateTimeUtc, + expiration: Option, + code_hash: &str, + chain_id: &str, + ) -> Result { + let init_account = namada_core::types::transaction::account::InitAccount { + public_keys, + vp_code_hash, + threshold, + }; + + build_tx(init_account.serialize_to_vec(), timestamp, expiration, code_hash, TX_INIT_ACCOUNT_WASM, chain_id) + .map(Self) + } + /// Takes any kind of inner tx and gives me back my sign bytes + pub fn sign_bytes(&self) -> Hash { + sign_bytes(&self.0) + } + + /// Attach the given inner signatures to the transaction + pub fn attach_signatures( + &self, + signer: Signer, + signatures: BTreeMap, + ) -> Self { + Self(attach_inner_signatures(&self.0, signer, signatures)) + } } -/* - * takes any kind of outer tx and gives me back my sign bytes - */ -pub fn get_outer_sign_bytes() -> () { +pub struct UpdateAccount(Tx); + +impl UpdateAccount { + /// Build a raw Bond transaction from the given parameters + pub fn new( + addr: Address, + vp_code_hash: Option, + public_keys: Vec, + threshold: Option, + timestamp: DateTimeUtc, + expiration: Option, + code_hash: &str, + chain_id: &str, + ) -> Result { + let update_account = namada_core::types::transaction::account::UpdateAccount { + addr, + vp_code_hash, + public_keys, + threshold, + }; + + build_tx(update_account.serialize_to_vec(), timestamp, expiration, code_hash, TX_UPDATE_ACCOUNT_WASM, chain_id) + .map(Self) + } + + /// Takes any kind of inner tx and gives me back my sign bytes + pub fn sign_bytes(&self) -> Hash { + sign_bytes(&self.0) + } + /// Attach the given inner signatures to the transaction + pub fn attach_signatures( + &self, + signer: Signer, + signatures: BTreeMap, + ) -> Self { + Self(attach_inner_signatures(&self.0, signer, signatures)) + } } +pub struct InitValidator(Tx); + +impl InitValidator { + /// Build a raw Bond transaction from the given parameters + pub fn new( + account_keys: Vec, + threshold: u8, + consensus_key: common::PublicKey, + eth_cold_key: secp256k1::PublicKey, + eth_hot_key: secp256k1::PublicKey, + protocol_key: common::PublicKey, + commission_rate: Dec, + max_commission_rate_change: Dec, + email: String, + description: Option, + website: Option, + discord_handle: Option, + validator_vp_code_hash: Hash, + timestamp: DateTimeUtc, + expiration: Option, + code_hash: &str, + chain_id: &str, + ) -> Result { + let update_account = namada_core::types::transaction::pos::InitValidator { + account_keys, + threshold, + consensus_key, + eth_cold_key, + eth_hot_key, + protocol_key, + commission_rate, + max_commission_rate_change, + email, + description, + website, + discord_handle, + validator_vp_code_hash, + }; + + build_tx(update_account.serialize_to_vec(), timestamp, expiration, code_hash, TX_INIT_VALIDATOR_WASM, chain_id) + .map(Self) + } + /// Takes any kind of inner tx and gives me back my sign bytes + pub fn sign_bytes(&self) -> Hash { + sign_bytes(&self.0) + } -pub fn add(left: usize, right: usize) -> usize { - left + right + /// Attach the given inner signatures to the transaction + pub fn attach_signatures( + &self, + signer: Signer, + signatures: BTreeMap, + ) -> Self { + Self(attach_inner_signatures(&self.0, signer, signatures)) + } +} + +pub struct InitProposal(Tx); + +impl InitProposal { + /// Build a raw Bond transaction from the given parameters + pub fn new( + id: Option, + content: Hash, + author: Address, + r#type: ProposalType, + voting_start_epoch: Epoch, + voting_end_epoch: Epoch, + grace_epoch: Epoch, + timestamp: DateTimeUtc, + expiration: Option, + code_hash: &str, + chain_id: &str, + ) -> Result { + let init_proposal = namada_core::types::transaction::governance::InitProposalData { + id, + content, + author, + r#type, + voting_start_epoch, + voting_end_epoch, + grace_epoch, + }; + + build_tx(init_proposal.serialize_to_vec(), timestamp, expiration, code_hash, TX_INIT_PROPOSAL_WASM, chain_id) + .map(Self) + } + + /// Takes any kind of inner tx and gives me back my sign bytes + pub fn sign_bytes(&self) -> Hash { + sign_bytes(&self.0) + } + + /// Attach the given inner signatures to the transaction + pub fn attach_signatures( + &self, + signer: Signer, + signatures: BTreeMap, + ) -> Self { + Self(attach_inner_signatures(&self.0, signer, signatures)) + } +} + +pub struct Transfer(Tx); + +impl Transfer { + /// Build a raw Bond transaction from the given parameters + pub fn new( + source: Address, + target: Address, + token: Address, + amount: DenominatedAmount, + key: Option, + shielded: Option, + timestamp: DateTimeUtc, + expiration: Option, + code_hash: &str, + chain_id: &str, + ) -> Result { + let init_proposal = namada_core::types::token::Transfer { + source, + target, + token, + amount, + key, + shielded, + }; + + build_tx(init_proposal.serialize_to_vec(), timestamp, expiration, code_hash, TX_TRANSFER_WASM, chain_id) + .map(Self) + } + + /// Takes any kind of inner tx and gives me back my sign bytes + pub fn sign_bytes(&self) -> Hash { + sign_bytes(&self.0) + } + + /// Attach the given inner signatures to the transaction + pub fn attach_signatures( + &self, + signer: Signer, + signatures: BTreeMap, + ) -> Self { + Self(attach_inner_signatures(&self.0, signer, signatures)) + } +} + +pub struct Withdraw(Tx); + +impl Withdraw { + /// Build a raw Bond transaction from the given parameters + pub fn new( + validator: Address, + source: Option
, + timestamp: DateTimeUtc, + expiration: Option, + code_hash: &str, + chain_id: &str, + ) -> Result { + let init_proposal = namada_core::types::transaction::pos::Withdraw { + validator, + source, + }; + + build_tx(init_proposal.serialize_to_vec(), timestamp, expiration, code_hash, TX_WITHDRAW_WASM, chain_id) + .map(Self) + } + + /// Takes any kind of inner tx and gives me back my sign bytes + pub fn sign_bytes(&self) -> Hash { + sign_bytes(&self.0) + } + + /// Attach the given inner signatures to the transaction + pub fn attach_signatures( + &self, + signer: Signer, + signatures: BTreeMap, + ) -> Self { + Self(attach_inner_signatures(&self.0, signer, signatures)) + } +} + +pub struct ClaimRewards(Tx); + +impl ClaimRewards { + /// Build a raw Bond transaction from the given parameters + pub fn new( + validator: Address, + source: Option
, + timestamp: DateTimeUtc, + expiration: Option, + code_hash: &str, + chain_id: &str, + ) -> Result { + let init_proposal = namada_core::types::transaction::pos::Withdraw { + validator, + source, + }; + + build_tx(init_proposal.serialize_to_vec(), timestamp, expiration, code_hash, TX_CLAIM_REWARDS_WASM, chain_id) + .map(Self) + } + + /// Takes any kind of inner tx and gives me back my sign bytes + pub fn sign_bytes(&self) -> Hash { + sign_bytes(&self.0) + } + + /// Attach the given inner signatures to the transaction + pub fn attach_signatures( + &self, + signer: Signer, + signatures: BTreeMap, + ) -> Self { + Self(attach_inner_signatures(&self.0, signer, signatures)) + } +} + +pub struct ChangeCommission(Tx); + +impl ChangeCommission { + /// Build a raw Bond transaction from the given parameters + pub fn new( + validator: Address, + new_rate: Dec, + timestamp: DateTimeUtc, + expiration: Option, + code_hash: &str, + chain_id: &str, + ) -> Result { + let init_proposal = namada_core::types::transaction::pos::CommissionChange { + validator, + new_rate, + }; + + build_tx(init_proposal.serialize_to_vec(), timestamp, expiration, code_hash, TX_CHANGE_COMMISSION_WASM, chain_id) + .map(Self) + } + + /// Takes any kind of inner tx and gives me back my sign bytes + pub fn sign_bytes(&self) -> Hash { + sign_bytes(&self.0) + } + + /// Attach the given inner signatures to the transaction + pub fn attach_signatures( + &self, + signer: Signer, + signatures: BTreeMap, + ) -> Self { + Self(attach_inner_signatures(&self.0, signer, signatures)) + } +} + +pub struct ChangeMetaData(Tx); + +impl ChangeMetaData { + /// Build a raw Bond transaction from the given parameters + pub fn new( + validator: Address, + email: Option, + description: Option, + website: Option, + discord_handle: Option, + commission_rate: Option, + timestamp: DateTimeUtc, + expiration: Option, + code_hash: &str, + chain_id: &str, + ) -> Result { + let init_proposal = namada_core::types::transaction::pos::MetaDataChange { + validator, + email, + description, + website, + discord_handle, + commission_rate, + }; + + build_tx(init_proposal.serialize_to_vec(), timestamp, expiration, code_hash, TX_CHANGE_METADATA_WASM, chain_id) + .map(Self) + } + + /// Takes any kind of inner tx and gives me back my sign bytes + pub fn sign_bytes(&self) -> Hash { + sign_bytes(&self.0) + } + + /// Attach the given inner signatures to the transaction + pub fn attach_signatures( + &self, + signer: Signer, + signatures: BTreeMap, + ) -> Self { + Self(attach_inner_signatures(&self.0, signer, signatures)) + } +} + +pub struct ChangeConsensusKey(Tx); + +impl ChangeConsensusKey { + /// Build a raw Bond transaction from the given parameters + pub fn new( + validator: Address, + consensus_key: common::PublicKey, + timestamp: DateTimeUtc, + expiration: Option, + code_hash: &str, + chain_id: &str, + ) -> Result { + let init_proposal = namada_core::types::transaction::pos::ConsensusKeyChange { + validator, + consensus_key, + }; + + build_tx(init_proposal.serialize_to_vec(), timestamp, expiration, code_hash, TX_CHANGE_CONSENSUS_KEY_WASM, chain_id) + .map(Self) + } + + /// Takes any kind of inner tx and gives me back my sign bytes + pub fn sign_bytes(&self) -> Hash { + sign_bytes(&self.0) + } + + /// Attach the given inner signatures to the transaction + pub fn attach_signatures( + &self, + signer: Signer, + signatures: BTreeMap, + ) -> Self { + Self(attach_inner_signatures(&self.0, signer, signatures)) + } +} + +pub struct UnjailValidator(Tx); + +impl UnjailValidator { + /// Build a raw Bond transaction from the given parameters + pub fn new( + address: Address, + timestamp: DateTimeUtc, + expiration: Option, + code_hash: &str, + chain_id: &str, + ) -> Result { + build_tx(address.serialize_to_vec(), timestamp, expiration, code_hash, TX_UNJAIL_VALIDATOR_WASM, chain_id) + .map(Self) + } + + /// Takes any kind of inner tx and gives me back my sign bytes + pub fn sign_bytes(&self) -> Hash { + sign_bytes(&self.0) + } + + /// Attach the given inner signatures to the transaction + pub fn attach_signatures( + &self, + signer: Signer, + signatures: BTreeMap, + ) -> Self { + Self(attach_inner_signatures(&self.0, signer, signatures)) + } +} + +pub struct DeactivateValidator(Tx); + +impl DeactivateValidator { + /// Build a raw Bond transaction from the given parameters + pub fn new( + address: Address, + timestamp: DateTimeUtc, + expiration: Option, + code_hash: &str, + chain_id: &str, + ) -> Result { + build_tx(address.serialize_to_vec(), timestamp, expiration, code_hash, TX_DEACTIVATE_VALIDATOR_WASM, chain_id) + .map(Self) + } + + /// Takes any kind of inner tx and gives me back my sign bytes + pub fn sign_bytes(&self) -> Hash { + sign_bytes(&self.0) + } + + /// Attach the given inner signatures to the transaction + pub fn attach_signatures( + &self, + signer: Signer, + signatures: BTreeMap, + ) -> Self { + Self(attach_inner_signatures(&self.0, signer, signatures)) + } +} + +pub struct ReactivateValidator(Tx); + +impl ReactivateValidator { + /// Build a raw Bond transaction from the given parameters + pub fn new( + address: Address, + timestamp: DateTimeUtc, + expiration: Option, + code_hash: &str, + chain_id: &str, + ) -> Result { + build_tx(address.serialize_to_vec(), timestamp, expiration, code_hash, TX_REACTIVATE_VALIDATOR_WASM, chain_id) + .map(Self) + } + + /// Takes any kind of inner tx and gives me back my sign bytes + pub fn sign_bytes(&self) -> Hash { + sign_bytes(&self.0) + } + + /// Attach the given inner signatures to the transaction + pub fn attach_signatures( + &self, + signer: Signer, + signatures: BTreeMap, + ) -> Self { + Self(attach_inner_signatures(&self.0, signer, signatures)) + } +} + +pub struct Wrapper(Tx); + +impl Wrapper { + /// Takes a transaction and some signatures and wraps them in a wrapper + /// transaction + pub fn new( + tx: &Tx, + fee: Fee, + fee_payer: common::PublicKey, + epoch: Epoch, + gas_limit: GasLimit, + unshield_hash: Option, + ) -> Self { + let mut tx = tx.clone(); + tx.add_wrapper(fee, fee_payer, epoch, gas_limit, unshield_hash); + Self(tx) + } + + /// Takes any kind of outer tx and gives me back my sign bytes + pub fn sign_bytes(&self) -> Hash { + let mut tx = self.0.clone(); + tx.protocol_filter(); + Signature { + targets: tx.sechashes(), + signer: Signer::PubKeys(vec![]), + signatures: BTreeMap::new(), + }.get_raw_hash() + } + + /// Attach the given outer signatures to the transaction + pub fn attach_signatures( + &self, + signer: common::PublicKey, + signature: common::Signature, + ) -> Self { + let mut tx = self.0.clone(); + tx.add_section(Section::Signature(Signature { + targets: tx.sechashes(), + signer: Signer::PubKeys(vec![signer]), + signatures: [(0, signature)].into_iter().collect(), + })); + Self(tx) + } + + /// Validate this wrapper transaction + pub fn validate_tx(&self) -> std::result::Result, TxError> { + self.0.validate_tx() + } } #[cfg(test)] mod tests { use super::*; + use namada_core::types::key::RefTo; + use namada_core::types::key::SigScheme; + use namada_core::types::token::Amount; + use namada_core::types::address::Address; + use std::str::FromStr; #[test] fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); + // Setup the keys and addresses necessary for this test + let nam = Address::from_str("tnam1q8vyjrk5n30vfphaa26v7prkh8mjv4rfd5fkxh80") + .expect("unable to construct address"); + let sk = common::SecretKey::from_str("0083318ccceac6c08a0177667840b4b93f0e455e45d4c38c28b73b8f8462fbf548") + .expect("unable to construct secret key"); + let pk = sk.ref_to(); + let now = DateTimeUtc::now(); + // Make the raw reveal PK transaction + let reveal_pk = RevealPk::new(pk.clone(), now, None, TX_REVEAL_PK_HASH, CHAIN_ID) + .unwrap(); + // Sign the raw reveal PK transaction + let inner_hash = reveal_pk.sign_bytes(); + let sig = common::SigScheme::sign(&sk, inner_hash); + let signatures = [(0, sig)].into_iter().collect(); + // Attach the inner signature to the transaction + let reveal_pk = reveal_pk.attach_signatures(Signer::PubKeys(vec![pk.clone()]), signatures); + let fee = Fee { + amount_per_gas_unit: Amount::from(10), + token: nam, + }; + // Wrap the inner transaction + let wrapper_tx = Wrapper::new( + &reveal_pk.0, + fee, + pk.clone(), + Epoch::from(10), + GasLimit::from(20_000), + None, + ); + // Sign the wrapper transaction + let outer_hash = wrapper_tx.sign_bytes(); + let sig = common::SigScheme::sign(&sk, outer_hash); + // Attach the wrapper signature to the transaction + let wrapper_tx = wrapper_tx.attach_signatures(pk.clone(), sig); + // Validate the outcome + wrapper_tx.0.validate_tx().expect("failed to validate transaction"); } } diff --git a/light_sdk/src/main.rs b/light_sdk/src/main.rs index 4b793e2f97..f79c691f08 100644 --- a/light_sdk/src/main.rs +++ b/light_sdk/src/main.rs @@ -1,4 +1,2 @@ fn main() { - light_sdk::build_reveal_pk(); - println!("{:?}", light_sdk::add(1,2)); -} \ No newline at end of file +} From 70731720554a7ccc2e207e97cebc5eb4fabd093a Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 5 Dec 2023 16:20:22 +0100 Subject: [PATCH 182/216] Refactors pos txs in separate file. Improved shared functions --- Cargo.lock | 1 + light_sdk/Cargo.toml | 1 + light_sdk/src/lib.rs | 921 +++--------------------- light_sdk/src/main.rs | 2 - light_sdk/src/tx_builders/account.rs | 139 ++++ light_sdk/src/tx_builders/governance.rs | 117 +++ light_sdk/src/tx_builders/mod.rs | 69 ++ light_sdk/src/tx_builders/pgf.rs | 93 +++ light_sdk/src/tx_builders/pos.rs | 454 ++++++++++++ 9 files changed, 986 insertions(+), 811 deletions(-) delete mode 100644 light_sdk/src/main.rs create mode 100644 light_sdk/src/tx_builders/account.rs create mode 100644 light_sdk/src/tx_builders/governance.rs create mode 100644 light_sdk/src/tx_builders/mod.rs create mode 100644 light_sdk/src/tx_builders/pgf.rs create mode 100644 light_sdk/src/tx_builders/pos.rs diff --git a/Cargo.lock b/Cargo.lock index b6bf8d3dcd..a956dc79d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3824,6 +3824,7 @@ dependencies = [ name = "light_sdk" version = "0.27.0" dependencies = [ + "borsh", "borsh-ext", "namada_core", ] diff --git a/light_sdk/Cargo.toml b/light_sdk/Cargo.toml index 2aa82b7c73..752b0e3e4a 100644 --- a/light_sdk/Cargo.toml +++ b/light_sdk/Cargo.toml @@ -14,5 +14,6 @@ version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +borsh.workspace = true borsh-ext.workspace = true namada_core = {path = "../core", default-features = false} diff --git a/light_sdk/src/lib.rs b/light_sdk/src/lib.rs index 78bce14728..f66f7ed730 100644 --- a/light_sdk/src/lib.rs +++ b/light_sdk/src/lib.rs @@ -1,497 +1,118 @@ - - /* - SDK for building transactions without having to pass in an entire Namada chain - - Requirements: - * Don't try to refactor the existing sdk - this shouldn't cause any merge conflicts with anything - * Minimal dependencies - no filesystem, no multi-threading, no IO, no wasm decoding - * core with default features turned off seems fine - * No lifetimes or abstract types - * it should be dump-ass simple to use this from other languages or contexts - * Callers should never have to worry about TxType::Raw or TxType::Wrapper - this should be hidden in the implementation - * No usage of async - * None of this signing code requires any async behavior - it's crazy to force all callers into async - - Proposed Flow: - * the crate should expose 1 struct and an implementation for that struct for every transaction type - * every struct (ie Bond) should have these functions - * new() - to create a new type of Bond - it takes all parameters, like chain_id (this will lead to duplication, which is desired for a simple API (if desired the caller can build their own TxFactory)) - * sign_bytes() - the bytes that need to be signed by the signer - * the crate should expose 1 struct and an implementation for a wrapper transaction - * new() - * should take a correctly signed inner tx (this is not enforced but rather documented, you can use this API to create garbage) - * takes the inner tx & the inner signature - * should take all the fields that are required by the wrapper - * sign_bytes() - the bytes that need to be signed by the signer - - Future Development: - * !!! AVOID ABSTRACT TYPES (that a caller needs to pass in) or LIFETIMES (in the caller api) LIKE THE PLAGUE !!! - * around this core light_sdk we can build more complex features - * get data via helper functions from a connected node - * can have an query endpoint that just takes a Tendermint RPC node and allows me to query it - * verify signatures - * support multi-sig signers - * dry-run these transactions - * ALWAYS EXPOSE SYNC AND ASYNC FUNCTIONALITY - * never force callers into using async - we must always support an API that synchronous - * none of this extra stuff should leak into the core - * need to be able to import the core to iOS or other languages without complex dependencies - * key backends - * file based key backends that callers can use - * libudev based key backends that call out to HSMs - - Questions: - * Can a single wrapper contain more than 1 inner transaction? - * Can the signer of the inner tx be different than of the wrapper transaction? - * Is the signature of the outer transaction dependent on the signature of the inner one? - * How do the tags work again? Can I only sign over the tags and not the wasm_hash? - * If we need wasm_hashes, those should be saved as constants in the binary and a caller can decide to pass in their own wasm hashes or load the constants. - - MAINNET_BOND_WASM_HASH: &str = "mainnet_wasm_hash"; - Bond::new("wasm_hash"); - Bond::new(MAINNET_BOND_WASM_HASH) - */ + SDK for building transactions without having to pass in an entire Namada chain + + Requirements: + * Don't try to refactor the existing sdk - this shouldn't cause any merge conflicts with anything + * Minimal dependencies - no filesystem, no multi-threading, no IO, no wasm decoding + * core with default features turned off seems fine + * No lifetimes or abstract types + * it should be dump-ass simple to use this from other languages or contexts + * Callers should never have to worry about TxType::Raw or TxType::Wrapper - this should be hidden in the implementation + * No usage of async + * None of this signing code requires any async behavior - it's crazy to force all callers into async + + Proposed Flow: + * the crate should expose 1 struct and an implementation for that struct for every transaction type + * every struct (ie Bond) should have these functions + * new() - to create a new type of Bond - it takes all parameters, like chain_id (this will lead to duplication, which is desired for a simple API (if desired the caller can build their own TxFactory)) + * sign_bytes() - the bytes that need to be signed by the signer + * the crate should expose 1 struct and an implementation for a wrapper transaction + * new() + * should take a correctly signed inner tx (this is not enforced but rather documented, you can use this API to create garbage) + * takes the inner tx & the inner signature + * should take all the fields that are required by the wrapper + * sign_bytes() - the bytes that need to be signed by the signer + + Future Development: + * !!! AVOID ABSTRACT TYPES (that a caller needs to pass in) or LIFETIMES (in the caller api) LIKE THE PLAGUE !!! + * around this core light_sdk we can build more complex features + * get data via helper functions from a connected node + * can have an query endpoint that just takes a Tendermint RPC node and allows me to query it + * verify signatures + * support multi-sig signers + * dry-run these transactions + * ALWAYS EXPOSE SYNC AND ASYNC FUNCTIONALITY + * never force callers into using async - we must always support an API that synchronous + * none of this extra stuff should leak into the core + * need to be able to import the core to iOS or other languages without complex dependencies + * key backends + * file based key backends that callers can use + * libudev based key backends that call out to HSMs + + Questions: + * Can a single wrapper contain more than 1 inner transaction? Yes but it will not be executed (just extra payload for which gas is paid) + * Can the signer of the inner tx be different than of the wrapper transaction? Yes it can + * Is the signature of the outer transaction dependent on the signature of the inner one? Not at all, we only sign headers of transactions, so the inner signature is not part of the message that is signed for the wrapper + * How do the tags work again? Can I only sign over the tags and not the wasm_hash? + * If we need wasm_hashes, those should be saved as constants in the binary and a caller can decide to pass in their own wasm hashes or load the constants. + + MAINNET_BOND_WASM_HASH: &str = "mainnet_wasm_hash"; + Bond::new("wasm_hash"); + Bond::new(MAINNET_BOND_WASM_HASH) +*/ /* - * need a function sign_bytes() that just returns me the bytes to sign - * need a function that takes a transaction and some sign_bytes and forms a submittable tx from them - * this is roughly the API that we want - ```rust - let keypair = gen_keypair(); - - let mut wrapper = - Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( - Fee { - amount_per_gas_unit: 0.into(), - token: shell.wl_storage.storage.native_token.clone(), - }, - keypair.ref_to(), - Epoch(0), - 0.into(), - None, - )))); - wrapper.header.chain_id = shell.chain_id.clone(); - wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); - wrapper.set_data(Data::new( - "Encrypted transaction data".as_bytes().to_owned(), - )); - wrapper.add_section(Section::Signature(Signature::new( - wrapper.sechashes(), - [(0, keypair)].into_iter().collect(), - None, - ))); - ``` - */ + * need a function sign_bytes() that just returns me the bytes to sign + * need a function that takes a transaction and some sign_bytes and forms a submittable tx from them + * this is roughly the API that we want + ```rust + let keypair = gen_keypair(); + + let mut wrapper = + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount_per_gas_unit: 0.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + keypair.ref_to(), + Epoch(0), + 0.into(), + None, + )))); + wrapper.header.chain_id = shell.chain_id.clone(); + wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); + wrapper.set_data(Data::new( + "Encrypted transaction data".as_bytes().to_owned(), + )); + wrapper.add_section(Section::Signature(Signature::new( + wrapper.sechashes(), + [(0, keypair)].into_iter().collect(), + None, + ))); + ``` +*/ -use namada_core::proto::{Tx, Signature}; +use borsh_ext::BorshSerializeExt; +use namada_core::ledger::governance::storage::proposal::ProposalType; +use namada_core::proto::Section; +use namada_core::proto::Signer; +use namada_core::proto::TxError; +use namada_core::proto::{Signature, Tx}; +use namada_core::types::address::Address; use namada_core::types::chain::ChainId; -use namada_core::types::key::{common, secp256k1}; -use namada_core::types::time::DateTimeUtc; +use namada_core::types::dec::Dec; use namada_core::types::hash::Hash; -use namada_core::types::transaction::GasLimit; +use namada_core::types::key::{common, secp256k1}; use namada_core::types::storage::Epoch; +use namada_core::types::time::DateTimeUtc; +use namada_core::types::token; +use namada_core::types::token::{Amount, DenominatedAmount, MaspDenom}; use namada_core::types::transaction::Fee; -use namada_core::proto::Signer; +use namada_core::types::transaction::GasLimit; use std::collections::BTreeMap; -use namada_core::proto::Section; use std::str::FromStr; -use namada_core::proto::TxError; -use namada_core::types::address::Address; -use namada_core::types::token; -use borsh_ext::BorshSerializeExt; -use namada_core::types::dec::Dec; -use namada_core::ledger::governance::storage::proposal::ProposalType; -use namada_core::types::token::{Amount, DenominatedAmount, MaspDenom}; -/// Initialize account transaction WASM -pub const TX_INIT_ACCOUNT_WASM: &str = "tx_init_account.wasm"; -/// Initialize validator transaction WASM path -pub const TX_INIT_VALIDATOR_WASM: &str = "tx_init_validator.wasm"; -/// Unjail validator transaction WASM path -pub const TX_UNJAIL_VALIDATOR_WASM: &str = "tx_unjail_validator.wasm"; -/// Deactivate validator transaction WASM path -pub const TX_DEACTIVATE_VALIDATOR_WASM: &str = "tx_deactivate_validator.wasm"; -/// Reactivate validator transaction WASM path -pub const TX_REACTIVATE_VALIDATOR_WASM: &str = "tx_reactivate_validator.wasm"; -/// Initialize proposal transaction WASM path -pub const TX_INIT_PROPOSAL_WASM: &str = "tx_init_proposal.wasm"; -/// Vote transaction WASM path -pub const TX_VOTE_PROPOSAL: &str = "tx_vote_proposal.wasm"; -/// Reveal public key transaction WASM path -pub const TX_REVEAL_PK_WASM: &str = "tx_reveal_pk.wasm"; -/// Update validity predicate WASM path -pub const TX_UPDATE_ACCOUNT_WASM: &str = "tx_update_account.wasm"; +pub mod tx_builders; + +//FIXME: check that we covered all the transactions + +//FIXME: move these to the proper files /// Transfer transaction WASM path pub const TX_TRANSFER_WASM: &str = "tx_transfer.wasm"; /// IBC transaction WASM path pub const TX_IBC_WASM: &str = "tx_ibc.wasm"; -/// User validity predicate WASM path -pub const VP_USER_WASM: &str = "vp_user.wasm"; -/// Validator validity predicate WASM path -pub const VP_VALIDATOR_WASM: &str = "vp_validator.wasm"; -/// Bond WASM path -pub const TX_BOND_WASM: &str = "tx_bond.wasm"; -/// Unbond WASM path -pub const TX_UNBOND_WASM: &str = "tx_unbond.wasm"; -/// Withdraw WASM path -pub const TX_WITHDRAW_WASM: &str = "tx_withdraw.wasm"; -/// Claim-rewards WASM path -pub const TX_CLAIM_REWARDS_WASM: &str = "tx_claim_rewards.wasm"; /// Bridge pool WASM path pub const TX_BRIDGE_POOL_WASM: &str = "tx_bridge_pool.wasm"; -/// Change commission WASM path -pub const TX_CHANGE_COMMISSION_WASM: &str = - "tx_change_validator_commission.wasm"; -/// Change consensus key WASM path -pub const TX_CHANGE_CONSENSUS_KEY_WASM: &str = "tx_change_consensus_key.wasm"; -/// Change validator metadata WASM path -pub const TX_CHANGE_METADATA_WASM: &str = "tx_change_validator_metadata.wasm"; -/// Resign steward WASM path -pub const TX_RESIGN_STEWARD: &str = "tx_resign_steward.wasm"; -/// Update steward commission WASM path -pub const TX_UPDATE_STEWARD_COMMISSION: &str = - "tx_update_steward_commission.wasm"; -/// Redelegate transaction WASM path -pub const TX_REDELEGATE_WASM: &str = "tx_redelegate.wasm"; -/// Target chain ID -pub const CHAIN_ID: &str = "localnet.3e837878d84b54a40f-0"; -/// Reveal public key transaction code hash -pub const TX_REVEAL_PK_HASH: &str = "924d926119e24a16d2eb50752acedd9ffc506f5131bbfc866cdc1c0c20d2de77"; - -#[derive(Debug)] -pub enum Error { ParseWasmHashErr, ParseChainIdErr } - -fn build_tx( - data: Vec, - timestamp: DateTimeUtc, - expiration: Option, - code_hash: &str, - code_tag: &str, - chain_id: &str, -) -> Result { - // Provide default values for chain ID and code hash - let chain_id = ChainId(chain_id.to_owned()); - let code_hash = Hash::from_str(code_hash).map_err(|_| Error::ParseWasmHashErr)?; - // Construct a raw transaction - let mut inner_tx = Tx::new(chain_id, expiration); - inner_tx.header.timestamp = timestamp; - inner_tx.add_code_from_hash(code_hash, Some(code_tag.to_owned())); - inner_tx.add_serialized_data(data); // takes the borsh encoded data - Ok(inner_tx) -} - -fn sign_bytes(tx: &Tx) -> Hash { - let mut tx = tx.clone(); - tx.protocol_filter(); - Signature { - targets: vec![tx.raw_header_hash()], - signer: Signer::PubKeys(vec![]), - signatures: BTreeMap::new(), - }.get_raw_hash() -} - -fn attach_inner_signatures( - tx: &Tx, - signer: Signer, - signatures: BTreeMap, -) -> Tx { - let mut tx = tx.clone(); - tx.add_section(Section::Signature(Signature { - targets: vec![tx.raw_header_hash()], - signer, - signatures, - })); - tx -} - -pub struct RevealPk(Tx); - -impl RevealPk { - /// Build a raw Reveal Public Key transaction from the given parameters - pub fn new( - public_key: common::PublicKey, - timestamp: DateTimeUtc, - expiration: Option, - code_hash: &str, - chain_id: &str, - ) -> Result { - build_tx(public_key.serialize_to_vec(), timestamp, expiration, code_hash, TX_REVEAL_PK_WASM, chain_id) - .map(Self) - } - - /// Takes any kind of inner tx and gives me back my sign bytes - pub fn sign_bytes(&self) -> Hash { - sign_bytes(&self.0) - } - - /// Attach the given inner signatures to the transaction - pub fn attach_signatures( - &self, - signer: Signer, - signatures: BTreeMap, - ) -> Self { - Self(attach_inner_signatures(&self.0, signer, signatures)) - } -} - -pub struct Bond(Tx); - -impl Bond { - /// Build a raw Bond transaction from the given parameters - pub fn new( - validator: Address, - amount: token::Amount, - source: Option
, - timestamp: DateTimeUtc, - expiration: Option, - code_hash: &str, - chain_id: &str, - ) -> Result { - let bond = namada_core::types::transaction::pos::Bond { - validator, - amount, - source, - }; - - build_tx(bond.serialize_to_vec(), timestamp, expiration, code_hash, TX_BOND_WASM, chain_id) - .map(Self) - } - - /// Takes any kind of inner tx and gives me back my sign bytes - pub fn sign_bytes(&self) -> Hash { - sign_bytes(&self.0) - } - - /// Attach the given inner signatures to the transaction - pub fn attach_signatures( - &self, - signer: Signer, - signatures: BTreeMap, - ) -> Self { - Self(attach_inner_signatures(&self.0, signer, signatures)) - } -} - -pub struct Unbond(Tx); - -impl Unbond { - /// Build a raw Bond transaction from the given parameters - pub fn new( - validator: Address, - amount: token::Amount, - source: Option
, - timestamp: DateTimeUtc, - expiration: Option, - code_hash: &str, - chain_id: &str, - ) -> Result { - let bond = namada_core::types::transaction::pos::Unbond { - validator, - amount, - source, - }; - - build_tx(bond.serialize_to_vec(), timestamp, expiration, code_hash, TX_UNBOND_WASM, chain_id) - .map(Self) - } - - /// Takes any kind of inner tx and gives me back my sign bytes - pub fn sign_bytes(&self) -> Hash { - sign_bytes(&self.0) - } - - /// Attach the given inner signatures to the transaction - pub fn attach_signatures( - &self, - signer: Signer, - signatures: BTreeMap, - ) -> Self { - Self(attach_inner_signatures(&self.0, signer, signatures)) - } -} - -pub struct InitAccount(Tx); - -impl InitAccount { - /// Build a raw Bond transaction from the given parameters - pub fn new( - public_keys: Vec, - vp_code_hash: Hash, - threshold: u8, - timestamp: DateTimeUtc, - expiration: Option, - code_hash: &str, - chain_id: &str, - ) -> Result { - let init_account = namada_core::types::transaction::account::InitAccount { - public_keys, - vp_code_hash, - threshold, - }; - - build_tx(init_account.serialize_to_vec(), timestamp, expiration, code_hash, TX_INIT_ACCOUNT_WASM, chain_id) - .map(Self) - } - - /// Takes any kind of inner tx and gives me back my sign bytes - pub fn sign_bytes(&self) -> Hash { - sign_bytes(&self.0) - } - - /// Attach the given inner signatures to the transaction - pub fn attach_signatures( - &self, - signer: Signer, - signatures: BTreeMap, - ) -> Self { - Self(attach_inner_signatures(&self.0, signer, signatures)) - } -} - -pub struct UpdateAccount(Tx); - -impl UpdateAccount { - /// Build a raw Bond transaction from the given parameters - pub fn new( - addr: Address, - vp_code_hash: Option, - public_keys: Vec, - threshold: Option, - timestamp: DateTimeUtc, - expiration: Option, - code_hash: &str, - chain_id: &str, - ) -> Result { - let update_account = namada_core::types::transaction::account::UpdateAccount { - addr, - vp_code_hash, - public_keys, - threshold, - }; - - build_tx(update_account.serialize_to_vec(), timestamp, expiration, code_hash, TX_UPDATE_ACCOUNT_WASM, chain_id) - .map(Self) - } - - /// Takes any kind of inner tx and gives me back my sign bytes - pub fn sign_bytes(&self) -> Hash { - sign_bytes(&self.0) - } - - /// Attach the given inner signatures to the transaction - pub fn attach_signatures( - &self, - signer: Signer, - signatures: BTreeMap, - ) -> Self { - Self(attach_inner_signatures(&self.0, signer, signatures)) - } -} - -pub struct InitValidator(Tx); - -impl InitValidator { - /// Build a raw Bond transaction from the given parameters - pub fn new( - account_keys: Vec, - threshold: u8, - consensus_key: common::PublicKey, - eth_cold_key: secp256k1::PublicKey, - eth_hot_key: secp256k1::PublicKey, - protocol_key: common::PublicKey, - commission_rate: Dec, - max_commission_rate_change: Dec, - email: String, - description: Option, - website: Option, - discord_handle: Option, - validator_vp_code_hash: Hash, - timestamp: DateTimeUtc, - expiration: Option, - code_hash: &str, - chain_id: &str, - ) -> Result { - let update_account = namada_core::types::transaction::pos::InitValidator { - account_keys, - threshold, - consensus_key, - eth_cold_key, - eth_hot_key, - protocol_key, - commission_rate, - max_commission_rate_change, - email, - description, - website, - discord_handle, - validator_vp_code_hash, - }; - - build_tx(update_account.serialize_to_vec(), timestamp, expiration, code_hash, TX_INIT_VALIDATOR_WASM, chain_id) - .map(Self) - } - - /// Takes any kind of inner tx and gives me back my sign bytes - pub fn sign_bytes(&self) -> Hash { - sign_bytes(&self.0) - } - - /// Attach the given inner signatures to the transaction - pub fn attach_signatures( - &self, - signer: Signer, - signatures: BTreeMap, - ) -> Self { - Self(attach_inner_signatures(&self.0, signer, signatures)) - } -} - -pub struct InitProposal(Tx); - -impl InitProposal { - /// Build a raw Bond transaction from the given parameters - pub fn new( - id: Option, - content: Hash, - author: Address, - r#type: ProposalType, - voting_start_epoch: Epoch, - voting_end_epoch: Epoch, - grace_epoch: Epoch, - timestamp: DateTimeUtc, - expiration: Option, - code_hash: &str, - chain_id: &str, - ) -> Result { - let init_proposal = namada_core::types::transaction::governance::InitProposalData { - id, - content, - author, - r#type, - voting_start_epoch, - voting_end_epoch, - grace_epoch, - }; - - build_tx(init_proposal.serialize_to_vec(), timestamp, expiration, code_hash, TX_INIT_PROPOSAL_WASM, chain_id) - .map(Self) - } - - /// Takes any kind of inner tx and gives me back my sign bytes - pub fn sign_bytes(&self) -> Hash { - sign_bytes(&self.0) - } - - /// Attach the given inner signatures to the transaction - pub fn attach_signatures( - &self, - signer: Signer, - signatures: BTreeMap, - ) -> Self { - Self(attach_inner_signatures(&self.0, signer, signatures)) - } -} pub struct Transfer(Tx); @@ -518,286 +139,15 @@ impl Transfer { shielded, }; - build_tx(init_proposal.serialize_to_vec(), timestamp, expiration, code_hash, TX_TRANSFER_WASM, chain_id) - .map(Self) - } - - /// Takes any kind of inner tx and gives me back my sign bytes - pub fn sign_bytes(&self) -> Hash { - sign_bytes(&self.0) - } - - /// Attach the given inner signatures to the transaction - pub fn attach_signatures( - &self, - signer: Signer, - signatures: BTreeMap, - ) -> Self { - Self(attach_inner_signatures(&self.0, signer, signatures)) - } -} - -pub struct Withdraw(Tx); - -impl Withdraw { - /// Build a raw Bond transaction from the given parameters - pub fn new( - validator: Address, - source: Option
, - timestamp: DateTimeUtc, - expiration: Option, - code_hash: &str, - chain_id: &str, - ) -> Result { - let init_proposal = namada_core::types::transaction::pos::Withdraw { - validator, - source, - }; - - build_tx(init_proposal.serialize_to_vec(), timestamp, expiration, code_hash, TX_WITHDRAW_WASM, chain_id) - .map(Self) - } - - /// Takes any kind of inner tx and gives me back my sign bytes - pub fn sign_bytes(&self) -> Hash { - sign_bytes(&self.0) - } - - /// Attach the given inner signatures to the transaction - pub fn attach_signatures( - &self, - signer: Signer, - signatures: BTreeMap, - ) -> Self { - Self(attach_inner_signatures(&self.0, signer, signatures)) - } -} - -pub struct ClaimRewards(Tx); - -impl ClaimRewards { - /// Build a raw Bond transaction from the given parameters - pub fn new( - validator: Address, - source: Option
, - timestamp: DateTimeUtc, - expiration: Option, - code_hash: &str, - chain_id: &str, - ) -> Result { - let init_proposal = namada_core::types::transaction::pos::Withdraw { - validator, - source, - }; - - build_tx(init_proposal.serialize_to_vec(), timestamp, expiration, code_hash, TX_CLAIM_REWARDS_WASM, chain_id) - .map(Self) - } - - /// Takes any kind of inner tx and gives me back my sign bytes - pub fn sign_bytes(&self) -> Hash { - sign_bytes(&self.0) - } - - /// Attach the given inner signatures to the transaction - pub fn attach_signatures( - &self, - signer: Signer, - signatures: BTreeMap, - ) -> Self { - Self(attach_inner_signatures(&self.0, signer, signatures)) - } -} - -pub struct ChangeCommission(Tx); - -impl ChangeCommission { - /// Build a raw Bond transaction from the given parameters - pub fn new( - validator: Address, - new_rate: Dec, - timestamp: DateTimeUtc, - expiration: Option, - code_hash: &str, - chain_id: &str, - ) -> Result { - let init_proposal = namada_core::types::transaction::pos::CommissionChange { - validator, - new_rate, - }; - - build_tx(init_proposal.serialize_to_vec(), timestamp, expiration, code_hash, TX_CHANGE_COMMISSION_WASM, chain_id) - .map(Self) - } - - /// Takes any kind of inner tx and gives me back my sign bytes - pub fn sign_bytes(&self) -> Hash { - sign_bytes(&self.0) - } - - /// Attach the given inner signatures to the transaction - pub fn attach_signatures( - &self, - signer: Signer, - signatures: BTreeMap, - ) -> Self { - Self(attach_inner_signatures(&self.0, signer, signatures)) - } -} - -pub struct ChangeMetaData(Tx); - -impl ChangeMetaData { - /// Build a raw Bond transaction from the given parameters - pub fn new( - validator: Address, - email: Option, - description: Option, - website: Option, - discord_handle: Option, - commission_rate: Option, - timestamp: DateTimeUtc, - expiration: Option, - code_hash: &str, - chain_id: &str, - ) -> Result { - let init_proposal = namada_core::types::transaction::pos::MetaDataChange { - validator, - email, - description, - website, - discord_handle, - commission_rate, - }; - - build_tx(init_proposal.serialize_to_vec(), timestamp, expiration, code_hash, TX_CHANGE_METADATA_WASM, chain_id) - .map(Self) - } - - /// Takes any kind of inner tx and gives me back my sign bytes - pub fn sign_bytes(&self) -> Hash { - sign_bytes(&self.0) - } - - /// Attach the given inner signatures to the transaction - pub fn attach_signatures( - &self, - signer: Signer, - signatures: BTreeMap, - ) -> Self { - Self(attach_inner_signatures(&self.0, signer, signatures)) - } -} - -pub struct ChangeConsensusKey(Tx); - -impl ChangeConsensusKey { - /// Build a raw Bond transaction from the given parameters - pub fn new( - validator: Address, - consensus_key: common::PublicKey, - timestamp: DateTimeUtc, - expiration: Option, - code_hash: &str, - chain_id: &str, - ) -> Result { - let init_proposal = namada_core::types::transaction::pos::ConsensusKeyChange { - validator, - consensus_key, - }; - - build_tx(init_proposal.serialize_to_vec(), timestamp, expiration, code_hash, TX_CHANGE_CONSENSUS_KEY_WASM, chain_id) - .map(Self) - } - - /// Takes any kind of inner tx and gives me back my sign bytes - pub fn sign_bytes(&self) -> Hash { - sign_bytes(&self.0) - } - - /// Attach the given inner signatures to the transaction - pub fn attach_signatures( - &self, - signer: Signer, - signatures: BTreeMap, - ) -> Self { - Self(attach_inner_signatures(&self.0, signer, signatures)) - } -} - -pub struct UnjailValidator(Tx); - -impl UnjailValidator { - /// Build a raw Bond transaction from the given parameters - pub fn new( - address: Address, - timestamp: DateTimeUtc, - expiration: Option, - code_hash: &str, - chain_id: &str, - ) -> Result { - build_tx(address.serialize_to_vec(), timestamp, expiration, code_hash, TX_UNJAIL_VALIDATOR_WASM, chain_id) - .map(Self) - } - - /// Takes any kind of inner tx and gives me back my sign bytes - pub fn sign_bytes(&self) -> Hash { - sign_bytes(&self.0) - } - - /// Attach the given inner signatures to the transaction - pub fn attach_signatures( - &self, - signer: Signer, - signatures: BTreeMap, - ) -> Self { - Self(attach_inner_signatures(&self.0, signer, signatures)) - } -} - -pub struct DeactivateValidator(Tx); - -impl DeactivateValidator { - /// Build a raw Bond transaction from the given parameters - pub fn new( - address: Address, - timestamp: DateTimeUtc, - expiration: Option, - code_hash: &str, - chain_id: &str, - ) -> Result { - build_tx(address.serialize_to_vec(), timestamp, expiration, code_hash, TX_DEACTIVATE_VALIDATOR_WASM, chain_id) - .map(Self) - } - - /// Takes any kind of inner tx and gives me back my sign bytes - pub fn sign_bytes(&self) -> Hash { - sign_bytes(&self.0) - } - - /// Attach the given inner signatures to the transaction - pub fn attach_signatures( - &self, - signer: Signer, - signatures: BTreeMap, - ) -> Self { - Self(attach_inner_signatures(&self.0, signer, signatures)) - } -} - -pub struct ReactivateValidator(Tx); - -impl ReactivateValidator { - /// Build a raw Bond transaction from the given parameters - pub fn new( - address: Address, - timestamp: DateTimeUtc, - expiration: Option, - code_hash: &str, - chain_id: &str, - ) -> Result { - build_tx(address.serialize_to_vec(), timestamp, expiration, code_hash, TX_REACTIVATE_VALIDATOR_WASM, chain_id) - .map(Self) + build_tx( + init_proposal.serialize_to_vec(), + timestamp, + expiration, + code_hash, + TX_TRANSFER_WASM, + chain_id, + ) + .map(Self) } /// Takes any kind of inner tx and gives me back my sign bytes @@ -841,7 +191,8 @@ impl Wrapper { targets: tx.sechashes(), signer: Signer::PubKeys(vec![]), signatures: BTreeMap::new(), - }.get_raw_hash() + } + .get_raw_hash() } /// Attach the given outer signatures to the transaction @@ -860,57 +211,9 @@ impl Wrapper { } /// Validate this wrapper transaction - pub fn validate_tx(&self) -> std::result::Result, TxError> { + pub fn validate_tx( + &self, + ) -> std::result::Result, TxError> { self.0.validate_tx() } } - -#[cfg(test)] -mod tests { - use super::*; - use namada_core::types::key::RefTo; - use namada_core::types::key::SigScheme; - use namada_core::types::token::Amount; - use namada_core::types::address::Address; - use std::str::FromStr; - - #[test] - fn it_works() { - // Setup the keys and addresses necessary for this test - let nam = Address::from_str("tnam1q8vyjrk5n30vfphaa26v7prkh8mjv4rfd5fkxh80") - .expect("unable to construct address"); - let sk = common::SecretKey::from_str("0083318ccceac6c08a0177667840b4b93f0e455e45d4c38c28b73b8f8462fbf548") - .expect("unable to construct secret key"); - let pk = sk.ref_to(); - let now = DateTimeUtc::now(); - // Make the raw reveal PK transaction - let reveal_pk = RevealPk::new(pk.clone(), now, None, TX_REVEAL_PK_HASH, CHAIN_ID) - .unwrap(); - // Sign the raw reveal PK transaction - let inner_hash = reveal_pk.sign_bytes(); - let sig = common::SigScheme::sign(&sk, inner_hash); - let signatures = [(0, sig)].into_iter().collect(); - // Attach the inner signature to the transaction - let reveal_pk = reveal_pk.attach_signatures(Signer::PubKeys(vec![pk.clone()]), signatures); - let fee = Fee { - amount_per_gas_unit: Amount::from(10), - token: nam, - }; - // Wrap the inner transaction - let wrapper_tx = Wrapper::new( - &reveal_pk.0, - fee, - pk.clone(), - Epoch::from(10), - GasLimit::from(20_000), - None, - ); - // Sign the wrapper transaction - let outer_hash = wrapper_tx.sign_bytes(); - let sig = common::SigScheme::sign(&sk, outer_hash); - // Attach the wrapper signature to the transaction - let wrapper_tx = wrapper_tx.attach_signatures(pk.clone(), sig); - // Validate the outcome - wrapper_tx.0.validate_tx().expect("failed to validate transaction"); - } -} diff --git a/light_sdk/src/main.rs b/light_sdk/src/main.rs deleted file mode 100644 index f79c691f08..0000000000 --- a/light_sdk/src/main.rs +++ /dev/null @@ -1,2 +0,0 @@ -fn main() { -} diff --git a/light_sdk/src/tx_builders/account.rs b/light_sdk/src/tx_builders/account.rs new file mode 100644 index 0000000000..a32151e8ca --- /dev/null +++ b/light_sdk/src/tx_builders/account.rs @@ -0,0 +1,139 @@ +use crate::tx_builders; +use borsh_ext::BorshSerializeExt; +use namada_core::ledger::governance::storage::proposal::ProposalType; +use namada_core::proto::Section; +use namada_core::proto::SignatureIndex; +use namada_core::proto::Signer; +use namada_core::proto::TxError; +use namada_core::proto::{Signature, Tx}; +use namada_core::types::address::Address; +use namada_core::types::chain::ChainId; +use namada_core::types::dec::Dec; +use namada_core::types::hash::Hash; +use namada_core::types::key::{common, secp256k1}; +use namada_core::types::storage::Epoch; +use namada_core::types::time::DateTimeUtc; +use namada_core::types::token; +use namada_core::types::token::{Amount, DenominatedAmount, MaspDenom}; +use namada_core::types::transaction::Fee; +use namada_core::types::transaction::GasLimit; +use std::collections::BTreeMap; +use std::str::FromStr; + +use super::GlobalArgs; + +//FIXME: docs for all the structs + +const TX_INIT_ACCOUNT_WASM: &str = "tx_init_account.wasm"; +const TX_REVEAL_PK_WASM: &str = "tx_reveal_pk.wasm"; +const TX_UPDATE_ACCOUNT_WASM: &str = "tx_update_account.wasm"; + +pub struct InitAccount(Tx); + +impl InitAccount { + /// Build a raw InitAccount transaction from the given parameters + pub fn new( + public_keys: Vec, + vp_code_hash: Hash, + threshold: u8, + args: GlobalArgs, + ) -> Self { + let init_account = + namada_core::types::transaction::account::InitAccount { + public_keys, + vp_code_hash, + threshold, + }; + + Self(tx_builders::build_tx( + args, + init_account, + TX_INIT_ACCOUNT_WASM.to_string(), + )) + } + + /// Get the bytes to sign for the given transaction + pub fn get_msg_to_sign(mut self) -> (Self, Vec) { + let (tx, msg) = tx_builders::get_msg_to_sign(self.0); + + (Self(tx), msg) + } + + /// Attach the provided signatures to the tx + pub fn attach_signatures( + mut self, + signatures: Vec, + ) -> Self { + Self(tx_builders::attach_raw_signatures(self.0, signatures)) + } +} + +pub struct RevealPk(Tx); + +impl RevealPk { + /// Build a raw Reveal Public Key transaction from the given parameters + pub fn new(public_key: common::PublicKey, args: GlobalArgs) -> Self { + Self(tx_builders::build_tx( + args, + public_key, + TX_REVEAL_PK_WASM.to_string(), + )) + } + + /// Get the bytes to sign for the given transaction + pub fn get_msg_to_sign(mut self) -> (Self, Vec) { + let (tx, msg) = tx_builders::get_msg_to_sign(self.0); + + (Self(tx), msg) + } + + /// Attach the provided signatures to the tx + pub fn attach_signatures( + mut self, + signatures: Vec, + ) -> Self { + Self(tx_builders::attach_raw_signatures(self.0, signatures)) + } +} + +pub struct UpdateAccount(Tx); + +impl UpdateAccount { + /// Build a raw UpdateAccount transaction from the given parameters + pub fn new( + addr: Address, + vp_code_hash: Option, + public_keys: Vec, + threshold: Option, + args: GlobalArgs, + ) -> Self { + let update_account = + namada_core::types::transaction::account::UpdateAccount { + addr, + vp_code_hash, + public_keys, + threshold, + }; + + Self(tx_builders::build_tx( + args, + update_account, + TX_UPDATE_ACCOUNT_WASM.to_string(), + )) + } + + /// Get the bytes to sign for the given transaction + pub fn get_msg_to_sign(mut self) -> (Self, Vec) { + let (tx, msg) = tx_builders::get_msg_to_sign(self.0); + + (Self(tx), msg) + } + + /// Attach the provided signatures to the tx + pub fn attach_signatures( + mut self, + signatures: Vec, + ) -> Self { + Self(tx_builders::attach_raw_signatures(self.0, signatures)) + } +} diff --git a/light_sdk/src/tx_builders/governance.rs b/light_sdk/src/tx_builders/governance.rs new file mode 100644 index 0000000000..8bcfd18ea2 --- /dev/null +++ b/light_sdk/src/tx_builders/governance.rs @@ -0,0 +1,117 @@ +use crate::tx_builders; +use borsh_ext::BorshSerializeExt; +use namada_core::ledger::governance::storage::proposal::ProposalType; +use namada_core::ledger::governance::storage::vote::StorageProposalVote; +use namada_core::proto::Section; +use namada_core::proto::SignatureIndex; +use namada_core::proto::Signer; +use namada_core::proto::TxError; +use namada_core::proto::{Signature, Tx}; +use namada_core::types::address::Address; +use namada_core::types::chain::ChainId; +use namada_core::types::dec::Dec; +use namada_core::types::hash::Hash; +use namada_core::types::key::{common, secp256k1}; +use namada_core::types::storage::Epoch; +use namada_core::types::time::DateTimeUtc; +use namada_core::types::token; +use namada_core::types::token::{Amount, DenominatedAmount, MaspDenom}; +use namada_core::types::transaction::Fee; +use namada_core::types::transaction::GasLimit; +use std::collections::BTreeMap; +use std::str::FromStr; + +use super::GlobalArgs; + +const TX_INIT_PROPOSAL_WASM: &str = "tx_init_proposal.wasm"; +const TX_VOTE_PROPOSAL: &str = "tx_vote_proposal.wasm"; + +pub struct InitProposal(Tx); + +impl InitProposal { + /// Build a raw InitProposal transaction from the given parameters + pub fn new( + id: Option, + content: Hash, + author: Address, + r#type: ProposalType, + voting_start_epoch: Epoch, + voting_end_epoch: Epoch, + grace_epoch: Epoch, + args: GlobalArgs, + ) -> Self { + let init_proposal = + namada_core::types::transaction::governance::InitProposalData { + id, + content, + author, + r#type, + voting_start_epoch, + voting_end_epoch, + grace_epoch, + }; + + Self(tx_builders::build_tx( + args, + init_proposal, + TX_INIT_PROPOSAL_WASM.to_string(), + )) + } + + /// Get the bytes to sign for the given transaction + pub fn get_msg_to_sign(mut self) -> (Self, Vec) { + let (tx, msg) = tx_builders::get_msg_to_sign(self.0); + + (Self(tx), msg) + } + + /// Attach the provided signatures to the tx + pub fn attach_signatures( + mut self, + signatures: Vec, + ) -> Self { + Self(tx_builders::attach_raw_signatures(self.0, signatures)) + } +} + +pub struct VoteProposal(Tx); + +impl VoteProposal { + /// Build a raw VoteProposal transaction from the given parameters + pub fn new( + id: u64, + vote: StorageProposalVote, + voter: Address, + delegations: Vec
, + args: GlobalArgs, + ) -> Self { + let vote_proposal = + namada_core::types::transaction::governance::VoteProposalData { + id, + vote, + voter, + delegations, + }; + + Self(tx_builders::build_tx( + args, + vote_proposal, + TX_VOTE_PROPOSAL.to_string(), + )) + } + + /// Get the bytes to sign for the given transaction + pub fn get_msg_to_sign(mut self) -> (Self, Vec) { + let (tx, msg) = tx_builders::get_msg_to_sign(self.0); + + (Self(tx), msg) + } + + /// Attach the provided signatures to the tx + pub fn attach_signatures( + mut self, + signatures: Vec, + ) -> Self { + Self(tx_builders::attach_raw_signatures(self.0, signatures)) + } +} diff --git a/light_sdk/src/tx_builders/mod.rs b/light_sdk/src/tx_builders/mod.rs new file mode 100644 index 0000000000..7feffa12a5 --- /dev/null +++ b/light_sdk/src/tx_builders/mod.rs @@ -0,0 +1,69 @@ +use borsh::BorshSerialize; +use borsh_ext::BorshSerializeExt; +use namada_core::ledger::governance::storage::proposal::ProposalType; +use namada_core::proto::Section; +use namada_core::proto::SignatureIndex; +use namada_core::proto::Signer; +use namada_core::proto::TxError; +use namada_core::proto::{Signature, Tx}; +use namada_core::types::account::AccountPublicKeysMap; +use namada_core::types::address::Address; +use namada_core::types::chain::ChainId; +use namada_core::types::dec::Dec; +use namada_core::types::hash::Hash; +use namada_core::types::key::{common, secp256k1}; +use namada_core::types::storage::Epoch; +use namada_core::types::time::DateTimeUtc; +use namada_core::types::token; +use namada_core::types::token::{Amount, DenominatedAmount, MaspDenom}; +use namada_core::types::transaction::Fee; +use namada_core::types::transaction::GasLimit; +use std::collections::BTreeMap; +use std::str::FromStr; + +pub mod account; +pub mod governance; +pub mod pgf; +pub mod pos; + +/// Generic arguments required to construct a transaction +pub struct GlobalArgs { + timestamp: DateTimeUtc, + expiration: Option, + code_hash: Hash, + chain_id: ChainId, +} + +pub(in crate::tx_builders) fn build_tx( + GlobalArgs { + timestamp, + expiration, + code_hash, + chain_id, + }: GlobalArgs, + data: impl BorshSerialize, + code_tag: String, +) -> Tx { + let mut inner_tx = Tx::new(chain_id, expiration); + inner_tx.header.timestamp = timestamp; + inner_tx.add_code_from_hash(code_hash, Some(code_tag)); + inner_tx.add_data(data); + + inner_tx +} + +//FIXME: just take reference? +pub(in crate::tx_builders) fn get_msg_to_sign(tx: Tx) -> (Tx, Vec) { + let msg = tx.raw_header_hash().serialize_to_vec(); + + (tx, msg) +} + +pub(in crate::tx_builders) fn attach_raw_signatures( + mut tx: Tx, + //FIXME: accept bytes here? + signatures: Vec, +) -> Tx { + tx.add_signatures(signatures); + tx +} diff --git a/light_sdk/src/tx_builders/pgf.rs b/light_sdk/src/tx_builders/pgf.rs new file mode 100644 index 0000000000..6bc4ebe551 --- /dev/null +++ b/light_sdk/src/tx_builders/pgf.rs @@ -0,0 +1,93 @@ +use crate::tx_builders; +use borsh_ext::BorshSerializeExt; +use namada_core::ledger::governance::storage::proposal::ProposalType; +use namada_core::proto::Section; +use namada_core::proto::SignatureIndex; +use namada_core::proto::Signer; +use namada_core::proto::TxError; +use namada_core::proto::{Signature, Tx}; +use namada_core::types::address::Address; +use namada_core::types::chain::ChainId; +use namada_core::types::dec::Dec; +use namada_core::types::hash::Hash; +use namada_core::types::key::{common, secp256k1}; +use namada_core::types::storage::Epoch; +use namada_core::types::time::DateTimeUtc; +use namada_core::types::token; +use namada_core::types::token::{Amount, DenominatedAmount, MaspDenom}; +use namada_core::types::transaction::Fee; +use namada_core::types::transaction::GasLimit; +use std::collections::BTreeMap; +use std::collections::HashMap; +use std::str::FromStr; + +use super::GlobalArgs; + +const TX_RESIGN_STEWARD: &str = "tx_resign_steward.wasm"; +const TX_UPDATE_STEWARD_COMMISSION: &str = "tx_update_steward_commission.wasm"; + +pub struct ResignSteward(Tx); + +impl ResignSteward { + /// Build a raw ResignSteward transaction from the given parameters + pub fn new(steward: Address, args: GlobalArgs) -> Self { + Self(tx_builders::build_tx( + args, + steward, + TX_RESIGN_STEWARD.to_string(), + )) + } + + /// Get the bytes to sign for the given transaction + pub fn get_msg_to_sign(mut self) -> (Self, Vec) { + let (tx, msg) = tx_builders::get_msg_to_sign(self.0); + + (Self(tx), msg) + } + + /// Attach the provided signatures to the tx + pub fn attach_signatures( + mut self, + signatures: Vec, + ) -> Self { + Self(tx_builders::attach_raw_signatures(self.0, signatures)) + } +} + +pub struct UpdateStewardCommission(Tx); + +impl UpdateStewardCommission { + /// Build a raw UpdateStewardCommission transaction from the given parameters + pub fn new( + steward: Address, + commission: HashMap, + args: GlobalArgs, + ) -> Self { + let update_commission = + namada_core::types::transaction::pgf::UpdateStewardCommission { + steward, + commission, + }; + + Self(tx_builders::build_tx( + args, + update_commission, + TX_UPDATE_STEWARD_COMMISSION.to_string(), + )) + } + + /// Get the bytes to sign for the given transaction + pub fn get_msg_to_sign(mut self) -> (Self, Vec) { + let (tx, msg) = tx_builders::get_msg_to_sign(self.0); + + (Self(tx), msg) + } + + /// Attach the provided signatures to the tx + pub fn attach_signatures( + mut self, + signatures: Vec, + ) -> Self { + Self(tx_builders::attach_raw_signatures(self.0, signatures)) + } +} diff --git a/light_sdk/src/tx_builders/pos.rs b/light_sdk/src/tx_builders/pos.rs new file mode 100644 index 0000000000..50219330e7 --- /dev/null +++ b/light_sdk/src/tx_builders/pos.rs @@ -0,0 +1,454 @@ +use crate::tx_builders; +use namada_core::ledger::governance::storage::proposal::ProposalType; +use namada_core::proto::Section; +use namada_core::proto::SignatureIndex; +use namada_core::proto::Signer; +use namada_core::proto::TxError; +use namada_core::proto::{Signature, Tx}; +use namada_core::types::account::AccountPublicKeysMap; +use namada_core::types::address::Address; +use namada_core::types::chain::ChainId; +use namada_core::types::dec::Dec; +use namada_core::types::hash::Hash; +use namada_core::types::key::{common, secp256k1}; +use namada_core::types::storage::Epoch; +use namada_core::types::time::DateTimeUtc; +use namada_core::types::token; +use namada_core::types::token::{Amount, DenominatedAmount, MaspDenom}; +use namada_core::types::transaction::Fee; +use namada_core::types::transaction::GasLimit; +use std::collections::BTreeMap; +use std::str::FromStr; + +use super::GlobalArgs; + +const TX_BOND_WASM: &str = "tx_bond.wasm"; +const TX_UNBOND_WASM: &str = "tx_unbond.wasm"; +const TX_INIT_VALIDATOR_WASM: &str = "tx_init_validator.wasm"; +const TX_UNJAIL_VALIDATOR_WASM: &str = "tx_unjail_validator.wasm"; +const TX_DEACTIVATE_VALIDATOR_WASM: &str = "tx_deactivate_validator.wasm"; +const TX_REACTIVATE_VALIDATOR_WASM: &str = "tx_reactivate_validator.wasm"; +const TX_CLAIM_REWARDS_WASM: &str = "tx_claim_rewards.wasm"; +const TX_REDELEGATE_WASM: &str = "tx_redelegate.wasm"; +const TX_CHANGE_METADATA_WASM: &str = "tx_change_validator_metadata.wasm"; +const TX_CHANGE_CONSENSUS_KEY_WASM: &str = "tx_change_consensus_key.wasm"; +const TX_CHANGE_COMMISSION_WASM: &str = "tx_change_validator_commission.wasm"; +const TX_WITHDRAW_WASM: &str = "tx_withdraw.wasm"; + +/// A bond transaction +pub struct Bond(Tx); + +impl Bond { + /// Build a raw Bond transaction from the given parameters + pub fn new( + validator: Address, + amount: token::Amount, + source: Option
, + args: GlobalArgs, + ) -> Self { + let unbond = namada_core::types::transaction::pos::Bond { + validator, + amount, + source, + }; + + Self(tx_builders::build_tx( + args, + unbond, + TX_BOND_WASM.to_string(), + )) + } + + //FIXME: share + /// Get the bytes to sign for the given transaction + pub fn get_msg_to_sign(mut self) -> (Self, Vec) { + let (tx, msg) = tx_builders::get_msg_to_sign(self.0); + + (Self(tx), msg) + } + + //FIXME: share + /// Attach the provided signatures to the tx + pub fn attach_signatures( + mut self, + signatures: Vec, + ) -> Self { + Self(tx_builders::attach_raw_signatures(self.0, signatures)) + } +} + +/// An unbond transaction +pub struct Unbond(Tx); + +impl Unbond { + /// Build a raw Unbond transaction from the given parameters + pub fn new( + validator: Address, + amount: token::Amount, + source: Option
, + args: GlobalArgs, + ) -> Self { + let unbond = namada_core::types::transaction::pos::Unbond { + validator, + amount, + source, + }; + + Self(tx_builders::build_tx( + args, + unbond, + TX_UNBOND_WASM.to_string(), + )) + } + + /// Get the bytes to sign for the given transaction + pub fn get_msg_to_sign(mut self) -> (Self, Vec) { + let (tx, msg) = tx_builders::get_msg_to_sign(self.0); + + (Self(tx), msg) + } + + /// Attach the provided signatures to the tx + pub fn attach_signatures( + mut self, + signatures: Vec, + ) -> Self { + Self(tx_builders::attach_raw_signatures(self.0, signatures)) + } +} + +pub struct InitValidator(Tx); + +impl InitValidator { + /// Build a raw Init validator transaction from the given parameters + pub fn new( + account_keys: Vec, + threshold: u8, + consensus_key: common::PublicKey, + eth_cold_key: secp256k1::PublicKey, + eth_hot_key: secp256k1::PublicKey, + protocol_key: common::PublicKey, + commission_rate: Dec, + max_commission_rate_change: Dec, + email: String, + description: Option, + website: Option, + discord_handle: Option, + validator_vp_code_hash: Hash, + args: GlobalArgs, + ) -> Self { + let update_account = + namada_core::types::transaction::pos::InitValidator { + account_keys, + threshold, + consensus_key, + eth_cold_key, + eth_hot_key, + protocol_key, + commission_rate, + max_commission_rate_change, + email, + description, + website, + discord_handle, + validator_vp_code_hash, + }; + + Self(tx_builders::build_tx( + args, + update_account, + TX_INIT_VALIDATOR_WASM.to_string(), + )) + } + + /// Get the bytes to sign for the given transaction + pub fn get_msg_to_sign(mut self) -> (Self, Vec) { + let (tx, msg) = tx_builders::get_msg_to_sign(self.0); + + (Self(tx), msg) + } + + /// Attach the provided signatures to the tx + pub fn attach_signatures( + mut self, + signatures: Vec, + ) -> Self { + Self(tx_builders::attach_raw_signatures(self.0, signatures)) + } +} + +pub struct UnjailValidator(Tx); + +impl UnjailValidator { + /// Build a raw Unjail validator transaction from the given parameters + pub fn new(address: Address, args: GlobalArgs) -> Self { + Self(tx_builders::build_tx( + args, + address, + TX_UNJAIL_VALIDATOR_WASM.to_string(), + )) + } + + /// Get the bytes to sign for the given transaction + pub fn get_msg_to_sign(mut self) -> (Self, Vec) { + let (tx, msg) = tx_builders::get_msg_to_sign(self.0); + + (Self(tx), msg) + } + + /// Attach the provided signatures to the tx + pub fn attach_signatures( + mut self, + signatures: Vec, + ) -> Self { + Self(tx_builders::attach_raw_signatures(self.0, signatures)) + } +} + +pub struct DeactivateValidator(Tx); + +impl DeactivateValidator { + /// Build a raw DeactivateValidator transaction from the given parameters + pub fn new(address: Address, args: GlobalArgs) -> Self { + Self(tx_builders::build_tx( + args, + address, + TX_DEACTIVATE_VALIDATOR_WASM.to_string(), + )) + } + + /// Get the bytes to sign for the given transaction + pub fn get_msg_to_sign(mut self) -> (Self, Vec) { + let (tx, msg) = tx_builders::get_msg_to_sign(self.0); + + (Self(tx), msg) + } + + /// Attach the provided signatures to the tx + pub fn attach_signatures( + mut self, + signatures: Vec, + ) -> Self { + Self(tx_builders::attach_raw_signatures(self.0, signatures)) + } +} + +pub struct ReactivateValidator(Tx); + +impl ReactivateValidator { + /// Build a raw ReactivateValidator transaction from the given parameters + pub fn new(address: Address, args: GlobalArgs) -> Self { + Self(tx_builders::build_tx( + args, + address, + TX_REACTIVATE_VALIDATOR_WASM.to_string(), + )) + } + + /// Get the bytes to sign for the given transaction + pub fn get_msg_to_sign(mut self) -> (Self, Vec) { + let (tx, msg) = tx_builders::get_msg_to_sign(self.0); + + (Self(tx), msg) + } + + /// Attach the provided signatures to the tx + pub fn attach_signatures( + mut self, + signatures: Vec, + ) -> Self { + Self(tx_builders::attach_raw_signatures(self.0, signatures)) + } +} + +pub struct ClaimRewards(Tx); + +impl ClaimRewards { + /// Build a raw ClaimRewards transaction from the given parameters + pub fn new( + validator: Address, + source: Option
, + args: GlobalArgs, + ) -> Self { + let init_proposal = namada_core::types::transaction::pos::Withdraw { + validator, + source, + }; + + Self(tx_builders::build_tx( + args, + init_proposal, + TX_CLAIM_REWARDS_WASM.to_string(), + )) + } + + /// Get the bytes to sign for the given transaction + pub fn get_msg_to_sign(mut self) -> (Self, Vec) { + let (tx, msg) = tx_builders::get_msg_to_sign(self.0); + + (Self(tx), msg) + } + + /// Attach the provided signatures to the tx + pub fn attach_signatures( + mut self, + signatures: Vec, + ) -> Self { + Self(tx_builders::attach_raw_signatures(self.0, signatures)) + } +} + +pub struct ChangeMetaData(Tx); + +impl ChangeMetaData { + /// Build a raw ChangeMetadata transaction from the given parameters + pub fn new( + validator: Address, + email: Option, + description: Option, + website: Option, + discord_handle: Option, + commission_rate: Option, + args: GlobalArgs, + ) -> Self { + let init_proposal = + namada_core::types::transaction::pos::MetaDataChange { + validator, + email, + description, + website, + discord_handle, + commission_rate, + }; + + Self(tx_builders::build_tx( + args, + init_proposal, + TX_CHANGE_METADATA_WASM.to_string(), + )) + } + + /// Get the bytes to sign for the given transaction + pub fn get_msg_to_sign(mut self) -> (Self, Vec) { + let (tx, msg) = tx_builders::get_msg_to_sign(self.0); + + (Self(tx), msg) + } + + /// Attach the provided signatures to the tx + pub fn attach_signatures( + mut self, + signatures: Vec, + ) -> Self { + Self(tx_builders::attach_raw_signatures(self.0, signatures)) + } +} + +pub struct ChangeConsensusKey(Tx); + +impl ChangeConsensusKey { + /// Build a raw ChangeConsensusKey transaction from the given parameters + pub fn new( + validator: Address, + consensus_key: common::PublicKey, + args: GlobalArgs, + ) -> Self { + let init_proposal = + namada_core::types::transaction::pos::ConsensusKeyChange { + validator, + consensus_key, + }; + + Self(tx_builders::build_tx( + args, + init_proposal, + TX_CHANGE_CONSENSUS_KEY_WASM.to_string(), + )) + } + + /// Get the bytes to sign for the given transaction + pub fn get_msg_to_sign(mut self) -> (Self, Vec) { + let (tx, msg) = tx_builders::get_msg_to_sign(self.0); + + (Self(tx), msg) + } + + /// Attach the provided signatures to the tx + pub fn attach_signatures( + mut self, + signatures: Vec, + ) -> Self { + Self(tx_builders::attach_raw_signatures(self.0, signatures)) + } +} + +pub struct ChangeCommission(Tx); + +impl ChangeCommission { + /// Build a raw ChangeCommission transaction from the given parameters + pub fn new(validator: Address, new_rate: Dec, args: GlobalArgs) -> Self { + let init_proposal = + namada_core::types::transaction::pos::CommissionChange { + validator, + new_rate, + }; + + Self(tx_builders::build_tx( + args, + init_proposal, + TX_CHANGE_COMMISSION_WASM.to_string(), + )) + } + + /// Get the bytes to sign for the given transaction + pub fn get_msg_to_sign(mut self) -> (Self, Vec) { + let (tx, msg) = tx_builders::get_msg_to_sign(self.0); + + (Self(tx), msg) + } + + /// Attach the provided signatures to the tx + pub fn attach_signatures( + mut self, + signatures: Vec, + ) -> Self { + Self(tx_builders::attach_raw_signatures(self.0, signatures)) + } +} + +pub struct Withdraw(Tx); + +impl Withdraw { + /// Build a raw Withdraw transaction from the given parameters + pub fn new( + validator: Address, + source: Option
, + args: GlobalArgs, + ) -> Self { + let init_proposal = namada_core::types::transaction::pos::Withdraw { + validator, + source, + }; + + Self(tx_builders::build_tx( + args, + init_proposal, + TX_WITHDRAW_WASM.to_string(), + )) + } + + /// Get the bytes to sign for the given transaction + pub fn get_msg_to_sign(mut self) -> (Self, Vec) { + let (tx, msg) = tx_builders::get_msg_to_sign(self.0); + + (Self(tx), msg) + } + + /// Attach the provided signatures to the tx + pub fn attach_signatures( + mut self, + signatures: Vec, + ) -> Self { + Self(tx_builders::attach_raw_signatures(self.0, signatures)) + } +} From 4e8861a5d4a520da0fb15d64fc33f172d5db3926 Mon Sep 17 00:00:00 2001 From: Adrian Brink Date: Mon, 11 Dec 2023 18:17:38 +0100 Subject: [PATCH 183/216] First draft of all transactions in light sdk --- Cargo.lock | 2 + light_sdk/Cargo.toml | 4 +- light_sdk/src/lib.rs | 152 ++--------------- light_sdk/src/reading/mod.rs | 8 + .../{tx_builders => transaction}/account.rs | 34 ++-- light_sdk/src/transaction/bridge.rs | 64 ++++++++ .../governance.rs | 22 ++- light_sdk/src/transaction/ibc.rs | 65 ++++++++ .../src/{tx_builders => transaction}/mod.rs | 19 ++- .../src/{tx_builders => transaction}/pgf.rs | 22 ++- .../src/{tx_builders => transaction}/pos.rs | 153 ++++++++++-------- light_sdk/src/transaction/transfer.rs | 69 ++++++++ light_sdk/src/transaction/wrapper.rs | 75 +++++++++ light_sdk/src/writing/mod.rs | 7 + 14 files changed, 437 insertions(+), 259 deletions(-) create mode 100644 light_sdk/src/reading/mod.rs rename light_sdk/src/{tx_builders => transaction}/account.rs (80%) create mode 100644 light_sdk/src/transaction/bridge.rs rename light_sdk/src/{tx_builders => transaction}/governance.rs (84%) create mode 100644 light_sdk/src/transaction/ibc.rs rename light_sdk/src/{tx_builders => transaction}/mod.rs (80%) rename light_sdk/src/{tx_builders => transaction}/pgf.rs (81%) rename light_sdk/src/{tx_builders => transaction}/pos.rs (75%) create mode 100644 light_sdk/src/transaction/transfer.rs create mode 100644 light_sdk/src/transaction/wrapper.rs create mode 100644 light_sdk/src/writing/mod.rs diff --git a/Cargo.lock b/Cargo.lock index a956dc79d9..386b3b1266 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3826,7 +3826,9 @@ version = "0.27.0" dependencies = [ "borsh", "borsh-ext", + "ibc", "namada_core", + "prost 0.12.1", ] [[package]] diff --git a/light_sdk/Cargo.toml b/light_sdk/Cargo.toml index 752b0e3e4a..d5460bcfee 100644 --- a/light_sdk/Cargo.toml +++ b/light_sdk/Cargo.toml @@ -16,4 +16,6 @@ version.workspace = true [dependencies] borsh.workspace = true borsh-ext.workspace = true -namada_core = {path = "../core", default-features = false} +namada_core = {path = "../core"} +prost.workspace = true +ibc = "0.47.0" diff --git a/light_sdk/src/lib.rs b/light_sdk/src/lib.rs index f66f7ed730..3084cc122e 100644 --- a/light_sdk/src/lib.rs +++ b/light_sdk/src/lib.rs @@ -1,3 +1,17 @@ +/* + * tx status + * block height + * account balance + * anything else if needed + */ +// pub mod reading; + +// either sync or async tx submission - prints tx hashes that can be used for tracking +// pub mod writing; +// pub mod wallet; + +// ==== + /* SDK for building transactions without having to pass in an entire Namada chain @@ -82,138 +96,6 @@ ``` */ -use borsh_ext::BorshSerializeExt; -use namada_core::ledger::governance::storage::proposal::ProposalType; -use namada_core::proto::Section; -use namada_core::proto::Signer; -use namada_core::proto::TxError; -use namada_core::proto::{Signature, Tx}; -use namada_core::types::address::Address; -use namada_core::types::chain::ChainId; -use namada_core::types::dec::Dec; -use namada_core::types::hash::Hash; -use namada_core::types::key::{common, secp256k1}; -use namada_core::types::storage::Epoch; -use namada_core::types::time::DateTimeUtc; -use namada_core::types::token; -use namada_core::types::token::{Amount, DenominatedAmount, MaspDenom}; -use namada_core::types::transaction::Fee; -use namada_core::types::transaction::GasLimit; -use std::collections::BTreeMap; -use std::str::FromStr; - -pub mod tx_builders; - -//FIXME: check that we covered all the transactions - -//FIXME: move these to the proper files -/// Transfer transaction WASM path -pub const TX_TRANSFER_WASM: &str = "tx_transfer.wasm"; -/// IBC transaction WASM path -pub const TX_IBC_WASM: &str = "tx_ibc.wasm"; -/// Bridge pool WASM path -pub const TX_BRIDGE_POOL_WASM: &str = "tx_bridge_pool.wasm"; - -pub struct Transfer(Tx); - -impl Transfer { - /// Build a raw Bond transaction from the given parameters - pub fn new( - source: Address, - target: Address, - token: Address, - amount: DenominatedAmount, - key: Option, - shielded: Option, - timestamp: DateTimeUtc, - expiration: Option, - code_hash: &str, - chain_id: &str, - ) -> Result { - let init_proposal = namada_core::types::token::Transfer { - source, - target, - token, - amount, - key, - shielded, - }; - - build_tx( - init_proposal.serialize_to_vec(), - timestamp, - expiration, - code_hash, - TX_TRANSFER_WASM, - chain_id, - ) - .map(Self) - } - - /// Takes any kind of inner tx and gives me back my sign bytes - pub fn sign_bytes(&self) -> Hash { - sign_bytes(&self.0) - } - - /// Attach the given inner signatures to the transaction - pub fn attach_signatures( - &self, - signer: Signer, - signatures: BTreeMap, - ) -> Self { - Self(attach_inner_signatures(&self.0, signer, signatures)) - } -} - -pub struct Wrapper(Tx); - -impl Wrapper { - /// Takes a transaction and some signatures and wraps them in a wrapper - /// transaction - pub fn new( - tx: &Tx, - fee: Fee, - fee_payer: common::PublicKey, - epoch: Epoch, - gas_limit: GasLimit, - unshield_hash: Option, - ) -> Self { - let mut tx = tx.clone(); - tx.add_wrapper(fee, fee_payer, epoch, gas_limit, unshield_hash); - Self(tx) - } - - /// Takes any kind of outer tx and gives me back my sign bytes - pub fn sign_bytes(&self) -> Hash { - let mut tx = self.0.clone(); - tx.protocol_filter(); - Signature { - targets: tx.sechashes(), - signer: Signer::PubKeys(vec![]), - signatures: BTreeMap::new(), - } - .get_raw_hash() - } - - /// Attach the given outer signatures to the transaction - pub fn attach_signatures( - &self, - signer: common::PublicKey, - signature: common::Signature, - ) -> Self { - let mut tx = self.0.clone(); - tx.add_section(Section::Signature(Signature { - targets: tx.sechashes(), - signer: Signer::PubKeys(vec![signer]), - signatures: [(0, signature)].into_iter().collect(), - })); - Self(tx) - } - - /// Validate this wrapper transaction - pub fn validate_tx( - &self, - ) -> std::result::Result, TxError> { - self.0.validate_tx() - } -} +pub mod reading; +pub mod transaction; +pub mod writing; diff --git a/light_sdk/src/reading/mod.rs b/light_sdk/src/reading/mod.rs new file mode 100644 index 0000000000..169ce9a645 --- /dev/null +++ b/light_sdk/src/reading/mod.rs @@ -0,0 +1,8 @@ +// query tx status +pub mod tx { + +} + +pub mod account { + +} \ No newline at end of file diff --git a/light_sdk/src/tx_builders/account.rs b/light_sdk/src/transaction/account.rs similarity index 80% rename from light_sdk/src/tx_builders/account.rs rename to light_sdk/src/transaction/account.rs index a32151e8ca..eab80d49ff 100644 --- a/light_sdk/src/tx_builders/account.rs +++ b/light_sdk/src/transaction/account.rs @@ -1,4 +1,4 @@ -use crate::tx_builders; +use crate::transaction; use borsh_ext::BorshSerializeExt; use namada_core::ledger::governance::storage::proposal::ProposalType; use namada_core::proto::Section; @@ -22,8 +22,6 @@ use std::str::FromStr; use super::GlobalArgs; -//FIXME: docs for all the structs - const TX_INIT_ACCOUNT_WASM: &str = "tx_init_account.wasm"; const TX_REVEAL_PK_WASM: &str = "tx_reveal_pk.wasm"; const TX_UPDATE_ACCOUNT_WASM: &str = "tx_update_account.wasm"; @@ -45,7 +43,7 @@ impl InitAccount { threshold, }; - Self(tx_builders::build_tx( + Self(transaction::build_tx( args, init_account, TX_INIT_ACCOUNT_WASM.to_string(), @@ -53,10 +51,8 @@ impl InitAccount { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(mut self) -> (Self, Vec) { - let (tx, msg) = tx_builders::get_msg_to_sign(self.0); - - (Self(tx), msg) + pub fn get_msg_to_sign(&self) -> Vec { + transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx @@ -64,7 +60,7 @@ impl InitAccount { mut self, signatures: Vec, ) -> Self { - Self(tx_builders::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures(self.0, signatures)) } } @@ -73,7 +69,7 @@ pub struct RevealPk(Tx); impl RevealPk { /// Build a raw Reveal Public Key transaction from the given parameters pub fn new(public_key: common::PublicKey, args: GlobalArgs) -> Self { - Self(tx_builders::build_tx( + Self(transaction::build_tx( args, public_key, TX_REVEAL_PK_WASM.to_string(), @@ -81,10 +77,8 @@ impl RevealPk { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(mut self) -> (Self, Vec) { - let (tx, msg) = tx_builders::get_msg_to_sign(self.0); - - (Self(tx), msg) + pub fn get_msg_to_sign(&self) -> Vec { + transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx @@ -92,7 +86,7 @@ impl RevealPk { mut self, signatures: Vec, ) -> Self { - Self(tx_builders::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures(self.0, signatures)) } } @@ -115,7 +109,7 @@ impl UpdateAccount { threshold, }; - Self(tx_builders::build_tx( + Self(transaction::build_tx( args, update_account, TX_UPDATE_ACCOUNT_WASM.to_string(), @@ -123,10 +117,8 @@ impl UpdateAccount { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(mut self) -> (Self, Vec) { - let (tx, msg) = tx_builders::get_msg_to_sign(self.0); - - (Self(tx), msg) + pub fn get_msg_to_sign(&self) -> Vec { + transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx @@ -134,6 +126,6 @@ impl UpdateAccount { mut self, signatures: Vec, ) -> Self { - Self(tx_builders::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures(self.0, signatures)) } } diff --git a/light_sdk/src/transaction/bridge.rs b/light_sdk/src/transaction/bridge.rs new file mode 100644 index 0000000000..1aab0b63cc --- /dev/null +++ b/light_sdk/src/transaction/bridge.rs @@ -0,0 +1,64 @@ +use crate::transaction; +use borsh_ext::BorshSerializeExt; +use namada_core::ledger::governance::storage::proposal::ProposalType; +use namada_core::proto::Section; +use namada_core::proto::SignatureIndex; +use namada_core::proto::Signer; +use namada_core::proto::TxError; +use namada_core::proto::{Signature, Tx}; +use namada_core::types::address::Address; +use namada_core::types::chain::ChainId; +use namada_core::types::dec::Dec; +use namada_core::types::hash::Hash; +use namada_core::types::key::{common, secp256k1}; +use namada_core::types::storage::Epoch; +use namada_core::types::time::DateTimeUtc; +use namada_core::types::token; +use namada_core::types::token::{Amount, DenominatedAmount, MaspDenom}; +use namada_core::types::transaction::Fee; +use namada_core::types::transaction::GasLimit; +use std::collections::BTreeMap; +use std::str::FromStr; + +pub use namada_core::types::eth_bridge_pool::{GasFee, TransferToEthereum}; + +use super::GlobalArgs; + +const TX_BRIDGE_POOL_WASM: &str = "tx_bridge_pool.wasm"; + +/// A transfer over the Ethereum bridge +pub struct BridgeTransfer(Tx); + +impl BridgeTransfer { + /// Build a raw BridgeTransfer transaction from the given parameters + pub fn new( + transfer: TransferToEthereum, + gas_fee: GasFee, + args: GlobalArgs, + ) -> Self { + let pending_transfer = + namada_core::types::eth_bridge_pool::PendingTransfer { + transfer, + gas_fee, + }; + + Self(transaction::build_tx( + args, + pending_transfer, + TX_BRIDGE_POOL_WASM.to_string(), + )) + } + + /// Get the bytes to sign for the given transaction + pub fn get_msg_to_sign(&self) -> Vec { + transaction::get_msg_to_sign(&self.0) + } + + /// Attach the provided signatures to the tx + pub fn attach_signatures( + mut self, + signatures: Vec, + ) -> Self { + Self(transaction::attach_raw_signatures(self.0, signatures)) + } +} diff --git a/light_sdk/src/tx_builders/governance.rs b/light_sdk/src/transaction/governance.rs similarity index 84% rename from light_sdk/src/tx_builders/governance.rs rename to light_sdk/src/transaction/governance.rs index 8bcfd18ea2..66fcce96dc 100644 --- a/light_sdk/src/tx_builders/governance.rs +++ b/light_sdk/src/transaction/governance.rs @@ -1,4 +1,4 @@ -use crate::tx_builders; +use crate::transaction; use borsh_ext::BorshSerializeExt; use namada_core::ledger::governance::storage::proposal::ProposalType; use namada_core::ledger::governance::storage::vote::StorageProposalVote; @@ -51,7 +51,7 @@ impl InitProposal { grace_epoch, }; - Self(tx_builders::build_tx( + Self(transaction::build_tx( args, init_proposal, TX_INIT_PROPOSAL_WASM.to_string(), @@ -59,10 +59,8 @@ impl InitProposal { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(mut self) -> (Self, Vec) { - let (tx, msg) = tx_builders::get_msg_to_sign(self.0); - - (Self(tx), msg) + pub fn get_msg_to_sign(&self) -> Vec { + transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx @@ -70,7 +68,7 @@ impl InitProposal { mut self, signatures: Vec, ) -> Self { - Self(tx_builders::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures(self.0, signatures)) } } @@ -93,7 +91,7 @@ impl VoteProposal { delegations, }; - Self(tx_builders::build_tx( + Self(transaction::build_tx( args, vote_proposal, TX_VOTE_PROPOSAL.to_string(), @@ -101,10 +99,8 @@ impl VoteProposal { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(mut self) -> (Self, Vec) { - let (tx, msg) = tx_builders::get_msg_to_sign(self.0); - - (Self(tx), msg) + pub fn get_msg_to_sign(&self) -> Vec { + transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx @@ -112,6 +108,6 @@ impl VoteProposal { mut self, signatures: Vec, ) -> Self { - Self(tx_builders::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures(self.0, signatures)) } } diff --git a/light_sdk/src/transaction/ibc.rs b/light_sdk/src/transaction/ibc.rs new file mode 100644 index 0000000000..0dec45790a --- /dev/null +++ b/light_sdk/src/transaction/ibc.rs @@ -0,0 +1,65 @@ +use crate::transaction; +use borsh_ext::BorshSerializeExt; +use ibc::core::Msg; +pub use namada_core::ibc::applications::transfer::msgs::transfer::MsgTransfer; +use namada_core::ledger::governance::storage::proposal::ProposalType; +use namada_core::proto::Section; +use namada_core::proto::SignatureIndex; +use namada_core::proto::Signer; +use namada_core::proto::TxError; +use namada_core::proto::{Signature, Tx}; +use namada_core::types::address::Address; +use namada_core::types::chain::ChainId; +use namada_core::types::dec::Dec; +use namada_core::types::hash::Hash; +use namada_core::types::key::{common, secp256k1}; +use namada_core::types::storage::Epoch; +use namada_core::types::time::DateTimeUtc; +use namada_core::types::token; +use namada_core::types::token::{Amount, DenominatedAmount, MaspDenom}; +use namada_core::types::transaction::Fee; +use namada_core::types::transaction::GasLimit; +use std::collections::BTreeMap; +use std::str::FromStr; + +use super::GlobalArgs; + +const TX_IBC_WASM: &str = "tx_ibc.wasm"; + +pub struct IbcTransfer(Tx); + +impl IbcTransfer { + /// Build a raw IbcTransfer transaction from the given parameters + pub fn new( + packet_data: MsgTransfer, + GlobalArgs { + timestamp, + expiration, + code_hash, + chain_id, + }: GlobalArgs, + ) -> Self { + let mut tx = Tx::new(chain_id, expiration); + tx.header.timestamp = timestamp; + tx.add_code_from_hash(code_hash, Some(TX_IBC_WASM.to_string())); + + let mut data = vec![]; + prost::Message::encode(&packet_data.to_any(), &mut data).unwrap(); + tx.set_data(namada_core::proto::Data::new(data)); + + Self(tx) + } + + /// Get the bytes to sign for the given transaction + pub fn get_msg_to_sign(&self) -> Vec { + transaction::get_msg_to_sign(&self.0) + } + + /// Attach the provided signatures to the tx + pub fn attach_signatures( + mut self, + signatures: Vec, + ) -> Self { + Self(transaction::attach_raw_signatures(self.0, signatures)) + } +} diff --git a/light_sdk/src/tx_builders/mod.rs b/light_sdk/src/transaction/mod.rs similarity index 80% rename from light_sdk/src/tx_builders/mod.rs rename to light_sdk/src/transaction/mod.rs index 7feffa12a5..652c29d95a 100644 --- a/light_sdk/src/tx_builders/mod.rs +++ b/light_sdk/src/transaction/mod.rs @@ -22,9 +22,13 @@ use std::collections::BTreeMap; use std::str::FromStr; pub mod account; +pub mod bridge; pub mod governance; +pub mod ibc; pub mod pgf; pub mod pos; +pub mod transfer; +pub mod wrapper; /// Generic arguments required to construct a transaction pub struct GlobalArgs { @@ -34,7 +38,7 @@ pub struct GlobalArgs { chain_id: ChainId, } -pub(in crate::tx_builders) fn build_tx( +pub(in crate::transaction) fn build_tx( GlobalArgs { timestamp, expiration, @@ -52,16 +56,15 @@ pub(in crate::tx_builders) fn build_tx( inner_tx } -//FIXME: just take reference? -pub(in crate::tx_builders) fn get_msg_to_sign(tx: Tx) -> (Tx, Vec) { - let msg = tx.raw_header_hash().serialize_to_vec(); - - (tx, msg) +//FIXME: back to hash here +//FIXME: actually I think we need to sign a vector of one hash +pub(in crate::transaction) fn get_msg_to_sign(tx: &Tx) -> Vec { + tx.raw_header_hash().serialize_to_vec() } -pub(in crate::tx_builders) fn attach_raw_signatures( +pub(in crate::transaction) fn attach_raw_signatures( + //FIXME: make it as the wrapper function mut tx: Tx, - //FIXME: accept bytes here? signatures: Vec, ) -> Tx { tx.add_signatures(signatures); diff --git a/light_sdk/src/tx_builders/pgf.rs b/light_sdk/src/transaction/pgf.rs similarity index 81% rename from light_sdk/src/tx_builders/pgf.rs rename to light_sdk/src/transaction/pgf.rs index 6bc4ebe551..a87e25d216 100644 --- a/light_sdk/src/tx_builders/pgf.rs +++ b/light_sdk/src/transaction/pgf.rs @@ -1,4 +1,4 @@ -use crate::tx_builders; +use crate::transaction; use borsh_ext::BorshSerializeExt; use namada_core::ledger::governance::storage::proposal::ProposalType; use namada_core::proto::Section; @@ -31,7 +31,7 @@ pub struct ResignSteward(Tx); impl ResignSteward { /// Build a raw ResignSteward transaction from the given parameters pub fn new(steward: Address, args: GlobalArgs) -> Self { - Self(tx_builders::build_tx( + Self(transaction::build_tx( args, steward, TX_RESIGN_STEWARD.to_string(), @@ -39,10 +39,8 @@ impl ResignSteward { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(mut self) -> (Self, Vec) { - let (tx, msg) = tx_builders::get_msg_to_sign(self.0); - - (Self(tx), msg) + pub fn get_msg_to_sign(&self) -> Vec { + transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx @@ -50,7 +48,7 @@ impl ResignSteward { mut self, signatures: Vec, ) -> Self { - Self(tx_builders::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures(self.0, signatures)) } } @@ -69,7 +67,7 @@ impl UpdateStewardCommission { commission, }; - Self(tx_builders::build_tx( + Self(transaction::build_tx( args, update_commission, TX_UPDATE_STEWARD_COMMISSION.to_string(), @@ -77,10 +75,8 @@ impl UpdateStewardCommission { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(mut self) -> (Self, Vec) { - let (tx, msg) = tx_builders::get_msg_to_sign(self.0); - - (Self(tx), msg) + pub fn get_msg_to_sign(&self) -> Vec { + transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx @@ -88,6 +84,6 @@ impl UpdateStewardCommission { mut self, signatures: Vec, ) -> Self { - Self(tx_builders::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures(self.0, signatures)) } } diff --git a/light_sdk/src/tx_builders/pos.rs b/light_sdk/src/transaction/pos.rs similarity index 75% rename from light_sdk/src/tx_builders/pos.rs rename to light_sdk/src/transaction/pos.rs index 50219330e7..159217cf06 100644 --- a/light_sdk/src/tx_builders/pos.rs +++ b/light_sdk/src/transaction/pos.rs @@ -1,4 +1,4 @@ -use crate::tx_builders; +use crate::transaction; use namada_core::ledger::governance::storage::proposal::ProposalType; use namada_core::proto::Section; use namada_core::proto::SignatureIndex; @@ -15,6 +15,7 @@ use namada_core::types::storage::Epoch; use namada_core::types::time::DateTimeUtc; use namada_core::types::token; use namada_core::types::token::{Amount, DenominatedAmount, MaspDenom}; +use namada_core::types::transaction::pos::Redelegation; use namada_core::types::transaction::Fee; use namada_core::types::transaction::GasLimit; use std::collections::BTreeMap; @@ -52,28 +53,24 @@ impl Bond { source, }; - Self(tx_builders::build_tx( + Self(transaction::build_tx( args, unbond, TX_BOND_WASM.to_string(), )) } - //FIXME: share /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(mut self) -> (Self, Vec) { - let (tx, msg) = tx_builders::get_msg_to_sign(self.0); - - (Self(tx), msg) + pub fn get_msg_to_sign(&self) -> Vec { + transaction::get_msg_to_sign(&self.0) } - //FIXME: share /// Attach the provided signatures to the tx pub fn attach_signatures( mut self, signatures: Vec, ) -> Self { - Self(tx_builders::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures(self.0, signatures)) } } @@ -94,7 +91,7 @@ impl Unbond { source, }; - Self(tx_builders::build_tx( + Self(transaction::build_tx( args, unbond, TX_UNBOND_WASM.to_string(), @@ -102,10 +99,8 @@ impl Unbond { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(mut self) -> (Self, Vec) { - let (tx, msg) = tx_builders::get_msg_to_sign(self.0); - - (Self(tx), msg) + pub fn get_msg_to_sign(&self) -> Vec { + transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx @@ -113,7 +108,7 @@ impl Unbond { mut self, signatures: Vec, ) -> Self { - Self(tx_builders::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures(self.0, signatures)) } } @@ -154,7 +149,7 @@ impl InitValidator { validator_vp_code_hash, }; - Self(tx_builders::build_tx( + Self(transaction::build_tx( args, update_account, TX_INIT_VALIDATOR_WASM.to_string(), @@ -162,10 +157,8 @@ impl InitValidator { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(mut self) -> (Self, Vec) { - let (tx, msg) = tx_builders::get_msg_to_sign(self.0); - - (Self(tx), msg) + pub fn get_msg_to_sign(&self) -> Vec { + transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx @@ -173,7 +166,7 @@ impl InitValidator { mut self, signatures: Vec, ) -> Self { - Self(tx_builders::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures(self.0, signatures)) } } @@ -182,7 +175,7 @@ pub struct UnjailValidator(Tx); impl UnjailValidator { /// Build a raw Unjail validator transaction from the given parameters pub fn new(address: Address, args: GlobalArgs) -> Self { - Self(tx_builders::build_tx( + Self(transaction::build_tx( args, address, TX_UNJAIL_VALIDATOR_WASM.to_string(), @@ -190,10 +183,8 @@ impl UnjailValidator { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(mut self) -> (Self, Vec) { - let (tx, msg) = tx_builders::get_msg_to_sign(self.0); - - (Self(tx), msg) + pub fn get_msg_to_sign(&self) -> Vec { + transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx @@ -201,7 +192,7 @@ impl UnjailValidator { mut self, signatures: Vec, ) -> Self { - Self(tx_builders::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures(self.0, signatures)) } } @@ -210,7 +201,7 @@ pub struct DeactivateValidator(Tx); impl DeactivateValidator { /// Build a raw DeactivateValidator transaction from the given parameters pub fn new(address: Address, args: GlobalArgs) -> Self { - Self(tx_builders::build_tx( + Self(transaction::build_tx( args, address, TX_DEACTIVATE_VALIDATOR_WASM.to_string(), @@ -218,10 +209,8 @@ impl DeactivateValidator { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(mut self) -> (Self, Vec) { - let (tx, msg) = tx_builders::get_msg_to_sign(self.0); - - (Self(tx), msg) + pub fn get_msg_to_sign(&self) -> Vec { + transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx @@ -229,7 +218,7 @@ impl DeactivateValidator { mut self, signatures: Vec, ) -> Self { - Self(tx_builders::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures(self.0, signatures)) } } @@ -238,7 +227,7 @@ pub struct ReactivateValidator(Tx); impl ReactivateValidator { /// Build a raw ReactivateValidator transaction from the given parameters pub fn new(address: Address, args: GlobalArgs) -> Self { - Self(tx_builders::build_tx( + Self(transaction::build_tx( args, address, TX_REACTIVATE_VALIDATOR_WASM.to_string(), @@ -246,10 +235,8 @@ impl ReactivateValidator { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(mut self) -> (Self, Vec) { - let (tx, msg) = tx_builders::get_msg_to_sign(self.0); - - (Self(tx), msg) + pub fn get_msg_to_sign(&self) -> Vec { + transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx @@ -257,7 +244,7 @@ impl ReactivateValidator { mut self, signatures: Vec, ) -> Self { - Self(tx_builders::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures(self.0, signatures)) } } @@ -275,7 +262,7 @@ impl ClaimRewards { source, }; - Self(tx_builders::build_tx( + Self(transaction::build_tx( args, init_proposal, TX_CLAIM_REWARDS_WASM.to_string(), @@ -283,10 +270,8 @@ impl ClaimRewards { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(mut self) -> (Self, Vec) { - let (tx, msg) = tx_builders::get_msg_to_sign(self.0); - - (Self(tx), msg) + pub fn get_msg_to_sign(&self) -> Vec { + transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx @@ -294,7 +279,7 @@ impl ClaimRewards { mut self, signatures: Vec, ) -> Self { - Self(tx_builders::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures(self.0, signatures)) } } @@ -321,7 +306,7 @@ impl ChangeMetaData { commission_rate, }; - Self(tx_builders::build_tx( + Self(transaction::build_tx( args, init_proposal, TX_CHANGE_METADATA_WASM.to_string(), @@ -329,10 +314,8 @@ impl ChangeMetaData { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(mut self) -> (Self, Vec) { - let (tx, msg) = tx_builders::get_msg_to_sign(self.0); - - (Self(tx), msg) + pub fn get_msg_to_sign(&self) -> Vec { + transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx @@ -340,7 +323,7 @@ impl ChangeMetaData { mut self, signatures: Vec, ) -> Self { - Self(tx_builders::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures(self.0, signatures)) } } @@ -359,7 +342,7 @@ impl ChangeConsensusKey { consensus_key, }; - Self(tx_builders::build_tx( + Self(transaction::build_tx( args, init_proposal, TX_CHANGE_CONSENSUS_KEY_WASM.to_string(), @@ -367,10 +350,8 @@ impl ChangeConsensusKey { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(mut self) -> (Self, Vec) { - let (tx, msg) = tx_builders::get_msg_to_sign(self.0); - - (Self(tx), msg) + pub fn get_msg_to_sign(&self) -> Vec { + transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx @@ -378,7 +359,7 @@ impl ChangeConsensusKey { mut self, signatures: Vec, ) -> Self { - Self(tx_builders::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures(self.0, signatures)) } } @@ -393,7 +374,7 @@ impl ChangeCommission { new_rate, }; - Self(tx_builders::build_tx( + Self(transaction::build_tx( args, init_proposal, TX_CHANGE_COMMISSION_WASM.to_string(), @@ -401,10 +382,8 @@ impl ChangeCommission { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(mut self) -> (Self, Vec) { - let (tx, msg) = tx_builders::get_msg_to_sign(self.0); - - (Self(tx), msg) + pub fn get_msg_to_sign(&self) -> Vec { + transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx @@ -412,7 +391,7 @@ impl ChangeCommission { mut self, signatures: Vec, ) -> Self { - Self(tx_builders::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures(self.0, signatures)) } } @@ -425,12 +404,13 @@ impl Withdraw { source: Option
, args: GlobalArgs, ) -> Self { + //FIXME: request the correct type directly in the args instead of rebuilding it let init_proposal = namada_core::types::transaction::pos::Withdraw { validator, source, }; - Self(tx_builders::build_tx( + Self(transaction::build_tx( args, init_proposal, TX_WITHDRAW_WASM.to_string(), @@ -438,10 +418,47 @@ impl Withdraw { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(mut self) -> (Self, Vec) { - let (tx, msg) = tx_builders::get_msg_to_sign(self.0); + pub fn get_msg_to_sign(&self) -> Vec { + transaction::get_msg_to_sign(&self.0) + } - (Self(tx), msg) + /// Attach the provided signatures to the tx + pub fn attach_signatures( + mut self, + signatures: Vec, + ) -> Self { + Self(transaction::attach_raw_signatures(self.0, signatures)) + } +} + +pub struct Redelegate(Tx); + +impl Redelegate { + /// Build a raw Redelegate transaction from the given parameters + pub fn new( + src_validator: Address, + dest_validator: Address, + owner: Address, + amount: Amount, + args: GlobalArgs, + ) -> Self { + let redelegation = Redelegation { + src_validator, + dest_validator, + owner, + amount, + }; + + Self(transaction::build_tx( + args, + redelegation, + TX_REDELEGATE_WASM.to_string(), + )) + } + + /// Get the bytes to sign for the given transaction + pub fn get_msg_to_sign(&self) -> Vec { + transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx @@ -449,6 +466,6 @@ impl Withdraw { mut self, signatures: Vec, ) -> Self { - Self(tx_builders::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures(self.0, signatures)) } } diff --git a/light_sdk/src/transaction/transfer.rs b/light_sdk/src/transaction/transfer.rs new file mode 100644 index 0000000000..8d7bdb4db6 --- /dev/null +++ b/light_sdk/src/transaction/transfer.rs @@ -0,0 +1,69 @@ +use crate::transaction; +use borsh_ext::BorshSerializeExt; +use namada_core::ledger::governance::storage::proposal::ProposalType; +use namada_core::proto::Section; +use namada_core::proto::SignatureIndex; +use namada_core::proto::Signer; +use namada_core::proto::TxError; +use namada_core::proto::{Signature, Tx}; +use namada_core::types::address::Address; +use namada_core::types::chain::ChainId; +use namada_core::types::dec::Dec; +use namada_core::types::hash::Hash; +use namada_core::types::key::{common, secp256k1}; +use namada_core::types::storage::Epoch; +use namada_core::types::time::DateTimeUtc; +use namada_core::types::token; +use namada_core::types::token::{Amount, DenominatedAmount, MaspDenom}; +use namada_core::types::transaction::Fee; +use namada_core::types::transaction::GasLimit; +use std::collections::BTreeMap; +use std::str::FromStr; + +use super::GlobalArgs; + +const TX_TRANSFER_WASM: &str = "tx_transfer.wasm"; + +pub struct Transfer(Tx); + +impl Transfer { + /// Build a raw Transfer transaction from the given parameters + pub fn new( + source: Address, + target: Address, + token: Address, + amount: DenominatedAmount, + key: Option, + //FIXME: handle masp here + shielded: Option, + args: GlobalArgs, + ) -> Self { + let init_proposal = namada_core::types::token::Transfer { + source, + target, + token, + amount, + key, + shielded, + }; + + Self(transaction::build_tx( + args, + init_proposal.serialize_to_vec(), + TX_TRANSFER_WASM.to_string(), + )) + } + + /// Get the bytes to sign for the given transaction + pub fn get_msg_to_sign(&self) -> Vec { + transaction::get_msg_to_sign(&self.0) + } + + /// Attach the provided signatures to the tx + pub fn attach_signatures( + mut self, + signatures: Vec, + ) -> Self { + Self(transaction::attach_raw_signatures(self.0, signatures)) + } +} diff --git a/light_sdk/src/transaction/wrapper.rs b/light_sdk/src/transaction/wrapper.rs new file mode 100644 index 0000000000..f24cfb1131 --- /dev/null +++ b/light_sdk/src/transaction/wrapper.rs @@ -0,0 +1,75 @@ +use borsh_ext::BorshSerializeExt; +use namada_core::ledger::governance::storage::proposal::ProposalType; +use namada_core::proto::Section; +use namada_core::proto::Signer; +use namada_core::proto::TxError; +use namada_core::proto::{Signature, Tx}; +use namada_core::types::address::Address; +use namada_core::types::chain::ChainId; +use namada_core::types::dec::Dec; +use namada_core::types::hash::Hash; +use namada_core::types::key::{common, secp256k1}; +use namada_core::types::storage::Epoch; +use namada_core::types::time::DateTimeUtc; +use namada_core::types::token; +use namada_core::types::token::{Amount, DenominatedAmount, MaspDenom}; +use namada_core::types::transaction::Fee; +use namada_core::types::transaction::GasLimit; +use std::collections::BTreeMap; +use std::str::FromStr; + +//FIXME: instead of this file would be better to call finalize(args) on every transaction type that would produce a signed wrapper with the provided args +pub struct Wrapper(Tx); + +impl Wrapper { + /// Takes a transaction and a signature and wraps them in a wrapper + /// transaction ready for submission + pub fn new( + mut tx: Tx, + fee: Fee, + fee_payer: common::PublicKey, + gas_limit: GasLimit, + //FIXME: fix masp unshielding + unshield_hash: Option, + ) -> Self { + tx.add_wrapper( + fee, + fee_payer, + Epoch::default(), + gas_limit, + unshield_hash, + ); + + Self(tx) + } + + /// Returns the message to be signed for this transaction + pub fn get_msg_to_sign(mut self) -> (Self, Vec) { + self.0.protocol_filter(); + let msg = self.0.sechashes(); + + (self, msg) + } + + /// Attach the given outer signature to the transaction + pub fn attach_signature( + mut self, + signer: common::PublicKey, + signature: common::Signature, + ) -> Self { + self.0.add_section(Section::Signature(Signature { + targets: self.0.sechashes(), + signer: Signer::PubKeys(vec![signer]), + signatures: [(0, signature)].into_iter().collect(), + })); + + self + } + + /// Validate this wrapper transaction + pub fn validate_tx( + &self, + ) -> std::result::Result, TxError> { + self.0.validate_tx() + } +} diff --git a/light_sdk/src/writing/mod.rs b/light_sdk/src/writing/mod.rs new file mode 100644 index 0000000000..087d1700da --- /dev/null +++ b/light_sdk/src/writing/mod.rs @@ -0,0 +1,7 @@ +pub mod blocking { + +} + +pub mod unblocking { + +} \ No newline at end of file From 90fa75e91c57eec151cd1f4698992ff9e6591bb2 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 14 Dec 2023 16:29:41 +0100 Subject: [PATCH 184/216] [feat]: Small fixes and making stuff public --- Cargo.toml | 8 +- light_sdk/Cargo.toml | 2 + light_sdk/src/lib.rs | 1 + light_sdk/src/reading/mod.rs | 8 +- light_sdk/src/transaction/account.rs | 57 ++++----- light_sdk/src/transaction/bridge.rs | 37 ++---- light_sdk/src/transaction/governance.rs | 42 +++---- light_sdk/src/transaction/ibc.rs | 38 ++---- light_sdk/src/transaction/mod.rs | 49 +++----- light_sdk/src/transaction/pgf.rs | 49 ++++---- light_sdk/src/transaction/pos.rs | 154 ++++++++++++++---------- light_sdk/src/transaction/transfer.rs | 35 ++---- light_sdk/src/transaction/wrapper.rs | 27 ++--- light_sdk/src/writing/mod.rs | 8 +- 14 files changed, 222 insertions(+), 293 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index aba1b48fda..77cd007ef5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,18 +5,18 @@ members = [ "apps", "benches", "core", + "encoding_spec", "ethereum_bridge", + "light_sdk", + "macros", "proof_of_stake", + "sdk", "shared", "test_utils", "tests", "tx_prelude", "vm_env", - "macros", "vp_prelude", - "encoding_spec", - "sdk", - "light_sdk" ] # wasm packages have to be built separately diff --git a/light_sdk/Cargo.toml b/light_sdk/Cargo.toml index d5460bcfee..8d7a9c433b 100644 --- a/light_sdk/Cargo.toml +++ b/light_sdk/Cargo.toml @@ -11,6 +11,8 @@ readme.workspace = true repository.workspace = true version.workspace = true + + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] diff --git a/light_sdk/src/lib.rs b/light_sdk/src/lib.rs index 3084cc122e..91848c5242 100644 --- a/light_sdk/src/lib.rs +++ b/light_sdk/src/lib.rs @@ -99,3 +99,4 @@ pub mod reading; pub mod transaction; pub mod writing; +pub use namada_core; \ No newline at end of file diff --git a/light_sdk/src/reading/mod.rs b/light_sdk/src/reading/mod.rs index 169ce9a645..63d85a590a 100644 --- a/light_sdk/src/reading/mod.rs +++ b/light_sdk/src/reading/mod.rs @@ -1,8 +1,4 @@ // query tx status -pub mod tx { +pub mod tx {} -} - -pub mod account { - -} \ No newline at end of file +pub mod account {} diff --git a/light_sdk/src/transaction/account.rs b/light_sdk/src/transaction/account.rs index eab80d49ff..be66fde04b 100644 --- a/light_sdk/src/transaction/account.rs +++ b/light_sdk/src/transaction/account.rs @@ -1,26 +1,10 @@ -use crate::transaction; -use borsh_ext::BorshSerializeExt; -use namada_core::ledger::governance::storage::proposal::ProposalType; -use namada_core::proto::Section; -use namada_core::proto::SignatureIndex; -use namada_core::proto::Signer; -use namada_core::proto::TxError; -use namada_core::proto::{Signature, Tx}; +use namada_core::proto::Tx; use namada_core::types::address::Address; -use namada_core::types::chain::ChainId; -use namada_core::types::dec::Dec; use namada_core::types::hash::Hash; -use namada_core::types::key::{common, secp256k1}; -use namada_core::types::storage::Epoch; -use namada_core::types::time::DateTimeUtc; -use namada_core::types::token; -use namada_core::types::token::{Amount, DenominatedAmount, MaspDenom}; -use namada_core::types::transaction::Fee; -use namada_core::types::transaction::GasLimit; -use std::collections::BTreeMap; -use std::str::FromStr; +use namada_core::types::key::common; use super::GlobalArgs; +use crate::transaction; const TX_INIT_ACCOUNT_WASM: &str = "tx_init_account.wasm"; const TX_REVEAL_PK_WASM: &str = "tx_reveal_pk.wasm"; @@ -51,20 +35,23 @@ impl InitAccount { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { + pub fn get_msg_to_sign(&self) -> Vec { transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx pub fn attach_signatures( - mut self, - signatures: Vec, + self, + signer: common::PublicKey, + signature: common::Signature, ) -> Self { - Self(transaction::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures( + self.0, signer, signature, + )) } } -pub struct RevealPk(Tx); +pub struct RevealPk(pub Tx); impl RevealPk { /// Build a raw Reveal Public Key transaction from the given parameters @@ -77,16 +64,19 @@ impl RevealPk { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { + pub fn get_msg_to_sign(&self) -> Vec { transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx pub fn attach_signatures( - mut self, - signatures: Vec, + self, + signer: common::PublicKey, + signature: common::Signature, ) -> Self { - Self(transaction::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures( + self.0, signer, signature, + )) } } @@ -117,15 +107,18 @@ impl UpdateAccount { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { + pub fn get_msg_to_sign(&self) -> Vec { transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx pub fn attach_signatures( - mut self, - signatures: Vec, + self, + signer: common::PublicKey, + signature: common::Signature, ) -> Self { - Self(transaction::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures( + self.0, signer, signature, + )) } } diff --git a/light_sdk/src/transaction/bridge.rs b/light_sdk/src/transaction/bridge.rs index 1aab0b63cc..166f4c10ad 100644 --- a/light_sdk/src/transaction/bridge.rs +++ b/light_sdk/src/transaction/bridge.rs @@ -1,28 +1,10 @@ -use crate::transaction; -use borsh_ext::BorshSerializeExt; -use namada_core::ledger::governance::storage::proposal::ProposalType; -use namada_core::proto::Section; -use namada_core::proto::SignatureIndex; -use namada_core::proto::Signer; -use namada_core::proto::TxError; -use namada_core::proto::{Signature, Tx}; -use namada_core::types::address::Address; -use namada_core::types::chain::ChainId; -use namada_core::types::dec::Dec; -use namada_core::types::hash::Hash; -use namada_core::types::key::{common, secp256k1}; -use namada_core::types::storage::Epoch; -use namada_core::types::time::DateTimeUtc; -use namada_core::types::token; -use namada_core::types::token::{Amount, DenominatedAmount, MaspDenom}; -use namada_core::types::transaction::Fee; -use namada_core::types::transaction::GasLimit; -use std::collections::BTreeMap; -use std::str::FromStr; - +use namada_core::proto::Tx; pub use namada_core::types::eth_bridge_pool::{GasFee, TransferToEthereum}; +use namada_core::types::hash::Hash; +use namada_core::types::key::common; use super::GlobalArgs; +use crate::transaction; const TX_BRIDGE_POOL_WASM: &str = "tx_bridge_pool.wasm"; @@ -50,15 +32,18 @@ impl BridgeTransfer { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { + pub fn get_msg_to_sign(&self) -> Vec { transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx pub fn attach_signatures( - mut self, - signatures: Vec, + self, + signer: common::PublicKey, + signature: common::Signature, ) -> Self { - Self(transaction::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures( + self.0, signer, signature, + )) } } diff --git a/light_sdk/src/transaction/governance.rs b/light_sdk/src/transaction/governance.rs index 66fcce96dc..b95dbfc552 100644 --- a/light_sdk/src/transaction/governance.rs +++ b/light_sdk/src/transaction/governance.rs @@ -1,27 +1,13 @@ -use crate::transaction; -use borsh_ext::BorshSerializeExt; use namada_core::ledger::governance::storage::proposal::ProposalType; use namada_core::ledger::governance::storage::vote::StorageProposalVote; -use namada_core::proto::Section; -use namada_core::proto::SignatureIndex; -use namada_core::proto::Signer; -use namada_core::proto::TxError; -use namada_core::proto::{Signature, Tx}; +use namada_core::proto::Tx; use namada_core::types::address::Address; -use namada_core::types::chain::ChainId; -use namada_core::types::dec::Dec; use namada_core::types::hash::Hash; -use namada_core::types::key::{common, secp256k1}; +use namada_core::types::key::common; use namada_core::types::storage::Epoch; -use namada_core::types::time::DateTimeUtc; -use namada_core::types::token; -use namada_core::types::token::{Amount, DenominatedAmount, MaspDenom}; -use namada_core::types::transaction::Fee; -use namada_core::types::transaction::GasLimit; -use std::collections::BTreeMap; -use std::str::FromStr; use super::GlobalArgs; +use crate::transaction; const TX_INIT_PROPOSAL_WASM: &str = "tx_init_proposal.wasm"; const TX_VOTE_PROPOSAL: &str = "tx_vote_proposal.wasm"; @@ -59,16 +45,19 @@ impl InitProposal { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { + pub fn get_msg_to_sign(&self) -> Vec { transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx pub fn attach_signatures( - mut self, - signatures: Vec, + self, + signer: common::PublicKey, + signature: common::Signature, ) -> Self { - Self(transaction::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures( + self.0, signer, signature, + )) } } @@ -99,15 +88,18 @@ impl VoteProposal { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { + pub fn get_msg_to_sign(&self) -> Vec { transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx pub fn attach_signatures( - mut self, - signatures: Vec, + self, + signer: common::PublicKey, + signature: common::Signature, ) -> Self { - Self(transaction::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures( + self.0, signer, signature, + )) } } diff --git a/light_sdk/src/transaction/ibc.rs b/light_sdk/src/transaction/ibc.rs index 0dec45790a..60fa68b7e3 100644 --- a/light_sdk/src/transaction/ibc.rs +++ b/light_sdk/src/transaction/ibc.rs @@ -1,28 +1,12 @@ -use crate::transaction; -use borsh_ext::BorshSerializeExt; use ibc::core::Msg; pub use namada_core::ibc::applications::transfer::msgs::transfer::MsgTransfer; -use namada_core::ledger::governance::storage::proposal::ProposalType; -use namada_core::proto::Section; -use namada_core::proto::SignatureIndex; -use namada_core::proto::Signer; -use namada_core::proto::TxError; -use namada_core::proto::{Signature, Tx}; -use namada_core::types::address::Address; -use namada_core::types::chain::ChainId; -use namada_core::types::dec::Dec; +use namada_core::proto::Tx; use namada_core::types::hash::Hash; -use namada_core::types::key::{common, secp256k1}; -use namada_core::types::storage::Epoch; -use namada_core::types::time::DateTimeUtc; -use namada_core::types::token; -use namada_core::types::token::{Amount, DenominatedAmount, MaspDenom}; -use namada_core::types::transaction::Fee; -use namada_core::types::transaction::GasLimit; -use std::collections::BTreeMap; -use std::str::FromStr; +use namada_core::types::key::common; +use namada_core::types::time::MIN_UTC; use super::GlobalArgs; +use crate::transaction; const TX_IBC_WASM: &str = "tx_ibc.wasm"; @@ -33,14 +17,13 @@ impl IbcTransfer { pub fn new( packet_data: MsgTransfer, GlobalArgs { - timestamp, expiration, code_hash, chain_id, }: GlobalArgs, ) -> Self { let mut tx = Tx::new(chain_id, expiration); - tx.header.timestamp = timestamp; + tx.header.timestamp = MIN_UTC; tx.add_code_from_hash(code_hash, Some(TX_IBC_WASM.to_string())); let mut data = vec![]; @@ -51,15 +34,18 @@ impl IbcTransfer { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { + pub fn get_msg_to_sign(&self) -> Vec { transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx pub fn attach_signatures( - mut self, - signatures: Vec, + self, + signer: common::PublicKey, + signature: common::Signature, ) -> Self { - Self(transaction::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures( + self.0, signer, signature, + )) } } diff --git a/light_sdk/src/transaction/mod.rs b/light_sdk/src/transaction/mod.rs index 652c29d95a..3e21fd6d3d 100644 --- a/light_sdk/src/transaction/mod.rs +++ b/light_sdk/src/transaction/mod.rs @@ -1,25 +1,9 @@ use borsh::BorshSerialize; -use borsh_ext::BorshSerializeExt; -use namada_core::ledger::governance::storage::proposal::ProposalType; -use namada_core::proto::Section; -use namada_core::proto::SignatureIndex; -use namada_core::proto::Signer; -use namada_core::proto::TxError; -use namada_core::proto::{Signature, Tx}; -use namada_core::types::account::AccountPublicKeysMap; -use namada_core::types::address::Address; +use namada_core::proto::{Section, Signature, Signer, Tx}; use namada_core::types::chain::ChainId; -use namada_core::types::dec::Dec; use namada_core::types::hash::Hash; -use namada_core::types::key::{common, secp256k1}; -use namada_core::types::storage::Epoch; -use namada_core::types::time::DateTimeUtc; -use namada_core::types::token; -use namada_core::types::token::{Amount, DenominatedAmount, MaspDenom}; -use namada_core::types::transaction::Fee; -use namada_core::types::transaction::GasLimit; -use std::collections::BTreeMap; -use std::str::FromStr; +use namada_core::types::key::common; +use namada_core::types::time::{DateTimeUtc, MIN_UTC}; pub mod account; pub mod bridge; @@ -31,16 +15,15 @@ pub mod transfer; pub mod wrapper; /// Generic arguments required to construct a transaction +#[repr(C)] pub struct GlobalArgs { - timestamp: DateTimeUtc, - expiration: Option, - code_hash: Hash, - chain_id: ChainId, + pub expiration: Option, + pub code_hash: Hash, + pub chain_id: ChainId, } pub(in crate::transaction) fn build_tx( GlobalArgs { - timestamp, expiration, code_hash, chain_id, @@ -49,24 +32,26 @@ pub(in crate::transaction) fn build_tx( code_tag: String, ) -> Tx { let mut inner_tx = Tx::new(chain_id, expiration); - inner_tx.header.timestamp = timestamp; + inner_tx.header.timestamp = MIN_UTC; inner_tx.add_code_from_hash(code_hash, Some(code_tag)); inner_tx.add_data(data); inner_tx } -//FIXME: back to hash here -//FIXME: actually I think we need to sign a vector of one hash -pub(in crate::transaction) fn get_msg_to_sign(tx: &Tx) -> Vec { - tx.raw_header_hash().serialize_to_vec() +pub(in crate::transaction) fn get_msg_to_sign(tx: &Tx) -> Vec { + vec![tx.raw_header_hash()] } pub(in crate::transaction) fn attach_raw_signatures( - //FIXME: make it as the wrapper function mut tx: Tx, - signatures: Vec, + signer: common::PublicKey, + signature: common::Signature, ) -> Tx { - tx.add_signatures(signatures); + tx.add_section(Section::Signature(Signature { + targets: vec![tx.raw_header_hash()], + signer: Signer::PubKeys(vec![signer]), + signatures: [(0, signature)].into_iter().collect(), + })); tx } diff --git a/light_sdk/src/transaction/pgf.rs b/light_sdk/src/transaction/pgf.rs index a87e25d216..90266b0060 100644 --- a/light_sdk/src/transaction/pgf.rs +++ b/light_sdk/src/transaction/pgf.rs @@ -1,27 +1,13 @@ -use crate::transaction; -use borsh_ext::BorshSerializeExt; -use namada_core::ledger::governance::storage::proposal::ProposalType; -use namada_core::proto::Section; -use namada_core::proto::SignatureIndex; -use namada_core::proto::Signer; -use namada_core::proto::TxError; -use namada_core::proto::{Signature, Tx}; +use std::collections::HashMap; + +use namada_core::proto::Tx; use namada_core::types::address::Address; -use namada_core::types::chain::ChainId; use namada_core::types::dec::Dec; use namada_core::types::hash::Hash; -use namada_core::types::key::{common, secp256k1}; -use namada_core::types::storage::Epoch; -use namada_core::types::time::DateTimeUtc; -use namada_core::types::token; -use namada_core::types::token::{Amount, DenominatedAmount, MaspDenom}; -use namada_core::types::transaction::Fee; -use namada_core::types::transaction::GasLimit; -use std::collections::BTreeMap; -use std::collections::HashMap; -use std::str::FromStr; +use namada_core::types::key::common; use super::GlobalArgs; +use crate::transaction; const TX_RESIGN_STEWARD: &str = "tx_resign_steward.wasm"; const TX_UPDATE_STEWARD_COMMISSION: &str = "tx_update_steward_commission.wasm"; @@ -39,23 +25,27 @@ impl ResignSteward { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { + pub fn get_msg_to_sign(&self) -> Vec { transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx pub fn attach_signatures( - mut self, - signatures: Vec, + self, + signer: common::PublicKey, + signature: common::Signature, ) -> Self { - Self(transaction::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures( + self.0, signer, signature, + )) } } pub struct UpdateStewardCommission(Tx); impl UpdateStewardCommission { - /// Build a raw UpdateStewardCommission transaction from the given parameters + /// Build a raw UpdateStewardCommission transaction from the given + /// parameters pub fn new( steward: Address, commission: HashMap, @@ -75,15 +65,18 @@ impl UpdateStewardCommission { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { + pub fn get_msg_to_sign(&self) -> Vec { transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx pub fn attach_signatures( - mut self, - signatures: Vec, + self, + signer: common::PublicKey, + signature: common::Signature, ) -> Self { - Self(transaction::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures( + self.0, signer, signature, + )) } } diff --git a/light_sdk/src/transaction/pos.rs b/light_sdk/src/transaction/pos.rs index 159217cf06..532329bf53 100644 --- a/light_sdk/src/transaction/pos.rs +++ b/light_sdk/src/transaction/pos.rs @@ -1,27 +1,14 @@ -use crate::transaction; -use namada_core::ledger::governance::storage::proposal::ProposalType; -use namada_core::proto::Section; -use namada_core::proto::SignatureIndex; -use namada_core::proto::Signer; -use namada_core::proto::TxError; -use namada_core::proto::{Signature, Tx}; -use namada_core::types::account::AccountPublicKeysMap; +use namada_core::proto::Tx; use namada_core::types::address::Address; -use namada_core::types::chain::ChainId; use namada_core::types::dec::Dec; use namada_core::types::hash::Hash; use namada_core::types::key::{common, secp256k1}; -use namada_core::types::storage::Epoch; -use namada_core::types::time::DateTimeUtc; use namada_core::types::token; -use namada_core::types::token::{Amount, DenominatedAmount, MaspDenom}; +use namada_core::types::token::Amount; use namada_core::types::transaction::pos::Redelegation; -use namada_core::types::transaction::Fee; -use namada_core::types::transaction::GasLimit; -use std::collections::BTreeMap; -use std::str::FromStr; use super::GlobalArgs; +use crate::transaction; const TX_BOND_WASM: &str = "tx_bond.wasm"; const TX_UNBOND_WASM: &str = "tx_unbond.wasm"; @@ -61,16 +48,19 @@ impl Bond { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { + pub fn get_msg_to_sign(&self) -> Vec { transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx pub fn attach_signatures( - mut self, - signatures: Vec, + self, + signer: common::PublicKey, + signature: common::Signature, ) -> Self { - Self(transaction::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures( + self.0, signer, signature, + )) } } @@ -99,16 +89,19 @@ impl Unbond { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { + pub fn get_msg_to_sign(&self) -> Vec { transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx pub fn attach_signatures( - mut self, - signatures: Vec, + self, + signer: common::PublicKey, + signature: common::Signature, ) -> Self { - Self(transaction::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures( + self.0, signer, signature, + )) } } @@ -157,16 +150,19 @@ impl InitValidator { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { + pub fn get_msg_to_sign(&self) -> Vec { transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx pub fn attach_signatures( - mut self, - signatures: Vec, + self, + signer: common::PublicKey, + signature: common::Signature, ) -> Self { - Self(transaction::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures( + self.0, signer, signature, + )) } } @@ -183,16 +179,19 @@ impl UnjailValidator { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { + pub fn get_msg_to_sign(&self) -> Vec { transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx pub fn attach_signatures( - mut self, - signatures: Vec, + self, + signer: common::PublicKey, + signature: common::Signature, ) -> Self { - Self(transaction::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures( + self.0, signer, signature, + )) } } @@ -209,16 +208,19 @@ impl DeactivateValidator { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { + pub fn get_msg_to_sign(&self) -> Vec { transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx pub fn attach_signatures( - mut self, - signatures: Vec, + self, + signer: common::PublicKey, + signature: common::Signature, ) -> Self { - Self(transaction::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures( + self.0, signer, signature, + )) } } @@ -235,16 +237,19 @@ impl ReactivateValidator { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { + pub fn get_msg_to_sign(&self) -> Vec { transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx pub fn attach_signatures( - mut self, - signatures: Vec, + self, + signer: common::PublicKey, + signature: common::Signature, ) -> Self { - Self(transaction::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures( + self.0, signer, signature, + )) } } @@ -270,16 +275,19 @@ impl ClaimRewards { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { + pub fn get_msg_to_sign(&self) -> Vec { transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx pub fn attach_signatures( - mut self, - signatures: Vec, + self, + signer: common::PublicKey, + signature: common::Signature, ) -> Self { - Self(transaction::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures( + self.0, signer, signature, + )) } } @@ -314,16 +322,19 @@ impl ChangeMetaData { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { + pub fn get_msg_to_sign(&self) -> Vec { transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx pub fn attach_signatures( - mut self, - signatures: Vec, + self, + signer: common::PublicKey, + signature: common::Signature, ) -> Self { - Self(transaction::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures( + self.0, signer, signature, + )) } } @@ -350,16 +361,19 @@ impl ChangeConsensusKey { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { + pub fn get_msg_to_sign(&self) -> Vec { transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx pub fn attach_signatures( - mut self, - signatures: Vec, + self, + signer: common::PublicKey, + signature: common::Signature, ) -> Self { - Self(transaction::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures( + self.0, signer, signature, + )) } } @@ -382,16 +396,19 @@ impl ChangeCommission { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { + pub fn get_msg_to_sign(&self) -> Vec { transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx pub fn attach_signatures( - mut self, - signatures: Vec, + self, + signer: common::PublicKey, + signature: common::Signature, ) -> Self { - Self(transaction::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures( + self.0, signer, signature, + )) } } @@ -404,7 +421,8 @@ impl Withdraw { source: Option
, args: GlobalArgs, ) -> Self { - //FIXME: request the correct type directly in the args instead of rebuilding it + // FIXME: request the correct type directly in the args instead of + // rebuilding it let init_proposal = namada_core::types::transaction::pos::Withdraw { validator, source, @@ -418,16 +436,19 @@ impl Withdraw { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { + pub fn get_msg_to_sign(&self) -> Vec { transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx pub fn attach_signatures( - mut self, - signatures: Vec, + self, + signer: common::PublicKey, + signature: common::Signature, ) -> Self { - Self(transaction::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures( + self.0, signer, signature, + )) } } @@ -457,15 +478,18 @@ impl Redelegate { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { + pub fn get_msg_to_sign(&self) -> Vec { transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx pub fn attach_signatures( - mut self, - signatures: Vec, + self, + signer: common::PublicKey, + signature: common::Signature, ) -> Self { - Self(transaction::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures( + self.0, signer, signature, + )) } } diff --git a/light_sdk/src/transaction/transfer.rs b/light_sdk/src/transaction/transfer.rs index 8d7bdb4db6..52fb8e2bcb 100644 --- a/light_sdk/src/transaction/transfer.rs +++ b/light_sdk/src/transaction/transfer.rs @@ -1,26 +1,12 @@ -use crate::transaction; use borsh_ext::BorshSerializeExt; -use namada_core::ledger::governance::storage::proposal::ProposalType; -use namada_core::proto::Section; -use namada_core::proto::SignatureIndex; -use namada_core::proto::Signer; -use namada_core::proto::TxError; -use namada_core::proto::{Signature, Tx}; +use namada_core::proto::Tx; use namada_core::types::address::Address; -use namada_core::types::chain::ChainId; -use namada_core::types::dec::Dec; use namada_core::types::hash::Hash; -use namada_core::types::key::{common, secp256k1}; -use namada_core::types::storage::Epoch; -use namada_core::types::time::DateTimeUtc; -use namada_core::types::token; -use namada_core::types::token::{Amount, DenominatedAmount, MaspDenom}; -use namada_core::types::transaction::Fee; -use namada_core::types::transaction::GasLimit; -use std::collections::BTreeMap; -use std::str::FromStr; +use namada_core::types::key::common; +use namada_core::types::token::DenominatedAmount; use super::GlobalArgs; +use crate::transaction; const TX_TRANSFER_WASM: &str = "tx_transfer.wasm"; @@ -34,7 +20,7 @@ impl Transfer { token: Address, amount: DenominatedAmount, key: Option, - //FIXME: handle masp here + // FIXME: handle masp here shielded: Option, args: GlobalArgs, ) -> Self { @@ -55,15 +41,18 @@ impl Transfer { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { + pub fn get_msg_to_sign(&self) -> Vec { transaction::get_msg_to_sign(&self.0) } /// Attach the provided signatures to the tx pub fn attach_signatures( - mut self, - signatures: Vec, + self, + signer: common::PublicKey, + signature: common::Signature, ) -> Self { - Self(transaction::attach_raw_signatures(self.0, signatures)) + Self(transaction::attach_raw_signatures( + self.0, signer, signature, + )) } } diff --git a/light_sdk/src/transaction/wrapper.rs b/light_sdk/src/transaction/wrapper.rs index f24cfb1131..0475823dea 100644 --- a/light_sdk/src/transaction/wrapper.rs +++ b/light_sdk/src/transaction/wrapper.rs @@ -1,24 +1,11 @@ -use borsh_ext::BorshSerializeExt; -use namada_core::ledger::governance::storage::proposal::ProposalType; -use namada_core::proto::Section; -use namada_core::proto::Signer; -use namada_core::proto::TxError; -use namada_core::proto::{Signature, Tx}; -use namada_core::types::address::Address; -use namada_core::types::chain::ChainId; -use namada_core::types::dec::Dec; +use namada_core::proto::{Section, Signature, Signer, Tx, TxError}; use namada_core::types::hash::Hash; -use namada_core::types::key::{common, secp256k1}; +use namada_core::types::key::common; use namada_core::types::storage::Epoch; -use namada_core::types::time::DateTimeUtc; -use namada_core::types::token; -use namada_core::types::token::{Amount, DenominatedAmount, MaspDenom}; -use namada_core::types::transaction::Fee; -use namada_core::types::transaction::GasLimit; -use std::collections::BTreeMap; -use std::str::FromStr; +use namada_core::types::transaction::{Fee, GasLimit}; -//FIXME: instead of this file would be better to call finalize(args) on every transaction type that would produce a signed wrapper with the provided args +// FIXME: instead of this file would be better to call finalize(args) on every +// transaction type that would produce a signed wrapper with the provided args pub struct Wrapper(Tx); impl Wrapper { @@ -29,7 +16,7 @@ impl Wrapper { fee: Fee, fee_payer: common::PublicKey, gas_limit: GasLimit, - //FIXME: fix masp unshielding + // FIXME: fix masp unshielding unshield_hash: Option, ) -> Self { tx.add_wrapper( @@ -69,7 +56,7 @@ impl Wrapper { /// Validate this wrapper transaction pub fn validate_tx( &self, - ) -> std::result::Result, TxError> { + ) -> Result, TxError> { self.0.validate_tx() } } diff --git a/light_sdk/src/writing/mod.rs b/light_sdk/src/writing/mod.rs index 087d1700da..ac809ebf48 100644 --- a/light_sdk/src/writing/mod.rs +++ b/light_sdk/src/writing/mod.rs @@ -1,7 +1,3 @@ -pub mod blocking { +pub mod blocking {} -} - -pub mod unblocking { - -} \ No newline at end of file +pub mod unblocking {} From 45e986bdd9fbdbea1052d86887236250bca6630a Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 15 Dec 2023 13:51:43 +0100 Subject: [PATCH 185/216] Renames `get_msg_to_sign` to `get_sign_bytes` --- light_sdk/src/lib.rs | 183 ++++++++++++------------ light_sdk/src/transaction/account.rs | 12 +- light_sdk/src/transaction/bridge.rs | 4 +- light_sdk/src/transaction/governance.rs | 8 +- light_sdk/src/transaction/ibc.rs | 4 +- light_sdk/src/transaction/mod.rs | 2 +- light_sdk/src/transaction/pgf.rs | 8 +- light_sdk/src/transaction/pos.rs | 48 +++---- light_sdk/src/transaction/transfer.rs | 4 +- light_sdk/src/transaction/wrapper.rs | 6 +- 10 files changed, 139 insertions(+), 140 deletions(-) diff --git a/light_sdk/src/lib.rs b/light_sdk/src/lib.rs index 91848c5242..beb6aa8fc9 100644 --- a/light_sdk/src/lib.rs +++ b/light_sdk/src/lib.rs @@ -1,102 +1,103 @@ -/* - * tx status - * block height - * account balance - * anything else if needed - */ +// tx status +// block height +// account balance +// anything else if needed // pub mod reading; -// either sync or async tx submission - prints tx hashes that can be used for tracking -// pub mod writing; +// either sync or async tx submission - prints tx hashes that can be used for +// tracking pub mod writing; // pub mod wallet; // ==== -/* - SDK for building transactions without having to pass in an entire Namada chain +// SDK for building transactions without having to pass in an entire Namada +// chain +// +// Requirements: +// Don't try to refactor the existing sdk - this shouldn't cause any merge +// conflicts with anything Minimal dependencies - no filesystem, no +// multi-threading, no IO, no wasm decoding core with default features turned +// off seems fine No lifetimes or abstract types +// it should be dump-ass simple to use this from other languages or contexts +// Callers should never have to worry about TxType::Raw or TxType::Wrapper - +// this should be hidden in the implementation No usage of async +// None of this signing code requires any async behavior - it's crazy to force +// all callers into async +// +// Proposed Flow: +// the crate should expose 1 struct and an implementation for that struct for +// every transaction type every struct (ie Bond) should have these functions +// new() - to create a new type of Bond - it takes all parameters, like chain_id +// (this will lead to duplication, which is desired for a simple API (if desired +// the caller can build their own TxFactory)) sign_bytes() - the bytes that need +// to be signed by the signer the crate should expose 1 struct and an +// implementation for a wrapper transaction new() +// should take a correctly signed inner tx (this is not enforced but rather +// documented, you can use this API to create garbage) takes the inner tx & the +// inner signature should take all the fields that are required by the wrapper +// sign_bytes() - the bytes that need to be signed by the signer +// +// Future Development: +// !!! AVOID ABSTRACT TYPES (that a caller needs to pass in) or LIFETIMES (in +// the caller api) LIKE THE PLAGUE !!! around this core light_sdk we can build +// more complex features get data via helper functions from a connected node +// can have an query endpoint that just takes a Tendermint RPC node and allows +// me to query it verify signatures +// support multi-sig signers +// dry-run these transactions +// ALWAYS EXPOSE SYNC AND ASYNC FUNCTIONALITY +// never force callers into using async - we must always support an API that +// synchronous none of this extra stuff should leak into the core +// need to be able to import the core to iOS or other languages without complex +// dependencies key backends +// file based key backends that callers can use +// libudev based key backends that call out to HSMs +// +// Questions: +// Can a single wrapper contain more than 1 inner transaction? Yes but it will +// not be executed (just extra payload for which gas is paid) Can the signer of +// the inner tx be different than of the wrapper transaction? Yes it can +// Is the signature of the outer transaction dependent on the signature of the +// inner one? Not at all, we only sign headers of transactions, so the inner +// signature is not part of the message that is signed for the wrapper +// How do the tags work again? Can I only sign over the tags and not the +// wasm_hash? If we need wasm_hashes, those should be saved as constants in the +// binary and a caller can decide to pass in their own wasm hashes or load the +// constants. +// +// MAINNET_BOND_WASM_HASH: &str = "mainnet_wasm_hash"; +// Bond::new("wasm_hash"); +// Bond::new(MAINNET_BOND_WASM_HASH) - Requirements: - * Don't try to refactor the existing sdk - this shouldn't cause any merge conflicts with anything - * Minimal dependencies - no filesystem, no multi-threading, no IO, no wasm decoding - * core with default features turned off seems fine - * No lifetimes or abstract types - * it should be dump-ass simple to use this from other languages or contexts - * Callers should never have to worry about TxType::Raw or TxType::Wrapper - this should be hidden in the implementation - * No usage of async - * None of this signing code requires any async behavior - it's crazy to force all callers into async - - Proposed Flow: - * the crate should expose 1 struct and an implementation for that struct for every transaction type - * every struct (ie Bond) should have these functions - * new() - to create a new type of Bond - it takes all parameters, like chain_id (this will lead to duplication, which is desired for a simple API (if desired the caller can build their own TxFactory)) - * sign_bytes() - the bytes that need to be signed by the signer - * the crate should expose 1 struct and an implementation for a wrapper transaction - * new() - * should take a correctly signed inner tx (this is not enforced but rather documented, you can use this API to create garbage) - * takes the inner tx & the inner signature - * should take all the fields that are required by the wrapper - * sign_bytes() - the bytes that need to be signed by the signer - - Future Development: - * !!! AVOID ABSTRACT TYPES (that a caller needs to pass in) or LIFETIMES (in the caller api) LIKE THE PLAGUE !!! - * around this core light_sdk we can build more complex features - * get data via helper functions from a connected node - * can have an query endpoint that just takes a Tendermint RPC node and allows me to query it - * verify signatures - * support multi-sig signers - * dry-run these transactions - * ALWAYS EXPOSE SYNC AND ASYNC FUNCTIONALITY - * never force callers into using async - we must always support an API that synchronous - * none of this extra stuff should leak into the core - * need to be able to import the core to iOS or other languages without complex dependencies - * key backends - * file based key backends that callers can use - * libudev based key backends that call out to HSMs - - Questions: - * Can a single wrapper contain more than 1 inner transaction? Yes but it will not be executed (just extra payload for which gas is paid) - * Can the signer of the inner tx be different than of the wrapper transaction? Yes it can - * Is the signature of the outer transaction dependent on the signature of the inner one? Not at all, we only sign headers of transactions, so the inner signature is not part of the message that is signed for the wrapper - * How do the tags work again? Can I only sign over the tags and not the wasm_hash? - * If we need wasm_hashes, those should be saved as constants in the binary and a caller can decide to pass in their own wasm hashes or load the constants. - - MAINNET_BOND_WASM_HASH: &str = "mainnet_wasm_hash"; - Bond::new("wasm_hash"); - Bond::new(MAINNET_BOND_WASM_HASH) -*/ - -/* - * need a function sign_bytes() that just returns me the bytes to sign - * need a function that takes a transaction and some sign_bytes and forms a submittable tx from them - * this is roughly the API that we want - ```rust - let keypair = gen_keypair(); - - let mut wrapper = - Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( - Fee { - amount_per_gas_unit: 0.into(), - token: shell.wl_storage.storage.native_token.clone(), - }, - keypair.ref_to(), - Epoch(0), - 0.into(), - None, - )))); - wrapper.header.chain_id = shell.chain_id.clone(); - wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); - wrapper.set_data(Data::new( - "Encrypted transaction data".as_bytes().to_owned(), - )); - wrapper.add_section(Section::Signature(Signature::new( - wrapper.sechashes(), - [(0, keypair)].into_iter().collect(), - None, - ))); - ``` -*/ +// need a function sign_bytes() that just returns me the bytes to sign +// need a function that takes a transaction and some sign_bytes and forms a +// submittable tx from them this is roughly the API that we want +// ```rust +// let keypair = gen_keypair(); +// +// let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( +// Fee { +// amount_per_gas_unit: 0.into(), +// token: shell.wl_storage.storage.native_token.clone(), +// }, +// keypair.ref_to(), +// Epoch(0), +// 0.into(), +// None, +// )))); +// wrapper.header.chain_id = shell.chain_id.clone(); +// wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); +// wrapper.set_data(Data::new( +// "Encrypted transaction data".as_bytes().to_owned(), +// )); +// wrapper.add_section(Section::Signature(Signature::new( +// wrapper.sechashes(), +// [(0, keypair)].into_iter().collect(), +// None, +// ))); +// ``` pub mod reading; pub mod transaction; pub mod writing; -pub use namada_core; \ No newline at end of file +pub use namada_core; diff --git a/light_sdk/src/transaction/account.rs b/light_sdk/src/transaction/account.rs index be66fde04b..971ba0d6ab 100644 --- a/light_sdk/src/transaction/account.rs +++ b/light_sdk/src/transaction/account.rs @@ -35,8 +35,8 @@ impl InitAccount { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { - transaction::get_msg_to_sign(&self.0) + pub fn get_sign_bytes(&self) -> Vec { + transaction::get_sign_bytes(&self.0) } /// Attach the provided signatures to the tx @@ -64,8 +64,8 @@ impl RevealPk { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { - transaction::get_msg_to_sign(&self.0) + pub fn get_sign_bytes(&self) -> Vec { + transaction::get_sign_bytes(&self.0) } /// Attach the provided signatures to the tx @@ -107,8 +107,8 @@ impl UpdateAccount { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { - transaction::get_msg_to_sign(&self.0) + pub fn get_sign_bytes(&self) -> Vec { + transaction::get_sign_bytes(&self.0) } /// Attach the provided signatures to the tx diff --git a/light_sdk/src/transaction/bridge.rs b/light_sdk/src/transaction/bridge.rs index 166f4c10ad..898322e296 100644 --- a/light_sdk/src/transaction/bridge.rs +++ b/light_sdk/src/transaction/bridge.rs @@ -32,8 +32,8 @@ impl BridgeTransfer { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { - transaction::get_msg_to_sign(&self.0) + pub fn get_sign_bytes(&self) -> Vec { + transaction::get_sign_bytes(&self.0) } /// Attach the provided signatures to the tx diff --git a/light_sdk/src/transaction/governance.rs b/light_sdk/src/transaction/governance.rs index b95dbfc552..2a339704d4 100644 --- a/light_sdk/src/transaction/governance.rs +++ b/light_sdk/src/transaction/governance.rs @@ -45,8 +45,8 @@ impl InitProposal { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { - transaction::get_msg_to_sign(&self.0) + pub fn get_sign_bytes(&self) -> Vec { + transaction::get_sign_bytes(&self.0) } /// Attach the provided signatures to the tx @@ -88,8 +88,8 @@ impl VoteProposal { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { - transaction::get_msg_to_sign(&self.0) + pub fn get_sign_bytes(&self) -> Vec { + transaction::get_sign_bytes(&self.0) } /// Attach the provided signatures to the tx diff --git a/light_sdk/src/transaction/ibc.rs b/light_sdk/src/transaction/ibc.rs index 60fa68b7e3..39b8372e21 100644 --- a/light_sdk/src/transaction/ibc.rs +++ b/light_sdk/src/transaction/ibc.rs @@ -34,8 +34,8 @@ impl IbcTransfer { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { - transaction::get_msg_to_sign(&self.0) + pub fn get_sign_bytes(&self) -> Vec { + transaction::get_sign_bytes(&self.0) } /// Attach the provided signatures to the tx diff --git a/light_sdk/src/transaction/mod.rs b/light_sdk/src/transaction/mod.rs index 3e21fd6d3d..c119d8f18f 100644 --- a/light_sdk/src/transaction/mod.rs +++ b/light_sdk/src/transaction/mod.rs @@ -39,7 +39,7 @@ pub(in crate::transaction) fn build_tx( inner_tx } -pub(in crate::transaction) fn get_msg_to_sign(tx: &Tx) -> Vec { +pub(in crate::transaction) fn get_sign_bytes(tx: &Tx) -> Vec { vec![tx.raw_header_hash()] } diff --git a/light_sdk/src/transaction/pgf.rs b/light_sdk/src/transaction/pgf.rs index 90266b0060..208454329b 100644 --- a/light_sdk/src/transaction/pgf.rs +++ b/light_sdk/src/transaction/pgf.rs @@ -25,8 +25,8 @@ impl ResignSteward { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { - transaction::get_msg_to_sign(&self.0) + pub fn get_sign_bytes(&self) -> Vec { + transaction::get_sign_bytes(&self.0) } /// Attach the provided signatures to the tx @@ -65,8 +65,8 @@ impl UpdateStewardCommission { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { - transaction::get_msg_to_sign(&self.0) + pub fn get_sign_bytes(&self) -> Vec { + transaction::get_sign_bytes(&self.0) } /// Attach the provided signatures to the tx diff --git a/light_sdk/src/transaction/pos.rs b/light_sdk/src/transaction/pos.rs index 532329bf53..cae610cf05 100644 --- a/light_sdk/src/transaction/pos.rs +++ b/light_sdk/src/transaction/pos.rs @@ -48,8 +48,8 @@ impl Bond { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { - transaction::get_msg_to_sign(&self.0) + pub fn get_sign_bytes(&self) -> Vec { + transaction::get_sign_bytes(&self.0) } /// Attach the provided signatures to the tx @@ -89,8 +89,8 @@ impl Unbond { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { - transaction::get_msg_to_sign(&self.0) + pub fn get_sign_bytes(&self) -> Vec { + transaction::get_sign_bytes(&self.0) } /// Attach the provided signatures to the tx @@ -150,8 +150,8 @@ impl InitValidator { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { - transaction::get_msg_to_sign(&self.0) + pub fn get_sign_bytes(&self) -> Vec { + transaction::get_sign_bytes(&self.0) } /// Attach the provided signatures to the tx @@ -179,8 +179,8 @@ impl UnjailValidator { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { - transaction::get_msg_to_sign(&self.0) + pub fn get_sign_bytes(&self) -> Vec { + transaction::get_sign_bytes(&self.0) } /// Attach the provided signatures to the tx @@ -208,8 +208,8 @@ impl DeactivateValidator { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { - transaction::get_msg_to_sign(&self.0) + pub fn get_sign_bytes(&self) -> Vec { + transaction::get_sign_bytes(&self.0) } /// Attach the provided signatures to the tx @@ -237,8 +237,8 @@ impl ReactivateValidator { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { - transaction::get_msg_to_sign(&self.0) + pub fn get_sign_bytes(&self) -> Vec { + transaction::get_sign_bytes(&self.0) } /// Attach the provided signatures to the tx @@ -275,8 +275,8 @@ impl ClaimRewards { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { - transaction::get_msg_to_sign(&self.0) + pub fn get_sign_bytes(&self) -> Vec { + transaction::get_sign_bytes(&self.0) } /// Attach the provided signatures to the tx @@ -322,8 +322,8 @@ impl ChangeMetaData { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { - transaction::get_msg_to_sign(&self.0) + pub fn get_sign_bytes(&self) -> Vec { + transaction::get_sign_bytes(&self.0) } /// Attach the provided signatures to the tx @@ -361,8 +361,8 @@ impl ChangeConsensusKey { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { - transaction::get_msg_to_sign(&self.0) + pub fn get_sign_bytes(&self) -> Vec { + transaction::get_sign_bytes(&self.0) } /// Attach the provided signatures to the tx @@ -396,8 +396,8 @@ impl ChangeCommission { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { - transaction::get_msg_to_sign(&self.0) + pub fn get_sign_bytes(&self) -> Vec { + transaction::get_sign_bytes(&self.0) } /// Attach the provided signatures to the tx @@ -436,8 +436,8 @@ impl Withdraw { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { - transaction::get_msg_to_sign(&self.0) + pub fn get_sign_bytes(&self) -> Vec { + transaction::get_sign_bytes(&self.0) } /// Attach the provided signatures to the tx @@ -478,8 +478,8 @@ impl Redelegate { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { - transaction::get_msg_to_sign(&self.0) + pub fn get_sign_bytes(&self) -> Vec { + transaction::get_sign_bytes(&self.0) } /// Attach the provided signatures to the tx diff --git a/light_sdk/src/transaction/transfer.rs b/light_sdk/src/transaction/transfer.rs index 52fb8e2bcb..92f64e58a5 100644 --- a/light_sdk/src/transaction/transfer.rs +++ b/light_sdk/src/transaction/transfer.rs @@ -41,8 +41,8 @@ impl Transfer { } /// Get the bytes to sign for the given transaction - pub fn get_msg_to_sign(&self) -> Vec { - transaction::get_msg_to_sign(&self.0) + pub fn get_sign_bytes(&self) -> Vec { + transaction::get_sign_bytes(&self.0) } /// Attach the provided signatures to the tx diff --git a/light_sdk/src/transaction/wrapper.rs b/light_sdk/src/transaction/wrapper.rs index 0475823dea..e9540f23f5 100644 --- a/light_sdk/src/transaction/wrapper.rs +++ b/light_sdk/src/transaction/wrapper.rs @@ -31,7 +31,7 @@ impl Wrapper { } /// Returns the message to be signed for this transaction - pub fn get_msg_to_sign(mut self) -> (Self, Vec) { + pub fn get_sign_bytes(mut self) -> (Self, Vec) { self.0.protocol_filter(); let msg = self.0.sechashes(); @@ -54,9 +54,7 @@ impl Wrapper { } /// Validate this wrapper transaction - pub fn validate_tx( - &self, - ) -> Result, TxError> { + pub fn validate_tx(&self) -> Result, TxError> { self.0.validate_tx() } } From 32a2f256ee1d096c781d2138e44d882d52585e77 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 19 Dec 2023 17:02:14 +0100 Subject: [PATCH 186/216] [feat]: Added rpc functions to light sdk --- Cargo.lock | 3 + light_sdk/Cargo.toml | 5 +- light_sdk/src/reading/mod.rs | 681 ++++++++++++++++++++++++++- light_sdk/src/transaction/mod.rs | 1 + light_sdk/src/transaction/pos.rs | 2 - light_sdk/src/transaction/wrapper.rs | 2 - 6 files changed, 686 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 386b3b1266..e680d00a90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3828,7 +3828,10 @@ dependencies = [ "borsh-ext", "ibc", "namada_core", + "namada_sdk", "prost 0.12.1", + "tendermint-config", + "tokio", ] [[package]] diff --git a/light_sdk/Cargo.toml b/light_sdk/Cargo.toml index 8d7a9c433b..ec4e0489cc 100644 --- a/light_sdk/Cargo.toml +++ b/light_sdk/Cargo.toml @@ -18,6 +18,9 @@ version.workspace = true [dependencies] borsh.workspace = true borsh-ext.workspace = true +ibc = "0.47.0" namada_core = {path = "../core"} +namada_sdk = {path = "../sdk"} prost.workspace = true -ibc = "0.47.0" +tendermint-config.workspace = true +tokio = {workspace = true, features = ["rt"]} diff --git a/light_sdk/src/reading/mod.rs b/light_sdk/src/reading/mod.rs index 63d85a590a..5736c0af0a 100644 --- a/light_sdk/src/reading/mod.rs +++ b/light_sdk/src/reading/mod.rs @@ -1,4 +1,679 @@ -// query tx status -pub mod tx {} +use std::str::FromStr; -pub mod account {} +use namada_core::ledger::storage::LastBlock; +use namada_core::types::address::Address; +use namada_core::types::storage::BlockResults; +use namada_core::types::token; +use namada_sdk::error::Error; +use namada_sdk::queries::RPC; +use namada_sdk::tendermint_rpc::HttpClient; +use namada_sdk::rpc; +use tendermint_config::net::Address as TendermintAddress; +use tokio::runtime::Runtime; + +/// Query the address of the native token +pub fn query_native_token(tendermint_addr: &str) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::query_native_token(&client)) +} + +/// Query the last committed block, if any. +pub fn query_block(tendermint_addr: &str) -> Result, Error> { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::query_block(&client)) +} + +/// Query the results of the last committed block +pub fn query_results( + tendermint_addr: &str, +) -> Result, Error> { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::query_results(&client)) +} + +pub mod tx { + use namada_sdk::events::Event; + use namada_sdk::rpc::{TxEventQuery, TxResponse}; + + use super::*; + + /// Call the corresponding `tx_event_query` RPC method, to fetch + /// the current status of a transation. + pub fn query_tx_events( + tendermint_addr: &str, + tx_event_query: TxEventQuery<'_>, + ) -> std::result::Result, Error> { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on( + rpc::query_tx_events(&client, tx_event_query) + ) .map_err(|e| Error::Other(e.to_string())) + } + + /// Dry run a transaction + pub fn dry_run_tx( + tendermint_addr: &str, + tx_bytes: Vec, + ) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let (data, height, prove) = (Some(tx_bytes), None, false); + let rt = Runtime::new().unwrap(); + let result = rt + .block_on(RPC.shell().dry_run_tx(&client, data, height, prove)) + .map_err(|err| { + Error::from(namada_sdk::error::QueryError::NoResponse( + err.to_string(), + )) + })? + .data; + println!("Dry-run result: {}", result); + Ok(result) + } + + /// Lookup the full response accompanying the specified transaction event + pub fn query_tx_response( + tendermint_addr: &str, + tx_query: TxEventQuery<'_>, + ) -> Result { + let client = + HttpClient::new( + TendermintAddress::from_str(tendermint_addr).map_err(|e| Error::Other(e.to_string()))? + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::query_tx_response(&client, tx_query)).map_err(|e| Error::Other(e.to_string())) + } + + /// Query the status of a given transaction. + pub async fn query_tx_status( + tendermint_addr: &str, + status: TxEventQuery<'_>, + ) -> Result { + let maybe_event = query_tx_events(tendermint_addr, status)?; + if let Some(e) = maybe_event { + Ok(e) + } else { + Err(match status { + TxEventQuery::Accepted(_) => { + Error::Tx(namada_sdk::error::TxError::AcceptTimeout) + } + TxEventQuery::Applied(_) => { + Error::Tx(namada_sdk::error::TxError::AppliedTimeout) + } + }) + } + } +} + +pub mod governance { + use namada_core::ledger::governance::parameters::GovernanceParameters; + use namada_core::ledger::governance::storage::proposal::StorageProposal; + use namada_core::ledger::governance::utils::Vote; + + use super::*; + + /// Query proposal by Id + pub fn query_proposal_by_id( + tendermint_addr: &str, + proposal_id: u64, + ) -> Result, Error> { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::query_proposal_by_id(&client, proposal_id)) + } + + /// Get the givernance parameters + pub fn query_governance_parameters( + tendermint_addr: &str, + ) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + Ok(rt.block_on(rpc::query_governance_parameters(&client))) + } + + /// Get the givernance parameters + pub fn query_proposal_votes( + tendermint_addr: &str, + proposal_id: u64, + ) -> Result, Error> { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::query_proposal_votes(&client, proposal_id)) + } +} + +pub mod pos { + use std::collections::{BTreeSet, HashMap, HashSet}; + + use namada_core::types::address::Address; + use namada_core::types::key::common; + use namada_core::types::storage::{BlockHeight, Epoch}; + use namada_sdk::proof_of_stake::types::{ + BondsAndUnbondsDetails, CommissionPair, ValidatorMetaData, + ValidatorState, + }; + use namada_sdk::proof_of_stake::PosParams; + use namada_sdk::queries::vp::pos::EnrichedBondsAndUnbondsDetails; + + use super::*; + + /// Query the epoch of the last committed block + pub fn query_epoch(tendermint_addr: &str) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::query_epoch(&client)) + } + + /// Query the epoch of the given block height, if it exists. + /// Will return none if the input block height is greater than + /// the latest committed block height. + pub fn query_epoch_at_height( + tendermint_addr: &str, + height: BlockHeight, + ) -> Result, Error> { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::query_epoch_at_height(&client, height)) + } + + /// Check if the given address is a known validator. + pub fn is_validator( + tendermint_addr: &str, + address: &Address, + ) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::is_validator(&client, address)) + } + + /// Check if a given address is a known delegator + pub fn is_delegator( + tendermint_addr: &str, + address: &Address, + ) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::is_delegator(&client, address)) + } + + /// Check if a given address is a known delegator at the given epoch + pub fn is_delegator_at( + tendermint_addr: &str, + address: &Address, + epoch: Epoch, + ) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::is_delegator_at(&client, address, epoch)) + } + + /// Get the set of consensus keys registered in the network + pub fn get_consensus_keys( + tendermint_addr: &str, + ) -> Result, Error> { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::get_consensus_keys(&client)) + } + + /// Get the PoS parameters + pub fn get_pos_params(tendermint_addr: &str) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::get_pos_params(&client)) + } + + /// Get all validators in the given epoch + pub fn get_all_validators( + tendermint_addr: &str, + epoch: Epoch, + ) -> Result, Error> { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::get_all_validators(&client, epoch)) + } + + /// Get the total staked tokens in the given epoch + pub fn get_total_staked_tokens( + tendermint_addr: &str, + epoch: Epoch, + ) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::get_total_staked_tokens(&client, epoch)) + } + + /// Get the given validator's stake at the given epoch + pub fn get_validator_stake( + tendermint_addr: &str, + epoch: Epoch, + validator: &Address, + ) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::get_validator_stake(&client, epoch, validator)) + } + + /// Query and return a validator's state + pub fn get_validator_state( + tendermint_addr: &str, + validator: &Address, + epoch: Option, + ) -> Result, Error> { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::get_validator_state(&client, validator, epoch)) + } + + /// Get the delegator's delegation + pub fn get_delegators_delegation( + tendermint_addr: &str, + address: &Address, + ) -> Result, Error> { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::get_delegators_delegation(&client, address)) + } + + /// Get the delegator's delegation at some epoh + pub fn get_delegators_delegation_at( + tendermint_addr: &str, + address: &Address, + epoch: Epoch, + ) -> Result, Error> { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::get_delegators_delegation_at(&client, address, epoch)) + } + + /// Query and return validator's commission rate and max commission rate + /// change per epoch + pub fn query_commission_rate( + tendermint_addr: &str, + validator: &Address, + epoch: Option, + ) -> Result, Error> { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::query_commission_rate(&client, validator, epoch)) + } + + /// Query and return validator's metadata, including the commission rate and + /// max commission rate change + pub fn query_metadata( + tendermint_addr: &str, + validator: &Address, + epoch: Option, + ) -> Result<(Option, Option), Error> + { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::query_metadata(&client, validator, epoch)) + } + + /// Query and return the incoming redelegation epoch for a given pair of + /// source validator and delegator, if there is any. + pub fn query_incoming_redelegations( + tendermint_addr: &str, + src_validator: &Address, + delegator: &Address, + ) -> Result, Error> { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::query_incoming_redelegations( + &client, + src_validator, + delegator, + )) + } + + /// Query a validator's bonds for a given epoch + pub fn query_bond( + tendermint_addr: &str, + source: &Address, + validator: &Address, + epoch: Option, + ) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::query_bond(&client, source, validator, epoch)) + } + + /// Query a validator's unbonds for a given epoch + pub fn query_and_print_unbonds( + tendermint_addr: &str, + source: &Address, + validator: &Address, + ) -> Result<(), Error> { + let rt = Runtime::new().unwrap(); + rt.block_on(async { + let unbonds = + query_unbond_with_slashing(tendermint_addr, source, validator)?; + let current_epoch = query_epoch(tendermint_addr)?; + + let mut total_withdrawable = token::Amount::zero(); + let mut not_yet_withdrawable = + HashMap::::new(); + for ((_start_epoch, withdraw_epoch), amount) in unbonds.into_iter() + { + if withdraw_epoch <= current_epoch { + total_withdrawable += amount; + } else { + let withdrawable_amount = + not_yet_withdrawable.entry(withdraw_epoch).or_default(); + *withdrawable_amount += amount; + } + } + if !total_withdrawable.is_zero() { + println!( + "Total withdrawable now: {}.", + total_withdrawable.to_string_native() + ); + } + if !not_yet_withdrawable.is_empty() { + println!("Current epoch: {current_epoch}.") + } + for (withdraw_epoch, amount) in not_yet_withdrawable { + println!( + "Amount {} withdrawable starting from epoch \ + {withdraw_epoch}.", + amount.to_string_native() + ); + } + Ok(()) + }) + } + + /// Query withdrawable tokens in a validator account for a given epoch + pub fn query_withdrawable_tokens( + tendermint_addr: &str, + bond_source: &Address, + validator: &Address, + epoch: Option, + ) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::query_withdrawable_tokens( + &client, + bond_source, + validator, + epoch, + )) + } + + /// Query all unbonds for a validator, applying slashes + pub fn query_unbond_with_slashing( + tendermint_addr: &str, + source: &Address, + validator: &Address, + ) -> Result, Error> { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::query_unbond_with_slashing(&client, source, validator)) + } + + /// Get the bond amount at the given epoch + pub fn get_bond_amount_at( + tendermint_addr: &str, + delegator: &Address, + validator: &Address, + epoch: Epoch, + ) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::get_bond_amount_at( + &client, delegator, validator, epoch, + )) + } + + /// Get bonds and unbonds with all details (slashes and rewards, if any) + /// grouped by their bond IDs. + pub fn bonds_and_unbonds( + tendermint_addr: &str, + source: &Option
, + validator: &Option
, + ) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::bonds_and_unbonds(&client, source, validator)) + } + + /// Get bonds and unbonds with all details (slashes and rewards, if any) + /// grouped by their bond IDs, enriched with extra information calculated + /// from the data. + pub fn enriched_bonds_and_unbonds( + tendermint_addr: &str, + current_epoch: Epoch, + source: &Option
, + validator: &Option
, + ) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::enriched_bonds_and_unbonds( + &client, + current_epoch, + source, + validator, + )) + } +} + +pub mod account { + use namada_core::types::account::Account; + use namada_core::types::address::Address; + use namada_core::types::key::common; + + use super::*; + + /// Query token amount of owner. + pub fn get_token_balance( + tendermint_addr: &str, + token: &Address, + owner: &Address, + ) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::get_token_balance(&client, token, owner)) + } + + /// Check if the address exists on chain. Established address exists if it + /// has a stored validity predicate. Implicit and internal addresses + /// always return true. + pub fn known_address( + tendermint_addr: &str, + address: &Address, + ) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::known_address(&client, address)) + } + + /// Query the accunt substorage space of an address + pub fn get_account_info( + tendermint_addr: &str, + owner: &Address, + ) -> Result, Error> { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::get_account_info(&client, owner)) + } + + /// Query if the public_key is revealed + pub fn is_public_key_revealed( + tendermint_addr: &str, + owner: &Address, + ) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::is_public_key_revealed(&client, owner)) + } + + /// Query an account substorage at a specific index + pub fn get_public_key_at( + tendermint_addr: &str, + owner: &Address, + index: u8, + ) -> Result, Error> { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::get_public_key_at(&client, owner, index)) + } +} + +pub mod pgf { + use super::*; + + /// Check if the given address is a pgf steward. + pub async fn is_steward(tendermint_addr: &str, address: &Address) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + Ok(rt.block_on(rpc::is_steward(&client, address))) + } +} diff --git a/light_sdk/src/transaction/mod.rs b/light_sdk/src/transaction/mod.rs index c119d8f18f..c77ea1009c 100644 --- a/light_sdk/src/transaction/mod.rs +++ b/light_sdk/src/transaction/mod.rs @@ -48,6 +48,7 @@ pub(in crate::transaction) fn attach_raw_signatures( signer: common::PublicKey, signature: common::Signature, ) -> Tx { + tx.protocol_filter(); tx.add_section(Section::Signature(Signature { targets: vec![tx.raw_header_hash()], signer: Signer::PubKeys(vec![signer]), diff --git a/light_sdk/src/transaction/pos.rs b/light_sdk/src/transaction/pos.rs index cae610cf05..9a2051fb4d 100644 --- a/light_sdk/src/transaction/pos.rs +++ b/light_sdk/src/transaction/pos.rs @@ -421,8 +421,6 @@ impl Withdraw { source: Option
, args: GlobalArgs, ) -> Self { - // FIXME: request the correct type directly in the args instead of - // rebuilding it let init_proposal = namada_core::types::transaction::pos::Withdraw { validator, source, diff --git a/light_sdk/src/transaction/wrapper.rs b/light_sdk/src/transaction/wrapper.rs index e9540f23f5..0d68a11b5d 100644 --- a/light_sdk/src/transaction/wrapper.rs +++ b/light_sdk/src/transaction/wrapper.rs @@ -4,8 +4,6 @@ use namada_core::types::key::common; use namada_core::types::storage::Epoch; use namada_core::types::transaction::{Fee, GasLimit}; -// FIXME: instead of this file would be better to call finalize(args) on every -// transaction type that would produce a signed wrapper with the provided args pub struct Wrapper(Tx); impl Wrapper { From 6d8a7eaf99bd87b3fe564c7544859e9d2a961465 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 20 Dec 2023 13:48:14 +0100 Subject: [PATCH 187/216] Some small type simplifications in the reading api --- light_sdk/src/reading/mod.rs | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/light_sdk/src/reading/mod.rs b/light_sdk/src/reading/mod.rs index 5736c0af0a..c96aa9456f 100644 --- a/light_sdk/src/reading/mod.rs +++ b/light_sdk/src/reading/mod.rs @@ -56,14 +56,15 @@ pub mod tx { /// the current status of a transation. pub fn query_tx_events( tendermint_addr: &str, - tx_event_query: TxEventQuery<'_>, - ) -> std::result::Result, Error> { + tx_hash: &str, + ) -> Result, Error> { let client = HttpClient::new( TendermintAddress::from_str(tendermint_addr) .map_err(|e| Error::Other(e.to_string()))?, ) .map_err(|e| Error::Other(e.to_string()))?; let rt = Runtime::new().unwrap(); + let tx_event_query= TxEventQuery::Applied(tx_hash); rt.block_on( rpc::query_tx_events(&client, tx_event_query) ) .map_err(|e| Error::Other(e.to_string())) @@ -96,13 +97,14 @@ pub mod tx { /// Lookup the full response accompanying the specified transaction event pub fn query_tx_response( tendermint_addr: &str, - tx_query: TxEventQuery<'_>, + tx_hash: &str, ) -> Result { let client = HttpClient::new( TendermintAddress::from_str(tendermint_addr).map_err(|e| Error::Other(e.to_string()))? ) .map_err(|e| Error::Other(e.to_string()))?; + let tx_query = TxEventQuery::Applied(tx_hash); let rt = Runtime::new().unwrap(); rt.block_on(rpc::query_tx_response(&client, tx_query)).map_err(|e| Error::Other(e.to_string())) } @@ -110,20 +112,13 @@ pub mod tx { /// Query the status of a given transaction. pub async fn query_tx_status( tendermint_addr: &str, - status: TxEventQuery<'_>, + tx_hash: &str, ) -> Result { - let maybe_event = query_tx_events(tendermint_addr, status)?; + let maybe_event = query_tx_events(tendermint_addr, tx_hash)?; if let Some(e) = maybe_event { Ok(e) } else { - Err(match status { - TxEventQuery::Accepted(_) => { - Error::Tx(namada_sdk::error::TxError::AcceptTimeout) - } - TxEventQuery::Applied(_) => { - Error::Tx(namada_sdk::error::TxError::AppliedTimeout) - } - }) + Err(Error::Tx(namada_sdk::error::TxError::AppliedTimeout)) } } } From ae486a7af8332a6f59e8832578f3c42aa4f26dda Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 20 Dec 2023 15:01:29 +0100 Subject: [PATCH 188/216] Updates docstrings and fixes clippy --- light_sdk/src/lib.rs | 114 ++++-------------------- light_sdk/src/reading/mod.rs | 27 +++--- light_sdk/src/transaction/account.rs | 4 + light_sdk/src/transaction/governance.rs | 3 + light_sdk/src/transaction/ibc.rs | 1 + light_sdk/src/transaction/mod.rs | 1 - light_sdk/src/transaction/pgf.rs | 2 + light_sdk/src/transaction/pos.rs | 11 +++ light_sdk/src/transaction/transfer.rs | 1 + light_sdk/src/transaction/wrapper.rs | 1 + 10 files changed, 54 insertions(+), 111 deletions(-) diff --git a/light_sdk/src/lib.rs b/light_sdk/src/lib.rs index beb6aa8fc9..317eaada4c 100644 --- a/light_sdk/src/lib.rs +++ b/light_sdk/src/lib.rs @@ -1,101 +1,19 @@ -// tx status -// block height -// account balance -// anything else if needed -// pub mod reading; - -// either sync or async tx submission - prints tx hashes that can be used for -// tracking pub mod writing; -// pub mod wallet; - -// ==== - -// SDK for building transactions without having to pass in an entire Namada -// chain -// -// Requirements: -// Don't try to refactor the existing sdk - this shouldn't cause any merge -// conflicts with anything Minimal dependencies - no filesystem, no -// multi-threading, no IO, no wasm decoding core with default features turned -// off seems fine No lifetimes or abstract types -// it should be dump-ass simple to use this from other languages or contexts -// Callers should never have to worry about TxType::Raw or TxType::Wrapper - -// this should be hidden in the implementation No usage of async -// None of this signing code requires any async behavior - it's crazy to force -// all callers into async -// -// Proposed Flow: -// the crate should expose 1 struct and an implementation for that struct for -// every transaction type every struct (ie Bond) should have these functions -// new() - to create a new type of Bond - it takes all parameters, like chain_id -// (this will lead to duplication, which is desired for a simple API (if desired -// the caller can build their own TxFactory)) sign_bytes() - the bytes that need -// to be signed by the signer the crate should expose 1 struct and an -// implementation for a wrapper transaction new() -// should take a correctly signed inner tx (this is not enforced but rather -// documented, you can use this API to create garbage) takes the inner tx & the -// inner signature should take all the fields that are required by the wrapper -// sign_bytes() - the bytes that need to be signed by the signer -// -// Future Development: -// !!! AVOID ABSTRACT TYPES (that a caller needs to pass in) or LIFETIMES (in -// the caller api) LIKE THE PLAGUE !!! around this core light_sdk we can build -// more complex features get data via helper functions from a connected node -// can have an query endpoint that just takes a Tendermint RPC node and allows -// me to query it verify signatures -// support multi-sig signers -// dry-run these transactions -// ALWAYS EXPOSE SYNC AND ASYNC FUNCTIONALITY -// never force callers into using async - we must always support an API that -// synchronous none of this extra stuff should leak into the core -// need to be able to import the core to iOS or other languages without complex -// dependencies key backends -// file based key backends that callers can use -// libudev based key backends that call out to HSMs -// -// Questions: -// Can a single wrapper contain more than 1 inner transaction? Yes but it will -// not be executed (just extra payload for which gas is paid) Can the signer of -// the inner tx be different than of the wrapper transaction? Yes it can -// Is the signature of the outer transaction dependent on the signature of the -// inner one? Not at all, we only sign headers of transactions, so the inner -// signature is not part of the message that is signed for the wrapper -// How do the tags work again? Can I only sign over the tags and not the -// wasm_hash? If we need wasm_hashes, those should be saved as constants in the -// binary and a caller can decide to pass in their own wasm hashes or load the -// constants. -// -// MAINNET_BOND_WASM_HASH: &str = "mainnet_wasm_hash"; -// Bond::new("wasm_hash"); -// Bond::new(MAINNET_BOND_WASM_HASH) - -// need a function sign_bytes() that just returns me the bytes to sign -// need a function that takes a transaction and some sign_bytes and forms a -// submittable tx from them this is roughly the API that we want -// ```rust -// let keypair = gen_keypair(); -// -// let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( -// Fee { -// amount_per_gas_unit: 0.into(), -// token: shell.wl_storage.storage.native_token.clone(), -// }, -// keypair.ref_to(), -// Epoch(0), -// 0.into(), -// None, -// )))); -// wrapper.header.chain_id = shell.chain_id.clone(); -// wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); -// wrapper.set_data(Data::new( -// "Encrypted transaction data".as_bytes().to_owned(), -// )); -// wrapper.add_section(Section::Signature(Signature::new( -// wrapper.sechashes(), -// [(0, keypair)].into_iter().collect(), -// None, -// ))); -// ``` +//! The Namada light SDK is a simplified version of the SDK aimed at making +//! interaction with the protocol easier and faster. The library is developed +//! with ease-of-use and interoperability in mind so that it should be possible +//! to wrap it for usage in an FFI context. +//! +//! The [`namada_core`] crate of Namada is also re-exported to allow access to +//! its types. +//! +//! # Structure +//! +//! This SDK is divided into three modules: +//! +//! - transaction: contains functions to construct all the transactions +//! currently supported by the protocol +//! - reading: exposes queries to retrieve data from a Namada node +//! - writing: TO BE DONE pub mod reading; pub mod transaction; diff --git a/light_sdk/src/reading/mod.rs b/light_sdk/src/reading/mod.rs index c96aa9456f..96a9e2754e 100644 --- a/light_sdk/src/reading/mod.rs +++ b/light_sdk/src/reading/mod.rs @@ -6,8 +6,8 @@ use namada_core::types::storage::BlockResults; use namada_core::types::token; use namada_sdk::error::Error; use namada_sdk::queries::RPC; -use namada_sdk::tendermint_rpc::HttpClient; use namada_sdk::rpc; +use namada_sdk::tendermint_rpc::HttpClient; use tendermint_config::net::Address as TendermintAddress; use tokio::runtime::Runtime; @@ -64,10 +64,9 @@ pub mod tx { ) .map_err(|e| Error::Other(e.to_string()))?; let rt = Runtime::new().unwrap(); - let tx_event_query= TxEventQuery::Applied(tx_hash); - rt.block_on( - rpc::query_tx_events(&client, tx_event_query) - ) .map_err(|e| Error::Other(e.to_string())) + let tx_event_query = TxEventQuery::Applied(tx_hash); + rt.block_on(rpc::query_tx_events(&client, tx_event_query)) + .map_err(|e| Error::Other(e.to_string())) } /// Dry run a transaction @@ -99,14 +98,15 @@ pub mod tx { tendermint_addr: &str, tx_hash: &str, ) -> Result { - let client = - HttpClient::new( - TendermintAddress::from_str(tendermint_addr).map_err(|e| Error::Other(e.to_string()))? - ) - .map_err(|e| Error::Other(e.to_string()))?; + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; let tx_query = TxEventQuery::Applied(tx_hash); let rt = Runtime::new().unwrap(); - rt.block_on(rpc::query_tx_response(&client, tx_query)).map_err(|e| Error::Other(e.to_string())) + rt.block_on(rpc::query_tx_response(&client, tx_query)) + .map_err(|e| Error::Other(e.to_string())) } /// Query the status of a given transaction. @@ -662,7 +662,10 @@ pub mod pgf { use super::*; /// Check if the given address is a pgf steward. - pub async fn is_steward(tendermint_addr: &str, address: &Address) -> Result { + pub async fn is_steward( + tendermint_addr: &str, + address: &Address, + ) -> Result { let client = HttpClient::new( TendermintAddress::from_str(tendermint_addr) .map_err(|e| Error::Other(e.to_string()))?, diff --git a/light_sdk/src/transaction/account.rs b/light_sdk/src/transaction/account.rs index 971ba0d6ab..7768a321b9 100644 --- a/light_sdk/src/transaction/account.rs +++ b/light_sdk/src/transaction/account.rs @@ -10,6 +10,7 @@ const TX_INIT_ACCOUNT_WASM: &str = "tx_init_account.wasm"; const TX_REVEAL_PK_WASM: &str = "tx_reveal_pk.wasm"; const TX_UPDATE_ACCOUNT_WASM: &str = "tx_update_account.wasm"; +/// Transaction to initialize an established account pub struct InitAccount(Tx); impl InitAccount { @@ -51,6 +52,8 @@ impl InitAccount { } } +/// Transaction to reveal a public key to the ledger to validate signatures of +/// an implicit account pub struct RevealPk(pub Tx); impl RevealPk { @@ -80,6 +83,7 @@ impl RevealPk { } } +/// Transaction to update the parameters of an established account pub struct UpdateAccount(Tx); impl UpdateAccount { diff --git a/light_sdk/src/transaction/governance.rs b/light_sdk/src/transaction/governance.rs index 2a339704d4..357d24973c 100644 --- a/light_sdk/src/transaction/governance.rs +++ b/light_sdk/src/transaction/governance.rs @@ -12,10 +12,12 @@ use crate::transaction; const TX_INIT_PROPOSAL_WASM: &str = "tx_init_proposal.wasm"; const TX_VOTE_PROPOSAL: &str = "tx_vote_proposal.wasm"; +/// Transaction to initialize a governance proposal pub struct InitProposal(Tx); impl InitProposal { /// Build a raw InitProposal transaction from the given parameters + #[allow(clippy::too_many_arguments)] pub fn new( id: Option, content: Hash, @@ -61,6 +63,7 @@ impl InitProposal { } } +/// Transaction to vote on a governance proposal pub struct VoteProposal(Tx); impl VoteProposal { diff --git a/light_sdk/src/transaction/ibc.rs b/light_sdk/src/transaction/ibc.rs index 39b8372e21..df30e7bebb 100644 --- a/light_sdk/src/transaction/ibc.rs +++ b/light_sdk/src/transaction/ibc.rs @@ -10,6 +10,7 @@ use crate::transaction; const TX_IBC_WASM: &str = "tx_ibc.wasm"; +/// An IBC transfer pub struct IbcTransfer(Tx); impl IbcTransfer { diff --git a/light_sdk/src/transaction/mod.rs b/light_sdk/src/transaction/mod.rs index c77ea1009c..df3ca44891 100644 --- a/light_sdk/src/transaction/mod.rs +++ b/light_sdk/src/transaction/mod.rs @@ -15,7 +15,6 @@ pub mod transfer; pub mod wrapper; /// Generic arguments required to construct a transaction -#[repr(C)] pub struct GlobalArgs { pub expiration: Option, pub code_hash: Hash, diff --git a/light_sdk/src/transaction/pgf.rs b/light_sdk/src/transaction/pgf.rs index 208454329b..c93e3b9079 100644 --- a/light_sdk/src/transaction/pgf.rs +++ b/light_sdk/src/transaction/pgf.rs @@ -12,6 +12,7 @@ use crate::transaction; const TX_RESIGN_STEWARD: &str = "tx_resign_steward.wasm"; const TX_UPDATE_STEWARD_COMMISSION: &str = "tx_update_steward_commission.wasm"; +/// A transaction to resign from stewarding pgf pub struct ResignSteward(Tx); impl ResignSteward { @@ -41,6 +42,7 @@ impl ResignSteward { } } +/// Transaction to update a pgf steward's commission rate pub struct UpdateStewardCommission(Tx); impl UpdateStewardCommission { diff --git a/light_sdk/src/transaction/pos.rs b/light_sdk/src/transaction/pos.rs index 9a2051fb4d..3d09da5593 100644 --- a/light_sdk/src/transaction/pos.rs +++ b/light_sdk/src/transaction/pos.rs @@ -105,10 +105,12 @@ impl Unbond { } } +/// Transaction to initialize a new PoS validator pub struct InitValidator(Tx); impl InitValidator { /// Build a raw Init validator transaction from the given parameters + #[allow(clippy::too_many_arguments)] pub fn new( account_keys: Vec, threshold: u8, @@ -166,6 +168,7 @@ impl InitValidator { } } +/// Transaction to unjail a PoS validator pub struct UnjailValidator(Tx); impl UnjailValidator { @@ -195,6 +198,7 @@ impl UnjailValidator { } } +/// Transaction to deactivate a validator pub struct DeactivateValidator(Tx); impl DeactivateValidator { @@ -224,6 +228,7 @@ impl DeactivateValidator { } } +/// Transaction to reactivate a previously deactivated validator pub struct ReactivateValidator(Tx); impl ReactivateValidator { @@ -253,6 +258,7 @@ impl ReactivateValidator { } } +/// Transaction to claim PoS rewards pub struct ClaimRewards(Tx); impl ClaimRewards { @@ -291,6 +297,7 @@ impl ClaimRewards { } } +/// Transaction to change the validator's metadata pub struct ChangeMetaData(Tx); impl ChangeMetaData { @@ -338,6 +345,7 @@ impl ChangeMetaData { } } +/// Transaction to modify the validator's consensus key pub struct ChangeConsensusKey(Tx); impl ChangeConsensusKey { @@ -377,6 +385,7 @@ impl ChangeConsensusKey { } } +/// Transaction to modify the validator's commission rate pub struct ChangeCommission(Tx); impl ChangeCommission { @@ -412,6 +421,7 @@ impl ChangeCommission { } } +/// Transaction to withdraw previously unstaked funds pub struct Withdraw(Tx); impl Withdraw { @@ -450,6 +460,7 @@ impl Withdraw { } } +/// Transaction to redelegate pub struct Redelegate(Tx); impl Redelegate { diff --git a/light_sdk/src/transaction/transfer.rs b/light_sdk/src/transaction/transfer.rs index 92f64e58a5..dbc9b031d2 100644 --- a/light_sdk/src/transaction/transfer.rs +++ b/light_sdk/src/transaction/transfer.rs @@ -10,6 +10,7 @@ use crate::transaction; const TX_TRANSFER_WASM: &str = "tx_transfer.wasm"; +/// A transfer transaction pub struct Transfer(Tx); impl Transfer { diff --git a/light_sdk/src/transaction/wrapper.rs b/light_sdk/src/transaction/wrapper.rs index 0d68a11b5d..642d0398df 100644 --- a/light_sdk/src/transaction/wrapper.rs +++ b/light_sdk/src/transaction/wrapper.rs @@ -4,6 +4,7 @@ use namada_core::types::key::common; use namada_core::types::storage::Epoch; use namada_core::types::transaction::{Fee, GasLimit}; +#[allow(missing_docs)] pub struct Wrapper(Tx); impl Wrapper { From 3b93c627d16d6f8433bc64e44d1eda289f4e489e Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 20 Dec 2023 16:06:53 +0100 Subject: [PATCH 189/216] Updates types and imports after rebase --- light_sdk/src/transaction/ibc.rs | 4 ++-- light_sdk/src/transaction/pos.rs | 18 +++++++----------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/light_sdk/src/transaction/ibc.rs b/light_sdk/src/transaction/ibc.rs index df30e7bebb..13eb00ac4f 100644 --- a/light_sdk/src/transaction/ibc.rs +++ b/light_sdk/src/transaction/ibc.rs @@ -1,5 +1,5 @@ -use ibc::core::Msg; -pub use namada_core::ibc::applications::transfer::msgs::transfer::MsgTransfer; +pub use namada_core::ibc::apps::transfer::types::msgs::transfer::MsgTransfer; +use namada_core::ibc::primitives::Msg; use namada_core::proto::Tx; use namada_core::types::hash::Hash; use namada_core::types::key::common; diff --git a/light_sdk/src/transaction/pos.rs b/light_sdk/src/transaction/pos.rs index 3d09da5593..c4af55439b 100644 --- a/light_sdk/src/transaction/pos.rs +++ b/light_sdk/src/transaction/pos.rs @@ -12,7 +12,7 @@ use crate::transaction; const TX_BOND_WASM: &str = "tx_bond.wasm"; const TX_UNBOND_WASM: &str = "tx_unbond.wasm"; -const TX_INIT_VALIDATOR_WASM: &str = "tx_init_validator.wasm"; +const TX_BECOME_VALIDATOR_WASM: &str = "tx_become_validator.wasm"; const TX_UNJAIL_VALIDATOR_WASM: &str = "tx_unjail_validator.wasm"; const TX_DEACTIVATE_VALIDATOR_WASM: &str = "tx_deactivate_validator.wasm"; const TX_REACTIVATE_VALIDATOR_WASM: &str = "tx_reactivate_validator.wasm"; @@ -106,14 +106,13 @@ impl Unbond { } /// Transaction to initialize a new PoS validator -pub struct InitValidator(Tx); +pub struct BecomeValidator(Tx); -impl InitValidator { +impl BecomeValidator { /// Build a raw Init validator transaction from the given parameters #[allow(clippy::too_many_arguments)] pub fn new( - account_keys: Vec, - threshold: u8, + address: Address, consensus_key: common::PublicKey, eth_cold_key: secp256k1::PublicKey, eth_hot_key: secp256k1::PublicKey, @@ -124,13 +123,11 @@ impl InitValidator { description: Option, website: Option, discord_handle: Option, - validator_vp_code_hash: Hash, args: GlobalArgs, ) -> Self { let update_account = - namada_core::types::transaction::pos::InitValidator { - account_keys, - threshold, + namada_core::types::transaction::pos::BecomeValidator { + address, consensus_key, eth_cold_key, eth_hot_key, @@ -141,13 +138,12 @@ impl InitValidator { description, website, discord_handle, - validator_vp_code_hash, }; Self(transaction::build_tx( args, update_account, - TX_INIT_VALIDATOR_WASM.to_string(), + TX_BECOME_VALIDATOR_WASM.to_string(), )) } From 420e667850ec9ae73f9f2cd213c9749885949eab Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 20 Dec 2023 16:07:17 +0100 Subject: [PATCH 190/216] Imports `HttpClient` from `tendermint_rpc` --- Cargo.lock | 95 ++++++++++++++++++++++++++++-------- light_sdk/Cargo.toml | 1 + light_sdk/src/reading/mod.rs | 2 +- 3 files changed, 78 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e680d00a90..5a6ccfc69a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2899,6 +2899,34 @@ dependencies = [ "cc", ] +[[package]] +name = "ibc" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "184eb22140cb4143bbcf7ddc8fdfeb9cc058ef73a6066f8ea78162e69d3565d1" +dependencies = [ + "bytes", + "derive_more", + "displaydoc", + "ibc-derive 0.3.0", + "ibc-proto 0.37.1", + "ics23", + "num-traits 0.2.17", + "primitive-types", + "prost 0.12.3", + "serde 1.0.193", + "serde-json-wasm", + "serde_derive", + "sha2 0.10.8", + "subtle-encoding", + "tendermint", + "tendermint-light-client-verifier", + "tendermint-proto", + "time", + "tracing", + "uint", +] + [[package]] name = "ibc" version = "0.48.1" @@ -2909,7 +2937,7 @@ dependencies = [ "ibc-clients", "ibc-core", "ibc-core-host-cosmos", - "ibc-derive", + "ibc-derive 0.4.0", "ibc-primitives", ] @@ -2933,7 +2961,7 @@ dependencies = [ "derive_more", "displaydoc", "ibc-core", - "ibc-proto", + "ibc-proto 0.38.0", "primitive-types", "serde 1.0.193", "uint", @@ -2978,7 +3006,7 @@ dependencies = [ "ibc-core-commitment-types", "ibc-core-host-types", "ibc-primitives", - "ibc-proto", + "ibc-proto 0.38.0", "prost 0.12.3", "serde 1.0.193", "tendermint", @@ -3041,7 +3069,7 @@ dependencies = [ "ibc-core-connection-types", "ibc-core-host-types", "ibc-primitives", - "ibc-proto", + "ibc-proto 0.38.0", "prost 0.12.3", "serde 1.0.193", "sha2 0.10.8", @@ -3076,7 +3104,7 @@ dependencies = [ "ibc-core-commitment-types", "ibc-core-handler-types", "ibc-core-host-types", - "ibc-derive", + "ibc-derive 0.4.0", "ibc-primitives", "prost 0.12.3", "subtle-encoding", @@ -3094,7 +3122,7 @@ dependencies = [ "ibc-core-commitment-types", "ibc-core-host-types", "ibc-primitives", - "ibc-proto", + "ibc-proto 0.38.0", "prost 0.12.3", "serde 1.0.193", "subtle-encoding", @@ -3110,7 +3138,7 @@ dependencies = [ "derive_more", "displaydoc", "ibc-primitives", - "ibc-proto", + "ibc-proto 0.38.0", "ics23", "prost 0.12.3", "serde 1.0.193", @@ -3143,7 +3171,7 @@ dependencies = [ "ibc-core-commitment-types", "ibc-core-host-types", "ibc-primitives", - "ibc-proto", + "ibc-proto 0.38.0", "prost 0.12.3", "serde 1.0.193", "subtle-encoding", @@ -3181,7 +3209,7 @@ dependencies = [ "ibc-core-host-types", "ibc-core-router-types", "ibc-primitives", - "ibc-proto", + "ibc-proto 0.38.0", "prost 0.12.3", "serde 1.0.193", "subtle-encoding", @@ -3225,7 +3253,7 @@ dependencies = [ "ibc-core-handler-types", "ibc-core-host-types", "ibc-primitives", - "ibc-proto", + "ibc-proto 0.38.0", "prost 0.12.3", "serde 1.0.193", "sha2 0.10.8", @@ -3271,7 +3299,7 @@ dependencies = [ "displaydoc", "ibc-core-host-types", "ibc-primitives", - "ibc-proto", + "ibc-proto 0.38.0", "ics23", "prost 0.12.3", "serde 1.0.193", @@ -3279,6 +3307,18 @@ dependencies = [ "tendermint", ] +[[package]] +name = "ibc-derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92f1528535e9ca495badb76c143bdd4763c1c9d987f59d1f8b47963ba0c11674" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "ibc-derive" version = "0.4.0" @@ -3299,13 +3339,29 @@ checksum = "d5edea4685267fd68514c87e7aa3a62712340c4cff6903f088a9ab571428a08a" dependencies = [ "derive_more", "displaydoc", - "ibc-proto", + "ibc-proto 0.38.0", "prost 0.12.3", "serde 1.0.193", "tendermint", "time", ] +[[package]] +name = "ibc-proto" +version = "0.37.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63042806bb2f662ca1c68026231900cfe13361136ddfd0dd09bcb315056a22b8" +dependencies = [ + "base64 0.21.5", + "bytes", + "flex-error", + "ics23", + "prost 0.12.3", + "serde 1.0.193", + "subtle-encoding", + "tendermint-proto", +] + [[package]] name = "ibc-proto" version = "0.38.0" @@ -3331,8 +3387,8 @@ dependencies = [ "bytes", "derive_more", "displaydoc", - "ibc", - "ibc-proto", + "ibc 0.48.1", + "ibc-proto 0.38.0", "parking_lot", "primitive-types", "prost 0.12.3", @@ -3822,15 +3878,16 @@ dependencies = [ [[package]] name = "light_sdk" -version = "0.27.0" +version = "0.28.1" dependencies = [ "borsh", "borsh-ext", - "ibc", + "ibc 0.47.0", "namada_core", "namada_sdk", - "prost 0.12.1", + "prost 0.12.3", "tendermint-config", + "tendermint-rpc", "tokio", ] @@ -4329,8 +4386,8 @@ dependencies = [ "ethabi", "ethbridge-structs", "eyre", - "ibc", - "ibc-derive", + "ibc 0.48.1", + "ibc-derive 0.4.0", "ibc-testkit", "ics23", "impl-num-traits", diff --git a/light_sdk/Cargo.toml b/light_sdk/Cargo.toml index ec4e0489cc..07095179e8 100644 --- a/light_sdk/Cargo.toml +++ b/light_sdk/Cargo.toml @@ -23,4 +23,5 @@ namada_core = {path = "../core"} namada_sdk = {path = "../sdk"} prost.workspace = true tendermint-config.workspace = true +tendermint-rpc.workspace = true tokio = {workspace = true, features = ["rt"]} diff --git a/light_sdk/src/reading/mod.rs b/light_sdk/src/reading/mod.rs index 96a9e2754e..f2d97f4389 100644 --- a/light_sdk/src/reading/mod.rs +++ b/light_sdk/src/reading/mod.rs @@ -7,8 +7,8 @@ use namada_core::types::token; use namada_sdk::error::Error; use namada_sdk::queries::RPC; use namada_sdk::rpc; -use namada_sdk::tendermint_rpc::HttpClient; use tendermint_config::net::Address as TendermintAddress; +use tendermint_rpc::HttpClient; use tokio::runtime::Runtime; /// Query the address of the native token From 4a148f94dbeb6ca079a121d91bee354aa3949213 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 20 Dec 2023 16:09:47 +0100 Subject: [PATCH 191/216] Changelog #2220 --- .changelog/unreleased/SDK/2220-light-sdk.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 .changelog/unreleased/SDK/2220-light-sdk.md diff --git a/.changelog/unreleased/SDK/2220-light-sdk.md b/.changelog/unreleased/SDK/2220-light-sdk.md new file mode 100644 index 0000000000..76af7c6645 --- /dev/null +++ b/.changelog/unreleased/SDK/2220-light-sdk.md @@ -0,0 +1 @@ +- Added light sdk ([\#2220](https://github.com/anoma/namada/pull/2220)) \ No newline at end of file From 6b0fe4758c60b78735bed0d0b8a83e7b46b57f70 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 20 Dec 2023 16:28:36 +0100 Subject: [PATCH 192/216] Adds missing feature for tendermint-rpc in light sdk --- light_sdk/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/light_sdk/Cargo.toml b/light_sdk/Cargo.toml index 07095179e8..0b958ab3ad 100644 --- a/light_sdk/Cargo.toml +++ b/light_sdk/Cargo.toml @@ -23,5 +23,5 @@ namada_core = {path = "../core"} namada_sdk = {path = "../sdk"} prost.workspace = true tendermint-config.workspace = true -tendermint-rpc.workspace = true +tendermint-rpc = { worskpace = true, features = ["http-client"] } tokio = {workspace = true, features = ["rt"]} From 5c70e860633f526ebbde84f7cd6d1efd61562d9b Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 20 Dec 2023 20:12:08 +0100 Subject: [PATCH 193/216] Renames light sdk crate and adds description --- Cargo.lock | 30 +++++++++++++++--------------- light_sdk/Cargo.toml | 5 ++--- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5a6ccfc69a..83689aca32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3876,21 +3876,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "light_sdk" -version = "0.28.1" -dependencies = [ - "borsh", - "borsh-ext", - "ibc 0.47.0", - "namada_core", - "namada_sdk", - "prost 0.12.3", - "tendermint-config", - "tendermint-rpc", - "tokio", -] - [[package]] name = "linked-hash-map" version = "0.5.6" @@ -4462,6 +4447,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "namada_light_sdk" +version = "0.28.1" +dependencies = [ + "borsh", + "borsh-ext", + "ibc 0.47.0", + "namada_core", + "namada_sdk", + "prost 0.12.3", + "tendermint-config", + "tendermint-rpc", + "tokio", +] + [[package]] name = "namada_macros" version = "0.28.1" diff --git a/light_sdk/Cargo.toml b/light_sdk/Cargo.toml index 0b958ab3ad..63669efc45 100644 --- a/light_sdk/Cargo.toml +++ b/light_sdk/Cargo.toml @@ -1,5 +1,6 @@ [package] -name = "light_sdk" +name = "namada_light_sdk" +description = "A more simple version of the Namada SDK" resolver = "2" authors.workspace = true edition.workspace = true @@ -11,8 +12,6 @@ readme.workspace = true repository.workspace = true version.workspace = true - - # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] From 596a1eb3cfc85cf60ce6db04a5ef039226dee45f Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 20 Dec 2023 20:13:28 +0100 Subject: [PATCH 194/216] Splits light sdk queries into multiple files --- light_sdk/src/reading/account.rs | 79 ++++ light_sdk/src/reading/governance.rs | 46 ++ light_sdk/src/reading/mod.rs | 636 +--------------------------- light_sdk/src/reading/pgf.rs | 15 + light_sdk/src/reading/pos.rs | 366 ++++++++++++++++ light_sdk/src/reading/tx.rs | 73 ++++ 6 files changed, 585 insertions(+), 630 deletions(-) create mode 100644 light_sdk/src/reading/account.rs create mode 100644 light_sdk/src/reading/governance.rs create mode 100644 light_sdk/src/reading/pgf.rs create mode 100644 light_sdk/src/reading/pos.rs create mode 100644 light_sdk/src/reading/tx.rs diff --git a/light_sdk/src/reading/account.rs b/light_sdk/src/reading/account.rs new file mode 100644 index 0000000000..920580dbbe --- /dev/null +++ b/light_sdk/src/reading/account.rs @@ -0,0 +1,79 @@ +use namada_core::types::account::Account; +use namada_core::types::address::Address; +use namada_core::types::key::common; + +use super::*; + +/// Query token amount of owner. +pub fn get_token_balance( + tendermint_addr: &str, + token: &Address, + owner: &Address, +) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::get_token_balance(&client, token, owner)) +} + +/// Check if the address exists on chain. Established address exists if it +/// has a stored validity predicate. Implicit and internal addresses +/// always return true. +pub fn known_address( + tendermint_addr: &str, + address: &Address, +) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::known_address(&client, address)) +} + +/// Query the accunt substorage space of an address +pub fn get_account_info( + tendermint_addr: &str, + owner: &Address, +) -> Result, Error> { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::get_account_info(&client, owner)) +} + +/// Query if the public_key is revealed +pub fn is_public_key_revealed( + tendermint_addr: &str, + owner: &Address, +) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::is_public_key_revealed(&client, owner)) +} + +/// Query an account substorage at a specific index +pub fn get_public_key_at( + tendermint_addr: &str, + owner: &Address, + index: u8, +) -> Result, Error> { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::get_public_key_at(&client, owner, index)) +} diff --git a/light_sdk/src/reading/governance.rs b/light_sdk/src/reading/governance.rs new file mode 100644 index 0000000000..7f7399ef64 --- /dev/null +++ b/light_sdk/src/reading/governance.rs @@ -0,0 +1,46 @@ +use namada_core::ledger::governance::parameters::GovernanceParameters; +use namada_core::ledger::governance::storage::proposal::StorageProposal; +use namada_core::ledger::governance::utils::Vote; + +use super::*; + +/// Query proposal by Id +pub fn query_proposal_by_id( + tendermint_addr: &str, + proposal_id: u64, +) -> Result, Error> { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::query_proposal_by_id(&client, proposal_id)) +} + +/// Get the givernance parameters +pub fn query_governance_parameters( + tendermint_addr: &str, +) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + Ok(rt.block_on(rpc::query_governance_parameters(&client))) +} + +/// Get the givernance parameters +pub fn query_proposal_votes( + tendermint_addr: &str, + proposal_id: u64, +) -> Result, Error> { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::query_proposal_votes(&client, proposal_id)) +} diff --git a/light_sdk/src/reading/mod.rs b/light_sdk/src/reading/mod.rs index f2d97f4389..690ea074d4 100644 --- a/light_sdk/src/reading/mod.rs +++ b/light_sdk/src/reading/mod.rs @@ -11,6 +11,12 @@ use tendermint_config::net::Address as TendermintAddress; use tendermint_rpc::HttpClient; use tokio::runtime::Runtime; +pub mod account; +pub mod governance; +pub mod pgf; +pub mod pos; +pub mod tx; + /// Query the address of the native token pub fn query_native_token(tendermint_addr: &str) -> Result { let client = HttpClient::new( @@ -45,633 +51,3 @@ pub fn query_results( let rt = Runtime::new().unwrap(); rt.block_on(rpc::query_results(&client)) } - -pub mod tx { - use namada_sdk::events::Event; - use namada_sdk::rpc::{TxEventQuery, TxResponse}; - - use super::*; - - /// Call the corresponding `tx_event_query` RPC method, to fetch - /// the current status of a transation. - pub fn query_tx_events( - tendermint_addr: &str, - tx_hash: &str, - ) -> Result, Error> { - let client = HttpClient::new( - TendermintAddress::from_str(tendermint_addr) - .map_err(|e| Error::Other(e.to_string()))?, - ) - .map_err(|e| Error::Other(e.to_string()))?; - let rt = Runtime::new().unwrap(); - let tx_event_query = TxEventQuery::Applied(tx_hash); - rt.block_on(rpc::query_tx_events(&client, tx_event_query)) - .map_err(|e| Error::Other(e.to_string())) - } - - /// Dry run a transaction - pub fn dry_run_tx( - tendermint_addr: &str, - tx_bytes: Vec, - ) -> Result { - let client = HttpClient::new( - TendermintAddress::from_str(tendermint_addr) - .map_err(|e| Error::Other(e.to_string()))?, - ) - .map_err(|e| Error::Other(e.to_string()))?; - let (data, height, prove) = (Some(tx_bytes), None, false); - let rt = Runtime::new().unwrap(); - let result = rt - .block_on(RPC.shell().dry_run_tx(&client, data, height, prove)) - .map_err(|err| { - Error::from(namada_sdk::error::QueryError::NoResponse( - err.to_string(), - )) - })? - .data; - println!("Dry-run result: {}", result); - Ok(result) - } - - /// Lookup the full response accompanying the specified transaction event - pub fn query_tx_response( - tendermint_addr: &str, - tx_hash: &str, - ) -> Result { - let client = HttpClient::new( - TendermintAddress::from_str(tendermint_addr) - .map_err(|e| Error::Other(e.to_string()))?, - ) - .map_err(|e| Error::Other(e.to_string()))?; - let tx_query = TxEventQuery::Applied(tx_hash); - let rt = Runtime::new().unwrap(); - rt.block_on(rpc::query_tx_response(&client, tx_query)) - .map_err(|e| Error::Other(e.to_string())) - } - - /// Query the status of a given transaction. - pub async fn query_tx_status( - tendermint_addr: &str, - tx_hash: &str, - ) -> Result { - let maybe_event = query_tx_events(tendermint_addr, tx_hash)?; - if let Some(e) = maybe_event { - Ok(e) - } else { - Err(Error::Tx(namada_sdk::error::TxError::AppliedTimeout)) - } - } -} - -pub mod governance { - use namada_core::ledger::governance::parameters::GovernanceParameters; - use namada_core::ledger::governance::storage::proposal::StorageProposal; - use namada_core::ledger::governance::utils::Vote; - - use super::*; - - /// Query proposal by Id - pub fn query_proposal_by_id( - tendermint_addr: &str, - proposal_id: u64, - ) -> Result, Error> { - let client = HttpClient::new( - TendermintAddress::from_str(tendermint_addr) - .map_err(|e| Error::Other(e.to_string()))?, - ) - .map_err(|e| Error::Other(e.to_string()))?; - let rt = Runtime::new().unwrap(); - rt.block_on(rpc::query_proposal_by_id(&client, proposal_id)) - } - - /// Get the givernance parameters - pub fn query_governance_parameters( - tendermint_addr: &str, - ) -> Result { - let client = HttpClient::new( - TendermintAddress::from_str(tendermint_addr) - .map_err(|e| Error::Other(e.to_string()))?, - ) - .map_err(|e| Error::Other(e.to_string()))?; - let rt = Runtime::new().unwrap(); - Ok(rt.block_on(rpc::query_governance_parameters(&client))) - } - - /// Get the givernance parameters - pub fn query_proposal_votes( - tendermint_addr: &str, - proposal_id: u64, - ) -> Result, Error> { - let client = HttpClient::new( - TendermintAddress::from_str(tendermint_addr) - .map_err(|e| Error::Other(e.to_string()))?, - ) - .map_err(|e| Error::Other(e.to_string()))?; - let rt = Runtime::new().unwrap(); - rt.block_on(rpc::query_proposal_votes(&client, proposal_id)) - } -} - -pub mod pos { - use std::collections::{BTreeSet, HashMap, HashSet}; - - use namada_core::types::address::Address; - use namada_core::types::key::common; - use namada_core::types::storage::{BlockHeight, Epoch}; - use namada_sdk::proof_of_stake::types::{ - BondsAndUnbondsDetails, CommissionPair, ValidatorMetaData, - ValidatorState, - }; - use namada_sdk::proof_of_stake::PosParams; - use namada_sdk::queries::vp::pos::EnrichedBondsAndUnbondsDetails; - - use super::*; - - /// Query the epoch of the last committed block - pub fn query_epoch(tendermint_addr: &str) -> Result { - let client = HttpClient::new( - TendermintAddress::from_str(tendermint_addr) - .map_err(|e| Error::Other(e.to_string()))?, - ) - .map_err(|e| Error::Other(e.to_string()))?; - let rt = Runtime::new().unwrap(); - rt.block_on(rpc::query_epoch(&client)) - } - - /// Query the epoch of the given block height, if it exists. - /// Will return none if the input block height is greater than - /// the latest committed block height. - pub fn query_epoch_at_height( - tendermint_addr: &str, - height: BlockHeight, - ) -> Result, Error> { - let client = HttpClient::new( - TendermintAddress::from_str(tendermint_addr) - .map_err(|e| Error::Other(e.to_string()))?, - ) - .map_err(|e| Error::Other(e.to_string()))?; - let rt = Runtime::new().unwrap(); - rt.block_on(rpc::query_epoch_at_height(&client, height)) - } - - /// Check if the given address is a known validator. - pub fn is_validator( - tendermint_addr: &str, - address: &Address, - ) -> Result { - let client = HttpClient::new( - TendermintAddress::from_str(tendermint_addr) - .map_err(|e| Error::Other(e.to_string()))?, - ) - .map_err(|e| Error::Other(e.to_string()))?; - let rt = Runtime::new().unwrap(); - rt.block_on(rpc::is_validator(&client, address)) - } - - /// Check if a given address is a known delegator - pub fn is_delegator( - tendermint_addr: &str, - address: &Address, - ) -> Result { - let client = HttpClient::new( - TendermintAddress::from_str(tendermint_addr) - .map_err(|e| Error::Other(e.to_string()))?, - ) - .map_err(|e| Error::Other(e.to_string()))?; - let rt = Runtime::new().unwrap(); - rt.block_on(rpc::is_delegator(&client, address)) - } - - /// Check if a given address is a known delegator at the given epoch - pub fn is_delegator_at( - tendermint_addr: &str, - address: &Address, - epoch: Epoch, - ) -> Result { - let client = HttpClient::new( - TendermintAddress::from_str(tendermint_addr) - .map_err(|e| Error::Other(e.to_string()))?, - ) - .map_err(|e| Error::Other(e.to_string()))?; - let rt = Runtime::new().unwrap(); - rt.block_on(rpc::is_delegator_at(&client, address, epoch)) - } - - /// Get the set of consensus keys registered in the network - pub fn get_consensus_keys( - tendermint_addr: &str, - ) -> Result, Error> { - let client = HttpClient::new( - TendermintAddress::from_str(tendermint_addr) - .map_err(|e| Error::Other(e.to_string()))?, - ) - .map_err(|e| Error::Other(e.to_string()))?; - let rt = Runtime::new().unwrap(); - rt.block_on(rpc::get_consensus_keys(&client)) - } - - /// Get the PoS parameters - pub fn get_pos_params(tendermint_addr: &str) -> Result { - let client = HttpClient::new( - TendermintAddress::from_str(tendermint_addr) - .map_err(|e| Error::Other(e.to_string()))?, - ) - .map_err(|e| Error::Other(e.to_string()))?; - let rt = Runtime::new().unwrap(); - rt.block_on(rpc::get_pos_params(&client)) - } - - /// Get all validators in the given epoch - pub fn get_all_validators( - tendermint_addr: &str, - epoch: Epoch, - ) -> Result, Error> { - let client = HttpClient::new( - TendermintAddress::from_str(tendermint_addr) - .map_err(|e| Error::Other(e.to_string()))?, - ) - .map_err(|e| Error::Other(e.to_string()))?; - let rt = Runtime::new().unwrap(); - rt.block_on(rpc::get_all_validators(&client, epoch)) - } - - /// Get the total staked tokens in the given epoch - pub fn get_total_staked_tokens( - tendermint_addr: &str, - epoch: Epoch, - ) -> Result { - let client = HttpClient::new( - TendermintAddress::from_str(tendermint_addr) - .map_err(|e| Error::Other(e.to_string()))?, - ) - .map_err(|e| Error::Other(e.to_string()))?; - let rt = Runtime::new().unwrap(); - rt.block_on(rpc::get_total_staked_tokens(&client, epoch)) - } - - /// Get the given validator's stake at the given epoch - pub fn get_validator_stake( - tendermint_addr: &str, - epoch: Epoch, - validator: &Address, - ) -> Result { - let client = HttpClient::new( - TendermintAddress::from_str(tendermint_addr) - .map_err(|e| Error::Other(e.to_string()))?, - ) - .map_err(|e| Error::Other(e.to_string()))?; - let rt = Runtime::new().unwrap(); - rt.block_on(rpc::get_validator_stake(&client, epoch, validator)) - } - - /// Query and return a validator's state - pub fn get_validator_state( - tendermint_addr: &str, - validator: &Address, - epoch: Option, - ) -> Result, Error> { - let client = HttpClient::new( - TendermintAddress::from_str(tendermint_addr) - .map_err(|e| Error::Other(e.to_string()))?, - ) - .map_err(|e| Error::Other(e.to_string()))?; - let rt = Runtime::new().unwrap(); - rt.block_on(rpc::get_validator_state(&client, validator, epoch)) - } - - /// Get the delegator's delegation - pub fn get_delegators_delegation( - tendermint_addr: &str, - address: &Address, - ) -> Result, Error> { - let client = HttpClient::new( - TendermintAddress::from_str(tendermint_addr) - .map_err(|e| Error::Other(e.to_string()))?, - ) - .map_err(|e| Error::Other(e.to_string()))?; - let rt = Runtime::new().unwrap(); - rt.block_on(rpc::get_delegators_delegation(&client, address)) - } - - /// Get the delegator's delegation at some epoh - pub fn get_delegators_delegation_at( - tendermint_addr: &str, - address: &Address, - epoch: Epoch, - ) -> Result, Error> { - let client = HttpClient::new( - TendermintAddress::from_str(tendermint_addr) - .map_err(|e| Error::Other(e.to_string()))?, - ) - .map_err(|e| Error::Other(e.to_string()))?; - let rt = Runtime::new().unwrap(); - rt.block_on(rpc::get_delegators_delegation_at(&client, address, epoch)) - } - - /// Query and return validator's commission rate and max commission rate - /// change per epoch - pub fn query_commission_rate( - tendermint_addr: &str, - validator: &Address, - epoch: Option, - ) -> Result, Error> { - let client = HttpClient::new( - TendermintAddress::from_str(tendermint_addr) - .map_err(|e| Error::Other(e.to_string()))?, - ) - .map_err(|e| Error::Other(e.to_string()))?; - let rt = Runtime::new().unwrap(); - rt.block_on(rpc::query_commission_rate(&client, validator, epoch)) - } - - /// Query and return validator's metadata, including the commission rate and - /// max commission rate change - pub fn query_metadata( - tendermint_addr: &str, - validator: &Address, - epoch: Option, - ) -> Result<(Option, Option), Error> - { - let client = HttpClient::new( - TendermintAddress::from_str(tendermint_addr) - .map_err(|e| Error::Other(e.to_string()))?, - ) - .map_err(|e| Error::Other(e.to_string()))?; - let rt = Runtime::new().unwrap(); - rt.block_on(rpc::query_metadata(&client, validator, epoch)) - } - - /// Query and return the incoming redelegation epoch for a given pair of - /// source validator and delegator, if there is any. - pub fn query_incoming_redelegations( - tendermint_addr: &str, - src_validator: &Address, - delegator: &Address, - ) -> Result, Error> { - let client = HttpClient::new( - TendermintAddress::from_str(tendermint_addr) - .map_err(|e| Error::Other(e.to_string()))?, - ) - .map_err(|e| Error::Other(e.to_string()))?; - let rt = Runtime::new().unwrap(); - rt.block_on(rpc::query_incoming_redelegations( - &client, - src_validator, - delegator, - )) - } - - /// Query a validator's bonds for a given epoch - pub fn query_bond( - tendermint_addr: &str, - source: &Address, - validator: &Address, - epoch: Option, - ) -> Result { - let client = HttpClient::new( - TendermintAddress::from_str(tendermint_addr) - .map_err(|e| Error::Other(e.to_string()))?, - ) - .map_err(|e| Error::Other(e.to_string()))?; - let rt = Runtime::new().unwrap(); - rt.block_on(rpc::query_bond(&client, source, validator, epoch)) - } - - /// Query a validator's unbonds for a given epoch - pub fn query_and_print_unbonds( - tendermint_addr: &str, - source: &Address, - validator: &Address, - ) -> Result<(), Error> { - let rt = Runtime::new().unwrap(); - rt.block_on(async { - let unbonds = - query_unbond_with_slashing(tendermint_addr, source, validator)?; - let current_epoch = query_epoch(tendermint_addr)?; - - let mut total_withdrawable = token::Amount::zero(); - let mut not_yet_withdrawable = - HashMap::::new(); - for ((_start_epoch, withdraw_epoch), amount) in unbonds.into_iter() - { - if withdraw_epoch <= current_epoch { - total_withdrawable += amount; - } else { - let withdrawable_amount = - not_yet_withdrawable.entry(withdraw_epoch).or_default(); - *withdrawable_amount += amount; - } - } - if !total_withdrawable.is_zero() { - println!( - "Total withdrawable now: {}.", - total_withdrawable.to_string_native() - ); - } - if !not_yet_withdrawable.is_empty() { - println!("Current epoch: {current_epoch}.") - } - for (withdraw_epoch, amount) in not_yet_withdrawable { - println!( - "Amount {} withdrawable starting from epoch \ - {withdraw_epoch}.", - amount.to_string_native() - ); - } - Ok(()) - }) - } - - /// Query withdrawable tokens in a validator account for a given epoch - pub fn query_withdrawable_tokens( - tendermint_addr: &str, - bond_source: &Address, - validator: &Address, - epoch: Option, - ) -> Result { - let client = HttpClient::new( - TendermintAddress::from_str(tendermint_addr) - .map_err(|e| Error::Other(e.to_string()))?, - ) - .map_err(|e| Error::Other(e.to_string()))?; - let rt = Runtime::new().unwrap(); - rt.block_on(rpc::query_withdrawable_tokens( - &client, - bond_source, - validator, - epoch, - )) - } - - /// Query all unbonds for a validator, applying slashes - pub fn query_unbond_with_slashing( - tendermint_addr: &str, - source: &Address, - validator: &Address, - ) -> Result, Error> { - let client = HttpClient::new( - TendermintAddress::from_str(tendermint_addr) - .map_err(|e| Error::Other(e.to_string()))?, - ) - .map_err(|e| Error::Other(e.to_string()))?; - let rt = Runtime::new().unwrap(); - rt.block_on(rpc::query_unbond_with_slashing(&client, source, validator)) - } - - /// Get the bond amount at the given epoch - pub fn get_bond_amount_at( - tendermint_addr: &str, - delegator: &Address, - validator: &Address, - epoch: Epoch, - ) -> Result { - let client = HttpClient::new( - TendermintAddress::from_str(tendermint_addr) - .map_err(|e| Error::Other(e.to_string()))?, - ) - .map_err(|e| Error::Other(e.to_string()))?; - let rt = Runtime::new().unwrap(); - rt.block_on(rpc::get_bond_amount_at( - &client, delegator, validator, epoch, - )) - } - - /// Get bonds and unbonds with all details (slashes and rewards, if any) - /// grouped by their bond IDs. - pub fn bonds_and_unbonds( - tendermint_addr: &str, - source: &Option
, - validator: &Option
, - ) -> Result { - let client = HttpClient::new( - TendermintAddress::from_str(tendermint_addr) - .map_err(|e| Error::Other(e.to_string()))?, - ) - .map_err(|e| Error::Other(e.to_string()))?; - let rt = Runtime::new().unwrap(); - rt.block_on(rpc::bonds_and_unbonds(&client, source, validator)) - } - - /// Get bonds and unbonds with all details (slashes and rewards, if any) - /// grouped by their bond IDs, enriched with extra information calculated - /// from the data. - pub fn enriched_bonds_and_unbonds( - tendermint_addr: &str, - current_epoch: Epoch, - source: &Option
, - validator: &Option
, - ) -> Result { - let client = HttpClient::new( - TendermintAddress::from_str(tendermint_addr) - .map_err(|e| Error::Other(e.to_string()))?, - ) - .map_err(|e| Error::Other(e.to_string()))?; - let rt = Runtime::new().unwrap(); - rt.block_on(rpc::enriched_bonds_and_unbonds( - &client, - current_epoch, - source, - validator, - )) - } -} - -pub mod account { - use namada_core::types::account::Account; - use namada_core::types::address::Address; - use namada_core::types::key::common; - - use super::*; - - /// Query token amount of owner. - pub fn get_token_balance( - tendermint_addr: &str, - token: &Address, - owner: &Address, - ) -> Result { - let client = HttpClient::new( - TendermintAddress::from_str(tendermint_addr) - .map_err(|e| Error::Other(e.to_string()))?, - ) - .map_err(|e| Error::Other(e.to_string()))?; - let rt = Runtime::new().unwrap(); - rt.block_on(rpc::get_token_balance(&client, token, owner)) - } - - /// Check if the address exists on chain. Established address exists if it - /// has a stored validity predicate. Implicit and internal addresses - /// always return true. - pub fn known_address( - tendermint_addr: &str, - address: &Address, - ) -> Result { - let client = HttpClient::new( - TendermintAddress::from_str(tendermint_addr) - .map_err(|e| Error::Other(e.to_string()))?, - ) - .map_err(|e| Error::Other(e.to_string()))?; - let rt = Runtime::new().unwrap(); - rt.block_on(rpc::known_address(&client, address)) - } - - /// Query the accunt substorage space of an address - pub fn get_account_info( - tendermint_addr: &str, - owner: &Address, - ) -> Result, Error> { - let client = HttpClient::new( - TendermintAddress::from_str(tendermint_addr) - .map_err(|e| Error::Other(e.to_string()))?, - ) - .map_err(|e| Error::Other(e.to_string()))?; - let rt = Runtime::new().unwrap(); - rt.block_on(rpc::get_account_info(&client, owner)) - } - - /// Query if the public_key is revealed - pub fn is_public_key_revealed( - tendermint_addr: &str, - owner: &Address, - ) -> Result { - let client = HttpClient::new( - TendermintAddress::from_str(tendermint_addr) - .map_err(|e| Error::Other(e.to_string()))?, - ) - .map_err(|e| Error::Other(e.to_string()))?; - let rt = Runtime::new().unwrap(); - rt.block_on(rpc::is_public_key_revealed(&client, owner)) - } - - /// Query an account substorage at a specific index - pub fn get_public_key_at( - tendermint_addr: &str, - owner: &Address, - index: u8, - ) -> Result, Error> { - let client = HttpClient::new( - TendermintAddress::from_str(tendermint_addr) - .map_err(|e| Error::Other(e.to_string()))?, - ) - .map_err(|e| Error::Other(e.to_string()))?; - let rt = Runtime::new().unwrap(); - rt.block_on(rpc::get_public_key_at(&client, owner, index)) - } -} - -pub mod pgf { - use super::*; - - /// Check if the given address is a pgf steward. - pub async fn is_steward( - tendermint_addr: &str, - address: &Address, - ) -> Result { - let client = HttpClient::new( - TendermintAddress::from_str(tendermint_addr) - .map_err(|e| Error::Other(e.to_string()))?, - ) - .map_err(|e| Error::Other(e.to_string()))?; - let rt = Runtime::new().unwrap(); - Ok(rt.block_on(rpc::is_steward(&client, address))) - } -} diff --git a/light_sdk/src/reading/pgf.rs b/light_sdk/src/reading/pgf.rs new file mode 100644 index 0000000000..f6edfdaeac --- /dev/null +++ b/light_sdk/src/reading/pgf.rs @@ -0,0 +1,15 @@ +use super::*; + +/// Check if the given address is a pgf steward. +pub async fn is_steward( + tendermint_addr: &str, + address: &Address, +) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + Ok(rt.block_on(rpc::is_steward(&client, address))) +} diff --git a/light_sdk/src/reading/pos.rs b/light_sdk/src/reading/pos.rs new file mode 100644 index 0000000000..86aea2dc01 --- /dev/null +++ b/light_sdk/src/reading/pos.rs @@ -0,0 +1,366 @@ +use std::collections::{BTreeSet, HashMap, HashSet}; + +use namada_core::types::address::Address; +use namada_core::types::key::common; +use namada_core::types::storage::{BlockHeight, Epoch}; +use namada_sdk::proof_of_stake::types::{ + BondsAndUnbondsDetails, CommissionPair, ValidatorMetaData, ValidatorState, +}; +use namada_sdk::proof_of_stake::PosParams; +use namada_sdk::queries::vp::pos::EnrichedBondsAndUnbondsDetails; + +use super::*; + +/// Query the epoch of the last committed block +pub fn query_epoch(tendermint_addr: &str) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::query_epoch(&client)) +} + +/// Query the epoch of the given block height, if it exists. +/// Will return none if the input block height is greater than +/// the latest committed block height. +pub fn query_epoch_at_height( + tendermint_addr: &str, + height: BlockHeight, +) -> Result, Error> { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::query_epoch_at_height(&client, height)) +} + +/// Check if the given address is a known validator. +pub fn is_validator( + tendermint_addr: &str, + address: &Address, +) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::is_validator(&client, address)) +} + +/// Check if a given address is a known delegator +pub fn is_delegator( + tendermint_addr: &str, + address: &Address, +) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::is_delegator(&client, address)) +} + +/// Check if a given address is a known delegator at the given epoch +pub fn is_delegator_at( + tendermint_addr: &str, + address: &Address, + epoch: Epoch, +) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::is_delegator_at(&client, address, epoch)) +} + +/// Get the set of consensus keys registered in the network +pub fn get_consensus_keys( + tendermint_addr: &str, +) -> Result, Error> { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::get_consensus_keys(&client)) +} + +/// Get the PoS parameters +pub fn get_pos_params(tendermint_addr: &str) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::get_pos_params(&client)) +} + +/// Get all validators in the given epoch +pub fn get_all_validators( + tendermint_addr: &str, + epoch: Epoch, +) -> Result, Error> { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::get_all_validators(&client, epoch)) +} + +/// Get the total staked tokens in the given epoch +pub fn get_total_staked_tokens( + tendermint_addr: &str, + epoch: Epoch, +) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::get_total_staked_tokens(&client, epoch)) +} + +/// Get the given validator's stake at the given epoch +pub fn get_validator_stake( + tendermint_addr: &str, + epoch: Epoch, + validator: &Address, +) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::get_validator_stake(&client, epoch, validator)) +} + +/// Query and return a validator's state +pub fn get_validator_state( + tendermint_addr: &str, + validator: &Address, + epoch: Option, +) -> Result, Error> { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::get_validator_state(&client, validator, epoch)) +} + +/// Get the delegator's delegation +pub fn get_delegators_delegation( + tendermint_addr: &str, + address: &Address, +) -> Result, Error> { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::get_delegators_delegation(&client, address)) +} + +/// Get the delegator's delegation at some epoh +pub fn get_delegators_delegation_at( + tendermint_addr: &str, + address: &Address, + epoch: Epoch, +) -> Result, Error> { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::get_delegators_delegation_at(&client, address, epoch)) +} + +/// Query and return validator's commission rate and max commission rate +/// change per epoch +pub fn query_commission_rate( + tendermint_addr: &str, + validator: &Address, + epoch: Option, +) -> Result, Error> { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::query_commission_rate(&client, validator, epoch)) +} + +/// Query and return validator's metadata, including the commission rate and +/// max commission rate change +pub fn query_metadata( + tendermint_addr: &str, + validator: &Address, + epoch: Option, +) -> Result<(Option, Option), Error> { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::query_metadata(&client, validator, epoch)) +} + +/// Query and return the incoming redelegation epoch for a given pair of +/// source validator and delegator, if there is any. +pub fn query_incoming_redelegations( + tendermint_addr: &str, + src_validator: &Address, + delegator: &Address, +) -> Result, Error> { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::query_incoming_redelegations( + &client, + src_validator, + delegator, + )) +} + +/// Query a validator's bonds for a given epoch +pub fn query_bond( + tendermint_addr: &str, + source: &Address, + validator: &Address, + epoch: Option, +) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::query_bond(&client, source, validator, epoch)) +} + +/// Query a validator's unbonds for a given epoch +pub fn query_and_print_unbonds( + tendermint_addr: &str, + source: &Address, + validator: &Address, +) -> Result, Error> { + let rt = Runtime::new().unwrap(); + rt.block_on(async { + query_unbond_with_slashing(tendermint_addr, source, validator) + }) +} + +/// Query withdrawable tokens in a validator account for a given epoch +pub fn query_withdrawable_tokens( + tendermint_addr: &str, + bond_source: &Address, + validator: &Address, + epoch: Option, +) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::query_withdrawable_tokens( + &client, + bond_source, + validator, + epoch, + )) +} + +/// Query all unbonds for a validator, applying slashes +pub fn query_unbond_with_slashing( + tendermint_addr: &str, + source: &Address, + validator: &Address, +) -> Result, Error> { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::query_unbond_with_slashing(&client, source, validator)) +} + +/// Get the bond amount at the given epoch +pub fn get_bond_amount_at( + tendermint_addr: &str, + delegator: &Address, + validator: &Address, + epoch: Epoch, +) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::get_bond_amount_at( + &client, delegator, validator, epoch, + )) +} + +/// Get bonds and unbonds with all details (slashes and rewards, if any) +/// grouped by their bond IDs. +pub fn bonds_and_unbonds( + tendermint_addr: &str, + source: &Option
, + validator: &Option
, +) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::bonds_and_unbonds(&client, source, validator)) +} + +/// Get bonds and unbonds with all details (slashes and rewards, if any) +/// grouped by their bond IDs, enriched with extra information calculated +/// from the data. +pub fn enriched_bonds_and_unbonds( + tendermint_addr: &str, + current_epoch: Epoch, + source: &Option
, + validator: &Option
, +) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::enriched_bonds_and_unbonds( + &client, + current_epoch, + source, + validator, + )) +} diff --git a/light_sdk/src/reading/tx.rs b/light_sdk/src/reading/tx.rs new file mode 100644 index 0000000000..036f38ee59 --- /dev/null +++ b/light_sdk/src/reading/tx.rs @@ -0,0 +1,73 @@ +use namada_sdk::events::Event; +use namada_sdk::rpc::{TxEventQuery, TxResponse}; + +use super::*; + +/// Call the corresponding `tx_event_query` RPC method, to fetch +/// the current status of a transation. +pub fn query_tx_events( + tendermint_addr: &str, + tx_hash: &str, +) -> Result, Error> { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + let tx_event_query = TxEventQuery::Applied(tx_hash); + rt.block_on(rpc::query_tx_events(&client, tx_event_query)) + .map_err(|e| Error::Other(e.to_string())) +} + +/// Dry run a transaction +pub fn dry_run_tx( + tendermint_addr: &str, + tx_bytes: Vec, +) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let (data, height, prove) = (Some(tx_bytes), None, false); + let rt = Runtime::new().unwrap(); + let result = rt + .block_on(RPC.shell().dry_run_tx(&client, data, height, prove)) + .map_err(|err| { + Error::from(namada_sdk::error::QueryError::NoResponse( + err.to_string(), + )) + })? + .data; + Ok(result) +} + +/// Lookup the full response accompanying the specified transaction event +pub fn query_tx_response( + tendermint_addr: &str, + tx_hash: &str, +) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let tx_query = TxEventQuery::Applied(tx_hash); + let rt = Runtime::new().unwrap(); + rt.block_on(rpc::query_tx_response(&client, tx_query)) + .map_err(|e| Error::Other(e.to_string())) +} + +/// Query the status of a given transaction. +pub async fn query_tx_status( + tendermint_addr: &str, + tx_hash: &str, +) -> Result { + let maybe_event = query_tx_events(tendermint_addr, tx_hash)?; + if let Some(e) = maybe_event { + Ok(e) + } else { + Err(Error::Tx(namada_sdk::error::TxError::AppliedTimeout)) + } +} From 8d34ed207fbcb7ceb41e51d7157003209b039b78 Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 20 Dec 2023 12:09:20 +0100 Subject: [PATCH 195/216] add MsgShieldedTransfer --- apps/src/lib/cli.rs | 4 +- apps/src/lib/client/tx.rs | 7 +- core/src/types/ibc.rs | 38 +++++- sdk/src/args.rs | 4 +- sdk/src/lib.rs | 2 +- sdk/src/tx.rs | 240 ++++++++++++++++++++++++++------------ 6 files changed, 211 insertions(+), 84 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index c04f5a531b..0d8909d70d 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -4025,7 +4025,7 @@ pub mod args { let chain_ctx = ctx.borrow_mut_chain_or_exit(); TxIbcTransfer:: { tx, - source: chain_ctx.get(&self.source), + source: chain_ctx.get_cached(&self.source), receiver: self.receiver, token: chain_ctx.get(&self.token), amount: self.amount, @@ -4042,7 +4042,7 @@ pub mod args { impl Args for TxIbcTransfer { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); - let source = SOURCE.parse(matches); + let source = TRANSFER_SOURCE.parse(matches); let receiver = RECEIVER.parse(matches); let token = TOKEN.parse(matches); let amount = InputAmount::Unvalidated(AMOUNT.parse(matches)); diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 74ef2bab4c..ee098859f7 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -989,7 +989,12 @@ pub async fn submit_ibc_transfer( where ::Error: std::fmt::Display, { - submit_reveal_aux(namada, args.tx.clone(), &args.source).await?; + submit_reveal_aux( + namada, + args.tx.clone(), + &args.source.effective_address(), + ) + .await?; let (mut tx, signing_data, _epoch) = args.build(namada).await?; signing::generate_test_vector(namada, &tx).await?; diff --git a/core/src/types/ibc.rs b/core/src/types/ibc.rs index 2f04e11709..70fd372993 100644 --- a/core/src/types/ibc.rs +++ b/core/src/types/ibc.rs @@ -11,12 +11,15 @@ use serde::{Deserialize, Serialize}; use thiserror::Error; use super::address::HASH_LEN; +use crate::ibc::apps::transfer::types::msgs::transfer::MsgTransfer; use crate::ibc::apps::transfer::types::{Memo, PrefixedDenom, TracePath}; use crate::ibc::core::handler::types::events::{ Error as IbcEventError, IbcEvent as RawIbcEvent, }; +use crate::ibc::primitives::proto::Protobuf; use crate::tendermint::abci::Event as AbciEvent; use crate::types::masp::PaymentAddress; +use crate::types::token::Transfer; /// The event type defined in ibc-rs for receiving a token pub const EVENT_TYPE_PACKET: &str = "fungible_token_packet"; @@ -98,11 +101,44 @@ impl std::fmt::Display for IbcEvent { } } +/// IBC transfer message to send from a shielded address +#[derive(Debug, Clone)] +pub struct MsgShieldedTransfer { + /// IBC transfer message + pub message: MsgTransfer, + /// Token transfer with the masp section hash + pub transfer: Transfer, +} + +impl BorshSerialize for MsgShieldedTransfer { + fn serialize( + &self, + writer: &mut W, + ) -> std::io::Result<()> { + let encoded_msg = self.message.clone().encode_vec(); + let members = (encoded_msg, self.transfer.clone()); + BorshSerialize::serialize(&members, writer) + } +} + +impl BorshDeserialize for MsgShieldedTransfer { + fn deserialize_reader( + reader: &mut R, + ) -> std::io::Result { + use std::io::{Error, ErrorKind}; + let (msg, transfer): (Vec, Transfer) = + BorshDeserialize::deserialize_reader(reader)?; + let message = MsgTransfer::decode_vec(&msg) + .map_err(|err| Error::new(ErrorKind::InvalidData, err))?; + Ok(Self { message, transfer }) + } +} + /// IBC shielded transfer #[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] pub struct IbcShieldedTransfer { /// The IBC event type - pub transfer: crate::types::token::Transfer, + pub transfer: Transfer, /// The attributes of the IBC event pub masp_tx: masp_primitives::transaction::Transaction, } diff --git a/sdk/src/args.rs b/sdk/src/args.rs index 6599b0e9fb..0947788f34 100644 --- a/sdk/src/args.rs +++ b/sdk/src/args.rs @@ -296,7 +296,7 @@ pub struct TxIbcTransfer { /// Common tx arguments pub tx: Tx, /// Transfer source address - pub source: C::Address, + pub source: C::TransferSource, /// Transfer target address pub receiver: String, /// Transferred token address @@ -331,7 +331,7 @@ impl TxBuilder for TxIbcTransfer { impl TxIbcTransfer { /// Transfer source address - pub fn source(self, source: C::Address) -> Self { + pub fn source(self, source: C::TransferSource) -> Self { Self { source, ..self } } diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 29aab3041a..8ad9f5752f 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -245,7 +245,7 @@ pub trait Namada: Sized + MaybeSync + MaybeSend { /// Make a TxIbcTransfer builder from the given minimum set of arguments fn new_ibc_transfer( &self, - source: Address, + source: TransferSource, receiver: String, token: Address, amount: InputAmount, diff --git a/sdk/src/tx.rs b/sdk/src/tx.rs index d99a9c6f44..0035c55eea 100644 --- a/sdk/src/tx.rs +++ b/sdk/src/tx.rs @@ -6,6 +6,7 @@ use std::path::{Path, PathBuf}; use std::time::Duration; use borsh::BorshSerialize; +use borsh_ext::BorshSerializeExt; use masp_primitives::asset_type::AssetType; use masp_primitives::transaction::builder; use masp_primitives::transaction::builder::Builder; @@ -34,7 +35,7 @@ use namada_core::ledger::pgf::cli::steward::Commission; use namada_core::types::address::{Address, InternalAddress, MASP}; use namada_core::types::dec::Dec; use namada_core::types::hash::Hash; -use namada_core::types::ibc::IbcShieldedTransfer; +use namada_core::types::ibc::{IbcShieldedTransfer, MsgShieldedTransfer}; use namada_core::types::key::*; use namada_core::types::masp::{TransferSource, TransferTarget}; use namada_core::types::storage::Epoch; @@ -1924,18 +1925,17 @@ pub async fn build_ibc_transfer( context: &impl Namada, args: &args::TxIbcTransfer, ) -> Result<(Tx, SigningTxData, Option)> { - let default_signer = Some(args.source.clone()); + let source = args.source.effective_address(); let signing_data = signing::aux_signing_data( context, &args.tx, - Some(args.source.clone()), - default_signer, + Some(source.clone()), + Some(source.clone()), ) .await?; // Check that the source address exists on chain let source = - source_exists_or_err(args.source.clone(), args.tx.force, context) - .await?; + source_exists_or_err(source.clone(), args.tx.force, context).await?; // We cannot check the receiver // validate the amount given @@ -1973,6 +1973,19 @@ pub async fn build_ibc_transfer( .await .map_err(|e| Error::from(QueryError::Wasm(e.to_string())))?; + // For transfer from a spending key + let shielded_parts = construct_shielded_part( + context, + &args.source, + // Replace the target address as a MASP address because the target + // address could be a payment address, but the address isn't that + // of this chain. + &TransferTarget::Address(MASP), + &args.token, + validated_amount, + ) + .await?; + let ibc_denom = rpc::query_ibc_denom(context, &args.token, Some(&source)).await; let token = PrefixedCoin { @@ -2014,7 +2027,7 @@ pub async fn build_ibc_transfer( IbcTimestamp::none() }; - let msg = MsgTransfer { + let message = MsgTransfer { port_id_on_a: args.port_id.clone(), chan_id_on_a: args.channel_id.clone(), packet_data, @@ -2022,13 +2035,41 @@ pub async fn build_ibc_transfer( timeout_timestamp_on_b: timeout_timestamp, }; - let any_msg = msg.to_any(); - let mut data = vec![]; - prost::Message::encode(&any_msg, &mut data) - .map_err(TxError::EncodeFailure)?; - let chain_id = args.tx.chain_id.clone().unwrap(); let mut tx = Tx::new(chain_id, args.tx.expiration); + + let data = match shielded_parts { + Some((shielded_transfer, asset_types)) => { + let masp_tx_hash = + tx.add_masp_tx_section(shielded_transfer.masp_tx).1; + let transfer = token::Transfer { + source: source.clone(), + target: MASP, + token: args.token.clone(), + amount: validated_amount, + // The address could be a payment address, but the address isn't + // that of this chain. + key: None, + // Link the Transfer to the MASP Transaction by hash code + shielded: Some(masp_tx_hash), + }; + tx.add_masp_builder(MaspBuilder { + asset_types, + metadata: shielded_transfer.metadata, + builder: shielded_transfer.builder, + target: masp_tx_hash, + }); + MsgShieldedTransfer { message, transfer }.serialize_to_vec() + } + None => { + let any_msg = message.to_any(); + let mut data = vec![]; + prost::Message::encode(&any_msg, &mut data) + .map_err(TxError::EncodeFailure)?; + data + } + }; + tx.add_code_from_hash( tx_code_hash, Some(args.tx_code_path.to_string_lossy().into_owned()), @@ -2234,42 +2275,15 @@ pub async fn build_transfer( _ => None, }; - // Construct the shielded part of the transaction, if any - let stx_result = - ShieldedContext::::gen_shielded_transfer( - context, - &args.source, - &args.target, - &args.token, - validated_amount, - ) - .await; - - let shielded_parts = match stx_result { - Ok(stx) => Ok(stx), - Err(Build(builder::Error::InsufficientFunds(_))) => { - Err(TxError::NegativeBalanceAfterTransfer( - Box::new(source.clone()), - validated_amount.amount.to_string_native(), - Box::new(token.clone()), - )) - } - Err(err) => Err(TxError::MaspError(err.to_string())), - }?; - - let shielded_tx_epoch = shielded_parts.clone().map(|trans| trans.epoch); - - let asset_types = match shielded_parts.clone() { - None => None, - Some(transfer) => { - // Get the decoded asset types used in the transaction to give - // offline wallet users more information - let asset_types = used_asset_types(context, &transfer.builder) - .await - .unwrap_or_default(); - Some(asset_types) - } - }; + // if this transfer is shielded, `on_tx` adds the shielded part to the tx + let (on_tx, shielded_tx_epoch) = get_shielded_transfer_appender( + context, + &args.source, + &args.target, + &args.token, + validated_amount, + ) + .await?; // Construct the corresponding transparent Transfer object let transfer = token::Transfer { @@ -2282,40 +2296,12 @@ pub async fn build_transfer( shielded: None, }; - let add_shielded = |tx: &mut Tx, transfer: &mut token::Transfer| { - // Add the MASP Transaction and its Builder to facilitate validation - if let Some(ShieldedTransfer { - builder, - masp_tx, - metadata, - epoch: _, - }) = shielded_parts - { - // Add a MASP Transaction section to the Tx and get the tx hash - let masp_tx_hash = tx.add_masp_tx_section(masp_tx).1; - transfer.shielded = Some(masp_tx_hash); - - tracing::debug!("Transfer data {:?}", transfer); - - tx.add_masp_builder(MaspBuilder { - // Is safe - asset_types: asset_types.unwrap(), - // Store how the Info objects map to Descriptors/Outputs - metadata, - // Store the data that was used to construct the Transaction - builder, - // Link the Builder to the Transaction by hash code - target: masp_tx_hash, - }); - }; - Ok(()) - }; let (tx, unshielding_epoch) = build_pow_flag( context, &args.tx, args.tx_code_path.clone(), transfer, - add_shielded, + on_tx, &signing_data.fee_payer, tx_source_balance, ) @@ -2342,6 +2328,106 @@ pub async fn build_transfer( Ok((tx, signing_data, masp_epoch)) } +// Returns a function to add the shielded parts and the shielded epoch if this +// transfer is shielded. Otherwise, it returns the function is `do_nothing` and +// None as the shielded epoch +async fn get_shielded_transfer_appender( + context: &N, + source: &TransferSource, + target: &TransferTarget, + token: &Address, + amount: token::DenominatedAmount, +) -> Result<( + Box Result<()>>, + Option, +)> { + match construct_shielded_part(context, &source, &target, &token, amount) + .await? + { + Some((shielded_parts, asset_types)) => { + let epoch = shielded_parts.epoch; + Ok(( + add_shielded_transfer(shielded_parts, asset_types), + Some(epoch), + )) + } + None => Ok(( + Box::new(do_nothing as fn(&mut Tx, &mut _) -> Result<()>) + as Box Result<()>>, + None, + )), + } +} + +// Construct the shielded part of the transaction, if any +async fn construct_shielded_part( + context: &N, + source: &TransferSource, + target: &TransferTarget, + token: &Address, + amount: token::DenominatedAmount, +) -> Result)>> { + let stx_result = + ShieldedContext::::gen_shielded_transfer( + context, source, target, token, amount, + ) + .await; + + let shielded_parts = match stx_result { + Ok(Some(stx)) => stx, + Ok(None) => return Ok(None), + Err(Build(builder::Error::InsufficientFunds(_))) => { + return Err(TxError::NegativeBalanceAfterTransfer( + Box::new(source.effective_address()), + amount.amount.to_string_native(), + Box::new(token.clone()), + ) + .into()); + } + Err(err) => return Err(TxError::MaspError(err.to_string()).into()), + }; + + // Get the decoded asset types used in the transaction to give offline + // wallet users more information + let asset_types = used_asset_types(context, &shielded_parts.builder) + .await + .unwrap_or_default(); + + Ok(Some((shielded_parts, asset_types))) +} + +/// Add the MASP Transaction and its Builder to facilitate validation +fn add_shielded_transfer( + shielded_parts: ShieldedTransfer, + asset_types: HashSet<(Address, MaspDenom, Epoch)>, +) -> Box Result<()>> { + Box::new(|tx: &mut Tx, transfer: &mut token::Transfer| { + let ShieldedTransfer { + builder, + masp_tx, + metadata, + epoch: _, + } = shielded_parts; + // Add a MASP Transaction section to the Tx and get the tx hash + let masp_tx_hash = tx.add_masp_tx_section(masp_tx).1; + transfer.shielded = Some(masp_tx_hash); + + tracing::debug!("Transfer data {:?}", transfer); + + tx.add_masp_builder(MaspBuilder { + // Is safe + asset_types, + // Store how the Info objects map to Descriptors/Outputs + metadata, + // Store the data that was used to construct the Transaction + builder, + // Link the Builder to the Transaction by hash code + target: masp_tx_hash, + }); + Ok(()) + }) +} + /// Submit a transaction to initialize an account pub async fn build_init_account( context: &impl Namada, From 26c49e16999b9b92c53668dd48f799dd05f51226 Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 20 Dec 2023 15:00:37 +0100 Subject: [PATCH 196/216] decode MsgShieldedTransfer --- core/src/ledger/ibc/mod.rs | 90 ++++++++++++++++++++++++++++---------- core/src/ledger/vp_env.rs | 16 +++++-- core/src/types/ibc.rs | 13 +++--- sdk/src/tx.rs | 12 ++++- 4 files changed, 97 insertions(+), 34 deletions(-) diff --git a/core/src/ledger/ibc/mod.rs b/core/src/ledger/ibc/mod.rs index 1b01017d3f..ed972cb688 100644 --- a/core/src/ledger/ibc/mod.rs +++ b/core/src/ledger/ibc/mod.rs @@ -8,6 +8,7 @@ use std::fmt::Debug; use std::rc::Rc; use std::str::FromStr; +use borsh::BorshDeserialize; pub use context::common::IbcCommonContext; use context::router::IbcRouter; pub use context::storage::{IbcStorageContext, ProofSpec}; @@ -37,16 +38,16 @@ use crate::ibc::core::router::types::module::ModuleId; use crate::ibc::primitives::proto::Any; use crate::types::address::{Address, MASP}; use crate::types::ibc::{ - get_shielded_transfer, is_ibc_denom, EVENT_TYPE_DENOM_TRACE, - EVENT_TYPE_PACKET, + get_shielded_transfer, is_ibc_denom, MsgShieldedTransfer, + EVENT_TYPE_DENOM_TRACE, EVENT_TYPE_PACKET, }; use crate::types::masp::PaymentAddress; #[allow(missing_docs)] #[derive(Error, Debug)] pub enum Error { - #[error("Decoding IBC data error: {0}")] - DecodingData(prost::DecodeError), + #[error("Decoding IBC data error")] + DecodingData, #[error("Decoding message error: {0}")] DecodingMessage(RouterError), #[error("IBC context error: {0}")] @@ -99,28 +100,37 @@ where /// Execute according to the message in an IBC transaction or VP pub fn execute(&mut self, tx_data: &[u8]) -> Result<(), Error> { - let any_msg = Any::decode(tx_data).map_err(Error::DecodingData)?; - match MsgTransfer::try_from(any_msg.clone()) { - Ok(msg) => { + let message = decode_message(tx_data)?; + match &message { + IbcMessage::Transfer(msg) => { let mut token_transfer_ctx = TokenTransferContext::new(self.ctx.inner.clone()); send_transfer_execute( &mut self.ctx, &mut token_transfer_ctx, - msg, + msg.clone(), ) .map_err(Error::TokenTransfer) } - Err(_) => { - let envelope = MsgEnvelope::try_from(any_msg) - .map_err(Error::DecodingMessage)?; + IbcMessage::ShieldedTransfer(msg) => { + let mut token_transfer_ctx = + TokenTransferContext::new(self.ctx.inner.clone()); + send_transfer_execute( + &mut self.ctx, + &mut token_transfer_ctx, + msg.message.clone(), + ) + .map_err(Error::TokenTransfer)?; + self.handle_masp_tx(message) + } + IbcMessage::Envelope(envelope) => { execute(&mut self.ctx, &mut self.router, envelope.clone()) .map_err(|e| Error::Context(Box::new(e)))?; - // For receiving the token to a shielded address - self.handle_masp_tx(&envelope)?; // the current ibc-rs execution doesn't store the denom for the // token hash when transfer with MsgRecvPacket - self.store_denom(&envelope) + self.store_denom(&envelope)?; + // For receiving the token to a shielded address + self.handle_masp_tx(message) } } } @@ -218,17 +228,25 @@ where /// Validate according to the message in IBC VP pub fn validate(&self, tx_data: &[u8]) -> Result<(), Error> { - let any_msg = Any::decode(tx_data).map_err(Error::DecodingData)?; - match MsgTransfer::try_from(any_msg.clone()) { - Ok(msg) => { + let message = decode_message(tx_data)?; + match message { + IbcMessage::Transfer(msg) => { let token_transfer_ctx = TokenTransferContext::new(self.ctx.inner.clone()); send_transfer_validate(&self.ctx, &token_transfer_ctx, msg) .map_err(Error::TokenTransfer) } - Err(_) => { - let envelope = MsgEnvelope::try_from(any_msg) - .map_err(Error::DecodingMessage)?; + IbcMessage::ShieldedTransfer(msg) => { + let token_transfer_ctx = + TokenTransferContext::new(self.ctx.inner.clone()); + send_transfer_validate( + &self.ctx, + &token_transfer_ctx, + msg.message, + ) + .map_err(Error::TokenTransfer) + } + IbcMessage::Envelope(envelope) => { validate(&self.ctx, &self.router, envelope) .map_err(|e| Error::Context(Box::new(e))) } @@ -236,9 +254,9 @@ where } /// Handle the MASP transaction if needed - fn handle_masp_tx(&mut self, envelope: &MsgEnvelope) -> Result<(), Error> { - let shielded_transfer = match envelope { - MsgEnvelope::Packet(PacketMsg::Recv(_)) => { + fn handle_masp_tx(&mut self, message: IbcMessage) -> Result<(), Error> { + let shielded_transfer = match message { + IbcMessage::Envelope(MsgEnvelope::Packet(PacketMsg::Recv(_))) => { let event = self .ctx .inner @@ -257,6 +275,7 @@ where None => return Ok(()), } } + IbcMessage::ShieldedTransfer(msg) => Some(msg.shielded_transfer), _ => return Ok(()), }; if let Some(shielded_transfer) = shielded_transfer { @@ -272,6 +291,31 @@ where } } +enum IbcMessage { + Envelope(MsgEnvelope), + Transfer(MsgTransfer), + ShieldedTransfer(MsgShieldedTransfer), +} + +fn decode_message(tx_data: &[u8]) -> Result { + // ibc-rs message + if let Ok(any_msg) = Any::decode(tx_data) { + if let Ok(transfer_msg) = MsgTransfer::try_from(any_msg.clone()) { + return Ok(IbcMessage::Transfer(transfer_msg)); + } + if let Ok(envelope) = MsgEnvelope::try_from(any_msg) { + return Ok(IbcMessage::Envelope(envelope)); + } + } + + // Message with Transfer for the shielded transfer + if let Ok(msg) = MsgShieldedTransfer::try_from_slice(tx_data) { + return Ok(IbcMessage::ShieldedTransfer(msg)); + } + + Err(Error::DecodingData) +} + /// Get the IbcToken from the source/destination ports and channels pub fn received_ibc_token( ibc_denom: &PrefixedDenom, diff --git a/core/src/ledger/vp_env.rs b/core/src/ledger/vp_env.rs index 664e030786..728e66d9a6 100644 --- a/core/src/ledger/vp_env.rs +++ b/core/src/ledger/vp_env.rs @@ -8,7 +8,9 @@ use super::storage_api::{self, OptionExt, ResultExt, StorageRead}; use crate::proto::Tx; use crate::types::address::Address; use crate::types::hash::Hash; -use crate::types::ibc::{get_shielded_transfer, IbcEvent, EVENT_TYPE_PACKET}; +use crate::types::ibc::{ + get_shielded_transfer, IbcEvent, MsgShieldedTransfer, EVENT_TYPE_PACKET, +}; use crate::types::storage::{ BlockHash, BlockHeight, Epoch, Header, Key, TxIndex, }; @@ -112,9 +114,8 @@ where tx_data: &Tx, ) -> Result<(Transfer, Transaction), storage_api::Error> { let signed = tx_data; - if let Ok(transfer) = - Transfer::try_from_slice(&signed.data().unwrap()[..]) - { + let data = signed.data().ok_or_err_msg("No transaction data")?; + if let Ok(transfer) = Transfer::try_from_slice(&data) { let shielded_hash = transfer .shielded .ok_or_err_msg("unable to find shielded hash")?; @@ -125,6 +126,13 @@ where return Ok((transfer, masp_tx)); } + if let Ok(message) = MsgShieldedTransfer::try_from_slice(&data) { + return Ok(( + message.shielded_transfer.transfer, + message.shielded_transfer.masp_tx, + )); + } + // Shielded transfer over IBC let events = self.get_ibc_events(EVENT_TYPE_PACKET.to_string())?; // The receiving event should be only one in the single IBC transaction diff --git a/core/src/types/ibc.rs b/core/src/types/ibc.rs index 70fd372993..a66a84fae0 100644 --- a/core/src/types/ibc.rs +++ b/core/src/types/ibc.rs @@ -106,8 +106,8 @@ impl std::fmt::Display for IbcEvent { pub struct MsgShieldedTransfer { /// IBC transfer message pub message: MsgTransfer, - /// Token transfer with the masp section hash - pub transfer: Transfer, + /// MASP tx with token transfer + pub shielded_transfer: IbcShieldedTransfer, } impl BorshSerialize for MsgShieldedTransfer { @@ -116,7 +116,7 @@ impl BorshSerialize for MsgShieldedTransfer { writer: &mut W, ) -> std::io::Result<()> { let encoded_msg = self.message.clone().encode_vec(); - let members = (encoded_msg, self.transfer.clone()); + let members = (encoded_msg, self.shielded_transfer.clone()); BorshSerialize::serialize(&members, writer) } } @@ -126,11 +126,14 @@ impl BorshDeserialize for MsgShieldedTransfer { reader: &mut R, ) -> std::io::Result { use std::io::{Error, ErrorKind}; - let (msg, transfer): (Vec, Transfer) = + let (msg, shielded_transfer): (Vec, IbcShieldedTransfer) = BorshDeserialize::deserialize_reader(reader)?; let message = MsgTransfer::decode_vec(&msg) .map_err(|err| Error::new(ErrorKind::InvalidData, err))?; - Ok(Self { message, transfer }) + Ok(Self { + message, + shielded_transfer, + }) } } diff --git a/sdk/src/tx.rs b/sdk/src/tx.rs index 0035c55eea..758b6648b9 100644 --- a/sdk/src/tx.rs +++ b/sdk/src/tx.rs @@ -2041,7 +2041,7 @@ pub async fn build_ibc_transfer( let data = match shielded_parts { Some((shielded_transfer, asset_types)) => { let masp_tx_hash = - tx.add_masp_tx_section(shielded_transfer.masp_tx).1; + tx.add_masp_tx_section(shielded_transfer.masp_tx.clone()).1; let transfer = token::Transfer { source: source.clone(), target: MASP, @@ -2059,7 +2059,15 @@ pub async fn build_ibc_transfer( builder: shielded_transfer.builder, target: masp_tx_hash, }); - MsgShieldedTransfer { message, transfer }.serialize_to_vec() + let shielded_transfer = IbcShieldedTransfer { + transfer, + masp_tx: shielded_transfer.masp_tx, + }; + MsgShieldedTransfer { + message, + shielded_transfer, + } + .serialize_to_vec() } None => { let any_msg = message.to_any(); From ced7545790ac497c9d384cb6d49a1e830d8987fa Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 20 Dec 2023 21:20:11 +0100 Subject: [PATCH 197/216] fix target addr for shielded_parts --- core/src/ledger/ibc/mod.rs | 2 +- sdk/src/tx.rs | 109 +++++++++++++------------------------ tests/src/e2e/ibc_tests.rs | 33 ++++++----- 3 files changed, 58 insertions(+), 86 deletions(-) diff --git a/core/src/ledger/ibc/mod.rs b/core/src/ledger/ibc/mod.rs index ed972cb688..936d8641e0 100644 --- a/core/src/ledger/ibc/mod.rs +++ b/core/src/ledger/ibc/mod.rs @@ -128,7 +128,7 @@ where .map_err(|e| Error::Context(Box::new(e)))?; // the current ibc-rs execution doesn't store the denom for the // token hash when transfer with MsgRecvPacket - self.store_denom(&envelope)?; + self.store_denom(envelope)?; // For receiving the token to a shielded address self.handle_masp_tx(message) } diff --git a/sdk/src/tx.rs b/sdk/src/tx.rs index 758b6648b9..d664bb302e 100644 --- a/sdk/src/tx.rs +++ b/sdk/src/tx.rs @@ -1977,10 +1977,8 @@ pub async fn build_ibc_transfer( let shielded_parts = construct_shielded_part( context, &args.source, - // Replace the target address as a MASP address because the target - // address could be a payment address, but the address isn't that - // of this chain. - &TransferTarget::Address(MASP), + // The token will be escrowed to IBC address + &TransferTarget::Address(Address::Internal(InternalAddress::Ibc)), &args.token, validated_amount, ) @@ -2044,7 +2042,8 @@ pub async fn build_ibc_transfer( tx.add_masp_tx_section(shielded_transfer.masp_tx.clone()).1; let transfer = token::Transfer { source: source.clone(), - target: MASP, + // The token will be escrowed to IBC address + target: Address::Internal(InternalAddress::Ibc), token: args.token.clone(), amount: validated_amount, // The address could be a payment address, but the address isn't @@ -2283,8 +2282,7 @@ pub async fn build_transfer( _ => None, }; - // if this transfer is shielded, `on_tx` adds the shielded part to the tx - let (on_tx, shielded_tx_epoch) = get_shielded_transfer_appender( + let shielded_parts = construct_shielded_part( context, &args.source, &args.target, @@ -2292,6 +2290,7 @@ pub async fn build_transfer( validated_amount, ) .await?; + let shielded_tx_epoch = shielded_parts.as_ref().map(|trans| trans.0.epoch); // Construct the corresponding transparent Transfer object let transfer = token::Transfer { @@ -2304,12 +2303,43 @@ pub async fn build_transfer( shielded: None, }; + let add_shielded = |tx: &mut Tx, transfer: &mut token::Transfer| { + // Add the MASP Transaction and its Builder to facilitate validation + if let Some(( + ShieldedTransfer { + builder, + masp_tx, + metadata, + epoch: _, + }, + asset_types, + )) = shielded_parts + { + // Add a MASP Transaction section to the Tx and get the tx hash + let masp_tx_hash = tx.add_masp_tx_section(masp_tx).1; + transfer.shielded = Some(masp_tx_hash); + + tracing::debug!("Transfer data {:?}", transfer); + + tx.add_masp_builder(MaspBuilder { + asset_types, + // Store how the Info objects map to Descriptors/Outputs + metadata, + // Store the data that was used to construct the Transaction + builder, + // Link the Builder to the Transaction by hash code + target: masp_tx_hash, + }); + }; + Ok(()) + }; + let (tx, unshielding_epoch) = build_pow_flag( context, &args.tx, args.tx_code_path.clone(), transfer, - on_tx, + add_shielded, &signing_data.fee_payer, tx_source_balance, ) @@ -2336,37 +2366,6 @@ pub async fn build_transfer( Ok((tx, signing_data, masp_epoch)) } -// Returns a function to add the shielded parts and the shielded epoch if this -// transfer is shielded. Otherwise, it returns the function is `do_nothing` and -// None as the shielded epoch -async fn get_shielded_transfer_appender( - context: &N, - source: &TransferSource, - target: &TransferTarget, - token: &Address, - amount: token::DenominatedAmount, -) -> Result<( - Box Result<()>>, - Option, -)> { - match construct_shielded_part(context, &source, &target, &token, amount) - .await? - { - Some((shielded_parts, asset_types)) => { - let epoch = shielded_parts.epoch; - Ok(( - add_shielded_transfer(shielded_parts, asset_types), - Some(epoch), - )) - } - None => Ok(( - Box::new(do_nothing as fn(&mut Tx, &mut _) -> Result<()>) - as Box Result<()>>, - None, - )), - } -} - // Construct the shielded part of the transaction, if any async fn construct_shielded_part( context: &N, @@ -2404,38 +2403,6 @@ async fn construct_shielded_part( Ok(Some((shielded_parts, asset_types))) } -/// Add the MASP Transaction and its Builder to facilitate validation -fn add_shielded_transfer( - shielded_parts: ShieldedTransfer, - asset_types: HashSet<(Address, MaspDenom, Epoch)>, -) -> Box Result<()>> { - Box::new(|tx: &mut Tx, transfer: &mut token::Transfer| { - let ShieldedTransfer { - builder, - masp_tx, - metadata, - epoch: _, - } = shielded_parts; - // Add a MASP Transaction section to the Tx and get the tx hash - let masp_tx_hash = tx.add_masp_tx_section(masp_tx).1; - transfer.shielded = Some(masp_tx_hash); - - tracing::debug!("Transfer data {:?}", transfer); - - tx.add_masp_builder(MaspBuilder { - // Is safe - asset_types, - // Store how the Info objects map to Descriptors/Outputs - metadata, - // Store the data that was used to construct the Transaction - builder, - // Link the Builder to the Transaction by hash code - target: masp_tx_hash, - }); - Ok(()) - }) -} - /// Submit a transaction to initialize an account pub async fn build_init_account( context: &impl Namada, diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index d69d3f1844..3806c8d362 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -167,7 +167,8 @@ fn run_ledger_ibc() -> Result<()> { try_invalid_transfers(&test_a, &test_b, &port_id_a, &channel_id_a)?; // Transfer 50000 received over IBC on Chain B - transfer_received_token(&port_id_b, &channel_id_b, &test_b)?; + let token = format!("{port_id_b}/{channel_id_b}/nam"); + transfer_on_chain(&test_b, BERTHA, ALBERT, token, 50000, BERTHA_KEY)?; check_balances_after_non_ibc(&port_id_b, &channel_id_b, &test_b)?; // Transfer 50000 back from the origin-specific account on Chain B to Chain @@ -862,26 +863,27 @@ fn try_invalid_transfers( Ok(()) } -fn transfer_received_token( - port_id: &PortId, - channel_id: &ChannelId, +fn transfer_on_chain( test: &Test, + sender: impl AsRef, + receiver: impl AsRef, + token: impl AsRef, + amount: u64, + signer: impl AsRef, ) -> Result<()> { let rpc = get_actor_rpc(test, &Who::Validator(0)); - let ibc_denom = format!("{port_id}/{channel_id}/nam"); - let amount = Amount::native_whole(50000).to_string_native(); let tx_args = [ "transfer", "--source", - BERTHA, + sender.as_ref(), "--target", - ALBERT, + receiver.as_ref(), "--token", - &ibc_denom, + token.as_ref(), "--amount", - &amount, - "--gas-token", - NAM, + &amount.to_string(), + "--signing-keys", + signer.as_ref(), "--node", &rpc, ]; @@ -1046,11 +1048,14 @@ fn shielded_transfer( let file_path = get_shielded_transfer_path(&mut client)?; client.assert_success(); - // Send a token from Chain A to PA(B) on Chain B + // Send a token to the shielded address on Chain A + transfer_on_chain(test_a, ALBERT, AA_PAYMENT_ADDRESS, BTC, 10, ALBERT_KEY)?; + + // Send a token from SP(A) on Chain A to PA(B) on Chain B let amount = Amount::native_whole(10).to_string_native(); let height = transfer( test_a, - ALBERT, + A_SPENDING_KEY, AB_PAYMENT_ADDRESS, BTC, amount, From 9e092d27751cff63a4b06cbcaf2e70f080c117c1 Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 20 Dec 2023 21:33:59 +0100 Subject: [PATCH 198/216] add changelog --- .../unreleased/features/2321-ibc_shielded_transfer.md | 2 ++ sdk/src/tx.rs | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 .changelog/unreleased/features/2321-ibc_shielded_transfer.md diff --git a/.changelog/unreleased/features/2321-ibc_shielded_transfer.md b/.changelog/unreleased/features/2321-ibc_shielded_transfer.md new file mode 100644 index 0000000000..1d6de75a7a --- /dev/null +++ b/.changelog/unreleased/features/2321-ibc_shielded_transfer.md @@ -0,0 +1,2 @@ +- IBC transfer from a spending key + ([\#2321](https://github.com/anoma/namada/issues/2321)) \ No newline at end of file diff --git a/sdk/src/tx.rs b/sdk/src/tx.rs index d664bb302e..70ca41a144 100644 --- a/sdk/src/tx.rs +++ b/sdk/src/tx.rs @@ -1974,7 +1974,7 @@ pub async fn build_ibc_transfer( .map_err(|e| Error::from(QueryError::Wasm(e.to_string())))?; // For transfer from a spending key - let shielded_parts = construct_shielded_part( + let shielded_parts = construct_shielded_parts( context, &args.source, // The token will be escrowed to IBC address @@ -2282,7 +2282,7 @@ pub async fn build_transfer( _ => None, }; - let shielded_parts = construct_shielded_part( + let shielded_parts = construct_shielded_parts( context, &args.source, &args.target, @@ -2367,7 +2367,7 @@ pub async fn build_transfer( } // Construct the shielded part of the transaction, if any -async fn construct_shielded_part( +async fn construct_shielded_parts( context: &N, source: &TransferSource, target: &TransferTarget, From 7ad4c0f370eefba8598c2e76dea27b91cf21c8b7 Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 20 Dec 2023 22:17:15 +0100 Subject: [PATCH 199/216] fix parsing token address --- sdk/src/rpc.rs | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/sdk/src/rpc.rs b/sdk/src/rpc.rs index c09f892138..d1e269825e 100644 --- a/sdk/src/rpc.rs +++ b/sdk/src/rpc.rs @@ -1170,26 +1170,7 @@ pub async fn query_ibc_denom( Ok(Address::Internal(InternalAddress::IbcToken(hash))) => { hash.to_string() } - Ok(_) => return token.as_ref().to_string(), - Err(_) => match namada_core::types::ibc::is_ibc_denom(token.as_ref()) { - Some((trace_path, base_denom)) => { - let base_token = context - .wallet() - .await - .find_address(&base_denom) - .map(|addr| addr.to_string()) - .unwrap_or(base_denom); - return format!("{trace_path}/{base_token}"); - } - None => { - return context - .wallet() - .await - .find_address(token.as_ref()) - .map(|addr| addr.to_string()) - .unwrap_or(token.as_ref().to_string()); - } - }, + _ => return token.as_ref().to_string(), }; if let Some(owner) = owner { From a3927cd08c0748d3eed65d0640265c5049627d28 Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 20 Dec 2023 23:07:43 +0100 Subject: [PATCH 200/216] check epoch --- sdk/src/tx.rs | 54 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/sdk/src/tx.rs b/sdk/src/tx.rs index 70ca41a144..084a7b16b5 100644 --- a/sdk/src/tx.rs +++ b/sdk/src/tx.rs @@ -1983,6 +1983,7 @@ pub async fn build_ibc_transfer( validated_amount, ) .await?; + let shielded_tx_epoch = shielded_parts.as_ref().map(|trans| trans.0.epoch); let ibc_denom = rpc::query_ibc_denom(context, &args.token, Some(&source)).await; @@ -2083,7 +2084,7 @@ pub async fn build_ibc_transfer( ) .add_serialized_data(data); - let epoch = prepare_tx( + let unshielding_epoch = prepare_tx( context, &args.tx, &mut tx, @@ -2092,6 +2093,9 @@ pub async fn build_ibc_transfer( ) .await?; + let epoch = + check_epochs(unshielding_epoch, shielded_tx_epoch, args.tx.force)?; + Ok((tx, signing_data, epoch)) } @@ -2344,25 +2348,10 @@ pub async fn build_transfer( tx_source_balance, ) .await?; - // Manage the two masp epochs - let masp_epoch = match (unshielding_epoch, shielded_tx_epoch) { - (Some(fee_unshield_epoch), Some(transfer_unshield_epoch)) => { - // If the two masp epochs are different, either the wrapper or the - // inner tx will fail, so abort tx creation - if fee_unshield_epoch != transfer_unshield_epoch && !args.tx.force { - return Err(Error::Other( - "Fee unshielding masp tx and inner tx masp transaction \ - were crafted on an epoch boundary" - .to_string(), - )); - } - // Take the smaller of the two epochs - Some(fee_unshield_epoch.min(transfer_unshield_epoch)) - } - (Some(_fee_unshielding_epoch), None) => unshielding_epoch, - (None, Some(_transfer_unshield_epoch)) => shielded_tx_epoch, - (None, None) => None, - }; + + let masp_epoch = + check_epochs(unshielding_epoch, shielded_tx_epoch, args.tx.force)?; + Ok((tx, signing_data, masp_epoch)) } @@ -2403,6 +2392,31 @@ async fn construct_shielded_parts( Ok(Some((shielded_parts, asset_types))) } +fn check_epochs( + unshielding_epoch: Option, + shielded_tx_epoch: Option, + force: bool, +) -> Result> { + match (unshielding_epoch, shielded_tx_epoch) { + (Some(fee_unshield_epoch), Some(transfer_unshield_epoch)) => { + // If the two masp epochs are different, either the wrapper or the + // inner tx will fail, so abort tx creation + if fee_unshield_epoch != transfer_unshield_epoch && !force { + return Err(Error::Other( + "Fee unshielding masp tx and inner tx masp transaction \ + were crafted on an epoch boundary" + .to_string(), + )); + } + // Take the smaller of the two epochs + Ok(Some(fee_unshield_epoch.min(transfer_unshield_epoch))) + } + (Some(_fee_unshielding_epoch), None) => Ok(unshielding_epoch), + (None, Some(_transfer_unshield_epoch)) => Ok(shielded_tx_epoch), + (None, None) => Ok(None), + } +} + /// Submit a transaction to initialize an account pub async fn build_init_account( context: &impl Namada, From 9a7e4b9bd5ba5ac2b1d5781bf91c54dbeabcd2cf Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 8 Dec 2023 15:27:05 +0100 Subject: [PATCH 201/216] [feat]: Added a cli command to dry run init-chain with genesis files and report all errors found --- apps/src/lib/cli.rs | 53 ++ apps/src/lib/cli/client.rs | 34 + apps/src/lib/client/utils.rs | 3 +- apps/src/lib/config/genesis/chain.rs | 7 +- apps/src/lib/node/ledger/mod.rs | 71 ++ apps/src/lib/node/ledger/shell/init_chain.rs | 713 +++++++++++++++++-- apps/src/lib/node/ledger/shell/mod.rs | 3 +- apps/src/lib/wasm_loader/mod.rs | 22 +- core/src/ledger/parameters/mod.rs | 9 +- core/src/ledger/storage/mod.rs | 3 +- 10 files changed, 825 insertions(+), 93 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index c04f5a531b..a5db3f6b5e 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2208,6 +2208,7 @@ pub mod cmds { DefaultBaseDir(DefaultBaseDir), EpochSleep(EpochSleep), ValidateGenesisTemplates(ValidateGenesisTemplates), + TestGenesis(TestGenesis), SignGenesisTxs(SignGenesisTxs), } @@ -2240,6 +2241,8 @@ pub mod cmds { SubCmd::parse(matches).map(Self::ValidateGenesisTemplates); let genesis_tx = SubCmd::parse(matches).map(Self::SignGenesisTxs); + let test_genesis = + SubCmd::parse(matches).map(Self::TestGenesis); join_network .or(fetch_wasms) .or(validate_wasm) @@ -2252,6 +2255,7 @@ pub mod cmds { .or(default_base_dir) .or(epoch_sleep) .or(validate_genesis_templates) + .or(test_genesis) .or(genesis_tx) }) } @@ -2271,6 +2275,7 @@ pub mod cmds { .subcommand(DefaultBaseDir::def()) .subcommand(EpochSleep::def()) .subcommand(ValidateGenesisTemplates::def()) + .subcommand(TestGenesis::def()) .subcommand(SignGenesisTxs::def()) .subcommand_required(true) .arg_required_else_help(true) @@ -2460,6 +2465,28 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct TestGenesis(pub args::TestGenesis); + + impl SubCmd for TestGenesis { + const CMD: &'static str = "test-genesis"; + + fn parse(matches: &ArgMatches) -> Option { + matches + .subcommand_matches(Self::CMD) + .map(|matches| Self(args::TestGenesis::parse(matches))) + } + + fn def() -> App { + App::new(Self::CMD) + .about( + "Dry run genesis files and get a report on problems that \ + may be found.", + ) + .add_args::() + } + } + #[derive(Clone, Debug)] pub struct SignGenesisTxs(pub args::SignGenesisTxs); @@ -6936,6 +6963,32 @@ pub mod args { } } + #[derive(Clone, Debug)] + pub struct TestGenesis { + /// Templates dir + pub path: PathBuf, + pub wasm_dir: PathBuf, + } + + impl Args for TestGenesis { + fn parse(matches: &ArgMatches) -> Self { + let path = PATH.parse(matches); + let wasm_dir = WASM_DIR.parse(matches).unwrap_or_default(); + Self { path, wasm_dir } + } + + fn def(app: App) -> App { + app.arg( + PATH.def() + .help("Path to the directory with the template files."), + ) + .arg(WASM_DIR.def().help( + "Can optionally provide a wasm directory as part of verifying \ + genesis template files", + )) + } + } + #[derive(Clone, Debug)] pub struct SignGenesisTxs { pub path: PathBuf, diff --git a/apps/src/lib/cli/client.rs b/apps/src/lib/cli/client.rs index 9602f80cdd..34a7e73fc2 100644 --- a/apps/src/lib/cli/client.rs +++ b/apps/src/lib/cli/client.rs @@ -7,6 +7,7 @@ use crate::cli::api::{CliApi, CliClient}; use crate::cli::args::CliToSdk; use crate::cli::cmds::*; use crate::client::{rpc, tx, utils}; +use crate::node::ledger; impl CliApi { pub async fn handle_client_command( @@ -677,6 +678,39 @@ impl CliApi { Utils::ValidateGenesisTemplates(ValidateGenesisTemplates( args, )) => utils::validate_genesis_templates(global_args, args), + Utils::TestGenesis(TestGenesis(args)) => { + use std::str::FromStr; + + use crate::config::genesis; + use crate::facade::tendermint::Timeout; + + let templates = + genesis::templates::load_and_validate(&args.path) + .unwrap(); + let genesis = genesis::chain::finalize( + templates, + FromStr::from_str("namada-dryrun").unwrap(), + Default::default(), + Timeout::from_str("30s").unwrap(), + ); + let chain_id = &genesis.metadata.chain_id; + let test_dir = tempfile::tempdir().unwrap(); + let config = crate::config::Config::load( + test_dir.path(), + chain_id, + None, + ); + genesis + .write_toml_files( + &test_dir.path().join(chain_id.to_string()), + ) + .unwrap(); + ledger::test_genesis_files( + config.ledger, + genesis, + args.wasm_dir, + ); + } Utils::SignGenesisTxs(SignGenesisTxs(args)) => { utils::sign_genesis_tx(global_args, args).await } diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 01e764c0a2..e1100f6364 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -569,7 +569,8 @@ pub fn init_network( // After the archive is created, try to copy the built WASM, if they're // present with the checksums. This is used for local network setup, so // that we can use a local WASM build. - let checksums = wasm_loader::Checksums::read_checksums(&wasm_dir_full); + let checksums = wasm_loader::Checksums::read_checksums(&wasm_dir_full) + .unwrap_or_else(|_| safe_exit(1)); for (_, full_name) in checksums.0 { // try to copy built file from the Namada WASM root dir let file = std::env::current_dir() diff --git a/apps/src/lib/config/genesis/chain.rs b/apps/src/lib/config/genesis/chain.rs index d8e57f11f6..895bf9277b 100644 --- a/apps/src/lib/config/genesis/chain.rs +++ b/apps/src/lib/config/genesis/chain.rs @@ -284,10 +284,11 @@ impl Finalized { .get(&implicit_vp) .expect("Implicit VP must be present") .filename; - let implicit_vp = + + let implicit_vp_code_hash = wasm_loader::read_wasm(&wasm_dir, implicit_vp_filename) - .expect("Implicit VP WASM code couldn't get read"); - let implicit_vp_code_hash = Hash::sha256(implicit_vp); + .ok() + .map(Hash::sha256); let min_duration: i64 = 60 * 60 * 24 * 365 / (epochs_per_year as i64); let epoch_duration = EpochDuration { diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 11bd75aba5..dbccfe6440 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -743,6 +743,77 @@ async fn maybe_start_ethereum_oracle( } } +/// This function runs `Shell::init_chain` on the provided genesis files. +/// This is to check that all the transactions included therein run +/// successfully on chain initialization. +pub fn test_genesis_files( + config: config::Ledger, + genesis: config::genesis::chain::Finalized, + wasm_dir: PathBuf, +) { + use namada::ledger::storage::mockdb::MockDB; + use namada::ledger::storage::Sha256Hasher; + + use crate::facade::tendermint::block::Size; + use crate::facade::tendermint::consensus::params::ValidatorParams; + use crate::facade::tendermint::consensus::Params; + use crate::facade::tendermint::evidence::{Duration, Params as Evidence}; + use crate::facade::tendermint::time::Time; + use crate::facade::tendermint::v0_37::abci::request; + + // Channels for validators to send protocol txs to be broadcast to the + // broadcaster service + let (broadcast_sender, _broadcaster_receiver) = mpsc::unbounded_channel(); + + // Start dummy broadcaster + let _broadcaster = spawn_dummy_task(()); + + // craft a request to initailize the chain + let init = request::InitChain { + time: Time::now(), + chain_id: config.chain_id.to_string(), + consensus_params: Params { + block: Size { + max_bytes: 0, + max_gas: 0, + time_iota_ms: 0, + }, + evidence: Evidence { + max_age_num_blocks: 0, + max_age_duration: Duration(Default::default()), + max_bytes: 0, + }, + validator: ValidatorParams { + pub_key_types: vec![], + }, + version: None, + abci: Default::default(), + }, + validators: vec![], + app_state_bytes: Default::default(), + initial_height: 0u32.into(), + }; + + // start an instance of the ledger + let mut shell = Shell::::new( + config, + wasm_dir, + broadcast_sender, + None, + None, + 50 * 1024 * 1024, + 50 * 1024 * 1024, + ); + let mut initializer = shell::InitChainValidation::new(&mut shell, true); + initializer.run( + init, + genesis, + #[cfg(any(test, feature = "testing"))] + 1, + ); + initializer.report(); +} + /// Spawn a dummy asynchronous task into the runtime, /// which will resolve instantly. fn spawn_dummy_task(ready: T) -> task::JoinHandle { diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index c50dfd67d1..c099f8b709 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -1,6 +1,6 @@ //! Implementation of chain initialization for the Shell use std::collections::HashMap; -use std::hash::Hash; +use std::ops::ControlFlow; use namada::ledger::parameters::Parameters; use namada::ledger::storage::traits::StorageHasher; @@ -30,6 +30,45 @@ use crate::facade::tendermint::v0_37::abci::{request, response}; use crate::facade::tendermint_proto::google::protobuf; use crate::wasm_loader; +/// Errors that represent panics in normal flow but get demoted to errors +/// when dry-running genesis files in order to accumulate as many problems +/// as possible in a report. +#[derive(Error, Debug, Clone, PartialEq)] +enum Panic { + #[error( + "No VP found matching the expected implicit VP sha256 hash: \ + {0}\n(this will be `None` if no wasm file was found for the implicit \ + vp)" + )] + MissingImplicitVP(String), + #[error("Missing validity predicate for {0}")] + MissingVpWasmConfig(String), + #[error("Could not find checksums.json file")] + ChecksumsFile, + #[error("Invalid wasm code sha256 hash for {0}")] + Checksum(String), + #[error( + "Config for token '{0}' with configured balance not found in genesis" + )] + MissingTokenConfig(String), + #[error("Failed to read wasm {0} with reason: {1}")] + ReadingWasm(String, String), +} + +/// Warnings generated by problems in genesis files. +#[derive(Error, Debug, PartialEq)] +enum Warning { + #[error("The wasm {0} isn't whitelisted.")] + WhitelistedWasm(String), + #[error("Genesis init genesis validator tx for {0} failed with {1}.")] + Validator(String, String), + #[error( + "Genesis bond by {0} to validiator {1} of {2} NAM failed with reason: \ + {3}" + )] + FailedBond(String, String, token::DenominatedAmount, String), +} + impl Shell where D: DB + for<'iter> DBIter<'iter> + Sync + 'static, @@ -84,7 +123,41 @@ where let native_token = genesis.get_native_token().clone(); self.wl_storage.storage.native_token = native_token; } + let mut validation = InitChainValidation::new(self, false); + validation.run( + init, + genesis, + #[cfg(any(test, feature = "testing"))] + _num_validators, + ); + // propogate errors or panic + validation.error_out()?; + // Set the initial validator set + response.validators = self + .get_abci_validator_updates(true, |pk, power| { + let pub_key: crate::facade::tendermint::PublicKey = pk.into(); + let power = + crate::facade::tendermint::vote::Power::try_from(power) + .unwrap(); + validator::Update { pub_key, power } + }) + .expect("Must be able to set genesis validator set"); + debug_assert!(!response.validators.is_empty()); + Ok(response) + } +} +impl<'shell, D, H> InitChainValidation<'shell, D, H> +where + D: DB + for<'iter> DBIter<'iter> + Sync + 'static, + H: StorageHasher + Sync + 'static, +{ + pub fn run( + &mut self, + init: request::InitChain, + genesis: genesis::chain::Finalized, + #[cfg(any(test, feature = "testing"))] _num_validators: u64, + ) -> ControlFlow<()> { let ts: protobuf::Timestamp = init.time.into(); let initial_height = init .initial_height @@ -100,11 +173,11 @@ where // Initialize protocol parameters let parameters = genesis.get_chain_parameters(&self.wasm_dir); self.store_wasms(¶meters)?; - parameters.init_storage(&mut self.wl_storage)?; + parameters.init_storage(&mut self.wl_storage).unwrap(); // Initialize governance parameters let gov_params = genesis.get_gov_params(); - gov_params.init_storage(&mut self.wl_storage)?; + gov_params.init_storage(&mut self.wl_storage).unwrap(); // configure the Ethereum bridge if the configuration is set. if let Some(config) = genesis.get_eth_bridge_params() { @@ -169,39 +242,49 @@ where .expect("Must be able to copy PoS genesis validator sets"); ibc::init_genesis_storage(&mut self.wl_storage); - - // Set the initial validator set - response.validators = self - .get_abci_validator_updates(true, |pk, power| { - let pub_key: crate::facade::tendermint::PublicKey = pk.into(); - let power = - crate::facade::tendermint::vote::Power::try_from(power) - .unwrap(); - validator::Update { pub_key, power } - }) - .expect("Must be able to set genesis validator set"); - debug_assert!(!response.validators.is_empty()); - Ok(response) + ControlFlow::Continue(()) } /// Look-up WASM code of a genesis VP by its name fn lookup_vp( - &self, + &mut self, name: &str, genesis: &genesis::chain::Finalized, vp_cache: &mut HashMap>, - ) -> Vec { - let config = - genesis.vps.wasm.get(name).unwrap_or_else(|| { - panic!("Missing validity predicate for {name}") - }); - let vp_filename = &config.filename; - vp_cache.get_or_insert_with(vp_filename.clone(), || { - wasm_loader::read_wasm(&self.wasm_dir, vp_filename).unwrap() - }) + ) -> ControlFlow<(), Vec> { + use std::collections::hash_map::Entry; + let Some(vp_filename) = + self.validate( + genesis + .vps + .wasm + .get(name) + .map(|conf| conf.filename.clone()) + .ok_or_else(|| { + Panic::MissingVpWasmConfig(name.to_string()) + })) + .or_placeholder(None)? else { + return self.proceed_with(vec![]); + }; + let code = match vp_cache.entry(vp_filename.clone()) { + Entry::Occupied(o) => o.get().clone(), + Entry::Vacant(v) => { + let code = self + .validate( + wasm_loader::read_wasm(&self.wasm_dir, &vp_filename) + .map_err(|e| { + Panic::ReadingWasm(vp_filename, e.to_string()) + }), + ) + .or_placeholder(Some(vec![]))? + .unwrap(); + v.insert(code).clone() + } + }; + self.proceed_with(code) } - fn store_wasms(&mut self, params: &Parameters) -> Result<()> { + fn store_wasms(&mut self, params: &Parameters) -> ControlFlow<()> { let Parameters { tx_whitelist, vp_whitelist, @@ -209,31 +292,64 @@ where .. } = params; let mut is_implicit_vp_stored = false; - let checksums = wasm_loader::Checksums::read_checksums(&self.wasm_dir); + + let Some(checksums) = self.validate( + wasm_loader::Checksums::read_checksums(&self.wasm_dir) + .map_err(|_| Panic::ChecksumsFile) + ).or_placeholder(None)? else { + return self.proceed_with(()); + }; + for (name, full_name) in checksums.0.iter() { - let code = wasm_loader::read_wasm(&self.wasm_dir, name) - .map_err(Error::ReadingWasm)?; + let code = self + .validate( + wasm_loader::read_wasm(&self.wasm_dir, name) + .map_err(Error::ReadingWasm), + ) + .or_placeholder(Some(vec![]))? + .unwrap(); + let code_hash = CodeHash::sha256(&code); - let code_len = u64::try_from(code.len()) - .map_err(|e| Error::LoadingWasm(e.to_string()))?; + let code_len = self + .validate( + u64::try_from(code.len()) + .map_err(|e| Error::LoadingWasm(e.to_string())), + ) + .or_placeholder(Some(1))? + .unwrap(); let elements = full_name.split('.').collect::>(); - let checksum = elements.get(1).ok_or_else(|| { - Error::LoadingWasm(format!("invalid full name: {}", full_name)) - })?; - assert_eq!( - code_hash.to_string(), - checksum.to_uppercase(), - "Invalid wasm code sha256 hash for {}", - name - ); + let checksum = self + .validate( + elements + .get(1) + .map(|c| c.to_string().to_uppercase()) + .ok_or_else(|| { + Error::LoadingWasm(format!( + "invalid full name: {}", + full_name + )) + }), + ) + .or_placeholder(Some(code_hash.to_string()))? + .unwrap(); + + self.validate(if checksum == code_hash.to_string() { + Ok(()) + } else { + Err(Panic::Checksum(name.to_string())) + }) + .or_placeholder(None)?; if (tx_whitelist.is_empty() && vp_whitelist.is_empty()) || tx_whitelist.contains(&code_hash.to_string().to_lowercase()) || vp_whitelist.contains(&code_hash.to_string().to_lowercase()) { - validate_untrusted_wasm(&code) - .map_err(|e| Error::LoadingWasm(e.to_string()))?; + self.validate( + validate_untrusted_wasm(&code) + .map_err(|e| Error::LoadingWasm(e.to_string())), + ) + .or_placeholder(None)?; #[cfg(not(test))] if name.starts_with("tx_") { @@ -247,25 +363,32 @@ where let hash_key = Key::wasm_hash(name); let code_name_key = Key::wasm_code_name(name.to_owned()); - self.wl_storage.write_bytes(&code_key, code)?; - self.wl_storage.write(&code_len_key, code_len)?; - self.wl_storage.write_bytes(&hash_key, code_hash)?; - if &code_hash == implicit_vp_code_hash { + self.wl_storage.write_bytes(&code_key, code).unwrap(); + self.wl_storage.write(&code_len_key, code_len).unwrap(); + self.wl_storage.write_bytes(&hash_key, code_hash).unwrap(); + if &Some(code_hash) == implicit_vp_code_hash { is_implicit_vp_stored = true; } - self.wl_storage.write_bytes(&code_name_key, code_hash)?; + self.wl_storage + .write_bytes(&code_name_key, code_hash) + .unwrap(); } else { tracing::warn!("The wasm {name} isn't whitelisted."); + self.warn(Warning::WhitelistedWasm(name.to_string())); } } // check if implicit_vp wasm is stored - assert!( - is_implicit_vp_stored, - "No VP found matching the expected implicit VP sha256 hash: {}", - implicit_vp_code_hash - ); - Ok(()) + if !is_implicit_vp_stored { + self.register_err(Panic::MissingImplicitVP( + match implicit_vp_code_hash { + None => "None".to_string(), + Some(h) => h.to_string(), + }, + )); + } + + self.proceed_with(()) } /// Init genesis token accounts @@ -294,16 +417,24 @@ where } /// Init genesis token balances - fn init_token_balances(&mut self, genesis: &genesis::chain::Finalized) { + fn init_token_balances( + &mut self, + genesis: &genesis::chain::Finalized, + ) -> ControlFlow<()> { for (token_alias, TokenBalances(balances)) in &genesis.balances.token { tracing::debug!("Initializing token balances {token_alias}"); - let token_address = &genesis + let Some(token_address) = self.validate(genesis .tokens .token .get(token_alias) - .expect("Token with configured balance not found in genesis.") - .address; + .ok_or_else(|| Panic::MissingTokenConfig(token_alias.to_string())) + .map(|conf| &conf.address) + ) + .or_placeholder(None)? else { + continue + }; + let mut total_token_balance = token::Amount::zero(); for (owner, balance) in balances { if let genesis::GenesisAddress::PublicKey(pk) = owner { @@ -338,6 +469,7 @@ where ) .unwrap(); } + self.proceed_with(()) } /// Apply genesis txs to initialize established accounts @@ -345,7 +477,7 @@ where &mut self, genesis: &genesis::chain::Finalized, vp_cache: &mut HashMap>, - ) { + ) -> ControlFlow<()> { if let Some(txs) = genesis.transactions.established_account.as_ref() { for FinalizedEstablishedAccountTx { address, @@ -361,7 +493,7 @@ where "Applying genesis tx to init an established account \ {address}" ); - let vp_code = self.lookup_vp(vp, genesis, vp_cache); + let vp_code = self.lookup_vp(vp, genesis, vp_cache)?; let code_hash = CodeHash::sha256(&vp_code); self.wl_storage .write_bytes(&Key::validity_predicate(address), code_hash) @@ -378,6 +510,7 @@ where .unwrap(); } } + self.proceed_with(()) } /// Apply genesis txs to initialize validator accounts @@ -387,7 +520,7 @@ where vp_cache: &mut HashMap>, params: &PosParams, current_epoch: namada::types::storage::Epoch, - ) { + ) -> ControlFlow<()> { if let Some(txs) = genesis.transactions.validator_account.as_ref() { for FinalizedValidatorAccountTx { tx: @@ -417,7 +550,7 @@ where "Applying genesis tx to init a validator account {address}" ); - let vp_code = self.lookup_vp(vp, genesis, vp_cache); + let vp_code = self.lookup_vp(vp, genesis, vp_cache)?; let code_hash = CodeHash::sha256(&vp_code); self.wl_storage .write_bytes(&Key::validity_predicate(address), code_hash) @@ -449,10 +582,15 @@ where "Genesis init genesis validator tx for {address} \ failed with {err}. Skipping." ); + self.warn(Warning::Validator( + address.to_string(), + err.to_string(), + )); continue; } } } + self.proceed_with(()) } /// Apply genesis txs to transfer tokens @@ -483,6 +621,12 @@ where tracing::warn!( "Genesis bond tx failed with: {err}. Skipping." ); + self.warn(Warning::FailedBond( + source.to_string(), + validator.to_string(), + *amount, + err.to_string(), + )); continue; }; } @@ -490,37 +634,238 @@ where } } -trait HashMapExt +/// A helper struct to accumulate errors in genesis files while +/// attempting to initialize the ledger +#[derive(Debug)] +pub struct InitChainValidation<'shell, D, H> where - K: Eq + Hash, - V: Clone, + D: DB + for<'iter> DBIter<'iter> + Sync + 'static, + H: StorageHasher + Sync + 'static, { - /// Inserts a value computed from `f` into the map if the given `key` is not - /// present, then returns a clone of the value from the map. - fn get_or_insert_with(&mut self, key: K, f: impl FnOnce() -> V) -> V; + /// Errors that can be encountered while initializing chain + /// and are propagated up the stack in normal flow. Ultimately + /// these are reported back to Comet BFT + errors: Vec, + /// Errors that cause `init_chain` to panic in normal flow but are not + /// `expect` calls, so they could reasonably occur. These are demoted + /// to errors while validating correctness of genesis files pre-network + /// launch. + panics: Vec, + /// Events that should not occur but would not prevent the chain from + /// being successfully initialized. However, we don't reasonably expect + /// to get any as these are checked as part of validating genesis + /// templates. + warnings: Vec, + dry_run: bool, + shell: &'shell mut Shell, } -impl HashMapExt for HashMap +impl<'shell, D, H> std::ops::Deref for InitChainValidation<'shell, D, H> where - K: Eq + Hash, - V: Clone, + D: DB + for<'iter> DBIter<'iter> + Sync + 'static, + H: StorageHasher + Sync + 'static, { - fn get_or_insert_with(&mut self, key: K, f: impl FnOnce() -> V) -> V { - use std::collections::hash_map::Entry; - match self.entry(key) { - Entry::Occupied(o) => o.get().clone(), - Entry::Vacant(v) => v.insert(f()).clone(), + type Target = Shell; + + fn deref(&self) -> &Self::Target { + self.shell + } +} + +impl<'shell, D, H> std::ops::DerefMut for InitChainValidation<'shell, D, H> +where + D: DB + for<'iter> DBIter<'iter> + Sync + 'static, + H: StorageHasher + Sync + 'static, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + self.shell + } +} + +impl<'shell, D, H> InitChainValidation<'shell, D, H> +where + D: DB + for<'iter> DBIter<'iter> + Sync + 'static, + H: StorageHasher + Sync + 'static, +{ + pub fn new( + shell: &'shell mut Shell, + dry_run: bool, + ) -> InitChainValidation { + Self { + shell, + errors: vec![], + panics: vec![], + warnings: vec![], + dry_run, + } + } + + /// Print out a report of errors encountered while dry-running + /// genesis files + pub fn report(&self) { + use color_eyre::owo_colors::{OwoColorize, Style}; + let separator: String = ["="; 60].into_iter().collect(); + println!( + "\n\n{}\n{}\n{}\n\n", + separator, + "Report".bold().underline(), + separator + ); + if self.errors.is_empty() + && self.panics.is_empty() + && self.warnings.is_empty() + { + println!( + "{}\n", + "Genesis files were dry-run successfully" + .bright_green() + .underline() + ); + return; + } + + if !self.warnings.is_empty() { + println!("{}\n\n", "Warnings".yellow().underline()); + let warnings = Style::new().yellow(); + for warning in &self.warnings { + println!("{}\n", warning.to_string().style(warnings)); + } + } + + if !self.errors.is_empty() { + println!("{}\n\n", "Errors".magenta().underline()); + let errors = Style::new().magenta(); + for error in &self.errors { + println!("{}\n", error.to_string().style(errors)); + } + } + + if !self.panics.is_empty() { + println!("{}\n\n", "Panics".bright_red().underline()); + let panics = Style::new().bright_red(); + for panic in &self.panics { + println!("{}\n", panic.to_string().style(panics)); + } + } + } + + /// Add a warning + fn warn(&mut self, warning: Warning) { + self.warnings.push(warning); + } + + /// Categorize an error as normal or something that would panic. + fn register_err>(&mut self, err: E) { + match err.into() { + ErrorType::Runtime(e) => self.errors.push(e), + ErrorType::DryRun(e) => self.panics.push(e), + } + } + + /// Categorize the error encountered and return a handle to allow + /// the code to specify how to proceed. + fn validate(&mut self, res: std::result::Result) -> Policy + where + E: Into, + { + match res { + Ok(data) => Policy { + result: Some(data), + dry_run: self.dry_run, + }, + Err(e) => { + self.register_err(e); + Policy { + result: None, + dry_run: self.dry_run, + } + } + } + } + + /// Check if any errors have been encountered + fn is_ok(&self) -> bool { + self.errors.is_empty() && self.panics.is_empty() + } + + /// This should only be called after checking that `is_ok` returned false. + fn error_out(mut self) -> Result<()> { + if self.is_ok() { + return Ok(()); + } + if !self.panics.is_empty() { + panic!( + "Namada ledger failed to initialize due to: {}", + self.panics.remove(0) + ); + } else { + Err(self.errors.remove(0)) + } + } + + /// Used to indicate to the functions up the stack to begin panicking + /// if not dry running a genesis file + fn proceed_with(&self, value: T) -> ControlFlow<(), T> { + if self.dry_run || self.is_ok() { + ControlFlow::Continue(value) + } else { + ControlFlow::Break(()) + } + } +} + +enum ErrorType { + Runtime(Error), + DryRun(Panic), +} + +impl From for ErrorType { + fn from(err: Error) -> Self { + Self::Runtime(err) + } +} + +impl From for ErrorType { + fn from(err: Panic) -> Self { + Self::DryRun(err) + } +} + +/// Changes the control flow of `init_chain` depending on whether +/// or not it is a dry-run. If so, errors / panics are accumulated to make +/// a report rather than immediately exiting. +struct Policy { + result: Option, + dry_run: bool, +} + +impl Policy { + /// A default value to return if an error / panic is encountered + /// during a dry-run. This allows `init_chain` to continue. + fn or_placeholder(self, value: Option) -> ControlFlow<(), Option> { + if let Some(data) = self.result { + ControlFlow::Continue(Some(data)) + } else if self.dry_run { + ControlFlow::Continue(value) + } else { + ControlFlow::Break(()) } } } -#[cfg(test)] +#[cfg(all(test, not(feature = "integration")))] mod test { use std::collections::BTreeMap; + use std::str::FromStr; + use namada::core::types::string_encoding::StringEncoded; use namada::ledger::storage::DBIter; + use namada_sdk::wallet::alias::Alias; + use super::*; + use crate::config::genesis::{transactions, GenesisAddress}; use crate::node::ledger::shell::test_utils::{self, TestShell}; + use crate::wallet::defaults; /// Test that the init-chain handler never commits changes directly to the /// DB. @@ -553,4 +898,218 @@ mod test { storage_state.iter(), ); } + + /// Tests validation works properly on `lookup_vp`. + /// This function can fail if + /// *the wasm requested has no config in the genesis files + /// * cannot be read from disk. + #[test] + fn test_dry_run_lookup_vp() { + let (mut shell, _x, _y, _z) = TestShell::new_at_height(0); + shell.wasm_dir = PathBuf::new(); + let mut initializer = InitChainValidation::new(&mut shell, true); + let mut genesis = genesis::make_dev_genesis(1, PathBuf::new()); + + let mut vp_cache = HashMap::new(); + let code = initializer.lookup_vp("vp_user", &genesis, &mut vp_cache); + assert_eq!(code, ControlFlow::Continue(vec![])); + assert_eq!( + *vp_cache.get("vp_user.wasm").expect("Test failed"), + Vec::::new() + ); + let [Panic::ReadingWasm(_, _)]: [Panic; 1] = initializer.panics + .clone() + .try_into() + .expect("Test failed") else { + panic!("Test failed") + }; + + initializer.panics.clear(); + genesis.vps.wasm.remove("vp_user").expect("Test failed"); + let code = initializer.lookup_vp("vp_user", &genesis, &mut vp_cache); + assert_eq!(code, ControlFlow::Continue(vec![])); + let [Panic::MissingVpWasmConfig(_)]: [Panic; 1] = initializer.panics + .clone() + .try_into() + .expect("Test failed") else { + panic!("Test failed") + }; + } + + /// Test validation of `store_wasms`. + /// This can fail if + /// * The checksums file cannot be found. + /// * A wasm file in the checksums file cannot be read from disk + /// * A checksum entry is invalid + /// * A wasm's code hash does not match it's checksum entry + /// * the wasm isn't whitelisted + /// * no vp_implicit wasm is stored + #[test] + fn test_dry_run_store_wasms() { + let (mut shell, _x, _y, _z) = TestShell::new_at_height(0); + let test_dir = tempfile::tempdir().unwrap(); + shell.wasm_dir = test_dir.path().into(); + + let mut initializer = InitChainValidation::new(&mut shell, true); + let genesis = genesis::make_dev_genesis(1, PathBuf::new()); + + let res = initializer + .store_wasms(&genesis.get_chain_parameters(PathBuf::new())); + assert_eq!(res, ControlFlow::Continue(())); + let expected = vec![Panic::ChecksumsFile]; + assert_eq!(expected, initializer.panics); + initializer.panics.clear(); + + let checksums_file = test_dir.path().join("checksums.json"); + std::fs::write( + &checksums_file, + r#"{ + "tx_get_rich.wasm": "tx_get_rich.moneymoneymoney" + }"#, + ) + .expect("Test failed"); + let res = initializer + .store_wasms(&genesis.get_chain_parameters(test_dir.path())); + assert_eq!(res, ControlFlow::Continue(())); + let errors = initializer.errors.iter().collect::>(); + let [ + Error::ReadingWasm(_), + Error::LoadingWasm(_), + ]: [&Error; 2] = errors.try_into().expect("Test failed") else { + panic!("Test failed"); + }; + let expected_panics = vec![ + Panic::Checksum("tx_get_rich.wasm".into()), + Panic::MissingImplicitVP("None".into()), + ]; + assert_eq!(initializer.panics, expected_panics); + + initializer.panics.clear(); + initializer.errors.clear(); + + std::fs::write( + checksums_file, + r#"{ + "tx_stuff.wasm": "tx_stuff" + }"#, + ) + .expect("Test failed"); + let res = initializer + .store_wasms(&genesis.get_chain_parameters(test_dir.path())); + assert_eq!(res, ControlFlow::Continue(())); + let errors = initializer.errors.iter().collect::>(); + let [ + Error::ReadingWasm(_), + Error::LoadingWasm(_), + Error::LoadingWasm(_), + ]: [&Error; 3] = errors.try_into().expect("Test failed") else { + panic!("Test failed"); + }; + let expected_panics = vec![Panic::MissingImplicitVP("None".into())]; + assert_eq!(initializer.panics, expected_panics); + } + + /// Test validation of `init_token_balance`. + /// This can fail if a token alias with no + /// corresponding config is encountered. + #[test] + fn test_dry_run_init_token_balance() { + let (mut shell, _x, _y, _z) = TestShell::new_at_height(0); + shell.wasm_dir = PathBuf::new(); + let mut initializer = InitChainValidation::new(&mut shell, true); + let mut genesis = genesis::make_dev_genesis(1, PathBuf::new()); + let token_alias = Alias::from_str("apfel").unwrap(); + genesis + .tokens + .token + .remove(&token_alias) + .expect("Test failed"); + let res = initializer.init_token_balances(&genesis); + assert_eq!(res, ControlFlow::Continue(())); + let [Panic::MissingTokenConfig(_)]: [Panic; 1] = initializer.panics + .clone() + .try_into() + .expect("Test failed") else { + panic!("Test failed") + }; + } + + /// Test validation of `apply_genesis_txs_bonds` + /// This can fail for + /// * insufficient funds + /// * bonding to a non-validator + #[test] + fn test_dry_run_genesis_bonds() { + let (mut shell, _x, _y, _z) = TestShell::new_at_height(0); + shell.wasm_dir = PathBuf::new(); + let mut initializer = InitChainValidation::new(&mut shell, true); + let mut genesis = genesis::make_dev_genesis(1, PathBuf::new()); + let default_addresses: HashMap = + defaults::addresses().into_iter().collect(); + let albert_address = if let Some(Address::Established(albert)) = + default_addresses.get(&Alias::from_str("albert").unwrap()) + { + albert.clone() + } else { + panic!("Test failed") + }; + // Initialize governance parameters + let gov_params = genesis.get_gov_params(); + gov_params + .init_storage(&mut initializer.wl_storage) + .unwrap(); + // PoS system depends on epoch being initialized + let pos_params = genesis.get_pos_params(); + let (current_epoch, _gas) = + initializer.wl_storage.storage.get_current_epoch(); + pos::namada_proof_of_stake::init_genesis( + &mut initializer.wl_storage, + &pos_params, + current_epoch, + ) + .expect("Must be able to initialize PoS genesis storage"); + + genesis.transactions.bond = Some(vec![transactions::BondTx { + source: GenesisAddress::EstablishedAddress(albert_address.clone()), + validator: defaults::albert_address(), + amount: token::DenominatedAmount { + amount: token::Amount::from_uint(1, 6).unwrap(), + denom: 6.into(), + }, + }]); + + // bonds should fail since no balances have been initialized + let albert_address_str = StringEncoded::new(albert_address).to_string(); + initializer.apply_genesis_txs_bonds(&genesis); + let expected = vec![Warning::FailedBond( + albert_address_str.clone(), + albert_address_str.clone(), + token::DenominatedAmount { + amount: token::Amount::from_uint(1, 6).unwrap(), + denom: 6.into(), + }, + "Insufficient source balance".to_string(), + )]; + assert_eq!(expected, initializer.warnings); + initializer.warnings.clear(); + + // initialize balances + let res = initializer.init_token_balances(&genesis); + assert_eq!(res, ControlFlow::Continue(())); + + initializer.apply_genesis_txs_bonds(&genesis); + let expected = vec![Warning::FailedBond( + albert_address_str.clone(), + albert_address_str.clone(), + token::DenominatedAmount { + amount: token::Amount::from_uint(1, 6).unwrap(), + denom: 6.into(), + }, + format!( + "The given address {} is not a validator address", + albert_address_str + ), + )]; + assert_eq!(expected, initializer.warnings); + } } diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 8e33bf87bb..cf69b416f0 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -9,6 +9,7 @@ pub mod block_alloc; mod finalize_block; mod governance; mod init_chain; +pub use init_chain::InitChainValidation; pub mod prepare_proposal; pub mod process_proposal; pub(super) mod queries; @@ -396,7 +397,7 @@ where byzantine_validators: Vec, /// Path to the base directory with DB data and configs #[allow(dead_code)] - base_dir: PathBuf, + pub(crate) base_dir: PathBuf, /// Path to the WASM directory for files used in the genesis block. pub(super) wasm_dir: PathBuf, /// Information about the running shell instance diff --git a/apps/src/lib/wasm_loader/mod.rs b/apps/src/lib/wasm_loader/mod.rs index 163b777f03..f7248d74e4 100644 --- a/apps/src/lib/wasm_loader/mod.rs +++ b/apps/src/lib/wasm_loader/mod.rs @@ -39,16 +39,21 @@ const DEFAULT_WASM_SERVER: &str = "https://artifacts.heliax.click/namada-wasm"; impl Checksums { /// Read WASM checksums from the given path - pub fn read_checksums_file(checksums_path: impl AsRef) -> Self { + pub fn read_checksums_file( + checksums_path: impl AsRef, + ) -> Result { match fs::File::open(&checksums_path) { Ok(file) => match serde_json::from_reader(file) { - Ok(result) => result, + Ok(result) => Ok(result), Err(_) => { eprintln!( "Can't read checksums from {}", checksums_path.as_ref().to_string_lossy() ); - safe_exit(1); + Err(eyre!( + "Can't read checksums from {}", + checksums_path.as_ref().to_string_lossy() + )) } }, Err(_) => { @@ -56,13 +61,18 @@ impl Checksums { "Can't find checksums at {}", checksums_path.as_ref().to_string_lossy() ); - safe_exit(1); + Err(eyre!( + "Can't find checksums at {}", + checksums_path.as_ref().to_string_lossy() + )) } } } /// Read WASM checksums from "checksums.json" in the given directory - pub fn read_checksums(wasm_directory: impl AsRef) -> Self { + pub fn read_checksums( + wasm_directory: impl AsRef, + ) -> Result { let checksums_path = wasm_directory.as_ref().join(DEFAULT_WASM_CHECKSUMS_FILE); Self::read_checksums_file(checksums_path) @@ -203,7 +213,7 @@ pub fn read_wasm( file_path: impl AsRef, ) -> eyre::Result> { // load json with wasm hashes - let checksums = Checksums::read_checksums(&wasm_directory); + let checksums = Checksums::read_checksums(&wasm_directory)?; if let Some(os_name) = file_path.as_ref().file_name() { if let Some(name) = os_name.to_str() { diff --git a/core/src/ledger/parameters/mod.rs b/core/src/ledger/parameters/mod.rs index f8f33d9768..a36682bd90 100644 --- a/core/src/ledger/parameters/mod.rs +++ b/core/src/ledger/parameters/mod.rs @@ -50,7 +50,7 @@ pub struct Parameters { /// Whitelisted tx hashes (read only) pub tx_whitelist: Vec, /// Implicit accounts validity predicate WASM code hash - pub implicit_vp_code_hash: Hash, + pub implicit_vp_code_hash: Option, /// Expected number of epochs per year (read only) pub epochs_per_year: u64, /// Maximum number of signature per transaction @@ -196,7 +196,10 @@ impl Parameters { let implicit_vp_key = storage::get_implicit_vp_key(); // Using `fn write_bytes` here, because implicit_vp code hash doesn't // need to be encoded, it's bytes already. - storage.write_bytes(&implicit_vp_key, implicit_vp_code_hash)?; + storage.write_bytes( + &implicit_vp_key, + implicit_vp_code_hash.unwrap_or_default(), + )?; let epochs_per_year_key = storage::get_epochs_per_year_key(); storage.write(&epochs_per_year_key, epochs_per_year)?; @@ -564,7 +567,7 @@ where max_block_gas, vp_whitelist, tx_whitelist, - implicit_vp_code_hash, + implicit_vp_code_hash: Some(implicit_vp_code_hash), epochs_per_year, max_signatures_per_transaction, pos_gain_p, diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index 6a51b207a8..64e99892fa 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -2,7 +2,6 @@ pub mod ics23_specs; pub mod merkle_tree; -#[cfg(any(test, feature = "testing"))] pub mod mockdb; pub mod traits; pub mod types; @@ -1457,7 +1456,7 @@ mod tests { max_expected_time_per_block: Duration::seconds(max_expected_time_per_block).into(), vp_whitelist: vec![], tx_whitelist: vec![], - implicit_vp_code_hash: Hash::zero(), + implicit_vp_code_hash: Some(Hash::zero()), epochs_per_year: 100, max_signatures_per_transaction: 15, pos_gain_p: Dec::new(1,1).expect("Cannot fail"), From f5561df6f3e3de45213797d283ccb7173660c2e0 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 8 Dec 2023 15:30:17 +0100 Subject: [PATCH 202/216] [chore]: Add changelog --- .../unreleased/features/2255-add-dry-run-genesis-cli-command.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/features/2255-add-dry-run-genesis-cli-command.md diff --git a/.changelog/unreleased/features/2255-add-dry-run-genesis-cli-command.md b/.changelog/unreleased/features/2255-add-dry-run-genesis-cli-command.md new file mode 100644 index 0000000000..79ba4abac3 --- /dev/null +++ b/.changelog/unreleased/features/2255-add-dry-run-genesis-cli-command.md @@ -0,0 +1,2 @@ +- A new client command has been added that takes a set of pre-genesis template files, validates them, +and runs them through init_chain. All errors are collected into a report. ([\#2255](https://github.com/anoma/namada/pull/2255)) \ No newline at end of file From 075b5e3cbec85b341e314b12922e82fc50f3b209 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 11 Dec 2023 13:57:00 +0100 Subject: [PATCH 203/216] [fix]: Addressing review comments --- apps/src/lib/cli.rs | 2 +- apps/src/lib/cli/client.rs | 33 +------------- apps/src/lib/client/utils.rs | 24 ++++++++++ apps/src/lib/node/ledger/mod.rs | 42 +----------------- apps/src/lib/node/ledger/shell/init_chain.rs | 46 ++++++++++++++++++++ shared/src/ledger/storage/mod.rs | 4 +- 6 files changed, 75 insertions(+), 76 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index a5db3f6b5e..dcea109ad5 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -6983,7 +6983,7 @@ pub mod args { .help("Path to the directory with the template files."), ) .arg(WASM_DIR.def().help( - "Can optionally provide a wasm directory as part of verifying \ + "Optional wasm directory to provide as part of verifying \ genesis template files", )) } diff --git a/apps/src/lib/cli/client.rs b/apps/src/lib/cli/client.rs index 34a7e73fc2..a5f597724b 100644 --- a/apps/src/lib/cli/client.rs +++ b/apps/src/lib/cli/client.rs @@ -7,7 +7,6 @@ use crate::cli::api::{CliApi, CliClient}; use crate::cli::args::CliToSdk; use crate::cli::cmds::*; use crate::client::{rpc, tx, utils}; -use crate::node::ledger; impl CliApi { pub async fn handle_client_command( @@ -679,37 +678,7 @@ impl CliApi { args, )) => utils::validate_genesis_templates(global_args, args), Utils::TestGenesis(TestGenesis(args)) => { - use std::str::FromStr; - - use crate::config::genesis; - use crate::facade::tendermint::Timeout; - - let templates = - genesis::templates::load_and_validate(&args.path) - .unwrap(); - let genesis = genesis::chain::finalize( - templates, - FromStr::from_str("namada-dryrun").unwrap(), - Default::default(), - Timeout::from_str("30s").unwrap(), - ); - let chain_id = &genesis.metadata.chain_id; - let test_dir = tempfile::tempdir().unwrap(); - let config = crate::config::Config::load( - test_dir.path(), - chain_id, - None, - ); - genesis - .write_toml_files( - &test_dir.path().join(chain_id.to_string()), - ) - .unwrap(); - ledger::test_genesis_files( - config.ledger, - genesis, - args.wasm_dir, - ); + utils::test_genesis(args) } Utils::SignGenesisTxs(SignGenesisTxs(args)) => { utils::sign_genesis_tx(global_args, args).await diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index e1100f6364..c2da6a31df 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -24,6 +24,7 @@ use sha2::{Digest, Sha256}; use tokio::sync::RwLock; use crate::cli::args; +use crate::cli::args::TestGenesis; use crate::cli::context::ENV_VAR_WASM_DIR; use crate::config::genesis::chain::DeriveEstablishedAddress; use crate::config::genesis::transactions::{ @@ -583,6 +584,29 @@ pub fn init_network( } } +pub fn test_genesis(args: TestGenesis) { + use crate::facade::tendermint::Timeout; + + let templates = genesis::templates::load_and_validate(&args.path).unwrap(); + let genesis = genesis::chain::finalize( + templates, + FromStr::from_str("namada-dryrun").unwrap(), + Default::default(), + Timeout::from_str("30s").unwrap(), + ); + let chain_id = &genesis.metadata.chain_id; + let test_dir = tempfile::tempdir().unwrap(); + let config = crate::config::Config::load(test_dir.path(), chain_id, None); + genesis + .write_toml_files(&test_dir.path().join(chain_id.to_string())) + .unwrap(); + crate::node::ledger::test_genesis_files( + config.ledger, + genesis, + args.wasm_dir, + ); +} + pub fn pk_to_tm_address( _global_args: args::Global, args::PkToTmAddress { public_key }: args::PkToTmAddress, diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index dbccfe6440..957ad4ca0b 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -754,46 +754,13 @@ pub fn test_genesis_files( use namada::ledger::storage::mockdb::MockDB; use namada::ledger::storage::Sha256Hasher; - use crate::facade::tendermint::block::Size; - use crate::facade::tendermint::consensus::params::ValidatorParams; - use crate::facade::tendermint::consensus::Params; - use crate::facade::tendermint::evidence::{Duration, Params as Evidence}; - use crate::facade::tendermint::time::Time; - use crate::facade::tendermint::v0_37::abci::request; - // Channels for validators to send protocol txs to be broadcast to the // broadcaster service let (broadcast_sender, _broadcaster_receiver) = mpsc::unbounded_channel(); // Start dummy broadcaster let _broadcaster = spawn_dummy_task(()); - - // craft a request to initailize the chain - let init = request::InitChain { - time: Time::now(), - chain_id: config.chain_id.to_string(), - consensus_params: Params { - block: Size { - max_bytes: 0, - max_gas: 0, - time_iota_ms: 0, - }, - evidence: Evidence { - max_age_num_blocks: 0, - max_age_duration: Duration(Default::default()), - max_bytes: 0, - }, - validator: ValidatorParams { - pub_key_types: vec![], - }, - version: None, - abci: Default::default(), - }, - validators: vec![], - app_state_bytes: Default::default(), - initial_height: 0u32.into(), - }; - + let chain_id = config.chain_id.to_string(); // start an instance of the ledger let mut shell = Shell::::new( config, @@ -805,12 +772,7 @@ pub fn test_genesis_files( 50 * 1024 * 1024, ); let mut initializer = shell::InitChainValidation::new(&mut shell, true); - initializer.run( - init, - genesis, - #[cfg(any(test, feature = "testing"))] - 1, - ); + initializer.run_validation(chain_id, genesis); initializer.report(); } diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index c099f8b709..dce3ed0106 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -700,6 +700,52 @@ where } } + pub fn run_validation( + &mut self, + chain_id: String, + genesis: config::genesis::chain::Finalized, + ) { + use crate::facade::tendermint::block::Size; + use crate::facade::tendermint::consensus::params::ValidatorParams; + use crate::facade::tendermint::consensus::Params; + use crate::facade::tendermint::evidence::{ + Duration, Params as Evidence, + }; + use crate::facade::tendermint::time::Time; + + // craft a request to initialize the chain + let init = request::InitChain { + time: Time::now(), + chain_id, + consensus_params: Params { + block: Size { + max_bytes: 0, + max_gas: 0, + time_iota_ms: 0, + }, + evidence: Evidence { + max_age_num_blocks: 0, + max_age_duration: Duration(Default::default()), + max_bytes: 0, + }, + validator: ValidatorParams { + pub_key_types: vec![], + }, + version: None, + abci: Default::default(), + }, + validators: vec![], + app_state_bytes: Default::default(), + initial_height: 0u32.into(), + }; + self.run( + init, + genesis, + #[cfg(any(test, feature = "testing"))] + 1, + ); + } + /// Print out a report of errors encountered while dry-running /// genesis files pub fn report(&self) { diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 0578a8286c..84d751ca92 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -1,7 +1,5 @@ //! Ledger's state storage with key-value backed store and a merkle tree -#[cfg(any(test, feature = "testing"))] -pub use namada_core::ledger::storage::mockdb; pub use namada_core::ledger::storage::{ - traits, write_log, PrefixIter, WlStorage, *, + mockdb, traits, write_log, PrefixIter, WlStorage, *, }; From 3f85b404f14b6285add93bcc4c9b0ed37bd2c258 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 14 Dec 2023 19:48:27 +0000 Subject: [PATCH 204/216] test: use tempdirs for genesis artifacts --- apps/src/lib/config/genesis.rs | 4 ++-- apps/src/lib/node/ledger/shell/init_chain.rs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index f7da635257..51eae935f6 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -311,7 +311,7 @@ pub struct Parameters { #[cfg(all(any(test, feature = "benches"), not(feature = "integration")))] pub fn make_dev_genesis( num_validators: u64, - target_chain_dir: std::path::PathBuf, + target_chain_dir: &std::path::Path, ) -> Finalized { use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::time::Duration; @@ -545,7 +545,7 @@ pub fn make_dev_genesis( // Write out the TOML files for benches #[cfg(feature = "benches")] genesis - .write_toml_files(&target_chain_dir) + .write_toml_files(target_chain_dir) .expect("Must be able to write the finalized genesis"); #[cfg(not(feature = "benches"))] let _ = target_chain_dir; // avoid unused warn diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index dce3ed0106..ca2ba5eb83 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -112,7 +112,7 @@ where ))] let genesis = { let chain_dir = self.base_dir.join(chain_id); - genesis::make_dev_genesis(_num_validators, chain_dir) + genesis::make_dev_genesis(_num_validators, &chain_dir) }; #[cfg(all( any(test, feature = "benches"), @@ -953,8 +953,8 @@ mod test { fn test_dry_run_lookup_vp() { let (mut shell, _x, _y, _z) = TestShell::new_at_height(0); shell.wasm_dir = PathBuf::new(); + let mut genesis = genesis::make_dev_genesis(1, &shell.base_dir); let mut initializer = InitChainValidation::new(&mut shell, true); - let mut genesis = genesis::make_dev_genesis(1, PathBuf::new()); let mut vp_cache = HashMap::new(); let code = initializer.lookup_vp("vp_user", &genesis, &mut vp_cache); @@ -996,8 +996,8 @@ mod test { let test_dir = tempfile::tempdir().unwrap(); shell.wasm_dir = test_dir.path().into(); + let genesis = genesis::make_dev_genesis(1, &shell.base_dir); let mut initializer = InitChainValidation::new(&mut shell, true); - let genesis = genesis::make_dev_genesis(1, PathBuf::new()); let res = initializer .store_wasms(&genesis.get_chain_parameters(PathBuf::new())); @@ -1062,8 +1062,8 @@ mod test { fn test_dry_run_init_token_balance() { let (mut shell, _x, _y, _z) = TestShell::new_at_height(0); shell.wasm_dir = PathBuf::new(); + let mut genesis = genesis::make_dev_genesis(1, &shell.base_dir); let mut initializer = InitChainValidation::new(&mut shell, true); - let mut genesis = genesis::make_dev_genesis(1, PathBuf::new()); let token_alias = Alias::from_str("apfel").unwrap(); genesis .tokens @@ -1088,8 +1088,8 @@ mod test { fn test_dry_run_genesis_bonds() { let (mut shell, _x, _y, _z) = TestShell::new_at_height(0); shell.wasm_dir = PathBuf::new(); + let mut genesis = genesis::make_dev_genesis(1, &shell.base_dir); let mut initializer = InitChainValidation::new(&mut shell, true); - let mut genesis = genesis::make_dev_genesis(1, PathBuf::new()); let default_addresses: HashMap = defaults::addresses().into_iter().collect(); let albert_address = if let Some(Address::Established(albert)) = From b2f4bd172facfbb8dc52767ca4e413864057469e Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 22 Dec 2023 12:27:35 +0100 Subject: [PATCH 205/216] Fixes datetime --- light_sdk/src/transaction/ibc.rs | 7 +++++-- light_sdk/src/transaction/mod.rs | 8 ++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/light_sdk/src/transaction/ibc.rs b/light_sdk/src/transaction/ibc.rs index 13eb00ac4f..e11a3d36b4 100644 --- a/light_sdk/src/transaction/ibc.rs +++ b/light_sdk/src/transaction/ibc.rs @@ -1,9 +1,11 @@ +use std::str::FromStr; + pub use namada_core::ibc::apps::transfer::types::msgs::transfer::MsgTransfer; use namada_core::ibc::primitives::Msg; use namada_core::proto::Tx; use namada_core::types::hash::Hash; use namada_core::types::key::common; -use namada_core::types::time::MIN_UTC; +use namada_core::types::time::DateTimeUtc; use super::GlobalArgs; use crate::transaction; @@ -24,7 +26,8 @@ impl IbcTransfer { }: GlobalArgs, ) -> Self { let mut tx = Tx::new(chain_id, expiration); - tx.header.timestamp = MIN_UTC; + tx.header.timestamp = + DateTimeUtc::from_str("2000-01-01T00:00:00Z").unwrap(); tx.add_code_from_hash(code_hash, Some(TX_IBC_WASM.to_string())); let mut data = vec![]; diff --git a/light_sdk/src/transaction/mod.rs b/light_sdk/src/transaction/mod.rs index df3ca44891..2c755da39f 100644 --- a/light_sdk/src/transaction/mod.rs +++ b/light_sdk/src/transaction/mod.rs @@ -1,9 +1,11 @@ +use std::str::FromStr; + use borsh::BorshSerialize; use namada_core::proto::{Section, Signature, Signer, Tx}; use namada_core::types::chain::ChainId; use namada_core::types::hash::Hash; use namada_core::types::key::common; -use namada_core::types::time::{DateTimeUtc, MIN_UTC}; +use namada_core::types::time::DateTimeUtc; pub mod account; pub mod bridge; @@ -31,7 +33,9 @@ pub(in crate::transaction) fn build_tx( code_tag: String, ) -> Tx { let mut inner_tx = Tx::new(chain_id, expiration); - inner_tx.header.timestamp = MIN_UTC; + + inner_tx.header.timestamp = + DateTimeUtc::from_str("2000-01-01T00:00:00Z").unwrap(); inner_tx.add_code_from_hash(code_hash, Some(code_tag)); inner_tx.add_data(data); From 7049bb53ae69464be9c21824e323df53680a57fa Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 22 Dec 2023 12:28:01 +0100 Subject: [PATCH 206/216] Makes internal struct of `RevealPk` private --- light_sdk/src/transaction/account.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/light_sdk/src/transaction/account.rs b/light_sdk/src/transaction/account.rs index 7768a321b9..07d180427e 100644 --- a/light_sdk/src/transaction/account.rs +++ b/light_sdk/src/transaction/account.rs @@ -54,7 +54,7 @@ impl InitAccount { /// Transaction to reveal a public key to the ledger to validate signatures of /// an implicit account -pub struct RevealPk(pub Tx); +pub struct RevealPk(Tx); impl RevealPk { /// Build a raw Reveal Public Key transaction from the given parameters From 5a62829530ab1ea8089e156c7a95d02ad9cd7f20 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 22 Dec 2023 12:53:26 +0100 Subject: [PATCH 207/216] Adds a method to serialize transactions --- light_sdk/src/transaction/account.rs | 15 +++++++ light_sdk/src/transaction/bridge.rs | 5 +++ light_sdk/src/transaction/governance.rs | 10 +++++ light_sdk/src/transaction/ibc.rs | 5 +++ light_sdk/src/transaction/pgf.rs | 10 +++++ light_sdk/src/transaction/pos.rs | 60 +++++++++++++++++++++++++ light_sdk/src/transaction/transfer.rs | 5 +++ light_sdk/src/transaction/wrapper.rs | 5 +++ 8 files changed, 115 insertions(+) diff --git a/light_sdk/src/transaction/account.rs b/light_sdk/src/transaction/account.rs index 07d180427e..146915881a 100644 --- a/light_sdk/src/transaction/account.rs +++ b/light_sdk/src/transaction/account.rs @@ -50,6 +50,11 @@ impl InitAccount { self.0, signer, signature, )) } + + /// Generates the protobuf encoding of this transaction + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes() + } } /// Transaction to reveal a public key to the ledger to validate signatures of @@ -81,6 +86,11 @@ impl RevealPk { self.0, signer, signature, )) } + + /// Generates the protobuf encoding of this transaction + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes() + } } /// Transaction to update the parameters of an established account @@ -125,4 +135,9 @@ impl UpdateAccount { self.0, signer, signature, )) } + + /// Generates the protobuf encoding of this transaction + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes() + } } diff --git a/light_sdk/src/transaction/bridge.rs b/light_sdk/src/transaction/bridge.rs index 898322e296..c41407f31e 100644 --- a/light_sdk/src/transaction/bridge.rs +++ b/light_sdk/src/transaction/bridge.rs @@ -46,4 +46,9 @@ impl BridgeTransfer { self.0, signer, signature, )) } + + /// Generates the protobuf encoding of this transaction + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes() + } } diff --git a/light_sdk/src/transaction/governance.rs b/light_sdk/src/transaction/governance.rs index 357d24973c..ca6183569e 100644 --- a/light_sdk/src/transaction/governance.rs +++ b/light_sdk/src/transaction/governance.rs @@ -61,6 +61,11 @@ impl InitProposal { self.0, signer, signature, )) } + + /// Generates the protobuf encoding of this transaction + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes() + } } /// Transaction to vote on a governance proposal @@ -105,4 +110,9 @@ impl VoteProposal { self.0, signer, signature, )) } + + /// Generates the protobuf encoding of this transaction + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes() + } } diff --git a/light_sdk/src/transaction/ibc.rs b/light_sdk/src/transaction/ibc.rs index e11a3d36b4..98a28aa226 100644 --- a/light_sdk/src/transaction/ibc.rs +++ b/light_sdk/src/transaction/ibc.rs @@ -52,4 +52,9 @@ impl IbcTransfer { self.0, signer, signature, )) } + + /// Generates the protobuf encoding of this transaction + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes() + } } diff --git a/light_sdk/src/transaction/pgf.rs b/light_sdk/src/transaction/pgf.rs index c93e3b9079..7a53a7e4ca 100644 --- a/light_sdk/src/transaction/pgf.rs +++ b/light_sdk/src/transaction/pgf.rs @@ -40,6 +40,11 @@ impl ResignSteward { self.0, signer, signature, )) } + + /// Generates the protobuf encoding of this transaction + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes() + } } /// Transaction to update a pgf steward's commission rate @@ -81,4 +86,9 @@ impl UpdateStewardCommission { self.0, signer, signature, )) } + + /// Generates the protobuf encoding of this transaction + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes() + } } diff --git a/light_sdk/src/transaction/pos.rs b/light_sdk/src/transaction/pos.rs index c4af55439b..a436e1588a 100644 --- a/light_sdk/src/transaction/pos.rs +++ b/light_sdk/src/transaction/pos.rs @@ -62,6 +62,11 @@ impl Bond { self.0, signer, signature, )) } + + /// Generates the protobuf encoding of this transaction + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes() + } } /// An unbond transaction @@ -103,6 +108,11 @@ impl Unbond { self.0, signer, signature, )) } + + /// Generates the protobuf encoding of this transaction + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes() + } } /// Transaction to initialize a new PoS validator @@ -162,6 +172,11 @@ impl BecomeValidator { self.0, signer, signature, )) } + + /// Generates the protobuf encoding of this transaction + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes() + } } /// Transaction to unjail a PoS validator @@ -192,6 +207,11 @@ impl UnjailValidator { self.0, signer, signature, )) } + + /// Generates the protobuf encoding of this transaction + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes() + } } /// Transaction to deactivate a validator @@ -222,6 +242,11 @@ impl DeactivateValidator { self.0, signer, signature, )) } + + /// Generates the protobuf encoding of this transaction + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes() + } } /// Transaction to reactivate a previously deactivated validator @@ -252,6 +277,11 @@ impl ReactivateValidator { self.0, signer, signature, )) } + + /// Generates the protobuf encoding of this transaction + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes() + } } /// Transaction to claim PoS rewards @@ -291,6 +321,11 @@ impl ClaimRewards { self.0, signer, signature, )) } + + /// Generates the protobuf encoding of this transaction + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes() + } } /// Transaction to change the validator's metadata @@ -339,6 +374,11 @@ impl ChangeMetaData { self.0, signer, signature, )) } + + /// Generates the protobuf encoding of this transaction + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes() + } } /// Transaction to modify the validator's consensus key @@ -379,6 +419,11 @@ impl ChangeConsensusKey { self.0, signer, signature, )) } + + /// Generates the protobuf encoding of this transaction + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes() + } } /// Transaction to modify the validator's commission rate @@ -415,6 +460,11 @@ impl ChangeCommission { self.0, signer, signature, )) } + + /// Generates the protobuf encoding of this transaction + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes() + } } /// Transaction to withdraw previously unstaked funds @@ -454,6 +504,11 @@ impl Withdraw { self.0, signer, signature, )) } + + /// Generates the protobuf encoding of this transaction + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes() + } } /// Transaction to redelegate @@ -497,4 +552,9 @@ impl Redelegate { self.0, signer, signature, )) } + + /// Generates the protobuf encoding of this transaction + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes() + } } diff --git a/light_sdk/src/transaction/transfer.rs b/light_sdk/src/transaction/transfer.rs index dbc9b031d2..cce45f1e91 100644 --- a/light_sdk/src/transaction/transfer.rs +++ b/light_sdk/src/transaction/transfer.rs @@ -56,4 +56,9 @@ impl Transfer { self.0, signer, signature, )) } + + /// Generates the protobuf encoding of this transaction + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes() + } } diff --git a/light_sdk/src/transaction/wrapper.rs b/light_sdk/src/transaction/wrapper.rs index 642d0398df..eb4373a0c2 100644 --- a/light_sdk/src/transaction/wrapper.rs +++ b/light_sdk/src/transaction/wrapper.rs @@ -56,4 +56,9 @@ impl Wrapper { pub fn validate_tx(&self) -> Result, TxError> { self.0.validate_tx() } + + /// Generates the protobuf encoding of this transaction + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes() + } } From fcf45af54cde10ad886d2146835a5aa3b03003d8 Mon Sep 17 00:00:00 2001 From: mariari Date: Wed, 8 Nov 2023 10:46:32 +0800 Subject: [PATCH 208/216] repace manua path with functions. Start abstraction on file creation Before the code would bind a variable just for creating a path, we abstract this out with some functions Further I started to remove the rather verbose file creation --- apps/src/lib/node/ledger/tendermint_node.rs | 70 +++++++++++++-------- 1 file changed, 45 insertions(+), 25 deletions(-) diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index 9845b65c50..a9ebb0e0ba 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -256,18 +256,13 @@ pub async fn write_validator_key_async( home_dir: impl AsRef, consensus_key: &common::SecretKey, ) { - let home_dir = home_dir.as_ref(); - let path = home_dir.join("config").join("priv_validator_key.json"); + let path = validator_key(home_dir); // Make sure the dir exists let wallet_dir = path.parent().unwrap(); fs::create_dir_all(wallet_dir) .await .expect("Couldn't create private validator key directory"); - let mut file = OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(&path) + let mut file = ensure_empty_async(&path) .await .expect("Couldn't create private validator key file"); let key = validator_key_to_json(consensus_key).unwrap(); @@ -283,17 +278,12 @@ pub fn write_validator_key( home_dir: impl AsRef, consensus_key: &common::SecretKey, ) { - let home_dir = home_dir.as_ref(); - let path = home_dir.join("config").join("priv_validator_key.json"); + let path = validator_key(home_dir); // Make sure the dir exists let wallet_dir = path.parent().unwrap(); std::fs::create_dir_all(wallet_dir) .expect("Couldn't create private validator key directory"); - let file = std::fs::OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(&path) + let file = ensure_empty(&path) .expect("Couldn't create private validator key file"); let key = validator_key_to_json(consensus_key).unwrap(); serde_json::to_writer_pretty(file, &key) @@ -302,17 +292,12 @@ pub fn write_validator_key( /// Initialize validator private state for Tendermint pub fn write_validator_state(home_dir: impl AsRef) { - let home_dir = home_dir.as_ref(); - let path = home_dir.join("data").join("priv_validator_state.json"); + let path = validator_state(home_dir); // Make sure the dir exists let wallet_dir = path.parent().unwrap(); std::fs::create_dir_all(wallet_dir) .expect("Couldn't create private validator state directory"); - let file = std::fs::OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(&path) + let file = ensure_empty(&path) .expect("Couldn't create private validator state file"); let state = json!({ "height": "0", @@ -349,8 +334,7 @@ async fn update_tendermint_config( home_dir: impl AsRef, mut config: TendermintConfig, ) -> Result<()> { - let home_dir = home_dir.as_ref(); - let path = home_dir.join("config").join("config.toml"); + let path = configuration(home_dir); config.moniker = Moniker::from_str(&format!("{}-{}", config.moniker, namada_version())) @@ -409,8 +393,7 @@ async fn write_tm_genesis( chain_id: ChainId, genesis_time: DateTimeUtc, ) { - let home_dir = home_dir.as_ref(); - let path = home_dir.join("config").join("genesis.json"); + let path = genesis(home_dir); let mut file = File::open(&path).await.unwrap_or_else(|err| { panic!( "Couldn't open the genesis file at {:?}, error: {}", @@ -461,3 +444,40 @@ async fn write_tm_genesis( .await .expect("Couldn't write the CometBFT genesis file"); } + +async fn ensure_empty_async(path: &PathBuf) -> std::io::Result { + OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(path) + .await +} + +fn ensure_empty(path: &PathBuf) -> std::io::Result { + std::fs::OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(path) +} +fn validator_key(home_dir: impl AsRef) -> PathBuf { + home_dir + .as_ref() + .join("config") + .join("priv_validator_key.json") +} + +fn validator_state(home_dir: impl AsRef) -> PathBuf { + home_dir + .as_ref() + .join("data") + .join("priv_validator_state.json") +} + +fn configuration(home_dir: impl AsRef) -> PathBuf { + home_dir.as_ref().join("config").join("config.toml") +} +fn genesis(home_dir: impl AsRef) -> PathBuf { + home_dir.as_ref().join("config").join("genesis.json") +} From c65ca9907255430ea25350c071f3f98f7ab94113 Mon Sep 17 00:00:00 2001 From: mariari Date: Wed, 8 Nov 2023 10:54:21 +0800 Subject: [PATCH 209/216] Remove write_validator This was an unused function that simply repeated the logic of the non async version --- apps/src/lib/node/ledger/tendermint_node.rs | 33 +-------------------- 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index a9ebb0e0ba..61633dfc3c 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -11,7 +11,7 @@ use namada::types::time::DateTimeUtc; use serde_json::json; use sha2::{Digest, Sha256}; use thiserror::Error; -use tokio::fs::{self, File, OpenOptions}; +use tokio::fs::{File, OpenOptions}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::process::Command; @@ -251,28 +251,6 @@ fn validator_key_to_json( })) } -/// Initialize validator private key for Tendermint -pub async fn write_validator_key_async( - home_dir: impl AsRef, - consensus_key: &common::SecretKey, -) { - let path = validator_key(home_dir); - // Make sure the dir exists - let wallet_dir = path.parent().unwrap(); - fs::create_dir_all(wallet_dir) - .await - .expect("Couldn't create private validator key directory"); - let mut file = ensure_empty_async(&path) - .await - .expect("Couldn't create private validator key file"); - let key = validator_key_to_json(consensus_key).unwrap(); - let data = serde_json::to_vec_pretty(&key) - .expect("Couldn't encode private validator key file"); - file.write_all(&data[..]) - .await - .expect("Couldn't write private validator key file"); -} - /// Initialize validator private key for Tendermint pub fn write_validator_key( home_dir: impl AsRef, @@ -445,15 +423,6 @@ async fn write_tm_genesis( .expect("Couldn't write the CometBFT genesis file"); } -async fn ensure_empty_async(path: &PathBuf) -> std::io::Result { - OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(path) - .await -} - fn ensure_empty(path: &PathBuf) -> std::io::Result { std::fs::OpenOptions::new() .create(true) From 7d372ea4c157972d0f056af3477fb5fb360560bf Mon Sep 17 00:00:00 2001 From: mariari Date: Wed, 8 Nov 2023 11:48:43 +0800 Subject: [PATCH 210/216] Abstract out file error messages with the error type Instead of expecting right away and placing a literal string, we can instead abstract the commonalities of the strings into the error type This shows off a lot of similarities between write_* functions, that can now be cleanly abstracted --- apps/src/lib/client/tx.rs | 5 +- apps/src/lib/client/utils.rs | 5 +- apps/src/lib/node/ledger/tendermint_node.rs | 51 ++++++++++++++------- 3 files changed, 41 insertions(+), 20 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 74ef2bab4c..b46b0a3865 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -798,10 +798,11 @@ pub async fn submit_become_validator( &wallet .find_key_by_pk(&consensus_key, None) .expect("unable to find consensus key pair in the wallet"), - ); + ) + .unwrap(); // To avoid wallet deadlocks in following operations drop(wallet); - tendermint_node::write_validator_state(tendermint_home); + tendermint_node::write_validator_state(tendermint_home).unwrap(); // Write Namada config stuff or figure out how to do the above // tendermint_node things two epochs in the future!!! diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 01e764c0a2..ed61e31a66 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -291,13 +291,14 @@ pub async fn join_network( ); let tm_home_dir = chain_dir.join(config::COMETBFT_DIR); // Write consensus key to tendermint home - tendermint_node::write_validator_key(&tm_home_dir, &consensus_key); + tendermint_node::write_validator_key(&tm_home_dir, &consensus_key) + .unwrap(); // Write tendermint node key write_tendermint_node_key(&tm_home_dir, tendermint_node_key); // Pre-initialize tendermint validator state - tendermint_node::write_validator_state(&tm_home_dir); + tendermint_node::write_validator_state(&tm_home_dir).unwrap(); } else { println!( "No validator keys are being used. Make sure you didn't forget to \ diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index 61633dfc3c..1da3f5ff98 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -46,6 +46,12 @@ pub enum Error { RollBack(String), #[error("Failed to convert to String: {0:?}")] TendermintPath(std::ffi::OsString), + #[error("Couldn't write {0}")] + CantWrite(String), + #[error("Couldn't create {0}")] + CantCreate(String), + #[error("Couldn't encode {0}")] + CantEncode(&'static str), } pub type Result = std::result::Result; @@ -92,7 +98,7 @@ pub async fn run( panic!("Tendermint failed to initialize with {:#?}", output); } - write_tm_genesis(&home_dir, chain_id, genesis_time).await; + write_tm_genesis(&home_dir, chain_id, genesis_time).await?; update_tendermint_config(&home_dir, config.cometbft).await?; @@ -255,35 +261,34 @@ fn validator_key_to_json( pub fn write_validator_key( home_dir: impl AsRef, consensus_key: &common::SecretKey, -) { +) -> Result<()> { let path = validator_key(home_dir); // Make sure the dir exists let wallet_dir = path.parent().unwrap(); std::fs::create_dir_all(wallet_dir) - .expect("Couldn't create private validator key directory"); - let file = ensure_empty(&path) - .expect("Couldn't create private validator key file"); + .map_err(|_| Error::CantCreate(KEY_DIR))?; + let file = ensure_empty(&path).map_err(|_| Error::CantCreate(KEY_FILE))?; let key = validator_key_to_json(consensus_key).unwrap(); serde_json::to_writer_pretty(file, &key) - .expect("Couldn't write private validator key file"); + .map_err(|_| Error::CantWrite(KEY_FILE)) } /// Initialize validator private state for Tendermint -pub fn write_validator_state(home_dir: impl AsRef) { +pub fn write_validator_state(home_dir: impl AsRef) -> Result<()> { let path = validator_state(home_dir); // Make sure the dir exists let wallet_dir = path.parent().unwrap(); std::fs::create_dir_all(wallet_dir) - .expect("Couldn't create private validator state directory"); - let file = ensure_empty(&path) - .expect("Couldn't create private validator state file"); + .map_err(|_| Error::CantCreate(STATE_DIR))?; + let file = + ensure_empty(&path).map_err(|_| Error::CantCreate(STATE_FILE))?; let state = json!({ "height": "0", "round": 0, "step": 0 }); serde_json::to_writer_pretty(file, &state) - .expect("Couldn't write private validator state file"); + .map_err(|_| Error::CantWrite(STATE_FILE)) } /// Length of a Tendermint Node ID in bytes @@ -370,7 +375,7 @@ async fn write_tm_genesis( home_dir: impl AsRef, chain_id: ChainId, genesis_time: DateTimeUtc, -) { +) -> Result<()> { let path = genesis(home_dir); let mut file = File::open(&path).await.unwrap_or_else(|err| { panic!( @@ -417,10 +422,14 @@ async fn write_tm_genesis( ) }); let data = serde_json::to_vec_pretty(&genesis) - .expect("Couldn't encode the CometBFT genesis file"); - file.write_all(&data[..]) - .await - .expect("Couldn't write the CometBFT genesis file"); + .map_err(|_| Error::CantEncode(GENESIS_FILE))?; + file.write_all(&data[..]).await.map_err(|err| { + Error::CantWrite(format!( + "{} to {}. Caused by {err}", + GENESIS_FILE, + path.to_string_lossy() + )) + }) } fn ensure_empty(path: &PathBuf) -> std::io::Result { @@ -450,3 +459,13 @@ fn configuration(home_dir: impl AsRef) -> PathBuf { fn genesis(home_dir: impl AsRef) -> PathBuf { home_dir.as_ref().join("config").join("genesis.json") } + +// Constant strings to avoid repeating our magic words + +const KEY_FILE: &str = "private validator key file"; +const KEY_DIR: &str = "private validator key directory"; + +const STATE_FILE: &str = "private validator state file"; +const STATE_DIR: &str = "private validator state directory"; + +const GENESIS_FILE: &str = "CometBFT genesis file"; From 0712c33dc9c66f2c327e407329b76041f090f7e7 Mon Sep 17 00:00:00 2001 From: mariari Date: Wed, 8 Nov 2023 12:07:09 +0800 Subject: [PATCH 211/216] Abstract out the commanality in write tendermint After the previous commits the write_validator_state and write_validator_key were basically the same modulo some error parameters and minor state details --- apps/src/lib/node/ledger/tendermint_node.rs | 51 ++++++++++++++------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index 1da3f5ff98..ad15fd0b78 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -262,33 +262,50 @@ pub fn write_validator_key( home_dir: impl AsRef, consensus_key: &common::SecretKey, ) -> Result<()> { - let path = validator_key(home_dir); - // Make sure the dir exists - let wallet_dir = path.parent().unwrap(); - std::fs::create_dir_all(wallet_dir) - .map_err(|_| Error::CantCreate(KEY_DIR))?; - let file = ensure_empty(&path).map_err(|_| Error::CantCreate(KEY_FILE))?; let key = validator_key_to_json(consensus_key).unwrap(); - serde_json::to_writer_pretty(file, &key) - .map_err(|_| Error::CantWrite(KEY_FILE)) + write_validator(validator_key(home_dir), KEY_DIR, KEY_FILE, key) } /// Initialize validator private state for Tendermint pub fn write_validator_state(home_dir: impl AsRef) -> Result<()> { - let path = validator_state(home_dir); - // Make sure the dir exists - let wallet_dir = path.parent().unwrap(); - std::fs::create_dir_all(wallet_dir) - .map_err(|_| Error::CantCreate(STATE_DIR))?; - let file = - ensure_empty(&path).map_err(|_| Error::CantCreate(STATE_FILE))?; let state = json!({ "height": "0", "round": 0, "step": 0 }); - serde_json::to_writer_pretty(file, &state) - .map_err(|_| Error::CantWrite(STATE_FILE)) + write_validator(validator_state(home_dir), STATE_DIR, STATE_FILE, state) +} + +/// Abstract over the initialization of validator data for Tendermint +fn write_validator( + path: PathBuf, + err_dir: &'static str, + err_file: &'static str, + data: serde_json::Value, +) -> Result<()> { + let parent_dir = path.parent().unwrap(); + // Make sure the dir exists + std::fs::create_dir_all(parent_dir).map_err(|err| { + Error::CantCreate(format!( + "{} at {}. Caused by {err}", + err_dir, + parent_dir.to_string_lossy() + )) + })?; + let file = ensure_empty(&path).map_err(|err| { + Error::CantCreate(format!( + "{} at {}. Caused by {err}", + err_dir, + path.to_string_lossy() + )) + })?; + serde_json::to_writer_pretty(file, &data).map_err(|err| { + Error::CantWrite(format!( + "{} to {}. Caused by {err}", + err_file, + path.to_string_lossy() + )) + }) } /// Length of a Tendermint Node ID in bytes From 4f3bb08a989c462ca3645c2e48827ace4acfa1ea Mon Sep 17 00:00:00 2001 From: mariari Date: Wed, 8 Nov 2023 14:22:31 +0800 Subject: [PATCH 212/216] Abstract `tendermint_node::run` into 3 distinct phases Before the function had 3 phases, that was jumbled into 1 whole. There was: 1. setup 2. running 3. response handling Since this structure is rather well defined, and not much data is shared between these phases, we just turn the function into dispatching into functions that reprsent each stage --- apps/src/lib/node/ledger/tendermint_node.rs | 84 ++++++++++++++++----- 1 file changed, 64 insertions(+), 20 deletions(-) diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index ad15fd0b78..87616d9df2 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -13,7 +13,9 @@ use sha2::{Digest, Sha256}; use thiserror::Error; use tokio::fs::{File, OpenOptions}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; -use tokio::process::Command; +use tokio::process::{Child, Command}; +use tokio::sync::oneshot::error::RecvError; +use tokio::sync::oneshot::{Receiver, Sender}; use crate::cli::namada_version; use crate::config; @@ -80,10 +82,26 @@ pub async fn run( genesis_time: DateTimeUtc, proxy_app_address: String, config: config::Ledger, - abort_recv: tokio::sync::oneshot::Receiver< - tokio::sync::oneshot::Sender<()>, - >, + abort_recv: Receiver>, ) -> Result<()> { + let (home_dir_string, tendermint_path) = + initalize_config(home_dir, chain_id, genesis_time, config).await?; + let tendermint_node = + start_node(proxy_app_address, home_dir_string, tendermint_path)?; + + tracing::info!("CometBFT node started"); + + handle_node_response(tendermint_node, abort_recv).await +} + +/// Setup the tendermint configuration. We return the tendermint path and home +/// directory +async fn initalize_config( + home_dir: PathBuf, + chain_id: ChainId, + genesis_time: DateTimeUtc, + config: config::Ledger, +) -> Result<(String, String)> { let home_dir_string = home_dir.to_string_lossy().to_string(); let tendermint_path = from_env_or_default()?; let mode = config.shell.tendermint_mode.to_str().to_owned(); @@ -101,8 +119,16 @@ pub async fn run( write_tm_genesis(&home_dir, chain_id, genesis_time).await?; update_tendermint_config(&home_dir, config.cometbft).await?; + Ok((home_dir_string, tendermint_path)) +} - let mut tendermint_node = Command::new(&tendermint_path); +/// Startup the node +fn start_node( + proxy_app_address: String, + home_dir_string: String, + tendermint_path: String, +) -> Result { + let mut tendermint_node = Command::new(tendermint_path); tendermint_node.args([ "start", "--proxy_app", @@ -119,12 +145,17 @@ pub async fn run( tendermint_node.stdout(Stdio::null()); } - let mut tendermint_node = tendermint_node + tendermint_node .kill_on_drop(true) .spawn() - .map_err(Error::StartUp)?; - tracing::info!("CometBFT node started"); + .map_err(Error::StartUp) +} +/// Handle the node response +async fn handle_node_response( + mut tendermint_node: Child, + abort_recv: Receiver>, +) -> Result<()> { tokio::select! { status = tendermint_node.wait() => { match status { @@ -141,22 +172,30 @@ pub async fn run( } }, resp_sender = abort_recv => { - match resp_sender { - Ok(resp_sender) => { - tracing::info!("Shutting down Tendermint node..."); - tendermint_node.kill().await.unwrap(); - resp_sender.send(()).unwrap(); - }, - Err(err) => { - tracing::error!("The Tendermint abort sender has unexpectedly dropped: {}", err); - tracing::info!("Shutting down Tendermint node..."); - tendermint_node.kill().await.unwrap(); - } - } + handle_abort(resp_sender, &mut tendermint_node).await; Ok(()) } } } +// Handle tendermint aborting +async fn handle_abort( + resp_sender: std::result::Result, RecvError>, + node: &mut Child, +) { + match resp_sender { + Ok(resp_sender) => { + tracing_kill(node).await; + resp_sender.send(()).unwrap(); + } + Err(err) => { + tracing::error!( + "The Tendermint abort sender has unexpectedly dropped: {}", + err + ); + tracing_kill(node).await; + } + } +} pub fn reset(tendermint_dir: impl AsRef) -> Result<()> { let tendermint_path = from_env_or_default()?; @@ -449,6 +488,11 @@ async fn write_tm_genesis( }) } +async fn tracing_kill(node: &mut Child) { + tracing::info!("Shutting down Tendermint node..."); + node.kill().await.unwrap(); +} + fn ensure_empty(path: &PathBuf) -> std::io::Result { std::fs::OpenOptions::new() .create(true) From 73daaee26757cd073de6f1186955ab3ba8930bb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 22 Dec 2023 12:51:15 +0000 Subject: [PATCH 213/216] changelog: add #2127 --- .changelog/unreleased/improvements/2127-basic-abstraction.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/2127-basic-abstraction.md diff --git a/.changelog/unreleased/improvements/2127-basic-abstraction.md b/.changelog/unreleased/improvements/2127-basic-abstraction.md new file mode 100644 index 0000000000..8ae68fafc2 --- /dev/null +++ b/.changelog/unreleased/improvements/2127-basic-abstraction.md @@ -0,0 +1,2 @@ +- Refactored module dealing with Tendermint configuration. + ([\#2127](https://github.com/anoma/namada/pull/2127)) \ No newline at end of file From c6fb6278466d6e56fb1749d130529fb39e2cf865 Mon Sep 17 00:00:00 2001 From: yito88 Date: Fri, 22 Dec 2023 20:41:42 +0100 Subject: [PATCH 214/216] add SDK changelog --- .changelog/unreleased/SDK/2308-fix-ibc-gen-shielded.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/SDK/2308-fix-ibc-gen-shielded.md diff --git a/.changelog/unreleased/SDK/2308-fix-ibc-gen-shielded.md b/.changelog/unreleased/SDK/2308-fix-ibc-gen-shielded.md new file mode 100644 index 0000000000..5e65bcff6f --- /dev/null +++ b/.changelog/unreleased/SDK/2308-fix-ibc-gen-shielded.md @@ -0,0 +1,2 @@ +- ibc-gen-shielded can set non-Namada token + ([\#2308](https://github.com/anoma/namada/issues/2308)) \ No newline at end of file From ec2488d046e6c47bb5514ec333b0781633f08b36 Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 27 Dec 2023 12:07:48 +0100 Subject: [PATCH 215/216] fix e2e test --- tests/src/e2e/ibc_tests.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index d69d3f1844..ae549725cf 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -203,7 +203,7 @@ fn run_ledger_ibc() -> Result<()> { &port_id_b, &channel_id_b, )?; - check_shielded_balances(&port_id_b, &channel_id_b, &test_b)?; + check_shielded_balances(&port_id_b, &channel_id_b, &test_a, &test_b)?; // Skip tests for closing a channel and timeout_on_close since the transfer // channel cannot be closed @@ -1024,6 +1024,8 @@ fn shielded_transfer( // It will send 10 BTC from Chain A to PA(B) on Chain B let rpc_b = get_actor_rpc(test_b, &Who::Validator(0)); let output_folder = test_b.test_dir.path().to_string_lossy(); + // PA(B) on Chain B will receive BTC on chain A + let token_addr = find_address(test_a, BTC)?; let amount = Amount::native_whole(10).to_string_native(); let args = [ "ibc-gen-shielded", @@ -1032,7 +1034,7 @@ fn shielded_transfer( "--target", AB_PAYMENT_ADDRESS, "--token", - BTC, + &token_addr.to_string(), "--amount", &amount, "--port-id", @@ -1526,16 +1528,19 @@ fn check_balances_after_back( fn check_shielded_balances( dest_port_id: &PortId, dest_channel_id: &ChannelId, + test_a: &Test, test_b: &Test, ) -> Result<()> { // Check the balance on Chain B let rpc_b = get_actor_rpc(test_b, &Who::Validator(0)); + // PA(B) on Chain B has received BTC on chain A + let token_addr = find_address(test_a, BTC)?.to_string(); let query_args = vec![ "balance", "--owner", AB_VIEWING_KEY, "--token", - BTC, + &token_addr, "--no-conversions", "--node", &rpc_b, From 57f94e94ea61beaa424920d8c70f674e710abbc3 Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 27 Dec 2023 16:22:05 +0100 Subject: [PATCH 216/216] add SDK changelog --- .changelog/unreleased/SDK/2321-ibc_shielded_transfer.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/SDK/2321-ibc_shielded_transfer.md diff --git a/.changelog/unreleased/SDK/2321-ibc_shielded_transfer.md b/.changelog/unreleased/SDK/2321-ibc_shielded_transfer.md new file mode 100644 index 0000000000..a90b2c4075 --- /dev/null +++ b/.changelog/unreleased/SDK/2321-ibc_shielded_transfer.md @@ -0,0 +1,2 @@ +- ibc-transfer can set a spending key to the source + ([\#2321](https://github.com/anoma/namada/issues/2321)) \ No newline at end of file

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ℑ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 O0f}1+*KE)S8UGL4dM%6q delta 146319 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 diff --git a/wasm_for_tests/tx_mint_tokens.wasm b/wasm_for_tests/tx_mint_tokens.wasm index e79d206dfd61c87bbf7707ac7a33db09682c92f7..761e1a14e90d92dd9527555320a3569913e76ea7 100755 GIT binary patch delta 95 zcmX@LT; o7V)x3DX>jWNS||V*+AkAZ7t#Rv-q6v2RZ^<4Cy+0H5F(2LJ#7 delta 95 zcmX@LT;m=Fk@0>WNl9~V*+AkAZ7t#5T6Z**|(>eairV@0H6OD2LJ#7 diff --git a/wasm_for_tests/tx_no_op.wasm b/wasm_for_tests/tx_no_op.wasm index cf752705ea14e2ec079c5c2bbb65a335545b7849..24be8e9b13a1513ac47d364a5e825545e1db15d7 100755 GIT binary patch delta 109135 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%0D%DeY`h}=rT+5H2Kd1$Gd~+KufKRjym4hQpR=D+ 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 delta 127476 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 diff --git a/wasm_for_tests/tx_read_storage_key.wasm b/wasm_for_tests/tx_read_storage_key.wasm index 1fcb89063ddc70f530ccd85151679a2b9268f892..e85b8aa6e5a2596bdf417be63ea7f067b44cabc0 100755 GIT binary patch delta 136576 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