nW;Y)Mw?YLNNs*uO9vMtG*ty5%u}aO$1B
zoRMrpId%Sk+wptyH!XZ+nEqAxT2Tw@I;LSvyL(1
zf+_mGIUec_R9ndh)em4xaz-X5cBq56AWeAF1884bUIJNRK%p}k+~D7{7Ix}pzfjET
z%1R%a{cUF!ETv1L%m7t2z7LGO+Mb(zQcbhBva3Q5k^yep!gwJe`dC43AF@`04vcOg-)<17Zu-(#|Q?
z@lGr5=Tr~%e2Ed6*Wz0-UCgi^`<{Y<>bjv4gpC6ggCkChyK
zDbHyOJ2NQ->Y#EB!V<804A8ytBN=9}d$&NEreiL)$$B7965#snTLC`$-7WWttQXC&Y^wW1jnP0gNMRIzVOhd=2m(lYYa4d)zGR
zhcJqqGdrd=Q$k&UhG)%5W7k~8U}VU3(RqyJS}rQ}-gED$8_!WV1;+Ik`GXTTn3(Sr
zv2mC|@f9l-n|gR|_~6v9^ZkU#2Vi{`$UVGufy%l0nYp+sWAE^4l_#guv-8!9+
zid+3NxDIk%q^oQ{=v$a_RFx`Ln|w*d$BSn$k%>CiTf;OKZY!Sr>TMF+bpIT5=z~$JLNVZm&pa1XvH%Tl)g}5A8fK_UCyI{3(!KiI@aBTI~>R?HdR(l;M5QS4NUz@DIo$
zHP04_(Cn33FjBt|*JNN$$`ji9&?tkn+1tf>lv}*Nnw<{iP+jkDp55~5bW*GnG;*6N
zF=LOD1~jDgB@9fHJo}~-U2+N^2`C+^*SUKQ#XBl`+Svur_D%hy?HTy_)FV{8)Rw=a
z=VP2|7LVfy64U%<{7F}Qwfn|LtR%v2x*-v)-HO61jD>ZPeu=ZL|#zw!y
z%CADuScI{V8s<)uQbMn?9Fk|l5oPY}9z&}=OZPlY#GbD(PkDK>Gkn(~$5CyUcQwr4xp3$74a`_Ju-Iox
z+%8&S+6H?sM{nvBqYX{8s7Ji{2ho8TWiwV>>qwN#Bff8m|F773_$A&e$I>
zsM$BUllv8Bku-D#CmUV*rgMhvlexOdu-qUB>kdCI+xZ
zi4Wk$4c14pK-H6>bB=fXFT08FEyfrIh4x!RVcVkuhjU$hY3@Qo;(bVTvQ^jG`jMn{*6c*B`X|9?kitti~OQK^p^02N%>PbX~oCUrZ-FX$sWIn{#`c
z?#z|unVYfaxjMSle(Cyt{bY0w?i|oZudTX(Fk<8le}`v7lz!(c{bg2oga0Wowj{Ue
z`y#c}HT?Yq1N&UwBiL7S`xQQq!{MX6U0aXKxIyDtE&KYf4vWj|@|o=AS5Y&PI=Yrp
zZw`*aLz#2V#qZ%0iz%G$RZP6uQEEZ&%9tx>;pN-`VzcxLcI^9(vG6b?CQGwE)rg)0
zSEC9%%_OgESynU<+ixj#S>JN)Em?LQzs}qEEs{k=4e*-S+kn>p`dG19?BR8f11Az{
z$*sxe#O9lcJvoCUjfUQT&r#$P9NXRWh2%SPS~A?LySFKuKEu;y0G&E?!w
z_tbPQO=8C0(?7|WQF4sgsJ1v@fQ-ze-=+RTZCSaqg&o4I**1jbf%1OWrcC{>IaiX&
z=*WAI~JqF?66!6SjO6|jSBExBoX5|qYBXIpN*M;N4k+l;jcZ4aY-nZ^1ctOc2{m+
zU8N>f84n#)Qv_slYVny*{m*?7wb@n4G7vR1rDF&-B)m15&GrX4@TiUS8HWyc)8899tjN&Wrh`6pF
z$EHS|%7zmS7*?qrN1T(Jt<0p60n>Tj?;&t03)*pSE)K*P)@QZBHC;G0QX}34+bsGJ
z=n#M+$*B*nfNB3_<}WH-@h-1_o%d{rUM${_@OTnv{(9?8vy+*HLm>v8TqUTyn=J>d
zaxC>#EO99ASGt*`U}E6FLimN>sZ(@b>UFLsNTjbY5{vbDW2eHc
zwy$>=4zTM3U09y3{MUHL*6sIAg)ChL;MPrxZT#?|MMKCkrb2CUDLZHU*_V
zM}P;9V(`7I_qLkDw~r5^DWabW9LH6m8;&Ox)VR7fr$`5LZ{p};k_Jez!u9{E+IpxS
z`X=?3PW;?w>oPpG{cmDmL&{`y)arJw+pNo*@~Dj;tzYM6-(lS3YYVeWWWN3thZ!+Zuo4l$
zB|2PUincJR!)Q_OeY)g1bN!CB-09vZ{N$)SyFIM%*VY67*rxLLMgX*74H!nuGs;H3
zEj~@Kr7bL09tr={2v|}A0cK?
zK*Z-TQgOCZ$Ow6I#Luj_%R>g@&@`eyG*QAiLPmGyFIDvQ1YW*s6)qxhH|v;hXF#}l
z<~`N_`9mO3A`sO?;9#A=QR%;w$Em%){Met{zd5q#6L?v`TGn}+z}0}=1JNh~VtW?L
zY;Xr$Xykt0gvlg1X#?f&Hh@9l56@$M0!Ob#opL>Qf?}mc3i5MroM7nR?2{oaXa_{)
zrwr6Z%1FOE{}Y5gj3XvsW5`)T!yyov6TmKg`Shhfq}2b6%Wp}}P`)LMOWyyzU#I*Z
z4=DlsL(UTh_YCCKNhkeSPqF_0zApr)Iz{Hcxht37UHzlIqfV%T+kf@AQnzJz#vz-
z`@l0^LdZR(-w3_Ll9>5p$o@(lkC_R#|I%<@i3yH0QcR%p&JD7l$lHicNCx3+WT!I+
znmZvs0?MRM!1Qk)$&h3P8Hm69+^N++WFcr2Y||g+Qrex6N&Y1_GQdtlmWfZAb>=;#
zBE#ErC!7l6AKL$>QLe<2q6|erI%k>BQJ}c?tR6@u-l7DbSf+UvWKrswhNh!8YNYd!
zWw8|ydn^7ZyLC?tTo$Kq$&zb8D`fiy$Xc%8zlco*wd@Cj;`xs&Gnivd8Mm|GL$GtG
z$_nLM6|&K58}9>C53nl>K@o;n`7~khBMhe{w(+^2DCh@;KKjdEp@d`-!Mz?Z(p4}Wn_A>s%+l5VPLuoASK7;$7VZ7=LSr;bDAxdk?mBsk761
zk&@f@#CiNN3K=;iKu2S=OTNQfjD)8BgT)>lJYLmBQ6W@3+U2TxZH}SFewC(UiWbx3
ztKv-Th-wmWebW)0Y`fR}zu?FUEKs<%qDstC-vwZ?>|4Po>`o^5fBx}j}#A+ai2m`C#aj=(z|
z)z#AYs_(be5!)fIb_EGqLsZ;sYMwq|+mJcuW#;;i+gVORUExJr(Kw#jAm(@5;aM
zOp>^b+4hFd$pJplMoO$=UFOtyeb^pDcZkPVpj1I7!)Tyz&ztsB0X8i!jwAbf(B@)J
zWln{r1m>6s(FPqwxhon_5S)fq;7s#!CL
zhK)Uvk`(&ms(Ft(H!yMp#5P^b*9Bs74i=tnL`c*xYQoWoo;Y1#1S8hekR?~lEIdo6
z+wx6Zx1lEMpjdmsQgF%3Xtjw-1g!^zVM-{gzt(l|G#HOh5BAQ^zS0N$Fu0S*2kI@e
zH`Xb74gZNzHt1RE(G8V{Pf4BUUc*edp_WrC%+dkf113)!%Co
zzih$oo--@sW^NaR9OvP2=GGlL@Qp)`VsiT=)WU4q>PGj3WnSnH<<8{9VxT-l8_GWu@p}M`x1vjfHCh&M
zAtG$h2yIXY2~}E*#GWm&;|suueQ3rSDH~a#VAbW4)n#JU<)0oJ2~b6lk87|*3M&O`
z?O}~{)Z#u2>H;8y6y?RKR_w-_$1awN+zu)nH*C8Ng4YL147w`r)G+PCM`5hi&Ry~N
z+avc*0Et=}?%&;;3VT3jf?-UfMvXfK^9Wkx$;7$>$-x7WUEI
zw(9yW8-yAKy_VcJTGQvA%)sul(R`;}pw+h^n-g1}dV9}b7UvkrjTt+V&c
zaBO*MAa@46SywH
zMq{H7gz;d?i#K2cAu%#4jT3KH0yHU%^fEg?VK8w^)nGzOF?a_tvqS?tuInn8vgKb&
zkO72=;B*nKn>Dw;cC?@=l#mKGs&Z+k{GSrTB<{y}WcJ!_TvoTcx)Vc_A^lZ#
zcf{zQp4o3?d%{VdO5(_Ili}ySj|}k4(WwMa;n!b{V=|DP9=(hAQ#M`oCXy@uU2_JW(Am
z*b8G9_|mYS#8qa<-1&++id#w=LXE4cH
z@o`n&Bc?Y!$QBRE7J~%Yf{t9At>VXQ6h4%0sm3b~Q=-Ju+}fB!NY_4rkwGOz0WdKa
z9P1Z|5yD-FREb{~DRcqxiV#6$c|Z)eV#B8E-h=N`Ifz%7-97$jUOTbqk5MKDGqEtz
z$!HY}$p(JlZm>eFS5fsIn(N$3G;%9Jl_5~!A>olce~gO@hlSnCap#ij3Zg09*J`ih
zAEfZUUSJKGu=UIw{*gR9!k77DFW`4;1FRFBTDgkk-`=KI^z`Z^WRv+MBFXtVw!Pi#
zpNQd+=<9@3kftZ;=6*||%+VWCEVHoym~9#g&WCio(U3Z}F>FsZ`b;m*rYskWi78E<
zT1t8Gu7VECicklJl2PkA^h78_`?G0-v$S9GCmlAs*QmGJ2?6I%q6i;?;yBgOW9%!A
z{+J*|hRNtK(vR#pCQX=5x7aPkLqEbrcYuy+8lDiig)P-U)qSp0O*n_E&WfjaCV{vJ
z+(RJ>PSJ%|r!GG(InSP?7Qn0)3x!zPE%&_c+br_8#$42cbyWquYn4sSkKi4
zwN+o<`+!Ne=)G5W2&wHK$z6{=PiY^vCoNQ!ZfZqsxWCip%!lAuve`V^qC^|zxthX_
z>m0UPmIf^pk#ptDc+k%XdGV>xuS0m5cu#R5xsL5v#aBCW)dp_tn;>*>r@CA!BF7sx
zt`~x-tR_`|K}fEDmjv*KYoHKbhi6l6Hrv2lB$!PUNdYkT2v-3%a4V=XTtH6?dyk*Hk$r0S3-
z6{|@C>U|`?C*tC2$l0lDJh(_SEUs0_ZDERG^|;0pHpGdoPOkDQbb5>)7{@7R^9C@%eCNs*@``x>2fQE1C&zpuk|R*
zlBBUY$qPr->wl^$4C%F%>xyW`=sT7rFp@VBFh}q{gXi_X@azfUflpKjrYjCpLJKn{
za1m981Kvk?llwp~h^S6$N_4JhD~||3`)-7P82P7iS-s25LmzNHbZaD}2LiVckbh&}
z>jPiN#vUOJaTHKWp`8x(EROb1(k|ux;?w?oQq8
zy6irW8z7|+C=E>6kYM?Z01Ppl_#dj%Z-*t|nQ?ij2Hz;#;ZS
zvuzIDmV|p!4Xh`hSw*fe&QG|Y2Go0+5n+XTe@mml4La>L28zR$=mM_K?G=XguX`lm
zS5{Wm>@`%^Qd^J@3`W3#`_CTXhsk5Zc>@yN*m15WWTYMox%#!=nsDRe-j#Otl-R2C
zzx@WBbwz_i-;5k@b0UuITSnqfns9zAOx!CyJ;e_V{uFOTgF}|bj#Czidl#2WsW;Ae
zQ%qcN^77UQMNAUZQX9DIm_{h_h{`j(QUQaiwcSp9jftF3gTp5#wd$Oll%cqy370UJ
zNI}w=LuO7{R*Pu@M}rexSLX`CP^}bH&6U25
zrXefiL8fDjx4xHcRz3xsr%DUKTXej^Ud2}wXS4COpUla^8{y+UeMWe0?q$GI$^G1$
z2ns_U_ymycDWbHSY#&>%AU>m3Qs*8sG%9c#0dh$}@xwQm;$Il_iFbviR=B{`yVq>&
z0*EGP;W|T91t|?>;v&@_lt$P`lXu)4Etby=C@g~MK*Gly?`sncK2|!e_nF5>ggnNZ
z95=onlGa%!AopG)ug6t^S@r#IjLLPhPg#zc1M#Up6bd{8w_cKKee5!
zX&%=VxKQNJC3(A}Ul$oaDe{z&avMYa%Zu@(@El$!YuP%QcHUGUNgMoS1{Z0-Z!V!f
z#!6*9ICONFDff6fk`Y&uQ&ZC)gbSZJMkHy|YFE=$Rnt9>VTl`^zgdA;?>n9qg=j~RySNepT0m=rLausBudgFbD1d#<06%ehW
z()c^j-FH!ZkQ1&$DOf?gEXB}RBntVp5HY?&(t1Sh&cFaOZ@LW)W@I*jds?DH(%lP(
zZIf|tm~%WtVb&YM&hsP%uWMN@2dg9tRpLc7e)
z5A855>nevOf)*DXCnUsc+IZPT3lBB$thsDB@z_<$0$@>;+8H=*`PH}O$Nvtq=dzcV
zNF_eL{z0{N>V4^VF{aJOrYqc^1`d4C?7ku1aB6!E{W%f=QrO;g4p|lSCmn;2m0
z=nM4amnHk<=x$9MBX<0n3`nGQ|mQZChr5k}09&0bnp+Zpb>Nlj&8m$EP8@KMRZwyJ
zG8rPTG3T21ksWpyEp`Lz^I+_s4|yxW#!lV3@*>^*+sqasl?dBo_!`Ec
zKXyEyAAe`9l+WjqJG-*z+wDS@gesP(cEf1Z?_}2%C4L-^xwP~?pmf}UB_sC7Mt16bA#9Osd5>h~%LA8Umz`CBl)H*D1~Acr
zUy3E)_NjT2BPZTylBJ8>#<}Ji`_jyU8usO$fx8Ps_`4-fjWRp?bpd6h_!yJ}!C^CQ
zW*}6fbU4bY{}dQaG1ei+$L*>kDUYDkC`7
zYppG+mrCb^Y8r6QW%f<#2OZ#p2US>KXz}0^rsxIZX-88x5n&HlR&lmkYWl1v4+8J8BBZ0L45fh|g5XmTdYO|kP?DCSA7X~~>NyrZ?=B&|yq3amZw(tE^V)jOu8V6=aXbF7#KCteGk;$3Y
z`wX6!ozx<>USlmP_~aHc=$9w|f<1|SJDfwn#U78>4UrBr^^LDFMI=}f
zEz`3p@xn3I%3NV#LtcoYR^zZtOXUL@k4e@yFAuMbh^-=)LotJH
zqWL*IlKUMWv7>M7TsM9dDUY5vPBI?ESy!UG>M%tqh_u|-zMXp(<4a$!MBc6}
z)rMNZtEN1PE)x*etfY?iY#Q?`XdPxtvbj>*!_Jr?kyp)A?mbrmW-DZ915;mBCC%Vr
zWL&jr(NAPNJZ>}@p3slIn-uD;Lze(|wsTH001j+u_dI)MXAb~cD9A_=!|A>Uu_Oyo
zTGG--N~*qy$1uVA{gWh{ISa2RJExTva|;W{1+0XbM7pRs=L-hu6zg2=`a5*mpg*~(oT!@R|k(
zSkQ{9G30u6s;Tw`;_4g46W_afBXD0yq*PI36E;dPgXC|C>ZUzgE^B@uZ8aIaEEMmdDmMpW5u--Zx-i2Ja
zQq@P+2@S#^NI@M63k>(i4R_wa^?hSx8m@ZtY6+^K;%N{pJD{SBbd(#2i%HPRb{B%n
zqD#2=a2zn6VHbU%Xp|3bi4Ve9K;Qwl#nLf#RY3}G&}?M5xcjMl~*=U!{0egXN?
z`R7}20GptxJ{z*aFP+h=nZH6J+=~?SgE2vbS3`e>x_4F-V=2BR7FR!6SM)R_RgA42
zOb~X0W?6eu&so`b&*bE|8#yNl$HzOxAgdB)MQYl?Y=UWb(6;*kyC><%h>br3x>xJX
z)tNXKbYP7~5r@^+bZcHT|7C6lM=wu!ueo2o77~ehZy7HN^|VWJtu4>QPqBWW-Ao>?
zx(HW312RNTa{_h^C)kywkXdJ{yrih1L}f*
zD0cAN6{x#`RnxW=ghLptY7DWUs7+9g|66|~)LRn-N|2vDIRpcqoOlI>6*7gtQZ+$q
z{L7*dqp*{yn!abLKAze_{;_6eu>p#`p={QZp8M%s`#gb_A)r;g(zzdSwNZ*BtoxbHR+On=K(S5iH2WP+#$e*_|H5%
zwXjH|g0s(1p4g9ap#XdQ5}YxLkL+TJ;0;M4&<6;L^HZ8QcX<@wh5+eZe~e6A$6L%9
z!boX?)v3~tK!uG1+z!PElsHo~;l*tBQ$bj9_gnK@MTWl`q9X)O97PSz23UwgxG_lu
z3X}w>J$pY5e-j(On?#%h`t?Igc>r?KE0D2cGX0AxeuLxP2cd=_rpZ5>+n4iuyt>!Pmu4j2~y2lET1DiS)ZgH4tw$u>0}sO+Gq>T)_pqNu;_-CpH*r>#mz5>l534tYj(2QqSI`SF8evfj2x&k|
z-Cnr&P_e3@R!TmUfyNK`^uCH`wB|z$;`fmBGz!J19{vp^7ay`-G`L}O|z-Y>qX3K{KoHI
zZquc&%QTua^(Mq9gLL)9Uv=R!m(5*Wt>)&ROYO&%`8r`LD(Xh0{08SAhI)g?9aznf
z-`LV8~SHTX-O;lt{>D2me?o_Bb
z*CYFi8olL=Ti^MpOdM+sr^g-Bc;73-MCTa_+i_9hR|4u3NavdFQ$(!XQ|!Yzj}}ho
z(6S;%mKtxub+k#X9vWQOt7tmVY8SkxvB%5{dpk8G4=kIoBc^`Z_ZO)=n^E365ZH^S
z%rV-3Y=0;(b-augzqkGi!=`B{`|PYLbyMT^kADpRI}x_3VFil8NF8qWuCh2f?(_~#L_Sfb)X=*j
zZmVki2|F+ryP2bZSKDXQmqyuu3odo^km=`=xd4ifG+~OZt~%GTVf3eBsFiyZK_~^c
z6;@@HeW*SV$zuIPxjpAF0ljcXc^~;9=MbJUy4?_8WtT2>z*F7Vsm$zicx$CD9
z!b@%}RN=a+w)&<9jbR!d}NM6kE^W294x^f
zeBKmh#bHC{n^gC+cI`KPb;tT%pQIy<=5cuA#=|$vRNlMp)(a?%m{Un|XCzCHj`*{~
z-E9=Uzr?n3C0eP&XH$_>ZT=ft1%Cl1UU@!goCK6vWHx;tfd-pGiW}9gfYIu6@Ar-Z
ztdHbX+oNS9T54aiVe`|J%+sl|>qLuT?UBy3Ke#4Zuir5CI^Jq=Yf}3;h>Y4cZaSPr
z+N-{u-v5#lE{Vft-2KQNg%65x$#j;wUNxwEPPW2lZN8|lLO}j)Ip_t+H5
zOA-h|QPHc~U`*KTh*Rz7L8ZGFP<+H$$DRPUZylkiNqr1F*b+lOyZTn&9%CDw7JiLm?97^6^U?H*`=&}@
z6ctO1UT7oeQTo8wa#z&{Yxu$`Gc@A;!tHqI_akZ&q~PWNN>puk;0zHIt}B<2
zqGa1HtILec;q}&FNd_nG{_ka}WmCfn4|sQt8yC3GxbY*`>GsNItwp_ha$
z34O}Ug6Xa_z{*09>m<)dPCt`Jh5u576FMv?o#tX`DF=WwDLMGTs>T&A*kPehf)#l7
zN1tC2$hZkk%*bAC`sQZQo?)66DFKtk6N>K)jJKzTx&*}|a9f~+?nN-BM4y&LlN{NJ
zb2DlYYs70_Tnrg)^7P!skF9adQ{b-ZK*hw_kWZ;_?+em*I;#ffnT?Tz0sY-Te+`Q(
zeOnd)Gol-~m}{`Xrx&e#>T=?}vR97)fm>{nFQuI(KJRlD?G7MnJJ;`x!fG8?=dsP-
z$5XxeaQm(+Ju71Yp-UVJZmsh*+F_qdDkZm=Py-vU2WL(GGrUTsu>C_>ab6U)jhe~2
zB#s?WzJ8Ev?XudVYG+vVX@B2(mtxEeK9M~_iGO7Ck}lmUWxggMW$2ze`+%i6Xa`(H
zCk<@%Sa}~N$X6JtE74y&ktwmjRZYpyG^)+D6midNwL3sL0k2fEr?}cC4$U+&HH8gw
zztM5Z7cfQ4g4O=t&M7g(f(Kf`A35sI<_z|oh=;Uq#XR8DZ^{hM&u
zKFm7psi*h@)B%obn0U#D{Nd1@0)l`j@f0N2Fb%%C(
zV2k-l73Ym4<%UDG@xRVFBkl*Vj8_;FzD_1+3%`=
zL`aTz`usQ(5Z1yjvLGDZKy3AOGFo`+=OVW@pe1zqGIJS=VjTqjKgzk%pr*2Qi^G*x
zo6v$bq7aZNpsh?cGe!nwCWwLqqcR4BFeX5NsGxw&B(p$7B@smdg$P0#(ugw2AfXWn
zgdiZukRS<3Fd@l1;kI4(zN%Msf4-{wE2mOt?|sfb`|R_r^{w@Z)^`upkpz~c%;y|g
z9=j)>Ho_tG6d=vvH;j#OUhm^5ruO58EBg;BG04ZN^538=7_>8%^Bhh))|}EByTD;k
zfi;M_*BCEp$M%eXtHOxKmFYeBnsR~U5^rVgw{V!KoWqjkr6p{PD`gB}6s~SWzR3en
za1}%P^f3_B+(__KwGj0Ow~vbdUaK6IJm%d3rqsd+VNm?Twvy0gQLld%a4J
z2_x+haAbWXNFqTo5PLWEhle{UN1u^=i9)
z=WuPk_vsG+NMtJ8P(YZSPW6{l8V7D=NxCoH{quqiL`D)|B1$|dFire~Qo3;clN=8M
zjIklK@}XcVY4I}%sDLHsN{{4yE{23G;7G4I(g)05oXH2|TWNYTLZMFwz
zEwTwzo#W&={y@T9n?6fT#oQW}ueqzNMiUVnc?B*jl^~B1-V}D`keG|mHh!%(eI@xk
zTELkNGT;IOkuJ=V9u?otZ`3^-8+CyCL{5|;bs%NO-DV4+XuajRqbT@=iVF|o|>S6^gjI7S)IDqCk>oAUPI6mKC
zXHN$i
zDE&H)hjJW6hj!XR&r}OPqr7=;Pk45hC{U3-G?A;#V+Z}r%g4nEkKl`e^jB`Px3?*}
z*lZ-aeRE#~M1Lu(y4>kI%Gq8&5zQb-1GsdiS32R~pJ!lO2U!UTm)aI_0@lhJpN0`K
zx@aFeF%hI`l98XOI=w=~J|YmZP|(PF4vfT79fE#q{(!1K{(=w+c`YHL_DG_BJr0wv
z8dyHFwCJ^HEG_iO89Qu6g?{W`p7VOS#7NN^aoBqPx?x)-u@+EEM(U4>kyybSdGZO|
zc;KfZaAzEjpj58DuLx~Q`z0L5mQ%DVX^U5
z0g=okwJkY}-(N}JxS9M7D|9I%RnjUpm`^9eo}Wu4gvz343iiQ(|--S3a^K`av_ita`D$f|sMW&u`xbmLct-r6ji0yl;Dxn6O+33EqZk%)C
z3Gk+WTOkRAOHhm+Ovs+iF!&0sD6x5qfR=oD@*F&ME>bd(+r94H59CgrtjKMO{m1|-Dtv*5GW?4<
z9}{|tn4|W>3)lkAd%>2Wk_?p~t^pDVkUH;m_7t1CQ;`DyP}>A*Nw*xJqa=@qlJH{=Tq&c{!(+VS3`OKUj~zS<&`%cls*w4tpE
zt2eHaCy6!lx6Djjh3+tEqmZVqvr5a=MQ>6B9cYD)cgN^Mr+PH#ZJ988Q(Ukwom29d
z(`^FK;b7o-9}VxE$f>&}sFY0Dy3Dd87BP_wjr%nCmr2{SZMU&;o|*#OxDvE_^2dYU
zAlMpf3Up&>mb@2-Jy<1b72*h9jBb|6qs8&6EGM0@I0^*UZ+wUw9);<3GI{0D6z9CX
zrOvsZSd2u1`nZ22l20J+Qj^2V3y@h>*Sv$BI~8UK1L=f4V6ChM76HLf^la&%<~DH%
z;gCF}6WX*sL`}8NGhfB*6s7!H`<74dm*W<%>E&luQ3t`sxA93nFzNW1H%|0Qe(!q}
z+-E2M`eZ~5AsS*V1p)R#JyXF-q$*&iH%#a;Y-QZcH((MRX08JIgHP>~PUg+S*h&F0
z3BFQsmg3kHn?0Md;I;)vg)B=3+Sn00X>pF2+4!`LK^LxI`HtWMrT7(#sLd?n9;3FQ
z7Usf6*0cYCY1j`B^8I?szktx3&@y)wRnL6}P)Qbc<$#20-b&b(P|Q$_NJrZw8GkOP
zckIN4?aM{p&P|KVczQ|{;hd;NUf2`T6`g$`0!gj-AtzX@n;qJNp_{s7(M{2kF*Z$V
z>wo?NqjW*~%p%uZ&X+`Q@c?ju-b80|S&Yr#Av*QE^hR7hD6Va!jWAelvYlL{LZ4gz
zYHRu-#>!CT;LXIxckxNI<(`b>ru6whC{B_$G@Dldp})|j>x8`dwcD#1yI_YdmCduH
z$aE`*vHJ^HPeGjE6^+&9>SFLu^y(v!A`Xa(j#va_=f-4kVh!|ASlYwdI+7?Flwnhq
zS?dvDgS$u>gTCs>KA{}0u{;ch7b}66m!EV+0cLB#Y^f|6sk->8x=2OPRC7o0o2@e(
z7HjDmV{Nbe?%J6qRDY`!Dtz+9xg26K2!uj$*(aqpM7}6F^1Xd<=_m@1oUN->xaCgx
z8D65#>;*mP=u$5V7d29BM&35Hy!dsZ-^k6Q#{As4MiH`6kE`kIcEEOg>d%%(2{Bg|
zeM!H>=Gst(vAKJ-FfzIN(fxa$N|3?tVV`%;eA@cDq_oYVTCuc%q=7YfI_^(CabSX!
z{K`+L&zBCTwIUj6@obAzUX|DSLh&j#=x{?mUW)oap~2y=Gw(HS7suNBtB|?}%2_`$Q9?2PJF*r7
zU&~kN%A=3>-Umh5qI+fYRM4B9CjCe=)|Z$vnJvES&$cFl`znbc!CVZGT|W
zJxabemx{~X?Dk*5cRzj$^c?YbsDm2bh*&-#`}O
zw;*2SMnHwe&UlIpfThBgmXgWCb&IHYcg4PR;I#qTHnKM_noTr&3Xr$CV%O8ZAmMfb
z9-Cz8YU}aOfdRd1duE>}U{c$JX44kEJ|F-=1L2w&K?7bjQ%L}v0}L`;PWn3o>H%3T0niE)GFL*j62AVcP+8|mHI>x8g6e$Vq(Z-7L;ueOmM%oVQHmhg$z
zcSfW(2?0QH9hUo=Z42jrh^a01upgUkb|ZBSi`$`rY1m;E6YQiC{k_MP6Y_bfHP!Z0_Q(4I3HgC<76l0M(}su^eF+WV{qt`H>Pp
zcYjZT+n;4p+7I_~ktoE9X1HS3)X#|KRw7zkaJxJ7_>vnb$L!x`sE$BLVz>^!`vbkJNXVf3gHCHKbnMu9;k+ZiFdU+D?2^eKRZe;7^A`4@E
zb^z1auqZ*^G5oez=IMaFywA`(3Atj&(-~xq2=6U`1a5a#y*RZ4d<;=S7DS3`{LR6@qoe61?=V{qweDgq%7y}FE%{7&)Ov})Ad7+r`dm4
zUQ7A&%JhGGS**V+b^ZT2xF)+HdOj8F-e$E32Chmy=G284|I0A}CVo)?z>kEUj;_&B
z9iyYV#^-d8o9OA9=pH|!qhq3@V@aGi^=}Qr5dQGM`2V|s{+$0e@qOE!a<;Cu^u6&v
DyUat^
literal 0
HcmV?d00001
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 4f5119e2f8..fc92e187ef 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -80,6 +80,7 @@ We look forward to seeing what you can do with Corda!
design/monitoring-management/design.md
design/sgx-integration/design.md
design/sgx-infrastructure/design.md
+ design/threat-model/corda-threat-model.md
design/data-model-upgrades/signature-constraints.md
.. conditional-toctree::
From 965f9ce528b95244acc35846f1268f095d5c7a92 Mon Sep 17 00:00:00 2001
From: Rick Parker
Date: Mon, 24 Sep 2018 09:55:56 +0100
Subject: [PATCH 2/2] ENT-2431 Lay foundations for caching metrics (#3955)
---
.../client/jfx/model/NetworkIdentityModel.kt | 3 +-
.../net/corda/core/internal/NamedCache.kt | 6 ---
.../messaging/ArtemisMessagingTest.kt | 8 ++-
.../network/PersistentNetworkMapCacheTest.kt | 3 +-
.../RaftTransactionCommitLogTests.kt | 3 +-
.../net/corda/node/internal/AbstractNode.kt | 31 ++++++-----
.../kotlin/net/corda/node/internal/Node.kt | 19 ++++---
.../node/services/api/ServiceHubInternal.kt | 2 +
.../identity/PersistentIdentityService.kt | 17 +++---
.../keys/PersistentKeyManagementService.kt | 15 ++++--
.../messaging/P2PMessageDeduplicator.kt | 10 ++--
.../services/messaging/P2PMessagingClient.kt | 7 ++-
.../network/PersistentNetworkMapCache.kt | 12 +++--
.../persistence/DBTransactionStorage.kt | 9 ++--
.../persistence/NodeAttachmentService.kt | 16 +++---
.../BFTNonValidatingNotaryService.kt | 3 +-
.../PersistentUniquenessProvider.kt | 16 +++---
.../transactions/RaftUniquenessProvider.kt | 9 ++--
.../transactions/SimpleNotaryService.kt | 2 +-
.../transactions/ValidatingNotaryService.kt | 2 +-
.../node/utilities/AppendOnlyPersistentMap.kt | 13 +++--
.../corda/node/utilities/NodeNamedCache.kt | 54 +++++++++++++++++++
.../node/utilities/NonInvalidatingCache.kt | 21 ++++----
.../PersistentIdentityServiceTests.kt | 5 +-
.../AppendOnlyPersistentMapTest.kt | 4 +-
.../persistence/DBTransactionStorageTests.kt | 4 +-
.../HibernateColumnConverterTests.kt | 3 +-
.../persistence/NodeAttachmentServiceTest.kt | 3 +-
.../PersistentUniquenessProviderTests.kt | 5 +-
.../utilities/TestingNamedCacheFactory.kt | 33 ++++++++++++
.../corda/notarydemo/MyCustomNotaryService.kt | 2 +-
.../node/internal/InternalMockNetwork.kt | 9 ++--
.../node/internal/performance/Reporter.kt | 8 +--
33 files changed, 243 insertions(+), 114 deletions(-)
create mode 100644 node/src/main/kotlin/net/corda/node/utilities/NodeNamedCache.kt
create mode 100644 node/src/test/kotlin/net/corda/node/utilities/TestingNamedCacheFactory.kt
diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NetworkIdentityModel.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NetworkIdentityModel.kt
index 0b09957678..aa7f7e50f2 100644
--- a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NetworkIdentityModel.kt
+++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NetworkIdentityModel.kt
@@ -1,5 +1,6 @@
package net.corda.client.jfx.model
+import com.github.benmanes.caffeine.cache.CacheLoader
import com.github.benmanes.caffeine.cache.Caffeine
import javafx.beans.value.ObservableValue
import javafx.collections.FXCollections
@@ -31,7 +32,7 @@ class NetworkIdentityModel {
private val rpcProxy by observableValue(NodeMonitorModel::proxyObservable)
private val identityCache = Caffeine.newBuilder()
- .buildNamed>("NetworkIdentityModel_identity", { publicKey ->
+ .buildNamed>("NetworkIdentityModel_identity", CacheLoader { publicKey: PublicKey ->
publicKey.let { rpcProxy.map { it?.cordaRPCOps?.nodeInfoFromParty(AnonymousParty(publicKey)) } }
})
val notaries = ChosenList(rpcProxy.map { FXCollections.observableList(it?.cordaRPCOps?.notaryIdentities() ?: emptyList()) }, "notaries")
diff --git a/core/src/main/kotlin/net/corda/core/internal/NamedCache.kt b/core/src/main/kotlin/net/corda/core/internal/NamedCache.kt
index 7b54b6ceb4..2a96de4835 100644
--- a/core/src/main/kotlin/net/corda/core/internal/NamedCache.kt
+++ b/core/src/main/kotlin/net/corda/core/internal/NamedCache.kt
@@ -29,12 +29,6 @@ fun Caffeine.buildNamed(name: String): Cache {
return this.build()
}
-fun Caffeine.buildNamed(name: String, loadFunc: (K) -> V): LoadingCache {
- checkCacheName(name)
- return this.build(loadFunc)
-}
-
-
fun Caffeine.buildNamed(name: String, loader: CacheLoader): LoadingCache {
checkCacheName(name)
return this.build(loader)
diff --git a/node/src/integration-test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTest.kt
index 1ddef593f5..fef890d2ec 100644
--- a/node/src/integration-test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTest.kt
+++ b/node/src/integration-test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTest.kt
@@ -1,5 +1,6 @@
package net.corda.node.services.messaging
+import com.codahale.metrics.MetricRegistry
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.crypto.generateKeyPair
@@ -13,15 +14,16 @@ import net.corda.node.services.config.configureWithDevSSLCertificate
import net.corda.node.services.network.PersistentNetworkMapCache
import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor
+import net.corda.node.utilities.TestingNamedCacheFactory
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.MAX_MESSAGE_SIZE
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.driver.PortAllocation
-import net.corda.testing.internal.stubs.CertificateStoreStubs
import net.corda.testing.internal.LogHelper
import net.corda.testing.internal.rigorousMock
+import net.corda.testing.internal.stubs.CertificateStoreStubs
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
import net.corda.testing.node.internal.MOCK_VERSION_INFO
import org.apache.activemq.artemis.api.core.ActiveMQConnectionTimedOutException
@@ -88,7 +90,7 @@ class ArtemisMessagingTest {
}
LogHelper.setLevel(PersistentUniquenessProvider::class)
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), { null }, { null })
- networkMapCache = PersistentNetworkMapCache(database, rigorousMock()).apply { start(emptyList()) }
+ networkMapCache = PersistentNetworkMapCache(TestingNamedCacheFactory(), database, rigorousMock()).apply { start(emptyList()) }
}
@After
@@ -223,6 +225,8 @@ class ArtemisMessagingTest {
ServiceAffinityExecutor("ArtemisMessagingTests", 1),
database,
networkMapCache,
+ MetricRegistry(),
+ TestingNamedCacheFactory(),
isDrainingModeOn = { false },
drainingModeWasChangedEvents = PublishSubject.create>()).apply {
config.configureWithDevSSLCertificate()
diff --git a/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt
index 6ac68ccf5b..1eea4f49db 100644
--- a/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt
+++ b/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt
@@ -5,6 +5,7 @@ import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.internal.configureDatabase
import net.corda.node.internal.schemas.NodeInfoSchemaV1
import net.corda.node.services.identity.InMemoryIdentityService
+import net.corda.node.utilities.TestingNamedCacheFactory
import net.corda.nodeapi.internal.DEV_ROOT_CA
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.core.*
@@ -27,7 +28,7 @@ class PersistentNetworkMapCacheTest {
private var portCounter = 1000
private val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), { null }, { null })
- private val charlieNetMapCache = PersistentNetworkMapCache(database, InMemoryIdentityService(trustRoot = DEV_ROOT_CA.certificate))
+ private val charlieNetMapCache = PersistentNetworkMapCache(TestingNamedCacheFactory(), database, InMemoryIdentityService(trustRoot = DEV_ROOT_CA.certificate))
@After
fun cleanUp() {
diff --git a/node/src/integration-test/kotlin/net/corda/node/services/transactions/RaftTransactionCommitLogTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/transactions/RaftTransactionCommitLogTests.kt
index 3edb3d3f3e..38389ef595 100644
--- a/node/src/integration-test/kotlin/net/corda/node/services/transactions/RaftTransactionCommitLogTests.kt
+++ b/node/src/integration-test/kotlin/net/corda/node/services/transactions/RaftTransactionCommitLogTests.kt
@@ -16,6 +16,7 @@ import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.getOrThrow
import net.corda.node.internal.configureDatabase
import net.corda.node.services.schema.NodeSchemaService
+import net.corda.node.utilities.TestingNamedCacheFactory
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.core.ALICE_NAME
@@ -155,7 +156,7 @@ class RaftTransactionCommitLogTests {
val address = Address(myAddress.host, myAddress.port)
val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), { null }, { null }, NodeSchemaService(includeNotarySchemas = true))
databases.add(database)
- val stateMachineFactory = { RaftTransactionCommitLog(database, Clock.systemUTC(), RaftUniquenessProvider.Companion::createMap) }
+ val stateMachineFactory = { RaftTransactionCommitLog(database, Clock.systemUTC(), { RaftUniquenessProvider.createMap(TestingNamedCacheFactory()) }) }
val server = CopycatServer.builder(address)
.withStateMachine(stateMachineFactory)
diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt
index 76392a3b51..2391cf7fca 100644
--- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt
+++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt
@@ -64,10 +64,7 @@ import net.corda.node.services.statemachine.*
import net.corda.node.services.transactions.*
import net.corda.node.services.upgrade.ContractUpgradeServiceImpl
import net.corda.node.services.vault.NodeVaultService
-import net.corda.node.utilities.AffinityExecutor
-import net.corda.node.utilities.JVMAgentRegistry
-import net.corda.node.utilities.NamedThreadFactory
-import net.corda.node.utilities.NodeBuildProperties
+import net.corda.node.utilities.*
import net.corda.nodeapi.internal.NodeInfoAndSigned
import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.config.CertificateStore
@@ -116,6 +113,7 @@ import net.corda.core.crypto.generateKeyPair as cryptoGenerateKeyPair
// TODO Log warning if this node is a notary but not one of the ones specified in the network parameters, both for core and custom
abstract class AbstractNode(val configuration: NodeConfiguration,
val platformClock: CordaClock,
+ cacheFactoryPrototype: NamedCacheFactory,
protected val versionInfo: VersionInfo,
protected val cordappLoader: CordappLoader,
protected val serverThread: AffinityExecutor.ServiceAffinityExecutor,
@@ -125,6 +123,11 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
@Suppress("LeakingThis")
private var tokenizableServices: MutableList? = mutableListOf(platformClock, this)
+
+ protected val metricRegistry = MetricRegistry()
+ protected val cacheFactory = cacheFactoryPrototype.bindWithConfig(configuration).bindWithMetrics(metricRegistry).tokenize()
+ val monitoringService = MonitoringService(metricRegistry).tokenize()
+
protected val runOnStop = ArrayList<() -> Any?>()
init {
@@ -139,7 +142,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
}
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas, configuration.notary != null).tokenize()
- val identityService = PersistentIdentityService().tokenize()
+ val identityService = PersistentIdentityService(cacheFactory).tokenize()
val database: CordaPersistence = createCordaPersistence(
configuration.database,
identityService::wellKnownPartyFromX500Name,
@@ -151,13 +154,13 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
// TODO Break cyclic dependency
identityService.database = database
}
- val networkMapCache = PersistentNetworkMapCache(database, identityService).tokenize()
+
+ val networkMapCache = PersistentNetworkMapCache(cacheFactory, database, identityService).tokenize()
val checkpointStorage = DBCheckpointStorage()
@Suppress("LeakingThis")
val transactionStorage = makeTransactionStorage(configuration.transactionCacheSizeBytes).tokenize()
val networkMapClient: NetworkMapClient? = configuration.networkServices?.let { NetworkMapClient(it.networkMapURL, versionInfo) }
- private val metricRegistry = MetricRegistry()
- val attachments = NodeAttachmentService(metricRegistry, database, configuration.attachmentContentCacheSizeBytes, configuration.attachmentCacheBound).tokenize()
+ val attachments = NodeAttachmentService(metricRegistry, cacheFactory, database).tokenize()
val cordappProvider = CordappProviderImpl(cordappLoader, CordappConfigFileProvider(), attachments).tokenize()
@Suppress("LeakingThis")
val keyManagementService = makeKeyManagementService(identityService).tokenize()
@@ -166,7 +169,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
val vaultService = makeVaultService(keyManagementService, servicesForResolution, database).tokenize()
val nodeProperties = NodePropertiesPersistentStore(StubbedNodeUniqueIdProvider::value, database)
val flowLogicRefFactory = FlowLogicRefFactoryImpl(cordappLoader.appClassLoader)
- val monitoringService = MonitoringService(metricRegistry).tokenize()
val networkMapUpdater = NetworkMapUpdater(
networkMapCache,
NodeInfoWatcher(
@@ -314,7 +316,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
servicesForResolution.start(netParams)
networkMapCache.start(netParams.notaries)
- startDatabase(metricRegistry)
+ startDatabase()
val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null)
identityService.start(trustRoot, listOf(identity.certificate, nodeCa))
@@ -708,7 +710,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
}
protected open fun makeTransactionStorage(transactionCacheSizeBytes: Long): WritableTransactionStorage {
- return DBTransactionStorage(transactionCacheSizeBytes, database)
+ return DBTransactionStorage(database, cacheFactory)
}
@VisibleForTesting
@@ -768,7 +770,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
// Specific class so that MockNode can catch it.
class DatabaseConfigurationException(message: String) : CordaException(message)
- protected open fun startDatabase(metricRegistry: MetricRegistry? = null) {
+ protected open fun startDatabase() {
val props = configuration.dataSourceProperties
if (props.isEmpty) throw DatabaseConfigurationException("There must be a database configured.")
database.startHikariPool(props, configuration.database, schemaService.internalSchemas(), metricRegistry)
@@ -792,7 +794,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
// Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because
// the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with
// the identity key. But the infrastructure to make that easy isn't here yet.
- return PersistentKeyManagementService(identityService, database)
+ return PersistentKeyManagementService(cacheFactory, identityService, database)
}
private fun makeCoreNotaryService(notaryConfig: NotaryConfig, myNotaryIdentity: PartyAndCertificate?): NotaryService {
@@ -801,7 +803,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
return notaryConfig.run {
when {
raft != null -> {
- val uniquenessProvider = RaftUniquenessProvider(configuration.baseDirectory, configuration.p2pSslOptions, database, platformClock, monitoringService.metrics, raft)
+ val uniquenessProvider = RaftUniquenessProvider(configuration.baseDirectory, configuration.p2pSslOptions, database, platformClock, monitoringService.metrics, cacheFactory, raft)
(if (validating) ::RaftValidatingNotaryService else ::RaftNonValidatingNotaryService)(services, notaryKey, uniquenessProvider)
}
bftSMaRt != null -> {
@@ -942,6 +944,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
override val clock: Clock get() = platformClock
override val configuration: NodeConfiguration get() = this@AbstractNode.configuration
override val networkMapUpdater: NetworkMapUpdater get() = this@AbstractNode.networkMapUpdater
+ override val cacheFactory: NamedCacheFactory get() = this@AbstractNode.cacheFactory
private lateinit var _myInfo: NodeInfo
override val myInfo: NodeInfo get() = _myInfo
diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt
index 3d204ac7de..7b01e18c95 100644
--- a/node/src/main/kotlin/net/corda/node/internal/Node.kt
+++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt
@@ -48,6 +48,7 @@ import net.corda.node.services.messaging.*
import net.corda.node.services.rpc.ArtemisRpcBroker
import net.corda.node.utilities.AddressUtils
import net.corda.node.utilities.AffinityExecutor
+import net.corda.node.utilities.DefaultNamedCacheFactory
import net.corda.node.utilities.DemoClock
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.INTERNAL_SHELL_USER
import net.corda.nodeapi.internal.ShutdownHook
@@ -93,6 +94,7 @@ open class Node(configuration: NodeConfiguration,
) : AbstractNode(
configuration,
createClock(configuration),
+ DefaultNamedCacheFactory(),
versionInfo,
cordappLoader,
// Under normal (non-test execution) it will always be "1"
@@ -195,7 +197,9 @@ open class Node(configuration: NodeConfiguration,
database = database,
networkMap = networkMapCache,
isDrainingModeOn = nodeProperties.flowsDrainingMode::isEnabled,
- drainingModeWasChangedEvents = nodeProperties.flowsDrainingMode.values
+ drainingModeWasChangedEvents = nodeProperties.flowsDrainingMode.values,
+ metricRegistry = metricRegistry,
+ cacheFactory = cacheFactory
)
}
@@ -332,7 +336,7 @@ open class Node(configuration: NodeConfiguration,
* This is not using the H2 "automatic mixed mode" directly but leans on many of the underpinnings. For more details
* on H2 URLs and configuration see: http://www.h2database.com/html/features.html#database_url
*/
- override fun startDatabase(metricRegistry: MetricRegistry?) {
+ override fun startDatabase() {
val databaseUrl = configuration.dataSourceProperties.getProperty("dataSource.url")
val h2Prefix = "jdbc:h2:file:"
@@ -369,7 +373,7 @@ open class Node(configuration: NodeConfiguration,
}
}
- super.startDatabase(metricRegistry)
+ super.startDatabase()
database.closeOnStop()
}
@@ -418,12 +422,13 @@ open class Node(configuration: NodeConfiguration,
// https://jolokia.org/agent/jvm.html
JmxReporter.forRegistry(registry).inDomain("net.corda").createsObjectNamesWith { _, domain, name ->
// Make the JMX hierarchy a bit better organised.
- val category = name.substringBefore('.')
+ val category = name.substringBefore('.').substringBeforeLast('/')
+ val component = name.substringBefore('.').substringAfterLast('/', "")
val subName = name.substringAfter('.', "")
- if (subName == "")
- ObjectName("$domain:name=$category")
+ (if (subName == "")
+ ObjectName("$domain:name=$category${if (component.isNotEmpty()) ",component=$component," else ""}")
else
- ObjectName("$domain:type=$category,name=$subName")
+ ObjectName("$domain:type=$category,${if (component.isNotEmpty()) "component=$component," else ""}name=$subName"))
}.build().start()
}
diff --git a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt
index 674ba91b89..8d5583544f 100644
--- a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt
+++ b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt
@@ -25,6 +25,7 @@ import net.corda.node.services.network.NetworkMapUpdater
import net.corda.node.services.persistence.AttachmentStorageInternal
import net.corda.node.services.statemachine.ExternalEvent
import net.corda.node.services.statemachine.FlowStateMachineImpl
+import net.corda.node.utilities.NamedCacheFactory
import net.corda.nodeapi.internal.persistence.CordaPersistence
import java.security.PublicKey
@@ -132,6 +133,7 @@ interface ServiceHubInternal : ServiceHub {
}
fun getFlowFactory(initiatingFlowClass: Class>): InitiatedFlowFactory<*>?
+ val cacheFactory: NamedCacheFactory
}
interface FlowStarter {
diff --git a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt
index 8ed683b8bf..46f29b1ffd 100644
--- a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt
+++ b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt
@@ -10,6 +10,7 @@ import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.node.services.api.IdentityServiceInternal
import net.corda.node.utilities.AppendOnlyPersistentMap
+import net.corda.node.utilities.NamedCacheFactory
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
import net.corda.nodeapi.internal.crypto.x509Certificates
import net.corda.nodeapi.internal.persistence.CordaPersistence
@@ -29,13 +30,14 @@ import javax.persistence.Lob
* cached for efficient lookup.
*/
@ThreadSafe
-class PersistentIdentityService : SingletonSerializeAsToken(), IdentityServiceInternal {
+class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSerializeAsToken(), IdentityServiceInternal {
companion object {
private val log = contextLogger()
- fun createPKMap(): AppendOnlyPersistentMap {
+ fun createPKMap(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap {
return AppendOnlyPersistentMap(
- "PersistentIdentityService_partyByKey",
+ cacheFactory = cacheFactory,
+ name = "PersistentIdentityService_partyByKey",
toPersistentEntityKey = { it.toString() },
fromPersistentEntity = {
Pair(
@@ -50,9 +52,10 @@ class PersistentIdentityService : SingletonSerializeAsToken(), IdentityServiceIn
)
}
- fun createX500Map(): AppendOnlyPersistentMap {
+ fun createX500Map(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap {
return AppendOnlyPersistentMap(
- "PersistentIdentityService_partyByName",
+ cacheFactory = cacheFactory,
+ name = "PersistentIdentityService_partyByName",
toPersistentEntityKey = { it.toString() },
fromPersistentEntity = { Pair(CordaX500Name.parse(it.name), SecureHash.parse(it.publicKeyHash)) },
toPersistentEntity = { key: CordaX500Name, value: SecureHash ->
@@ -101,8 +104,8 @@ class PersistentIdentityService : SingletonSerializeAsToken(), IdentityServiceIn
// CordaPersistence is not a c'tor parameter to work around the cyclic dependency
lateinit var database: CordaPersistence
- private val keyToParties = createPKMap()
- private val principalToParties = createX500Map()
+ private val keyToParties = createPKMap(cacheFactory)
+ private val principalToParties = createX500Map(cacheFactory)
fun start(trustRoot: X509Certificate, caCertificates: List = emptyList()) {
_trustRoot = trustRoot
diff --git a/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt b/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt
index ef6e2b3265..835a751c73 100644
--- a/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt
+++ b/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt
@@ -6,6 +6,7 @@ import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.utilities.MAX_HASH_HEX_SIZE
import net.corda.node.services.identity.PersistentIdentityService
import net.corda.node.utilities.AppendOnlyPersistentMap
+import net.corda.node.utilities.NamedCacheFactory
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY
@@ -25,7 +26,7 @@ import javax.persistence.Lob
*
* This class needs database transactions to be in-flight during method calls and init.
*/
-class PersistentKeyManagementService(val identityService: PersistentIdentityService,
+class PersistentKeyManagementService(cacheFactory: NamedCacheFactory, val identityService: PersistentIdentityService,
private val database: CordaPersistence) : SingletonSerializeAsToken(), KeyManagementServiceInternal {
@Entity
@javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}our_key_pairs")
@@ -46,11 +47,15 @@ class PersistentKeyManagementService(val identityService: PersistentIdentityServ
}
private companion object {
- fun createKeyMap(): AppendOnlyPersistentMap {
+ fun createKeyMap(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap {
return AppendOnlyPersistentMap(
- "PersistentKeyManagementService_keys",
+ cacheFactory = cacheFactory,
+ name = "PersistentKeyManagementService_keys",
toPersistentEntityKey = { it.toStringShort() },
- fromPersistentEntity = { Pair(Crypto.decodePublicKey(it.publicKey), Crypto.decodePrivateKey(it.privateKey)) },
+ fromPersistentEntity = {
+ Pair(Crypto.decodePublicKey(it.publicKey),
+ Crypto.decodePrivateKey(it.privateKey))
+ },
toPersistentEntity = { key: PublicKey, value: PrivateKey ->
PersistentKey(key, value)
},
@@ -59,7 +64,7 @@ class PersistentKeyManagementService(val identityService: PersistentIdentityServ
}
}
- private val keysMap = createKeyMap()
+ private val keysMap = createKeyMap(cacheFactory)
override fun start(initialKeyPairs: Set) {
initialKeyPairs.forEach { keysMap.addWithDuplicatesAllowed(it.public, it.private) }
diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessageDeduplicator.kt b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessageDeduplicator.kt
index fcf89d2177..b8c29d8955 100644
--- a/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessageDeduplicator.kt
+++ b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessageDeduplicator.kt
@@ -4,6 +4,7 @@ import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name
import net.corda.node.services.statemachine.DeduplicationId
import net.corda.node.utilities.AppendOnlyPersistentMap
+import net.corda.node.utilities.NamedCacheFactory
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
import java.time.Instant
@@ -15,17 +16,18 @@ import javax.persistence.Id
/**
* Encapsulate the de-duplication logic.
*/
-class P2PMessageDeduplicator(private val database: CordaPersistence) {
+class P2PMessageDeduplicator(cacheFactory: NamedCacheFactory, private val database: CordaPersistence) {
// A temporary in-memory set of deduplication IDs and associated high water mark details.
// When we receive a message we don't persist the ID immediately,
// so we store the ID here in the meantime (until the persisting db tx has committed). This is because Artemis may
// redeliver messages to the same consumer if they weren't ACKed.
private val beingProcessedMessages = ConcurrentHashMap()
- private val processedMessages = createProcessedMessages()
+ private val processedMessages = createProcessedMessages(cacheFactory)
- private fun createProcessedMessages(): AppendOnlyPersistentMap {
+ private fun createProcessedMessages(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap {
return AppendOnlyPersistentMap(
- "P2PMessageDeduplicator_processedMessages",
+ cacheFactory = cacheFactory,
+ name = "P2PMessageDeduplicator_processedMessages",
toPersistentEntityKey = { it.toString },
fromPersistentEntity = { Pair(DeduplicationId(it.id), MessageMeta(it.insertionTime, it.hash, it.seqNo)) },
toPersistentEntity = { key: DeduplicationId, value: MessageMeta ->
diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt
index 8d67ba191f..13b96c61b0 100644
--- a/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt
+++ b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt
@@ -1,6 +1,7 @@
package net.corda.node.services.messaging
import co.paralleluniverse.fibers.Suspendable
+import com.codahale.metrics.MetricRegistry
import net.corda.core.crypto.toStringShort
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.ThreadBox
@@ -26,6 +27,7 @@ import net.corda.node.services.statemachine.DeduplicationId
import net.corda.node.services.statemachine.ExternalEvent
import net.corda.node.services.statemachine.SenderDeduplicationId
import net.corda.node.utilities.AffinityExecutor
+import net.corda.node.utilities.NamedCacheFactory
import net.corda.nodeapi.internal.ArtemisMessagingComponent
import net.corda.nodeapi.internal.ArtemisMessagingComponent.*
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.BRIDGE_CONTROL
@@ -81,6 +83,9 @@ class P2PMessagingClient(val config: NodeConfiguration,
private val nodeExecutor: AffinityExecutor.ServiceAffinityExecutor,
private val database: CordaPersistence,
private val networkMap: NetworkMapCacheInternal,
+ @Suppress("UNUSED")
+ private val metricRegistry: MetricRegistry,
+ cacheFactory: NamedCacheFactory,
private val isDrainingModeOn: () -> Boolean,
private val drainingModeWasChangedEvents: Observable>
) : SingletonSerializeAsToken(), MessagingService, AddressToArtemisQueueResolver {
@@ -129,7 +134,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
private val handlers = ConcurrentHashMap()
- private val deduplicator = P2PMessageDeduplicator(database)
+ private val deduplicator = P2PMessageDeduplicator(cacheFactory, database)
internal var messagingExecutor: MessagingExecutor? = null
/**
diff --git a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt
index 4cacee3a59..f873120c32 100644
--- a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt
+++ b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt
@@ -23,6 +23,7 @@ import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.node.internal.schemas.NodeInfoSchemaV1
import net.corda.node.services.api.NetworkMapCacheInternal
+import net.corda.node.utilities.NamedCacheFactory
import net.corda.node.utilities.NonInvalidatingCache
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.bufferUntilDatabaseCommit
@@ -36,7 +37,8 @@ import javax.annotation.concurrent.ThreadSafe
/** Database-based network map cache. */
@ThreadSafe
-open class PersistentNetworkMapCache(private val database: CordaPersistence,
+open class PersistentNetworkMapCache(cacheFactory: NamedCacheFactory,
+ private val database: CordaPersistence,
private val identityService: IdentityService) : NetworkMapCacheInternal, SingletonSerializeAsToken() {
companion object {
private val logger = contextLogger()
@@ -124,8 +126,8 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence,
override fun getNodesByLegalIdentityKey(identityKey: PublicKey): List = nodesByKeyCache[identityKey]!!
private val nodesByKeyCache = NonInvalidatingCache>(
- "PersistentNetworkMap_nodesByKey",
- 1024) { key ->
+ cacheFactory = cacheFactory,
+ name = "PersistentNetworkMap_nodesByKey") { key ->
database.transaction { queryByIdentityKey(session, key) }
}
@@ -144,8 +146,8 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence,
}
private val identityByLegalNameCache = NonInvalidatingCache>(
- "PersistentNetworkMap_idByLegalName",
- 1024) { name ->
+ cacheFactory = cacheFactory,
+ name = "PersistentNetworkMap_idByLegalName") { name ->
Optional.ofNullable(database.transaction { queryIdentityByLegalName(session, name) })
}
diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt b/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt
index 1fe72c50a9..034e91a2cf 100644
--- a/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt
+++ b/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt
@@ -15,6 +15,7 @@ import net.corda.core.transactions.SignedTransaction
import net.corda.node.services.api.WritableTransactionStorage
import net.corda.node.services.statemachine.FlowStateMachineImpl
import net.corda.node.utilities.AppendOnlyPersistentMapBase
+import net.corda.node.utilities.NamedCacheFactory
import net.corda.node.utilities.WeightBasedAppendOnlyPersistentMap
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
@@ -31,7 +32,7 @@ typealias TxCacheValue = Pair, List {
return WeightBasedAppendOnlyPersistentMap(
+ cacheFactory = cacheFactory,
name = "DBTransactionStorage_transactions",
toPersistentEntityKey = { it.toString() },
fromPersistentEntity = {
@@ -67,7 +69,6 @@ class DBTransactionStorage(cacheSizeBytes: Long, private val database: CordaPers
}
},
persistentEntityClass = DBTransaction::class.java,
- maxWeight = maxSizeInBytes,
weighingFunc = { hash, tx -> hash.size + weighTx(tx) }
)
}
@@ -86,7 +87,7 @@ class DBTransactionStorage(cacheSizeBytes: Long, private val database: CordaPers
}
}
- private val txStorage = ThreadBox(createTransactionsMap(cacheSizeBytes))
+ private val txStorage = ThreadBox(createTransactionsMap(cacheFactory))
override fun addTransaction(transaction: SignedTransaction): Boolean = database.transaction {
txStorage.locked {
diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt b/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt
index 78a62f2149..ca96ef1ad5 100644
--- a/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt
+++ b/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt
@@ -18,8 +18,8 @@ import net.corda.core.node.services.vault.AttachmentQueryCriteria
import net.corda.core.node.services.vault.AttachmentSort
import net.corda.core.serialization.*
import net.corda.core.utilities.contextLogger
-import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.vault.HibernateAttachmentQueryCriteriaParser
+import net.corda.node.utilities.NamedCacheFactory
import net.corda.node.utilities.NonInvalidatingCache
import net.corda.node.utilities.NonInvalidatingWeightBasedCache
import net.corda.nodeapi.exceptions.DuplicateAttachmentException
@@ -43,9 +43,8 @@ import javax.persistence.*
@ThreadSafe
class NodeAttachmentService(
metrics: MetricRegistry,
- private val database: CordaPersistence,
- attachmentContentCacheSize: Long = NodeConfiguration.defaultAttachmentContentCacheSize,
- attachmentCacheBound: Long = NodeConfiguration.defaultAttachmentCacheBound
+ cacheFactory: NamedCacheFactory,
+ private val database: CordaPersistence
) : AttachmentStorageInternal, SingletonSerializeAsToken() {
companion object {
private val log = contextLogger()
@@ -206,8 +205,8 @@ class NodeAttachmentService(
// a problem somewhere else or this needs to be revisited.
private val attachmentContentCache = NonInvalidatingWeightBasedCache(
+ cacheFactory = cacheFactory,
name = "NodeAttachmentService_attachmentContent",
- maxWeight = attachmentContentCacheSize,
weigher = Weigher>> { key, value -> key.size + if (value.isPresent) value.get().second.size else 0 },
loadFunction = { Optional.ofNullable(loadAttachmentContent(it)) }
)
@@ -228,10 +227,9 @@ class NodeAttachmentService(
}
private val attachmentCache = NonInvalidatingCache>(
- "NodeAttachmentService_attachemnt",
- attachmentCacheBound) { key ->
- Optional.ofNullable(createAttachment(key))
- }
+ cacheFactory = cacheFactory,
+ name = "NodeAttachmentService_attachmentPresence",
+ loadFunction = { key -> Optional.ofNullable(createAttachment(key)) })
private fun createAttachment(key: SecureHash): Attachment? {
val content = attachmentContentCache.get(key)!!
diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt
index d6963e7130..156640c443 100644
--- a/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt
+++ b/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt
@@ -102,7 +102,8 @@ class BFTNonValidatingNotaryService(
private fun createMap(): AppendOnlyPersistentMap {
return AppendOnlyPersistentMap(
- "BFTNonValidatingNotaryService_transactions",
+ cacheFactory = services.cacheFactory,
+ name = "BFTNonValidatingNotaryService_transactions",
toPersistentEntityKey = { PersistentStateRef(it.txhash.toString(), it.index) },
fromPersistentEntity = {
//TODO null check will become obsolete after making DB/JPA columns not nullable
diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt b/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt
index eadedd4e64..b6c8ebf437 100644
--- a/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt
+++ b/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt
@@ -11,7 +11,10 @@ import net.corda.core.flows.StateConsumptionDetails
import net.corda.core.identity.Party
import net.corda.core.internal.concurrent.OpenFuture
import net.corda.core.internal.concurrent.openFuture
-import net.corda.core.internal.notary.*
+import net.corda.core.internal.notary.AsyncUniquenessProvider
+import net.corda.core.internal.notary.NotaryInternalException
+import net.corda.core.internal.notary.isConsumedByTheSameTx
+import net.corda.core.internal.notary.validateTimeWindow
import net.corda.core.schemas.PersistentStateRef
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SingletonSerializeAsToken
@@ -19,10 +22,10 @@ import net.corda.core.serialization.serialize
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.node.utilities.AppendOnlyPersistentMap
+import net.corda.node.utilities.NamedCacheFactory
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
import net.corda.nodeapi.internal.persistence.currentDBSession
-import net.corda.serialization.internal.CordaSerializationEncoding
import java.time.Clock
import java.time.Instant
import java.util.*
@@ -33,7 +36,7 @@ import kotlin.concurrent.thread
/** A RDBMS backed Uniqueness provider */
@ThreadSafe
-class PersistentUniquenessProvider(val clock: Clock, val database: CordaPersistence) : AsyncUniquenessProvider, SingletonSerializeAsToken() {
+class PersistentUniquenessProvider(val clock: Clock, val database: CordaPersistence, cacheFactory: NamedCacheFactory) : AsyncUniquenessProvider, SingletonSerializeAsToken() {
@MappedSuperclass
class BaseComittedState(
@@ -80,7 +83,7 @@ class PersistentUniquenessProvider(val clock: Clock, val database: CordaPersiste
@javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}notary_committed_states")
class CommittedState(id: PersistentStateRef, consumingTxHash: String) : BaseComittedState(id, consumingTxHash)
- private val commitLog = createMap()
+ private val commitLog = createMap(cacheFactory)
private val requestQueue = LinkedBlockingQueue(requestQueueSize)
@@ -98,9 +101,10 @@ class PersistentUniquenessProvider(val clock: Clock, val database: CordaPersiste
companion object {
private const val requestQueueSize = 100_000
private val log = contextLogger()
- fun createMap(): AppendOnlyPersistentMap =
+ fun createMap(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap =
AppendOnlyPersistentMap(
- "PersistentUniquenessProvider_transactions",
+ cacheFactory = cacheFactory,
+ name = "PersistentUniquenessProvider_transactions",
toPersistentEntityKey = { PersistentStateRef(it.txhash.toString(), it.index) },
fromPersistentEntity = {
//TODO null check will become obsolete after making DB/JPA columns not nullable
diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt b/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt
index 1265461d17..2192343fa1 100644
--- a/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt
+++ b/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt
@@ -28,6 +28,7 @@ import net.corda.core.utilities.debug
import net.corda.node.services.config.RaftConfig
import net.corda.node.services.transactions.RaftTransactionCommitLog.Commands.CommitTransaction
import net.corda.node.utilities.AppendOnlyPersistentMap
+import net.corda.node.utilities.NamedCacheFactory
import net.corda.nodeapi.internal.config.MutualSslConfiguration
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
@@ -55,13 +56,15 @@ class RaftUniquenessProvider(
private val db: CordaPersistence,
private val clock: Clock,
private val metrics: MetricRegistry,
+ private val cacheFactory: NamedCacheFactory,
private val raftConfig: RaftConfig
) : UniquenessProvider, SingletonSerializeAsToken() {
companion object {
private val log = contextLogger()
- fun createMap(): AppendOnlyPersistentMap, CommittedState, PersistentStateRef> =
+ fun createMap(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap, CommittedState, PersistentStateRef> =
AppendOnlyPersistentMap(
- "RaftUniquenessProvider_transactions",
+ cacheFactory = cacheFactory,
+ name = "RaftUniquenessProvider_transactions",
toPersistentEntityKey = { PersistentStateRef(it) },
fromPersistentEntity = {
val txId = it.id.txId
@@ -109,7 +112,7 @@ class RaftUniquenessProvider(
fun start() {
log.info("Creating Copycat server, log stored in: ${storagePath.toAbsolutePath()}")
val stateMachineFactory = {
- RaftTransactionCommitLog(db, clock, RaftUniquenessProvider.Companion::createMap)
+ RaftTransactionCommitLog(db, clock, { createMap(cacheFactory) })
}
val address = raftConfig.nodeAddress.let { Address(it.host, it.port) }
val storage = buildStorage(storagePath)
diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/SimpleNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/SimpleNotaryService.kt
index 16ad4464af..51909ea5b1 100644
--- a/node/src/main/kotlin/net/corda/node/services/transactions/SimpleNotaryService.kt
+++ b/node/src/main/kotlin/net/corda/node/services/transactions/SimpleNotaryService.kt
@@ -8,7 +8,7 @@ import java.security.PublicKey
/** A simple Notary service that does not perform transaction validation */
class SimpleNotaryService(override val services: ServiceHubInternal, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() {
- override val uniquenessProvider = PersistentUniquenessProvider(services.clock, services.database)
+ override val uniquenessProvider = PersistentUniquenessProvider(services.clock, services.database, services.cacheFactory)
override fun createServiceFlow(otherPartySession: FlowSession): NotaryServiceFlow = NonValidatingNotaryFlow(otherPartySession, this)
diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryService.kt
index 4a6f46b2ce..6e39a3ea1e 100644
--- a/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryService.kt
+++ b/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryService.kt
@@ -8,7 +8,7 @@ import java.security.PublicKey
/** A Notary service that validates the transaction chain of the submitted transaction before committing it */
class ValidatingNotaryService(override val services: ServiceHubInternal, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() {
- override val uniquenessProvider = PersistentUniquenessProvider(services.clock, services.database)
+ override val uniquenessProvider = PersistentUniquenessProvider(services.clock, services.database, services.cacheFactory)
override fun createServiceFlow(otherPartySession: FlowSession): NotaryServiceFlow = ValidatingNotaryFlow(otherPartySession, this)
diff --git a/node/src/main/kotlin/net/corda/node/utilities/AppendOnlyPersistentMap.kt b/node/src/main/kotlin/net/corda/node/utilities/AppendOnlyPersistentMap.kt
index 274ceb6c5a..81f080e425 100644
--- a/node/src/main/kotlin/net/corda/node/utilities/AppendOnlyPersistentMap.kt
+++ b/node/src/main/kotlin/net/corda/node/utilities/AppendOnlyPersistentMap.kt
@@ -309,21 +309,20 @@ abstract class AppendOnlyPersistentMapBase(
// Open for tests to override
open class AppendOnlyPersistentMap(
+ cacheFactory: NamedCacheFactory,
name: String,
toPersistentEntityKey: (K) -> EK,
fromPersistentEntity: (E) -> Pair,
toPersistentEntity: (key: K, value: V) -> E,
- persistentEntityClass: Class,
- cacheBound: Long = 1024
+ persistentEntityClass: Class
) : AppendOnlyPersistentMapBase(
toPersistentEntityKey,
fromPersistentEntity,
toPersistentEntity,
persistentEntityClass) {
- //TODO determine cacheBound based on entity class later or with node config allowing tuning, or using some heuristic based on heap size
override val cache = NonInvalidatingCache(
+ cacheFactory = cacheFactory,
name = name,
- bound = cacheBound,
loadFunction = { key: K ->
// This gets called if a value is read and the cache has no Transactional for this key yet.
val value: V? = loadValue(key)
@@ -355,12 +354,12 @@ open class AppendOnlyPersistentMap(
// Same as above, but with weighted values (e.g. memory footprint sensitive).
class WeightBasedAppendOnlyPersistentMap(
+ cacheFactory: NamedCacheFactory,
name: String,
toPersistentEntityKey: (K) -> EK,
fromPersistentEntity: (E) -> Pair,
toPersistentEntity: (key: K, value: V) -> E,
persistentEntityClass: Class,
- maxWeight: Long,
weighingFunc: (K, Transactional) -> Int
) : AppendOnlyPersistentMapBase(
toPersistentEntityKey,
@@ -368,8 +367,8 @@ class WeightBasedAppendOnlyPersistentMap(
toPersistentEntity,
persistentEntityClass) {
override val cache = NonInvalidatingWeightBasedCache(
- name,
- maxWeight = maxWeight,
+ cacheFactory = cacheFactory,
+ name = name,
weigher = Weigher { key, value -> weighingFunc(key, value) },
loadFunction = { key: K ->
val value: V? = loadValue(key)
diff --git a/node/src/main/kotlin/net/corda/node/utilities/NodeNamedCache.kt b/node/src/main/kotlin/net/corda/node/utilities/NodeNamedCache.kt
new file mode 100644
index 0000000000..d11c9667c2
--- /dev/null
+++ b/node/src/main/kotlin/net/corda/node/utilities/NodeNamedCache.kt
@@ -0,0 +1,54 @@
+package net.corda.node.utilities
+
+import com.codahale.metrics.MetricRegistry
+import com.github.benmanes.caffeine.cache.Cache
+import com.github.benmanes.caffeine.cache.CacheLoader
+import com.github.benmanes.caffeine.cache.Caffeine
+import com.github.benmanes.caffeine.cache.LoadingCache
+import net.corda.core.internal.buildNamed
+import net.corda.core.serialization.SerializeAsToken
+import net.corda.core.serialization.SingletonSerializeAsToken
+import net.corda.node.services.config.NodeConfiguration
+
+/**
+ * Allow passing metrics and config to caching implementations.
+ */
+interface NamedCacheFactory : SerializeAsToken {
+ /**
+ * Build a new cache factory of the same type that incorporates metrics.
+ */
+ fun bindWithMetrics(metricRegistry: MetricRegistry): NamedCacheFactory
+
+ /**
+ * Build a new cache factory of the same type that incorporates the associated configuration.
+ */
+ fun bindWithConfig(nodeConfiguration: NodeConfiguration): NamedCacheFactory
+
+ fun buildNamed(caffeine: Caffeine, name: String): Cache
+ fun buildNamed(caffeine: Caffeine, name: String, loader: CacheLoader): LoadingCache
+}
+
+class DefaultNamedCacheFactory private constructor(private val metricRegistry: MetricRegistry?, private val nodeConfiguration: NodeConfiguration?) : NamedCacheFactory, SingletonSerializeAsToken() {
+ constructor() : this(null, null)
+
+ override fun bindWithMetrics(metricRegistry: MetricRegistry): NamedCacheFactory = DefaultNamedCacheFactory(metricRegistry, this.nodeConfiguration)
+ override fun bindWithConfig(nodeConfiguration: NodeConfiguration): NamedCacheFactory = DefaultNamedCacheFactory(this.metricRegistry, nodeConfiguration)
+
+ override fun buildNamed(caffeine: Caffeine, name: String): Cache {
+ checkNotNull(metricRegistry)
+ checkNotNull(nodeConfiguration)
+ return caffeine.maximumSize(1024).buildNamed(name)
+ }
+
+ override fun buildNamed(caffeine: Caffeine, name: String, loader: CacheLoader): LoadingCache {
+ checkNotNull(metricRegistry)
+ checkNotNull(nodeConfiguration)
+ val configuredCaffeine = when (name) {
+ "DBTransactionStorage_transactions" -> caffeine.maximumWeight(nodeConfiguration!!.transactionCacheSizeBytes)
+ "NodeAttachmentService_attachmentContent" -> caffeine.maximumWeight(nodeConfiguration!!.attachmentContentCacheSizeBytes)
+ "NodeAttachmentService_attachmentPresence" -> caffeine.maximumSize(nodeConfiguration!!.attachmentCacheBound)
+ else -> caffeine.maximumSize(1024)
+ }
+ return configuredCaffeine.buildNamed(name, loader)
+ }
+}
\ No newline at end of file
diff --git a/node/src/main/kotlin/net/corda/node/utilities/NonInvalidatingCache.kt b/node/src/main/kotlin/net/corda/node/utilities/NonInvalidatingCache.kt
index 814ef0102e..2cf4904282 100644
--- a/node/src/main/kotlin/net/corda/node/utilities/NonInvalidatingCache.kt
+++ b/node/src/main/kotlin/net/corda/node/utilities/NonInvalidatingCache.kt
@@ -4,19 +4,18 @@ import com.github.benmanes.caffeine.cache.CacheLoader
import com.github.benmanes.caffeine.cache.Caffeine
import com.github.benmanes.caffeine.cache.LoadingCache
import com.github.benmanes.caffeine.cache.Weigher
-import net.corda.core.internal.buildNamed
class NonInvalidatingCache private constructor(
val cache: LoadingCache
) : LoadingCache by cache {
- constructor(name: String, bound: Long, loadFunction: (K) -> V) :
- this(buildCache(name, bound, loadFunction))
+ constructor(cacheFactory: NamedCacheFactory, name: String, loadFunction: (K) -> V) :
+ this(buildCache(cacheFactory, name, loadFunction))
private companion object {
- private fun buildCache(name: String, bound: Long, loadFunction: (K) -> V): LoadingCache {
- val builder = Caffeine.newBuilder().maximumSize(bound)
- return builder.buildNamed(name, NonInvalidatingCacheLoader(loadFunction))
+ private fun buildCache(cacheFactory: NamedCacheFactory, name: String, loadFunction: (K) -> V): LoadingCache {
+ val builder = Caffeine.newBuilder()
+ return cacheFactory.buildNamed(builder, name, NonInvalidatingCacheLoader(loadFunction))
}
}
@@ -33,13 +32,13 @@ class NonInvalidatingCache private constructor(
class NonInvalidatingWeightBasedCache private constructor(
val cache: LoadingCache
) : LoadingCache by cache {
- constructor (name: String, maxWeight: Long, weigher: Weigher, loadFunction: (K) -> V) :
- this(buildCache(name, maxWeight, weigher, loadFunction))
+ constructor (cacheFactory: NamedCacheFactory, name: String, weigher: Weigher, loadFunction: (K) -> V) :
+ this(buildCache(cacheFactory, name, weigher, loadFunction))
private companion object {
- private fun buildCache(name: String, maxWeight: Long, weigher: Weigher, loadFunction: (K) -> V): LoadingCache {
- val builder = Caffeine.newBuilder().maximumWeight(maxWeight).weigher(weigher)
- return builder.buildNamed(name, NonInvalidatingCache.NonInvalidatingCacheLoader(loadFunction))
+ private fun buildCache(cacheFactory: NamedCacheFactory, name: String, weigher: Weigher, loadFunction: (K) -> V): LoadingCache {
+ val builder = Caffeine.newBuilder().weigher(weigher)
+ return cacheFactory.buildNamed(builder, name, NonInvalidatingCache.NonInvalidatingCacheLoader(loadFunction))
}
}
}
\ No newline at end of file
diff --git a/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt
index 927608421c..091a47d168 100644
--- a/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt
+++ b/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt
@@ -8,6 +8,7 @@ import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.services.UnknownAnonymousPartyException
import net.corda.node.internal.configureDatabase
+import net.corda.node.utilities.TestingNamedCacheFactory
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.crypto.x509Certificates
@@ -46,7 +47,7 @@ class PersistentIdentityServiceTests {
@Before
fun setup() {
- identityService = PersistentIdentityService()
+ identityService = PersistentIdentityService(TestingNamedCacheFactory())
database = configureDatabase(
makeTestDataSourceProperties(),
DatabaseConfig(),
@@ -218,7 +219,7 @@ class PersistentIdentityServiceTests {
identityService.verifyAndRegisterIdentity(anonymousBob)
// Create new identity service mounted onto same DB
- val newPersistentIdentityService = PersistentIdentityService().also {
+ val newPersistentIdentityService = PersistentIdentityService(TestingNamedCacheFactory()).also {
it.database = database
it.start(DEV_ROOT_CA.certificate)
}
diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/AppendOnlyPersistentMapTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/AppendOnlyPersistentMapTest.kt
index 69646a6ccc..5ed16e74a8 100644
--- a/node/src/test/kotlin/net/corda/node/services/persistence/AppendOnlyPersistentMapTest.kt
+++ b/node/src/test/kotlin/net/corda/node/services/persistence/AppendOnlyPersistentMapTest.kt
@@ -5,6 +5,7 @@ import net.corda.core.utilities.loggerFor
import net.corda.node.internal.configureDatabase
import net.corda.node.services.schema.NodeSchemaService
import net.corda.node.utilities.AppendOnlyPersistentMap
+import net.corda.node.utilities.TestingNamedCacheFactory
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
import org.junit.After
@@ -271,7 +272,8 @@ class AppendOnlyPersistentMapTest(var scenario: Scenario) {
)
class TestMap : AppendOnlyPersistentMap(
- "ApoendOnlyPersistentMap_test",
+ cacheFactory = TestingNamedCacheFactory(),
+ name = "ApoendOnlyPersistentMap_test",
toPersistentEntityKey = { it },
fromPersistentEntity = { Pair(it.key, it.value) },
toPersistentEntity = { key: Long, value: String ->
diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt b/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt
index 44703546e2..f0c7c95859 100644
--- a/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt
+++ b/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt
@@ -8,8 +8,8 @@ import net.corda.core.crypto.TransactionSignature
import net.corda.core.toFuture
import net.corda.core.transactions.SignedTransaction
import net.corda.node.internal.configureDatabase
-import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.transactions.PersistentUniquenessProvider
+import net.corda.node.utilities.TestingNamedCacheFactory
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.core.*
@@ -154,7 +154,7 @@ class DBTransactionStorageTests {
}
private fun newTransactionStorage(cacheSizeBytesOverride: Long? = null) {
- transactionStorage = DBTransactionStorage(cacheSizeBytesOverride ?: NodeConfiguration.defaultTransactionCacheSize, database)
+ transactionStorage = DBTransactionStorage(database, TestingNamedCacheFactory(cacheSizeBytesOverride ?: 1024))
}
private fun assertTransactionIsRetrievable(transaction: SignedTransaction) {
diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateColumnConverterTests.kt b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateColumnConverterTests.kt
index 4832474cac..0c5213eb63 100644
--- a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateColumnConverterTests.kt
+++ b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateColumnConverterTests.kt
@@ -9,6 +9,7 @@ import net.corda.finance.contracts.asset.Cash
import net.corda.finance.flows.CashIssueFlow
import net.corda.node.services.identity.PersistentIdentityService
import net.corda.node.services.keys.E2ETestKeyManagementService
+import net.corda.node.utilities.TestingNamedCacheFactory
import net.corda.testing.core.BOC_NAME
import net.corda.testing.node.InMemoryMessagingNetwork
import net.corda.testing.node.MockNetwork
@@ -47,7 +48,7 @@ class HibernateColumnConverterTests {
val ref = OpaqueBytes.of(0x01)
// Create parallel set of key and identity services so that the values are not cached, forcing the node caches to do a lookup.
- val identityService = PersistentIdentityService()
+ val identityService = PersistentIdentityService(TestingNamedCacheFactory())
val originalIdentityService: PersistentIdentityService = bankOfCordaNode.services.identityService as PersistentIdentityService
identityService.database = originalIdentityService.database
identityService.start(originalIdentityService.trustRoot)
diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentServiceTest.kt
index baee00a8f3..70c827b496 100644
--- a/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentServiceTest.kt
+++ b/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentServiceTest.kt
@@ -15,6 +15,7 @@ import net.corda.core.node.services.vault.Sort
import net.corda.core.utilities.getOrThrow
import net.corda.node.internal.configureDatabase
import net.corda.node.services.transactions.PersistentUniquenessProvider
+import net.corda.node.utilities.TestingNamedCacheFactory
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.internal.LogHelper
@@ -51,7 +52,7 @@ class NodeAttachmentServiceTest {
val dataSourceProperties = makeTestDataSourceProperties()
database = configureDatabase(dataSourceProperties, DatabaseConfig(), { null }, { null })
fs = Jimfs.newFileSystem(Configuration.unix())
- storage = NodeAttachmentService(MetricRegistry(), database).also {
+ storage = NodeAttachmentService(MetricRegistry(), TestingNamedCacheFactory(), database).also {
database.transaction {
it.start()
}
diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt
index bdc2b642d0..4bf28c246c 100644
--- a/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt
+++ b/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt
@@ -10,6 +10,7 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.notary.NotaryInternalException
import net.corda.node.internal.configureDatabase
import net.corda.node.services.schema.NodeSchemaService
+import net.corda.node.utilities.TestingNamedCacheFactory
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.core.SerializationEnvironmentRule
@@ -49,7 +50,7 @@ class PersistentUniquenessProviderTests {
@Test
fun `should commit a transaction with unused inputs without exception`() {
- val provider = PersistentUniquenessProvider(Clock.systemUTC(), database)
+ val provider = PersistentUniquenessProvider(Clock.systemUTC(), database, TestingNamedCacheFactory())
val inputState = generateStateRef()
provider.commit(listOf(inputState), txID, identity, requestSignature)
@@ -57,7 +58,7 @@ class PersistentUniquenessProviderTests {
@Test
fun `should report a conflict for a transaction with previously used inputs`() {
- val provider = PersistentUniquenessProvider(Clock.systemUTC(), database)
+ val provider = PersistentUniquenessProvider(Clock.systemUTC(), database, TestingNamedCacheFactory())
val inputState = generateStateRef()
val inputs = listOf(inputState)
diff --git a/node/src/test/kotlin/net/corda/node/utilities/TestingNamedCacheFactory.kt b/node/src/test/kotlin/net/corda/node/utilities/TestingNamedCacheFactory.kt
new file mode 100644
index 0000000000..4582246e09
--- /dev/null
+++ b/node/src/test/kotlin/net/corda/node/utilities/TestingNamedCacheFactory.kt
@@ -0,0 +1,33 @@
+package net.corda.node.utilities
+
+import com.codahale.metrics.MetricRegistry
+import com.github.benmanes.caffeine.cache.Cache
+import com.github.benmanes.caffeine.cache.CacheLoader
+import com.github.benmanes.caffeine.cache.Caffeine
+import com.github.benmanes.caffeine.cache.LoadingCache
+import net.corda.core.internal.buildNamed
+import net.corda.core.serialization.SingletonSerializeAsToken
+import net.corda.node.services.config.MB
+import net.corda.node.services.config.NodeConfiguration
+
+class TestingNamedCacheFactory private constructor(private val sizeOverride: Long, private val metricRegistry: MetricRegistry?, private val nodeConfiguration: NodeConfiguration?) : NamedCacheFactory, SingletonSerializeAsToken() {
+ constructor(sizeOverride: Long = 1024) : this(sizeOverride, null, null)
+
+ override fun bindWithMetrics(metricRegistry: MetricRegistry): NamedCacheFactory = TestingNamedCacheFactory(sizeOverride, metricRegistry, this.nodeConfiguration)
+ override fun bindWithConfig(nodeConfiguration: NodeConfiguration): NamedCacheFactory = TestingNamedCacheFactory(sizeOverride, this.metricRegistry, nodeConfiguration)
+
+ override fun buildNamed(caffeine: Caffeine, name: String): Cache {
+ // Does not check metricRegistry or nodeConfiguration, because for tests we don't care.
+ return caffeine.maximumSize(sizeOverride).buildNamed(name)
+ }
+
+ override fun buildNamed(caffeine: Caffeine, name: String, loader: CacheLoader): LoadingCache {
+ // Does not check metricRegistry or nodeConfiguration, because for tests we don't care.
+ val configuredCaffeine = when (name) {
+ "DBTransactionStorage_transactions" -> caffeine.maximumWeight(1.MB)
+ "NodeAttachmentService_attachmentContent" -> caffeine.maximumWeight(1.MB)
+ else -> caffeine.maximumSize(sizeOverride)
+ }
+ return configuredCaffeine.buildNamed(name, loader)
+ }
+}
\ No newline at end of file
diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/MyCustomNotaryService.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/MyCustomNotaryService.kt
index 666844691c..ad684fc489 100644
--- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/MyCustomNotaryService.kt
+++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/MyCustomNotaryService.kt
@@ -27,7 +27,7 @@ import java.security.SignatureException
// START 1
@CordaService
class MyCustomValidatingNotaryService(override val services: ServiceHubInternal, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() {
- override val uniquenessProvider = PersistentUniquenessProvider(services.clock, services.database)
+ override val uniquenessProvider = PersistentUniquenessProvider(services.clock, services.database, services.cacheFactory)
override fun createServiceFlow(otherPartySession: FlowSession): FlowLogic = MyValidatingNotaryFlow(otherPartySession, this)
diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt
index ae0a4d00f4..ba52e79e72 100644
--- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt
+++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt
@@ -1,6 +1,5 @@
package net.corda.testing.node.internal
-import com.codahale.metrics.MetricRegistry
import com.google.common.jimfs.Configuration.unix
import com.google.common.jimfs.Jimfs
import com.nhaarman.mockito_kotlin.doReturn
@@ -47,6 +46,7 @@ import net.corda.node.services.statemachine.StateMachineManager
import net.corda.node.services.transactions.BFTNonValidatingNotaryService
import net.corda.node.services.transactions.BFTSMaRt
import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor
+import net.corda.node.utilities.DefaultNamedCacheFactory
import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.nodeapi.internal.config.User
import net.corda.nodeapi.internal.network.NetworkParametersCopier
@@ -54,9 +54,9 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.driver.TestCorDapp
-import net.corda.testing.internal.stubs.CertificateStoreStubs
import net.corda.testing.internal.rigorousMock
import net.corda.testing.internal.setGlobalSerialization
+import net.corda.testing.internal.stubs.CertificateStoreStubs
import net.corda.testing.internal.testThreadFactory
import net.corda.testing.node.*
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
@@ -279,6 +279,7 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe
open class MockNode(args: MockNodeArgs, cordappLoader: CordappLoader = JarScanningCordappLoader.fromDirectories(args.config.cordappDirectories)) : AbstractNode(
args.config,
TestClock(Clock.systemUTC()),
+ DefaultNamedCacheFactory(),
args.version,
cordappLoader,
args.network.getServerThread(args.id),
@@ -405,8 +406,8 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe
get() = _serializationWhitelists
private var dbCloser: (() -> Any?)? = null
- override fun startDatabase(metricRegistry: MetricRegistry?) {
- super.startDatabase(metricRegistry)
+ override fun startDatabase() {
+ super.startDatabase()
dbCloser = database::close
runOnStop += dbCloser!!
}
diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/performance/Reporter.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/performance/Reporter.kt
index a0a9e48a0f..cbd6cd5a00 100644
--- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/performance/Reporter.kt
+++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/performance/Reporter.kt
@@ -12,12 +12,14 @@ fun startReporter(shutdownManager: ShutdownManager, metricRegistry: MetricRegist
val jmxReporter = thread {
JmxReporter.forRegistry(metricRegistry).inDomain("net.corda").createsObjectNamesWith { _, domain, name ->
// Make the JMX hierarchy a bit better organised.
- val category = name.substringBefore('.')
+ val category = name.substringBefore('.').substringBeforeLast('/')
+ val component = name.substringBefore('.').substringAfterLast('/', "")
val subName = name.substringAfter('.', "")
if (subName == "")
- ObjectName("$domain:name=$category")
+ ObjectName("$domain:name=$category${if (component.isNotEmpty()) ",component=$component," else ""}")
else
- ObjectName("$domain:type=$category,name=$subName")
+ ObjectName("$domain:type=$category,${if (component.isNotEmpty()) "component=$component," else ""}name=$subName")
+
}.build().start()
}
shutdownManager.registerShutdown { jmxReporter.interrupt() }