From 66082ca3488e7ad78149e05631dccd09be03c961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc?= Date: Sat, 9 Mar 2024 20:56:19 +0100 Subject: [PATCH 01/58] Preallocate addresses in GetAddr based on nNodes > make && ./src/bench/bench_bitcoin --filter=AddrManGetAddr --min-time=1000 Before: ``` | ns/op | op/s | err% | total | benchmark |--------------------:|--------------------:|--------:|----------:|:---------- | 76,852.79 | 13,011.89 | 0.4% | 1.07 | `AddrManGetAddr` | 76,598.21 | 13,055.14 | 0.2% | 1.07 | `AddrManGetAddr` | 76,296.32 | 13,106.79 | 0.1% | 1.07 | `AddrManGetAddr` ``` After: ``` | ns/op | op/s | err% | total | benchmark |--------------------:|--------------------:|--------:|----------:|:---------- | 65,966.97 | 15,159.10 | 0.3% | 1.07 | `AddrManGetAddr` | 66,075.40 | 15,134.23 | 0.2% | 1.06 | `AddrManGetAddr` | 66,306.34 | 15,081.51 | 0.3% | 1.06 | `AddrManGetAddr` ``` --- src/addrman.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/addrman.cpp b/src/addrman.cpp index d0b820ee65..a351b6fbc5 100644 --- a/src/addrman.cpp +++ b/src/addrman.cpp @@ -818,6 +818,7 @@ std::vector AddrManImpl::GetAddr_(size_t max_addresses, size_t max_pct // gather a list of random nodes, skipping those of low quality const auto now{Now()}; std::vector addresses; + addresses.reserve(nNodes); for (unsigned int n = 0; n < vRandom.size(); n++) { if (addresses.size() >= nNodes) break; From bdad0243be80c4c72f8cefd1233fd7c057a1945b Mon Sep 17 00:00:00 2001 From: brunoerg Date: Fri, 24 May 2024 09:25:35 -0300 Subject: [PATCH 02/58] rpc, net: getrawaddrman "mapped_as" follow-ups --- src/rpc/net.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 1119a3e668..be9962e458 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -1102,12 +1102,12 @@ static RPCHelpMan getaddrmaninfo() }; } -UniValue AddrmanEntryToJSON(const AddrInfo& info, CConnman& connman) +UniValue AddrmanEntryToJSON(const AddrInfo& info, const CConnman& connman) { UniValue ret(UniValue::VOBJ); ret.pushKV("address", info.ToStringAddr()); - const auto mapped_as{connman.GetMappedAS(info)}; - if (mapped_as != 0) { + const uint32_t mapped_as{connman.GetMappedAS(info)}; + if (mapped_as) { ret.pushKV("mapped_as", mapped_as); } ret.pushKV("port", info.GetPort()); @@ -1116,14 +1116,14 @@ UniValue AddrmanEntryToJSON(const AddrInfo& info, CConnman& connman) ret.pushKV("network", GetNetworkName(info.GetNetClass())); ret.pushKV("source", info.source.ToStringAddr()); ret.pushKV("source_network", GetNetworkName(info.source.GetNetClass())); - const auto source_mapped_as{connman.GetMappedAS(info.source)}; - if (source_mapped_as != 0) { + const uint32_t source_mapped_as{connman.GetMappedAS(info.source)}; + if (source_mapped_as) { ret.pushKV("source_mapped_as", source_mapped_as); } return ret; } -UniValue AddrmanTableToJSON(const std::vector>& tableInfos, CConnman& connman) +UniValue AddrmanTableToJSON(const std::vector>& tableInfos, const CConnman& connman) { UniValue table(UniValue::VOBJ); for (const auto& e : tableInfos) { From def6dd0c597f2c7b0c55910792e646b8ff93e36c Mon Sep 17 00:00:00 2001 From: fanquake Date: Mon, 11 Sep 2023 11:11:02 +0100 Subject: [PATCH 03/58] depends: sqlite 3.46.1 --- depends/packages/sqlite.mk | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/depends/packages/sqlite.mk b/depends/packages/sqlite.mk index 15bc0f4d7a..d803a375a6 100644 --- a/depends/packages/sqlite.mk +++ b/depends/packages/sqlite.mk @@ -1,8 +1,8 @@ package=sqlite -$(package)_version=3380500 -$(package)_download_path=https://sqlite.org/2022/ +$(package)_version=3460100 +$(package)_download_path=https://sqlite.org/2024/ $(package)_file_name=sqlite-autoconf-$($(package)_version).tar.gz -$(package)_sha256_hash=5af07de982ba658fd91a03170c945f99c971f6955bc79df3266544373e39869c +$(package)_sha256_hash=67d3fe6d268e6eaddcae3727fce58fcc8e9c53869bdd07a0c61e38ddf2965071 define $(package)_set_vars $(package)_config_opts=--disable-shared --disable-readline --disable-dynamic-extensions --enable-option-checking From 77b2923f87131a407f7d4193c54db22375130403 Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Mon, 14 Aug 2023 11:24:16 +0200 Subject: [PATCH 04/58] Add Signet launch shortcut for Windows cd src/qt/res/icons convert bitcoin.png -modulate 100,87,119.4 -define icon:auto-resize="256,48,32,16" bitcoin_signet.ico This commit also removes the 64-bit mention from testnet. --- share/setup.nsi.in | 6 ++++-- src/qt/res/bitcoin-qt-res.rc | 1 + src/qt/res/icons/bitcoin_signet.ico | Bin 0 -> 44428 bytes 3 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 src/qt/res/icons/bitcoin_signet.ico diff --git a/share/setup.nsi.in b/share/setup.nsi.in index 2ce798bd2d..6a6b18a55c 100644 --- a/share/setup.nsi.in +++ b/share/setup.nsi.in @@ -95,7 +95,8 @@ Section -post SEC0001 !insertmacro MUI_STARTMENU_WRITE_BEGIN Application CreateDirectory $SMPROGRAMS\$StartMenuGroup CreateShortcut "$SMPROGRAMS\$StartMenuGroup\$(^Name).lnk" $INSTDIR\@BITCOIN_GUI_NAME@@EXEEXT@ - CreateShortcut "$SMPROGRAMS\$StartMenuGroup\@PACKAGE_NAME@ (testnet, 64-bit).lnk" "$INSTDIR\@BITCOIN_GUI_NAME@@EXEEXT@" "-testnet" "$INSTDIR\@BITCOIN_GUI_NAME@@EXEEXT@" 1 + CreateShortcut "$SMPROGRAMS\$StartMenuGroup\@PACKAGE_NAME@ (testnet).lnk" "$INSTDIR\@BITCOIN_GUI_NAME@@EXEEXT@" "-testnet" "$INSTDIR\@BITCOIN_GUI_NAME@@EXEEXT@" 1 + CreateShortcut "$SMPROGRAMS\$StartMenuGroup\@PACKAGE_NAME@ (test signet).lnk" "$INSTDIR\@BITCOIN_GUI_NAME@@EXEEXT@" "-signet" "$INSTDIR\@BITCOIN_GUI_NAME@@EXEEXT@" 2 CreateShortcut "$SMPROGRAMS\$StartMenuGroup\Uninstall $(^Name).lnk" $INSTDIR\uninstall.exe !insertmacro MUI_STARTMENU_WRITE_END WriteRegStr HKCU "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" DisplayName "$(^Name)" @@ -140,7 +141,8 @@ Section -un.post UNSEC0001 DeleteRegKey HKCU "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" Delete /REBOOTOK "$SMPROGRAMS\$StartMenuGroup\Uninstall $(^Name).lnk" Delete /REBOOTOK "$SMPROGRAMS\$StartMenuGroup\$(^Name).lnk" - Delete /REBOOTOK "$SMPROGRAMS\$StartMenuGroup\@PACKAGE_NAME@ (testnet, 64-bit).lnk" + Delete /REBOOTOK "$SMPROGRAMS\$StartMenuGroup\@PACKAGE_NAME@ (testnet).lnk" + Delete /REBOOTOK "$SMPROGRAMS\$StartMenuGroup\@PACKAGE_NAME@ (test signet).lnk" Delete /REBOOTOK "$SMSTARTUP\Bitcoin.lnk" Delete /REBOOTOK $INSTDIR\uninstall.exe Delete /REBOOTOK $INSTDIR\debug.log diff --git a/src/qt/res/bitcoin-qt-res.rc b/src/qt/res/bitcoin-qt-res.rc index e590b407b0..18bf877fc2 100644 --- a/src/qt/res/bitcoin-qt-res.rc +++ b/src/qt/res/bitcoin-qt-res.rc @@ -1,5 +1,6 @@ IDI_ICON1 ICON DISCARDABLE "icons/bitcoin.ico" IDI_ICON2 ICON DISCARDABLE "icons/bitcoin_testnet.ico" +IDI_ICON3 ICON DISCARDABLE "icons/bitcoin_signet.ico" #include // needed for VERSIONINFO #include "../../clientversion.h" // holds the needed client version information diff --git a/src/qt/res/icons/bitcoin_signet.ico b/src/qt/res/icons/bitcoin_signet.ico new file mode 100644 index 0000000000000000000000000000000000000000..fb9be5153b05851d74af13a717ec082d2abb2359 GIT binary patch literal 44428 zcmafaWmFtZ6YlI{i!bi5XduBo1Xl&J&&qBPXxaj__|o}1z-D#&R4@9O^!2>Nqj;#O<}0B*X9GLkx< z7LM9KBADd;xt>Z&kN=xrUUYfAqb;iVB3TnmFN4Q0Ki5Au*B=ltZm}7P8-Pd7-MO=a zII^{J2#&47C1nMqq!bVUJ$)$nW72W{vM9r^5cse`+s3Y5=uMKe65-n?! zqsMM$V~lk>XV_^&afi@=xf?a`*QsuE)Qo0)YqAOY#_t4tR=p0psGr-7h)-=EuEz_x z#%9zsZ4!SP2d9#K4sPbOit8oc#K8dw;4(09GFVYDjr?M2P{9zaL=V@{eI=xPiGMLS zb}@nQQ_Ml(!+Czmo8OR+JFn%Pyi>3>UB;GMsy?lRVf&7WG*&uKf1Bu$RmO^L@i8zj z>GM@K^{Y0{HXD%SB4gzV2V1$2Gh)Q_xIs|Ckm`c^Pwki>$LQGw|Qf6 z@O@sy)0Jn6)CIb|A9uI1YR#n0^z*s5Ksy!7j|x>2?XePL_>+idLm`4{@fU`_SDRoW z?{ze=?3l`V(sI6Fjk04t*J*haUg3#N7KK|t`-7ACee-6iwN!Cf%rII^31ONCKuABP z+571$F$=kYiL7kjOBu@~Cc3Xl__yM3G&wd;=ZY8H&zq1vPwm?w>JqR09uny?$XCv` z&J+RhLoGzGA~b0Lk0*`!xszt3@)Vr!Wp7j1k2u=zVh_knU5}FLsTw`Lzp0AMnw%~d z6hW((g9Ku3K7qSFLI}~5h<=m134vd4S0VUAN8dtWgt}T|#mNt$`r8;U`cDs|te&)6 zKdOn||Nf{>+j4D>5g{kf@+y)U?d!z>G{i6%5A+^)YI4(4hKxHg^ZL?*Wc6PhhjK-` z>wJnQRW#WpeigRT^0~wMr!tCbA5c6I6Xd1FLhPBuY!-A*u3b0+n7}ceji9ZH4@}A4dALsk{@-d|1k{ z$;E`Hf2Hrmf~Rrgm06QVSW}yC&=SkizEOXv7%Dyjm10nQRb1D^xxc3Z3fqpm(XIaI z7bD?%ScTk;8yN6fx?RQiCw3mfm4ut4qA8C4rCPq#R-O2R3^LE@Umr!xT^S{ zyoir`T*F2-IA7=C(@jT7DnKr+CY+$86?AWY0~v974uvrF5p@Q?zT5HF&U?Y;0prca zoE<+-zA+E)-bv|Sf%}J}A-d2oD?_d)l^~;5!}sq!a`Jt1dG{P7yj8?bZy59N1I{Ss- zIO2xkY%|rYx%6cDO^1=n#$NnlP}H(Rn00SisAIp2zB%VH=DhaNYl6b877b?zV2 ziu~+&oFXE+ei~jC>wK)1kBf(?LX!Hjr6H7vvB)~pgmi=0KE8Kbx?Rt-ZGR(Tanx2jI6)D0eSba3_yvP-(^uG8-rC(sqzSGF(hUgV^BGdb6SOLU8%YY8`hGrcJ7P&K0ualuNKvo&SvQX?6@qq zW|_h1-Bq`#nc*&l@iXjV5>Yo`#6^0b%ii5=yXVh?)beC!SFBB2qF(}eEB~#;dRy#K z_18^VFicFjB-({`1(|b*`W|w3_|2xquiiG&`QM>sld2@LpwrwV&~48LRX^Vg(yW4D z0k?ssb$3O9u1BN8`X!C%krG29k>>aYa)f-x=+*f2Dk%6AC?V1T^{0n_q;Do1k*-^D z>JOt8{@hWB3d+#V)1S6X1j0n}E@G|{QV;fKHC}5Uk?#&(31$8a{KFCJcmNB?>mn=~ zfphg+S`TeKO%8O!1_ul5=9iy(oRF*HPk+SBk^hA7vZTI2Jf^@I-i-4$%j)z`=U@Go z=^k5$D;K;z?3tQkvsAngG~+w|{$Y8Voe7#1Ow3&VVQuIK;+2~IBG5VLYg`WdFQE2um z^MwT-bH4%q>%{8IM{=-9v~JMM2CWr!K31=EQJ?e#Ju;T$jZc7>!nJDOS8h(*2`zY{16HmB$+^pj0@dAZXGMJh<_IHr4cmTRB&?^#$pD#pTf2bNj_|_ICjm zk=j6N76zu=&<211hCpaq(1YPHsnO>`5pN@~7P0dFk67atGoK6fN>_E-)NzUHk@pmT zYwQgLM32@y9FjSe7n(Y-yKZ+^&HTHE%{x!q#r!ubnPOf`&K>W~(Ub$NuJ|P=!vUOp z52QFu?P+c>`Rl557s*ten7&;Fcv3s*fo>?k7uK0Of z*9)~fP50$VkT3Ins!1w9eosN!@?ZcI6D20j>?L+)11@r}0?z{*pPu%wzFu?l)Wr<; zez-F=1`2n_2>Zp1GaanVn^edXB6gEE%4^RYAxnuU(WxS{o#(i(Qogo)9)H}$D3VJw zi@L1~oeTqZ2P^Ndq_;jIk0)o%pZWpqSy1ipDabyNx_}*#qBX{8AEt8b3$$w-D2)kv5?>a2IU5tCm>P*|x*!SK%kf zyONz3FZ{r7{4qxSSO7t?telh1{j2m8zX!h5$NrYl>D3@|qlKuywUX!qwu)olt>+tw z&}_u;#yz%KGX5O2mU)h=dderzZ%P9Xz+W0 ztMjfPdWJYUj7Gx8#idS7_V4!Ew|G?8>kzK62fD|bX|l&lF3$V+A5TtS5vz_2L^+J@&<)WlUaEt7B0y6q~>9Xa5(=xCx zV||*2EDKN3TpZ4(`G)KLK)|S5ma@yWPMS0Ht+GPl&+FXec`UqN2a^43zQ<2T@n0n# z;%3FY$bz98FGs@Ulk9Qo#74+)hBhCZa@JBhK8+8r)NK3Rf=<_&zZ*P3E#t-k=zp_v zv$c-1BMi_T z(PrSL(?OVQnKu=;-bb%Ct<$8Bok;-VHN_zh2-B_^zuOtnSc)4Jj#XOFpwu-z3cTeN z&q3>4Y^Y|`5C4uAM5VrGgx96%W{rMZWsRAi3XOE%;&>Cz?}Ul`K1PL+-RhU`9W-yh znthyDP@P^yq1(&jTbanRT1$6>s_XHa&ot8t42~2_Zca{V^AUbI$wVnvydU$K?%fQ&iM*PFvv%h1)Q_3oVF4K;h#+}uZrhob~4#f|G)rqvq)O!@D zUVXF8PdZSE2YV5e11KnE#Qx&&1xk35V>NY2`)Q=B?xFdvNx)`Vs$PfpYzfV#RRF8c zSlM3hks9zL;3+J+rBwnseXvq*BYo9KLbZ1c3p`@y{a#yqJyCYXA9oWA^DWlqwjsLW zFj@IEM3sPU@crH$h$)joBHVA4os4*&rjg>+?M9k*cLigdb+15!GEWs1mV4OOZCg9D zj{gGGzgK5SbQ>D7do6zVK+w#;9s2%tT#IzL1~Y#;*e`TkNxcuRE|CLPO0VyPL^NF% zR2uMJhKFZRtv2S;(jU*h&fCSC2e5gI)hcJq#U)ZknC{w-vb`0?u~0dG z;V=%r&mrg%_T(_zF!LJ4jlN#~M<@UBh24CESCH4UJ>8lO@mo^&KP#AZZZ+{si(214 zD@?ZqzZYruDj%JdBz0BD8rc2sZ|iN!qsCHv>h+kMpG|(j!C=x#kPhve@LKeXQ4(%R zG368z&W_Whj-uz87KKv3)nKMyWeS1cFkcz^C;mhtA2y@SJswrX=r)f}`}u6;%<<%H zMI;)C-n z_cy#>>3Kfhyt&59gtXG}Ro_a!+J7@kQY@Kt@donuqP6oXC|am<&o=cT%$Rb;%O>>N zhQ6Z%M}ISt?(e^Lr<SVyzzfXJQL7@>0x|QFsx-B?4OSfyS6dsOus|^ihKiO%pR^jfL z*IBF|fBza`{wbhFOWEnw7RKL9U@j&A4G1GC^YMN9tIg$e(v@_UrK{s)?zURPsgS?7 zUdKk|?T^{@`z#8+9j)<>ORmHKp0k^)!J51KFL&*SOkx&_emkV66Gvwr%g_+Fass6* z!i9w>NqW|O?yI}DHyth;1NG!V4T}K|oR|pJ zlX&*0M!#)lXVcn0zI4^q8`Rf-*gYgamF~^`$$8xupY`V|5YNn;ttI2aZqOW_v)04W z+xa`rJh{nP4b#`f)My`IIjsZf72mX`* zK9Pp6Y|^#A3?;=a7fchNTBQ3z3*MWnfD#}W0C@OChr%` z8J^vF9KR25=X<_Kb(NCkrJILHRi|w{2yUfnN!BoCPc%w8Ftqxiv!dNF&IYNk;0y#3 zq&1RkRf2A@VI(=z$>b+ns9?}R!0zZsz}EZupI zLSXlRw(m{i(J6I#?ST}mX{^FKZFRIzQZ5o_^W$)2BNcYM8>gMXA|c}0 zbpyLRoo7|FY}MFf1@`ytCV8I3PK!!b*d-xm_GqSQ25l7#kjUgdNjMW3Zc9@8Nxxa}war`by!w z-dn*JD0?+k-H+zT6xCs~yV9p?S6c`Fs@!oubQ*$sdz+O;M%QzZ#rD+y23+O|V5w47 z+iMvV3UpFU6t*g;kWj^hIXcJ14>0j)dr29DNmOtxD=#N078-H1?DL+iBulJ}My$-K z^po&&n5@hEJoKCpGGo;cO^?UZFo);%r!`#MCfSk-3<@J0^SMeV?YRDRa7V_F640wC zU2AqWpNac^{prW)ip0{n`!Y6*nP4E}BcV^gXg$gN2w(YKZ1W{$^sLYY#*80g$kXLh zr6d64MTqAH z-Cbud#)N{?ZJ0H;I2NINa2P)?Q`QgYx?Dbx+kZa#8v46aqwOvbxqYY47#F5k3gTNq z%(DQAO&2?X?zi6dLL2&!FURatIkKwUrVe+!CLb!CH*Zj#-PlS8Rzc3Ep2cyd&l0BJ zc#sYjv3ICj41!W>TXmZm++>0gaV=1QBvK2yaqUvFu;i8P>)Q5#m?;sEi&empY_c#{ zVvCSLxd}O@;CDtF?>+qQ2|@iOb=w&mm5(zRvyO5`Bxzqik9$zV=O9*@$o+bMF`*8h z_14fgEt2(*B2Vl>9Poo5UmZu?84zu;ti|?ggWylo7!Plu5;#Ly5nI0S4QLuUIvFkb z3vQP*Y?tig*VtEq3#jYFxf-j5m&oKXx)T;ubXG%zG*wr$-2zn*MdKEl3g}jPn;K#1%gSCC2rjHR+0e8ZFfkF&X;A^%ei+p?uZ6YPQYp*$u&e zjNqXFNCWT2lVRUa7_ScK^*uaN|2Lkppr(K%d9LHno=c{6$n{Hxgzy_zgvK|6I$Umx zGv2d8eB@)l@5=p{>8dP(as628Z8p@V6jVzO;(k9ExLG#K*>ajR!4(vL*|;M#w01K> zZTENCuMeLd{!6vqg2J6a4Z(!QI*na0Bl)WgHvKLZMjiTYymuA?CZiQbNnn7mYcOwV z5%(!UwGF@9PMEy@n~k-_6*R2h?TM1rOdIdaH^{bahr0fmQj$E>Tnrfc*t6*i zxKf+M1Yy!;Xu{~BYZhWAAG_dB4=@Bqg! z5-WRe!Ul77vJ9h<@43R>q{&-ICWro>9dN#BmoRnT9B=QU>&^ zbs#T3HdHKD{fA_jdza(ifmZt>aqQlKDZ4|hVi7$OsF>*{L2)si)X&U_`65~f2{yql z;f&BBIxQ=Lxk5glglybPZg3NaS0ld11{()&Ldqg(r#0idN#x<=DCZCW&_aXM<1IOw zAXAlsGag~d1hQ-t+Y52+y%;jynEiMCcH2a2z#W;s=>Gzl*9ZfWv_F{A ze3F32v2)X-)6;(>LajfC%8#sWhX3C1e6dp-A7_A;79!s`VM;|##fZYng?5Ngiwb!! zL*6q+R-mV0SmtR6$0fBk$i0`zx7k7gjRin2>L6WBG-oLrK~E>#lSp%OB-gVQ?0e~G z;6-?5@A+p8#pmr8FeULBWANmP7F=Jbb;~x;dhej~be<6z31F`{?gV!=@NGY0Yl6x)L? zv=FElvTf`tgddNy6y^%$IsfUO8^KCIkY)nXXOH>iEQ5RzgZdQMC%RD48S!ZIjqXSb zApAfYO!xyX!i8`>)wN%4d_4En^MBM141CW82Ez!}{=_JN%HvR~TmwN}-lOTM?cE&7 z?BA7ir8T2Lc1nd%c9nZDkcZDk!IM{cSOL5$jVOQN} z-S%Gr?S|!OR+Abbrp+jsHVHWB*G^uf{d$X2=x@PILp`SN#{-Q7pSO6TG88SXlqdiN zMBBE;v~_|RrWPUbE%2@?K|Yq8buYKrNhYk_jeuqutVUt^0XIXJtRp zBz_sU{z_mL5h?=-R>uCVte@S-DL0*k*un2EqYZk80lNjksnPn_682^P7@`c+x03a? z^huzF*>SWPtV$Z6RQ6Y>PUmDAS!}!kIhH-8f5%8}c~+<2eeSIP{t4a_k)<(w&YMHv z-AFP4Hxuw6*4gQ~oV0z3-s8Y|_Vw+2qZ>}Evjo-I)q#lbnkF12nH{uZq#!T#Yi;K~ zAaul#=Th?nMJT-}KBhwHkS9){2*Goxfxz!Kb*Tm_CeaG?;3d()%ijm$MLSme`6S$b=nD&eCYM=MdVf^~(Y47H1bg0+4rBx7J%{J@AQFJ89Q+p;A^wOT{C zQ}>|@)noufY!mof~Pd&^VGi4 z>U=HFoc|@Xm=W0dy~}le!unP8qBC-wXq-V1y&)M=2j+1Mm$)F-Xf*sJh*jZ= zpaLwIZuI*`gOe*OWuHIiaru;%+L{hm6WuV>0HL;0gw7^RFzVB4_Z_F`qGt!Og|A-f z-z2ZKf6VAHy?e(58{N*aD}m=t`eBNI5xATIU^_<2)3vUhkM7i%W4tUN^}QL}P3XCtfCu+^70jjHQd>+zC{%?^9~|9|_?}0qkIGUOhH^v@ zD6W_En#&{5M*5^6Or73ExkTaWzVn!Cof=%TYYm*6kZ zTsfT2YknLi&djANy2Wb8VFR>b?4p>*nc$uJ1oQs7x8KE-t7cr!np0>iDJ<&H(K|H8 z+Py$vYK&;kmb&|GfL4~8B#l{I0C(~$;LtCzvwR|EJ9;H_^(08P%{*C|8+L%f$gFp* zP3gOd-eD3qZg=uIp|gLSFLy$#rIVTCYy93enuO)1#)csr4^iehL-Nr`60iwQm@`SK zeo62^t%>MO>B+h1Zo9~NKuaJ8&wYY=yz^_!Pl=y;bTAoAL8sKFUjWG0TKu|i$o(%~ zNYGMAMjrv?Fo1q1K1KMcH$DUTYrAY~5p#uqxbfh$5O0opqX^f}d!s$PFI=;F%za^% zy)RyHa|A|Ken zQ>eryEb8hDm6aN=McXypCkXSoUce8vc=Y!l`?vVC**E#Vn#ZN(C0yxY)k80Eyao68 zRd@eViGD1zngH=EssE30ilpX^i$m+S32%eHU6v)C%^MQK!&xd;umDtAvFKcr4=Q*6 zVeO1b`{j`lwgjG=#sH!?JioqbtaOo|s^ zq3FA^0*DnVWI#I<+RjBULHQ;gT9op}qEbl;@4~2yI^K=R6fyBY#_S2A8~~|IjB~wS z{TBdFaS58&#<1nf?@g)N5}Wx1C2#Sb$GgR20q4#e{G~S@#MP+Wj`8qUOaSA#^P+XT zzWX|d{n(AIn;$RfX4Tfv_RXEAKPMf7jgv~JYJ@}I?}5vVSPJwnp`VL0D5!q3=KuXG zub{4>M5&O;FTB7&$`yy+Jcy(BZ{MwXKoDa!!a%?fiL!4h z`_61ww347+s23%JxN@M>0pEWcpS1eM16W31ofGiK{-(G!V7xttC{$h+lK6 z+aCIPx_hw}_V~@NxE2$gdmk#qUs3thZ8mOE87 zo4!vAA%gzN+(Yq1*W{CO?N205AO8KS6l9-1fYD^d%ZbeNA~r&hvz%lu?OlSQ90NXq z0v_DL8!Gh%mmy*5UTi#K2ft^hm45y$Toj*$y2?^0Z6BkgK!X>C^`iv`ryaV4M#f-Z zW0DsAweB4$VTIuZH-ob|;T+&0U5zIr?m!aJKps>s zNeHbf=8P@D0z{H=GD&)H+3bfKsln}zrgauN*I@|B5MM}$Lm4D}V5uud5a>rR4`lAFE6RW~b&;t7o;l`NiL#_L+Vj9+17|P+M6vizqsbuCdjIH9yV=<_~A)oinckQM9ByY}B?# zWD1M1Rr=H=iX?_}gC^7Ht6cl=7Zp8+aXjaQV|75mGk=}h{;)FX0wuo?!l>}=G64b} z0Ko542Odzd;GF^YO;@c>;FLGX1 z1`#6B`rG@+vp;D;m~n%-FUEw}V5jKp?Wak{*uWut0XpXqW^9SFWA{7P?lGcD zmxNnf{wtRxLDXJl#~H3hs++QO0 zp0pFlL74t1zheYtwEcoCA~G#pje;>wrL!mAR`qP2mhDLUq)PqL)zIqZXoBS~yp-n7 z4=ParH#7{M*B9$#qO{P4ivKgbJaT-66S1ebC1kAbxQ_|BLm4~QJ>Jf^Sp2GWw}dG9 zDimg$v>_2Vxj4(dzgOTW>5_yy@rB#K=1+HGfkrWH67Ohi8cs{~mVlCV%Ds^h$6tPm z+N_aEWNm7}wzUWkPoNC2r39J5;4I_>&7}~hY6Z=?B4VeU-RG2NN?JiltX^0;58^u4 zJL^#x8WngsS{1oQ<>=8tc9L=pgT0~(lqj*fiOXm%ZTf(lYpEGASpwxNRAVG_RO69Z zI)LJH-}m!5ph%KcC|=htaj1Q!$t{I}rPI=a^yT1%-*W>6eSe;kV1vzn_L>DM2+$&C zC3pK?53}+~P3ef8|Egc+BN&dmIlIUwf5yw`KQ~kP3S#{YXCQRJ3rQU1O#&)UbC_>_x`v05soie4CCjOg_sH0cs`1y zTJ#YHvC$Ynq(5&ie*6AH(V#=#z=l&2>-d#!XtpQEH1dkZXPsJ(T8`Ge-{AwqwUa44 zC^KYDvU!odwIlN7(dS6S$vpw++soZDVl-c-XPgW4Ogw2kMrmrR7yPh=Iytk!Av3K3 z+ODrg1cYs6!M}Y&QP%t)e{OU!1W2~oxzcLzFGZeD{m`(jwT>hRi~L}{rHI8+W5d8V zym8}|UlDObFfqe1sq<;@B*J3Yr~--c<}xNYim3h{1A|-PY5H3#g{%7ZK!w8y^A=p) z1(fj%d!hiNA}rMBq^nIA$PcuOak(Fp_KybkRz7}nVg46}i;w@Ftxj0uAe0v0o7Y@{%ef$?)9U%pYQ|_y=PhAnH2URs zcfBPl2eRFX1>b6ORey zYIZwpdD(2;s9iZ3Vy8c^*YC~h=z2V&;BLW5P_T&;kcU>{BWIq{?SUWR|K&9c|2f<- z^oEl7v%~1u*{g~m(+3CgZ}h+rK@X@()%K(Fx(P17SxiH?ejx|E(=idpj%TqOUZaTN z3FVqNyU+n&m}R~iXTtG5QUArPFs(cU4mIMFz6&IAi32n!g(~Gy0iu#i0Yb!O+Tow1(eeV3qLI+5UQbQ=F`&d^sc?^0&1IqjMj0BUO|G=KU^)v+l7AfYuhu0qU`=-cC z;0Lz0E~qD~_T7K(;%ONhEnpCxdORBM@nWK=_do?pnr_5T*)*;v7JZVzu~h}yl&lCK z=@IJST^F?*vxj`GrSZwBVbsfMW`G3^fiHP(nlNGF4EwRE)18(W$qC1ulf9!%R91&J z)W1?4Ic#`_?EC3SIlaatp~HOEY!)J@&*bF$xB4IPDQA#Y++tw%S<-~qhvF!&YJA zn8H6WlWq|QD9xV)*ulZ!Oxx$%&-B4F^f!=*ogS6A?*cB_*U|;QFPrx8d*Nr6;v!JG z3b<%@KVsLFK3$qqO~qsUc28107}o*yJbbXU_=wDR#PH5ofh*aJe@SNG#f<#druCx` zNoJ#hjNe3FiR=?5{+C&RCsTI1Rq>m6i(U{XcfT%im3=0|p>OT)6mv?|#OmOK{TsQ^ zYB3jyQnQ@F8Iyk-D^Ethk{Q71^&s-j3(iB+m(=y|TGl5%@N{6Z0qZZ6cHaJJ_R6v_ zqq;p{Ed9QizMSXod$9Hy66qbULm*XNKH8f_@t76Uz@RzogTur&-PII+ra!(Jq%daT z!6ChXZSh(NqL$qF<)xOncuKAO;e)=W}H~d|7Q3Ut)P}hoyCs! z*m%1C7x_H}WLJaAJXy0!&X_$~V?O-lCiQP=VV%Vbe$?}O*jx?8G}7}PW0;!HXEC9P zZU_7Q2P0>-*#R_|O+pA^m){)bmvS$5MrLCyZoLvaj0b$2ApZkF;#t)qy;wqEF>*UP zMdYAc=dBT6AwziP_~U=B1+!Qd+49t6^TULD$$a9{!8ZV)v^E;o=eSPb6Xc8*iVU z>C|lN&!@@haP|`v0+dvH*r$U_%IOg|1^&8(Dn8?w?O) zqBL&Za7SpMZ4&)y-KfrZOK zWDMsBVyaiDF@WL-@o2h5YLlnlFk5vd*KSa{#Bz67z;QU3tQ zl{cqvKU9g&aK;90VL+4iTA7iWK+?NRbIo4cZggrvij*V*?f|v0QrLh`FhFNG!~#(l z_+sZ!7cj8U8V=jATUv{Z{;&;z9OjnV;SkFAj|`t<$?wknkna*c^RHJ$-Tj?>H!Qu^ zrWYm2N6M2Dc0=m4$rWVLJ{bq7oLu&HXD|hKiN3Ho{+W+5mcAru4F|?-B1`5gkiDIiF#bTHuW!&6M~FVg7Qc! z&GZjTPnFvP`HAqoNNvYv&&#vL08g} z4$`5AmhO=|Et2Hs;5c~rN5=H`nmZr1Nma-60XBA`;_`v$>ehX3aB2lBr)5k_x?cxg zXot_QPW;6dPQ&BQtslWYT8L3WXzDjFdZYcGeF7rw2fYcjqG)OhSx{GS;zh z@3<)*bFW^8EHOefszkWOahImA=iSFq6P>Qt(=*G;i~x_+Q+WE{W-TnUdO(L$7QdCCphv5T@;d9d zqJOA-)Q?TlQX-O0$PT^m;P&5iaPJwx0B_pSA9+2S?=d)6ZsR!I=#93Zll1+IQ79no z#gSsbMIQB^oqJtNm0Zx_4^Vb^n^anq*Dy>Fry12AC*sEf;4@j9X2v zDVV2@4Q7$EAT6Vj1bZYLG)9p(i%W8+#d5-Q>JB<+LO)lJxKD4TQdBSmc^)Bp-j$vZ zi4hW*B;;qdQZNAjX;zC1IVGu8CWApl!Ua(pW3!}ykkIt589rgc+E zxuDlFAt81NA|_;U-_IaNSd%S?%E-Mm?JRPBW0NwR>qemfQmF`EEB_CP--imz;G)~V z?G`l0(IU;B0`ixD0#SuZur5tV57f_#uumQ2K)yx`XjrfZ*Msju85~RJQM5N;|i|ks^HI{f^-J z(tZbFQ?Dd$4Q>}of}P&_^UJlMIzYoq7gi~OpB{dllW zjk|c*-n6u35f z^Pi{Q6F?a6pqtnhMNkc`%D9iqEX|D!=gMqosU}IHX=(I~KH}`=DqT z`g3I1;LaN>>Pu&S!Cm0gAu7S|0Z_tF>Y6 zuHX|?5L|DJCX=NA!4#wh1*3DMz=qaq^AW|;{Kj2}NvNE^by?la%8O+%rXtw9K`mr1 zW3O9m8WKN*m|8{hM)fx>T#x?+x@12L8%2wP_nJ(5gb16N|u=9hrlm5BTA$v zP;EO?uT7}868%_=*<-@czSJ&eDHksOl6m)@VH;6a&vAh~2;%MLGQ(rXnX558sAa!y zXc-d40GQ)EJSZIEc$trw2!MzMbS)~zZblfWWGodKSL3bun!R3h3J>7 zD4k8%Nk<_)GPq}+bn$8`M~{x9=|MN_%+gGj3WR$fj1e^5w%S-Qa0y#Dqk0sefcblB z2ArkF_jcc>CGtDz4%* z28yVygb-s$vW8@L8IUlcclU``1*2%_SAB0V!P(=AxJ*TPl5&|r8>5M&={jTp{gal1tYd{bq`fo-vJhSCcMWj>JhW47Cxwe>-GZH<~$yt}? zP)@RRI#FvM-r>sq(x0>rLHU?9kzSY&{-i5Qoc3-9IES{IiF0riRHlpMyv2_EOTTM9 z&}_w(%_3;$J-Q15-?qCLG9)A04ASv`^XsYc8FIo{PtI!@*wS%M6i??` zp5gyEbJ4%Lyf4nwdcbwWSw{rz?}5=%tz8}nsRx3R37^xA*{l`NMW*bPCsLq<$)A`v z#k0?`k_X77yjF47<5-lr?dS;GQx*Y4WYnGV+gzs)?Z^Naxrm{Cr2i zB_8|(9J2TBU$o)LO-8l`I#I+kP-Ri@LUP4N=auog{!I5->skxwf2j&G$-LCxuxs8) zM?UbXI(_!3-g1a^S1;)FDh2eqjd>orKG%=UUl_w!rQ2k+6x5`x6kD^NA(!+(Va?>Eyd zQIuW17hz#csUhkYRW`Fxz#j4|e0-q+%IkTX>nC_NsBA|9C3s0fDG5C!&^|@aF=Vu0 zwfw!6FQQn>L&w^v#I8D5du~0CrK+dG5R9y#n^U&mjz7x#T=8iR;!lxt)Y0 zWPh0*^IMdg<6!&WOi%c=>#?4bAqN>y1!|I-SGxFv;9nI5V))O6^6~hk00)QA|HIx} z$5pkh3&WEI(vlL2v`R}O%@UAQQ4lN?6dMT%rBf7Cqy+>41*Mc!5F`Z&X^`&j?ws!! z=-SugcAvA)J@J1#J zc`@^a@?`u`LfU%{tlAnz2NlyB0vBCkYO7Chf8_osN7WeYmUFeX%H|wNil%gVm16&U zN#WJElhPB8Z^xFeLHy%+VPJ@llgT45{#WS%i4Ah1{&Wdqc%6 zh6HbS^J29nQ_Csm0SumYUGZ#mSJYs?7=bs9ON`CohZLs>gFcURwBym9+uhK>&E-rV zt7TzLrX711E%(Pn#n+SWTW7m=0#%Jcud^jO#Vl-2EOBM(XboHn0K4&fAFKA->e@sw z*zO+_5V(e)tEI>hrHS8>&rNJBao6op#Dns8i|=JASYxktk~F%CDP+Eg^$Gkc?>OKm z@4dI>Jc$W%Y!t=L(_l}{Sjupz#P}2*h>SdY!%&s|YqqMLe02QHn5SPghObMwx$eQN zQ~*R2Nc0*|(RRiqEQMc6W`5p%L;{bulhSAD^RnB`+;X1sDy+pvpyVD}JcddIqA2ZGPIhuP)XSCxVnG6#4Yd6`Xo&bV9=Hx9423)VjOqP5_5v|)nstG6`u zH@~_@SyO!u72Z?`iM$%1@2~peXvpw^zV^uI6R$`jN%oUs%361@3Kj&99#MZYM%dtm zWfZBrDW!H$abhcjTDX2=+0J;ZoiGY4NZe8D+VPg))l;!r(j0co*MPCwmzzPn&y_c* z-XvgrwZBF(o+gT2QooK7I~j4}G1Yq}%p?tlgW_fzb)VwdCd(#mYZ=7#3hUks-Vetl zY85vXbXjkO5c}?z3s|QWnZxS|@O9vdS%@7VQB$;7O^cdll(Z`turS`Va@&?fpc#jh$Me7=J5p*+d6OtEIV}@gdzvMdkHm)aw7c#_@?8{4bW50OC!t}< zk0AuKG~u&`hPT8|XuI(;yeiH5+;ZR(UD#EXwS2Hj`}vzu|C+IJm9l$>#xYE0<`XHe(h=}ya?VC&+&3yWafl{qpk0nQzt>g}VTuR(DAJFCg z$i!BB%{-fahKqeOz;^=KWsjF9iFnc)I$#V*V;=Qv=`DC=Mx8lB&}{wYgYMmH8~K*9 z^N+bsB3Wg=cOoZBg?bsh0bbWFEmqVc2LJ=t*XaH}J2v`L#AwyE=B*>i>2 zE1$XR_6o-Zj`kS&Jn?30Iu`aIUIQUTOq_39Sy<>#o~^Agkk;juNXM4>`mn{NMgX3`yp&QZCinj(*1erqi>%@!jWq&(cat02LAg=1HzwRtDY$F{qzI=x$q z3#iS|1%IYiLR~`t^q8`U!u@`wx+F2#1t;E5Db#8h`aV}POTK>LrbpWJ)!?h6-D->} zi6UL-y7NJxU?-lSua>{SLfp)ZRF6)pHqLf~?x|KJgi)-IknQ@Z~6@Lp3v zZ$Y*_W1q+k2}Z5K_w!z!89Drtkuxo?IYOJlQv;#LwCrL`WW;BvJfp4IjK;ph_$GLu z&)j*L8q5Ne1;YAOWrZ3{{mX(^5?S4MXfY_wB;6FWzL-o|aDe)gd_cl+Ir068T7C8s zu3dT;1AQv*1cf`9?k5wsXFJJKc-&ok#)o+^0z=+C{e;G4Kyw$;wytkGJY@MOIy^c* zl%YoU2H(;kDdweT_&%x+BA$qus7Bx3U8EGG9vJ=RW7IBhU)bE%G}XE{oo~~)m$Fid zfNm5O8cG`HC8rtgeXEkjk>?b#D|+7VusUIqgv%#W%>%N7OL&pUnG|K~=LW-Uhs2xc z6D+$&nE2X+-v#oY$3_(U?#xql2k(KIdhp=!?x0GKcomFFKC6@2mB`_`!i>j6^=vHm zfwu2?kSBOw8C<8P7r-LE`GrG|)P1<(i}MvOp9HoP=Mnz3SkU=tZ!P89$KjO{UtXhWL0SqYtWe*p%nU>$f6*acJ+!y!l zJd?cxQ*w5lEZ6?%tZdSjY zrlwV~o%_AMs0BzZKWGow9KdzX#EO4Ns^sc~j#=sOd|J&&GIRUBZn8zsLs{boKaTCnrjlGD8G zp03TxfdI9~Tj{SbBkxWiBkRkQ3d6SVlN~1!^$U@2m#ueWc`)-Cg@cJY^UK$b)+hKq zV@tC~t|cpGZivs6AHqI0Q?kBHlJf|^`?TxRy_1R8mt1$wj$4+eJgm&oSc^N(fB(EA zUv}r?Ui$DdwKmc))ddgo`NnmH{b=*fIHWdM!Fi)NInQj6_KUxy8E;UCxZxeatM6DDw}LJn(non` zUIko?5_#=YLvh$M%Q?l#sgN1Dp37|$*1Jw;vqiiyCakviz95C0SF~ZZ5M#2NrN<4w z@KXd~PNocI{atu2ZO4@_;>DRBVK7xZeTkNIN5hQNg-(SDW5-YPhVua;Wv$-kmI*2j ztK}JDfj+)EZ)}f;+RjCItp`>rWNiwI@JD`JPIOmN>$%kVP(Y}&)0GYpmwL%t>SLpD z?!9u(PLou%8^^tiOs}8C==D_S1*I1@J}Br3Y;hzKIx;sf*DR_|WyL|Ij~|Zzw5azh zM~pr>Pt5~umMhleX?8R;rM>B32PQcc*xbp;nCxzCcDUKg2}cl@;C^cxI`|K$i9R(^95L9ovFMt3)jS15aUTCP|AYQry^^wnvp0gW z10Ok@BCS8oHf&49^zDIT#zK9ZV0WeZv$XV@o4lSr%AQn%a;!=pG)nKu%ID$Vol@YV zak;o0hcU>X{I+#E?@n(uOR!*=VrV*%_}7Ts%oMkTBs|@Wf_wWNwA?IN3-_99tS$tE zn;sxhOnb7AnmD$5OjYz9@oRdQ8)vJ6S6cQr^r={oEnyMlvK!iUv z^>Mr+mI9kzc1BTIi-*35T*o8sO%nDS2zt(4e1h8>PjhO= zhPUr6=7cQ~?nWXWvJZHXPiNAp3EE3S)!l0OU+tS8Umx`tUX$I3VKmY7an4w7)XVQ( zDpLtN|6Z4%pkrBx4S6)_PE*rJ9xhF}pL6QqT9?4dpo!(@_3AV)=>f@ur-! zH*cQs9~rqc{YeYGa+$p0+osYDuboj#0qv||D_aTH9s!CeUmCdYJ;NSa3oR&StF=^M zBt;ev#}Cd7+NotxcNmaO3F4_zO5S6aBx2~8+0{a<_-tLs?m8`TG%e+eko)0JEKNxw z)X|0YwfT(Y)|kELKRq{jyjbyV>=H*lB~RjsFIRu~x++1w5VzLM6QLwSpT`AN45@9CE1;H{3~cUlvn0a_zag8Vxnm z;S}cdslnVLm7H^4D|Vg!Wz*|4G(?A|Xk8!ctVi5DsUYuj&mtkR*@l9I%rW3qp6WF$$^t8A@#@561zEHsU&UV;KHf#Jr&~3dIz^rC z?0M&_W+Zy~m7xoEA$Iwc-OIhUbaC}cCnnUGQ|0bl>|gQDUP#*-k~7LZ-m*I_@+Ntp zmeoSaazRq;+decr4!LB^L5H|F^!cR{H75z~1;vxLJZ)?m$y-+=;Og zTSt|tKAs~y(Cl8Z({O8#`8iCXJ)1J#)99XT*Mn1%hnEjMI{MC^W?}A)j$TE+vfH|9 znZY9aDhv0r>WLV^!>ukLQ+M!YoO)y0p{co4wR>gk@M}d?R(qQXq8l$)D32Oo)NJwP z=h3I~_C<+=ETiYSFN(gSuaPYFWKJsgl{?K>7CFa!f1dSBoU7hpNxUywZnG~8Judgi zM-Td1$Vr;q4;;syA$+w1K}zqC=jWZw@lYDx!G5ecN;25jW1}&FNb9Lf_VmNcr2=I^ zz4Z+nx@dUGsT4Q+|rl9~MZ+H_#CE{5uSC`K^+Vd{o zo}6~tVbpNY`B~HubJ-c`C*(W@3q50rpF%3ks64+SyeBqnqC}#IS@GlQNp}r4o+&V( zZ^ifT=)6|k(BS%BQl7b}FMKhlv2(a+9xOoU+BEV8t_#J?+h{D;NFU+=Z(t-8e=@Oi zZ8i2%PX&IrlJ)e|S2%YF@Z2yrt_986bKC z-ja}e057g)qsULmJ$A#aii`>SSuy-WsHe9<3xCPAd;6|4Hcchad~72QDr_}jPrBpREP0s1@!uoZL5bJqz{MSBS*6OnEds zj$URQM{ca1@95N^KF%^foxj-a!|E}5H!=f{DkP6IYB|u9eBrLF{GI9g!Kro+L;QD> z{`5-kxj8R~ovh~l-1NHW);L95plhH7mcW0r=l$o9>%K|PSkQyL>$)pry8`(Y%N1kN z2&Pmnc~>?J_hq_E8|oPQH*Ha^CXNZk4NIuy(GI<0?S1)Rft+6Xo6p+4+GNA3;PTCQ z2`x7feFss3M$(<8&bK$@5teh6o32e%E-4HgS_rZj>MWVPIcIFgI?6`!!3vR;p5AF0nuQ4RN;A)0H#Y+0C(J?yza z^%TFE-PgE{c+8gW_VC)gcZ|4>lgO!7t3_M9;Zpgy2tlPet|-B^-Al$3b;i|h-5;x2 z4Vdmr}%j2lx^WC@ac}Elij9co9K0zaO@w}tW7$UnR>v_!0E4DzH|Jlp?eWIA=nsv1wP?Zsoc@z}WwCobG8 z7_MPs9KUwJzMg#KCp~}mcyI*pAF}F>fqiwT&&F0Bls@>g`b|}VU^xHz1WFjBkpO;;^Ea+CI zOz4KWL}M6WSPeF85be{Lxryk0z#|(tU%2|J?2|7YbI3r+nq$G#)mk50-~CfuehNJs zGAALWYOEC7LSuLZRh$vAz zk^IPqS}GL{l2OeS3ru|{gJ)3;?PDQsbvGdj4{C0$C{1MFSUbaQh4bNsr!H@%AK>t? zT#IgXyhW}di$At{Fs23XNWh0Y4gsgwpuOEr$6h||KCu-_H0sxppJ`FLX}~It_i3bC zf)`87J|}uZxo13cXVb{$tzZ5dQ@M*3(ORKt5%hQ z9=KVP2ockKda2=~=oK3!w0EN5K6g^W;VH^usj%kRex3H)Wm!xqw6t@r;ER>k3iW=@ zCOmk^T^=lWMJ0>;Oi89-o@3{mzV+cQiq4}(X&;>(2e>YaYhj*P9YIbru`Ba@xT*4t z`h+@NasK?JN%1vvA9w%2KJ#zVEqXVayj-7dL<~nODyRfaB1JS*0)(rZY>hh~&@hr3 z9(6g)saIEj`9SECTHp6~x!pKemFc)8h-p-@Uxu;Oq9-P(c9l@i?^Lci^mQ@bRkkTP z%(yn}{PDr{`$|L!_d4dvjfgHiLIno5u*M|VGr}*B6=bE^RL*u-F!$bzMzpen*V(3 zOKuh_7m=@h598*hF;5ZN9Vv>3uyQVAL^_*bBa4b0vWQ6_B0u7yj~vA6UvEVMKoEIL zo^q!^YDXUzc7suEFX1IBu$tb6VGED!?s>I0kj0?&hrAG4lH3S#Ph;Co8=Q zh;*JRSoWfrO6*vR&zG6QT&rDS_8%UIQk9l11~1WYa*M)XK=|n=<3&bFG)2V+L*!H|?xGdx28vieXIjiHUu;sW=~FdYY+Z zyu#06kI!9HtfWX!86aRz^vFG%X|QCy^f_$u5EWO+V0Ci0#ER|}OZ1tZ-5dUS9-bar zba{sCA>}Ev{f!TDBZ&CrdQ3{K$^>t!QLAC}Omd=#zAec~}lsQOwt zg(ucrB(q=R9O}sPvc-w2#64}n`Tm}vmQwxjTbw>#jKrNnH)f{zQx)i!)92eGU6=NK zKH$<9YL`AyVMgc>InqPccwS+iGQDWiHIyA$i>rH$p!w)z@nit)oLA_LvPv<0VfVC7 zQEh(rfWE@S7_x^FuNTl$QiT2*zAdNE-^22SjfxXzhh8~-EVs-@5PYjy*Q3v#Iw_e| zya~H; zS|+-797Eh5Lqu3!vpHV}Dn`!~5_jpoIlVC9E-zd4ZAEQ2B@MCfCe`z;$kI*g&B$VT z7nj=IwdZ@T93ZOcBN9=; zuzAcL5j|&E6ttvd$DEUpAiG{Ua2=i3DW$*`?XF}4Sm^CYQOBF>XO&vnYJDS@Apg*d@<{5#b$Q7`N+-)Q1T z%=oDRsYzPT=8UJG#47H0oU)d@hu>gX74c56i!0x+H`pt7Vt-ph9#)xAdOzz*Y`&}m zrI>ypxuvt=wf)EqI?DJ^bmpEa5i44^66+ZG;Mz*3fawu!hBBcCXQy~DA^9v%NDtpC z@$XcLyB&j48ozEMan;_dZ$jKOSyP7wOmn2N7MBQcIA=&khQ9GytfT1<^q#o$xJ>d* z`@<*8xB8s9FU6dQ$)#4fZxQ-{#revFsE>oQ71e>|$U9@=4ZPTT#u0p9=e0Zr7J1u_ zPnJ#&EVt$&JcP;sWV}a}Uj!FIRu*cAyck?=teZ6m(Yw3(cbccXxwNUUqI;O*C3@X! zhKJw-71^6<0bjG8=u>>pk2t(L7TUM9!FWNN<|*rJ-kjg^G+FR^Qx^JUWy?BbCkY~C zg16;m)K%PU#BOJXPF%N%pj3~=ZkWF!tMYec`}VB-16V5K{RY08 zQCt@Kt&oWt4U@TUJs*?2NOFw-+(wb2OS}1CwB3n!31d?i0?@f;R%ym+$B4Q(1*XcO z<9Yn6?YW6L(+^JXygfQLbiL-X+?$h|S8fPY$fk{)X;-a@cbnDlwkP2($5#s1_N@;j z%}Jo-iNGJXVdnyE(JISZLIzfJCT&riPBcTT^$(+V^%e`GQw6>u)v45DUKLG)9Re%EQ-UznZB_ zvV@C0AD%c8hY93R8!ei5^t^lw^-;X7R6EQ}FpEAtcR?v|!$X05v%8awL$EGyVxD+4 z-l=QAvXwVr^J1WX+c(bf&{d)U+OgW@DWOdfp?4_6;d>AO3V6x(ApO~^N)`Vm7_AZAA!&EV%2 z{*g-2)V)9EI^&z`TegMT?JZ=w8+YG1uz7`0pG~Q*ZAfMsoLHO>b*morTEUtGb@zAW zIxof&ZP|ed8ZoBMz+lD_y#0&BWhWz&VW#yu?V97PhrqDah5nDXd-pcSr`}3$^NXT( zS0Onp5ELrr*W=p47-C6|r6a5} z=i=JxyVspN>=aTKsXt-a1m0nXZCVgsYN0G$>RM@Yy(&jmoFQ?hB(Lz=zFhoP7T0fb z*A81LrK-`8({u77CHp(3HmNSy7lcP6CHM@HHSGJ{0}I2QyOY_S8{K0G3$v$*W_8-0 z5x0dHd9&L%GAlT~_`rf)nvbPaQk7+9gA- zZsePHf0#NaLGeQZwr|Hw#PkkPa&PgYYKa%qpKaQC?xlSawIWJ1i-)DYG!lpwotL-W zPy%1|b|Aky5E;Pz<}Mpj!tLDWH=8OjY(054Gv3C#KV>T`Nk3Zm5DE5M_=-HHFc^Ga zte(F0@hQY!sufQy;hGYoN1rP8GDDdo=9CS_{GMC!CGI`=eDahR9S)qq#vC@-vbtL% zE~sV3+)YSe=x}rrt(Gfd-3U`q_=tZq*IDn~mU@WsVVN_=yem?OK5_(^61ewbzUdvq zvP1Em7B{IRqZebP!#Y72mHtwErj~wn+a;q< zcE>#3+|ynoj8ZgZ)e|+LT^>|4@^%6O%qJPBg4v#R;TKla&Kr?!p7NEG_EBQ3R(qQ4 zKJ3?)A3Ilr&R@UhoFN;pSR84YYw$4Z0CKkA09T;&`~0~Dy5^;KV`KEg9|TFt(#Ebc zn;a~P*+munPIG4}^*ffn(o3HTAAQQOYbM*Q`V?F^OCn{PR)1W}*WY|XZL!+N(Hu3H zvb$A3G#x1lM!vfbq2}vc*MI$4>hA5_kLx+wUddtOj~Wv#q*>TxGr#O;ncDB|Hrsu> zDC`;iJF0Kki;q3JoRUfKSu6HbT&L*6Kb%t2qmC8=flv5MZ+wnd5&}g$qs{TdpQv3< z#q{(m>}9=>oAlAL|Lp6E4vc|FGhOAoyE|>i)$z{ep*_wKi>zge6Ov9BDdQ^+V6s1U zztt2xW_YWaT&gB=H^@YGKhtvD%+PUOp=6cGp>5sT%5VWMcjX`7aJ7G^rEhh!rc2kD z^t24c_>1vJ*YvvC5IfO&7QAt}Yh-)Um{9+l{G@&5Uzs9@Kh5r`UEP^&b?xz5xt@=A zf@62S^8mi{T03}^&F#m|?N-_{)CjZxi=sTI_MXB-gSLBC}Qts!K=lD87L)h z2e9B7Ux~^+-$$iyLURnC)~Y3t${>=JN-q;^kq+4#%ms56EvIpY$b8D1)e6_^oNQ~8 z2-pasqfOD|fKLV%4F&N>G&>XT8@>jLzvr?pTg_lF-ws9=S6L3KGaU_M);4z`R*Hf zWF=|s8GA_hu=MD8?1F>zn$KHW>$IBCJ6$cJz1EeJcS=4j%tgt@t3E1*$|EyTp=n|l?I`4i$oi>=d(l#;=j-No!xY-+ZJhtHNM7KGn=tG-1S|;Sy zZC4}&NPQ=4+ib{dNCLSUN78GKZVZ}jbWAxj?FpnMrpkXLK{9KytL+7X43RV917pe= zUMgg6?rc$L%RixifPk;s`fAm!=BL&%F0$^1j>^a9Hsd{J-*A$ftjb`Kue28C!%xe7 zHpk9K=3c#W=0SKq>H4(@lTNRBR|Jt#j?zA0JNaZtf$DyZvSIoY5H~r|}Cl-47XO zhsEdc=Y0@lNmrQThS)|fX{8|PsX;X z#hK>v!KVs_y3MX+o40V+TMT#$JNj#3I@1qKTd^_ZB>1aR*c)subhB;-Xl+&b4O2Z_ zZo9UGH=m+2%!h}Jt&}@0^t9CWZU#N9{uIX~N4BCT9Gi++G{Lize?~LIBsSCd248ej zddIx!lWJ)yyr8r_otd#4UOtY7r~z;}g`7f0>+N?fw9Mq_I(V;%jiN24wVY(PhJi1SSnsfu zsVFjx_mVSV?rJBmyod3-4uORYl~B!u$Rmykm zXC|h`Xs+K8ZeOR8;M_5CEcJzPvmLR)Ey?QjSt&j&!h2Qx&JE|qXIh(`ew1i~0ZW%x zOMc`B|C*b1S+SgWwS_BZe6|d4ue>BuaZXw6o8rvH@7r72yP3{MKBD8aXgq&WG{E}R z4ry*aXQDZ<->N0W!L+o$pLKQ8z-dc|QbEWLvp75#DZ%Uj-u?mN(uGLrULdE9a%P|> zEEn<;QgY4=FkP$BtX0!@=_1(Vzq^B`_N;I8juY>Ar(39}jSX@azpdu; zrEjM3W6yF$CV}@KtBVpBW=`*xw^+dr+MloH@wQ%^5diN|s(flv5PtA>B$uIu!?k_Z zRL_oSM$)Y1urFCHC?e~Hk9!$Q(#bLh0$QPfD zqBYHAr<-UpCBKEKd3fkuhUXbB*oW7jQNCQAlM!7whareMcQPoTH0M+PdR4FU*39~{ z0!hP%;H!fQCNgVQL5RPh4#_J@2lPVG!@F|gvjxvAKRp!~p$lUU-dXupEb85w(OZ1R zQ#EJ2xDqVVq?m6I#_W6YwCn{~E$l-#X3cVE&^)V(@?6g^o1!WMTAxD|5r5vpWA_Zr zJ~)gIAD7Lt%9^kBA57BWo?HBU=}RalzAL*>`8v-qugp9Avr)T%KPIGPj&thGKqY5~ zofjg&J1L#Mg}Ll*yjSA6p)T2}i=4L)xaEp{OD~BOe--zLpxpTK-1uOQRiPko`OTG2 z&Kd3@m@m4UUN7ytLmB%a;j25>rkTQ4QC*GG0OgW`td+)b4y@IMj!gEg-3_hR_u$pG z-%j7jc)uq*Y>ggmTi46G*3kOeU}TE=yvGged!b~WcZUWt31xl0jd-Ff=~~B}mRRNQ zj&zl>?q*e#=;PvCtByOaKuhA%wIs{=Qb&eidd>UlxWRJ9mqW?tbZj(FXSUQ!D;88X zJQZ|4*JH2TZ!{N*zs{T4wKiQk{SnBMlAU$7h7tUTMfDX-RHlMDft4ba2H{llX7wZQ zWQYFGhm!Pl9CS_}>gKoM%N7(|OUBPS!TCH-OH7{k9!tmR)%pvWFV@ELH~Ui^J*h9M zZit@i$Se#!jwr^((vK7uY#K}(WQ`6cpI<2%OzddBch9Tp^CgG1WcldQOAT0pl{|l1 zqp?;cBHql-#n(Cps}l+=WSgs`2k_J^HbkF~kJqvz2S(B@Y$o;DlsHyiFCR3pZx|=M zn0M{M>`uAdK915}4=DrZ0Wl&D#Sa=msnn}0;1{vlr}G?VwrbFdS$s84C;$HO#KrJg zPgce2AK$R^>>P+E-0NZkUR`|9zFj6*KXdkNeO0bw^;J{X#rbD(M9jor9C%0?AMM$* z+n;i}OWbyyLEds>ec9P@h2ZkOnub}KJ|{vem75Y`io^@7-FMGwp*6YoVEOyPH_L{; z*VUSu7cK|%3&nEM9o^|k9HH7Z@UhLY@8dlb{Ipd5YL?dC_N~gL@{Lr?-1ULlP$9QVy8Sx6vOI-WJQp^SDK&J!J9w7gbCW5ta%)KP*A@)*CmpP z3g6TGV!=&?+r}oR9Ur9O7u?XxQ@?wROt`7n6l&1Hhwy&L;JKOB|l zk8qm6;sZJBJbb%zQ_%~{MJRpP5Gl^6uwT2lSmJ6ue4)j%5WLXkb?#>Mk=HZ^(;wgZ z%-g)8a#@v$5qDEj_N4*ebx1s#;K*6DMXO4An$>o!+-q{VOX~gftZIpMjF!bpjCi+1 zRwBNR3;b!afp{W`{+Gx1F%EO*4ONjbT(CDnhrG{gFbL>R=+|~~R`Uk7+-mOK)WrD9A4c^9N;~r>B<`v znP${h#8_i?P2MsN!g5w*W`|?WihtM|(Lu*qw??MS!0K~pm2&y9TH(6jLVP*h zxi)k_UHxGMnBQ$oGz8GrOedV8QtcfrOV}ejFHM}w$ zES+Q@N5=ZX$q1_p8I5{+k6$53I6GKA@0r6}SNu_1IW+9zfnc5tcB|ZR{wvdLoBWF4 zCkX`x*aeMy9un54cLnUos3K0&I}6^izlPVU9D{F(Fk@;j7Fblz6W&( zy#2V@jltzfHk-fsLygY>LTscY6(2X0M3xRG3I{cN& z?Y8gx;s5>pGY2SD6cDk+PDBGmk;|aFgPs6-BIx%)hu1Vg7hmp1DAf-Cd4T^5SLm*s zL$m=-DCnRO?RB%>z5i{P2v1(32488qKL6K@tFXg!cn$tHp6$ z=P61?LVn=P`O=Sv?`y^t!M5U;!hIFhB11K!A|rK6BBS*ipl|)?8{5~w|G+&p;NDk! z+&KyV`*aZCBar|W;ojXOTp2M6f-OZM!UNS^qNDYjqT}FuF}OYk z&VSc``ahs+Q+S}N>tOTeV6G3*@&vnRNS=S^$^Qx+7r&2<*%ucq2=ERS8LA!M#u4HK za1{qfvESkR&#wJ=7RYQI(!u#UPyis0Ir~3MM?4shTuO`#$lM6<_+w$Nl&Ah|?R0FT`D8q6w7;9iB^p4rPLq$G>eGWA!Mc zVYl^7`Zxb34F=$T@KYdHbk2)`T7zYuuI#jt+4^ z&%Z?n@V^He^S#N$1j+ROJsL0|?p)s1_rH)oj`t8ZdBAIkuNuJp=y)?4Hq?REj18lY zhTBouiSK#^_3OVw2gHG|?EPivm;R1ph5mhie8@rI*IRyPt2kYSyoYwHIr0sS8SX;M zM+Z^M(Kb{A(2zSefTF8w=%55_ncr=R_W3>a=)gme*s+~jZZ9>RtCAOrN30a`Wn9XMYl4*2#vI~Q#E91L-!Gt~Q& z4Dv2n z9$tgvNrCP%*n;K)+w27TJ-4`sZUEeI_(Gnq0UD+Nk2`0k(3+_+vqJt zrKKhG!B{7X1^B~`iyePS2gqspK@J0P{khQ`SAidiggQ!I{VU!}j(tO)1OK?Oyz&pY z;5a)D^ge2$4|N*vLT^uWpqD2*P=m>K^yWl6`gXh@&;XvpwT%rl8}LCL_$+16VeI%$ z$M1aZ_xu*^3jCJJ zB#w^%^0`nJ5H7R{h#QzcRKa}{pbSlskjDxR>D{xsy*3; z9tZfVgYk|4+~N2T4vzQRxI_Mf4&w0=Z(gE0oKNH0V{m@(Yo6z&F~H=PT!sO@;?_VdTUIDd!t+jM}Oj=v)F zO59KQr-E~#-{22%hrIteKZd-AdINb6aX&Tn-M4fBUy0Le7zfs1yaGAU7SJacyEZ`I z+W4+xkc<`Zxe3V04$z?S7rciy_cQN*#vjfJaqS7j|2O&rZ64Y_)I)iokBUGqRes{V z4#5BPbUS)+s$)AA;cNin2jg$c%p{r*bR=YvIf|YcZ{7Bv z+jQxfDHiqg?`Olx{CNM5;^B(vdsP|A`q3%HbLmG5}3|xU8{A>e*b2F$d z!2ilm`0ImMU_a3XWbj|MvIg?j#~^=#Fkp@ic@K5=x4eh=57mwyNO~cB>?izH&=F+6 zr~PiRfvRd4BYx#QFH~?c9i~$A!cPOVI zaBUgjjB7B5a2oB{p6C24-fzdR{>o|=51ZYRYkhz4pFvX@ypaka6748`3Gc!29+zW6 zzCaqb{r(R+pdS4}$5bnN3dp43mpJea=>2&RdksN12D%dh@`?cvpJ9%)-GH4pfVl(A z17RKu;r%n-L)?Vgzq|y96H)(CLj~g@B30Q}0Pi;-|Dpcj@&#zWkT<{50c{ya2c!k& z_|VUM$88I3nwvs{K|Y-h;u*B%MHst&;11(l+tkGNTmZ%@IR18?0C4=nRskOWa1Lx& zpfd9c#P!#91KPlwU>>od|H_|#M+b~yP*ivw?p_}KJ~xA7{vF^VX$p9TICV3Hb+_0T2F) z4w$!VfO)&;NIRMZeB&g@J#qZ~E(DNP02-D6-fXO=8GtU~Pw@(3!*$IF z$l=OtgocfUMEo~0_?ZtduHopA1M`8mLtSVSu*3IY9_&5ZiJFeKqJ>l6Y03vjO<)_g zV4enj{S5G(7T_HE1E`08@ctKG{R|JD3$+zLBLwqzkZ1lDcihD?AOpd-;RnSBYg(bb z|2hut^LL&4Q9~JSKs7)e1=0fXf!%MkYr6)~1^9g)@cTT-wL3vBw7Rf}`VDvdF=zZ; zj)sG=eFQiFy>B@XA0qJbH+ql58+W822A&kuio`0jjKoIk7yn_b-|z_1AqHUJ{MGlm zVIz8Lqy?RspF?M6XHmYv=1nc*nti-bQr{~k?;Nr*0R3m_WwFh_yJ>6 zsQL2~3JGDdYk!0HKhre`?&or_pqJ<@1GVRW!k=IM_ni)4<3N{WM*pi$1bsZzdFXTg zF7JiGe4N?Vlpf~y|D6Wo1m7bkCP2fhzbgkEt$)w~WC3*?>I(EVxL5_R{Z)!zH{XdL5juuJ`K2iewz-EF9BTsYyI>Wd;XJr0KL%Pf@d~ht|iv~C0H;!UV)O8nIsF0O-uk@ z&G5h6)GD%(JOGOvsS^vve|9N_@JNXuJ@5#W8FwszpYag{cXWZD@et(KBfR{7v$;Uvzrm{jUjGUI9{Bku z`fzmP_<-XJj!%GP+kE?jk3B#5isLi50rDO0(_#Z13;G}Xw5Y&`|Db`rBnW%h69nw@ z*xs)s1?&QLxOWL&V-J4$VLC`Ud7l4iwxU-yGm8NtXll zMh)2EIk?8f^2mV_-Xn!hA}rp{+t1?R|Lyd4`+nRXTQJTpeH{b~j&zsLknnyzfa z58=MbLa=7n59-ZxBBOOH+Z~>RYhVqaP_VVggSR+EX4l12#0ub98olF)1vvgY?LD}k zzk1tmW58I_LS1FA!8~sg#w;-qV{!5IA7U7c*FR!5ycawZ!}~c|3c$sH{VhoH-^2k_ zG7yQ?9%Qep)lRTaz#5D-go6WO&h{MlpVnf2inD+xLkAlRESVh5$;H7QxY>V!L*@_I z?7^NiV9hEB)Wp|uG(j97T--bkm#0FUaQOY{2>1cUSOa@Cg4hB*XvCK~k(9r{!46y( z06YhDlM8i}2K~A}0>XhX;l2Tw@58!|H293m+5RaWV2=yLfywC(Ie$e4a_<)$pq}qK zuMJfMBiLMM1#$kB7D&4SfT;=QN*Z9!_Xy0#JwSaA&N*;-?;rWsZ+HU71hTM#Flf)7 zKz_9gsONlNQl);DHx37w7fFG6Pav2(!@UP*!PpT)T>|}_y*|d(<1<_@Bgm>{Lun+M_7a0o)-g~`J*NQ@Bluv zfW^z6f+OrPBGOrg@aDvG0lxI(Xx@em;6uK{xvBX`E9wmD_!VGW*k%BHm#LoZ^-s74 zdKT1?AAz;F)pbx?1FaqKadU|poe`hhQOF5x!woX2s7kFN9yK( zh7WBC+7+~QNQVj-3)XzMKrIi}9;2oQLH+&vI;IU+Pbh=90oiPTJqpQFL)&YYuyzdB zR$x60>H^L#VC(?;CMyBrrYz7s!Rlj&>^L2ddfSTlg@2yjgSM>~{1ZhYViR`@GFs>9g*1HSst@Zr5s zUXXUkKe&bo*CVb0Id_5eLD<#-4;Ge}&`DsALqHGb;rc(Ie-0d9PY$5Re#jrz6}El# zPd*947Xa|3(P=~)@L%vd|L1UWhxbFj25X^^b}c|VT+h)5>uH5EW83sYeNUVoMje43 zSOWe(ne0VJmqDEvv79p|&pwAFw|zA2$k7V1F>SL%BnoA?{H2kpJ8A2RIx9e6X45-1ak22l4MQNyexW*gw5aGCCKrGht^&jHTKl0-n8a>*B!gaMCu%D`7 zej4ndT0>K2Mz;4>&4cS6AQnN`5Z}L|UAVhEhW_#yVm3c_9Qf^{8sLXW>3eA~m;WQ* z!s!bR4_GfX12y=QU|kIE(Ha1FB+m?^hMqdD^<_9VlW&jOtO_&?Ak zQ1i56cfUt2z23iV=Que3>3b=0U>9Pz-00`=0qj3u{ouv6+~K|8 z8Nt$YX*h;RZ4UC~U(o$4KB?e-_KSLmIyfGye)j#)5n z0)M`J4dyVwk3nC;2A(+ro@Fx7LjwLy*f?Bp^G|`mhlmn7zs)}Zzx$LTxrtn&Uo+)I zYd&d!t}X)3g+UDA0e&3H5Bf9izz5sI;Ksm>`JWs~8R!w==s-lK>nozLF|Z8>!jcEF zl>%{00K^jL&;OkU>I%-F#-IS|4wOBhlu!Vw6R^8%S1*wNkp{;q_%Ut|fB|7am=HFs zQ^2wW8|Z)3DG0!azi0w=ISAMLSBThbE5hV{mw>v)Y)p(N_vXbIPM(M-V?tu02Tf>@CK{{%f(NNej2ws@Bv2@A2p*_HLjgBHlp;oJ zi*A45f*!{2+q%_wbdu?Aru*j2d-L|a@9jL~H5NPXQG=((T}K?BcqBBu>wh=IeGx5X z#nC%A71SHrz`SY1eFx)JzvG@`$l>iHL)R5;WmW(y`cv?pw2|p&A)~yQMBeWPxdE@8 z_n;kPTw{AN35*#comR{U)cE>*jdCH0+|sW{I}0Cn9=QVj_nCkFHEIGl2jo#sOpN&Q z>Kh9-u#-z(2Ktq18;?vOH(*UmKVma|Vc>fqlMAeGQy*QaK^1kbY0}2f`w%Ce)IZ6O zh~e+NrrbhqQ9JbH2*wygd>;9LP+w=GR)2 zo<5%Y5?#+(#&3H1Tl#v|rCkQ*dJlfvUQ3SGE7E}+Yghy8Hpzo}Aichxw|cOC_z8zT zlDtWO0hlV=+LI-(AqP;0IN7SnDs*rO^ueA|W^HLY97!cqb9X5n_y)QR2729;oxKySG%Uz9G!bS2lc>zkkGGJbQllZCBv3 zJPV9?j<&8(j`GY;F?U{f>?hdge&C|3z`M-L|JV`ir(75g!`36Pz24QFh_B^^F*_ED zbB4xwz-N^1s+lA$e-%c}QQK6H(YO+vzVQ%#jn?y@Y~#4x9R*PVsz|9Wlb; zSZs5_K9(9(ddRb~;?o8{R%uQckcIlt9}KBxaVBg!^OpH`-ZHR9C+{cJUdY@1@~Md7 O*`VFVx77b_r2Pd>X0Chy literal 0 HcmV?d00001 From cfd03de965a081facbd72316c76603dd7aa511bd Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Tue, 3 Sep 2024 10:25:38 +0200 Subject: [PATCH 05/58] Add Testnet4 launch shortcut for Windows --- share/setup.nsi.in | 2 ++ src/qt/res/bitcoin-qt-res.rc | 1 + 2 files changed, 3 insertions(+) diff --git a/share/setup.nsi.in b/share/setup.nsi.in index 6a6b18a55c..f22e256967 100644 --- a/share/setup.nsi.in +++ b/share/setup.nsi.in @@ -97,6 +97,7 @@ Section -post SEC0001 CreateShortcut "$SMPROGRAMS\$StartMenuGroup\$(^Name).lnk" $INSTDIR\@BITCOIN_GUI_NAME@@EXEEXT@ CreateShortcut "$SMPROGRAMS\$StartMenuGroup\@PACKAGE_NAME@ (testnet).lnk" "$INSTDIR\@BITCOIN_GUI_NAME@@EXEEXT@" "-testnet" "$INSTDIR\@BITCOIN_GUI_NAME@@EXEEXT@" 1 CreateShortcut "$SMPROGRAMS\$StartMenuGroup\@PACKAGE_NAME@ (test signet).lnk" "$INSTDIR\@BITCOIN_GUI_NAME@@EXEEXT@" "-signet" "$INSTDIR\@BITCOIN_GUI_NAME@@EXEEXT@" 2 + CreateShortcut "$SMPROGRAMS\$StartMenuGroup\@PACKAGE_NAME@ (testnet4).lnk" "$INSTDIR\@BITCOIN_GUI_NAME@@EXEEXT@" "-testnet4" "$INSTDIR\@BITCOIN_GUI_NAME@@EXEEXT@" 3 CreateShortcut "$SMPROGRAMS\$StartMenuGroup\Uninstall $(^Name).lnk" $INSTDIR\uninstall.exe !insertmacro MUI_STARTMENU_WRITE_END WriteRegStr HKCU "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" DisplayName "$(^Name)" @@ -142,6 +143,7 @@ Section -un.post UNSEC0001 Delete /REBOOTOK "$SMPROGRAMS\$StartMenuGroup\Uninstall $(^Name).lnk" Delete /REBOOTOK "$SMPROGRAMS\$StartMenuGroup\$(^Name).lnk" Delete /REBOOTOK "$SMPROGRAMS\$StartMenuGroup\@PACKAGE_NAME@ (testnet).lnk" + Delete /REBOOTOK "$SMPROGRAMS\$StartMenuGroup\@PACKAGE_NAME@ (testnet4).lnk" Delete /REBOOTOK "$SMPROGRAMS\$StartMenuGroup\@PACKAGE_NAME@ (test signet).lnk" Delete /REBOOTOK "$SMSTARTUP\Bitcoin.lnk" Delete /REBOOTOK $INSTDIR\uninstall.exe diff --git a/src/qt/res/bitcoin-qt-res.rc b/src/qt/res/bitcoin-qt-res.rc index 18bf877fc2..d84744f91e 100644 --- a/src/qt/res/bitcoin-qt-res.rc +++ b/src/qt/res/bitcoin-qt-res.rc @@ -1,6 +1,7 @@ IDI_ICON1 ICON DISCARDABLE "icons/bitcoin.ico" IDI_ICON2 ICON DISCARDABLE "icons/bitcoin_testnet.ico" IDI_ICON3 ICON DISCARDABLE "icons/bitcoin_signet.ico" +IDI_ICON4 ICON DISCARDABLE "icons/bitcoin_testnet.ico" // testnet4 #include // needed for VERSIONINFO #include "../../clientversion.h" // holds the needed client version information From e31bfb26c21f8b72f8c317e016d375862e27502e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc?= Date: Sun, 8 Sep 2024 21:57:11 +0200 Subject: [PATCH 06/58] refactor: Remove unrealistic simulation state In non-test code the input coin is never mutated - it's either replaced or ignored. --- src/test/fuzz/coinscache_sim.cpp | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/test/fuzz/coinscache_sim.cpp b/src/test/fuzz/coinscache_sim.cpp index 8e717e96b4..dd7a994335 100644 --- a/src/test/fuzz/coinscache_sim.cpp +++ b/src/test/fuzz/coinscache_sim.cpp @@ -137,9 +137,8 @@ struct CacheLevel /** Class for the base of the hierarchy (roughly simulating a memory-backed CCoinsViewDB). * - * The initial state consists of the empty UTXO set, though coins whose output index - * is 3 (mod 5) always have GetCoin() succeed (but returning an IsSpent() coin unless a UTXO - * exists). Coins whose output index is 4 (mod 5) have GetCoin() always succeed after being spent. + * The initial state consists of the empty UTXO set. + * Coins whose output index is 4 (mod 5) have GetCoin() always succeed after being spent. * This exercises code paths with spent, non-DIRTY cache entries. */ class CoinsViewBottom final : public CCoinsView @@ -151,14 +150,10 @@ class CoinsViewBottom final : public CCoinsView { auto it = m_data.find(outpoint); if (it == m_data.end()) { - if ((outpoint.n % 5) == 3) { - coin.Clear(); - return true; - } return false; } else { coin = it->second; - return true; + return true; // TODO GetCoin shouldn't return spent coins } } From 46dfbf169b49466de06dd16f7c23c6668419ef01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc?= Date: Sun, 8 Sep 2024 21:57:18 +0200 Subject: [PATCH 07/58] refactor: Return optional of Coin in GetCoin Leaving the parameter as well for now. Co-authored-by: TheCharlatan --- src/coins.cpp | 27 +++++++++++++++------------ src/coins.h | 13 +++++-------- src/test/coins_tests.cpp | 10 +++++----- src/test/fuzz/coins_view.cpp | 2 +- src/test/fuzz/coinscache_sim.cpp | 8 ++++---- src/txdb.cpp | 6 ++++-- src/txdb.h | 2 +- src/txmempool.cpp | 9 +++++---- src/txmempool.h | 2 +- 9 files changed, 41 insertions(+), 38 deletions(-) diff --git a/src/coins.cpp b/src/coins.cpp index a47ab8063e..058ba779f2 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -9,7 +9,7 @@ #include #include -bool CCoinsView::GetCoin(const COutPoint &outpoint, Coin &coin) const { return false; } +std::optional CCoinsView::GetCoin(const COutPoint& outpoint, Coin& coin) const { return std::nullopt; } uint256 CCoinsView::GetBestBlock() const { return uint256(); } std::vector CCoinsView::GetHeadBlocks() const { return std::vector(); } bool CCoinsView::BatchWrite(CoinsViewCacheCursor& cursor, const uint256 &hashBlock) { return false; } @@ -18,11 +18,11 @@ std::unique_ptr CCoinsView::Cursor() const { return nullptr; } bool CCoinsView::HaveCoin(const COutPoint &outpoint) const { Coin coin; - return GetCoin(outpoint, coin); + return GetCoin(outpoint, coin).has_value(); } CCoinsViewBacked::CCoinsViewBacked(CCoinsView *viewIn) : base(viewIn) { } -bool CCoinsViewBacked::GetCoin(const COutPoint &outpoint, Coin &coin) const { return base->GetCoin(outpoint, coin); } +std::optional CCoinsViewBacked::GetCoin(const COutPoint& outpoint, Coin& coin) const { return base->GetCoin(outpoint, coin); } bool CCoinsViewBacked::HaveCoin(const COutPoint &outpoint) const { return base->HaveCoin(outpoint); } uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); } std::vector CCoinsViewBacked::GetHeadBlocks() const { return base->GetHeadBlocks(); } @@ -58,13 +58,14 @@ CCoinsMap::iterator CCoinsViewCache::FetchCoin(const COutPoint &outpoint) const return ret; } -bool CCoinsViewCache::GetCoin(const COutPoint &outpoint, Coin &coin) const { +std::optional CCoinsViewCache::GetCoin(const COutPoint& outpoint, Coin& coin) const +{ CCoinsMap::const_iterator it = FetchCoin(outpoint); if (it != cacheCoins.end()) { coin = it->second.coin; - return !coin.IsSpent(); + if (!coin.IsSpent()) return coin; } - return false; + return std::nullopt; } void CCoinsViewCache::AddCoin(const COutPoint &outpoint, Coin&& coin, bool possible_overwrite) { @@ -363,8 +364,8 @@ const Coin& AccessByTxid(const CCoinsViewCache& view, const Txid& txid) return coinEmpty; } -template -static bool ExecuteBackedWrapper(Func func, const std::vector>& err_callbacks) +template +static ReturnType ExecuteBackedWrapper(Func func, const std::vector>& err_callbacks) { try { return func(); @@ -381,10 +382,12 @@ static bool ExecuteBackedWrapper(Func func, const std::vector CCoinsViewErrorCatcher::GetCoin(const COutPoint& outpoint, Coin& coin) const +{ + return ExecuteBackedWrapper>([&]() { return CCoinsViewBacked::GetCoin(outpoint, coin); }, m_err_callbacks); } -bool CCoinsViewErrorCatcher::HaveCoin(const COutPoint &outpoint) const { - return ExecuteBackedWrapper([&]() { return CCoinsViewBacked::HaveCoin(outpoint); }, m_err_callbacks); +bool CCoinsViewErrorCatcher::HaveCoin(const COutPoint& outpoint) const +{ + return ExecuteBackedWrapper([&]() { return CCoinsViewBacked::HaveCoin(outpoint); }, m_err_callbacks); } diff --git a/src/coins.h b/src/coins.h index 78b8eddacd..5445810559 100644 --- a/src/coins.h +++ b/src/coins.h @@ -303,11 +303,8 @@ struct CoinsViewCacheCursor class CCoinsView { public: - /** Retrieve the Coin (unspent transaction output) for a given outpoint. - * Returns true only when an unspent coin was found, which is returned in coin. - * When false is returned, coin's value is unspecified. - */ - virtual bool GetCoin(const COutPoint &outpoint, Coin &coin) const; + //! Retrieve the Coin (unspent transaction output) for a given outpoint. + virtual std::optional GetCoin(const COutPoint& outpoint, Coin& coin) const; //! Just check whether a given outpoint is unspent. virtual bool HaveCoin(const COutPoint &outpoint) const; @@ -344,7 +341,7 @@ class CCoinsViewBacked : public CCoinsView public: CCoinsViewBacked(CCoinsView *viewIn); - bool GetCoin(const COutPoint &outpoint, Coin &coin) const override; + std::optional GetCoin(const COutPoint& outpoint, Coin& coin) const override; bool HaveCoin(const COutPoint &outpoint) const override; uint256 GetBestBlock() const override; std::vector GetHeadBlocks() const override; @@ -384,7 +381,7 @@ class CCoinsViewCache : public CCoinsViewBacked CCoinsViewCache(const CCoinsViewCache &) = delete; // Standard CCoinsView methods - bool GetCoin(const COutPoint &outpoint, Coin &coin) const override; + std::optional GetCoin(const COutPoint& outpoint, Coin& coin) const override; bool HaveCoin(const COutPoint &outpoint) const override; uint256 GetBestBlock() const override; void SetBestBlock(const uint256 &hashBlock); @@ -514,7 +511,7 @@ class CCoinsViewErrorCatcher final : public CCoinsViewBacked m_err_callbacks.emplace_back(std::move(f)); } - bool GetCoin(const COutPoint &outpoint, Coin &coin) const override; + std::optional GetCoin(const COutPoint& outpoint, Coin& coin) const override; bool HaveCoin(const COutPoint &outpoint) const override; private: diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp index 0d18cd0c2b..7c08cb9ac2 100644 --- a/src/test/coins_tests.cpp +++ b/src/test/coins_tests.cpp @@ -44,18 +44,18 @@ class CCoinsViewTest : public CCoinsView public: CCoinsViewTest(FastRandomContext& rng) : m_rng{rng} {} - [[nodiscard]] bool GetCoin(const COutPoint& outpoint, Coin& coin) const override + std::optional GetCoin(const COutPoint& outpoint, Coin& coin) const override { std::map::const_iterator it = map_.find(outpoint); if (it == map_.end()) { - return false; + return std::nullopt; } coin = it->second; if (coin.IsSpent() && m_rng.randbool() == 0) { - // Randomly return false in case of an empty entry. - return false; + // Randomly return std::nullopt in case of an empty entry. + return std::nullopt; } - return true; + return coin; } uint256 GetBestBlock() const override { return hashBestBlock_; } diff --git a/src/test/fuzz/coins_view.cpp b/src/test/fuzz/coins_view.cpp index 368c69819a..ea40d71736 100644 --- a/src/test/fuzz/coins_view.cpp +++ b/src/test/fuzz/coins_view.cpp @@ -163,7 +163,7 @@ FUZZ_TARGET(coins_view, .init = initialize_coins_view) const bool exists_using_have_coin = coins_view_cache.HaveCoin(random_out_point); const bool exists_using_have_coin_in_cache = coins_view_cache.HaveCoinInCache(random_out_point); Coin coin_using_get_coin; - const bool exists_using_get_coin = coins_view_cache.GetCoin(random_out_point, coin_using_get_coin); + const bool exists_using_get_coin = coins_view_cache.GetCoin(random_out_point, coin_using_get_coin).has_value(); if (exists_using_get_coin) { assert(coin_using_get_coin == coin_using_access_coin); } diff --git a/src/test/fuzz/coinscache_sim.cpp b/src/test/fuzz/coinscache_sim.cpp index dd7a994335..a43cba6560 100644 --- a/src/test/fuzz/coinscache_sim.cpp +++ b/src/test/fuzz/coinscache_sim.cpp @@ -146,14 +146,14 @@ class CoinsViewBottom final : public CCoinsView std::map m_data; public: - bool GetCoin(const COutPoint& outpoint, Coin& coin) const final + std::optional GetCoin(const COutPoint& outpoint, Coin& coin) const final { auto it = m_data.find(outpoint); if (it == m_data.end()) { - return false; + return std::nullopt; } else { coin = it->second; - return true; // TODO GetCoin shouldn't return spent coins + return coin; // TODO GetCoin shouldn't return spent coins } } @@ -461,7 +461,7 @@ FUZZ_TARGET(coinscache_sim) // Compare the bottom coinsview (not a CCoinsViewCache) with sim_cache[0]. for (uint32_t outpointidx = 0; outpointidx < NUM_OUTPOINTS; ++outpointidx) { Coin realcoin; - bool real = bottom.GetCoin(data.outpoints[outpointidx], realcoin); + auto real = bottom.GetCoin(data.outpoints[outpointidx], realcoin); auto sim = lookup(outpointidx, 0); if (!sim.has_value()) { assert(!real || realcoin.IsSpent()); diff --git a/src/txdb.cpp b/src/txdb.cpp index 9b43a2b03e..c211479b3b 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -65,8 +65,10 @@ void CCoinsViewDB::ResizeCache(size_t new_cache_size) } } -bool CCoinsViewDB::GetCoin(const COutPoint &outpoint, Coin &coin) const { - return m_db->Read(CoinEntry(&outpoint), coin); +std::optional CCoinsViewDB::GetCoin(const COutPoint& outpoint, Coin& coin) const +{ + if (m_db->Read(CoinEntry(&outpoint), coin)) return coin; + else return std::nullopt; } bool CCoinsViewDB::HaveCoin(const COutPoint &outpoint) const { diff --git a/src/txdb.h b/src/txdb.h index 412d6c6009..e95c04c495 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -57,7 +57,7 @@ class CCoinsViewDB final : public CCoinsView public: explicit CCoinsViewDB(DBParams db_params, CoinsViewOptions options); - bool GetCoin(const COutPoint &outpoint, Coin &coin) const override; + std::optional GetCoin(const COutPoint& outpoint, Coin& coin) const override; bool HaveCoin(const COutPoint &outpoint) const override; uint256 GetBestBlock() const override; std::vector GetHeadBlocks() const override; diff --git a/src/txmempool.cpp b/src/txmempool.cpp index f8f5ec0360..80c33f2ba1 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -988,12 +988,13 @@ bool CTxMemPool::HasNoInputsOf(const CTransaction &tx) const CCoinsViewMemPool::CCoinsViewMemPool(CCoinsView* baseIn, const CTxMemPool& mempoolIn) : CCoinsViewBacked(baseIn), mempool(mempoolIn) { } -bool CCoinsViewMemPool::GetCoin(const COutPoint &outpoint, Coin &coin) const { +std::optional CCoinsViewMemPool::GetCoin(const COutPoint& outpoint, Coin& coin) const +{ // Check to see if the inputs are made available by another tx in the package. // These Coins would not be available in the underlying CoinsView. if (auto it = m_temp_added.find(outpoint); it != m_temp_added.end()) { coin = it->second; - return true; + return coin; } // If an entry in the mempool exists, always return that one, as it's guaranteed to never @@ -1004,9 +1005,9 @@ bool CCoinsViewMemPool::GetCoin(const COutPoint &outpoint, Coin &coin) const { if (outpoint.n < ptx->vout.size()) { coin = Coin(ptx->vout[outpoint.n], MEMPOOL_HEIGHT, false); m_non_base_coins.emplace(outpoint); - return true; + return coin; } else { - return false; + return std::nullopt; } } return base->GetCoin(outpoint, coin); diff --git a/src/txmempool.h b/src/txmempool.h index d0cb41a078..b9b0ba0f84 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -851,7 +851,7 @@ class CCoinsViewMemPool : public CCoinsViewBacked CCoinsViewMemPool(CCoinsView* baseIn, const CTxMemPool& mempoolIn); /** GetCoin, returning whether it exists and is not spent. Also updates m_non_base_coins if the * coin is not fetched from base. */ - bool GetCoin(const COutPoint &outpoint, Coin &coin) const override; + std::optional GetCoin(const COutPoint& outpoint, Coin& coin) const override; /** Add the coins created by this transaction. These coins are only temporarily stored in * m_temp_added and cannot be flushed to the back end. Only used for package validation. */ void PackageAddTransaction(const CTransactionRef& tx); From 4feaa28728442b0fd29a677d2b170a05fdf967c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc?= Date: Sat, 7 Sep 2024 23:12:35 +0200 Subject: [PATCH 08/58] refactor: Rely on returned value of GetCoin instead of parameter Also removed the unused coin parameter of GetCoin. Co-authored-by: Andrew Toth --- src/coins.cpp | 33 +++++++++++++-------------- src/coins.h | 8 +++---- src/node/coin.cpp | 9 ++++---- src/node/interfaces.cpp | 4 +--- src/rest.cpp | 7 +++--- src/rpc/blockchain.cpp | 21 ++++++++---------- src/test/coins_tests.cpp | 16 +++++--------- src/test/fuzz/coins_view.cpp | 16 ++++++-------- src/test/fuzz/coinscache_sim.cpp | 38 ++++++++++++++------------------ src/test/fuzz/tx_pool.cpp | 5 ++--- src/test/util/setup_common.cpp | 5 ++--- src/txdb.cpp | 6 ++--- src/txdb.h | 2 +- src/txmempool.cpp | 12 +++++----- src/txmempool.h | 2 +- src/validation.cpp | 14 +++++------- 16 files changed, 85 insertions(+), 113 deletions(-) diff --git a/src/coins.cpp b/src/coins.cpp index 058ba779f2..cb09aa2e7a 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -9,7 +9,7 @@ #include #include -std::optional CCoinsView::GetCoin(const COutPoint& outpoint, Coin& coin) const { return std::nullopt; } +std::optional CCoinsView::GetCoin(const COutPoint& outpoint) const { return std::nullopt; } uint256 CCoinsView::GetBestBlock() const { return uint256(); } std::vector CCoinsView::GetHeadBlocks() const { return std::vector(); } bool CCoinsView::BatchWrite(CoinsViewCacheCursor& cursor, const uint256 &hashBlock) { return false; } @@ -17,12 +17,11 @@ std::unique_ptr CCoinsView::Cursor() const { return nullptr; } bool CCoinsView::HaveCoin(const COutPoint &outpoint) const { - Coin coin; - return GetCoin(outpoint, coin).has_value(); + return GetCoin(outpoint).has_value(); } CCoinsViewBacked::CCoinsViewBacked(CCoinsView *viewIn) : base(viewIn) { } -std::optional CCoinsViewBacked::GetCoin(const COutPoint& outpoint, Coin& coin) const { return base->GetCoin(outpoint, coin); } +std::optional CCoinsViewBacked::GetCoin(const COutPoint& outpoint) const { return base->GetCoin(outpoint); } bool CCoinsViewBacked::HaveCoin(const COutPoint &outpoint) const { return base->HaveCoin(outpoint); } uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); } std::vector CCoinsViewBacked::GetHeadBlocks() const { return base->GetHeadBlocks(); } @@ -45,26 +44,24 @@ size_t CCoinsViewCache::DynamicMemoryUsage() const { CCoinsMap::iterator CCoinsViewCache::FetchCoin(const COutPoint &outpoint) const { const auto [ret, inserted] = cacheCoins.try_emplace(outpoint); if (inserted) { - if (!base->GetCoin(outpoint, ret->second.coin)) { + if (auto coin{base->GetCoin(outpoint)}) { + ret->second.coin = std::move(*coin); + cachedCoinsUsage += ret->second.coin.DynamicMemoryUsage(); + if (ret->second.coin.IsSpent()) { // TODO GetCoin cannot return spent coins + // The parent only has an empty entry for this outpoint; we can consider our version as fresh. + ret->second.AddFlags(CCoinsCacheEntry::FRESH, *ret, m_sentinel); + } + } else { cacheCoins.erase(ret); return cacheCoins.end(); } - if (ret->second.coin.IsSpent()) { - // The parent only has an empty entry for this outpoint; we can consider our version as fresh. - ret->second.AddFlags(CCoinsCacheEntry::FRESH, *ret, m_sentinel); - } - cachedCoinsUsage += ret->second.coin.DynamicMemoryUsage(); } return ret; } -std::optional CCoinsViewCache::GetCoin(const COutPoint& outpoint, Coin& coin) const +std::optional CCoinsViewCache::GetCoin(const COutPoint& outpoint) const { - CCoinsMap::const_iterator it = FetchCoin(outpoint); - if (it != cacheCoins.end()) { - coin = it->second.coin; - if (!coin.IsSpent()) return coin; - } + if (auto it{FetchCoin(outpoint)}; it != cacheCoins.end() && !it->second.coin.IsSpent()) return it->second.coin; return std::nullopt; } @@ -382,9 +379,9 @@ static ReturnType ExecuteBackedWrapper(Func func, const std::vector CCoinsViewErrorCatcher::GetCoin(const COutPoint& outpoint, Coin& coin) const +std::optional CCoinsViewErrorCatcher::GetCoin(const COutPoint& outpoint) const { - return ExecuteBackedWrapper>([&]() { return CCoinsViewBacked::GetCoin(outpoint, coin); }, m_err_callbacks); + return ExecuteBackedWrapper>([&]() { return CCoinsViewBacked::GetCoin(outpoint); }, m_err_callbacks); } bool CCoinsViewErrorCatcher::HaveCoin(const COutPoint& outpoint) const diff --git a/src/coins.h b/src/coins.h index 5445810559..a2449e1b81 100644 --- a/src/coins.h +++ b/src/coins.h @@ -304,7 +304,7 @@ class CCoinsView { public: //! Retrieve the Coin (unspent transaction output) for a given outpoint. - virtual std::optional GetCoin(const COutPoint& outpoint, Coin& coin) const; + virtual std::optional GetCoin(const COutPoint& outpoint) const; //! Just check whether a given outpoint is unspent. virtual bool HaveCoin(const COutPoint &outpoint) const; @@ -341,7 +341,7 @@ class CCoinsViewBacked : public CCoinsView public: CCoinsViewBacked(CCoinsView *viewIn); - std::optional GetCoin(const COutPoint& outpoint, Coin& coin) const override; + std::optional GetCoin(const COutPoint& outpoint) const override; bool HaveCoin(const COutPoint &outpoint) const override; uint256 GetBestBlock() const override; std::vector GetHeadBlocks() const override; @@ -381,7 +381,7 @@ class CCoinsViewCache : public CCoinsViewBacked CCoinsViewCache(const CCoinsViewCache &) = delete; // Standard CCoinsView methods - std::optional GetCoin(const COutPoint& outpoint, Coin& coin) const override; + std::optional GetCoin(const COutPoint& outpoint) const override; bool HaveCoin(const COutPoint &outpoint) const override; uint256 GetBestBlock() const override; void SetBestBlock(const uint256 &hashBlock); @@ -511,7 +511,7 @@ class CCoinsViewErrorCatcher final : public CCoinsViewBacked m_err_callbacks.emplace_back(std::move(f)); } - std::optional GetCoin(const COutPoint& outpoint, Coin& coin) const override; + std::optional GetCoin(const COutPoint& outpoint) const override; bool HaveCoin(const COutPoint &outpoint) const override; private: diff --git a/src/node/coin.cpp b/src/node/coin.cpp index 221854c5f6..eba67f4cb5 100644 --- a/src/node/coin.cpp +++ b/src/node/coin.cpp @@ -16,10 +16,11 @@ void FindCoins(const NodeContext& node, std::map& coins) LOCK2(cs_main, node.mempool->cs); CCoinsViewCache& chain_view = node.chainman->ActiveChainstate().CoinsTip(); CCoinsViewMemPool mempool_view(&chain_view, *node.mempool); - for (auto& coin : coins) { - if (!mempool_view.GetCoin(coin.first, coin.second)) { - // Either the coin is not in the CCoinsViewCache or is spent. Clear it. - coin.second.Clear(); + for (auto& [outpoint, coin] : coins) { + if (auto c{mempool_view.GetCoin(outpoint)}) { + coin = std::move(*c); + } else { + coin.Clear(); // Either the coin is not in the CCoinsViewCache or is spent } } } diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 510541dfda..2fe943035b 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -352,9 +352,7 @@ class NodeImpl : public Node std::optional getUnspentOutput(const COutPoint& output) override { LOCK(::cs_main); - Coin coin; - if (chainman().ActiveChainstate().CoinsTip().GetCoin(output, coin)) return coin; - return {}; + return chainman().ActiveChainstate().CoinsTip().GetCoin(output); } TransactionError broadcastTransaction(CTransactionRef tx, CAmount max_tx_fee, std::string& err_string) override { diff --git a/src/rest.cpp b/src/rest.cpp index 4732922a15..89932e2e0b 100644 --- a/src/rest.cpp +++ b/src/rest.cpp @@ -870,10 +870,9 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std:: { auto process_utxos = [&vOutPoints, &outs, &hits, &active_height, &active_hash, &chainman](const CCoinsView& view, const CTxMemPool* mempool) EXCLUSIVE_LOCKS_REQUIRED(chainman.GetMutex()) { for (const COutPoint& vOutPoint : vOutPoints) { - Coin coin; - bool hit = (!mempool || !mempool->isSpent(vOutPoint)) && view.GetCoin(vOutPoint, coin); - hits.push_back(hit); - if (hit) outs.emplace_back(std::move(coin)); + auto coin = !mempool || !mempool->isSpent(vOutPoint) ? view.GetCoin(vOutPoint) : std::nullopt; + hits.push_back(coin.has_value()); + if (coin) outs.emplace_back(std::move(*coin)); } active_height = chainman.ActiveHeight(); active_hash = chainman.ActiveTip()->GetBlockHash(); diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index a9cea44779..cdcf2ca597 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1140,35 +1140,32 @@ static RPCHelpMan gettxout() if (!request.params[2].isNull()) fMempool = request.params[2].get_bool(); - Coin coin; Chainstate& active_chainstate = chainman.ActiveChainstate(); CCoinsViewCache* coins_view = &active_chainstate.CoinsTip(); + std::optional coin; if (fMempool) { const CTxMemPool& mempool = EnsureMemPool(node); LOCK(mempool.cs); CCoinsViewMemPool view(coins_view, mempool); - if (!view.GetCoin(out, coin) || mempool.isSpent(out)) { - return UniValue::VNULL; - } + if (!mempool.isSpent(out)) coin = view.GetCoin(out); } else { - if (!coins_view->GetCoin(out, coin)) { - return UniValue::VNULL; - } + coin = coins_view->GetCoin(out); } + if (!coin) return UniValue::VNULL; const CBlockIndex* pindex = active_chainstate.m_blockman.LookupBlockIndex(coins_view->GetBestBlock()); ret.pushKV("bestblock", pindex->GetBlockHash().GetHex()); - if (coin.nHeight == MEMPOOL_HEIGHT) { + if (coin->nHeight == MEMPOOL_HEIGHT) { ret.pushKV("confirmations", 0); } else { - ret.pushKV("confirmations", (int64_t)(pindex->nHeight - coin.nHeight + 1)); + ret.pushKV("confirmations", (int64_t)(pindex->nHeight - coin->nHeight + 1)); } - ret.pushKV("value", ValueFromAmount(coin.out.nValue)); + ret.pushKV("value", ValueFromAmount(coin->out.nValue)); UniValue o(UniValue::VOBJ); - ScriptToUniv(coin.out.scriptPubKey, /*out=*/o, /*include_hex=*/true, /*include_address=*/true); + ScriptToUniv(coin->out.scriptPubKey, /*out=*/o, /*include_hex=*/true, /*include_address=*/true); ret.pushKV("scriptPubKey", std::move(o)); - ret.pushKV("coinbase", (bool)coin.fCoinBase); + ret.pushKV("coinbase", (bool)coin->fCoinBase); return ret; }, diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp index 7c08cb9ac2..ec4720966b 100644 --- a/src/test/coins_tests.cpp +++ b/src/test/coins_tests.cpp @@ -44,18 +44,14 @@ class CCoinsViewTest : public CCoinsView public: CCoinsViewTest(FastRandomContext& rng) : m_rng{rng} {} - std::optional GetCoin(const COutPoint& outpoint, Coin& coin) const override + std::optional GetCoin(const COutPoint& outpoint) const override { - std::map::const_iterator it = map_.find(outpoint); - if (it == map_.end()) { - return std::nullopt; - } - coin = it->second; - if (coin.IsSpent() && m_rng.randbool() == 0) { - // Randomly return std::nullopt in case of an empty entry. - return std::nullopt; + if (auto it{map_.find(outpoint)}; it != map_.end()) { + if (!it->second.IsSpent() || m_rng.randbool()) { + return it->second; // TODO spent coins shouldn't be returned + } } - return coin; + return std::nullopt; } uint256 GetBestBlock() const override { return hashBestBlock_; } diff --git a/src/test/fuzz/coins_view.cpp b/src/test/fuzz/coins_view.cpp index ea40d71736..54bfb93b49 100644 --- a/src/test/fuzz/coins_view.cpp +++ b/src/test/fuzz/coins_view.cpp @@ -162,22 +162,20 @@ FUZZ_TARGET(coins_view, .init = initialize_coins_view) const bool exists_using_access_coin = !(coin_using_access_coin == EMPTY_COIN); const bool exists_using_have_coin = coins_view_cache.HaveCoin(random_out_point); const bool exists_using_have_coin_in_cache = coins_view_cache.HaveCoinInCache(random_out_point); - Coin coin_using_get_coin; - const bool exists_using_get_coin = coins_view_cache.GetCoin(random_out_point, coin_using_get_coin).has_value(); - if (exists_using_get_coin) { - assert(coin_using_get_coin == coin_using_access_coin); + if (auto coin{coins_view_cache.GetCoin(random_out_point)}) { + assert(*coin == coin_using_access_coin); + assert(exists_using_access_coin && exists_using_have_coin_in_cache && exists_using_have_coin); + } else { + assert(!exists_using_access_coin && !exists_using_have_coin_in_cache && !exists_using_have_coin); } - assert((exists_using_access_coin && exists_using_have_coin_in_cache && exists_using_have_coin && exists_using_get_coin) || - (!exists_using_access_coin && !exists_using_have_coin_in_cache && !exists_using_have_coin && !exists_using_get_coin)); // If HaveCoin on the backend is true, it must also be on the cache if the coin wasn't spent. const bool exists_using_have_coin_in_backend = backend_coins_view.HaveCoin(random_out_point); if (!coin_using_access_coin.IsSpent() && exists_using_have_coin_in_backend) { assert(exists_using_have_coin); } - Coin coin_using_backend_get_coin; - if (backend_coins_view.GetCoin(random_out_point, coin_using_backend_get_coin)) { + if (auto coin{backend_coins_view.GetCoin(random_out_point)}) { assert(exists_using_have_coin_in_backend); - // Note we can't assert that `coin_using_get_coin == coin_using_backend_get_coin` because the coin in + // Note we can't assert that `coin_using_get_coin == *coin` because the coin in // the cache may have been modified but not yet flushed. } else { assert(!exists_using_have_coin_in_backend); diff --git a/src/test/fuzz/coinscache_sim.cpp b/src/test/fuzz/coinscache_sim.cpp index a43cba6560..6000d52fc9 100644 --- a/src/test/fuzz/coinscache_sim.cpp +++ b/src/test/fuzz/coinscache_sim.cpp @@ -146,15 +146,11 @@ class CoinsViewBottom final : public CCoinsView std::map m_data; public: - std::optional GetCoin(const COutPoint& outpoint, Coin& coin) const final + std::optional GetCoin(const COutPoint& outpoint) const final { - auto it = m_data.find(outpoint); - if (it == m_data.end()) { - return std::nullopt; - } else { - coin = it->second; - return coin; // TODO GetCoin shouldn't return spent coins - } + // TODO GetCoin shouldn't return spent coins + if (auto it = m_data.find(outpoint); it != m_data.end()) return it->second; + return std::nullopt; } bool HaveCoin(const COutPoint& outpoint) const final @@ -265,17 +261,16 @@ FUZZ_TARGET(coinscache_sim) // Look up in simulation data. auto sim = lookup(outpointidx); // Look up in real caches. - Coin realcoin; - auto real = caches.back()->GetCoin(data.outpoints[outpointidx], realcoin); + auto realcoin = caches.back()->GetCoin(data.outpoints[outpointidx]); // Compare results. if (!sim.has_value()) { - assert(!real || realcoin.IsSpent()); + assert(!realcoin || realcoin->IsSpent()); } else { - assert(real && !realcoin.IsSpent()); + assert(realcoin && !realcoin->IsSpent()); const auto& simcoin = data.coins[sim->first]; - assert(realcoin.out == simcoin.out); - assert(realcoin.fCoinBase == simcoin.fCoinBase); - assert(realcoin.nHeight == sim->second); + assert(realcoin->out == simcoin.out); + assert(realcoin->fCoinBase == simcoin.fCoinBase); + assert(realcoin->nHeight == sim->second); } }, @@ -460,16 +455,15 @@ FUZZ_TARGET(coinscache_sim) // Compare the bottom coinsview (not a CCoinsViewCache) with sim_cache[0]. for (uint32_t outpointidx = 0; outpointidx < NUM_OUTPOINTS; ++outpointidx) { - Coin realcoin; - auto real = bottom.GetCoin(data.outpoints[outpointidx], realcoin); + auto realcoin = bottom.GetCoin(data.outpoints[outpointidx]); auto sim = lookup(outpointidx, 0); if (!sim.has_value()) { - assert(!real || realcoin.IsSpent()); + assert(!realcoin || realcoin->IsSpent()); } else { - assert(real && !realcoin.IsSpent()); - assert(realcoin.out == data.coins[sim->first].out); - assert(realcoin.fCoinBase == data.coins[sim->first].fCoinBase); - assert(realcoin.nHeight == sim->second); + assert(realcoin && !realcoin->IsSpent()); + assert(realcoin->out == data.coins[sim->first].out); + assert(realcoin->fCoinBase == data.coins[sim->first].fCoinBase); + assert(realcoin->nHeight == sim->second); } } } diff --git a/src/test/fuzz/tx_pool.cpp b/src/test/fuzz/tx_pool.cpp index 64861311db..39aa404484 100644 --- a/src/test/fuzz/tx_pool.cpp +++ b/src/test/fuzz/tx_pool.cpp @@ -214,9 +214,8 @@ FUZZ_TARGET(tx_pool_standard, .init = initialize_tx_pool) // Helper to query an amount const CCoinsViewMemPool amount_view{WITH_LOCK(::cs_main, return &chainstate.CoinsTip()), tx_pool}; const auto GetAmount = [&](const COutPoint& outpoint) { - Coin c; - Assert(amount_view.GetCoin(outpoint, c)); - return c.out.nValue; + auto coin{amount_view.GetCoin(outpoint).value()}; + return coin.out.nValue; }; LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 300) diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index fa89ceb332..2246d3010a 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -432,9 +432,8 @@ std::pair TestChain100Setup::CreateValidTransactio std::map input_coins; CAmount inputs_amount{0}; for (const auto& outpoint_to_spend : inputs) { - // - Use GetCoin to properly populate utxo_to_spend, - Coin utxo_to_spend; - assert(coins_cache.GetCoin(outpoint_to_spend, utxo_to_spend)); + // Use GetCoin to properly populate utxo_to_spend + auto utxo_to_spend{coins_cache.GetCoin(outpoint_to_spend).value()}; input_coins.insert({outpoint_to_spend, utxo_to_spend}); inputs_amount += utxo_to_spend.out.nValue; } diff --git a/src/txdb.cpp b/src/txdb.cpp index c211479b3b..1622039d63 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -65,10 +65,10 @@ void CCoinsViewDB::ResizeCache(size_t new_cache_size) } } -std::optional CCoinsViewDB::GetCoin(const COutPoint& outpoint, Coin& coin) const +std::optional CCoinsViewDB::GetCoin(const COutPoint& outpoint) const { - if (m_db->Read(CoinEntry(&outpoint), coin)) return coin; - else return std::nullopt; + if (Coin coin; m_db->Read(CoinEntry(&outpoint), coin)) return coin; + return std::nullopt; } bool CCoinsViewDB::HaveCoin(const COutPoint &outpoint) const { diff --git a/src/txdb.h b/src/txdb.h index e95c04c495..565b060dbe 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -57,7 +57,7 @@ class CCoinsViewDB final : public CCoinsView public: explicit CCoinsViewDB(DBParams db_params, CoinsViewOptions options); - std::optional GetCoin(const COutPoint& outpoint, Coin& coin) const override; + std::optional GetCoin(const COutPoint& outpoint) const override; bool HaveCoin(const COutPoint &outpoint) const override; uint256 GetBestBlock() const override; std::vector GetHeadBlocks() const override; diff --git a/src/txmempool.cpp b/src/txmempool.cpp index 80c33f2ba1..4756c20209 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -988,13 +988,12 @@ bool CTxMemPool::HasNoInputsOf(const CTransaction &tx) const CCoinsViewMemPool::CCoinsViewMemPool(CCoinsView* baseIn, const CTxMemPool& mempoolIn) : CCoinsViewBacked(baseIn), mempool(mempoolIn) { } -std::optional CCoinsViewMemPool::GetCoin(const COutPoint& outpoint, Coin& coin) const +std::optional CCoinsViewMemPool::GetCoin(const COutPoint& outpoint) const { // Check to see if the inputs are made available by another tx in the package. // These Coins would not be available in the underlying CoinsView. if (auto it = m_temp_added.find(outpoint); it != m_temp_added.end()) { - coin = it->second; - return coin; + return it->second; } // If an entry in the mempool exists, always return that one, as it's guaranteed to never @@ -1003,14 +1002,13 @@ std::optional CCoinsViewMemPool::GetCoin(const COutPoint& outpoint, Coin& CTransactionRef ptx = mempool.get(outpoint.hash); if (ptx) { if (outpoint.n < ptx->vout.size()) { - coin = Coin(ptx->vout[outpoint.n], MEMPOOL_HEIGHT, false); + Coin coin(ptx->vout[outpoint.n], MEMPOOL_HEIGHT, false); m_non_base_coins.emplace(outpoint); return coin; - } else { - return std::nullopt; } + return std::nullopt; } - return base->GetCoin(outpoint, coin); + return base->GetCoin(outpoint); } void CCoinsViewMemPool::PackageAddTransaction(const CTransactionRef& tx) diff --git a/src/txmempool.h b/src/txmempool.h index b9b0ba0f84..f914cbd729 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -851,7 +851,7 @@ class CCoinsViewMemPool : public CCoinsViewBacked CCoinsViewMemPool(CCoinsView* baseIn, const CTxMemPool& mempoolIn); /** GetCoin, returning whether it exists and is not spent. Also updates m_non_base_coins if the * coin is not fetched from base. */ - std::optional GetCoin(const COutPoint& outpoint, Coin& coin) const override; + std::optional GetCoin(const COutPoint& outpoint) const override; /** Add the coins created by this transaction. These coins are only temporarily stored in * m_temp_added and cannot be flushed to the back end. Only used for package validation. */ void PackageAddTransaction(const CTransactionRef& tx); diff --git a/src/validation.cpp b/src/validation.cpp index 38fcda55af..5c33d3f8b6 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -180,18 +180,14 @@ std::optional> CalculatePrevHeights( std::vector prev_heights; prev_heights.resize(tx.vin.size()); for (size_t i = 0; i < tx.vin.size(); ++i) { - const CTxIn& txin = tx.vin[i]; - Coin coin; - if (!coins.GetCoin(txin.prevout, coin)) { + if (auto coin{coins.GetCoin(tx.vin[i].prevout)}) { + prev_heights[i] = coin->nHeight == MEMPOOL_HEIGHT + ? tip.nHeight + 1 // Assume all mempool transaction confirm in the next block. + : coin->nHeight; + } else { LogPrintf("ERROR: %s: Missing input %d in transaction \'%s\'\n", __func__, i, tx.GetHash().GetHex()); return std::nullopt; } - if (coin.nHeight == MEMPOOL_HEIGHT) { - // Assume all mempool transaction confirm in the next block. - prev_heights[i] = tip.nHeight + 1; - } else { - prev_heights[i] = coin.nHeight; - } } return prev_heights; } From b832ffe04468762f94c6b8a3efb2a978bd16e52e Mon Sep 17 00:00:00 2001 From: tdb3 <106488469+tdb3@users.noreply.github.com> Date: Wed, 28 Aug 2024 07:57:59 -0400 Subject: [PATCH 09/58] refactor: introduce default pid file name constant in tests --- test/functional/feature_filelock.py | 7 +++++-- test/functional/test_framework/test_node.py | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/test/functional/feature_filelock.py b/test/functional/feature_filelock.py index e71871114d..79d2f65164 100755 --- a/test/functional/feature_filelock.py +++ b/test/functional/feature_filelock.py @@ -7,7 +7,10 @@ import string from test_framework.test_framework import BitcoinTestFramework -from test_framework.test_node import ErrorMatch +from test_framework.test_node import ( + BITCOIN_PID_FILENAME_DEFAULT, + ErrorMatch, +) class FilelockTest(BitcoinTestFramework): def add_options(self, parser): @@ -33,7 +36,7 @@ def run_test(self): self.log.info("Check that cookie and PID file are not deleted when attempting to start a second bitcoind using the same datadir") cookie_file = datadir / ".cookie" assert cookie_file.exists() # should not be deleted during the second bitcoind instance shutdown - pid_file = datadir / "bitcoind.pid" + pid_file = datadir / BITCOIN_PID_FILENAME_DEFAULT assert pid_file.exists() if self.is_wallet_compiled(): diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index 60ca9269a5..e59ff4e392 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -48,6 +48,7 @@ NUM_XOR_BYTES = 8 # The null blocks key (all 0s) NULL_BLK_XOR_KEY = bytes([0] * NUM_XOR_BYTES) +BITCOIN_PID_FILENAME_DEFAULT = "bitcoind.pid" class FailedToStartError(Exception): From 04e4d52420a0e6bf40d4bd6fe1f31f66db9eab0a Mon Sep 17 00:00:00 2001 From: Sebastian Falbesoner Date: Tue, 27 Aug 2024 14:16:24 +0200 Subject: [PATCH 10/58] test: add test for specifying custom pidfile via `-pid` --- test/functional/feature_init.py | 38 ++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/test/functional/feature_init.py b/test/functional/feature_init.py index 659d33684e..ff3a9391b8 100755 --- a/test/functional/feature_init.py +++ b/test/functional/feature_init.py @@ -2,17 +2,20 @@ # Copyright (c) 2021-present The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. -"""Stress tests related to node initialization.""" +"""Tests related to node initialization.""" from pathlib import Path import platform import shutil from test_framework.test_framework import BitcoinTestFramework, SkipTest -from test_framework.test_node import ErrorMatch +from test_framework.test_node import ( + BITCOIN_PID_FILENAME_DEFAULT, + ErrorMatch, +) from test_framework.util import assert_equal -class InitStressTest(BitcoinTestFramework): +class InitTest(BitcoinTestFramework): """ Ensure that initialization can be interrupted at a number of points and not impair subsequent starts. @@ -25,7 +28,7 @@ def set_test_params(self): self.setup_clean_chain = False self.num_nodes = 1 - def run_test(self): + def init_stress_test(self): """ - test terminating initialization after seeing a certain log line. - test removing certain essential files to test startup error paths. @@ -147,6 +150,31 @@ def check_clean_start(): shutil.move(node.chain_path / "blocks_bak", node.chain_path / "blocks") shutil.move(node.chain_path / "chainstate_bak", node.chain_path / "chainstate") + def init_pid_test(self): + BITCOIN_PID_FILENAME_CUSTOM = "my_fancy_bitcoin_pid_file.foobar" + + self.log.info("Test specifying custom pid file via -pid command line option") + custom_pidfile_relative = BITCOIN_PID_FILENAME_CUSTOM + self.log.info(f"-> path relative to datadir ({custom_pidfile_relative})") + self.restart_node(0, [f"-pid={custom_pidfile_relative}"]) + datadir = self.nodes[0].chain_path + assert not (datadir / BITCOIN_PID_FILENAME_DEFAULT).exists() + assert (datadir / custom_pidfile_relative).exists() + self.stop_node(0) + assert not (datadir / custom_pidfile_relative).exists() + + custom_pidfile_absolute = Path(self.options.tmpdir) / BITCOIN_PID_FILENAME_CUSTOM + self.log.info(f"-> absolute path ({custom_pidfile_absolute})") + self.restart_node(0, [f"-pid={custom_pidfile_absolute}"]) + assert not (datadir / BITCOIN_PID_FILENAME_DEFAULT).exists() + assert custom_pidfile_absolute.exists() + self.stop_node(0) + assert not custom_pidfile_absolute.exists() + + def run_test(self): + self.init_pid_test() + self.init_stress_test() + if __name__ == '__main__': - InitStressTest(__file__).main() + InitTest(__file__).main() From 0a12cff2a8e54453de1f17e9c0e87e54bbe25a34 Mon Sep 17 00:00:00 2001 From: brunoerg Date: Wed, 28 Feb 2024 12:49:08 -0300 Subject: [PATCH 11/58] fuzz: move `AddrManDeterministic` to util --- src/test/fuzz/addrman.cpp | 110 ++------------------------------------ src/test/fuzz/util/net.h | 104 +++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 106 deletions(-) diff --git a/src/test/fuzz/addrman.cpp b/src/test/fuzz/addrman.cpp index bcc3dd3e14..570bcb2c13 100644 --- a/src/test/fuzz/addrman.cpp +++ b/src/test/fuzz/addrman.cpp @@ -118,121 +118,19 @@ void FillAddrman(AddrMan& addrman, FuzzedDataProvider& fuzzed_data_provider) } } -class AddrManDeterministic : public AddrMan -{ -public: - explicit AddrManDeterministic(const NetGroupManager& netgroupman, FuzzedDataProvider& fuzzed_data_provider) - : AddrMan(netgroupman, /*deterministic=*/true, GetCheckRatio()) - { - WITH_LOCK(m_impl->cs, m_impl->insecure_rand.Reseed(ConsumeUInt256(fuzzed_data_provider))); - } - - /** - * Compare with another AddrMan. - * This compares: - * - the values in `mapInfo` (the keys aka ids are ignored) - * - vvNew entries refer to the same addresses - * - vvTried entries refer to the same addresses - */ - bool operator==(const AddrManDeterministic& other) const - { - LOCK2(m_impl->cs, other.m_impl->cs); - - if (m_impl->mapInfo.size() != other.m_impl->mapInfo.size() || m_impl->nNew != other.m_impl->nNew || - m_impl->nTried != other.m_impl->nTried) { - return false; - } - - // Check that all values in `mapInfo` are equal to all values in `other.mapInfo`. - // Keys may be different. - - auto addrinfo_hasher = [](const AddrInfo& a) { - CSipHasher hasher(0, 0); - auto addr_key = a.GetKey(); - auto source_key = a.source.GetAddrBytes(); - hasher.Write(TicksSinceEpoch(a.m_last_success)); - hasher.Write(a.nAttempts); - hasher.Write(a.nRefCount); - hasher.Write(a.fInTried); - hasher.Write(a.GetNetwork()); - hasher.Write(a.source.GetNetwork()); - hasher.Write(addr_key.size()); - hasher.Write(source_key.size()); - hasher.Write(addr_key); - hasher.Write(source_key); - return (size_t)hasher.Finalize(); - }; - - auto addrinfo_eq = [](const AddrInfo& lhs, const AddrInfo& rhs) { - return std::tie(static_cast(lhs), lhs.source, lhs.m_last_success, lhs.nAttempts, lhs.nRefCount, lhs.fInTried) == - std::tie(static_cast(rhs), rhs.source, rhs.m_last_success, rhs.nAttempts, rhs.nRefCount, rhs.fInTried); - }; - - using Addresses = std::unordered_set; - - const size_t num_addresses{m_impl->mapInfo.size()}; - - Addresses addresses{num_addresses, addrinfo_hasher, addrinfo_eq}; - for (const auto& [id, addr] : m_impl->mapInfo) { - addresses.insert(addr); - } - - Addresses other_addresses{num_addresses, addrinfo_hasher, addrinfo_eq}; - for (const auto& [id, addr] : other.m_impl->mapInfo) { - other_addresses.insert(addr); - } - - if (addresses != other_addresses) { - return false; - } - - auto IdsReferToSameAddress = [&](nid_type id, nid_type other_id) EXCLUSIVE_LOCKS_REQUIRED(m_impl->cs, other.m_impl->cs) { - if (id == -1 && other_id == -1) { - return true; - } - if ((id == -1 && other_id != -1) || (id != -1 && other_id == -1)) { - return false; - } - return m_impl->mapInfo.at(id) == other.m_impl->mapInfo.at(other_id); - }; - - // Check that `vvNew` contains the same addresses as `other.vvNew`. Notice - `vvNew[i][j]` - // contains just an id and the address is to be found in `mapInfo.at(id)`. The ids - // themselves may differ between `vvNew` and `other.vvNew`. - for (size_t i = 0; i < ADDRMAN_NEW_BUCKET_COUNT; ++i) { - for (size_t j = 0; j < ADDRMAN_BUCKET_SIZE; ++j) { - if (!IdsReferToSameAddress(m_impl->vvNew[i][j], other.m_impl->vvNew[i][j])) { - return false; - } - } - } - - // Same for `vvTried`. - for (size_t i = 0; i < ADDRMAN_TRIED_BUCKET_COUNT; ++i) { - for (size_t j = 0; j < ADDRMAN_BUCKET_SIZE; ++j) { - if (!IdsReferToSameAddress(m_impl->vvTried[i][j], other.m_impl->vvTried[i][j])) { - return false; - } - } - } - - return true; - } -}; - FUZZ_TARGET(addrman, .init = initialize_addrman) { FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); SetMockTime(ConsumeTime(fuzzed_data_provider)); NetGroupManager netgroupman{ConsumeNetGroupManager(fuzzed_data_provider)}; - auto addr_man_ptr = std::make_unique(netgroupman, fuzzed_data_provider); + auto addr_man_ptr = std::make_unique(netgroupman, fuzzed_data_provider, GetCheckRatio()); if (fuzzed_data_provider.ConsumeBool()) { const std::vector serialized_data{ConsumeRandomLengthByteVector(fuzzed_data_provider)}; DataStream ds{serialized_data}; try { ds >> *addr_man_ptr; } catch (const std::ios_base::failure&) { - addr_man_ptr = std::make_unique(netgroupman, fuzzed_data_provider); + addr_man_ptr = std::make_unique(netgroupman, fuzzed_data_provider, GetCheckRatio()); } } AddrManDeterministic& addr_man = *addr_man_ptr; @@ -310,8 +208,8 @@ FUZZ_TARGET(addrman_serdeser, .init = initialize_addrman) SetMockTime(ConsumeTime(fuzzed_data_provider)); NetGroupManager netgroupman{ConsumeNetGroupManager(fuzzed_data_provider)}; - AddrManDeterministic addr_man1{netgroupman, fuzzed_data_provider}; - AddrManDeterministic addr_man2{netgroupman, fuzzed_data_provider}; + AddrManDeterministic addr_man1{netgroupman, fuzzed_data_provider, GetCheckRatio()}; + AddrManDeterministic addr_man2{netgroupman, fuzzed_data_provider, GetCheckRatio()}; DataStream data_stream{}; diff --git a/src/test/fuzz/util/net.h b/src/test/fuzz/util/net.h index 1a5902329e..e2cb34c68f 100644 --- a/src/test/fuzz/util/net.h +++ b/src/test/fuzz/util/net.h @@ -5,6 +5,8 @@ #ifndef BITCOIN_TEST_FUZZ_UTIL_NET_H #define BITCOIN_TEST_FUZZ_UTIL_NET_H +#include +#include #include #include #include @@ -34,6 +36,108 @@ */ CNetAddr ConsumeNetAddr(FuzzedDataProvider& fuzzed_data_provider, FastRandomContext* rand = nullptr) noexcept; +class AddrManDeterministic : public AddrMan +{ +public: + explicit AddrManDeterministic(const NetGroupManager& netgroupman, FuzzedDataProvider& fuzzed_data_provider, int32_t check_ratio) + : AddrMan(netgroupman, /*deterministic=*/true, check_ratio) + { + WITH_LOCK(m_impl->cs, m_impl->insecure_rand.Reseed(ConsumeUInt256(fuzzed_data_provider))); + } + + /** + * Compare with another AddrMan. + * This compares: + * - the values in `mapInfo` (the keys aka ids are ignored) + * - vvNew entries refer to the same addresses + * - vvTried entries refer to the same addresses + */ + bool operator==(const AddrManDeterministic& other) const + { + LOCK2(m_impl->cs, other.m_impl->cs); + + if (m_impl->mapInfo.size() != other.m_impl->mapInfo.size() || m_impl->nNew != other.m_impl->nNew || + m_impl->nTried != other.m_impl->nTried) { + return false; + } + + // Check that all values in `mapInfo` are equal to all values in `other.mapInfo`. + // Keys may be different. + + auto addrinfo_hasher = [](const AddrInfo& a) { + CSipHasher hasher(0, 0); + auto addr_key = a.GetKey(); + auto source_key = a.source.GetAddrBytes(); + hasher.Write(TicksSinceEpoch(a.m_last_success)); + hasher.Write(a.nAttempts); + hasher.Write(a.nRefCount); + hasher.Write(a.fInTried); + hasher.Write(a.GetNetwork()); + hasher.Write(a.source.GetNetwork()); + hasher.Write(addr_key.size()); + hasher.Write(source_key.size()); + hasher.Write(addr_key); + hasher.Write(source_key); + return (size_t)hasher.Finalize(); + }; + + auto addrinfo_eq = [](const AddrInfo& lhs, const AddrInfo& rhs) { + return std::tie(static_cast(lhs), lhs.source, lhs.m_last_success, lhs.nAttempts, lhs.nRefCount, lhs.fInTried) == + std::tie(static_cast(rhs), rhs.source, rhs.m_last_success, rhs.nAttempts, rhs.nRefCount, rhs.fInTried); + }; + + using Addresses = std::unordered_set; + + const size_t num_addresses{m_impl->mapInfo.size()}; + + Addresses addresses{num_addresses, addrinfo_hasher, addrinfo_eq}; + for (const auto& [id, addr] : m_impl->mapInfo) { + addresses.insert(addr); + } + + Addresses other_addresses{num_addresses, addrinfo_hasher, addrinfo_eq}; + for (const auto& [id, addr] : other.m_impl->mapInfo) { + other_addresses.insert(addr); + } + + if (addresses != other_addresses) { + return false; + } + + auto IdsReferToSameAddress = [&](nid_type id, nid_type other_id) EXCLUSIVE_LOCKS_REQUIRED(m_impl->cs, other.m_impl->cs) { + if (id == -1 && other_id == -1) { + return true; + } + if ((id == -1 && other_id != -1) || (id != -1 && other_id == -1)) { + return false; + } + return m_impl->mapInfo.at(id) == other.m_impl->mapInfo.at(other_id); + }; + + // Check that `vvNew` contains the same addresses as `other.vvNew`. Notice - `vvNew[i][j]` + // contains just an id and the address is to be found in `mapInfo.at(id)`. The ids + // themselves may differ between `vvNew` and `other.vvNew`. + for (size_t i = 0; i < ADDRMAN_NEW_BUCKET_COUNT; ++i) { + for (size_t j = 0; j < ADDRMAN_BUCKET_SIZE; ++j) { + if (!IdsReferToSameAddress(m_impl->vvNew[i][j], other.m_impl->vvNew[i][j])) { + return false; + } + } + } + + // Same for `vvTried`. + for (size_t i = 0; i < ADDRMAN_TRIED_BUCKET_COUNT; ++i) { + for (size_t j = 0; j < ADDRMAN_BUCKET_SIZE; ++j) { + if (!IdsReferToSameAddress(m_impl->vvTried[i][j], other.m_impl->vvTried[i][j])) { + return false; + } + } + } + + return true; + } +}; + class FuzzedSock : public Sock { FuzzedDataProvider& m_fuzzed_data_provider; From fe624631aeb4b5fbad732ad6476c5cd986674b4f Mon Sep 17 00:00:00 2001 From: brunoerg Date: Wed, 28 Feb 2024 12:49:44 -0300 Subject: [PATCH 12/58] fuzz: fuzz `connman` with a non-empty addrman --- src/test/fuzz/connman.cpp | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/test/fuzz/connman.cpp b/src/test/fuzz/connman.cpp index beefc9d82e..a3914bfcf3 100644 --- a/src/test/fuzz/connman.cpp +++ b/src/test/fuzz/connman.cpp @@ -20,6 +20,12 @@ namespace { const TestingSetup* g_setup; + +int32_t GetCheckRatio() +{ + return std::clamp(g_setup->m_node.args->GetIntArg("-checkaddrman", 0), 0, 1000000); +} + } // namespace void initialize_connman() @@ -32,9 +38,21 @@ FUZZ_TARGET(connman, .init = initialize_connman) { FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; SetMockTime(ConsumeTime(fuzzed_data_provider)); + auto netgroupman{*g_setup->m_node.netgroupman}; + auto addr_man_ptr{std::make_unique(netgroupman, fuzzed_data_provider, GetCheckRatio())}; + if (fuzzed_data_provider.ConsumeBool()) { + const std::vector serialized_data{ConsumeRandomLengthByteVector(fuzzed_data_provider)}; + DataStream ds{serialized_data}; + try { + ds >> *addr_man_ptr; + } catch (const std::ios_base::failure&) { + addr_man_ptr = std::make_unique(netgroupman, fuzzed_data_provider, GetCheckRatio()); + } + } + AddrManDeterministic& addr_man{*addr_man_ptr}; ConnmanTestMsg connman{fuzzed_data_provider.ConsumeIntegral(), fuzzed_data_provider.ConsumeIntegral(), - *g_setup->m_node.addrman, + addr_man, *g_setup->m_node.netgroupman, Params(), fuzzed_data_provider.ConsumeBool()}; From 18c8a0945bda554e121c2a684105dffd55505cd7 Mon Sep 17 00:00:00 2001 From: brunoerg Date: Mon, 5 Feb 2024 15:41:26 -0300 Subject: [PATCH 13/58] fuzz: move `ConsumeNetGroupManager` to util --- src/test/fuzz/addrman.cpp | 7 ------- src/test/fuzz/util/net.h | 8 ++++++++ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/test/fuzz/addrman.cpp b/src/test/fuzz/addrman.cpp index 570bcb2c13..a7e7f49d9f 100644 --- a/src/test/fuzz/addrman.cpp +++ b/src/test/fuzz/addrman.cpp @@ -39,13 +39,6 @@ void initialize_addrman() g_setup = testing_setup.get(); } -[[nodiscard]] inline NetGroupManager ConsumeNetGroupManager(FuzzedDataProvider& fuzzed_data_provider) noexcept -{ - std::vector asmap = ConsumeRandomLengthBitVector(fuzzed_data_provider); - if (!SanityCheckASMap(asmap, 128)) asmap.clear(); - return NetGroupManager(asmap); -} - FUZZ_TARGET(data_stream_addr_man, .init = initialize_addrman) { FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; diff --git a/src/test/fuzz/util/net.h b/src/test/fuzz/util/net.h index e2cb34c68f..cc73cdff4b 100644 --- a/src/test/fuzz/util/net.h +++ b/src/test/fuzz/util/net.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -197,6 +198,13 @@ class FuzzedSock : public Sock return FuzzedSock{fuzzed_data_provider}; } +[[nodiscard]] inline NetGroupManager ConsumeNetGroupManager(FuzzedDataProvider& fuzzed_data_provider) noexcept +{ + std::vector asmap = ConsumeRandomLengthBitVector(fuzzed_data_provider); + if (!SanityCheckASMap(asmap, 128)) asmap.clear(); + return NetGroupManager(asmap); +} + inline CSubNet ConsumeSubNet(FuzzedDataProvider& fuzzed_data_provider) noexcept { return {ConsumeNetAddr(fuzzed_data_provider), fuzzed_data_provider.ConsumeIntegral()}; From 33b0f3ae966ffa50b55489eb867c4d93c0ed3489 Mon Sep 17 00:00:00 2001 From: brunoerg Date: Fri, 1 Mar 2024 18:38:48 -0300 Subject: [PATCH 14/58] fuzz: use `ConsumeNetGroupManager` in connman target --- src/test/fuzz/connman.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/fuzz/connman.cpp b/src/test/fuzz/connman.cpp index a3914bfcf3..2537869b3d 100644 --- a/src/test/fuzz/connman.cpp +++ b/src/test/fuzz/connman.cpp @@ -38,7 +38,7 @@ FUZZ_TARGET(connman, .init = initialize_connman) { FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; SetMockTime(ConsumeTime(fuzzed_data_provider)); - auto netgroupman{*g_setup->m_node.netgroupman}; + auto netgroupman{ConsumeNetGroupManager(fuzzed_data_provider)}; auto addr_man_ptr{std::make_unique(netgroupman, fuzzed_data_provider, GetCheckRatio())}; if (fuzzed_data_provider.ConsumeBool()) { const std::vector serialized_data{ConsumeRandomLengthByteVector(fuzzed_data_provider)}; @@ -53,7 +53,7 @@ FUZZ_TARGET(connman, .init = initialize_connman) ConnmanTestMsg connman{fuzzed_data_provider.ConsumeIntegral(), fuzzed_data_provider.ConsumeIntegral(), addr_man, - *g_setup->m_node.netgroupman, + netgroupman, Params(), fuzzed_data_provider.ConsumeBool()}; From 552cae243a1bf26bfec03eccd1458f3bf33e01dc Mon Sep 17 00:00:00 2001 From: brunoerg Date: Mon, 5 Feb 2024 15:43:20 -0300 Subject: [PATCH 15/58] fuzz: cover `ASMapHealthCheck` in connman target --- src/test/fuzz/connman.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/fuzz/connman.cpp b/src/test/fuzz/connman.cpp index 2537869b3d..f2bf44c761 100644 --- a/src/test/fuzz/connman.cpp +++ b/src/test/fuzz/connman.cpp @@ -158,6 +158,7 @@ FUZZ_TARGET(connman, .init = initialize_connman) (void)connman.GetTotalBytesSent(); (void)connman.GetTryNewOutboundPeer(); (void)connman.GetUseAddrmanOutgoing(); + (void)connman.ASMapHealthCheck(); connman.ClearTestNodes(); } From 3db68e29ec632b29f5417dbef095520e75adc26d Mon Sep 17 00:00:00 2001 From: brunoerg Date: Fri, 19 Apr 2024 14:38:06 -0300 Subject: [PATCH 16/58] wallet: move `ImportDescriptors`/`FuzzedWallet` to util --- src/test/fuzz/util/wallet.h | 134 +++++++++++++++++++++++++ src/wallet/test/fuzz/notifications.cpp | 118 +--------------------- 2 files changed, 137 insertions(+), 115 deletions(-) create mode 100644 src/test/fuzz/util/wallet.h diff --git a/src/test/fuzz/util/wallet.h b/src/test/fuzz/util/wallet.h new file mode 100644 index 0000000000..8b55b7a985 --- /dev/null +++ b/src/test/fuzz/util/wallet.h @@ -0,0 +1,134 @@ +// Copyright (c) 2024-present The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_TEST_FUZZ_UTIL_WALLET_H +#define BITCOIN_TEST_FUZZ_UTIL_WALLET_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace wallet { + +/** + * Wraps a descriptor wallet for fuzzing. + */ +struct FuzzedWallet { + std::shared_ptr wallet; + FuzzedWallet(interfaces::Chain& chain, const std::string& name, const std::string& seed_insecure) + { + wallet = std::make_shared(&chain, name, CreateMockableWalletDatabase()); + { + LOCK(wallet->cs_wallet); + wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); + auto height{*Assert(chain.getHeight())}; + wallet->SetLastBlockProcessed(height, chain.getBlockHash(height)); + } + wallet->m_keypool_size = 1; // Avoid timeout in TopUp() + assert(wallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)); + ImportDescriptors(seed_insecure); + } + void ImportDescriptors(const std::string& seed_insecure) + { + const std::vector DESCS{ + "pkh(%s/%s/*)", + "sh(wpkh(%s/%s/*))", + "tr(%s/%s/*)", + "wpkh(%s/%s/*)", + }; + + for (const std::string& desc_fmt : DESCS) { + for (bool internal : {true, false}) { + const auto descriptor{(strprintf)(desc_fmt, "[5aa9973a/66h/4h/2h]" + seed_insecure, int{internal})}; + + FlatSigningProvider keys; + std::string error; + auto parsed_desc = std::move(Parse(descriptor, keys, error, /*require_checksum=*/false).at(0)); + assert(parsed_desc); + assert(error.empty()); + assert(parsed_desc->IsRange()); + assert(parsed_desc->IsSingleType()); + assert(!keys.keys.empty()); + WalletDescriptor w_desc{std::move(parsed_desc), /*creation_time=*/0, /*range_start=*/0, /*range_end=*/1, /*next_index=*/0}; + assert(!wallet->GetDescriptorScriptPubKeyMan(w_desc)); + LOCK(wallet->cs_wallet); + auto spk_manager{wallet->AddWalletDescriptor(w_desc, keys, /*label=*/"", internal)}; + assert(spk_manager); + wallet->AddActiveScriptPubKeyMan(spk_manager->GetID(), *Assert(w_desc.descriptor->GetOutputType()), internal); + } + } + } + CTxDestination GetDestination(FuzzedDataProvider& fuzzed_data_provider) + { + auto type{fuzzed_data_provider.PickValueInArray(OUTPUT_TYPES)}; + if (fuzzed_data_provider.ConsumeBool()) { + return *Assert(wallet->GetNewDestination(type, "")); + } else { + return *Assert(wallet->GetNewChangeDestination(type)); + } + } + CScript GetScriptPubKey(FuzzedDataProvider& fuzzed_data_provider) { return GetScriptForDestination(GetDestination(fuzzed_data_provider)); } + void FundTx(FuzzedDataProvider& fuzzed_data_provider, CMutableTransaction tx) + { + // The fee of "tx" is 0, so this is the total input and output amount + const CAmount total_amt{ + std::accumulate(tx.vout.begin(), tx.vout.end(), CAmount{}, [](CAmount t, const CTxOut& out) { return t + out.nValue; })}; + const uint32_t tx_size(GetVirtualTransactionSize(CTransaction{tx})); + std::set subtract_fee_from_outputs; + if (fuzzed_data_provider.ConsumeBool()) { + for (size_t i{}; i < tx.vout.size(); ++i) { + if (fuzzed_data_provider.ConsumeBool()) { + subtract_fee_from_outputs.insert(i); + } + } + } + std::vector recipients; + for (size_t idx = 0; idx < tx.vout.size(); idx++) { + const CTxOut& tx_out = tx.vout[idx]; + CTxDestination dest; + ExtractDestination(tx_out.scriptPubKey, dest); + CRecipient recipient = {dest, tx_out.nValue, subtract_fee_from_outputs.count(idx) == 1}; + recipients.push_back(recipient); + } + CCoinControl coin_control; + coin_control.m_allow_other_inputs = fuzzed_data_provider.ConsumeBool(); + CallOneOf( + fuzzed_data_provider, [&] { coin_control.destChange = GetDestination(fuzzed_data_provider); }, + [&] { coin_control.m_change_type.emplace(fuzzed_data_provider.PickValueInArray(OUTPUT_TYPES)); }, + [&] { /* no op (leave uninitialized) */ }); + coin_control.fAllowWatchOnly = fuzzed_data_provider.ConsumeBool(); + coin_control.m_include_unsafe_inputs = fuzzed_data_provider.ConsumeBool(); + { + auto& r{coin_control.m_signal_bip125_rbf}; + CallOneOf( + fuzzed_data_provider, [&] { r = true; }, [&] { r = false; }, [&] { r = std::nullopt; }); + } + coin_control.m_feerate = CFeeRate{ + // A fee of this range should cover all cases + fuzzed_data_provider.ConsumeIntegralInRange(0, 2 * total_amt), + tx_size, + }; + if (fuzzed_data_provider.ConsumeBool()) { + *coin_control.m_feerate += GetMinimumFeeRate(*wallet, coin_control, nullptr); + } + coin_control.fOverrideFeeRate = fuzzed_data_provider.ConsumeBool(); + // Add solving data (m_external_provider and SelectExternal)? + + int change_position{fuzzed_data_provider.ConsumeIntegralInRange(-1, tx.vout.size() - 1)}; + bilingual_str error; + // Clear tx.vout since it is not meant to be used now that we are passing outputs directly. + // This sets us up for a future PR to completely remove tx from the function signature in favor of passing inputs directly + tx.vout.clear(); + (void)FundTransaction(*wallet, tx, recipients, change_position, /*lockUnspents=*/false, coin_control); + } +}; +} + +#endif // BITCOIN_TEST_FUZZ_UTIL_WALLET_H diff --git a/src/wallet/test/fuzz/notifications.cpp b/src/wallet/test/fuzz/notifications.cpp index a7015f6685..1255218f3a 100644 --- a/src/wallet/test/fuzz/notifications.cpp +++ b/src/wallet/test/fuzz/notifications.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -53,121 +54,6 @@ void initialize_setup() g_setup = testing_setup.get(); } -void ImportDescriptors(CWallet& wallet, const std::string& seed_insecure) -{ - const std::vector DESCS{ - "pkh(%s/%s/*)", - "sh(wpkh(%s/%s/*))", - "tr(%s/%s/*)", - "wpkh(%s/%s/*)", - }; - - for (const std::string& desc_fmt : DESCS) { - for (bool internal : {true, false}) { - const auto descriptor{(strprintf)(desc_fmt, "[5aa9973a/66h/4h/2h]" + seed_insecure, int{internal})}; - - FlatSigningProvider keys; - std::string error; - auto parsed_desc = std::move(Parse(descriptor, keys, error, /*require_checksum=*/false).at(0)); - assert(parsed_desc); - assert(error.empty()); - assert(parsed_desc->IsRange()); - assert(parsed_desc->IsSingleType()); - assert(!keys.keys.empty()); - WalletDescriptor w_desc{std::move(parsed_desc), /*creation_time=*/0, /*range_start=*/0, /*range_end=*/1, /*next_index=*/0}; - assert(!wallet.GetDescriptorScriptPubKeyMan(w_desc)); - LOCK(wallet.cs_wallet); - auto spk_manager{wallet.AddWalletDescriptor(w_desc, keys, /*label=*/"", internal)}; - assert(spk_manager); - wallet.AddActiveScriptPubKeyMan(spk_manager->GetID(), *Assert(w_desc.descriptor->GetOutputType()), internal); - } - } -} - -/** - * Wraps a descriptor wallet for fuzzing. - */ -struct FuzzedWallet { - std::shared_ptr wallet; - FuzzedWallet(const std::string& name, const std::string& seed_insecure) - { - auto& chain{*Assert(g_setup->m_node.chain)}; - wallet = std::make_shared(&chain, name, CreateMockableWalletDatabase()); - { - LOCK(wallet->cs_wallet); - wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); - auto height{*Assert(chain.getHeight())}; - wallet->SetLastBlockProcessed(height, chain.getBlockHash(height)); - } - wallet->m_keypool_size = 1; // Avoid timeout in TopUp() - assert(wallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)); - ImportDescriptors(*wallet, seed_insecure); - } - CTxDestination GetDestination(FuzzedDataProvider& fuzzed_data_provider) - { - auto type{fuzzed_data_provider.PickValueInArray(OUTPUT_TYPES)}; - if (fuzzed_data_provider.ConsumeBool()) { - return *Assert(wallet->GetNewDestination(type, "")); - } else { - return *Assert(wallet->GetNewChangeDestination(type)); - } - } - CScript GetScriptPubKey(FuzzedDataProvider& fuzzed_data_provider) { return GetScriptForDestination(GetDestination(fuzzed_data_provider)); } - void FundTx(FuzzedDataProvider& fuzzed_data_provider, CMutableTransaction tx) - { - // The fee of "tx" is 0, so this is the total input and output amount - const CAmount total_amt{ - std::accumulate(tx.vout.begin(), tx.vout.end(), CAmount{}, [](CAmount t, const CTxOut& out) { return t + out.nValue; })}; - const uint32_t tx_size(GetVirtualTransactionSize(CTransaction{tx})); - std::set subtract_fee_from_outputs; - if (fuzzed_data_provider.ConsumeBool()) { - for (size_t i{}; i < tx.vout.size(); ++i) { - if (fuzzed_data_provider.ConsumeBool()) { - subtract_fee_from_outputs.insert(i); - } - } - } - std::vector recipients; - for (size_t idx = 0; idx < tx.vout.size(); idx++) { - const CTxOut& tx_out = tx.vout[idx]; - CTxDestination dest; - ExtractDestination(tx_out.scriptPubKey, dest); - CRecipient recipient = {dest, tx_out.nValue, subtract_fee_from_outputs.count(idx) == 1}; - recipients.push_back(recipient); - } - CCoinControl coin_control; - coin_control.m_allow_other_inputs = fuzzed_data_provider.ConsumeBool(); - CallOneOf( - fuzzed_data_provider, [&] { coin_control.destChange = GetDestination(fuzzed_data_provider); }, - [&] { coin_control.m_change_type.emplace(fuzzed_data_provider.PickValueInArray(OUTPUT_TYPES)); }, - [&] { /* no op (leave uninitialized) */ }); - coin_control.fAllowWatchOnly = fuzzed_data_provider.ConsumeBool(); - coin_control.m_include_unsafe_inputs = fuzzed_data_provider.ConsumeBool(); - { - auto& r{coin_control.m_signal_bip125_rbf}; - CallOneOf( - fuzzed_data_provider, [&] { r = true; }, [&] { r = false; }, [&] { r = std::nullopt; }); - } - coin_control.m_feerate = CFeeRate{ - // A fee of this range should cover all cases - fuzzed_data_provider.ConsumeIntegralInRange(0, 2 * total_amt), - tx_size, - }; - if (fuzzed_data_provider.ConsumeBool()) { - *coin_control.m_feerate += GetMinimumFeeRate(*wallet, coin_control, nullptr); - } - coin_control.fOverrideFeeRate = fuzzed_data_provider.ConsumeBool(); - // Add solving data (m_external_provider and SelectExternal)? - - int change_position{fuzzed_data_provider.ConsumeIntegralInRange(-1, tx.vout.size() - 1)}; - bilingual_str error; - // Clear tx.vout since it is not meant to be used now that we are passing outputs directly. - // This sets us up for a future PR to completely remove tx from the function signature in favor of passing inputs directly - tx.vout.clear(); - (void)FundTransaction(*wallet, tx, recipients, change_position, /*lockUnspents=*/false, coin_control); - } -}; - FUZZ_TARGET(wallet_notifications, .init = initialize_setup) { FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; @@ -176,10 +62,12 @@ FUZZ_TARGET(wallet_notifications, .init = initialize_setup) // total amount. const auto total_amount{ConsumeMoney(fuzzed_data_provider, /*max=*/MAX_MONEY / 100000)}; FuzzedWallet a{ + *g_setup->m_node.chain, "fuzzed_wallet_a", "tprv8ZgxMBicQKsPd1QwsGgzfu2pcPYbBosZhJknqreRHgsWx32nNEhMjGQX2cgFL8n6wz9xdDYwLcs78N4nsCo32cxEX8RBtwGsEGgybLiQJfk", }; FuzzedWallet b{ + *g_setup->m_node.chain, "fuzzed_wallet_b", "tprv8ZgxMBicQKsPfCunYTF18sEmEyjz8TfhGnZ3BoVAhkqLv7PLkQgmoG2Ecsp4JuqciWnkopuEwShit7st743fdmB9cMD4tznUkcs33vK51K9", }; From c495731a316d9c97ee05a08cf5087c5535f84bd4 Mon Sep 17 00:00:00 2001 From: brunoerg Date: Fri, 19 Apr 2024 14:53:18 -0300 Subject: [PATCH 17/58] fuzz: wallet: add target for `CreateTransaction` --- src/wallet/test/fuzz/CMakeLists.txt | 1 + src/wallet/test/fuzz/spend.cpp | 102 ++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 src/wallet/test/fuzz/spend.cpp diff --git a/src/wallet/test/fuzz/CMakeLists.txt b/src/wallet/test/fuzz/CMakeLists.txt index c30671db48..4e663977c2 100644 --- a/src/wallet/test/fuzz/CMakeLists.txt +++ b/src/wallet/test/fuzz/CMakeLists.txt @@ -11,6 +11,7 @@ target_sources(fuzz $<$:${CMAKE_CURRENT_LIST_DIR}/notifications.cpp> parse_iso8601.cpp $<$:${CMAKE_CURRENT_LIST_DIR}/scriptpubkeyman.cpp> + spend.cpp wallet_bdb_parser.cpp ) target_link_libraries(fuzz bitcoin_wallet) diff --git a/src/wallet/test/fuzz/spend.cpp b/src/wallet/test/fuzz/spend.cpp new file mode 100644 index 0000000000..9abd9e402a --- /dev/null +++ b/src/wallet/test/fuzz/spend.cpp @@ -0,0 +1,102 @@ +// Copyright (c) 2024-present The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using util::ToString; + +namespace wallet { +namespace { +const TestingSetup* g_setup; + +void initialize_setup() +{ + static const auto testing_setup = MakeNoLogFileContext(); + g_setup = testing_setup.get(); +} + +FUZZ_TARGET(wallet_create_transaction, .init = initialize_setup) +{ + SeedRandomStateForTest(SeedRand::ZEROS); + FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; + const auto& node = g_setup->m_node; + Chainstate& chainstate{node.chainman->ActiveChainstate()}; + ArgsManager& args = *node.args; + args.ForceSetArg("-dustrelayfee", ToString(fuzzed_data_provider.ConsumeIntegralInRange(0, MAX_MONEY))); + FuzzedWallet fuzzed_wallet{ + *g_setup->m_node.chain, + "fuzzed_wallet_a", + "tprv8ZgxMBicQKsPd1QwsGgzfu2pcPYbBosZhJknqreRHgsWx32nNEhMjGQX2cgFL8n6wz9xdDYwLcs78N4nsCo32cxEX8RBtwGsEGgybLiQJfk", + }; + + CCoinControl coin_control; + if (fuzzed_data_provider.ConsumeBool()) coin_control.m_version = fuzzed_data_provider.ConsumeIntegral(); + coin_control.m_avoid_partial_spends = fuzzed_data_provider.ConsumeBool(); + coin_control.m_include_unsafe_inputs = fuzzed_data_provider.ConsumeBool(); + if (fuzzed_data_provider.ConsumeBool()) coin_control.m_confirm_target = fuzzed_data_provider.ConsumeIntegral(); + coin_control.destChange = fuzzed_data_provider.ConsumeBool() ? fuzzed_wallet.GetDestination(fuzzed_data_provider) : ConsumeTxDestination(fuzzed_data_provider); + if (fuzzed_data_provider.ConsumeBool()) coin_control.m_change_type = fuzzed_data_provider.PickValueInArray(OUTPUT_TYPES); + if (fuzzed_data_provider.ConsumeBool()) coin_control.m_feerate = CFeeRate(ConsumeMoney(fuzzed_data_provider, /*max=*/COIN)); + coin_control.m_allow_other_inputs = fuzzed_data_provider.ConsumeBool(); + coin_control.m_locktime = fuzzed_data_provider.ConsumeIntegral(); + coin_control.fOverrideFeeRate = fuzzed_data_provider.ConsumeBool(); + + int next_locktime{0}; + CAmount all_values{0}; + LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) + { + CMutableTransaction tx; + tx.nLockTime = next_locktime++; + tx.vout.resize(1); + CAmount n_value{ConsumeMoney(fuzzed_data_provider)}; + all_values += n_value; + if (all_values > MAX_MONEY) return; + tx.vout[0].nValue = n_value; + tx.vout[0].scriptPubKey = GetScriptForDestination(fuzzed_wallet.GetDestination(fuzzed_data_provider)); + LOCK(fuzzed_wallet.wallet->cs_wallet); + auto txid{tx.GetHash()}; + auto ret{fuzzed_wallet.wallet->mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(txid), std::forward_as_tuple(MakeTransactionRef(std::move(tx)), TxStateConfirmed{chainstate.m_chain.Tip()->GetBlockHash(), chainstate.m_chain.Height(), /*index=*/0}))}; + assert(ret.second); + } + + std::vector recipients; + LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 100) { + CTxDestination destination; + CallOneOf( + fuzzed_data_provider, + [&] { + destination = fuzzed_wallet.GetDestination(fuzzed_data_provider); + }, + [&] { + CScript script; + script << OP_RETURN; + destination = CNoDestination{script}; + }, + [&] { + destination = ConsumeTxDestination(fuzzed_data_provider); + } + ); + recipients.push_back({destination, + /*nAmount=*/ConsumeMoney(fuzzed_data_provider), + /*fSubtractFeeFromAmount=*/fuzzed_data_provider.ConsumeBool()}); + } + + std::optional change_pos; + if (fuzzed_data_provider.ConsumeBool()) change_pos = fuzzed_data_provider.ConsumeIntegral(); + (void)CreateTransaction(*fuzzed_wallet.wallet, recipients, change_pos, coin_control); +} +} // namespace +} // namespace wallet From baea842ff184f98d2f07568f0a77e48a34d3cde3 Mon Sep 17 00:00:00 2001 From: stickies-v Date: Mon, 7 Oct 2024 11:02:46 +0200 Subject: [PATCH 18/58] init: Remove unneeded argument for mempool_opts checks --- src/init.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 3b8474dd46..d947b1c26e 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1070,9 +1070,7 @@ bool AppInitParameterInteraction(const ArgsManager& args) if (!blockman_result) { return InitError(util::ErrorString(blockman_result)); } - CTxMemPool::Options mempool_opts{ - .check_ratio = chainparams.DefaultConsistencyChecks() ? 1 : 0, - }; + CTxMemPool::Options mempool_opts{}; auto mempool_result{ApplyArgsManOptions(args, chainparams, mempool_opts)}; if (!mempool_result) { return InitError(util::ErrorString(mempool_result)); From 720ce880a355cf59a4f042a504750eb4e3ee68d3 Mon Sep 17 00:00:00 2001 From: Martin Zumsande Date: Mon, 7 Oct 2024 11:05:22 +0200 Subject: [PATCH 19/58] init: Improve comment describing chainstate load retry behaviour --- src/init.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/init.cpp b/src/init.cpp index d947b1c26e..2d3c118f1f 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1179,7 +1179,7 @@ bool CheckHostPortOptions(const ArgsManager& args) { return true; } -// A GUI user may opt to retry once if there is a failure during chainstate initialization. +// A GUI user may opt to retry once with do_reindex set if there is a failure during chainstate initialization. // The function therefore has to support re-entry. static ChainstateLoadResult InitAndLoadChainstate( NodeContext& node, From 635e9f85d76c28647120172d9524982ebe36cf3c Mon Sep 17 00:00:00 2001 From: TheCharlatan Date: Mon, 7 Oct 2024 11:11:33 +0200 Subject: [PATCH 20/58] init: Remove misleading log line when user chooses not to retry It is bad, because it is both printed for non-GUI users and does not convey additional information. Co-authored-by: Martin Zumsande --- src/init.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/init.cpp b/src/init.cpp index 2d3c118f1f..c22370e040 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1643,7 +1643,6 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) error.original + ".\nPlease restart with -reindex or -reindex-chainstate to recover.", "", CClientUIInterface::MSG_ERROR | CClientUIInterface::BTN_ABORT); if (!do_retry) { - LogError("Aborted block database rebuild. Exiting.\n"); return false; } do_reindex = true; From cd093049dda878e8424fdc1ef828b5f644bd91d4 Mon Sep 17 00:00:00 2001 From: MarcoFalke <*~=`'#}+{/-|&status@721217.xyz> Date: Mon, 7 Oct 2024 11:18:43 +0200 Subject: [PATCH 21/58] init: Remove incorrect comment about shutdown condition Shutdown is indeed called, and it being overkill does not make sense either. --- src/init.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/init.cpp b/src/init.cpp index c22370e040..883f8fcb89 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1662,7 +1662,6 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) // As LoadBlockIndex can take several minutes, it's possible the user // requested to kill the GUI during the last operation. If so, exit. - // As the program has not fully started yet, Shutdown() is possibly overkill. if (ShutdownRequested(node)) { LogPrintf("Shutdown requested. Exiting.\n"); return false; From fa71bedf8609f06618aa85342ea6f5c4d2c5fea0 Mon Sep 17 00:00:00 2001 From: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> Date: Fri, 20 Sep 2024 10:57:04 +0200 Subject: [PATCH 22/58] ci: Approximate MAKEJOBS in image build phase --- ci/test/01_base_install.sh | 8 +++++--- ci/test/02_run_container.sh | 10 ++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/ci/test/01_base_install.sh b/ci/test/01_base_install.sh index 538a58cbd5..72e3985a51 100755 --- a/ci/test/01_base_install.sh +++ b/ci/test/01_base_install.sh @@ -15,6 +15,8 @@ if [ "$(git config --global ${CFG_DONE})" == "true" ]; then exit 0 fi +MAKEJOBS="-j$( nproc )" # Use nproc, because MAKEJOBS is the default in docker image builds. + if [ -n "$DPKG_ADD_ARCH" ]; then dpkg --add-architecture "$DPKG_ADD_ARCH" fi @@ -45,7 +47,7 @@ if [[ ${USE_MEMORY_SANITIZER} == "true" ]]; then -DLLVM_ENABLE_RUNTIMES="compiler-rt;libcxx;libcxxabi;libunwind" \ -S /msan/llvm-project/llvm - ninja -C /msan/clang_build/ "-j$( nproc )" # Use nproc, because MAKEJOBS is the default in docker image builds + ninja -C /msan/clang_build/ "$MAKEJOBS" ninja -C /msan/clang_build/ install-runtimes update-alternatives --install /usr/bin/clang++ clang++ /msan/clang_build/bin/clang++ 100 @@ -64,7 +66,7 @@ if [[ ${USE_MEMORY_SANITIZER} == "true" ]]; then -DLIBCXX_HARDENING_MODE=debug \ -S /msan/llvm-project/runtimes - ninja -C /msan/cxx_build/ "-j$( nproc )" # Use nproc, because MAKEJOBS is the default in docker image builds + ninja -C /msan/cxx_build/ "$MAKEJOBS" # Clear no longer needed source folder du -sh /msan/llvm-project @@ -74,7 +76,7 @@ fi if [[ "${RUN_TIDY}" == "true" ]]; then ${CI_RETRY_EXE} git clone --depth=1 https://github.com/include-what-you-use/include-what-you-use -b clang_"${TIDY_LLVM_V}" /include-what-you-use cmake -B /iwyu-build/ -G 'Unix Makefiles' -DCMAKE_PREFIX_PATH=/usr/lib/llvm-"${TIDY_LLVM_V}" -S /include-what-you-use - make -C /iwyu-build/ install "-j$( nproc )" # Use nproc, because MAKEJOBS is the default in docker image builds + make -C /iwyu-build/ install "$MAKEJOBS" fi mkdir -p "${DEPENDS_DIR}/SDKs" "${DEPENDS_DIR}/sdk-sources" diff --git a/ci/test/02_run_container.sh b/ci/test/02_run_container.sh index 1727f9296b..9069fba156 100755 --- a/ci/test/02_run_container.sh +++ b/ci/test/02_run_container.sh @@ -15,12 +15,22 @@ if [ -z "$DANGER_RUN_CI_ON_HOST" ]; then python3 -c 'import os; [print(f"{key}={value}") for key, value in os.environ.items() if "\n" not in value and "HOME" != key and "PATH" != key and "USER" != key]' | tee "/tmp/env-$USER-$CONTAINER_NAME" # System-dependent env vars must be kept as is. So read them from the container. docker run --rm "${CI_IMAGE_NAME_TAG}" bash -c "env | grep --extended-regexp '^(HOME|PATH|USER)='" | tee --append "/tmp/env-$USER-$CONTAINER_NAME" + + # Env vars during the build can not be changed. For example, a modified + # $MAKEJOBS is ignored in the build process. Use --cpuset-cpus as an + # approximation to respect $MAKEJOBS somewhat, if cpuset is available. + MAYBE_CPUSET="" + if [ "$HAVE_CGROUP_CPUSET" ]; then + MAYBE_CPUSET="--cpuset-cpus=$( python3 -c "import random;P=$( nproc );M=min(P,int('$MAKEJOBS'.lstrip('-j')));print(','.join(map(str,sorted(random.sample(range(P),M)))))" )" + fi echo "Creating $CI_IMAGE_NAME_TAG container to run in" + # shellcheck disable=SC2086 DOCKER_BUILDKIT=1 docker build \ --file "${BASE_READ_ONLY_DIR}/ci/test_imagefile" \ --build-arg "CI_IMAGE_NAME_TAG=${CI_IMAGE_NAME_TAG}" \ --build-arg "FILE_ENV=${FILE_ENV}" \ + $MAYBE_CPUSET \ --label="${CI_IMAGE_LABEL}" \ --tag="${CONTAINER_NAME}" \ "${BASE_READ_ONLY_DIR}" From 8f1246e833804789ee5d8b59026b49142df5c455 Mon Sep 17 00:00:00 2001 From: TheCharlatan Date: Mon, 7 Oct 2024 12:15:22 +0200 Subject: [PATCH 23/58] init: Improve chainstate init db error messages They should name the correct source of an error, or be generic if no clear source can be ascertained. --- src/init.cpp | 4 ++-- src/node/chainstate.cpp | 30 ++++++++++++++++++++---------- test/functional/feature_init.py | 4 ++-- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 883f8fcb89..7ca9c7b570 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1259,7 +1259,7 @@ static ChainstateLoadResult InitAndLoadChainstate( return f(); } catch (const std::exception& e) { LogError("%s\n", e.what()); - return std::make_tuple(node::ChainstateLoadStatus::FAILURE, _("Error opening block database")); + return std::make_tuple(node::ChainstateLoadStatus::FAILURE, _("Error loading databases")); } }; auto [status, error] = catch_exceptions([&] { return LoadChainstate(chainman, cache_sizes, options); }); @@ -1639,7 +1639,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) if (status == ChainstateLoadStatus::FAILURE && !do_reindex && !ShutdownRequested(node)) { // suggest a reindex bool do_retry = uiInterface.ThreadSafeQuestion( - error + Untranslated(".\n\n") + _("Do you want to rebuild the block database now?"), + error + Untranslated(".\n\n") + _("Do you want to rebuild the databases now?"), error.original + ".\nPlease restart with -reindex or -reindex-chainstate to recover.", "", CClientUIInterface::MSG_ERROR | CClientUIInterface::BTN_ABORT); if (!do_retry) { diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index d7e6176be1..b3b215929a 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -41,12 +41,17 @@ static ChainstateLoadResult CompleteChainstateInitialization( // new BlockTreeDB tries to delete the existing file, which // fails if it's still open from the previous loop. Close it first: pblocktree.reset(); - pblocktree = std::make_unique(DBParams{ - .path = chainman.m_options.datadir / "blocks" / "index", - .cache_bytes = static_cast(cache_sizes.block_tree_db), - .memory_only = options.block_tree_db_in_memory, - .wipe_data = options.wipe_block_tree_db, - .options = chainman.m_options.block_tree_db}); + try { + pblocktree = std::make_unique(DBParams{ + .path = chainman.m_options.datadir / "blocks" / "index", + .cache_bytes = static_cast(cache_sizes.block_tree_db), + .memory_only = options.block_tree_db_in_memory, + .wipe_data = options.wipe_block_tree_db, + .options = chainman.m_options.block_tree_db}); + } catch (dbwrapper_error& err) { + LogError("%s\n", err.what()); + return {ChainstateLoadStatus::FAILURE, _("Error opening block database")}; + } if (options.wipe_block_tree_db) { pblocktree->WriteReindexing(true); @@ -107,10 +112,15 @@ static ChainstateLoadResult CompleteChainstateInitialization( for (Chainstate* chainstate : chainman.GetAll()) { LogPrintf("Initializing chainstate %s\n", chainstate->ToString()); - chainstate->InitCoinsDB( - /*cache_size_bytes=*/chainman.m_total_coinsdb_cache * init_cache_fraction, - /*in_memory=*/options.coins_db_in_memory, - /*should_wipe=*/options.wipe_chainstate_db); + try { + chainstate->InitCoinsDB( + /*cache_size_bytes=*/chainman.m_total_coinsdb_cache * init_cache_fraction, + /*in_memory=*/options.coins_db_in_memory, + /*should_wipe=*/options.wipe_chainstate_db); + } catch (dbwrapper_error& err) { + LogError("%s\n", err.what()); + return {ChainstateLoadStatus::FAILURE, _("Error opening coins database")}; + } if (options.coins_error_cb) { chainstate->CoinsErrorCatcher().AddReadErrCallback(options.coins_error_cb); diff --git a/test/functional/feature_init.py b/test/functional/feature_init.py index 659d33684e..a841052efb 100755 --- a/test/functional/feature_init.py +++ b/test/functional/feature_init.py @@ -97,13 +97,13 @@ def check_clean_start(): files_to_delete = { 'blocks/index/*.ldb': 'Error opening block database.', - 'chainstate/*.ldb': 'Error opening block database.', + 'chainstate/*.ldb': 'Error opening coins database.', 'blocks/blk*.dat': 'Error loading block database.', } files_to_perturb = { 'blocks/index/*.ldb': 'Error loading block database.', - 'chainstate/*.ldb': 'Error opening block database.', + 'chainstate/*.ldb': 'Error opening coins database.', 'blocks/blk*.dat': 'Corrupted block database detected.', } From 31cc5006c3de4dd6a1f7a238684163956604df45 Mon Sep 17 00:00:00 2001 From: Martin Zumsande Date: Tue, 8 Oct 2024 22:33:33 +0200 Subject: [PATCH 24/58] init: Return fatal failure on snapshot validation failure A general reindex won't typically help in this case, and there is already some action being taken with the call to `InvalidateCoinsDBOnDisk`. --- src/node/chainstate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index b3b215929a..fa8d7a386f 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -246,7 +246,7 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize return {init_status, init_error}; } } else { - return {ChainstateLoadStatus::FAILURE, _( + return {ChainstateLoadStatus::FAILURE_FATAL, _( "UTXO snapshot failed to validate. " "Restart to resume normal initial block download, or try loading a different snapshot.")}; } From fac6cfe5ac06547c90da6f976d7c8bed20da8bac Mon Sep 17 00:00:00 2001 From: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> Date: Wed, 9 Oct 2024 18:08:29 +0200 Subject: [PATCH 25/58] lint: commit-script-check.sh: echo to stderr --- test/lint/commit-script-check.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/lint/commit-script-check.sh b/test/lint/commit-script-check.sh index fe845ed19e..52ae95fbb6 100755 --- a/test/lint/commit-script-check.sh +++ b/test/lint/commit-script-check.sh @@ -35,20 +35,20 @@ for commit in $(git rev-list --reverse "$1"); do git checkout --quiet "$commit"^ || exit SCRIPT="$(git rev-list --format=%b -n1 "$commit" | sed '/^-BEGIN VERIFY SCRIPT-$/,/^-END VERIFY SCRIPT-$/{//!b};d')" if test -z "$SCRIPT"; then - echo "Error: missing script for: $commit" - echo "Failed" + echo "Error: missing script for: $commit" >&2 + echo "Failed" >&2 RET=1 else - echo "Running script for: $commit" - echo "$SCRIPT" + echo "Running script for: $commit" >&2 + echo "$SCRIPT" >&2 (eval "$SCRIPT") - git --no-pager diff --exit-code "$commit" && echo "OK" || (echo "Failed"; false) || RET=1 + git --no-pager diff --exit-code "$commit" && echo "OK" >&2 || (echo "Failed" >&2; false) || RET=1 fi git reset --quiet --hard HEAD else if git rev-list "--format=%b" -n1 "$commit" | grep -q '^-\(BEGIN\|END\)[ a-zA-Z]*-$'; then - echo "Error: script block marker but no scripted-diff in title of commit $commit" - echo "Failed" + echo "Error: script block marker but no scripted-diff in title of commit $commit" >&2 + echo "Failed" >&2 RET=1 fi fi From 42e62779873cf9d307c678c8867288ad4441855b Mon Sep 17 00:00:00 2001 From: TheCharlatan Date: Sat, 12 Oct 2024 16:48:42 +0200 Subject: [PATCH 26/58] build: Add static libraries to Kernel install component --- src/kernel/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/kernel/CMakeLists.txt b/src/kernel/CMakeLists.txt index 7bf8efc516..b9a18bbcef 100644 --- a/src/kernel/CMakeLists.txt +++ b/src/kernel/CMakeLists.txt @@ -123,7 +123,7 @@ if(NOT BUILD_SHARED_LIBS) # LIBS_PRIVATE is substituted in the pkg-config file. set(LIBS_PRIVATE "") foreach(lib ${all_kernel_static_link_libs}) - install(TARGETS ${lib} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + install(TARGETS ${lib} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Kernel) string(APPEND LIBS_PRIVATE " -l${lib}") endforeach() @@ -131,7 +131,7 @@ if(NOT BUILD_SHARED_LIBS) endif() configure_file(${PROJECT_SOURCE_DIR}/libbitcoinkernel.pc.in ${PROJECT_BINARY_DIR}/libbitcoinkernel.pc @ONLY) -install(FILES ${PROJECT_BINARY_DIR}/libbitcoinkernel.pc DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") +install(FILES ${PROJECT_BINARY_DIR}/libbitcoinkernel.pc DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig" COMPONENT Kernel) include(GNUInstallDirs) install(TARGETS bitcoinkernel From 82e16e698321ea6fb69ce5a08b048347aab8c74e Mon Sep 17 00:00:00 2001 From: Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> Date: Sun, 15 Sep 2024 10:45:48 +0100 Subject: [PATCH 27/58] cmake: Refactor install kernel dependencies --- src/kernel/CMakeLists.txt | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/kernel/CMakeLists.txt b/src/kernel/CMakeLists.txt index b9a18bbcef..6ea16e8a42 100644 --- a/src/kernel/CMakeLists.txt +++ b/src/kernel/CMakeLists.txt @@ -120,14 +120,10 @@ if(NOT BUILD_SHARED_LIBS) set(all_kernel_static_link_libs "") get_target_static_link_libs(bitcoinkernel all_kernel_static_link_libs) + install(TARGETS ${all_kernel_static_link_libs} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Kernel) + list(TRANSFORM all_kernel_static_link_libs PREPEND "-l") # LIBS_PRIVATE is substituted in the pkg-config file. - set(LIBS_PRIVATE "") - foreach(lib ${all_kernel_static_link_libs}) - install(TARGETS ${lib} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Kernel) - string(APPEND LIBS_PRIVATE " -l${lib}") - endforeach() - - string(STRIP "${LIBS_PRIVATE}" LIBS_PRIVATE) + list(JOIN all_kernel_static_link_libs " " LIBS_PRIVATE) endif() configure_file(${PROJECT_SOURCE_DIR}/libbitcoinkernel.pc.in ${PROJECT_BINARY_DIR}/libbitcoinkernel.pc @ONLY) From fa5126adcb1247efb7e480f37f9c23446f36ec53 Mon Sep 17 00:00:00 2001 From: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> Date: Fri, 22 Mar 2024 09:29:01 +0100 Subject: [PATCH 28/58] fees: Pin "version that wrote" to 0 The field is unused and there is no need to tie it to CLIENT_VERSION and increase it, if the format does not change. --- src/policy/fees.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/policy/fees.cpp b/src/policy/fees.cpp index a17faa3b99..dff378c321 100644 --- a/src/policy/fees.cpp +++ b/src/policy/fees.cpp @@ -168,7 +168,7 @@ class TxConfirmStats * Read saved state of estimation data from a file and replace all internal data structures and * variables with this state. */ - void Read(AutoFile& filein, int nFileVersion, size_t numBuckets); + void Read(AutoFile& filein, size_t numBuckets); }; @@ -414,7 +414,7 @@ void TxConfirmStats::Write(AutoFile& fileout) const fileout << Using>>(failAvg); } -void TxConfirmStats::Read(AutoFile& filein, int nFileVersion, size_t numBuckets) +void TxConfirmStats::Read(AutoFile& filein, size_t numBuckets) { // Read data file and do some very basic sanity checking // buckets and bucketMap are not updated yet, so don't access them @@ -962,7 +962,7 @@ bool CBlockPolicyEstimator::Write(AutoFile& fileout) const try { LOCK(m_cs_fee_estimator); fileout << 149900; // version required to read: 0.14.99 or later - fileout << CLIENT_VERSION; // version that wrote the file + fileout << int{0}; // Unused dummy field. Written files may contain any value in [0, 289900] fileout << nBestSeenHeight; if (BlockSpan() > HistoricalBlockSpan()/2) { fileout << firstRecordedHeight << nBestSeenHeight; @@ -986,8 +986,8 @@ bool CBlockPolicyEstimator::Read(AutoFile& filein) { try { LOCK(m_cs_fee_estimator); - int nVersionRequired, nVersionThatWrote; - filein >> nVersionRequired >> nVersionThatWrote; + int nVersionRequired, dummy; + filein >> nVersionRequired >> dummy; if (nVersionRequired > CLIENT_VERSION) { throw std::runtime_error(strprintf("up-version (%d) fee estimate file", nVersionRequired)); } @@ -1015,9 +1015,9 @@ bool CBlockPolicyEstimator::Read(AutoFile& filein) std::unique_ptr fileFeeStats(new TxConfirmStats(buckets, bucketMap, MED_BLOCK_PERIODS, MED_DECAY, MED_SCALE)); std::unique_ptr fileShortStats(new TxConfirmStats(buckets, bucketMap, SHORT_BLOCK_PERIODS, SHORT_DECAY, SHORT_SCALE)); std::unique_ptr fileLongStats(new TxConfirmStats(buckets, bucketMap, LONG_BLOCK_PERIODS, LONG_DECAY, LONG_SCALE)); - fileFeeStats->Read(filein, nVersionThatWrote, numBuckets); - fileShortStats->Read(filein, nVersionThatWrote, numBuckets); - fileLongStats->Read(filein, nVersionThatWrote, numBuckets); + fileFeeStats->Read(filein, numBuckets); + fileShortStats->Read(filein, numBuckets); + fileLongStats->Read(filein, numBuckets); // Fee estimates file parsed correctly // Copy buckets from file and refresh our bucketmap From ddddbac9c10ba80c94e1f3ceeffa7091d18ea48d Mon Sep 17 00:00:00 2001 From: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> Date: Fri, 22 Mar 2024 10:03:42 +0100 Subject: [PATCH 29/58] fees: Pin required version to 149900 There is no need to compare the field to CLIENT_VERSION. Either the format remains compatible and the value can be left unchanged, or it is incompatible and the value needs to be increased to at least 289900+1. --- src/policy/fees.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/policy/fees.cpp b/src/policy/fees.cpp index dff378c321..143a3d6171 100644 --- a/src/policy/fees.cpp +++ b/src/policy/fees.cpp @@ -5,7 +5,6 @@ #include -#include #include #include #include @@ -32,6 +31,10 @@ #include #include +// The current format written, and the version required to read. Must be +// increased to at least 289900+1 on the next breaking change. +constexpr int CURRENT_FEES_FILE_VERSION{149900}; + static constexpr double INF_FEERATE = 1e99; std::string StringForFeeEstimateHorizon(FeeEstimateHorizon horizon) @@ -961,7 +964,7 @@ bool CBlockPolicyEstimator::Write(AutoFile& fileout) const { try { LOCK(m_cs_fee_estimator); - fileout << 149900; // version required to read: 0.14.99 or later + fileout << CURRENT_FEES_FILE_VERSION; fileout << int{0}; // Unused dummy field. Written files may contain any value in [0, 289900] fileout << nBestSeenHeight; if (BlockSpan() > HistoricalBlockSpan()/2) { @@ -988,7 +991,7 @@ bool CBlockPolicyEstimator::Read(AutoFile& filein) LOCK(m_cs_fee_estimator); int nVersionRequired, dummy; filein >> nVersionRequired >> dummy; - if (nVersionRequired > CLIENT_VERSION) { + if (nVersionRequired > CURRENT_FEES_FILE_VERSION) { throw std::runtime_error(strprintf("up-version (%d) fee estimate file", nVersionRequired)); } @@ -997,9 +1000,9 @@ bool CBlockPolicyEstimator::Read(AutoFile& filein) unsigned int nFileBestSeenHeight; filein >> nFileBestSeenHeight; - if (nVersionRequired < 149900) { + if (nVersionRequired < CURRENT_FEES_FILE_VERSION) { LogPrintf("%s: incompatible old fee estimation data (non-fatal). Version: %d\n", __func__, nVersionRequired); - } else { // New format introduced in 149900 + } else { // nVersionRequired == CURRENT_FEES_FILE_VERSION unsigned int nFileHistoricalFirst, nFileHistoricalBest; filein >> nFileHistoricalFirst >> nFileHistoricalBest; if (nFileHistoricalFirst > nFileHistoricalBest || nFileHistoricalBest > nFileBestSeenHeight) { From f859ff8a4e9c3aa23bf5be6eceb7099ca72b2290 Mon Sep 17 00:00:00 2001 From: dergoegge Date: Wed, 16 Oct 2024 10:18:48 +0100 Subject: [PATCH 30/58] [validation] Improve script check error reporting --- src/validation.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/validation.cpp b/src/validation.cpp index 1953a42df1..c3bda60a75 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2179,6 +2179,8 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state, if (pvChecks) { pvChecks->emplace_back(std::move(check)); } else if (!check()) { + ScriptError error{check.GetScriptError()}; + if (flags & STANDARD_NOT_MANDATORY_VERIFY_FLAGS) { // Check whether the failure was caused by a // non-mandatory script verification check, such as @@ -2192,6 +2194,14 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state, flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheSigStore, &txdata); if (check2()) return state.Invalid(TxValidationResult::TX_NOT_STANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(check.GetScriptError()))); + + // If the second check failed, it failed due to a mandatory script verification + // flag, but the first check might have failed on a non-mandatory script + // verification flag. + // + // Avoid reporting a mandatory script check failure with a non-mandatory error + // string by reporting the error from the second check. + error = check2.GetScriptError(); } // MANDATORY flag failures correspond to // TxValidationResult::TX_CONSENSUS. Because CONSENSUS @@ -2202,7 +2212,7 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state, // support, to avoid splitting the network (but this // depends on the details of how net_processing handles // such errors). - return state.Invalid(TxValidationResult::TX_CONSENSUS, strprintf("mandatory-script-verify-flag-failed (%s)", ScriptErrorString(check.GetScriptError()))); + return state.Invalid(TxValidationResult::TX_CONSENSUS, strprintf("mandatory-script-verify-flag-failed (%s)", ScriptErrorString(error))); } } From 90b405516f7f3be522ced3e0c4d23b3892df0661 Mon Sep 17 00:00:00 2001 From: Ryan Ofsky Date: Wed, 16 Oct 2024 12:10:54 -0400 Subject: [PATCH 31/58] Update libmultiprocess library Add recent changes and fixes for shutdown bugs. https://github.com/chaincodelabs/libmultiprocess/pull/111: doc: Add internal design section https://github.com/chaincodelabs/libmultiprocess/pull/113: Add missing include to util.h https://github.com/chaincodelabs/libmultiprocess/pull/116: shutdown bugfix: destroy RPC system before running cleanup callbacks https://github.com/chaincodelabs/libmultiprocess/pull/118: shutdown bugfix: Prevent segfault in server if connection is broken during long function call https://github.com/chaincodelabs/libmultiprocess/pull/119: cmake: avoid libatomic not found error on debian --- depends/packages/native_libmultiprocess.mk | 4 ++-- src/test/ipc_test.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/depends/packages/native_libmultiprocess.mk b/depends/packages/native_libmultiprocess.mk index 3fa5faa4ba..7c69e0f0c6 100644 --- a/depends/packages/native_libmultiprocess.mk +++ b/depends/packages/native_libmultiprocess.mk @@ -1,8 +1,8 @@ package=native_libmultiprocess -$(package)_version=015e95f7ebaa47619a213a19801e7fffafc56864 +$(package)_version=abe254b9734f2e2b220d1456de195532d6e6ac1e $(package)_download_path=https://github.com/chaincodelabs/libmultiprocess/archive $(package)_file_name=$($(package)_version).tar.gz -$(package)_sha256_hash=4b1266b121337f3f6f37e1863fba91c1a5ee9ad126bcffc6fe6b9ca47ad050a1 +$(package)_sha256_hash=85777073259fdc75d24ac5777a19991ec1156c5f12db50b252b861c95dcb4f46 $(package)_dependencies=native_capnp define $(package)_config_cmds diff --git a/src/test/ipc_test.cpp b/src/test/ipc_test.cpp index 91eba9214f..af37434980 100644 --- a/src/test/ipc_test.cpp +++ b/src/test/ipc_test.cpp @@ -62,7 +62,7 @@ void IpcPipeTest() auto connection_client = std::make_unique(loop, kj::mv(pipe.ends[0])); auto foo_client = std::make_unique>( - connection_client->m_rpc_system.bootstrap(mp::ServerVatId().vat_id).castAs(), + connection_client->m_rpc_system->bootstrap(mp::ServerVatId().vat_id).castAs(), connection_client.get(), /* destroy_connection= */ false); foo_promise.set_value(std::move(foo_client)); disconnect_client = [&] { loop.sync([&] { connection_client.reset(); }); }; From 4d3da08d1b9d07acb43420899e0d16fad2437fb0 Mon Sep 17 00:00:00 2001 From: Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> Date: Tue, 20 Aug 2024 13:59:10 +0100 Subject: [PATCH 32/58] guix: Enable CET for `glibc` package --- contrib/guix/manifest.scm | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/guix/manifest.scm b/contrib/guix/manifest.scm index 3da98cf651..e5ebecbf93 100644 --- a/contrib/guix/manifest.scm +++ b/contrib/guix/manifest.scm @@ -469,6 +469,7 @@ inspecting signatures in Mach-O binaries.") `(append ,flags ;; https://www.gnu.org/software/libc/manual/html_node/Configuring-and-compiling.html (list "--enable-stack-protector=all", + "--enable-cet", "--enable-bind-now", "--disable-werror", building-on))) From 86e2a6b749c7fecbd086b361806ac9f6e9426d79 Mon Sep 17 00:00:00 2001 From: Antoine Poinsot Date: Wed, 16 Oct 2024 13:34:15 +0100 Subject: [PATCH 33/58] [test] A non-standard transaction which is also consensus-invalid should return the consensus error --- test/functional/data/invalid_txs.py | 11 +++++++++++ test/functional/feature_block.py | 1 + test/functional/p2p_invalid_tx.py | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/test/functional/data/invalid_txs.py b/test/functional/data/invalid_txs.py index 2e4ca83bf0..d2d7202d86 100644 --- a/test/functional/data/invalid_txs.py +++ b/test/functional/data/invalid_txs.py @@ -263,6 +263,17 @@ def get_tx(self): 'valid_in_block' : True }) +class NonStandardAndInvalid(BadTxTemplate): + """A non-standard transaction which is also consensus-invalid should return the consensus error.""" + reject_reason = "mandatory-script-verify-flag-failed (OP_RETURN was encountered)" + expect_disconnect = True + valid_in_block = False + + def get_tx(self): + return create_tx_with_script( + self.spend_tx, 0, script_sig=b'\x00' * 3 + b'\xab\x6a', + amount=(self.spend_avail // 2)) + # Disabled opcode tx templates (CVE-2010-5137) DisabledOpcodeTemplates = [getDisabledOpcodeTemplate(opcode) for opcode in [ OP_CAT, diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py index 384ca311c7..43bf61c174 100755 --- a/test/functional/feature_block.py +++ b/test/functional/feature_block.py @@ -88,6 +88,7 @@ def set_test_params(self): self.extra_args = [[ '-acceptnonstdtxn=1', # This is a consensus block test, we don't care about tx policy '-testactivationheight=bip34@2', + '-par=1', # Until https://github.com/bitcoin/bitcoin/issues/30960 is fixed ]] def run_test(self): diff --git a/test/functional/p2p_invalid_tx.py b/test/functional/p2p_invalid_tx.py index 241aefab24..ee8c6c16ca 100755 --- a/test/functional/p2p_invalid_tx.py +++ b/test/functional/p2p_invalid_tx.py @@ -165,7 +165,7 @@ def run_test(self): node.p2ps[0].send_txs_and_test([rejected_parent], node, success=False) self.log.info('Test that a peer disconnection causes erase its transactions from the orphan pool') - with node.assert_debug_log(['Erased 100 orphan transaction(s) from peer=25']): + with node.assert_debug_log(['Erased 100 orphan transaction(s) from peer=26']): self.reconnect_p2p(num_connections=1) self.log.info('Test that a transaction in the orphan pool is included in a new tip block causes erase this transaction from the orphan pool') From f0130ab1a1e65583637b6a362b879ea3253e7bb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc?= Date: Sat, 19 Oct 2024 18:44:22 +0200 Subject: [PATCH 34/58] doc: replace `-?` with `-h` for bench_bitcoin help The question mark (`?`) is interpreted as a wildcard for any single character in Zsh (see https://www.techrepublic.com/article/globbing-wildcard-characters-with-zsh), so `bench_bitcoin -?` will not work on systems using Zsh, such as macOS. Since `-h` provides equivalent help functionality (as defined in https://github.com/bitcoin/bitcoin/blob/master/src/common/args.cpp#L684-L693), the `benchmarking.md` documentation has been updated to ensure compatibility with macOS. \ --- doc/benchmarking.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/benchmarking.md b/doc/benchmarking.md index c6dc75dc5c..8f836219c9 100644 --- a/doc/benchmarking.md +++ b/doc/benchmarking.md @@ -40,7 +40,7 @@ The output will look similar to: Help --------------------- - build/src/bench/bench_bitcoin -? + build/src/bench/bench_bitcoin -h To print the various options, like listing the benchmarks without running them or using a regex filter to only run certain benchmarks. From 33a28e252a7349c0aa284005aee97873b965fcfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc?= Date: Mon, 21 Oct 2024 11:03:28 +0200 Subject: [PATCH 35/58] Change default help arg to `-help` and mention `-h` and `-?` as alternatives % build/src/bench/bench_bitcoin -h [...] -help Print this help message and exit (also -h or -?) --- src/common/args.cpp | 4 ++-- test/lint/check-doc.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/common/args.cpp b/src/common/args.cpp index f59d2b8f0f..ce80fbbc0a 100644 --- a/src/common/args.cpp +++ b/src/common/args.cpp @@ -688,8 +688,8 @@ bool HelpRequested(const ArgsManager& args) void SetupHelpOptions(ArgsManager& args) { - args.AddArg("-?", "Print this help message and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - args.AddHiddenArgs({"-h", "-help"}); + args.AddArg("-help", "Print this help message and exit (also -h or -?)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + args.AddHiddenArgs({"-h", "-?"}); } static const int screenWidth = 79; diff --git a/test/lint/check-doc.py b/test/lint/check-doc.py index f55d0f8cb7..3e9e5ba230 100755 --- a/test/lint/check-doc.py +++ b/test/lint/check-doc.py @@ -23,7 +23,7 @@ CMD_GREP_WALLET_HIDDEN_ARGS = r"git grep --function-context 'void DummyWalletInit::AddWalletOptions' -- {}".format(CMD_ROOT_DIR) CMD_GREP_DOCS = r"git grep --perl-regexp '{}' {}".format(REGEX_DOC, CMD_ROOT_DIR) # list unsupported, deprecated and duplicate args as they need no documentation -SET_DOC_OPTIONAL = set(['-h', '-help', '-dbcrashratio', '-forcecompactdb']) +SET_DOC_OPTIONAL = set(['-h', '-?', '-dbcrashratio', '-forcecompactdb']) def lint_missing_argument_documentation(): From 66c9936455fc0b79629686fe6b5737a020662e5d Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 29 Sep 2023 17:23:48 -0300 Subject: [PATCH 36/58] bench: add coverage for wallet migration process --- src/bench/CMakeLists.txt | 1 + src/bench/wallet_migration.cpp | 80 ++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 src/bench/wallet_migration.cpp diff --git a/src/bench/CMakeLists.txt b/src/bench/CMakeLists.txt index 8a52980e07..45b6510044 100644 --- a/src/bench/CMakeLists.txt +++ b/src/bench/CMakeLists.txt @@ -71,6 +71,7 @@ if(ENABLE_WALLET) wallet_create_tx.cpp wallet_loading.cpp wallet_ismine.cpp + wallet_migration.cpp ) target_link_libraries(bench_bitcoin bitcoin_wallet) endif() diff --git a/src/bench/wallet_migration.cpp b/src/bench/wallet_migration.cpp new file mode 100644 index 0000000000..eff6c6b526 --- /dev/null +++ b/src/bench/wallet_migration.cpp @@ -0,0 +1,80 @@ +// Copyright (c) 2024 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php. + +#include // IWYU pragma: keep + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if defined(USE_BDB) && defined(USE_SQLITE) // only enable benchmark when bdb and sqlite are enabled + +namespace wallet{ + +static void WalletMigration(benchmark::Bench& bench) +{ + const auto test_setup = MakeNoLogFileContext(); + + WalletContext context; + context.args = &test_setup->m_args; + context.chain = test_setup->m_node.chain.get(); + + // Number of imported watch only addresses + int NUM_WATCH_ONLY_ADDR = 20; + + // Setup legacy wallet + DatabaseOptions options; + options.use_unsafe_sync = true; + options.verify = false; + DatabaseStatus status; + bilingual_str error; + auto database = MakeWalletDatabase(fs::PathToString(test_setup->m_path_root / "legacy"), options, status, error); + uint64_t create_flags = 0; + auto wallet = TestLoadWallet(std::move(database), context, create_flags); + + // Add watch-only addresses + std::vector scripts_watch_only; + for (int w = 0; w < NUM_WATCH_ONLY_ADDR; ++w) { + CKey key = GenerateRandomKey(); + LOCK(wallet->cs_wallet); + const CScript& script = scripts_watch_only.emplace_back(GetScriptForDestination(GetDestinationForKey(key.GetPubKey(), OutputType::LEGACY))); + bool res = wallet->ImportScriptPubKeys(strprintf("watch_%d", w), {script}, + /*have_solving_data=*/false, /*apply_label=*/true, /*timestamp=*/1); + assert(res); + } + + // Generate transactions and local addresses + for (int j = 0; j < 400; ++j) { + CMutableTransaction mtx; + mtx.vout.emplace_back(COIN, GetScriptForDestination(*Assert(wallet->GetNewDestination(OutputType::BECH32, strprintf("bench_%d", j))))); + mtx.vout.emplace_back(COIN, GetScriptForDestination(*Assert(wallet->GetNewDestination(OutputType::LEGACY, strprintf("legacy_%d", j))))); + mtx.vout.emplace_back(COIN, scripts_watch_only.at(j % NUM_WATCH_ONLY_ADDR)); + mtx.vin.resize(2); + wallet->AddToWallet(MakeTransactionRef(mtx), TxStateInactive{}, /*update_wtx=*/nullptr, /*fFlushOnClose=*/false, /*rescanning_old_block=*/true); + } + + // Unload so the migration process loads it + TestUnloadWallet(std::move(wallet)); + + bench.epochs(/*numEpochs=*/1).run([&] { + util::Result res = MigrateLegacyToDescriptor(fs::PathToString(test_setup->m_path_root / "legacy"), "", context); + assert(res); + assert(res->wallet); + assert(res->watchonly_wallet); + }); +} + +BENCHMARK(WalletMigration, benchmark::PriorityLevel::LOW); + +} // namespace wallet + +#endif // end USE_SQLITE && USE_BDB From f2541d09e132ce17a5d166583be98f5cdbf06588 Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 30 Sep 2023 08:57:18 -0300 Subject: [PATCH 37/58] wallet: batch MigrateToDescriptor() db transactions Grouping all db writes into a single atomic write operation. Speeding up the flow and preventing inconsistent states. --- src/wallet/scriptpubkeyman.cpp | 25 +++++++++++++++++++------ src/wallet/scriptpubkeyman.h | 1 + 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index 46ec5dc1ac..2926536577 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -1807,6 +1807,12 @@ std::optional LegacyDataSPKM::MigrateToDescriptor() keyid_it++; } + WalletBatch batch(m_storage.GetDatabase()); + if (!batch.TxnBegin()) { + LogPrintf("Error generating descriptors for migration, cannot initialize db transaction\n"); + return std::nullopt; + } + // keyids is now all non-HD keys. Each key will have its own combo descriptor for (const CKeyID& keyid : keyids) { CKey key; @@ -1837,8 +1843,8 @@ std::optional LegacyDataSPKM::MigrateToDescriptor() // Make the DescriptorScriptPubKeyMan and get the scriptPubKeys auto desc_spk_man = std::make_unique(m_storage, w_desc, /*keypool_size=*/0); - desc_spk_man->AddDescriptorKey(key, key.GetPubKey()); - desc_spk_man->TopUp(); + WITH_LOCK(desc_spk_man->cs_desc_man, desc_spk_man->AddDescriptorKeyWithDB(batch, key, key.GetPubKey())); + desc_spk_man->TopUpWithDB(batch); auto desc_spks = desc_spk_man->GetScriptPubKeys(); // Remove the scriptPubKeys from our current set @@ -1883,8 +1889,8 @@ std::optional LegacyDataSPKM::MigrateToDescriptor() // Make the DescriptorScriptPubKeyMan and get the scriptPubKeys auto desc_spk_man = std::make_unique(m_storage, w_desc, /*keypool_size=*/0); - desc_spk_man->AddDescriptorKey(master_key.key, master_key.key.GetPubKey()); - desc_spk_man->TopUp(); + WITH_LOCK(desc_spk_man->cs_desc_man, desc_spk_man->AddDescriptorKeyWithDB(batch, master_key.key, master_key.key.GetPubKey())); + desc_spk_man->TopUpWithDB(batch); auto desc_spks = desc_spk_man->GetScriptPubKeys(); // Remove the scriptPubKeys from our current set @@ -1950,9 +1956,9 @@ std::optional LegacyDataSPKM::MigrateToDescriptor() if (!GetKey(keyid, key)) { continue; } - desc_spk_man->AddDescriptorKey(key, key.GetPubKey()); + WITH_LOCK(desc_spk_man->cs_desc_man, desc_spk_man->AddDescriptorKeyWithDB(batch, key, key.GetPubKey())); } - desc_spk_man->TopUp(); + desc_spk_man->TopUpWithDB(batch); auto desc_spks_set = desc_spk_man->GetScriptPubKeys(); desc_spks.insert(desc_spks.end(), desc_spks_set.begin(), desc_spks_set.end()); @@ -2019,6 +2025,13 @@ std::optional LegacyDataSPKM::MigrateToDescriptor() // Make sure that we have accounted for all scriptPubKeys assert(spks.size() == 0); + + // Finalize transaction + if (!batch.TxnCommit()) { + LogPrintf("Error generating descriptors for migration, cannot commit db transaction\n"); + return std::nullopt; + } + return out; } diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h index cf7b7eaf31..39bb41d996 100644 --- a/src/wallet/scriptpubkeyman.h +++ b/src/wallet/scriptpubkeyman.h @@ -582,6 +582,7 @@ class LegacySigningProvider : public SigningProvider class DescriptorScriptPubKeyMan : public ScriptPubKeyMan { + friend class LegacyDataSPKM; private: using ScriptPubKeyMap = std::map; // Map of scripts to descriptor range index using PubKeyMap = std::map; // Map of pubkeys involved in scripts to descriptor range index From 6052c7891dc033e30b342ae5ea1690f8e7cbeb9e Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 29 Sep 2023 16:12:04 -0300 Subject: [PATCH 38/58] wallet: decouple default descriptors creation from external signer setup This will be useful in the following-up commit to batch the entire wallet migration process. --- src/wallet/wallet.cpp | 29 ++++++++++++++++++----------- src/wallet/wallet.h | 3 +++ 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index de565102cc..75b258895d 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -3754,21 +3754,28 @@ void CWallet::SetupDescriptorScriptPubKeyMans(const CExtKey& master_key) if (!batch.TxnCommit()) throw std::runtime_error("Error: cannot commit db transaction for descriptors setup"); } -void CWallet::SetupDescriptorScriptPubKeyMans() +void CWallet::SetupOwnDescriptorScriptPubKeyMans() { AssertLockHeld(cs_wallet); + assert(!IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER)); + // Make a seed + CKey seed_key = GenerateRandomKey(); + CPubKey seed = seed_key.GetPubKey(); + assert(seed_key.VerifyPubKey(seed)); - if (!IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER)) { - // Make a seed - CKey seed_key = GenerateRandomKey(); - CPubKey seed = seed_key.GetPubKey(); - assert(seed_key.VerifyPubKey(seed)); + // Get the extended key + CExtKey master_key; + master_key.SetSeed(seed_key); - // Get the extended key - CExtKey master_key; - master_key.SetSeed(seed_key); + SetupDescriptorScriptPubKeyMans(master_key); +} - SetupDescriptorScriptPubKeyMans(master_key); +void CWallet::SetupDescriptorScriptPubKeyMans() +{ + AssertLockHeld(cs_wallet); + + if (!IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER)) { + SetupOwnDescriptorScriptPubKeyMans(); } else { ExternalSigner signer = ExternalSignerScriptPubKeyMan::GetExternalSigner(); @@ -4102,7 +4109,7 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error) SetupDescriptorScriptPubKeyMans(data.master_key); } else { // Setup with a new seed if we don't. - SetupDescriptorScriptPubKeyMans(); + SetupOwnDescriptorScriptPubKeyMans(); } } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index d3a7208b15..f70f9ce2cf 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1021,6 +1021,9 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati void SetupDescriptorScriptPubKeyMans(const CExtKey& master_key) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void SetupDescriptorScriptPubKeyMans() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + //! Create new seed and default DescriptorScriptPubKeyMans for this wallet + void SetupOwnDescriptorScriptPubKeyMans() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + //! Return the DescriptorScriptPubKeyMan for a WalletDescriptor if it is already in the wallet DescriptorScriptPubKeyMan* GetDescriptorScriptPubKeyMan(const WalletDescriptor& desc) const; From 122d103ca22f347eef7b323910477b72736c64b9 Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 29 Sep 2023 16:18:56 -0300 Subject: [PATCH 39/58] wallet: introduce 'SetWalletFlagWithDB' --- src/wallet/wallet.cpp | 8 +++++++- src/wallet/wallet.h | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 75b258895d..6695edc70e 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1701,10 +1701,16 @@ bool CWallet::CanGetAddresses(bool internal) const } void CWallet::SetWalletFlag(uint64_t flags) +{ + WalletBatch batch(GetDatabase()); + return SetWalletFlagWithDB(batch, flags); +} + +void CWallet::SetWalletFlagWithDB(WalletBatch& batch, uint64_t flags) { LOCK(cs_wallet); m_wallet_flags |= flags; - if (!WalletBatch(GetDatabase()).WriteWalletFlags(m_wallet_flags)) + if (!batch.WriteWalletFlags(m_wallet_flags)) throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed"); } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index f70f9ce2cf..0b759ee56a 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -422,6 +422,9 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati // Same as 'AddActiveScriptPubKeyMan' but designed for use within a batch transaction context void AddActiveScriptPubKeyManWithDb(WalletBatch& batch, uint256 id, OutputType type, bool internal); + /** Store wallet flags */ + void SetWalletFlagWithDB(WalletBatch& batch, uint64_t flags); + //! Cache of descriptor ScriptPubKeys used for IsMine. Maps ScriptPubKey to set of spkms std::unordered_map, SaltedSipHasher> m_cached_spks; From 055c0532fc8bb3135102590b46e266b68e9d9edc Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 13 Feb 2024 12:08:15 -0300 Subject: [PATCH 40/58] wallet: provide WalletBatch to 'DeleteRecords' So it can be used within an external db txn context. --- src/wallet/scriptpubkeyman.cpp | 8 +++++++- src/wallet/scriptpubkeyman.h | 3 ++- src/wallet/walletdb.cpp | 6 ++---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index 2926536577..62384056dc 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -2036,9 +2036,15 @@ std::optional LegacyDataSPKM::MigrateToDescriptor() } bool LegacyDataSPKM::DeleteRecords() +{ + return RunWithinTxn(m_storage.GetDatabase(), /*process_desc=*/"delete legacy records", [&](WalletBatch& batch){ + return DeleteRecordsWithDB(batch); + }); +} + +bool LegacyDataSPKM::DeleteRecordsWithDB(WalletBatch& batch) { LOCK(cs_KeyStore); - WalletBatch batch(m_storage.GetDatabase()); return batch.EraseRecords(DBKeys::LEGACY_TYPES); } diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h index 39bb41d996..d8b6c90178 100644 --- a/src/wallet/scriptpubkeyman.h +++ b/src/wallet/scriptpubkeyman.h @@ -366,8 +366,9 @@ class LegacyDataSPKM : public ScriptPubKeyMan, public FillableSigningProvider /** Get the DescriptorScriptPubKeyMans (with private keys) that have the same scriptPubKeys as this LegacyScriptPubKeyMan. * Does not modify this ScriptPubKeyMan. */ std::optional MigrateToDescriptor(); - /** Delete all the records ofthis LegacyScriptPubKeyMan from disk*/ + /** Delete all the records of this LegacyScriptPubKeyMan from disk*/ bool DeleteRecords(); + bool DeleteRecordsWithDB(WalletBatch& batch); }; // Implements the full legacy wallet behavior diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 597a4ef9a4..7f092b44b1 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -1335,10 +1335,8 @@ bool WalletBatch::WriteWalletFlags(const uint64_t flags) bool WalletBatch::EraseRecords(const std::unordered_set& types) { - return RunWithinTxn(*this, "erase records", [&types](WalletBatch& self) { - return std::all_of(types.begin(), types.end(), [&self](const std::string& type) { - return self.m_batch->ErasePrefix(DataStream() << type); - }); + return std::all_of(types.begin(), types.end(), [&](const std::string& type) { + return m_batch->ErasePrefix(DataStream() << type); }); } From 91e065ec175e3d40439230d0a70b2c2c73ff024b Mon Sep 17 00:00:00 2001 From: furszy Date: Sun, 24 Mar 2024 01:03:51 -0300 Subject: [PATCH 41/58] wallet: remove post-migration signals connection The wallet is isolated during migration and reloaded at the end of the process. There is no benefit on connecting the signals few lines before unloading the wallet. --- src/wallet/wallet.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 6695edc70e..680b4b651b 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -4275,10 +4275,6 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error) } local_wallet_batch.TxnCommit(); - // Connect the SPKM signals - ConnectScriptPubKeyManNotifiers(); - NotifyCanGetAddressesChanged(); - WalletLogPrintf("Wallet migration complete.\n"); return true; From 57249ff669748d60895c275255dfb9bffaecbb67 Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 26 Mar 2024 10:48:30 -0300 Subject: [PATCH 42/58] wallet: introduce active db txn listeners Useful to ensure that the in-memory state is updated only after successfully committing the data to disk. --- src/wallet/bdb.h | 1 + src/wallet/db.h | 1 + src/wallet/migrate.h | 1 + src/wallet/salvage.cpp | 1 + src/wallet/sqlite.h | 1 + src/wallet/test/util.h | 1 + src/wallet/walletdb.cpp | 26 ++++++++++++++++++++++++-- src/wallet/walletdb.h | 14 ++++++++++++++ 8 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/wallet/bdb.h b/src/wallet/bdb.h index af0c78f0d9..f3fe8a19c1 100644 --- a/src/wallet/bdb.h +++ b/src/wallet/bdb.h @@ -208,6 +208,7 @@ class BerkeleyBatch : public DatabaseBatch bool TxnBegin() override; bool TxnCommit() override; bool TxnAbort() override; + bool HasActiveTxn() override { return activeTxn != nullptr; } DbTxn* txn() const { return activeTxn; } }; diff --git a/src/wallet/db.h b/src/wallet/db.h index 049af8dd19..e8790006a4 100644 --- a/src/wallet/db.h +++ b/src/wallet/db.h @@ -122,6 +122,7 @@ class DatabaseBatch virtual bool TxnBegin() = 0; virtual bool TxnCommit() = 0; virtual bool TxnAbort() = 0; + virtual bool HasActiveTxn() = 0; }; /** An instance of this class represents one database. diff --git a/src/wallet/migrate.h b/src/wallet/migrate.h index 58c8c0adf4..16eadeb019 100644 --- a/src/wallet/migrate.h +++ b/src/wallet/migrate.h @@ -115,6 +115,7 @@ class BerkeleyROBatch : public DatabaseBatch bool TxnBegin() override { return false; } bool TxnCommit() override { return false; } bool TxnAbort() override { return false; } + bool HasActiveTxn() override { return false; } }; //! Return object giving access to Berkeley Read Only database at specified path. diff --git a/src/wallet/salvage.cpp b/src/wallet/salvage.cpp index 04c02b0dcc..0ac1b66897 100644 --- a/src/wallet/salvage.cpp +++ b/src/wallet/salvage.cpp @@ -44,6 +44,7 @@ class DummyBatch : public DatabaseBatch bool TxnBegin() override { return true; } bool TxnCommit() override { return true; } bool TxnAbort() override { return true; } + bool HasActiveTxn() override { return false; } }; /** A dummy WalletDatabase that does nothing and never fails. Only used by salvage. diff --git a/src/wallet/sqlite.h b/src/wallet/sqlite.h index 6b84f34366..78a3accf89 100644 --- a/src/wallet/sqlite.h +++ b/src/wallet/sqlite.h @@ -95,6 +95,7 @@ class SQLiteBatch : public DatabaseBatch bool TxnBegin() override; bool TxnCommit() override; bool TxnAbort() override; + bool HasActiveTxn() override { return m_txn; } }; /** An instance of this class represents one SQLite3 database. diff --git a/src/wallet/test/util.h b/src/wallet/test/util.h index ba12f5f6bf..c8a89c0e64 100644 --- a/src/wallet/test/util.h +++ b/src/wallet/test/util.h @@ -95,6 +95,7 @@ class MockableBatch : public DatabaseBatch bool TxnBegin() override { return m_pass; } bool TxnCommit() override { return m_pass; } bool TxnAbort() override { return m_pass; } + bool HasActiveTxn() override { return false; } }; /** A WalletDatabase whose contents and return values can be modified as needed for testing diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 7f092b44b1..b09a0e40eb 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -1347,12 +1347,34 @@ bool WalletBatch::TxnBegin() bool WalletBatch::TxnCommit() { - return m_batch->TxnCommit(); + bool res = m_batch->TxnCommit(); + if (res) { + for (const auto& listener : m_txn_listeners) { + listener.on_commit(); + } + // txn finished, clear listeners + m_txn_listeners.clear(); + } + return res; } bool WalletBatch::TxnAbort() { - return m_batch->TxnAbort(); + bool res = m_batch->TxnAbort(); + if (res) { + for (const auto& listener : m_txn_listeners) { + listener.on_abort(); + } + // txn finished, clear listeners + m_txn_listeners.clear(); + } + return res; +} + +void WalletBatch::RegisterTxnListener(const DbTxnListener& l) +{ + assert(m_batch->HasActiveTxn()); + m_txn_listeners.emplace_back(l); } std::unique_ptr MakeDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error) diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index bffcc87202..32c3c29b5e 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -180,6 +180,11 @@ class CKeyMetadata } }; +struct DbTxnListener +{ + std::function on_commit, on_abort; +}; + /** Access to the wallet database. * Opens the database and provides read and write access to it. Each read and write is its own transaction. * Multiple operation transactions can be started using TxnBegin() and committed using TxnCommit() @@ -292,9 +297,18 @@ class WalletBatch bool TxnCommit(); //! Abort current transaction bool TxnAbort(); + bool HasActiveTxn() { return m_batch->HasActiveTxn(); } + + //! Registers db txn callback functions + void RegisterTxnListener(const DbTxnListener& l); + private: std::unique_ptr m_batch; WalletDatabase& m_database; + + // External functions listening to the current db txn outcome. + // Listeners are cleared at the end of the transaction. + std::vector m_txn_listeners; }; /** From aacaaaa0d3aeb576e01e2b4e6b3b094ab10f602a Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 26 Mar 2024 10:37:12 -0300 Subject: [PATCH 43/58] wallet: provide WalletBatch to 'RemoveTxs' Preparing it to be used within a broader db txn procedure. --- src/wallet/wallet.cpp | 57 +++++++++++++++++++++++-------------------- src/wallet/wallet.h | 1 + 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 680b4b651b..7f2b4d3f49 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2388,8 +2388,21 @@ DBErrors CWallet::LoadWallet() util::Result CWallet::RemoveTxs(std::vector& txs_to_remove) { AssertLockHeld(cs_wallet); - WalletBatch batch(GetDatabase()); - if (!batch.TxnBegin()) return util::Error{_("Error starting db txn for wallet transactions removal")}; + bilingual_str str_err; // future: make RunWithinTxn return a util::Result + bool was_txn_committed = RunWithinTxn(GetDatabase(), /*process_desc=*/"remove transactions", [&](WalletBatch& batch) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { + util::Result result{RemoveTxs(batch, txs_to_remove)}; + if (!result) str_err = util::ErrorString(result); + return result.has_value(); + }); + if (!str_err.empty()) return util::Error{str_err}; + if (!was_txn_committed) return util::Error{_("Error starting/committing db txn for wallet transactions removal process")}; + return {}; // all good +} + +util::Result CWallet::RemoveTxs(WalletBatch& batch, std::vector& txs_to_remove) +{ + AssertLockHeld(cs_wallet); + if (!batch.HasActiveTxn()) return util::Error{strprintf(_("The transactions removal process can only be executed within a db txn"))}; // Check for transaction existence and remove entries from disk using TxIterator = std::unordered_map::const_iterator; @@ -2398,38 +2411,30 @@ util::Result CWallet::RemoveTxs(std::vector& txs_to_remove) for (const uint256& hash : txs_to_remove) { auto it_wtx = mapWallet.find(hash); if (it_wtx == mapWallet.end()) { - str_err = strprintf(_("Transaction %s does not belong to this wallet"), hash.GetHex()); - break; + return util::Error{strprintf(_("Transaction %s does not belong to this wallet"), hash.GetHex())}; } if (!batch.EraseTx(hash)) { - str_err = strprintf(_("Failure removing transaction: %s"), hash.GetHex()); - break; + return util::Error{strprintf(_("Failure removing transaction: %s"), hash.GetHex())}; } erased_txs.emplace_back(it_wtx); } - // Roll back removals in case of an error - if (!str_err.empty()) { - batch.TxnAbort(); - return util::Error{str_err}; - } - - // Dump changes to disk - if (!batch.TxnCommit()) return util::Error{_("Error committing db txn for wallet transactions removal")}; - - // Update the in-memory state and notify upper layers about the removals - for (const auto& it : erased_txs) { - const uint256 hash{it->first}; - wtxOrdered.erase(it->second.m_it_wtxOrdered); - for (const auto& txin : it->second.tx->vin) - mapTxSpends.erase(txin.prevout); - mapWallet.erase(it); - NotifyTransactionChanged(hash, CT_DELETED); - } + // Register callback to update the memory state only when the db txn is actually dumped to disk + batch.RegisterTxnListener({.on_commit=[&, erased_txs]() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { + // Update the in-memory state and notify upper layers about the removals + for (const auto& it : erased_txs) { + const uint256 hash{it->first}; + wtxOrdered.erase(it->second.m_it_wtxOrdered); + for (const auto& txin : it->second.tx->vin) + mapTxSpends.erase(txin.prevout); + mapWallet.erase(it); + NotifyTransactionChanged(hash, CT_DELETED); + } - MarkDirty(); + MarkDirty(); + }, .on_abort={}}); - return {}; // all good + return {}; } bool CWallet::SetAddressBookWithDB(WalletBatch& batch, const CTxDestination& address, const std::string& strName, const std::optional& new_purpose) diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 0b759ee56a..7f726bfa0b 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -794,6 +794,7 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati /** Erases the provided transactions from the wallet. */ util::Result RemoveTxs(std::vector& txs_to_remove) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + util::Result RemoveTxs(WalletBatch& batch, std::vector& txs_to_remove) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool SetAddressBook(const CTxDestination& address, const std::string& strName, const std::optional& purpose); From 34bf0795fc0fe30bd04a5b3a89b779f502189262 Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 13 Feb 2024 12:21:04 -0300 Subject: [PATCH 44/58] wallet: refactor ApplyMigrationData to return util::Result --- src/wallet/wallet.cpp | 45 ++++++++++++++++--------------------------- src/wallet/wallet.h | 2 +- 2 files changed, 18 insertions(+), 29 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 7f2b4d3f49..cf54d3c194 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -4073,15 +4073,14 @@ std::optional CWallet::GetDescriptorsForLegacy(bilingual_str& err return res; } -bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error) +util::Result CWallet::ApplyMigrationData(MigrationData& data) { AssertLockHeld(cs_wallet); LegacyDataSPKM* legacy_spkm = GetLegacyDataSPKM(); if (!Assume(legacy_spkm)) { // This shouldn't happen - error = Untranslated(STR_INTERNAL_BUG("Error: Legacy wallet data missing")); - return false; + return util::Error{Untranslated(STR_INTERNAL_BUG("Error: Legacy wallet data missing"))}; } // Get all invalid or non-watched scripts that will not be migrated @@ -4095,8 +4094,7 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error) for (auto& desc_spkm : data.desc_spkms) { if (m_spk_managers.count(desc_spkm->GetID()) > 0) { - error = _("Error: Duplicate descriptors created during migration. Your wallet may be corrupted."); - return false; + return util::Error{_("Error: Duplicate descriptors created during migration. Your wallet may be corrupted.")}; } uint256 id = desc_spkm->GetID(); AddScriptPubKeyMan(id, std::move(desc_spkm)); @@ -4104,7 +4102,7 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error) // Remove the LegacyScriptPubKeyMan from disk if (!legacy_spkm->DeleteRecords()) { - return false; + return util::Error{_("Error: cannot remove legacy wallet records")}; } // Remove the LegacyScriptPubKeyMan from memory @@ -4127,8 +4125,7 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error) // Get best block locator so that we can copy it to the watchonly and solvables CBlockLocator best_block_locator; if (!WalletBatch(GetDatabase()).ReadBestBlock(best_block_locator)) { - error = _("Error: Unable to read wallet's best block locator record"); - return false; + return util::Error{_("Error: Unable to read wallet's best block locator record")}; } // Check if the transactions in the wallet are still ours. Either they belong here, or they belong in the watchonly wallet. @@ -4143,15 +4140,13 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error) watchonly_batch->WriteOrderPosNext(data.watchonly_wallet->nOrderPosNext); // Write the best block locator to avoid rescanning on reload if (!watchonly_batch->WriteBestBlock(best_block_locator)) { - error = _("Error: Unable to write watchonly wallet best block locator record"); - return false; + return util::Error{_("Error: Unable to write watchonly wallet best block locator record")}; } } if (data.solvable_wallet) { // Write the best block locator to avoid rescanning on reload if (!WalletBatch(data.solvable_wallet->GetDatabase()).WriteBestBlock(best_block_locator)) { - error = _("Error: Unable to write solvable wallet best block locator record"); - return false; + return util::Error{_("Error: Unable to write solvable wallet best block locator record")}; } } for (const auto& [_pos, wtx] : wtxOrdered) { @@ -4170,8 +4165,7 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error) ins_wtx.CopyFrom(to_copy_wtx); return true; })) { - error = strprintf(_("Error: Could not add watchonly tx %s to watchonly wallet"), wtx->GetHash().GetHex()); - return false; + return util::Error{strprintf(_("Error: Could not add watchonly tx %s to watchonly wallet"), wtx->GetHash().GetHex())}; } watchonly_batch->WriteTx(data.watchonly_wallet->mapWallet.at(hash)); // Mark as to remove from the migrated wallet only if it does not also belong to it @@ -4183,16 +4177,14 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error) } if (!is_mine) { // Both not ours and not in the watchonly wallet - error = strprintf(_("Error: Transaction %s in wallet cannot be identified to belong to migrated wallets"), wtx->GetHash().GetHex()); - return false; + return util::Error{strprintf(_("Error: Transaction %s in wallet cannot be identified to belong to migrated wallets"), wtx->GetHash().GetHex())}; } } watchonly_batch.reset(); // Flush // Do the removes if (txids_to_delete.size() > 0) { if (auto res = RemoveTxs(txids_to_delete); !res) { - error = _("Error: Could not delete watchonly transactions. ") + util::ErrorString(res); - return false; + return util::Error{_("Error: Could not delete watchonly transactions. ") + util::ErrorString(res)}; } } @@ -4203,8 +4195,7 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error) std::unique_ptr batch = std::make_unique(ext_wallet->GetDatabase()); if (!batch->TxnBegin()) { - error = strprintf(_("Error: database transaction cannot be executed for wallet %s"), ext_wallet->GetName()); - return false; + return util::Error{strprintf(_("Error: database transaction cannot be executed for wallet %s"), ext_wallet->GetName())}; } wallets_vec.emplace_back(ext_wallet, std::move(batch)); } @@ -4254,16 +4245,14 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error) continue; } - error = _("Error: Address book data in wallet cannot be identified to belong to migrated wallets"); - return false; + return util::Error{_("Error: Address book data in wallet cannot be identified to belong to migrated wallets")}; } } // Persist external wallets address book entries for (auto& [wallet, batch] : wallets_vec) { if (!batch->TxnCommit()) { - error = strprintf(_("Error: address book copy failed for wallet %s"), wallet->GetName()); - return false; + return util::Error{strprintf(_("Error: address book copy failed for wallet %s"), wallet->GetName())}; } } @@ -4273,8 +4262,7 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error) if (dests_to_delete.size() > 0) { for (const auto& dest : dests_to_delete) { if (!DelAddressBookWithDB(local_wallet_batch, dest)) { - error = _("Error: Unable to remove watchonly address book data"); - return false; + return util::Error{_("Error: Unable to remove watchonly address book data")}; } } } @@ -4282,7 +4270,7 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error) WalletLogPrintf("Wallet migration complete.\n"); - return true; + return {}; // all good } bool CWallet::CanGrindR() const @@ -4393,7 +4381,8 @@ bool DoMigration(CWallet& wallet, WalletContext& context, bilingual_str& error, } // Add the descriptors to wallet, remove LegacyScriptPubKeyMan, and cleanup txs and address book data - if (!wallet.ApplyMigrationData(*data, error)) { + if (auto res_migration = wallet.ApplyMigrationData(*data); !res_migration) { + error = util::ErrorString(res_migration); return false; } return true; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 7f726bfa0b..26d470f409 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1051,7 +1051,7 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati //! Adds the ScriptPubKeyMans given in MigrationData to this wallet, removes LegacyScriptPubKeyMan, //! and where needed, moves tx and address book entries to watchonly_wallet or solvable_wallet - bool ApplyMigrationData(MigrationData& data, bilingual_str& error) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + util::Result ApplyMigrationData(MigrationData& data) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Whether the (external) signer performs R-value signature grinding bool CanGrindR() const; From 9ef20e86d7fd5d775c463a7a752c5bba9ef1266b Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 3 Jul 2024 17:22:00 -0300 Subject: [PATCH 45/58] wallet: provide WalletBatch to 'SetupDescriptorScriptPubKeyMans' So it can be used within an external db txn context. --- src/wallet/wallet.cpp | 24 ++++++++++-------------- src/wallet/wallet.h | 4 ++-- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index cf54d3c194..e00537e920 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -3747,25 +3747,17 @@ DescriptorScriptPubKeyMan& CWallet::SetupDescriptorScriptPubKeyMan(WalletBatch& return *out; } -void CWallet::SetupDescriptorScriptPubKeyMans(const CExtKey& master_key) +void CWallet::SetupDescriptorScriptPubKeyMans(WalletBatch& batch, const CExtKey& master_key) { AssertLockHeld(cs_wallet); - - // Create single batch txn - WalletBatch batch(GetDatabase()); - if (!batch.TxnBegin()) throw std::runtime_error("Error: cannot create db transaction for descriptors setup"); - for (bool internal : {false, true}) { for (OutputType t : OUTPUT_TYPES) { SetupDescriptorScriptPubKeyMan(batch, master_key, t, internal); } } - - // Ensure information is committed to disk - if (!batch.TxnCommit()) throw std::runtime_error("Error: cannot commit db transaction for descriptors setup"); } -void CWallet::SetupOwnDescriptorScriptPubKeyMans() +void CWallet::SetupOwnDescriptorScriptPubKeyMans(WalletBatch& batch) { AssertLockHeld(cs_wallet); assert(!IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER)); @@ -3778,7 +3770,7 @@ void CWallet::SetupOwnDescriptorScriptPubKeyMans() CExtKey master_key; master_key.SetSeed(seed_key); - SetupDescriptorScriptPubKeyMans(master_key); + SetupDescriptorScriptPubKeyMans(batch, master_key); } void CWallet::SetupDescriptorScriptPubKeyMans() @@ -3786,7 +3778,10 @@ void CWallet::SetupDescriptorScriptPubKeyMans() AssertLockHeld(cs_wallet); if (!IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER)) { - SetupOwnDescriptorScriptPubKeyMans(); + if (!RunWithinTxn(GetDatabase(), /*process_desc=*/"setup descriptors", [&](WalletBatch& batch) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet){ + SetupOwnDescriptorScriptPubKeyMans(batch); + return true; + })) throw std::runtime_error("Error: cannot process db transaction for descriptors setup"); } else { ExternalSigner signer = ExternalSignerScriptPubKeyMan::GetExternalSigner(); @@ -4113,12 +4108,13 @@ util::Result CWallet::ApplyMigrationData(MigrationData& data) // Setup new descriptors SetWalletFlag(WALLET_FLAG_DESCRIPTORS); if (!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + WalletBatch local_wallet_batch(GetDatabase()); // Use the existing master key if we have it if (data.master_key.key.IsValid()) { - SetupDescriptorScriptPubKeyMans(data.master_key); + SetupDescriptorScriptPubKeyMans(local_wallet_batch, data.master_key); } else { // Setup with a new seed if we don't. - SetupOwnDescriptorScriptPubKeyMans(); + SetupOwnDescriptorScriptPubKeyMans(local_wallet_batch); } } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 26d470f409..1d350fb210 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1022,11 +1022,11 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati //! Create new DescriptorScriptPubKeyMan and add it to the wallet DescriptorScriptPubKeyMan& SetupDescriptorScriptPubKeyMan(WalletBatch& batch, const CExtKey& master_key, const OutputType& output_type, bool internal) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Create new DescriptorScriptPubKeyMans and add them to the wallet - void SetupDescriptorScriptPubKeyMans(const CExtKey& master_key) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void SetupDescriptorScriptPubKeyMans(WalletBatch& batch, const CExtKey& master_key) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void SetupDescriptorScriptPubKeyMans() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Create new seed and default DescriptorScriptPubKeyMans for this wallet - void SetupOwnDescriptorScriptPubKeyMans() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void SetupOwnDescriptorScriptPubKeyMans(WalletBatch& batch) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Return the DescriptorScriptPubKeyMan for a WalletDescriptor if it is already in the wallet DescriptorScriptPubKeyMan* GetDescriptorScriptPubKeyMan(const WalletDescriptor& desc) const; From 7c9076a2d2e5dd117c31ca871e81c9170aa0e371 Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 13 Feb 2024 12:34:16 -0300 Subject: [PATCH 46/58] wallet: migration, consolidate main wallet db writes Perform a single db write operation for the entire migration procedure. --- src/wallet/wallet.cpp | 29 +++++++++++++---------------- src/wallet/wallet.h | 2 +- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index e00537e920..9c3b05b444 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -4068,7 +4068,7 @@ std::optional CWallet::GetDescriptorsForLegacy(bilingual_str& err return res; } -util::Result CWallet::ApplyMigrationData(MigrationData& data) +util::Result CWallet::ApplyMigrationData(WalletBatch& local_wallet_batch, MigrationData& data) { AssertLockHeld(cs_wallet); @@ -4096,7 +4096,7 @@ util::Result CWallet::ApplyMigrationData(MigrationData& data) } // Remove the LegacyScriptPubKeyMan from disk - if (!legacy_spkm->DeleteRecords()) { + if (!legacy_spkm->DeleteRecordsWithDB(local_wallet_batch)) { return util::Error{_("Error: cannot remove legacy wallet records")}; } @@ -4106,9 +4106,8 @@ util::Result CWallet::ApplyMigrationData(MigrationData& data) m_internal_spk_managers.clear(); // Setup new descriptors - SetWalletFlag(WALLET_FLAG_DESCRIPTORS); + SetWalletFlagWithDB(local_wallet_batch, WALLET_FLAG_DESCRIPTORS); if (!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { - WalletBatch local_wallet_batch(GetDatabase()); // Use the existing master key if we have it if (data.master_key.key.IsValid()) { SetupDescriptorScriptPubKeyMans(local_wallet_batch, data.master_key); @@ -4120,7 +4119,7 @@ util::Result CWallet::ApplyMigrationData(MigrationData& data) // Get best block locator so that we can copy it to the watchonly and solvables CBlockLocator best_block_locator; - if (!WalletBatch(GetDatabase()).ReadBestBlock(best_block_locator)) { + if (!local_wallet_batch.ReadBestBlock(best_block_locator)) { return util::Error{_("Error: Unable to read wallet's best block locator record")}; } @@ -4179,7 +4178,7 @@ util::Result CWallet::ApplyMigrationData(MigrationData& data) watchonly_batch.reset(); // Flush // Do the removes if (txids_to_delete.size() > 0) { - if (auto res = RemoveTxs(txids_to_delete); !res) { + if (auto res = RemoveTxs(local_wallet_batch, txids_to_delete); !res) { return util::Error{_("Error: Could not delete watchonly transactions. ") + util::ErrorString(res)}; } } @@ -4253,8 +4252,6 @@ util::Result CWallet::ApplyMigrationData(MigrationData& data) } // Remove the things to delete in this wallet - WalletBatch local_wallet_batch(GetDatabase()); - local_wallet_batch.TxnBegin(); if (dests_to_delete.size() > 0) { for (const auto& dest : dests_to_delete) { if (!DelAddressBookWithDB(local_wallet_batch, dest)) { @@ -4262,9 +4259,6 @@ util::Result CWallet::ApplyMigrationData(MigrationData& data) } } } - local_wallet_batch.TxnCommit(); - - WalletLogPrintf("Wallet migration complete.\n"); return {}; // all good } @@ -4377,11 +4371,14 @@ bool DoMigration(CWallet& wallet, WalletContext& context, bilingual_str& error, } // Add the descriptors to wallet, remove LegacyScriptPubKeyMan, and cleanup txs and address book data - if (auto res_migration = wallet.ApplyMigrationData(*data); !res_migration) { - error = util::ErrorString(res_migration); - return false; - } - return true; + return RunWithinTxn(wallet.GetDatabase(), /*process_desc=*/"apply migration process", [&](WalletBatch& batch) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet){ + if (auto res_migration = wallet.ApplyMigrationData(batch, *data); !res_migration) { + error = util::ErrorString(res_migration); + return false; + } + wallet.WalletLogPrintf("Wallet migration complete.\n"); + return true; + }); } util::Result MigrateLegacyToDescriptor(const std::string& wallet_name, const SecureString& passphrase, WalletContext& context) diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 1d350fb210..d869f031bb 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1051,7 +1051,7 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati //! Adds the ScriptPubKeyMans given in MigrationData to this wallet, removes LegacyScriptPubKeyMan, //! and where needed, moves tx and address book entries to watchonly_wallet or solvable_wallet - util::Result ApplyMigrationData(MigrationData& data) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + util::Result ApplyMigrationData(WalletBatch& local_wallet_batch, MigrationData& data) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Whether the (external) signer performs R-value signature grinding bool CanGrindR() const; From c98fc36d094a08d44f3c95431db2c5f34a96cc73 Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 3 Jul 2024 18:48:34 -0300 Subject: [PATCH 47/58] wallet: migration, consolidate external wallets db writes Perform a single db write operation for each external wallet (watch-only and solvables) for the entire migration procedure. --- src/wallet/wallet.cpp | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 9c3b05b444..652d6e9f6d 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -4129,6 +4129,7 @@ util::Result CWallet::ApplyMigrationData(WalletBatch& local_wallet_batch, std::unique_ptr watchonly_batch; if (data.watchonly_wallet) { watchonly_batch = std::make_unique(data.watchonly_wallet->GetDatabase()); + if (!watchonly_batch->TxnBegin()) return util::Error{strprintf(_("Error: database transaction cannot be executed for wallet %s"), data.watchonly_wallet->GetName())}; // Copy the next tx order pos to the watchonly wallet LOCK(data.watchonly_wallet->cs_wallet); data.watchonly_wallet->nOrderPosNext = nOrderPosNext; @@ -4138,9 +4139,12 @@ util::Result CWallet::ApplyMigrationData(WalletBatch& local_wallet_batch, return util::Error{_("Error: Unable to write watchonly wallet best block locator record")}; } } + std::unique_ptr solvables_batch; if (data.solvable_wallet) { + solvables_batch = std::make_unique(data.solvable_wallet->GetDatabase()); + if (!solvables_batch->TxnBegin()) return util::Error{strprintf(_("Error: database transaction cannot be executed for wallet %s"), data.solvable_wallet->GetName())}; // Write the best block locator to avoid rescanning on reload - if (!WalletBatch(data.solvable_wallet->GetDatabase()).WriteBestBlock(best_block_locator)) { + if (!solvables_batch->WriteBestBlock(best_block_locator)) { return util::Error{_("Error: Unable to write solvable wallet best block locator record")}; } } @@ -4175,7 +4179,7 @@ util::Result CWallet::ApplyMigrationData(WalletBatch& local_wallet_batch, return util::Error{strprintf(_("Error: Transaction %s in wallet cannot be identified to belong to migrated wallets"), wtx->GetHash().GetHex())}; } } - watchonly_batch.reset(); // Flush + // Do the removes if (txids_to_delete.size() > 0) { if (auto res = RemoveTxs(local_wallet_batch, txids_to_delete); !res) { @@ -4185,15 +4189,8 @@ util::Result CWallet::ApplyMigrationData(WalletBatch& local_wallet_batch, // Pair external wallets with their corresponding db handler std::vector, std::unique_ptr>> wallets_vec; - for (const auto& ext_wallet : {data.watchonly_wallet, data.solvable_wallet}) { - if (!ext_wallet) continue; - - std::unique_ptr batch = std::make_unique(ext_wallet->GetDatabase()); - if (!batch->TxnBegin()) { - return util::Error{strprintf(_("Error: database transaction cannot be executed for wallet %s"), ext_wallet->GetName())}; - } - wallets_vec.emplace_back(ext_wallet, std::move(batch)); - } + if (data.watchonly_wallet) wallets_vec.emplace_back(data.watchonly_wallet, std::move(watchonly_batch)); + if (data.solvable_wallet) wallets_vec.emplace_back(data.solvable_wallet, std::move(solvables_batch)); // Write address book entry to disk auto func_store_addr = [](WalletBatch& batch, const CTxDestination& dest, const CAddressBookData& entry) { @@ -4247,7 +4244,7 @@ util::Result CWallet::ApplyMigrationData(WalletBatch& local_wallet_batch, // Persist external wallets address book entries for (auto& [wallet, batch] : wallets_vec) { if (!batch->TxnCommit()) { - return util::Error{strprintf(_("Error: address book copy failed for wallet %s"), wallet->GetName())}; + return util::Error{strprintf(_("Error: Unable to write data to disk for wallet %s"), wallet->GetName())}; } } From a16917fb5981d1465ffd4c036586f8729e683b73 Mon Sep 17 00:00:00 2001 From: brunoerg Date: Fri, 24 May 2024 09:34:00 -0300 Subject: [PATCH 48/58] rpc, net: improve `mapped_as` doc for getrawaddrman/getpeerinfo Before, we did not explicity say that both fields `{source_}mapped_as` (that are optional in getrawaddrman) will be only available if the asmap config flag is set. Co-authored-by: Jon Atack --- src/bitcoin-cli.cpp | 2 +- src/rpc/net.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 934b5fb6dc..ec7932d4dd 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -678,7 +678,7 @@ class NetinfoRequestHandler : public BaseRequestHandler " \".\" - we do not relay addresses to this peer (addr_relay_enabled is false)\n" " addrl Total number of addresses dropped due to rate limiting\n" " age Duration of connection to the peer, in minutes\n" - " asmap Mapped AS (Autonomous System) number in the BGP route to the peer, used for diversifying\n" + " asmap Mapped AS (Autonomous System) number at the end of the BGP route to the peer, used for diversifying\n" " peer selection (only displayed if the -asmap config option is set)\n" " id Peer index, in increasing order of peer connections since node startup\n" " address IP address and port of the peer\n" diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index be9962e458..5398685074 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -129,8 +129,8 @@ static RPCHelpMan getpeerinfo() {RPCResult::Type::STR, "addrbind", /*optional=*/true, "(ip:port) Bind address of the connection to the peer"}, {RPCResult::Type::STR, "addrlocal", /*optional=*/true, "(ip:port) Local address as reported by the peer"}, {RPCResult::Type::STR, "network", "Network (" + Join(GetNetworkNames(/*append_unroutable=*/true), ", ") + ")"}, - {RPCResult::Type::NUM, "mapped_as", /*optional=*/true, "The AS in the BGP route to the peer used for diversifying\n" - "peer selection (only available if the asmap config flag is set)"}, + {RPCResult::Type::NUM, "mapped_as", /*optional=*/true, "Mapped AS (Autonomous System) number at the end of the BGP route to the peer, used for diversifying\n" + "peer selection (only displayed if the -asmap config option is set)"}, {RPCResult::Type::STR_HEX, "services", "The services offered"}, {RPCResult::Type::ARR, "servicesnames", "the services offered, in human-readable form", { @@ -1150,14 +1150,14 @@ static RPCHelpMan getrawaddrman() {RPCResult::Type::OBJ_DYN, "table", "buckets with addresses in the address manager table ( new, tried )", { {RPCResult::Type::OBJ, "bucket/position", "the location in the address manager table (/)", { {RPCResult::Type::STR, "address", "The address of the node"}, - {RPCResult::Type::NUM, "mapped_as", /*optional=*/true, "The ASN mapped to the IP of this peer per our current ASMap"}, + {RPCResult::Type::NUM, "mapped_as", /*optional=*/true, "Mapped AS (Autonomous System) number at the end of the BGP route to the peer, used for diversifying peer selection (only displayed if the -asmap config option is set)"}, {RPCResult::Type::NUM, "port", "The port number of the node"}, {RPCResult::Type::STR, "network", "The network (" + Join(GetNetworkNames(), ", ") + ") of the address"}, {RPCResult::Type::NUM, "services", "The services offered by the node"}, {RPCResult::Type::NUM_TIME, "time", "The " + UNIX_EPOCH_TIME + " when the node was last seen"}, {RPCResult::Type::STR, "source", "The address that relayed the address to us"}, {RPCResult::Type::STR, "source_network", "The network (" + Join(GetNetworkNames(), ", ") + ") of the source address"}, - {RPCResult::Type::NUM, "source_mapped_as", /*optional=*/true, "The ASN mapped to the IP of this peer's source per our current ASMap"} + {RPCResult::Type::NUM, "source_mapped_as", /*optional=*/true, "Mapped AS (Autonomous System) number at the end of the BGP route to the source, used for diversifying peer selection (only displayed if the -asmap config option is set)"} }} }} } From 9bb92c0e7ffe2426b4afb80ddd4b4c96968316b5 Mon Sep 17 00:00:00 2001 From: Hodlinator <172445034+hodlinator@users.noreply.github.com> Date: Mon, 21 Oct 2024 13:22:46 +0200 Subject: [PATCH 49/58] util: Remove RandAddSeedPerfmon RegQueryValueExA(HKEY_PERFORMANCE_DATA, ...) sometimes hangs bitcoind.exe on Windows during startup, at least on CI. We have other sources of entropy to seed randomness with on Windows, so should be alright removing this. Might resurrect if less drastic fix is found. --- src/random.cpp | 4 ++-- src/random.h | 2 +- src/randomenv.cpp | 42 ------------------------------------------ 3 files changed, 3 insertions(+), 45 deletions(-) diff --git a/src/random.cpp b/src/random.cpp index 163112585a..4d871d30bd 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -599,7 +599,7 @@ void SeedPeriodic(CSHA512& hasher, RNGState& rng) noexcept // Add the events hasher into the mix rng.SeedEvents(hasher); - // Dynamic environment data (performance monitoring, ...) + // Dynamic environment data (clocks, resource usage, ...) auto old_size = hasher.Size(); RandAddDynamicEnv(hasher); LogDebug(BCLog::RAND, "Feeding %i bytes of dynamic environment data into RNG\n", hasher.Size() - old_size); @@ -616,7 +616,7 @@ void SeedStartup(CSHA512& hasher, RNGState& rng) noexcept // Everything that the 'slow' seeder includes. SeedSlow(hasher, rng); - // Dynamic environment data (performance monitoring, ...) + // Dynamic environment data (clocks, resource usage, ...) auto old_size = hasher.Size(); RandAddDynamicEnv(hasher); diff --git a/src/random.h b/src/random.h index 536e697cca..2a26ca986b 100644 --- a/src/random.h +++ b/src/random.h @@ -49,7 +49,7 @@ * * - RandAddPeriodic() seeds everything that fast seeding includes, but additionally: * - A high-precision timestamp - * - Dynamic environment data (performance monitoring, ...) + * - Dynamic environment data (clocks, resource usage, ...) * - Strengthen the entropy for 10 ms using repeated SHA512. * This is run once every minute. * diff --git a/src/randomenv.cpp b/src/randomenv.cpp index dee48481c5..7a46a5109b 100644 --- a/src/randomenv.cpp +++ b/src/randomenv.cpp @@ -28,7 +28,6 @@ #ifdef WIN32 #include -#include #else #include #include @@ -64,45 +63,6 @@ extern char** environ; // NOLINT(readability-redundant-declaration): Necessary o namespace { -void RandAddSeedPerfmon(CSHA512& hasher) -{ -#ifdef WIN32 - // Seed with the entire set of perfmon data - - // This can take up to 2 seconds, so only do it every 10 minutes. - // Initialize last_perfmon to 0 seconds, we don't skip the first call. - static std::atomic last_perfmon{SteadyClock::time_point{0s}}; - auto last_time = last_perfmon.load(); - auto current_time = SteadyClock::now(); - if (current_time < last_time + 10min) return; - last_perfmon = current_time; - - std::vector vData(250000, 0); - long ret = 0; - unsigned long nSize = 0; - const size_t nMaxSize = 10000000; // Bail out at more than 10MB of performance data - while (true) { - nSize = vData.size(); - ret = RegQueryValueExA(HKEY_PERFORMANCE_DATA, "Global", nullptr, nullptr, vData.data(), &nSize); - if (ret != ERROR_MORE_DATA || vData.size() >= nMaxSize) - break; - vData.resize(std::min((vData.size() * 3) / 2, nMaxSize)); // Grow size of buffer exponentially - } - RegCloseKey(HKEY_PERFORMANCE_DATA); - if (ret == ERROR_SUCCESS) { - hasher.Write(vData.data(), nSize); - memory_cleanse(vData.data(), nSize); - } else { - // Performance data is only a best-effort attempt at improving the - // situation when the OS randomness (and other sources) aren't - // adequate. As a result, failure to read it is isn't considered critical, - // so we don't call RandFailure(). - // TODO: Add logging when the logger is made functional before global - // constructors have been invoked. - } -#endif -} - /** Helper to easily feed data into a CSHA512. * * Note that this does not serialize the passed object (like stream.h's << operators do). @@ -227,8 +187,6 @@ void AddAllCPUID(CSHA512& hasher) void RandAddDynamicEnv(CSHA512& hasher) { - RandAddSeedPerfmon(hasher); - // Various clocks #ifdef WIN32 FILETIME ftime; From fa1c5cc9df116411edca172f8b80fc225c776554 Mon Sep 17 00:00:00 2001 From: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> Date: Fri, 22 Mar 2024 10:29:18 +0100 Subject: [PATCH 50/58] fees: Log non-fatal errors as [warning], instead of info-level Also, remove not needed and possibly redundant function name and class names from the log string. Also, minimally reword the log messages. Also, remove redundant trailing newlines from log messages, while touching. --- src/policy/fees.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/policy/fees.cpp b/src/policy/fees.cpp index 143a3d6171..e85b2f2caa 100644 --- a/src/policy/fees.cpp +++ b/src/policy/fees.cpp @@ -979,7 +979,7 @@ bool CBlockPolicyEstimator::Write(AutoFile& fileout) const longStats->Write(fileout); } catch (const std::exception&) { - LogPrintf("CBlockPolicyEstimator::Write(): unable to write policy estimator data (non-fatal)\n"); + LogWarning("Unable to write policy estimator data (non-fatal)"); return false; } return true; @@ -992,7 +992,7 @@ bool CBlockPolicyEstimator::Read(AutoFile& filein) int nVersionRequired, dummy; filein >> nVersionRequired >> dummy; if (nVersionRequired > CURRENT_FEES_FILE_VERSION) { - throw std::runtime_error(strprintf("up-version (%d) fee estimate file", nVersionRequired)); + throw std::runtime_error{strprintf("File version (%d) too high to be read.", nVersionRequired)}; } // Read fee estimates file into temporary variables so existing data @@ -1001,7 +1001,7 @@ bool CBlockPolicyEstimator::Read(AutoFile& filein) filein >> nFileBestSeenHeight; if (nVersionRequired < CURRENT_FEES_FILE_VERSION) { - LogPrintf("%s: incompatible old fee estimation data (non-fatal). Version: %d\n", __func__, nVersionRequired); + LogWarning("Incompatible old fee estimation data (non-fatal). Version: %d", nVersionRequired); } else { // nVersionRequired == CURRENT_FEES_FILE_VERSION unsigned int nFileHistoricalFirst, nFileHistoricalBest; filein >> nFileHistoricalFirst >> nFileHistoricalBest; @@ -1041,7 +1041,7 @@ bool CBlockPolicyEstimator::Read(AutoFile& filein) } } catch (const std::exception& e) { - LogPrintf("CBlockPolicyEstimator::Read(): unable to read policy estimator data (non-fatal): %s\n",e.what()); + LogWarning("Unable to read policy estimator data (non-fatal): %s", e.what()); return false; } return true; From a0c9595810c7d8bb17d8b5bea8d916db194b5239 Mon Sep 17 00:00:00 2001 From: laanwj <126646+laanwj@users.noreply.github.com> Date: Wed, 23 Oct 2024 20:12:35 +0200 Subject: [PATCH 51/58] doc: Make list of targets in depends README consistent The description of `i686-pc-linux-gnu` and `x86_64-pc-linux-gnu` is incomplete and inconsistent with the rest. Fix this. Also use "64 bit" consistently instead of "64-bit". --- depends/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/depends/README.md b/depends/README.md index 4ef7247ea4..0b3430dc93 100644 --- a/depends/README.md +++ b/depends/README.md @@ -22,15 +22,15 @@ created. To use it during configuring Bitcoin Core: Common `host-platform-triplet`s for cross compilation are: -- `i686-pc-linux-gnu` for Linux 32 bit -- `x86_64-pc-linux-gnu` for x86 Linux +- `i686-pc-linux-gnu` for Linux x86 32 bit +- `x86_64-pc-linux-gnu` for Linux x86 64 bit - `x86_64-w64-mingw32` for Win64 - `x86_64-apple-darwin` for macOS - `arm64-apple-darwin` for ARM macOS - `arm-linux-gnueabihf` for Linux ARM 32 bit - `aarch64-linux-gnu` for Linux ARM 64 bit -- `powerpc64-linux-gnu` for Linux POWER 64-bit (big endian) -- `powerpc64le-linux-gnu` for Linux POWER 64-bit (little endian) +- `powerpc64-linux-gnu` for Linux POWER 64 bit (big endian) +- `powerpc64le-linux-gnu` for Linux POWER 64 bit (little endian) - `riscv32-linux-gnu` for Linux RISC-V 32 bit - `riscv64-linux-gnu` for Linux RISC-V 64 bit - `s390x-linux-gnu` for Linux S390X From fa9747a896188f4dd70f275aec2469dba5cd435e Mon Sep 17 00:00:00 2001 From: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> Date: Thu, 24 Oct 2024 11:37:19 +0200 Subject: [PATCH 52/58] ci: Temporary workaround for old CCACHE_DIR cirrus env --- ci/test/02_run_container.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ci/test/02_run_container.sh b/ci/test/02_run_container.sh index 9069fba156..ce01db325c 100755 --- a/ci/test/02_run_container.sh +++ b/ci/test/02_run_container.sh @@ -59,11 +59,16 @@ if [ -z "$DANGER_RUN_CI_ON_HOST" ]; then fi if [ "$DANGER_CI_ON_HOST_CCACHE_FOLDER" ]; then + # Temporary exclusion for https://github.com/bitcoin/bitcoin/issues/31108 + # to allow CI configs and envs generated in the past to work for a bit longer. + # Can be removed in March 2025. + if [ "${CCACHE_DIR}" != "/tmp/ccache_dir" ]; then if [ ! -d "${CCACHE_DIR}" ]; then echo "Error: Directory '${CCACHE_DIR}' must be created in advance." exit 1 fi CI_CCACHE_MOUNT="type=bind,src=${CCACHE_DIR},dst=${CCACHE_DIR}" + fi # End temporary exclusion fi docker network create --ipv6 --subnet 1111:1111::/112 ci-ip6net || true From fb46d57d4e7263495c007f4a499a349bff2b21e0 Mon Sep 17 00:00:00 2001 From: Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> Date: Thu, 24 Oct 2024 11:27:16 +0100 Subject: [PATCH 53/58] cmake, qt, test: Remove problematic code The removed code aimed to make Qt's minimal integration plugin DLL available for `test_bitcoin-qt.exe` on Windows. However, there are two issues: 1. The code is broken because the destination directory must end with a trailing slash (`/`). 2. It is unnecessary because Qt's minimal integration plugin is not used on Windows. For more details, please refer to the following code. --- src/qt/test/CMakeLists.txt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/qt/test/CMakeLists.txt b/src/qt/test/CMakeLists.txt index 582ed71466..8ce571c150 100644 --- a/src/qt/test/CMakeLists.txt +++ b/src/qt/test/CMakeLists.txt @@ -32,14 +32,6 @@ if(ENABLE_WALLET) ) endif() -if(NOT QT_IS_STATIC) - add_custom_command( - TARGET test_bitcoin-qt POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different $>> $/plugins/platforms - VERBATIM - ) -endif() - add_test(NAME test_bitcoin-qt COMMAND test_bitcoin-qt ) From 6c6b2442edabe9e3f77d29aacd6681bc3fa16d9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc?= Date: Mon, 19 Feb 2024 13:43:49 +0100 Subject: [PATCH 54/58] build: Replace MAC_OSX macro with existing __APPLE__ Adopting `__APPLE__` aligns our project with broader industry practices, including those in prominent projects such as the Linux kernel (and even our own code). See: https://sourceforge.net/p/predef/wiki/OperatingSystems/#macos --- CMakeLists.txt | 5 +---- src/common/args.cpp | 4 ++-- src/common/system.cpp | 2 +- src/random.cpp | 4 ++-- src/util/fs_helpers.cpp | 4 ++-- src/util/threadnames.cpp | 2 +- 6 files changed, 9 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index edc4710637..e68af79b51 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -324,10 +324,7 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SIZEOF_VOID_P EQUAL 4) endif() if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") - target_compile_definitions(core_interface INTERFACE - MAC_OSX - OBJC_OLD_DISPATCH_PROTOTYPES=0 - ) + target_compile_definitions(core_interface INTERFACE OBJC_OLD_DISPATCH_PROTOTYPES=0) # These flags are specific to ld64, and may cause issues with other linkers. # For example: GNU ld will interpret -dead_strip as -de and then try and use # "ad_strip" as the symbol for the entry point. diff --git a/src/common/args.cpp b/src/common/args.cpp index f59d2b8f0f..f8283b1288 100644 --- a/src/common/args.cpp +++ b/src/common/args.cpp @@ -184,7 +184,7 @@ bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::strin for (int i = 1; i < argc; i++) { std::string key(argv[i]); -#ifdef MAC_OSX +#ifdef __APPLE__ // At the first time when a user gets the "App downloaded from the // internet" warning, and clicks the Open button, macOS passes // a unique process serial number (PSN) as -psn_... command-line @@ -741,7 +741,7 @@ fs::path GetDefaultDataDir() pathRet = fs::path("/"); else pathRet = fs::path(pszHome); -#ifdef MAC_OSX +#ifdef __APPLE__ // macOS return pathRet / "Library/Application Support/Bitcoin"; #else diff --git a/src/common/system.cpp b/src/common/system.cpp index 6a9463a0a5..7af792db44 100644 --- a/src/common/system.cpp +++ b/src/common/system.cpp @@ -70,7 +70,7 @@ void SetupEnvironment() #endif // On most POSIX systems (e.g. Linux, but not BSD) the environment's locale // may be invalid, in which case the "C.UTF-8" locale is used as fallback. -#if !defined(WIN32) && !defined(MAC_OSX) && !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__) +#if !defined(WIN32) && !defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__) try { std::locale(""); // Raises a runtime error if current locale is invalid } catch (const std::runtime_error&) { diff --git a/src/random.cpp b/src/random.cpp index 163112585a..55d5f38872 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -34,7 +34,7 @@ #include #endif -#if defined(HAVE_GETRANDOM) || (defined(HAVE_GETENTROPY_RAND) && defined(MAC_OSX)) +#if defined(HAVE_GETRANDOM) || (defined(HAVE_GETENTROPY_RAND) && defined(__APPLE__)) #include #endif @@ -387,7 +387,7 @@ void GetOSRand(unsigned char *ent32) The function call is always successful. */ arc4random_buf(ent32, NUM_OS_RANDOM_BYTES); -#elif defined(HAVE_GETENTROPY_RAND) && defined(MAC_OSX) +#elif defined(HAVE_GETENTROPY_RAND) && defined(__APPLE__) if (getentropy(ent32, NUM_OS_RANDOM_BYTES) != 0) { RandFailure(); } diff --git a/src/util/fs_helpers.cpp b/src/util/fs_helpers.cpp index 7ac7b829d8..4d06afe144 100644 --- a/src/util/fs_helpers.cpp +++ b/src/util/fs_helpers.cpp @@ -117,7 +117,7 @@ bool FileCommit(FILE* file) LogPrintf("FlushFileBuffers failed: %s\n", Win32ErrorString(GetLastError())); return false; } -#elif defined(MAC_OSX) && defined(F_FULLFSYNC) +#elif defined(__APPLE__) && defined(F_FULLFSYNC) if (fcntl(fileno(file), F_FULLFSYNC, 0) == -1) { // Manpage says "value other than -1" is returned on success LogPrintf("fcntl F_FULLFSYNC failed: %s\n", SysErrorString(errno)); return false; @@ -195,7 +195,7 @@ void AllocateFileRange(FILE* file, unsigned int offset, unsigned int length) nFileSize.u.HighPart = nEndPos >> 32; SetFilePointerEx(hFile, nFileSize, 0, FILE_BEGIN); SetEndOfFile(hFile); -#elif defined(MAC_OSX) +#elif defined(__APPLE__) // OSX specific version // NOTE: Contrary to other OS versions, the OSX version assumes that // NOTE: offset is the size of the file. diff --git a/src/util/threadnames.cpp b/src/util/threadnames.cpp index 37c5b8f617..032572fb53 100644 --- a/src/util/threadnames.cpp +++ b/src/util/threadnames.cpp @@ -29,7 +29,7 @@ static void SetThreadName(const char* name) ::prctl(PR_SET_NAME, name, 0, 0, 0); #elif (defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)) pthread_set_name_np(pthread_self(), name); -#elif defined(MAC_OSX) +#elif defined(__APPLE__) pthread_setname_np(name); #else // Prevent warnings for unused parameters... From 8523d8c0fc85c5511311628d47c78fa380b99f6e Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 24 Oct 2024 10:36:02 -0300 Subject: [PATCH 55/58] ci: display logs of failed tests automatically --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 439d02cc8b..3b6418773e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,7 +72,7 @@ jobs: run: | # Run tests on commits after the last merge commit and before the PR head commit # Use clang++, because it is a bit faster and uses less memory than g++ - git rebase --exec "echo Running test-one-commit on \$( git log -1 ) && CC=clang CXX=clang++ cmake -B build -DWERROR=ON -DWITH_ZMQ=ON -DBUILD_GUI=ON -DBUILD_BENCH=ON -DBUILD_FUZZ_BINARY=ON -DWITH_BDB=ON -DWITH_MINIUPNPC=ON -DWITH_USDT=ON && cmake --build build -j $(nproc) && ctest --test-dir build -j $(nproc) && ./build/test/functional/test_runner.py -j $(( $(nproc) * 2 ))" ${{ env.TEST_BASE }} + git rebase --exec "echo Running test-one-commit on \$( git log -1 ) && CC=clang CXX=clang++ cmake -B build -DWERROR=ON -DWITH_ZMQ=ON -DBUILD_GUI=ON -DBUILD_BENCH=ON -DBUILD_FUZZ_BINARY=ON -DWITH_BDB=ON -DWITH_MINIUPNPC=ON -DWITH_USDT=ON && cmake --build build -j $(nproc) && ctest --output-on-failure --test-dir build -j $(nproc) && ./build/test/functional/test_runner.py -j $(( $(nproc) * 2 ))" ${{ env.TEST_BASE }} macos-native-arm64: name: 'macOS 14 native, arm64, no depends, sqlite only, gui' @@ -199,7 +199,7 @@ jobs: - name: Run test suite working-directory: build run: | - ctest -j $env:NUMBER_OF_PROCESSORS -C Release + ctest --output-on-failure -j $env:NUMBER_OF_PROCESSORS -C Release - name: Run functional tests working-directory: build From fa69a5f4b76a4e2a02db6c32d9c3311ce5fe29bd Mon Sep 17 00:00:00 2001 From: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> Date: Thu, 24 Oct 2024 17:09:46 +0200 Subject: [PATCH 56/58] util: Treat Assume as Assert when evaluating at compile-time --- src/util/check.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util/check.h b/src/util/check.h index 8f28f5dc94..187187e593 100644 --- a/src/util/check.h +++ b/src/util/check.h @@ -42,9 +42,9 @@ void assertion_fail(std::string_view file, int line, std::string_view func, std: template constexpr T&& inline_assertion_check(LIFETIMEBOUND T&& val, [[maybe_unused]] const char* file, [[maybe_unused]] int line, [[maybe_unused]] const char* func, [[maybe_unused]] const char* assertion) { - if constexpr (IS_ASSERT + if (IS_ASSERT || std::is_constant_evaluated() #ifdef ABORT_ON_FAILED_ASSUME - || true + || true #endif ) { if (!val) { From 5c299ecafe6f336cffa145d28036b04b87e26712 Mon Sep 17 00:00:00 2001 From: kevkevinpal Date: Sun, 6 Oct 2024 10:22:10 -0400 Subject: [PATCH 57/58] test: Assert that when we add the max orphan amount that we cannot add anymore and that a random orphan gets dropped --- test/functional/p2p_orphan_handling.py | 45 ++++++++++++++++++++++++++ test/functional/rpc_getorphantxs.py | 1 - 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/test/functional/p2p_orphan_handling.py b/test/functional/p2p_orphan_handling.py index 22600bf8a4..9ec23e9d6e 100755 --- a/test/functional/p2p_orphan_handling.py +++ b/test/functional/p2p_orphan_handling.py @@ -5,6 +5,7 @@ import time +from test_framework.mempool_util import tx_in_orphanage from test_framework.messages import ( CInv, CTxInWitness, @@ -41,6 +42,8 @@ # for one peer and y seconds for another, use specific values instead. TXREQUEST_TIME_SKIP = NONPREF_PEER_TX_DELAY + TXID_RELAY_DELAY + OVERLOADED_PEER_TX_DELAY + 1 +DEFAULT_MAX_ORPHAN_TRANSACTIONS = 100 + def cleanup(func): # Time to fastfoward (using setmocktime) in between subtests to ensure they do not interfere with # one another, in seconds. Equal to 12 hours, which is enough to expire anything that may exist @@ -566,6 +569,47 @@ def test_orphan_txid_inv(self): assert tx_child["txid"] in node_mempool assert_equal(node.getmempoolentry(tx_child["txid"])["wtxid"], tx_child["wtxid"]) + @cleanup + def test_max_orphan_amount(self): + self.log.info("Check that we never exceed our storage limits for orphans") + + node = self.nodes[0] + self.generate(self.wallet, 1) + peer_1 = node.add_p2p_connection(P2PInterface()) + + self.log.info("Check that orphanage is empty on start of test") + assert len(node.getorphantxs()) == 0 + + self.log.info("Filling up orphanage with " + str(DEFAULT_MAX_ORPHAN_TRANSACTIONS) + "(DEFAULT_MAX_ORPHAN_TRANSACTIONS) orphans") + orphans = [] + parent_orphans = [] + for _ in range(DEFAULT_MAX_ORPHAN_TRANSACTIONS): + tx_parent_1 = self.wallet.create_self_transfer() + tx_child_1 = self.wallet.create_self_transfer(utxo_to_spend=tx_parent_1["new_utxo"]) + parent_orphans.append(tx_parent_1["tx"]) + orphans.append(tx_child_1["tx"]) + peer_1.send_message(msg_tx(tx_child_1["tx"])) + + peer_1.sync_with_ping() + orphanage = node.getorphantxs() + assert_equal(len(orphanage), DEFAULT_MAX_ORPHAN_TRANSACTIONS) + + for orphan in orphans: + assert tx_in_orphanage(node, orphan) + + self.log.info("Check that we do not add more than the max orphan amount") + tx_parent_1 = self.wallet.create_self_transfer() + tx_child_1 = self.wallet.create_self_transfer(utxo_to_spend=tx_parent_1["new_utxo"]) + peer_1.send_and_ping(msg_tx(tx_child_1["tx"])) + parent_orphans.append(tx_parent_1["tx"]) + orphanage = node.getorphantxs() + assert_equal(len(orphanage), DEFAULT_MAX_ORPHAN_TRANSACTIONS) + + self.log.info("Clearing the orphanage") + for index, parent_orphan in enumerate(parent_orphans): + peer_1.send_and_ping(msg_tx(parent_orphan)) + assert_equal(len(node.getorphantxs()),0) + def run_test(self): self.nodes[0].setmocktime(int(time.time())) @@ -582,6 +626,7 @@ def run_test(self): self.test_same_txid_orphan() self.test_same_txid_orphan_of_orphan() self.test_orphan_txid_inv() + self.test_max_orphan_amount() if __name__ == '__main__': diff --git a/test/functional/rpc_getorphantxs.py b/test/functional/rpc_getorphantxs.py index 8d32ce1638..dfa1168b2e 100755 --- a/test/functional/rpc_getorphantxs.py +++ b/test/functional/rpc_getorphantxs.py @@ -125,6 +125,5 @@ def orphan_details_match(self, orphan, tx, verbosity): self.log.info("Check the transaction hex of orphan") assert_equal(orphan["hex"], tx["hex"]) - if __name__ == '__main__': GetOrphanTxsTest(__file__).main() From f32c34d0c3d4041a301822b27e88d6db4cbf631e Mon Sep 17 00:00:00 2001 From: Greg Sanders Date: Thu, 24 Oct 2024 11:58:12 -0400 Subject: [PATCH 58/58] functional test: Additional package evaluation coverage Current test coverage doesn't ensure that mempool trimming doesn't appear prior to the entire package, and not just the subpackage, is finished being submitted. Add a scenario that covers this case, where package ancestors can make it in individually, but would be immadiately evicted if not for the package CPFP. --- test/functional/mempool_limit.py | 95 ++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/test/functional/mempool_limit.py b/test/functional/mempool_limit.py index a29c103c3f..85d158d611 100755 --- a/test/functional/mempool_limit.py +++ b/test/functional/mempool_limit.py @@ -85,6 +85,100 @@ def test_rbf_carveout_disallowed(self): assert_equal(res["package_msg"], "transaction failed") assert "too-long-mempool-chain" in res["tx-results"][tx_C["wtxid"]]["error"] + def test_mid_package_eviction_success(self): + node = self.nodes[0] + self.log.info("Check a package where each parent passes the current mempoolminfee but a parent could be evicted before getting child's descendant feerate") + + # Clear mempool so it can be filled with minrelay txns + self.restart_node(0, extra_args=self.extra_args[0] + ["-persistmempool=0"]) + assert_equal(node.getrawmempool(), []) + + # Restarting the node resets mempool minimum feerate + assert_equal(node.getmempoolinfo()['minrelaytxfee'], Decimal('0.00001000')) + assert_equal(node.getmempoolinfo()['mempoolminfee'], Decimal('0.00001000')) + + fill_mempool(self, node) + current_info = node.getmempoolinfo() + mempoolmin_feerate = current_info["mempoolminfee"] + + mempool_txids = node.getrawmempool() + mempool_entries = [node.getmempoolentry(entry) for entry in mempool_txids] + fees_btc_per_kvb = [entry["fees"]["base"] / (Decimal(entry["vsize"]) / 1000) for entry in mempool_entries] + mempool_entry_minrate = min(fees_btc_per_kvb) + mempool_entry_minrate = mempool_entry_minrate.quantize(Decimal("0.00000000")) + + # There is a gap, our parents will be minrate, with child bringing up descendant fee sufficiently to avoid + # eviction even though parents cause eviction on their own + assert_greater_than(mempool_entry_minrate, mempoolmin_feerate) + + package_hex = [] + # UTXOs to be spent by the ultimate child transaction + parent_utxos = [] + + # Series of parents that don't need CPFP and are submitted individually. Each one is large + # which means in aggregate they could trigger eviction, but child submission should result + # in them not being evicted + parent_vsize = 25000 + num_big_parents = 3 + # Need to be large enough to trigger eviction + # (note that the mempool usage of a tx is about three times its vsize) + assert_greater_than(parent_vsize * num_big_parents * 3, current_info["maxmempool"] - current_info["bytes"]) + + big_parent_txids = [] + big_parent_wtxids = [] + for i in range(num_big_parents): + # Last parent is higher feerate causing other parents to possibly + # be evicted if trimming was allowed, which would cause the package to end up failing + parent_feerate = mempoolmin_feerate + Decimal("0.00000001") if i == num_big_parents - 1 else mempoolmin_feerate + parent = self.wallet.create_self_transfer(fee_rate=parent_feerate, target_vsize=parent_vsize, confirmed_only=True) + parent_utxos.append(parent["new_utxo"]) + package_hex.append(parent["hex"]) + big_parent_txids.append(parent["txid"]) + big_parent_wtxids.append(parent["wtxid"]) + # There is room for each of these transactions independently + assert node.testmempoolaccept([parent["hex"]])[0]["allowed"] + + # Create a child spending everything with an insane fee, bumping the package above mempool_entry_minrate + child = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_utxos, fee_per_output=10000000) + package_hex.append(child["hex"]) + + # Package should be submitted, temporarily exceeding maxmempool, but not evicted. + package_res = None + with node.assert_debug_log(expected_msgs=["rolling minimum fee bumped"]): + package_res = node.submitpackage(package=package_hex, maxfeerate=0) + + assert_equal(package_res["package_msg"], "success") + + # Ensure that intra-package trimming is not happening. + # Each transaction separately satisfies the current + # minfee and shouldn't need package evaluation to + # be included. If trimming of a parent were to happen, + # package evaluation would happen to reintrodce the evicted + # parent. + assert_equal(len(package_res["tx-results"]), len(big_parent_wtxids) + 1) + for wtxid in big_parent_wtxids + [child["wtxid"]]: + assert_equal(len(package_res["tx-results"][wtxid]["fees"]["effective-includes"]), 1) + + # Maximum size must never be exceeded. + assert_greater_than(node.getmempoolinfo()["maxmempool"], node.getmempoolinfo()["bytes"]) + + # Package found in mempool still + resulting_mempool_txids = node.getrawmempool() + assert child["txid"] in resulting_mempool_txids + for txid in big_parent_txids: + assert txid in resulting_mempool_txids + + # Check every evicted tx was higher feerate than parents which evicted it + eviction_set = set(mempool_txids) - set(resulting_mempool_txids) - set(big_parent_txids) + parent_entries = [node.getmempoolentry(entry) for entry in big_parent_txids] + max_parent_feerate = max([entry["fees"]["modified"] / (Decimal(entry["vsize"]) / 1000) for entry in parent_entries]) + for eviction in eviction_set: + assert eviction in mempool_txids + for txid, entry in zip(mempool_txids, mempool_entries): + if txid == eviction: + evicted_feerate_btc_per_kvb = entry["fees"]["modified"] / (Decimal(entry["vsize"]) / 1000) + assert_greater_than(evicted_feerate_btc_per_kvb, max_parent_feerate) + def test_mid_package_eviction(self): node = self.nodes[0] self.log.info("Check a package where each parent passes the current mempoolminfee but would cause eviction before package submission terminates") @@ -339,6 +433,7 @@ def run_test(self): self.stop_node(0) self.nodes[0].assert_start_raises_init_error(["-maxmempool=4"], "Error: -maxmempool must be at least 5 MB") + self.test_mid_package_eviction_success() self.test_mid_package_replacement() self.test_mid_package_eviction() self.test_rbf_carveout_disallowed()